diff --git a/crates/vite_global_cli/src/error.rs b/crates/vite_global_cli/src/error.rs index 00d999d2d3..769310d727 100644 --- a/crates/vite_global_cli/src/error.rs +++ b/crates/vite_global_cli/src/error.rs @@ -54,4 +54,14 @@ pub enum Error { #[error("{0}")] Setup(#[from] vite_setup::error::Error), + + #[error( + "Node.js {version} is incompatible with Vite+ CLI.\nRequired by Vite+: {requirement}{version_source}\n\n{help}" + )] + NodeVersionIncompatible { + version: String, + requirement: String, + version_source: String, + help: String, + }, } diff --git a/crates/vite_global_cli/src/js_executor.rs b/crates/vite_global_cli/src/js_executor.rs index 6e15c41cf9..6ebaa3e19c 100644 --- a/crates/vite_global_cli/src/js_executor.rs +++ b/crates/vite_global_cli/src/js_executor.rs @@ -5,16 +5,21 @@ use std::process::{ExitStatus, Output}; +use node_semver::{Range, Version}; use tokio::process::Command; use vite_js_runtime::{ JsRuntime, JsRuntimeType, download_runtime, download_runtime_for_project, is_valid_version, read_package_json, resolve_node_version, }; use vite_path::{AbsolutePath, AbsolutePathBuf}; -use vite_shared::{PrependOptions, PrependResult, env_vars, format_path_with_prepend}; +use vite_shared::{ + PrependOptions, PrependResult, + env_vars::{self, VP_NODE_VERSION}, + format_path_with_prepend, +}; use crate::{ - commands::env::config::{self, ShimMode}, + commands::env::config::{self, SESSION_VERSION_FILE, ShimMode}, error::Error, shim, }; @@ -112,6 +117,16 @@ impl JsExecutor { cmd } + /// Read the `engines.node` requirement from the CLI's own `package.json`. + /// + /// Returns `None` when the file is missing, unreadable, or has no `engines.node`. + async fn get_cli_engines_requirement(&self) -> Option { + let cli_dir = self.get_cli_package_dir().ok()?; + let pkg_path = cli_dir.join("package.json"); + let pkg = read_package_json(&pkg_path).await.ok()??; + pkg.engines?.node.map(|s| s.to_string()) + } + /// Get the CLI's package.json directory (parent of `scripts_dir`). /// /// This is used for resolving the CLI's default Node.js version @@ -140,7 +155,7 @@ impl JsExecutor { let cli_dir = self.get_cli_package_dir()?; tracing::debug!("Resolving CLI runtime from {:?}", cli_dir); - let runtime = download_runtime_for_project(&cli_dir).await?; + let runtime = download_runtime_for_project(&cli_dir).await?.0; self.cli_runtime = Some(runtime); } Ok(self.cli_runtime.as_ref().unwrap()) @@ -167,14 +182,25 @@ impl JsExecutor { } // 1–2. Session overrides: env var (from `vp env use`), then file - let session_version = vite_shared::EnvConfig::get() + let session_version = if let Some(session_version) = vite_shared::EnvConfig::get() .node_version .map(|v| v.trim().to_string()) - .filter(|v| !v.is_empty()); - let session_version = if session_version.is_some() { - session_version + .filter(|v| !v.is_empty()) + { + self.check_runtime_compatibility(&session_version, Some(VP_NODE_VERSION), false) + .await?; + Some(session_version) + } else if let Some(session_version) = config::read_session_version().await { + // Read from file + self.check_runtime_compatibility( + &session_version, + Some(SESSION_VERSION_FILE), + false, + ) + .await?; + Some(session_version) } else { - config::read_session_version().await + None }; if let Some(version) = session_version { let runtime = download_runtime(JsRuntimeType::Node, &version).await?; @@ -192,10 +218,23 @@ impl JsExecutor { // At least one valid project source exists — delegate to // download_runtime_for_project for cache-aware range resolution // and intra-project fallback chain - download_runtime_for_project(project_path).await? + let (runtime, source) = download_runtime_for_project(project_path).await?; + self.check_runtime_compatibility( + &runtime.version, + source.map(|s| format!("{s}")).as_deref(), + true, + ) + .await?; + runtime } else { // No valid project source — check user default from config, then LTS let resolution = config::resolve_version(project_path).await?; + self.check_runtime_compatibility( + &resolution.version, + Some(&resolution.source), + false, + ) + .await?; download_runtime(JsRuntimeType::Node, &resolution.version).await? }; self.project_runtime = Some(runtime); @@ -203,6 +242,58 @@ impl JsExecutor { Ok(self.project_runtime.as_ref().unwrap()) } + /// Check that a runtime's version satisfies vp's engine requirements. + /// + /// Skips silently when: + /// - The runtime is a system install (version == `"system"`) + /// - The version or requirement strings cannot be parsed as semver + /// + /// Returns [`Error::NodeVersionIncompatible`] when the version is parsable but + /// outside the required range. + async fn check_runtime_compatibility( + &self, + version: &str, + source: Option<&str>, + is_project_runtime: bool, + ) -> Result<(), Error> { + let Some(requirement) = self.get_cli_engines_requirement().await else { return Ok(()) }; + + // System runtimes report "system" — we cannot inspect the actual version cheaply, + // and the user has explicitly opted in via `vp env off`. + if version == "system" { + return Ok(()); + } + + let normalized = version.strip_prefix('v').unwrap_or(version); + let Ok(version) = Version::parse(normalized) else { + return Ok(()); // unparsable version — skip silently + }; + let Ok(range) = Range::parse(&requirement) else { + return Ok(()); // invalid range in package.json — skip silently + }; + + if !range.satisfies(&version) { + let version_source = + source.map(|s| format!("\nResolved from: {s}\n")).unwrap_or_default(); + + let help = (if is_project_runtime { + "Fix this project: vp env pin lts" + } else { + "Set a compatible version globally: vp env default lts" + }) + .to_owned(); + let help = format!("{help}\nTemporary override: vp env use lts"); + + return Err(Error::NodeVersionIncompatible { + version: version.to_string(), + requirement: requirement.to_string(), + version_source, + help, + }); + } + Ok(()) + } + /// Download a specific Node.js version. /// /// This is used when we need a specific version regardless of @@ -474,6 +565,45 @@ mod tests { assert_eq!(cmd.as_std().get_program(), OsStr::new(expected_program)); } + /// Pin Node.js to 20.0.0 + /// and any vp command should be blocked with a clear error instead of crashing + #[tokio::test] + async fn incompatible_node_version_should_be_blocked() { + use tempfile::TempDir; + use vite_shared::EnvConfig; + + // Point scripts_dir at the real packages/cli/dist so that + // get_cli_engines_requirement() reads the actual engines.node from + // packages/cli/package.json. The dist/ directory need not exist — only + // its parent (packages/cli/) and the package.json within it are read. + let scripts_dir = AbsolutePathBuf::new( + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../packages/cli/dist"), + ) + .unwrap(); + + // Use any existing directory as project_path; the session override + // fires before any project-source lookup or network download. + let temp_dir = TempDir::new().unwrap(); + let project_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Simulate `.node-version: 20.0.0` / `vp env use 20.0.0` via a session override. + let _guard = EnvConfig::test_guard(EnvConfig { + node_version: Some("20.0.0".to_string()), + ..EnvConfig::for_test() + }); + + let mut executor = JsExecutor::new(Some(scripts_dir)); + let err = executor + .ensure_project_runtime(&project_path) + .await + .expect_err("Node.js 20.0.0 should be rejected as incompatible with vp requirements"); + + assert!( + matches!(&err, Error::NodeVersionIncompatible { version, .. } if version == "20.0.0"), + "expected NodeVersionIncompatible for 20.0.0, got: {err:?}" + ); + } + #[tokio::test] #[serial] async fn test_delegate_to_local_cli_prints_node_version() { diff --git a/crates/vite_js_runtime/src/runtime.rs b/crates/vite_js_runtime/src/runtime.rs index faa2a88e9a..e518a6cf6f 100644 --- a/crates/vite_js_runtime/src/runtime.rs +++ b/crates/vite_js_runtime/src/runtime.rs @@ -361,7 +361,9 @@ pub async fn resolve_node_version( /// /// # Note /// Currently only supports Node.js runtime. -pub async fn download_runtime_for_project(project_path: &AbsolutePath) -> Result { +pub async fn download_runtime_for_project( + project_path: &AbsolutePath, +) -> Result<(JsRuntime, Option), Error> { let provider = NodeProvider::new(); let cache_dir = crate::cache::get_cache_dir()?; @@ -414,7 +416,7 @@ pub async fn download_runtime_for_project(project_path: &AbsolutePath) -> Result tracing::info!("Resolved Node.js version: {version}"); let runtime = download_runtime(JsRuntimeType::Node, &version).await?; - Ok(runtime) + Ok((runtime, source)) } /// Resolve version requirement to an exact version. @@ -643,7 +645,7 @@ mod tests { let package_json = r#"{"devEngines":{"runtime":{"name":"node","version":"^20.18.0"}}}"#; tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); // Version should be >= 20.18.0 and < 21.0.0 @@ -673,7 +675,7 @@ mod tests { }"#; tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; // Should use node runtime (deno is not supported yet) assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); @@ -691,7 +693,7 @@ mod tests { let package_json = r#"{"name": "test-project"}"#; tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; // Should download Node.js assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); @@ -731,7 +733,7 @@ mod tests { "#; tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); - let _runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let _runtime = download_runtime_for_project(&temp_path).await.unwrap().0; // .node-version should NOT be written (auto-write was removed) assert!( @@ -762,7 +764,7 @@ mod tests { "#; tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; let version = runtime.version(); let parsed = node_semver::Version::parse(version).unwrap(); assert_eq!(parsed.major, 20); @@ -784,7 +786,7 @@ mod tests { let package_json = r#"{"devEngines":{"runtime":{"name":"node","version":"v20.18.0"}}}"#; tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); // Version should be normalized (without 'v' prefix) @@ -801,7 +803,7 @@ mod tests { let temp_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); // No package.json file - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; // Should download latest Node.js assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); @@ -826,7 +828,7 @@ mod tests { tokio::fs::create_dir_all(&subdir).await.unwrap(); tokio::fs::write(subdir.join("package.json"), r#"{"name": "foo"}"#).await.unwrap(); - let runtime = download_runtime_for_project(&subdir).await.unwrap(); + let runtime = download_runtime_for_project(&subdir).await.unwrap().0; // Should inherit version from parent's .node-version assert_eq!(runtime.version(), "20.18.0"); @@ -1005,7 +1007,7 @@ mod tests { let package_json = r#"{"engines":{"node":">=22.0.0"}}"#; tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; assert_eq!(runtime.version(), "20.18.0"); // Should NOT write back since .node-version had exact version @@ -1026,7 +1028,7 @@ mod tests { }"#; tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; // Should use engines.node (^20.18.0), which will resolve to a 20.x version let version = runtime.version(); let parsed = node_semver::Version::parse(version).unwrap(); @@ -1042,7 +1044,7 @@ mod tests { let package_json = r#"{"engines":{"node":"^20.18.0"}}"#; tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; let version = runtime.version(); let parsed = node_semver::Version::parse(version).unwrap(); assert_eq!(parsed.major, 20); @@ -1059,7 +1061,7 @@ mod tests { // Create .node-version with partial version (two parts) tokio::fs::write(temp_path.join(".node-version"), "20.18\n").await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; let version = runtime.version(); let parsed = node_semver::Version::parse(version).unwrap(); // Should resolve to a 20.18.x or higher version in 20.x line @@ -1081,7 +1083,7 @@ mod tests { // Create .node-version with single-part version tokio::fs::write(temp_path.join(".node-version"), "20\n").await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; let version = runtime.version(); let parsed = node_semver::Version::parse(version).unwrap(); // Should resolve to a 20.x.x version @@ -1117,7 +1119,7 @@ mod tests { tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); // Should fall through to fetch latest LTS since .node-version is invalid - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); // Should have a valid version (latest LTS) @@ -1136,7 +1138,7 @@ mod tests { tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); // Should fall through to fetch latest LTS since engines.node is invalid - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); // Should have a valid version (latest LTS) @@ -1155,7 +1157,7 @@ mod tests { tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); // Should fall through to fetch latest LTS since devEngines.runtime is invalid - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); // Should have a valid version (latest LTS) @@ -1177,7 +1179,7 @@ mod tests { tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); // Should use engines.node since .node-version is invalid - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; let version = runtime.version(); let parsed = node_semver::Version::parse(version).unwrap(); assert_eq!(parsed.major, 20); @@ -1196,7 +1198,7 @@ mod tests { tokio::fs::write(temp_path.join("package.json"), package_json).await.unwrap(); // Should use devEngines.runtime since engines.node is invalid - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; let version = runtime.version(); let parsed = node_semver::Version::parse(version).unwrap(); assert_eq!(parsed.major, 20); @@ -1329,7 +1331,7 @@ mod tests { // Create .node-version with LTS alias tokio::fs::write(temp_path.join(".node-version"), "lts/iron\n").await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); // lts/iron should resolve to v20.x @@ -1351,7 +1353,7 @@ mod tests { // Create .node-version with lts/* alias tokio::fs::write(temp_path.join(".node-version"), "lts/*\n").await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); // lts/* should resolve to latest LTS (at least v22.x as of 2026) @@ -1373,7 +1375,7 @@ mod tests { // Create .node-version with "latest" alias tokio::fs::write(temp_path.join(".node-version"), "latest\n").await.unwrap(); - let runtime = download_runtime_for_project(&temp_path).await.unwrap(); + let runtime = download_runtime_for_project(&temp_path).await.unwrap().0; assert_eq!(runtime.runtime_type(), JsRuntimeType::Node); // "latest" should resolve to the absolute latest version (including non-LTS) diff --git a/packages/cli/snap-tests-global/command-env-off-on/package.json b/packages/cli/snap-tests-global/command-env-off-on/package.json index 2889c1ec79..079df3a8dc 100644 --- a/packages/cli/snap-tests-global/command-env-off-on/package.json +++ b/packages/cli/snap-tests-global/command-env-off-on/package.json @@ -7,6 +7,6 @@ "assert-not-managed": "node src/assert-not-managed.mjs" }, "engines": { - "node": "20.18.0" + "node": "22.12.0" } } diff --git a/packages/cli/snap-tests-global/command-env-off-on/snap.txt b/packages/cli/snap-tests-global/command-env-off-on/snap.txt index 0f842f0996..6867292d5a 100644 --- a/packages/cli/snap-tests-global/command-env-off-on/snap.txt +++ b/packages/cli/snap-tests-global/command-env-off-on/snap.txt @@ -1,4 +1,4 @@ -> vp run assert-managed # Managed mode: should use project's engines.node 20.18.0 +> vp run assert-managed # Managed mode: should use project's engines.node 22.12.0 VITE+ - The Unified Toolchain for the Web $ node src/assert-managed.mjs ⊘ cache disabled @@ -14,7 +14,7 @@ All vp commands and shims will now prefer system Node.js, falling back to manage Run `vp env on` to always use Vite+ managed Node.js. -> vp run assert-not-managed # System-first mode: must NOT use 20.18.0 +> vp run assert-not-managed # System-first mode: must NOT use 22.12.0 VITE+ - The Unified Toolchain for the Web $ node src/assert-not-managed.mjs ⊘ cache disabled @@ -30,7 +30,7 @@ All vp commands and shims will now always use Vite+ managed Node.js. Run `vp env off` to prefer system Node.js instead. -> vp run assert-managed # Managed mode restored: should use 20.18.0 again +> vp run assert-managed # Managed mode restored: should use 22.12.0 again VITE+ - The Unified Toolchain for the Web $ node src/assert-managed.mjs ⊘ cache disabled diff --git a/packages/cli/snap-tests-global/command-env-off-on/src/assert-managed.mjs b/packages/cli/snap-tests-global/command-env-off-on/src/assert-managed.mjs index 47fcfb0704..ad5f0bba1c 100644 --- a/packages/cli/snap-tests-global/command-env-off-on/src/assert-managed.mjs +++ b/packages/cli/snap-tests-global/command-env-off-on/src/assert-managed.mjs @@ -1,6 +1,6 @@ -// Assert we ARE using the managed Node.js (v20.18.0 from engines.node) -if (process.version !== 'v20.18.0') { - console.error(`Expected managed Node.js v20.18.0, got ${process.version}`); +// Assert we ARE using the managed Node.js (v22.12.0 from engines.node) +if (process.version !== 'v22.12.0') { + console.error(`Expected managed Node.js v22.12.0, got ${process.version}`); process.exit(1); } console.log(`OK: ${process.version}`); diff --git a/packages/cli/snap-tests-global/command-env-off-on/src/assert-not-managed.mjs b/packages/cli/snap-tests-global/command-env-off-on/src/assert-not-managed.mjs index addf76e39e..762908e149 100644 --- a/packages/cli/snap-tests-global/command-env-off-on/src/assert-not-managed.mjs +++ b/packages/cli/snap-tests-global/command-env-off-on/src/assert-not-managed.mjs @@ -1,6 +1,6 @@ -// Assert we are NOT using the managed Node.js (v20.18.0 from engines.node) -if (process.version === 'v20.18.0') { - console.error(`Expected system Node.js, got managed v20.18.0`); +// Assert we are NOT using the managed Node.js (v22.12.0 from engines.node) +if (process.version === 'v22.12.0') { + console.error(`Expected system Node.js, got managed v22.12.0`); process.exit(1); } console.log(`OK: ${process.version}`); diff --git a/packages/cli/snap-tests-global/command-env-off-on/steps.json b/packages/cli/snap-tests-global/command-env-off-on/steps.json index 4f402e64c6..2fce8ee423 100644 --- a/packages/cli/snap-tests-global/command-env-off-on/steps.json +++ b/packages/cli/snap-tests-global/command-env-off-on/steps.json @@ -2,11 +2,11 @@ "serial": true, "ignoredPlatforms": ["win32"], "commands": [ - "vp run assert-managed # Managed mode: should use project's engines.node 20.18.0", + "vp run assert-managed # Managed mode: should use project's engines.node 22.12.0", "vp env off # Switch to system-first mode", - "vp run assert-not-managed # System-first mode: must NOT use 20.18.0", + "vp run assert-not-managed # System-first mode: must NOT use 22.12.0", "vp env on # Switch back to managed mode", - "vp run assert-managed # Managed mode restored: should use 20.18.0 again" + "vp run assert-managed # Managed mode restored: should use 22.12.0 again" ], "after": ["vp env on"] } diff --git a/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/package.json b/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/package.json index ecc3be898b..bb18a57fc9 100644 --- a/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/package.json +++ b/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/package.json @@ -4,7 +4,7 @@ "devEngines": { "runtime": { "name": "node", - "version": "22.11.0" + "version": "22.12.0" } }, "packageManager": "pnpm@10.19.0" diff --git a/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/snap.txt b/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/snap.txt index 94234b6e27..8e0fcb4e6b 100644 --- a/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/snap.txt +++ b/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/snap.txt @@ -1,2 +1,2 @@ -> vp dlx -s print-current-version # should print Node.js version 22.11.0 from devEngines.runtime -v22.11.0 +> vp dlx -s print-current-version # should print Node.js version 22.12.0 from devEngines.runtime +v22.12.0 diff --git a/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/steps.json b/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/steps.json index 2bf71c6544..6c8ed7f4ca 100644 --- a/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/steps.json +++ b/packages/cli/snap-tests-global/dev-engines-runtime-pnpm10/steps.json @@ -1,5 +1,5 @@ { "commands": [ - "vp dlx -s print-current-version # should print Node.js version 22.11.0 from devEngines.runtime" + "vp dlx -s print-current-version # should print Node.js version 22.12.0 from devEngines.runtime" ] } diff --git a/packages/cli/snap-tests-global/runtime-with-incompatible-env-node/snap.txt b/packages/cli/snap-tests-global/runtime-with-incompatible-env-node/snap.txt new file mode 100644 index 0000000000..e1f53504a4 --- /dev/null +++ b/packages/cli/snap-tests-global/runtime-with-incompatible-env-node/snap.txt @@ -0,0 +1,13 @@ +> vp env use 20.0.0 +Using Node.js v (resolved from ) + +[1]> vp exec node --version +VITE+ - The Unified Toolchain for the Web + +error: Node.js is incompatible with Vite+ CLI. +Required by Vite+: ^20.19.0 || >=22.12.0 +Resolved from: .session-node-version + + +Set a compatible version globally: vp env default lts +Temporary override: vp env use lts diff --git a/packages/cli/snap-tests-global/runtime-with-incompatible-env-node/steps.json b/packages/cli/snap-tests-global/runtime-with-incompatible-env-node/steps.json new file mode 100644 index 0000000000..b7e48a438b --- /dev/null +++ b/packages/cli/snap-tests-global/runtime-with-incompatible-env-node/steps.json @@ -0,0 +1,5 @@ +{ + "ignoredPlatforms": ["win32"], + "commands": ["vp env use 20.0.0", "vp exec node --version"], + "after": ["vp env use --unset"] +} diff --git a/packages/cli/snap-tests-global/runtime-with-incompatible-global-node/package.json b/packages/cli/snap-tests-global/runtime-with-incompatible-global-node/package.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/packages/cli/snap-tests-global/runtime-with-incompatible-global-node/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/cli/snap-tests-global/runtime-with-incompatible-global-node/snap.txt b/packages/cli/snap-tests-global/runtime-with-incompatible-global-node/snap.txt new file mode 100644 index 0000000000..977c869fda --- /dev/null +++ b/packages/cli/snap-tests-global/runtime-with-incompatible-global-node/snap.txt @@ -0,0 +1,13 @@ +> vp env default 20.0.0 +VITE+ - The Unified Toolchain for the Web + +✓ Default Node.js version set to + +[1]> vp migrate --yes +error: Node.js is incompatible with Vite+ CLI. +Required by Vite+: ^20.19.0 || >=22.12.0 +Resolved from: default + + +Set a compatible version globally: vp env default lts +Temporary override: vp env use lts diff --git a/packages/cli/snap-tests-global/runtime-with-incompatible-global-node/steps.json b/packages/cli/snap-tests-global/runtime-with-incompatible-global-node/steps.json new file mode 100644 index 0000000000..4589eecba1 --- /dev/null +++ b/packages/cli/snap-tests-global/runtime-with-incompatible-global-node/steps.json @@ -0,0 +1,5 @@ +{ + "env": {}, + "commands": ["vp env default 20.0.0", "vp migrate --yes"], + "after": ["vp env default lts"] +} diff --git a/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/.node-version b/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/.node-version new file mode 100644 index 0000000000..e88320d7c3 --- /dev/null +++ b/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/.node-version @@ -0,0 +1 @@ +20.0.0 diff --git a/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/package.json b/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/package.json new file mode 100644 index 0000000000..6644068540 --- /dev/null +++ b/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/package.json @@ -0,0 +1,5 @@ +{ + "name": "block-running-with-unsupported-node", + "version": "1.0.0", + "private": true +} diff --git a/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/snap.txt b/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/snap.txt new file mode 100644 index 0000000000..fb6bb9d093 --- /dev/null +++ b/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/snap.txt @@ -0,0 +1,10 @@ +[1]> vp check +VITE+ - The Unified Toolchain for the Web + +error: Node.js is incompatible with Vite+ CLI. +Required by Vite+: ^20.19.0 || >=22.12.0 +Resolved from: .node-version + + +Fix this project: vp env pin lts +Temporary override: vp env use lts diff --git a/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/steps.json b/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/steps.json new file mode 100644 index 0000000000..ef522cb26d --- /dev/null +++ b/packages/cli/snap-tests-global/runtime-with-incompatible-project-node/steps.json @@ -0,0 +1,4 @@ +{ + "env": {}, + "commands": ["vp check"] +}