@@ -218,13 +218,13 @@ impl JsExecutor {
218218 Ok ( self . project_runtime . as_ref ( ) . unwrap ( ) )
219219 }
220220
221- /// Check that a resolved runtime's version satisfies vp's engine requirements.
221+ /// Check that a runtime's version satisfies vp's engine requirements.
222222 ///
223223 /// Skips silently when:
224224 /// - The runtime is a system install (version == `"system"`)
225225 /// - The version or requirement strings cannot be parsed as semver
226226 ///
227- /// Returns [`Error::NodeVersionIncompatible`] when the version is parseable but
227+ /// Returns [`Error::NodeVersionIncompatible`] when the version is parsable but
228228 /// outside the required range.
229229 async fn check_runtime_compatibility ( & self , version : & str ) -> Result < ( ) , Error > {
230230 let Some ( requirement) = self . get_cli_engines_requirement ( ) . await else { return Ok ( ( ) ) } ;
@@ -523,120 +523,43 @@ mod tests {
523523 assert_eq ! ( cmd. as_std( ) . get_program( ) , OsStr :: new( expected_program) ) ;
524524 }
525525
526- // ---- check_runtime_compatibility ----
527-
528- fn make_runtime_with_version ( version : & str ) -> JsRuntime {
529- let binary_path = if cfg ! ( windows) {
530- AbsolutePathBuf :: new ( "C:\\ node\\ node.exe" . into ( ) ) . unwrap ( )
531- } else {
532- AbsolutePathBuf :: new ( "/usr/local/bin/node" . into ( ) ) . unwrap ( )
533- } ;
534- let mut runtime = JsRuntime :: from_system ( JsRuntimeType :: Node , binary_path) ;
535- runtime. version = version. into ( ) ;
536- runtime
537- }
538-
539- #[ test]
540- fn compatible_version_passes ( ) {
541- let runtime = make_runtime_with_version ( "22.12.0" ) ;
542- assert ! (
543- check_runtime_compatibility( & runtime, "^20.19.0 || >=22.12.0" ) . is_ok( ) ,
544- "22.12.0 should satisfy ^20.19.0 || >=22.12.0"
545- ) ;
546- }
547-
548- #[ test]
549- fn compatible_version_with_v_prefix_passes ( ) {
550- let runtime = make_runtime_with_version ( "v20.19.0" ) ;
551- assert ! (
552- check_runtime_compatibility( & runtime, "^20.19.0 || >=22.12.0" ) . is_ok( ) ,
553- "v20.19.0 should satisfy ^20.19.0 || >=22.12.0"
554- ) ;
555- }
556-
557- #[ test]
558- fn incompatible_version_returns_error ( ) {
559- let runtime = make_runtime_with_version ( "18.0.0" ) ;
560- let err = check_runtime_compatibility ( & runtime, "^20.19.0 || >=22.12.0" )
561- . expect_err ( "18.0.0 should not satisfy ^20.19.0 || >=22.12.0" ) ;
562- assert ! (
563- matches!( err, Error :: NodeVersionIncompatible { .. } ) ,
564- "expected NodeVersionIncompatible, got {err:?}"
565- ) ;
566- }
567-
568- #[ test]
569- fn system_runtime_skips_check ( ) {
570- // from_system() sets version == "system"
571- let binary_path = if cfg ! ( windows) {
572- AbsolutePathBuf :: new ( "C:\\ node\\ node.exe" . into ( ) ) . unwrap ( )
573- } else {
574- AbsolutePathBuf :: new ( "/usr/local/bin/node" . into ( ) ) . unwrap ( )
575- } ;
576- let runtime = JsRuntime :: from_system ( JsRuntimeType :: Node , binary_path) ;
577- assert_eq ! ( runtime. version( ) , "system" ) ;
578- assert ! (
579- check_runtime_compatibility( & runtime, "^20.19.0 || >=22.12.0" ) . is_ok( ) ,
580- "system runtime should pass the check unconditionally"
581- ) ;
582- }
583-
584- #[ test]
585- fn unparsable_version_skips_check ( ) {
586- let runtime = make_runtime_with_version ( "not-a-version" ) ;
587- assert ! (
588- check_runtime_compatibility( & runtime, "^20.19.0 || >=22.12.0" ) . is_ok( ) ,
589- "unparsable version should not cause an error"
590- ) ;
591- }
592-
593- #[ test]
594- fn invalid_requirement_skips_check ( ) {
595- let runtime = make_runtime_with_version ( "18.0.0" ) ;
596- assert ! (
597- check_runtime_compatibility( & runtime, "not-a-range" ) . is_ok( ) ,
598- "invalid requirement range should not cause an error"
599- ) ;
600- }
601-
602- // ---- get_cli_engines_requirement ----
603-
526+ /// Pin Node.js to 20.0.0
527+ /// and any vp command should be blocked with a clear error instead of crashing
604528 #[ tokio:: test]
605- async fn get_cli_engines_requirement_reads_from_package_json ( ) {
606- use std:: io:: Write ;
607-
529+ async fn incompatible_node_version_should_be_blocked ( ) {
608530 use tempfile:: TempDir ;
609-
531+ use vite_shared:: EnvConfig ;
532+
533+ // Point scripts_dir at the real packages/cli/dist so that
534+ // get_cli_engines_requirement() reads the actual engines.node from
535+ // packages/cli/package.json. The dist/ directory need not exist — only
536+ // its parent (packages/cli/) and the package.json within it are read.
537+ let scripts_dir = AbsolutePathBuf :: new (
538+ std:: path:: PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "../../packages/cli/dist" ) ,
539+ )
540+ . unwrap ( ) ;
541+
542+ // Use any existing directory as project_path; the session override
543+ // fires before any project-source lookup or network download.
610544 let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
611- // The scripts dir is `<pkg_dir>/dist`, and get_cli_package_dir returns its parent.
612- let dist_dir = temp_dir. path ( ) . join ( "dist" ) ;
613- std:: fs:: create_dir ( & dist_dir) . unwrap ( ) ;
614-
615- let pkg_json_content = r#"{ "engines": { "node": "^20.19.0 || >=22.12.0" } }"# ;
616- let mut f = std:: fs:: File :: create ( temp_dir. path ( ) . join ( "package.json" ) ) . unwrap ( ) ;
617- f. write_all ( pkg_json_content. as_bytes ( ) ) . unwrap ( ) ;
618-
619- let scripts_dir = AbsolutePathBuf :: new ( dist_dir) . unwrap ( ) ;
620- let executor = JsExecutor :: new ( Some ( scripts_dir) ) ;
545+ let project_path = AbsolutePathBuf :: new ( temp_dir. path ( ) . to_path_buf ( ) ) . unwrap ( ) ;
621546
622- let req = executor. get_cli_engines_requirement ( ) . await ;
623- assert_eq ! ( req. as_deref( ) , Some ( "^20.19.0 || >=22.12.0" ) ) ;
624- }
625-
626- #[ tokio:: test]
627- async fn get_cli_engines_requirement_returns_none_when_missing ( ) {
628- use tempfile:: TempDir ;
629-
630- let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
631- let dist_dir = temp_dir. path ( ) . join ( "dist" ) ;
632- std:: fs:: create_dir ( & dist_dir) . unwrap ( ) ;
547+ // Simulate `.node-version: 20.0.0` / `vp env use 20.0.0` via a session override.
548+ let _guard = EnvConfig :: test_guard ( EnvConfig {
549+ node_version : Some ( "20.0.0" . to_string ( ) ) ,
550+ ..EnvConfig :: for_test ( )
551+ } ) ;
633552
634- // No package.json → should return None
635- let scripts_dir = AbsolutePathBuf :: new ( dist_dir) . unwrap ( ) ;
636- let executor = JsExecutor :: new ( Some ( scripts_dir) ) ;
553+ let mut executor = JsExecutor :: new ( Some ( scripts_dir) ) ;
554+ let err = executor
555+ . ensure_project_runtime ( & project_path)
556+ . await
557+ . expect_err ( "Node.js 20.0.0 should be rejected as incompatible with vp requirements" ) ;
637558
638- let req = executor. get_cli_engines_requirement ( ) . await ;
639- assert ! ( req. is_none( ) ) ;
559+ assert ! (
560+ matches!( & err, Error :: NodeVersionIncompatible { version, .. } if version == "20.0.0" ) ,
561+ "expected NodeVersionIncompatible for 20.0.0, got: {err:?}"
562+ ) ;
640563 }
641564
642565 #[ tokio:: test]
0 commit comments