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,23 @@
const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const { chmodSync, copyFileSync, mkdirSync } = require('node:fs');
const path = require('node:path');

const runtimeDir = path.resolve('custom-runtime');
mkdirSync(runtimeDir);
const runtime = path.join(runtimeDir, process.platform === 'win32' ? 'custom node.exe' : 'custom node');
copyFileSync(process.execPath, runtime);
chmodSync(runtime, 0o755);
const vp = path.join(path.dirname(require.resolve('vite-plus/package.json')), 'bin', 'vp');
const env = { ...process.env };
for (const key of Object.keys(env)) {
if (key.toUpperCase() === 'PATH') delete env[key];
}
env.PATH = runtimeDir;

for (const tool of ['lint', 'fmt']) {
const child = spawnSync(runtime, [vp, tool, '--version'], { env, encoding: 'utf8', timeout: 30000 });
assert.equal(child.status, 0, child.stderr || child.error?.message);
assert.match(child.stdout, /\d+\.\d+\.\d+/);
console.log(`${tool} reused the current runtime without node on PATH`);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "current-node-runtime",
"private": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[[case]]
name = "builtin_tools_without_node_on_path"
vp = "local"
steps = [
{ argv = ["node", "assert-runtime.cjs"], comment = "Built-in tools reuse the current runtime even when its filename is not node and PATH has no node executable." },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# builtin_tools_without_node_on_path

## `node assert-runtime.cjs`

Built-in tools reuse the current runtime even when its filename is not node and PATH has no node executable.

```
lint reused the current runtime without node on PATH
fmt reused the current runtime without node on PATH
```
2 changes: 2 additions & 0 deletions packages/cli/binding/index.d.cts
Original file line number Diff line number Diff line change
Expand Up @@ -3456,6 +3456,8 @@ export interface BatchRewriteResult {

/** Configuration options passed from JavaScript to Rust. */
export interface CliOptions {
/** The current JavaScript runtime (`process.execPath`). */
nodeExecPath: string;
lint: (err: Error | null, arg: JsCommandContext) => Promise<JsCommandResolvedResult>;
fmt: (err: Error | null, arg: JsCommandContext) => Promise<JsCommandResolvedResult>;
vite: (err: Error | null, arg: JsCommandContext) => Promise<JsCommandResolvedResult>;
Expand Down
68 changes: 60 additions & 8 deletions packages/cli/binding/src/cli/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl SubcommandResolver {
}

Ok(ResolvedSubcommand {
program: Arc::from(OsStr::new("node")),
program: Arc::clone(&cli_options.node_exec_path),
args: iter::once(Str::from("--disable-warning=MODULE_TYPELESS_PACKAGE_JSON"))
.chain(iter::once(Str::from(js_path_str)))
.chain(args.into_iter().map(Str::from))
Expand Down Expand Up @@ -130,7 +130,7 @@ impl SubcommandResolver {
}

Ok(ResolvedSubcommand {
program: Arc::from(OsStr::new("node")),
program: Arc::clone(&cli_options.node_exec_path),
args: iter::once(Str::from(js_path_str))
.chain(args.into_iter().map(Str::from))
.collect(),
Expand All @@ -152,7 +152,7 @@ impl SubcommandResolver {
.ok_or_else(|| anyhow::anyhow!("vite JS path is not valid UTF-8"))?;

Ok(ResolvedSubcommand {
program: Arc::from(OsStr::new("node")),
program: Arc::clone(&cli_options.node_exec_path),
args: iter::once(Str::from(js_path_str))
.chain(iter::once(Str::from("build")))
.chain(args.into_iter().map(Str::from))
Expand Down Expand Up @@ -187,7 +187,7 @@ impl SubcommandResolver {
};

Ok(ResolvedSubcommand {
program: Arc::from(OsStr::new("node")),
program: Arc::clone(&cli_options.node_exec_path),
args: iter::once(Str::from(js_path_str)).chain(vitest_args).collect(),
cache_config: UserCacheConfig::with_config(EnabledCacheConfig {
env: None,
Expand All @@ -213,7 +213,7 @@ impl SubcommandResolver {
.ok_or_else(|| anyhow::anyhow!("pack JS path is not valid UTF-8"))?;

Ok(ResolvedSubcommand {
program: Arc::from(OsStr::new("node")),
program: Arc::clone(&cli_options.node_exec_path),
args: iter::once(Str::from(js_path_str))
.chain(args.into_iter().map(Str::from))
.collect(),
Expand All @@ -235,7 +235,7 @@ impl SubcommandResolver {
.ok_or_else(|| anyhow::anyhow!("vite JS path is not valid UTF-8"))?;

Ok(ResolvedSubcommand {
program: Arc::from(OsStr::new("node")),
program: Arc::clone(&cli_options.node_exec_path),
args: iter::once(Str::from(js_path_str))
.chain(iter::once(Str::from("dev")))
.chain(args.into_iter().map(Str::from))
Expand All @@ -253,7 +253,7 @@ impl SubcommandResolver {
.ok_or_else(|| anyhow::anyhow!("vite JS path is not valid UTF-8"))?;

Ok(ResolvedSubcommand {
program: Arc::from(OsStr::new("node")),
program: Arc::clone(&cli_options.node_exec_path),
args: iter::once(Str::from(js_path_str))
.chain(iter::once(Str::from("preview")))
.chain(args.into_iter().map(Str::from))
Expand All @@ -271,7 +271,7 @@ impl SubcommandResolver {
.ok_or_else(|| anyhow::anyhow!("doc JS path is not valid UTF-8"))?;

Ok(ResolvedSubcommand {
program: Arc::from(OsStr::new("node")),
program: Arc::clone(&cli_options.node_exec_path),
args: iter::once(Str::from(js_path_str))
.chain(args.into_iter().map(Str::from))
.collect(),
Expand Down Expand Up @@ -343,3 +343,55 @@ fn merge_resolved_envs_with_version(
.or_insert_with(|| Arc::from(OsStr::new(env!("CARGO_PKG_VERSION"))));
merged
}

#[cfg(test)]
mod tests {
use vt_path::AbsolutePathBuf;

use super::*;
use crate::cli::types::{BoxedResolverFn, ResolveCommandResult};

fn tool_resolver() -> BoxedResolverFn {
Box::new(|_, _| {
Box::pin(async {
Ok(ResolveCommandResult {
bin_path: Arc::from(OsStr::new("tool.js")),
envs: Vec::new(),
})
})
})
}

#[tokio::test]
async fn builtins_reuse_the_calling_node_runtime() {
let temp = tempfile::tempdir().unwrap();
let cwd = AbsolutePathBuf::new(temp.path().to_path_buf()).unwrap();
let runtime: Arc<OsStr> = Arc::from(cwd.join("custom runtime").as_path().as_os_str());
let resolver = SubcommandResolver::new(cwd.clone().into()).with_cli_options(CliOptions {
node_exec_path: Arc::clone(&runtime),
lint: tool_resolver(),
fmt: tool_resolver(),
vite: tool_resolver(),
test: tool_resolver(),
pack: tool_resolver(),
doc: tool_resolver(),
toolchain_manifest_path: String::new(),
vite_plus_package_path: String::new(),
resolve_universal_vite_config: Arc::new(|_| Box::pin(async { Ok("{}".to_string()) })),
});
let envs = Arc::new(FxHashMap::default());
for command in [
SynthesizableSubcommand::Lint { args: vec![] },
SynthesizableSubcommand::Fmt { args: vec![] },
SynthesizableSubcommand::Build { args: vec![] },
SynthesizableSubcommand::Test { args: vec![] },
SynthesizableSubcommand::Pack { args: vec![] },
SynthesizableSubcommand::Dev { args: vec![] },
SynthesizableSubcommand::Preview { args: vec![] },
SynthesizableSubcommand::Doc { args: vec![] },
] {
let resolved = resolver.resolve(command, None, &envs, &cwd).await.unwrap();
assert_eq!(resolved.program, runtime);
}
}
}
1 change: 1 addition & 0 deletions packages/cli/binding/src/cli/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ pub type ViteConfigResolverFn = Arc<

/// CLI options containing JavaScript resolver functions (using boxed futures for simplicity)
pub struct CliOptions {
pub node_exec_path: Arc<OsStr>,
pub lint: BoxedResolverFn,
pub fmt: BoxedResolverFn,
pub vite: BoxedResolverFn,
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/binding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub fn ensure_blocking_stdio() {
/// Configuration options passed from JavaScript to Rust.
#[napi(object, object_to_js = false)]
pub struct CliOptions {
/// The current JavaScript runtime (`process.execPath`).
pub node_exec_path: String,
pub lint: Arc<ThreadsafeFunction<JsCommandContext, Promise<JsCommandResolvedResult>>>,
pub fmt: Arc<ThreadsafeFunction<JsCommandContext, Promise<JsCommandResolvedResult>>>,
pub vite: Arc<ThreadsafeFunction<JsCommandContext, Promise<JsCommandResolvedResult>>>,
Expand Down Expand Up @@ -199,6 +201,7 @@ pub async fn run(options: CliOptions) -> Result<i32> {
let explicit_chdir = options.explicit_chdir.unwrap_or(false);
let toolchain_manifest_path = options.toolchain_manifest_path;
let vite_plus_package_path = options.vite_plus_package_path;
let node_exec_path = Arc::from(OsStr::new(&options.node_exec_path));

// Create a channel to receive the result from the worker thread
let (tx, rx) = tokio::sync::oneshot::channel();
Expand All @@ -209,6 +212,7 @@ pub async fn run(options: CliOptions) -> Result<i32> {
std::thread::spawn(move || {
// Create the resolvers inside the thread (BoxedResolverFn is not Send)
let cli_options = ViteTaskCliOptions {
node_exec_path,
lint: create_resolver(lint_tsf, "Failed to resolve lint command"),
fmt: create_resolver(fmt_tsf, "Failed to resolve fmt command"),
vite: create_resolver(vite_tsf, "Failed to resolve vite command"),
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ if (maybePrintCommandHelp(args)) {
}

const exitCode = await run({
nodeExecPath: process.execPath,
lint,
pack,
fmt,
Expand Down
Loading