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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const { version } = require('vite-plus/package.json');

const [expected, packageManager] = process.argv.slice(2);
const agent = process.env.VP_USER_AGENT;
assert.equal(
agent,
expected === 'current' ? `vp/${version}` : expected === 'unset' ? undefined : expected,
);
if (packageManager) {
assert.equal(process.env.npm_config_user_agent?.split('/')[0], packageManager);
}

// A managed node shim and its children must preserve the marker, including an
// inherited version that differs from the installed CLI's version.
const child = spawnSync('node', ['-p', 'process.env.VP_USER_AGENT'], { encoding: 'utf8' });
assert.equal(child.status, 0, child.stderr || child.error?.message);
assert.equal(child.stdout.trim(), String(agent));
console.log(`VP_USER_AGENT=${agent ?? '(unset)'}`);
console.log('Node descendants preserve the marker.');
if (packageManager) {
console.log(`npm_config_user_agent identifies ${packageManager}.`);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const binDir = fileURLToPath(new URL('./bin/', import.meta.resolve('vite-plus/package.json')));
const env = { ...process.env };
const pathKey = Object.keys(env).find((key) => key.toUpperCase() === 'PATH');
const vpExecutables = ['vp', 'vp.exe', 'vp.cmd', 'vp.bat'];
const pathsWithoutVp = env[pathKey]
.split(path.delimiter)
.filter((dir) => !vpExecutables.some((bin) => existsSync(path.join(dir, bin))));
env[pathKey] = [path.dirname(process.execPath), ...pathsWithoutVp].join(path.delimiter);
delete env.VP_CLI_BIN;
// A local invocation must use its own package version, even under another vp.
env.VP_USER_AGENT = 'vp/0.0.0-parent';
env.VP_GLOBAL_VERSION = '0.0.0-parent';
assert.equal(spawnSync('vp', ['--version'], { env }).error?.code, 'ENOENT');

for (const [bin, args] of [
['vp', ['exec', 'node', 'check-agent.cjs', 'current']],
['vpr', ['check-agent']],
['vpx', ['./check-agent.cjs', 'current']],
['vp', ['dlx', '-s', '-p', 'vp-test-echo', '-c', 'node check-agent.cjs current pnpm']],
]) {
const result = spawnSync(process.execPath, [path.join(binDir, bin), ...args], {
env,
encoding: 'utf8',
timeout: 120000,
});
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}\n${result.error ?? ''}`);
assert.match(result.stdout, /Node descendants preserve the marker\./);
console.log(`${bin} ${args[0]} advertises the local version without global vp.`);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "user-agent-fixture",
"private": true,
"scripts": {
"check-agent": "node check-agent.cjs current"
},
"packageManager": "pnpm@11.20.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[[case]]
name = "entrypoints"
vp = ["local", "global"]
env = { VP_USER_AGENT = "vp/0.0.0-parent" }
comment = "Each explicit vp, vpr, or vpx invocation replaces an inherited user agent with its own version."
steps = [
["vp", "exec", "node", "check-agent.cjs", "current"],
["vp", "run", "check-agent"],
["vpr", "check-agent"],
["vpx", "./check-agent.cjs", "current"],
{ argv = ["vp", "dlx", "-s", "-p", "vp-test-echo", "-c", "node check-agent.cjs current pnpm"], comment = "The package manager replaces its own user agent but preserves VP_USER_AGENT." },
["vpx", "-s", "-p", "vp-test-echo", "-c", "node check-agent.cjs current pnpm"],
{ argv = ["vpt", "rm", "package.json"], snapshot = false },
{ argv = ["vp", "dlx", "-s", "-p", "vp-test-echo", "-c", "node check-agent.cjs current pnpm"], comment = "The marker also reaches dlx children outside a project." },
]

[[case]]
name = "local_without_global"
vp = "local"
steps = [
{ argv = ["node", "local-only.mjs"], timeout = 240000 },
]

[[case]]
name = "tool_shims"
vp = "global"
comment = "Ordinary tool shims do not create a user agent and do not erase an inherited one."
steps = [
["node", "check-agent.cjs", "unset"],
["pnpm", "--package", "vp-test-echo", "dlx", "--silent", "-c", "node check-agent.cjs unset pnpm"],
["npm", "exec", "--yes", "--package", "vp-test-echo", "--", "node", "check-agent.cjs", "unset", "npm"],
["npx", "--yes", "--package", "vp-test-echo", "node", "check-agent.cjs", "unset", "npm"],
{ argv = ["node", "check-agent.cjs", "vp/0.0.0-parent"], envs = [["VP_USER_AGENT", "vp/0.0.0-parent"]] },
{ argv = ["pnpm", "--package", "vp-test-echo", "dlx", "--silent", "-c", "node check-agent.cjs vp/0.0.0-parent pnpm"], envs = [["VP_USER_AGENT", "vp/0.0.0-parent"]] },
{ argv = ["npx", "--yes", "--package", "vp-test-echo", "node", "check-agent.cjs", "vp/0.0.0-parent", "npm"], envs = [["VP_USER_AGENT", "vp/0.0.0-parent"]] },
{ argv = ["vp", "check-agent.cjs", "unset"], envs = [["VP_SHIM_TOOL", "node"]], comment = "Windows trampoline-style shim dispatch must also leave the marker unset." },
{ argv = ["vp", "check-agent"], envs = [["VP_SHIM_TOOL", "vpr"]] },
{ argv = ["vp", "./check-agent.cjs", "current"], envs = [["VP_SHIM_TOOL", "vpx"]] },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# entrypoints

Each explicit vp, vpr, or vpx invocation replaces an inherited user agent with its own version.

## `vp exec node check-agent.cjs current`

```
VITE+ - The Unified Toolchain for the Web

VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `vp run check-agent`

```
VITE+ - The Unified Toolchain for the Web

$ node check-agent.cjs current ⊘ cache disabled
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `vpr check-agent`

```
$ node check-agent.cjs current ⊘ cache disabled
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `vpx ./check-agent.cjs current`

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `vp dlx -s -p vp-test-echo -c 'node check-agent.cjs current pnpm'`

The package manager replaces its own user agent but preserves VP_USER_AGENT.

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
npm_config_user_agent identifies pnpm.
```

## `vpx -s -p vp-test-echo -c 'node check-agent.cjs current pnpm'`

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
npm_config_user_agent identifies pnpm.
```

## `vpt rm package.json`


## `vp dlx -s -p vp-test-echo -c 'node check-agent.cjs current pnpm'`

The marker also reaches dlx children outside a project.

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
npm_config_user_agent identifies pnpm.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# entrypoints

Each explicit vp, vpr, or vpx invocation replaces an inherited user agent with its own version.

## `vp exec node check-agent.cjs current`

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `vp run check-agent`

```
$ node check-agent.cjs current ⊘ cache disabled
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `vpr check-agent`

```
$ node check-agent.cjs current ⊘ cache disabled
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `vpx ./check-agent.cjs current`

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `vp dlx -s -p vp-test-echo -c 'node check-agent.cjs current pnpm'`

The package manager replaces its own user agent but preserves VP_USER_AGENT.

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
npm_config_user_agent identifies pnpm.
```

## `vpx -s -p vp-test-echo -c 'node check-agent.cjs current pnpm'`

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
npm_config_user_agent identifies pnpm.
```

## `vpt rm package.json`


## `vp dlx -s -p vp-test-echo -c 'node check-agent.cjs current pnpm'`

The marker also reaches dlx children outside a project.

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
npm_config_user_agent identifies pnpm.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# local_without_global

## `node local-only.mjs`

```
vp exec advertises the local version without global vp.
vpr check-agent advertises the local version without global vp.
vpx ./check-agent.cjs advertises the local version without global vp.
vp dlx advertises the local version without global vp.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# tool_shims

Ordinary tool shims do not create a user agent and do not erase an inherited one.

## `node check-agent.cjs unset`

```
VP_USER_AGENT=(unset)
Node descendants preserve the marker.
```

## `pnpm --package vp-test-echo dlx --silent -c 'node check-agent.cjs unset pnpm'`

```
VP_USER_AGENT=(unset)
Node descendants preserve the marker.
npm_config_user_agent identifies pnpm.
```

## `npm exec --yes --package vp-test-echo -- node check-agent.cjs unset npm`

```
VP_USER_AGENT=(unset)
Node descendants preserve the marker.
npm_config_user_agent identifies npm.
```

## `npx --yes --package vp-test-echo node check-agent.cjs unset npm`

```
VP_USER_AGENT=(unset)
Node descendants preserve the marker.
npm_config_user_agent identifies npm.
```

## `VP_USER_AGENT=vp/0.0.0-parent node check-agent.cjs vp/0.0.0-parent`

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `VP_USER_AGENT=vp/0.0.0-parent pnpm --package vp-test-echo dlx --silent -c 'node check-agent.cjs vp/0.0.0-parent pnpm'`

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
npm_config_user_agent identifies pnpm.
```

## `VP_USER_AGENT=vp/0.0.0-parent npx --yes --package vp-test-echo node check-agent.cjs vp/0.0.0-parent npm`

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
npm_config_user_agent identifies npm.
```

## `VP_SHIM_TOOL=node vp check-agent.cjs unset`

Windows trampoline-style shim dispatch must also leave the marker unset.

```
VP_USER_AGENT=(unset)
Node descendants preserve the marker.
```

## `VP_SHIM_TOOL=vpr vp check-agent`

```
$ node check-agent.cjs current ⊘ cache disabled
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```

## `VP_SHIM_TOOL=vpx vp ./check-agent.cjs current`

```
VP_USER_AGENT=vp/<version>
Node descendants preserve the marker.
```
31 changes: 29 additions & 2 deletions crates/vp_global_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,35 @@ fn is_homebrew_recovery_command(args: &[String]) -> bool {
)
}

#[tokio::main]
async fn main() -> ExitCode {
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().collect();

// Inspect shim mode without clearing its environment markers: Homebrew
// setup still needs them before the normal shim dispatch.
let argv0 = args.first().map(|s| s.as_str()).unwrap_or("vp");
let shim_tool = shim::peek_shim_tool(argv0);

// Identify Vite+ to child tools independently of `npm_config_user_agent`,
// which package managers replace with their own value.
//
// `vpr` and `vpx` are Vite+ entry points too. Ordinary tool shims (such as
// `npm`/`npx`/`node`) only inherit an existing marker from their parent.
//
// SAFETY: `set_var` must run while the process is still single-threaded.
// This runs before the async runtime (and its worker threads) are created.
if matches!(shim_tool.as_deref(), None | Some("vpr" | "vpx")) {
unsafe {
std::env::set_var(
vp_shared::env_vars::VP_USER_AGENT,
concat!("vp/", env!("CARGO_PKG_VERSION")),
);
}
}

tokio::runtime::Runtime::new().expect("failed to build tokio runtime").block_on(run())
}

async fn run() -> ExitCode {
// Probe before tracing, directory resolution, or argument dispatch can emit output.
if let Some(check) = env::var_os(vp_shared::env_vars::VP_SELF_SETUP_SUPPORT_CHECK) {
for line in vp_setup::support::support_response(&check) {
Expand Down
8 changes: 8 additions & 0 deletions crates/vp_shared/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ pub const VP_CLI_BIN: &str = "VP_CLI_BIN";
/// Global CLI version, passed from Rust binary to JS for --version display.
pub const VP_GLOBAL_VERSION: &str = "VP_GLOBAL_VERSION";

/// Advertises that the process tree is running under Vite+ (value: `vp/<version>`).
///
/// Each global or local `vp`, `vpr`, or `vpx` invocation sets its own version.
/// Ordinary tool shims preserve an inherited value without creating one.
/// Tools we invoke (e.g. `vp dlx create-vite`) use this instead of
/// `npm_config_user_agent`, which the underlying package manager overwrites.
pub const VP_USER_AGENT: &str = "VP_USER_AGENT";

// ── HTTP client configuration ───────────────────────────────────────────

/// Override the per-request timeout (in seconds) for large file downloads
Expand Down
Loading
Loading