diff --git a/crates/vite_global_cli/src/help.rs b/crates/vite_global_cli/src/help.rs index e3e1e956f8..55767b0d56 100644 --- a/crates/vite_global_cli/src/help.rs +++ b/crates/vite_global_cli/src/help.rs @@ -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"), ], diff --git a/crates/vite_global_cli/src/main.rs b/crates/vite_global_cli/src/main.rs index f339b037f4..aa33495fcd 100644 --- a/crates/vite_global_cli/src/main.rs +++ b/crates/vite_global_cli/src/main.rs @@ -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) -> Vec { match args.get(1).map(String::as_str) { // `vp list ...` → `vp pm list ...` @@ -60,6 +61,16 @@ fn normalize_args(args: Vec) -> Vec { 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, } @@ -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 { + 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()]) diff --git a/docs/guide/env.md b/docs/guide/env.md index 4807b0d84c..8b2ad88355 100644 --- a/docs/guide/env.md +++ b/docs/guide/env.md @@ -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 @@ -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 diff --git a/packages/cli/snap-tests-global/cli-helper-message/snap.txt b/packages/cli/snap-tests-global/cli-helper-message/snap.txt index 33f1dd12fc..b4f36cc96c 100644 --- a/packages/cli/snap-tests-global/cli-helper-message/snap.txt +++ b/packages/cli/snap-tests-global/cli-helper-message/snap.txt @@ -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 diff --git a/packages/cli/snap-tests-global/command-node/package.json b/packages/cli/snap-tests-global/command-node/package.json new file mode 100644 index 0000000000..467e344135 --- /dev/null +++ b/packages/cli/snap-tests-global/command-node/package.json @@ -0,0 +1,8 @@ +{ + "name": "command-node", + "version": "1.0.0", + "private": true, + "engines": { + "node": "20.18.0" + } +} diff --git a/packages/cli/snap-tests-global/command-node/script.js b/packages/cli/snap-tests-global/command-node/script.js new file mode 100644 index 0000000000..89bb85cd97 --- /dev/null +++ b/packages/cli/snap-tests-global/command-node/script.js @@ -0,0 +1,3 @@ +const [, , ...rest] = process.argv; +console.log('node version:', process.version); +console.log('script args:', JSON.stringify(rest)); diff --git a/packages/cli/snap-tests-global/command-node/snap.txt b/packages/cli/snap-tests-global/command-node/snap.txt new file mode 100644 index 0000000000..71a7cb42c0 --- /dev/null +++ b/packages/cli/snap-tests-global/command-node/snap.txt @@ -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 +script args: [] + +> vp node script.js foo bar --flag # Forward script args to the local file +node version: v +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 diff --git a/packages/cli/snap-tests-global/command-node/steps.json b/packages/cli/snap-tests-global/command-node/steps.json new file mode 100644 index 0000000000..b8d4737dc8 --- /dev/null +++ b/packages/cli/snap-tests-global/command-node/steps.json @@ -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" + ] +}