@@ -440,9 +440,7 @@ impl JsExecutor {
440440 ) -> Option < AbsolutePathBuf > {
441441 use oxc_resolver:: { ResolveOptions , Resolver } ;
442442
443- // For projects that declare a vite-plus dependency, only trust an
444- // install within their workspace; the Node-semantics resolution below
445- // would otherwise walk past it (see `local_vite_plus_install_host`).
443+ // Enforce the workspace boundary before using Node's unbounded resolver.
446444 Self :: local_vite_plus_install_host ( project_path) ?;
447445
448446 let resolver = Resolver :: new ( ResolveOptions {
@@ -560,19 +558,23 @@ mod tests {
560558 dir
561559 }
562560
563- /// An independent project that *declares* a vite-plus dependency (with
564- /// its own workspace marker) checked out inside another project's tree
565- /// must not resolve the outer project's vite-plus when its own install is
566- /// missing — the declaration makes "run `vp install`" the right answer,
567- /// not silently delegating to an unrelated copy.
561+ fn write_local_cli ( root : & AbsolutePath , version : & str ) {
562+ let package_dir = root. join ( "node_modules/vite-plus" ) ;
563+ std:: fs:: create_dir_all ( package_dir. join ( "dist" ) ) . unwrap ( ) ;
564+ std:: fs:: write (
565+ package_dir. join ( "package.json" ) ,
566+ vt_str:: format!( r#"{{"version":"{version}"}}"# ) . as_bytes ( ) ,
567+ )
568+ . unwrap ( ) ;
569+ std:: fs:: write ( package_dir. join ( "dist/bin.js" ) , "" ) . unwrap ( ) ;
570+ }
571+
572+ /// A declared dependency must not resolve outside an independent workspace.
568573 #[ test]
569574 fn local_resolution_stays_within_the_workspace ( ) {
570575 let temp = tempfile:: tempdir ( ) . unwrap ( ) ;
571- let outer = temp. path ( ) ;
572- std:: fs:: create_dir_all ( outer. join ( "node_modules/vite-plus/dist" ) ) . unwrap ( ) ;
573- std:: fs:: write ( outer. join ( "node_modules/vite-plus/package.json" ) , r#"{"version":"0.2.1"}"# )
574- . unwrap ( ) ;
575- std:: fs:: write ( outer. join ( "node_modules/vite-plus/dist/bin.js" ) , "" ) . unwrap ( ) ;
576+ let outer = AbsolutePath :: new ( temp. path ( ) ) . unwrap ( ) ;
577+ write_local_cli ( outer, "0.2.1" ) ;
576578 std:: fs:: write ( outer. join ( "pnpm-workspace.yaml" ) , "packages: []\n " ) . unwrap ( ) ;
577579 std:: fs:: write ( outer. join ( "package.json" ) , r#"{"name":"outer"}"# ) . unwrap ( ) ;
578580
@@ -585,21 +587,16 @@ mod tests {
585587 . unwrap ( ) ;
586588 std:: fs:: write ( inner. join ( "pnpm-workspace.yaml" ) , "packages: []\n " ) . unwrap ( ) ;
587589
588- let inner = AbsolutePath :: new ( inner. as_path ( ) ) . unwrap ( ) ;
589- assert_eq ! ( JsExecutor :: local_vite_plus_install_host( inner) , None ) ;
590- assert_eq ! ( JsExecutor :: resolve_local_vite_plus_package_dir( inner) , None ) ;
591- assert_eq ! ( JsExecutor :: resolve_local_vite_plus( inner) , None ) ;
590+ assert_eq ! ( JsExecutor :: local_vite_plus_install_host( & inner) , None ) ;
591+ assert_eq ! ( JsExecutor :: resolve_local_vite_plus_package_dir( & inner) , None ) ;
592+ assert_eq ! ( JsExecutor :: resolve_local_vite_plus( & inner) , None ) ;
592593 }
593594
594- /// A workspace member still resolves the workspace root's install: the
595- /// boundary is the workspace root, not the member directory. The root
596- /// declares the dependency so the bounded walk is actually engaged —
597- /// without a declaration this case would pass trivially via the
598- /// unbounded default.
595+ /// The root declares vite-plus so this exercises bounded workspace lookup.
599596 #[ test]
600597 fn workspace_member_resolves_the_workspace_root_install ( ) {
601598 let temp = tempfile:: tempdir ( ) . unwrap ( ) ;
602- let ws = temp. path ( ) ;
599+ let ws = AbsolutePath :: new ( temp. path ( ) ) . unwrap ( ) ;
603600 std:: fs:: write ( ws. join ( "pnpm-workspace.yaml" ) , "packages:\n - packages/*\n " ) . unwrap ( ) ;
604601 std:: fs:: write (
605602 ws. join ( "package.json" ) ,
@@ -613,11 +610,10 @@ mod tests {
613610 std:: fs:: create_dir_all ( & member) . unwrap ( ) ;
614611 std:: fs:: write ( member. join ( "package.json" ) , r#"{"name":"app"}"# ) . unwrap ( ) ;
615612
616- let member = AbsolutePath :: new ( member. as_path ( ) ) . unwrap ( ) ;
617- let host = JsExecutor :: local_vite_plus_install_host ( member)
613+ let host = JsExecutor :: local_vite_plus_install_host ( & member)
618614 . expect ( "workspace root install must stay resolvable" ) ;
619- assert_eq ! ( host. as_path ( ) , ws) ;
620- let pkg_dir = JsExecutor :: resolve_local_vite_plus_package_dir ( member)
615+ assert_eq ! ( & host, ws) ;
616+ let pkg_dir = JsExecutor :: resolve_local_vite_plus_package_dir ( & member)
621617 . expect ( "workspace root install must stay resolvable" ) ;
622618 assert ! ( pkg_dir. as_path( ) . ends_with( "node_modules/vite-plus" ) ) ;
623619 }
@@ -628,23 +624,16 @@ mod tests {
628624 [ None , Some ( "{}" ) , Some ( r#"{"devDependencies":{"vite-plus":"0.3.0"}}"# ) , Some ( "{" ) ]
629625 {
630626 let temp = tempfile:: tempdir ( ) . unwrap ( ) ;
631- let outer = temp. path ( ) ;
627+ let outer = AbsolutePath :: new ( temp. path ( ) ) . unwrap ( ) ;
632628 if let Some ( ancestor) = ancestor {
633629 std:: fs:: write ( outer. join ( "package.json" ) , ancestor) . unwrap ( ) ;
634630 }
635- std:: fs:: create_dir_all ( outer. join ( "node_modules/vite-plus/dist" ) ) . unwrap ( ) ;
636- std:: fs:: write (
637- outer. join ( "node_modules/vite-plus/package.json" ) ,
638- r#"{"version":"0.2.1"}"# ,
639- )
640- . unwrap ( ) ;
641- std:: fs:: write ( outer. join ( "node_modules/vite-plus/dist/bin.js" ) , "" ) . unwrap ( ) ;
631+ write_local_cli ( outer, "0.2.1" ) ;
642632
643633 let workspace = outer. join ( "inner" ) ;
644634 std:: fs:: create_dir_all ( workspace. join ( "src" ) ) . unwrap ( ) ;
645635 std:: fs:: write ( workspace. join ( "pnpm-workspace.yaml" ) , "packages: []\n " ) . unwrap ( ) ;
646636 for cwd in [ & workspace, & workspace. join ( "src" ) ] {
647- let cwd = AbsolutePath :: new ( cwd) . unwrap ( ) ;
648637 assert_eq ! (
649638 JsExecutor :: local_vite_plus_install_host( cwd) ,
650639 None ,
@@ -654,15 +643,11 @@ mod tests {
654643 }
655644
656645 // A missing root manifest must not prevent a workspace-local install.
657- std:: fs:: create_dir_all ( workspace. join ( "node_modules/vite-plus/dist" ) ) . unwrap ( ) ;
658- std:: fs:: write (
659- workspace. join ( "node_modules/vite-plus/package.json" ) ,
660- r#"{"version":"0.3.0"}"# ,
661- )
662- . unwrap ( ) ;
663- std:: fs:: write ( workspace. join ( "node_modules/vite-plus/dist/bin.js" ) , "" ) . unwrap ( ) ;
664- let cwd = AbsolutePath :: new ( & workspace) . unwrap ( ) ;
665- assert_eq ! ( JsExecutor :: local_vite_plus_install_host( cwd) . as_deref( ) , Some ( cwd) ) ;
646+ write_local_cli ( & workspace, "0.3.0" ) ;
647+ assert_eq ! (
648+ JsExecutor :: local_vite_plus_install_host( & workspace) . as_deref( ) ,
649+ Some ( workspace. as_ref( ) )
650+ ) ;
666651 }
667652 }
668653
@@ -699,16 +684,10 @@ mod tests {
699684 ) ,
700685 ] {
701686 let temp = tempfile:: tempdir ( ) . unwrap ( ) ;
702- let root = temp. path ( ) ;
687+ let root = AbsolutePath :: new ( temp. path ( ) ) . unwrap ( ) ;
703688 std:: fs:: write ( root. join ( "package.json" ) , r#"{"name":"outer"}"# ) . unwrap ( ) ;
704689 std:: fs:: write ( root. join ( workspace_file) , content) . unwrap ( ) ;
705- std:: fs:: create_dir_all ( root. join ( "node_modules/vite-plus/dist" ) ) . unwrap ( ) ;
706- std:: fs:: write (
707- root. join ( "node_modules/vite-plus/package.json" ) ,
708- r#"{"version":"0.3.0"}"# ,
709- )
710- . unwrap ( ) ;
711- std:: fs:: write ( root. join ( "node_modules/vite-plus/dist/bin.js" ) , "" ) . unwrap ( ) ;
690+ write_local_cli ( root, "0.3.0" ) ;
712691 // Membership must not require reading unrelated members' manifests.
713692 std:: fs:: create_dir_all ( root. join ( "packages/broken" ) ) . unwrap ( ) ;
714693 std:: fs:: write ( root. join ( "packages/broken/package.json" ) , "{" ) . unwrap ( ) ;
@@ -721,9 +700,8 @@ mod tests {
721700 )
722701 . unwrap ( ) ;
723702 let cwd = project. join ( "src" ) ;
724- let cwd = AbsolutePath :: new ( & cwd) . unwrap ( ) ;
725703 assert_eq ! (
726- JsExecutor :: resolve_local_vite_plus( cwd) . is_some( ) ,
704+ JsExecutor :: resolve_local_vite_plus( & cwd) . is_some( ) ,
727705 is_member,
728706 "{workspace_file}: {content}, package: {package}" ,
729707 ) ;
@@ -742,80 +720,60 @@ mod tests {
742720 ( "{}" , "\u{feff} {\" devDependencies\" :{\" vite-plus\" :\" 0.3.0\" }}" , None ) ,
743721 ] {
744722 let temp = tempfile:: tempdir ( ) . unwrap ( ) ;
745- let root = temp. path ( ) ;
723+ let root = AbsolutePath :: new ( temp. path ( ) ) . unwrap ( ) ;
746724 std:: fs:: write ( root. join ( "package.json" ) , ancestor) . unwrap ( ) ;
747725 if let Some ( workspace) = workspace {
748726 std:: fs:: write ( root. join ( "pnpm-workspace.yaml" ) , workspace) . unwrap ( ) ;
749727 }
750- std:: fs:: create_dir_all ( root. join ( "node_modules/vite-plus/dist" ) ) . unwrap ( ) ;
751- std:: fs:: write (
752- root. join ( "node_modules/vite-plus/package.json" ) ,
753- r#"{"version":"0.2.1"}"# ,
754- )
755- . unwrap ( ) ;
756- std:: fs:: write ( root. join ( "node_modules/vite-plus/dist/bin.js" ) , "" ) . unwrap ( ) ;
728+ write_local_cli ( root, "0.2.1" ) ;
757729 let inner = root. join ( "inner" ) ;
758730 std:: fs:: create_dir_all ( & inner) . unwrap ( ) ;
759731 std:: fs:: write ( inner. join ( "package.json" ) , project) . unwrap ( ) ;
760- let inner = AbsolutePath :: new ( & inner) . unwrap ( ) ;
761732 assert_eq ! (
762- JsExecutor :: local_vite_plus_install_host( inner) ,
733+ JsExecutor :: local_vite_plus_install_host( & inner) ,
763734 None ,
764735 "{ancestor}: {project}"
765736 ) ;
766- assert_eq ! ( JsExecutor :: resolve_local_vite_plus( inner) , None ) ;
737+ assert_eq ! ( JsExecutor :: resolve_local_vite_plus( & inner) , None ) ;
767738 }
768739 }
769740
770- /// A project that does *not* declare a vite-plus dependency keeps Node's
771- /// unbounded upward resolution even across its own workspace marker —
772- /// this is the layout the snapshot harness depends on (staged workspaces
773- /// with no `node_modules` of their own, resolving a run-root install).
741+ /// Undeclared projects retain Node's upward lookup, as used by snapshot fixtures.
774742 #[ test]
775743 fn undeclared_project_keeps_the_unbounded_walk ( ) {
776744 let temp = tempfile:: tempdir ( ) . unwrap ( ) ;
777- let outer = temp. path ( ) ;
778- std:: fs:: create_dir_all ( outer. join ( "node_modules/vite-plus/dist" ) ) . unwrap ( ) ;
779- std:: fs:: write ( outer. join ( "node_modules/vite-plus/package.json" ) , r#"{"version":"0.2.1"}"# )
780- . unwrap ( ) ;
781- std:: fs:: write ( outer. join ( "node_modules/vite-plus/dist/bin.js" ) , "" ) . unwrap ( ) ;
745+ let outer = AbsolutePath :: new ( temp. path ( ) ) . unwrap ( ) ;
746+ write_local_cli ( outer, "0.2.1" ) ;
782747
783748 let inner = outer. join ( "cases/one/workspace" ) ;
784749 std:: fs:: create_dir_all ( & inner) . unwrap ( ) ;
785750 std:: fs:: write ( inner. join ( "package.json" ) , r#"{"name":"inner"}"# ) . unwrap ( ) ;
786751 std:: fs:: write ( inner. join ( "pnpm-workspace.yaml" ) , "packages: []\n " ) . unwrap ( ) ;
787752
788- let inner = AbsolutePath :: new ( inner. as_path ( ) ) . unwrap ( ) ;
789- let host = JsExecutor :: local_vite_plus_install_host ( inner)
753+ let host = JsExecutor :: local_vite_plus_install_host ( & inner)
790754 . expect ( "undeclared projects keep the unbounded walk" ) ;
791- assert_eq ! ( host. as_path ( ) , outer) ;
792- let pkg_dir = JsExecutor :: resolve_local_vite_plus_package_dir ( inner)
755+ assert_eq ! ( & host, outer) ;
756+ let pkg_dir = JsExecutor :: resolve_local_vite_plus_package_dir ( & inner)
793757 . expect ( "undeclared projects keep the unbounded walk" ) ;
794758 assert ! ( pkg_dir. as_path( ) . ends_with( "node_modules/vite-plus" ) ) ;
795759 }
796760
797- /// Without any project marker around (`find_workspace_root` errors) there
798- /// is no boundary to protect; the walk stays unbounded as before.
799- ///
800- /// Unix-only: the premise is that no ancestor of the tempdir carries a
801- /// package.json, which holds for `/tmp` / `/var/folders` but not for
802- /// Windows, where `%TEMP%` lives under the user profile and a stray
803- /// `package.json` there would create a boundary and fail the test.
761+ /// Unix-only: Windows tempdirs can have a package.json in an ancestor profile
762+ /// directory, which would invalidate this test's markerless layout.
804763 #[ cfg( unix) ]
805764 #[ test]
806765 fn unbounded_walk_without_project_markers ( ) {
807766 let temp = tempfile:: tempdir ( ) . unwrap ( ) ;
808- let root = temp. path ( ) ;
767+ let root = AbsolutePath :: new ( temp. path ( ) ) . unwrap ( ) ;
809768 std:: fs:: create_dir_all ( root. join ( "node_modules/vite-plus" ) ) . unwrap ( ) ;
810769 std:: fs:: write ( root. join ( "node_modules/vite-plus/package.json" ) , r#"{"version":"1.0.0"}"# )
811770 . unwrap ( ) ;
812771 let nested = root. join ( "a/b" ) ;
813772 std:: fs:: create_dir_all ( & nested) . unwrap ( ) ;
814773
815- let nested = AbsolutePath :: new ( nested. as_path ( ) ) . unwrap ( ) ;
816- let host = JsExecutor :: local_vite_plus_install_host ( nested)
774+ let host = JsExecutor :: local_vite_plus_install_host ( & nested)
817775 . expect ( "markerless directories keep the unbounded walk" ) ;
818- assert_eq ! ( host. as_path ( ) , root) ;
776+ assert_eq ! ( & host, root) ;
819777 }
820778
821779 #[ test]
0 commit comments