1- use std:: path:: { Path , PathBuf } ;
1+ use std:: {
2+ collections:: HashMap ,
3+ path:: { Path , PathBuf } ,
4+ } ;
25
36use vite_error:: Error ;
47
@@ -289,12 +292,33 @@ impl SkipPackages {
289292 }
290293}
291294
292- /// Parse package.json at the root and check which packages are in peerDependencies.
293- /// Returns default (no skipping) if package.json doesn't exist or can't be parsed.
294- fn get_skip_packages_from_root ( root : & Path ) -> SkipPackages {
295- let package_json_path = root. join ( "package.json" ) ;
295+ /// Find the nearest package.json by walking up from the file's directory.
296+ /// Stops at the root directory.
297+ fn find_nearest_package_json ( file_path : & Path , root : & Path ) -> Option < PathBuf > {
298+ let mut current = file_path. parent ( ) ?;
299+
300+ loop {
301+ let package_json = current. join ( "package.json" ) ;
302+ if package_json. exists ( ) {
303+ return Some ( package_json) ;
304+ }
305+
306+ // Stop if we've reached the root
307+ if current == root {
308+ break ;
309+ }
296310
297- let content = match std:: fs:: read_to_string ( & package_json_path) {
311+ // Move to parent directory
312+ current = current. parent ( ) ?;
313+ }
314+
315+ None
316+ }
317+
318+ /// Parse package.json and check which packages are in peerDependencies.
319+ /// Returns default (no skipping) if package.json doesn't exist or can't be parsed.
320+ fn get_skip_packages_from_package_json ( package_json_path : & Path ) -> SkipPackages {
321+ let content = match std:: fs:: read_to_string ( package_json_path) {
298322 Ok ( c) => c,
299323 Err ( _) => return SkipPackages :: default ( ) ,
300324 } ;
@@ -374,16 +398,27 @@ pub fn rewrite_imports_in_directory(root: &Path) -> Result<BatchRewriteResult, E
374398 errors : Vec :: new ( ) ,
375399 } ;
376400
377- // Check package.json at root for peerDependencies
378- let skip_packages = get_skip_packages_from_root ( root) ;
379-
380- // If all packages are in peerDeps, skip all files
381- if skip_packages. all_skipped ( ) {
382- result. unchanged_files = walk_result. files ;
383- return Ok ( result) ;
384- }
401+ // Cache package.json lookups to avoid re-reading the same file
402+ let mut skip_packages_cache: HashMap < PathBuf , SkipPackages > = HashMap :: new ( ) ;
385403
386404 for file_path in walk_result. files {
405+ // Find the nearest package.json for this file
406+ let skip_packages =
407+ if let Some ( package_json_path) = find_nearest_package_json ( & file_path, root) {
408+ skip_packages_cache
409+ . entry ( package_json_path. clone ( ) )
410+ . or_insert_with ( || get_skip_packages_from_package_json ( & package_json_path) )
411+ . clone ( )
412+ } else {
413+ SkipPackages :: default ( )
414+ } ;
415+
416+ // If all packages are in peerDeps for this file's package, skip it
417+ if skip_packages. all_skipped ( ) {
418+ result. unchanged_files . push ( file_path) ;
419+ continue ;
420+ }
421+
387422 match rewrite_import ( & file_path, & skip_packages) {
388423 Ok ( rewrite_result) => {
389424 if rewrite_result. updated {
@@ -1675,7 +1710,7 @@ export default defineConfig({});"#;
16751710 }
16761711
16771712 #[ test]
1678- fn test_get_skip_packages_from_root_with_vite_peer_dep ( ) {
1713+ fn test_get_skip_packages_from_package_json_with_vite_peer_dep ( ) {
16791714 use std:: fs;
16801715
16811716 let temp = tempdir ( ) . unwrap ( ) ;
@@ -1687,16 +1722,17 @@ export default defineConfig({});"#;
16871722 "vite": "^5.0.0"
16881723 }
16891724}"# ;
1690- fs:: write ( temp. path ( ) . join ( "package.json" ) , pkg_json) . unwrap ( ) ;
1725+ let package_json_path = temp. path ( ) . join ( "package.json" ) ;
1726+ fs:: write ( & package_json_path, pkg_json) . unwrap ( ) ;
16911727
1692- let skip = get_skip_packages_from_root ( temp . path ( ) ) ;
1728+ let skip = get_skip_packages_from_package_json ( & package_json_path ) ;
16931729 assert ! ( skip. skip_vite) ;
16941730 assert ! ( !skip. skip_vitest) ;
16951731 assert ! ( !skip. skip_tsdown) ;
16961732 }
16971733
16981734 #[ test]
1699- fn test_get_skip_packages_from_root_with_all_peer_deps ( ) {
1735+ fn test_get_skip_packages_from_package_json_with_all_peer_deps ( ) {
17001736 use std:: fs;
17011737
17021738 let temp = tempdir ( ) . unwrap ( ) ;
@@ -1709,17 +1745,18 @@ export default defineConfig({});"#;
17091745 "tsdown": "^1.0.0"
17101746 }
17111747}"# ;
1712- fs:: write ( temp. path ( ) . join ( "package.json" ) , pkg_json) . unwrap ( ) ;
1748+ let package_json_path = temp. path ( ) . join ( "package.json" ) ;
1749+ fs:: write ( & package_json_path, pkg_json) . unwrap ( ) ;
17131750
1714- let skip = get_skip_packages_from_root ( temp . path ( ) ) ;
1751+ let skip = get_skip_packages_from_package_json ( & package_json_path ) ;
17151752 assert ! ( skip. skip_vite) ;
17161753 assert ! ( skip. skip_vitest) ;
17171754 assert ! ( skip. skip_tsdown) ;
17181755 assert ! ( skip. all_skipped( ) ) ;
17191756 }
17201757
17211758 #[ test]
1722- fn test_get_skip_packages_from_root_no_peer_deps ( ) {
1759+ fn test_get_skip_packages_from_package_json_no_peer_deps ( ) {
17231760 use std:: fs;
17241761
17251762 let temp = tempdir ( ) . unwrap ( ) ;
@@ -1730,20 +1767,22 @@ export default defineConfig({});"#;
17301767 "vite": "^5.0.0"
17311768 }
17321769}"# ;
1733- fs:: write ( temp. path ( ) . join ( "package.json" ) , pkg_json) . unwrap ( ) ;
1770+ let package_json_path = temp. path ( ) . join ( "package.json" ) ;
1771+ fs:: write ( & package_json_path, pkg_json) . unwrap ( ) ;
17341772
1735- let skip = get_skip_packages_from_root ( temp . path ( ) ) ;
1773+ let skip = get_skip_packages_from_package_json ( & package_json_path ) ;
17361774 assert ! ( !skip. skip_vite) ;
17371775 assert ! ( !skip. skip_vitest) ;
17381776 assert ! ( !skip. skip_tsdown) ;
17391777 }
17401778
17411779 #[ test]
1742- fn test_get_skip_packages_from_root_no_package_json ( ) {
1780+ fn test_get_skip_packages_from_package_json_no_file ( ) {
17431781 let temp = tempdir ( ) . unwrap ( ) ;
17441782
17451783 // No package.json created - should return default (no skipping)
1746- let skip = get_skip_packages_from_root ( temp. path ( ) ) ;
1784+ let package_json_path = temp. path ( ) . join ( "package.json" ) ;
1785+ let skip = get_skip_packages_from_package_json ( & package_json_path) ;
17471786 assert ! ( !skip. skip_vite) ;
17481787 assert ! ( !skip. skip_vitest) ;
17491788 assert ! ( !skip. skip_tsdown) ;
@@ -1826,4 +1865,111 @@ import { build } from 'tsdown';"#;
18261865 let content = fs:: read_to_string ( temp. path ( ) . join ( "index.ts" ) ) . unwrap ( ) ;
18271866 assert_eq ! ( content, original_content) ;
18281867 }
1868+
1869+ #[ test]
1870+ fn test_find_nearest_package_json ( ) {
1871+ use std:: fs;
1872+
1873+ let temp = tempdir ( ) . unwrap ( ) ;
1874+
1875+ // Create monorepo structure
1876+ fs:: create_dir_all ( temp. path ( ) . join ( "packages/vite-plugin/src" ) ) . unwrap ( ) ;
1877+ fs:: create_dir_all ( temp. path ( ) . join ( "packages/app/src" ) ) . unwrap ( ) ;
1878+
1879+ // Root package.json (no peerDeps)
1880+ fs:: write ( temp. path ( ) . join ( "package.json" ) , r#"{"name": "monorepo"}"# ) . unwrap ( ) ;
1881+
1882+ // vite-plugin package.json (has vite in peerDeps)
1883+ fs:: write (
1884+ temp. path ( ) . join ( "packages/vite-plugin/package.json" ) ,
1885+ r#"{"name": "vite-plugin", "peerDependencies": {"vite": "^5.0.0"}}"# ,
1886+ )
1887+ . unwrap ( ) ;
1888+
1889+ // app package.json (no peerDeps)
1890+ fs:: write ( temp. path ( ) . join ( "packages/app/package.json" ) , r#"{"name": "app"}"# ) . unwrap ( ) ;
1891+
1892+ // Test finding package.json from vite-plugin/src/index.ts
1893+ let file_path = temp. path ( ) . join ( "packages/vite-plugin/src/index.ts" ) ;
1894+ let result = find_nearest_package_json ( & file_path, temp. path ( ) ) ;
1895+ assert_eq ! ( result, Some ( temp. path( ) . join( "packages/vite-plugin/package.json" ) ) ) ;
1896+
1897+ // Test finding package.json from app/src/index.ts
1898+ let file_path = temp. path ( ) . join ( "packages/app/src/index.ts" ) ;
1899+ let result = find_nearest_package_json ( & file_path, temp. path ( ) ) ;
1900+ assert_eq ! ( result, Some ( temp. path( ) . join( "packages/app/package.json" ) ) ) ;
1901+
1902+ // Test finding package.json from root level file
1903+ let file_path = temp. path ( ) . join ( "vite.config.ts" ) ;
1904+ let result = find_nearest_package_json ( & file_path, temp. path ( ) ) ;
1905+ assert_eq ! ( result, Some ( temp. path( ) . join( "package.json" ) ) ) ;
1906+ }
1907+
1908+ #[ test]
1909+ fn test_rewrite_imports_monorepo_different_peer_deps ( ) {
1910+ use std:: fs;
1911+
1912+ let temp = tempdir ( ) . unwrap ( ) ;
1913+
1914+ // Create monorepo structure
1915+ fs:: create_dir_all ( temp. path ( ) . join ( "packages/vite-plugin/src" ) ) . unwrap ( ) ;
1916+ fs:: create_dir_all ( temp. path ( ) . join ( "packages/app/src" ) ) . unwrap ( ) ;
1917+
1918+ // Root package.json (no peerDeps)
1919+ fs:: write ( temp. path ( ) . join ( "package.json" ) , r#"{"name": "monorepo"}"# ) . unwrap ( ) ;
1920+
1921+ // vite-plugin package.json (has vite in peerDeps)
1922+ fs:: write (
1923+ temp. path ( ) . join ( "packages/vite-plugin/package.json" ) ,
1924+ r#"{"name": "vite-plugin", "peerDependencies": {"vite": "^5.0.0"}}"# ,
1925+ )
1926+ . unwrap ( ) ;
1927+
1928+ // app package.json (no peerDeps)
1929+ fs:: write ( temp. path ( ) . join ( "packages/app/package.json" ) , r#"{"name": "app"}"# ) . unwrap ( ) ;
1930+
1931+ // vite-plugin source file with vite and vitest imports
1932+ fs:: write (
1933+ temp. path ( ) . join ( "packages/vite-plugin/src/index.ts" ) ,
1934+ r#"import { defineConfig } from 'vite';
1935+ import { describe } from 'vitest';
1936+ export default defineConfig({});"# ,
1937+ )
1938+ . unwrap ( ) ;
1939+
1940+ // app source file with vite and vitest imports
1941+ fs:: write (
1942+ temp. path ( ) . join ( "packages/app/src/index.ts" ) ,
1943+ r#"import { defineConfig } from 'vite';
1944+ import { describe } from 'vitest';
1945+ export default defineConfig({});"# ,
1946+ )
1947+ . unwrap ( ) ;
1948+
1949+ // Run the batch rewrite
1950+ let result = rewrite_imports_in_directory ( temp. path ( ) ) . unwrap ( ) ;
1951+
1952+ // Both files should be modified
1953+ assert_eq ! ( result. modified_files. len( ) , 2 ) ;
1954+
1955+ // vite-plugin: vite NOT rewritten (has peerDep), vitest IS rewritten
1956+ let vite_plugin_content =
1957+ fs:: read_to_string ( temp. path ( ) . join ( "packages/vite-plugin/src/index.ts" ) ) . unwrap ( ) ;
1958+ assert_eq ! (
1959+ vite_plugin_content,
1960+ r#"import { defineConfig } from 'vite';
1961+ import { describe } from '@voidzero-dev/vite-plus/test';
1962+ export default defineConfig({});"#
1963+ ) ;
1964+
1965+ // app: vite IS rewritten (no peerDep), vitest IS rewritten
1966+ let app_content =
1967+ fs:: read_to_string ( temp. path ( ) . join ( "packages/app/src/index.ts" ) ) . unwrap ( ) ;
1968+ assert_eq ! (
1969+ app_content,
1970+ r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1971+ import { describe } from '@voidzero-dev/vite-plus/test';
1972+ export default defineConfig({});"#
1973+ ) ;
1974+ }
18291975}
0 commit comments