From ef4a6acab26aaa3945dc45ba7c6966aec61b07fc Mon Sep 17 00:00:00 2001 From: MK Date: Sat, 28 Mar 2026 22:33:24 +0800 Subject: [PATCH 1/4] fix: stop intercepting `vite` as a `vp` command in task scripts `VitePlusCommandHandler` was intercepting both `vp` and `vite` program names in task scripts. This caused `vp run dev` to fail with "Invalid vite task command" when a package.json script used bare `vite` (e.g. `"dev": "vite"`), because `vite` is a separate tool, not a vp alias. Only intercept `vp` commands now; `vite` runs verbatim as an external process. Closes #1176 --- packages/cli/binding/src/cli.rs | 10 +++------ .../package.json | 10 +++++++++ .../setup-bin.js | 8 +++++++ .../command-run-script-vite-program/snap.txt | 21 +++++++++++++++++++ .../steps.json | 8 +++++++ 5 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 packages/cli/snap-tests-global/command-run-script-vite-program/package.json create mode 100644 packages/cli/snap-tests-global/command-run-script-vite-program/setup-bin.js create mode 100644 packages/cli/snap-tests-global/command-run-script-vite-program/snap.txt create mode 100644 packages/cli/snap-tests-global/command-run-script-vite-program/steps.json diff --git a/packages/cli/binding/src/cli.rs b/packages/cli/binding/src/cli.rs index beae924d1c..1db80095c1 100644 --- a/packages/cli/binding/src/cli.rs +++ b/packages/cli/binding/src/cli.rs @@ -551,16 +551,12 @@ impl CommandHandler for VitePlusCommandHandler { &mut self, command: &mut ScriptCommand, ) -> anyhow::Result { - // Intercept both "vp" and "vite" commands in task scripts. - // "vp" is the conventional alias used in vite-plus task configs. - // "vite" must also be intercepted so that `vite test`, `vite build`, etc. - // in task scripts are synthesized in-session rather than spawning a new CLI process. + // Only intercept "vp" commands in task scripts. + // "vite" is a separate tool and should always run verbatim. let program = command.program.as_str(); - if program != "vp" && program != "vite" { + if program != "vp" { return Ok(HandledCommand::Verbatim); } - // Parse "vp " using CLIArgs — always use "vp" as the program name - // so clap shows "Usage: vp ..." even if the original command was "vite ..." let cli_args = match CLIArgs::try_parse_from( iter::once("vp").chain(command.args.iter().map(Str::as_str)), ) { diff --git a/packages/cli/snap-tests-global/command-run-script-vite-program/package.json b/packages/cli/snap-tests-global/command-run-script-vite-program/package.json new file mode 100644 index 0000000000..973009ab8a --- /dev/null +++ b/packages/cli/snap-tests-global/command-run-script-vite-program/package.json @@ -0,0 +1,10 @@ +{ + "name": "command-run-script-vite-program", + "version": "1.0.0", + "scripts": { + "dev": "vite", + "dev-help": "vite -h", + "dev-version": "vite --version" + }, + "packageManager": "pnpm@10.19.0" +} diff --git a/packages/cli/snap-tests-global/command-run-script-vite-program/setup-bin.js b/packages/cli/snap-tests-global/command-run-script-vite-program/setup-bin.js new file mode 100644 index 0000000000..60f70aae3e --- /dev/null +++ b/packages/cli/snap-tests-global/command-run-script-vite-program/setup-bin.js @@ -0,0 +1,8 @@ +const fs = require('fs'); +fs.mkdirSync('node_modules/.bin', { recursive: true }); +fs.writeFileSync( + 'node_modules/.bin/vite', + '#!/usr/bin/env node\nconst args = process.argv.slice(2);\nconsole.log(args.length ? "vite " + args.join(" ") : "vite");\n', + { mode: 0o755 }, +); +fs.writeFileSync('node_modules/.bin/vite.cmd', '@node "%~dp0\\vite" %*\n'); diff --git a/packages/cli/snap-tests-global/command-run-script-vite-program/snap.txt b/packages/cli/snap-tests-global/command-run-script-vite-program/snap.txt new file mode 100644 index 0000000000..f7a8868e56 --- /dev/null +++ b/packages/cli/snap-tests-global/command-run-script-vite-program/snap.txt @@ -0,0 +1,21 @@ +> node setup-bin.js +> vp run dev # should run vite binary, not parse as vp subcommand +VITE+ - The Unified Toolchain for the Web + +$ vite ⊘ cache disabled +vite + + +> vp run dev-help # should run vite -h, not parse as vp subcommand +VITE+ - The Unified Toolchain for the Web + +$ vite -h ⊘ cache disabled +vite -h + + +> vp run dev-version # should run vite --version, not parse as vp subcommand +VITE+ - The Unified Toolchain for the Web + +$ vite --version ⊘ cache disabled +vite --version + diff --git a/packages/cli/snap-tests-global/command-run-script-vite-program/steps.json b/packages/cli/snap-tests-global/command-run-script-vite-program/steps.json new file mode 100644 index 0000000000..de312a699f --- /dev/null +++ b/packages/cli/snap-tests-global/command-run-script-vite-program/steps.json @@ -0,0 +1,8 @@ +{ + "commands": [ + { "command": "node setup-bin.js", "ignoreOutput": true }, + "vp run dev # should run vite binary, not parse as vp subcommand", + "vp run dev-help # should run vite -h, not parse as vp subcommand", + "vp run dev-version # should run vite --version, not parse as vp subcommand" + ] +} From 110f3c92754529a0d441faba0416746e6c40e411 Mon Sep 17 00:00:00 2001 From: MK Date: Sat, 28 Mar 2026 22:38:02 +0800 Subject: [PATCH 2/4] fix: update comment to explain why vp commands are intercepted Closes #1176 --- packages/cli/binding/src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/binding/src/cli.rs b/packages/cli/binding/src/cli.rs index 1db80095c1..8cfdc7dc57 100644 --- a/packages/cli/binding/src/cli.rs +++ b/packages/cli/binding/src/cli.rs @@ -551,8 +551,8 @@ impl CommandHandler for VitePlusCommandHandler { &mut self, command: &mut ScriptCommand, ) -> anyhow::Result { - // Only intercept "vp" commands in task scripts. - // "vite" is a separate tool and should always run verbatim. + // Intercept "vp" commands in task scripts so that `vp test`, `vp build`, etc. + // are synthesized in-session rather than spawning a new CLI process. let program = command.program.as_str(); if program != "vp" { return Ok(HandledCommand::Verbatim); From e9ef42c80b9efb40448b59669506951006dddd5b Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 30 Mar 2026 17:45:57 +0800 Subject: [PATCH 3/4] feat: intercept `vpr` commands in task scripts for in-session synthesis `vpr` in package.json scripts (e.g. `"ready": "vpr hello && vpr dev"`) is now expanded to `vp run` and synthesized in-session, avoiding unnecessary process spawns. Closes #1176 --- packages/cli/binding/src/cli.rs | 13 +++++++++---- .../package.json | 5 ++++- .../command-run-script-vite-program/snap.txt | 19 +++++++++++++++++++ .../steps.json | 4 +++- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/cli/binding/src/cli.rs b/packages/cli/binding/src/cli.rs index 8cfdc7dc57..35a92f73cb 100644 --- a/packages/cli/binding/src/cli.rs +++ b/packages/cli/binding/src/cli.rs @@ -551,14 +551,19 @@ impl CommandHandler for VitePlusCommandHandler { &mut self, command: &mut ScriptCommand, ) -> anyhow::Result { - // Intercept "vp" commands in task scripts so that `vp test`, `vp build`, etc. - // are synthesized in-session rather than spawning a new CLI process. + // Intercept "vp" and "vpr" commands in task scripts so that `vp test`, `vp build`, + // `vpr build`, etc. are synthesized in-session rather than spawning a new CLI process. let program = command.program.as_str(); - if program != "vp" { + if program != "vp" && program != "vpr" { return Ok(HandledCommand::Verbatim); } + // "vpr " is shorthand for "vp run ", so prepend "run" for parsing. let cli_args = match CLIArgs::try_parse_from( - iter::once("vp").chain(command.args.iter().map(Str::as_str)), + iter::once("vp").chain( + if program == "vpr" { Some("run") } else { None } + .into_iter() + .chain(command.args.iter().map(Str::as_str)), + ), ) { Ok(args) => args, Err(err) if err.kind() == ErrorKind::InvalidSubcommand => { diff --git a/packages/cli/snap-tests-global/command-run-script-vite-program/package.json b/packages/cli/snap-tests-global/command-run-script-vite-program/package.json index 973009ab8a..159685e51e 100644 --- a/packages/cli/snap-tests-global/command-run-script-vite-program/package.json +++ b/packages/cli/snap-tests-global/command-run-script-vite-program/package.json @@ -4,7 +4,10 @@ "scripts": { "dev": "vite", "dev-help": "vite -h", - "dev-version": "vite --version" + "dev-version": "vite --version", + "hello": "echo hello from script", + "hello-vpr": "vpr hello", + "ready": "vpr hello-vpr && vpr dev-version" }, "packageManager": "pnpm@10.19.0" } diff --git a/packages/cli/snap-tests-global/command-run-script-vite-program/snap.txt b/packages/cli/snap-tests-global/command-run-script-vite-program/snap.txt index f7a8868e56..41eb5dd326 100644 --- a/packages/cli/snap-tests-global/command-run-script-vite-program/snap.txt +++ b/packages/cli/snap-tests-global/command-run-script-vite-program/snap.txt @@ -19,3 +19,22 @@ VITE+ - The Unified Toolchain for the Web $ vite --version ⊘ cache disabled vite --version + +> vp run hello-vpr # vpr in script should be synthesized in-session +VITE+ - The Unified Toolchain for the Web + +$ echo hello from script ⊘ cache disabled +hello from script + + +> vp run ready # chained vpr commands should both be synthesized +VITE+ - The Unified Toolchain for the Web + +$ echo hello from script ⊘ cache disabled +hello from script + +$ vite --version ⊘ cache disabled +vite --version + +--- +vp run: 0/2 cache hit (0%). (Run `vp run --last-details` for full details) diff --git a/packages/cli/snap-tests-global/command-run-script-vite-program/steps.json b/packages/cli/snap-tests-global/command-run-script-vite-program/steps.json index de312a699f..c1b00c94c1 100644 --- a/packages/cli/snap-tests-global/command-run-script-vite-program/steps.json +++ b/packages/cli/snap-tests-global/command-run-script-vite-program/steps.json @@ -3,6 +3,8 @@ { "command": "node setup-bin.js", "ignoreOutput": true }, "vp run dev # should run vite binary, not parse as vp subcommand", "vp run dev-help # should run vite -h, not parse as vp subcommand", - "vp run dev-version # should run vite --version, not parse as vp subcommand" + "vp run dev-version # should run vite --version, not parse as vp subcommand", + "vp run hello-vpr # vpr in script should be synthesized in-session", + "vp run ready # chained vpr commands should both be synthesized" ] } From e60358aa1adaf5cbff125ca63e144a0d7d36ce49 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 30 Mar 2026 17:50:17 +0800 Subject: [PATCH 4/4] refactor: simplify vpr iterator construction with bool::then_some --- packages/cli/binding/src/cli.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/cli/binding/src/cli.rs b/packages/cli/binding/src/cli.rs index 35a92f73cb..4295dfc9b2 100644 --- a/packages/cli/binding/src/cli.rs +++ b/packages/cli/binding/src/cli.rs @@ -558,12 +558,11 @@ impl CommandHandler for VitePlusCommandHandler { return Ok(HandledCommand::Verbatim); } // "vpr " is shorthand for "vp run ", so prepend "run" for parsing. + let is_vpr = program == "vpr"; let cli_args = match CLIArgs::try_parse_from( - iter::once("vp").chain( - if program == "vpr" { Some("run") } else { None } - .into_iter() - .chain(command.args.iter().map(Str::as_str)), - ), + iter::once("vp") + .chain(is_vpr.then_some("run")) + .chain(command.args.iter().map(Str::as_str)), ) { Ok(args) => args, Err(err) if err.kind() == ErrorKind::InvalidSubcommand => {