Skip to content

Commit 5709cdf

Browse files
committed
fix(setup): execute original command after first-start installation
1 parent 2e234c9 commit 5709cdf

5 files changed

Lines changed: 79 additions & 14 deletions

File tree

crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_self_setup/snapshots.toml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,24 @@ steps = [
55
{ argv = ["vpt", "rm", "$VP_HOME/current/bin/.vp-setup-complete"], snapshot = false },
66
{ argv = ["vp", "--help"], envs = [["VP_SELF_SETUP_SUPPORT_CHECK", "1"]], comment = "The capability probe does not perform setup" },
77
["vpt", "stat-file", "$VP_HOME/current/bin/.vp-setup-complete", "--assert", "missing"],
8-
{ argv = ["vp", "not-a-command"], comment = "An unmarked deployed binary consumes the invocation as setup, without parsing the command", snapshot = false },
8+
{ argv = ["vp", "--help"], comment = "An unmarked deployed binary completes setup and executes the requested command", snapshot = false },
99
["vpt", "stat-file", "$VP_HOME/current/bin/.vp-setup-complete", "--assert", "file"],
1010
{ argv = ["vp", "not-a-command"], comment = "Once marked, the binary dispatches commands normally", continue-on-failure = true },
1111
]
1212

13+
[[case]]
14+
name = "command_self_setup_handoff"
15+
vp = "global"
16+
requires = ["bash"]
17+
skip-platforms = ["windows"]
18+
steps = [
19+
{ argv = ["vpt", "mkdir", "-p", "external", "project", "home/js_runtime/node"], snapshot = false },
20+
{ argv = ["vpt", "cp", "-r", "$VP_HOME/js_runtime/node/22.18.0", "home/js_runtime/node/22.18.0"], snapshot = false },
21+
{ argv = ["vpt", "cp", "$VP_HOME/bin/vp", "external/vp"], snapshot = false },
22+
{ argv = ["vpt", "chmod", "+x", "external/vp"], snapshot = false },
23+
{ argv = ["vpt", "pipe-stdin", "original stdin", "--", "bash", "-c", 'exec ./external/vp "$@" 2>setup.log', "--", "-C", "project", "env", "exec", "--node", "22.18.0", "node", "-e", "console.log(JSON.stringify({cwd: require('node:path').basename(process.cwd()), value: process.env.HANDOFF_VALUE, input: require('node:fs').readFileSync(0, 'utf8')})); process.exit(17)"], tty = false, envs = [["VP_HOME", "${workspace}/home"], ["VP_SKIP_DEPS_INSTALL", "1"], ["VP_VERSION", "handoff-test"], ["VP_NODE_MANAGER", "no"], ["VP_SELF_SETUP_NO_MODIFY_PATH", "1"], ["HANDOFF_VALUE", "original environment"]], comment = "The deployed binary executes the original command with its cwd, environment, stdin and exit status", continue-on-failure = true },
24+
]
25+
1326
[[case]]
1427
name = "command_self_setup_retry"
1528
vp = "global"

crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_self_setup/snapshots/command_self_setup.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ vite-plus-self-setup-v1
1717
<home>/.vite-plus/current/bin/.vp-setup-complete: missing
1818
```
1919

20-
## `vp not-a-command`
20+
## `vp --help`
2121

22-
An unmarked deployed binary consumes the invocation as setup, without parsing the command
22+
An unmarked deployed binary completes setup and executes the requested command
2323

2424

2525
## `vpt stat-file $VP_HOME/current/bin/.vp-setup-complete --assert file`
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# command_self_setup_handoff
2+
3+
## `vpt mkdir -p external project home/js_runtime/node`
4+
5+
6+
## `vpt cp -r $VP_HOME/js_runtime/node/22.18.0 home/js_runtime/node/22.18.0`
7+
8+
9+
## `vpt cp $VP_HOME/bin/vp external/vp`
10+
11+
12+
## `vpt chmod +x external/vp`
13+
14+
15+
## `VP_HOME=${workspace}/home VP_SKIP_DEPS_INSTALL=1 VP_VERSION=handoff-test VP_NODE_MANAGER=no VP_SELF_SETUP_NO_MODIFY_PATH=1 HANDOFF_VALUE=original environment vpt pipe-stdin 'original stdin' -- bash -c 'exec ./external/vp "$@" 2>setup.log' -- -C project env exec --node 22.18.0 node -e 'console.log(JSON.stringify({cwd: require('\''node:path'\'').basename(process.cwd()), value: process.env.HANDOFF_VALUE, input: require('\''node:fs'\'').readFileSync(0, '\''utf8'\'')})); process.exit(17)'`
16+
17+
The deployed binary executes the original command with its cwd, environment, stdin and exit status
18+
19+
**Exit code:** 17
20+
21+
```
22+
{"cwd":"project","value":"original environment","input":"original stdin\n"}
23+
```

