Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/vite_global_cli/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ pub fn top_level_help_doc() -> HelpDoc {
vec![
row("run", "Run tasks (also available as standalone `vpr`)"),
row("exec", "Execute a command from local node_modules/.bin"),
row("node", "Run a Node.js script (shorthand for `env exec node`)"),
row("dlx", "Execute a package binary without installing it as a dependency"),
row("cache", "Manage the task cache"),
],
Expand Down
31 changes: 30 additions & 1 deletion crates/vite_global_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use crate::cli::{
/// Normalize CLI arguments:
/// - `vp list ...` / `vp ls ...` → `vp pm list ...`
/// - `vp help [command]` → `vp [command] --help`
/// - `vp node [args...]` → `vp env exec node [args...]`
fn normalize_args(args: Vec<String>) -> Vec<String> {
match args.get(1).map(String::as_str) {
// `vp list ...` → `vp pm list ...`
Expand All @@ -60,6 +61,16 @@ fn normalize_args(args: Vec<String>) -> Vec<String> {
normalized.extend(args[3..].iter().cloned());
normalized
}
// `vp node [args...]` → `vp env exec node [args...]`
Some("node") => {
let mut normalized = Vec::with_capacity(args.len() + 2);
normalized.push(args[0].clone());
normalized.push("env".to_string());
normalized.push("exec".to_string());
normalized.push("node".to_string());
normalized.extend(args[2..].iter().cloned());
normalized
}
// No transformation needed
_ => args,
}
Expand Down Expand Up @@ -402,9 +413,27 @@ mod tests {

use super::{
extract_unknown_argument, has_pass_as_value_suggestion, is_affirmative_response,
replace_top_level_typoed_subcommand, try_parse_args_from,
normalize_args, replace_top_level_typoed_subcommand, try_parse_args_from,
};

fn s(v: &[&str]) -> Vec<String> {
v.iter().map(|s| s.to_string()).collect()
}

#[test]
fn normalize_args_rewrites_vp_node_to_env_exec_node() {
let input = s(&["vp", "node", "script.js", "foo", "--flag"]);
let normalized = normalize_args(input);
assert_eq!(normalized, s(&["vp", "env", "exec", "node", "script.js", "foo", "--flag"]));
}

#[test]
fn normalize_args_rewrites_bare_vp_node() {
let input = s(&["vp", "node"]);
let normalized = normalize_args(input);
assert_eq!(normalized, s(&["vp", "env", "exec", "node"]));
}

#[test]
fn unknown_argument_detected_without_pass_as_value_hint() {
let error = try_parse_args_from(["vp".to_string(), "--cache".to_string()])
Expand Down
3 changes: 3 additions & 0 deletions docs/guide/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ This switches to system-first mode, where the shims prefer your system Node.js a
- `vp env install` installs a Node.js version
- `vp env uninstall` removes an installed Node.js version
- `vp env exec` runs a command with a specific Node.js version
- `vp node` runs a Node.js script — shorthand for `vp env exec node`

### Inspect

Expand Down Expand Up @@ -81,6 +82,8 @@ vp env list-remote --lts # List only LTS versions
# Execute
vp env exec --node lts npm i # Execute npm with latest LTS
vp env exec node -v # Use shim mode with automatic version resolution
vp node script.js # Shorthand: run a Node.js script with the resolved version
vp node -e "console.log(1+1)" # Shorthand: forward any node flag or argument
```

## Custom Node.js Mirror
Expand Down
1 change: 1 addition & 0 deletions packages/cli/snap-tests-global/cli-helper-message/snap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Develop:
Execute:
run Run tasks (also available as standalone `vpr`)
exec Execute a command from local node_modules/.bin
node Run a Node.js script (shorthand for `env exec node`)
dlx Execute a package binary without installing it as a dependency
cache Manage the task cache

Expand Down
8 changes: 8 additions & 0 deletions packages/cli/snap-tests-global/command-node/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "command-node",
"version": "1.0.0",
"private": true,
"engines": {
"node": "20.18.0"
}
}
3 changes: 3 additions & 0 deletions packages/cli/snap-tests-global/command-node/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const [, , ...rest] = process.argv;
console.log('node version:', process.version);
console.log('script args:', JSON.stringify(rest));
16 changes: 16 additions & 0 deletions packages/cli/snap-tests-global/command-node/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
> vp node -v # Shorthand: version resolved from package.json engines.node
v20.18.0

> vp node script.js # Execute a local JS file (primary use case)
node version: v<semver>
script args: []

> vp node script.js foo bar --flag # Forward script args to the local file
node version: v<semver>
script args: ["foo","bar","--flag"]

> vp node -e "console.log('Hello from vp node')" # Inline script via -e
Hello from vp node

> vp env exec node -v # Equivalence check: same output as shorthand
v20.18.0
11 changes: 11 additions & 0 deletions packages/cli/snap-tests-global/command-node/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"env": {},
"ignoredPlatforms": [],
"commands": [
"vp node -v # Shorthand: version resolved from package.json engines.node",
"vp node script.js # Execute a local JS file (primary use case)",
"vp node script.js foo bar --flag # Forward script args to the local file",
"vp node -e \"console.log('Hello from vp node')\" # Inline script via -e",
"vp env exec node -v # Equivalence check: same output as shorthand"
]
}
Loading