crates/vp_global_cli/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,8 @@ async fn main() -> ExitCode {
387387
}
388388

389389
match self_setup::maybe_run().await {
390-
Ok(true) => return ExitCode::SUCCESS,
391-
Ok(false) => {}
390+
Ok(Some(code)) => return code,
391+
Ok(None) => {}
392392
Err(error) => {
393393
output::error(&error.to_string());
394394
return ExitCode::FAILURE;

crates/vp_global_cli/src/self_setup.rs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
//! First-start installation. A completed binary accepts commands; an unmarked one only sets up.
1+
//! First-start installation followed by command execution through the deployed binary.
22
33
mod shell;
44

5-
use std::path::Path;
5+
use std::{path::Path, process::ExitCode};
66

77
use dialoguer::{Confirm, theme::ColorfulTheme};
88
use vp_setup::{SELF_SETUP_MARKER, VP_BINARY_NAME, install};
@@ -14,7 +14,7 @@ use crate::{
1414
error::Error,
1515
};
1616

17-
pub(crate) async fn maybe_run() -> Result<bool, Error> {
17+
pub(crate) async fn maybe_run() -> Result<Option<ExitCode>, Error> {
1818
let shell = std::env::var(env_vars::VP_SELF_SETUP_SHELL).ok();
1919
if let Some(shell) = shell.as_deref() {
2020
if !matches!(shell, "sh" | "powershell") {
@@ -32,17 +32,46 @@ pub(crate) async fn maybe_run() -> Result<bool, Error> {
3232
if bin.join(SELF_SETUP_MARKER).try_exists()? {
3333
if let Some(shell) = shell.as_deref() {
3434
print_shell_result(shell);
35-
return Ok(true);
35+
return Ok(Some(ExitCode::SUCCESS));
3636
}
37-
return Ok(false);
37+
return Ok(None);
3838
}
3939

4040
vp_shared::validate_vp_dir_env().map_err(|error| Error::Other(error.to_string().into()))?;
41-
run(&binary).await?;
41+
// Setup diagnostics must not pollute the original command's machine-readable stdout.
42+
output::route_user_output_to_stderr();
43+
let installed_binary = run(&binary).await?;
4244
if let Some(shell) = shell.as_deref() {
4345
print_shell_result(shell);
46+
return Ok(Some(ExitCode::SUCCESS));
47+
}
48+
let mut args = std::env::args_os();
49+
let argv0 = args.next();
50+
let shim_tool =
51+
argv0.as_deref().and_then(|name| name.to_str()).and_then(crate::shim::detect_shim_tool);
52+
if args.len() == 0 && shim_tool.is_none() && std::env::var_os("VP_COMPLETE").is_none() {
53+
return Ok(Some(ExitCode::SUCCESS));
54+
}
55+
// Re-enter through the marked installation, inheriting cwd, environment and stdio.
56+
let mut command = std::process::Command::new(installed_binary.as_path());
57+
command.args(args);
58+
#[cfg(unix)]
59+
{
60+
use std::os::unix::process::CommandExt;
61+
62+
if let Some(argv0) = argv0 {
63+
command.arg0(argv0);
64+
}
65+
Err(command.exec().into())
66+
}
67+
#[cfg(windows)]
68+
{
69+
if let Some(tool) = shim_tool {
70+
command.env(env_vars::VP_SHIM_TOOL, tool);
71+
}
72+
let status = command.status()?;
73+
Ok(Some(ExitCode::from(vp_shared::exit_code_from_status(status) as u8)))
4474
}
45-
Ok(true)
4675
}
4776

4877
// Only successful setup emits executable output; logs use stderr in this mode.
@@ -68,7 +97,7 @@ fn print_shell_result(shell: &str) {
6897
}
6998

7099
/// Setup Vite+ for the first run
71-
async fn run(source: &Path) -> Result<(), Error> {
100+
async fn run(source: &Path) -> Result<AbsolutePathBuf, Error> {
72101
let env = EnvConfig::get();
73102
let dirs = &env.dirs;
74103
let active_binary = dirs.data.join("current").join("bin").join(VP_BINARY_NAME);
@@ -231,7 +260,7 @@ async fn run(source: &Path) -> Result<(), Error> {
231260
// A failure above leaves the marker absent so a later launch can retry.
232261
tokio::fs::write(version_dir.join("bin").join(SELF_SETUP_MARKER), b"").await?;
233262
output::success("Vite+ setup complete.");
234-
Ok(())
263+
Ok(binary)
235264
}
236265

237266
fn interactive() -> bool {

0 commit comments

Comments
 (0)