Skip to content

Commit 482040c

Browse files
committed
feat(pm): install with ignore patterns
1 parent 73eb4ec commit 482040c

2 files changed

Lines changed: 285 additions & 3 deletions

File tree

crates/vite_package_manager/src/package_manager.rs

Lines changed: 284 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,54 @@ impl PackageManager {
153153
envs: HashMap::from([("PATH".to_string(), format_path_env(self.get_bin_prefix()))]),
154154
}
155155
}
156+
157+
#[must_use]
158+
pub fn get_fingerprint_ignores(&self) -> Vec<Str> {
159+
let mut ignores: Vec<Str> = vec![
160+
// ignore all files by default
161+
"**/*".into(),
162+
// keep all package.json files except under node_modules
163+
"!**/package.json".into(),
164+
"!**/.npmrc".into(),
165+
];
166+
match self.client {
167+
PackageManagerType::Pnpm => {
168+
ignores.push("!**/pnpm-workspace.yaml".into());
169+
ignores.push("!**/pnpm-lock.yaml".into());
170+
// https://pnpm.io/pnpmfile
171+
ignores.push("!**/.pnpmfile.cjs".into());
172+
ignores.push("!**/pnpmfile.cjs".into());
173+
// pnpm support Plug'n'Play https://pnpm.io/blog/2020/10/17/node-modules-configuration-options-with-pnpm#plugnplay-the-strictest-configuration
174+
ignores.push("!**/.pnp.cjs".into());
175+
}
176+
PackageManagerType::Yarn => {
177+
ignores.push("!**/.yarnrc".into()); // yarn 1.x
178+
ignores.push("!**/.yarnrc.yml".into()); // yarn 2.x
179+
ignores.push("!**/yarn.config.cjs".into()); // yarn 2.x
180+
ignores.push("!**/yarn.lock".into());
181+
// .yarn/patches, .yarn/releases
182+
ignores.push("!**/.yarn/**/*".into());
183+
// .pnp.cjs https://yarnpkg.com/features/pnp
184+
ignores.push("!**/.pnp.cjs".into());
185+
}
186+
PackageManagerType::Npm => {
187+
ignores.push("!**/package-lock.json".into());
188+
ignores.push("!**/npm-shrinkwrap.json".into());
189+
}
190+
}
191+
// ignore all files under node_modules
192+
// e.g. node_modules/mqtt/package.json
193+
ignores.push("**/node_modules/**/*".into());
194+
// keep the node_modules directory
195+
ignores.push("!**/node_modules".into());
196+
// keep the scoped directory
197+
ignores.push("!**/node_modules/@*".into());
198+
// ignore all patterns under nested node_modules
199+
// e.g. node_modules/mqtt/node_modules/mqtt-packet/node_modules
200+
ignores.push("**/node_modules/**/node_modules/**".into());
201+
202+
ignores
203+
}
156204
}
157205

158206
/// The package root directory and its package.json file.
@@ -328,11 +376,17 @@ fn get_package_manager_type_and_version(
328376
return Ok((PackageManagerType::Npm, version, None));
329377
}
330378

331-
// if pnpmfile.cjs exists, use pnpm@latest
332-
let pnpmfile_cjs_path = workspace_root.path.join("pnpmfile.cjs");
379+
// if .pnpmfile.cjs exists, use pnpm@latest
380+
let pnpmfile_cjs_path = workspace_root.path.join(".pnpmfile.cjs");
333381
if is_exists_file(&pnpmfile_cjs_path)? {
334382
return Ok((PackageManagerType::Pnpm, version, None));
335383
}
384+
// if legacy pnpmfile.cjs exists, use pnpm@latest
385+
// https://newreleases.io/project/npm/pnpm/release/6.0.0
386+
let legacy_pnpmfile_cjs_path = workspace_root.path.join("pnpmfile.cjs");
387+
if is_exists_file(&legacy_pnpmfile_cjs_path)? {
388+
return Ok((PackageManagerType::Pnpm, version, None));
389+
}
336390

337391
// if yarn.config.cjs exists, use yarn@latest (yarn 2.0+)
338392
let yarn_config_cjs_path = workspace_root.path.join("yarn.config.cjs");
@@ -1611,4 +1665,232 @@ mod tests {
16111665
"pnpmfile.cjs should be detected before yarn.config.cjs"
16121666
);
16131667
}
1668+
1669+
// Tests for get_fingerprint_ignores method
1670+
mod get_fingerprint_ignores_tests {
1671+
use vite_glob::GlobPatternSet;
1672+
1673+
use super::*;
1674+
1675+
fn create_mock_package_manager(pm_type: PackageManagerType) -> PackageManager {
1676+
let temp_dir = create_temp_dir();
1677+
let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
1678+
let install_dir = temp_dir_path.join("install");
1679+
1680+
PackageManager {
1681+
client: pm_type,
1682+
package_name: pm_type.to_string().into(),
1683+
version: "1.0.0".into(),
1684+
hash: None,
1685+
bin_name: pm_type.to_string().into(),
1686+
workspace_root: temp_dir_path.clone(),
1687+
install_dir,
1688+
}
1689+
}
1690+
1691+
#[test]
1692+
fn test_pnpm_fingerprint_ignores() {
1693+
let pm = create_mock_package_manager(PackageManagerType::Pnpm);
1694+
let ignores = pm.get_fingerprint_ignores();
1695+
let matcher = GlobPatternSet::new(&ignores).expect("Should compile patterns");
1696+
1697+
// Should ignore most files in node_modules
1698+
assert!(
1699+
matcher.is_match("node_modules/pkg-a/index.js"),
1700+
"Should ignore implementation files"
1701+
);
1702+
assert!(
1703+
matcher.is_match("foo/bar/node_modules/pkg-a/lib/util.js"),
1704+
"Should ignore nested files"
1705+
);
1706+
assert!(matcher.is_match("node_modules/.bin/cli"), "Should ignore binaries");
1707+
1708+
// Should NOT ignore package.json files (including in node_modules)
1709+
assert!(!matcher.is_match("package.json"), "Should NOT ignore root package.json");
1710+
assert!(
1711+
!matcher.is_match("packages/app/package.json"),
1712+
"Should NOT ignore package package.json"
1713+
);
1714+
1715+
// Should ignore package.json files under node_modules
1716+
assert!(
1717+
matcher.is_match("node_modules/pkg-a/package.json"),
1718+
"Should ignore package.json in node_modules"
1719+
);
1720+
assert!(
1721+
matcher.is_match("foo/bar/node_modules/pkg-a/package.json"),
1722+
"Should ignore package.json in node_modules"
1723+
);
1724+
assert!(
1725+
matcher.is_match("node_modules/@scope/pkg-a/package.json"),
1726+
"Should ignore package.json in node_modules"
1727+
);
1728+
1729+
// Should keep node_modules directories themselves
1730+
assert!(!matcher.is_match("node_modules"), "Should NOT ignore node_modules directory");
1731+
assert!(
1732+
!matcher.is_match("packages/app/node_modules"),
1733+
"Should NOT ignore nested node_modules"
1734+
);
1735+
assert!(
1736+
matcher.is_match("node_modules/mqtt/node_modules"),
1737+
"Should ignore sub node_modules under node_modules"
1738+
);
1739+
assert!(
1740+
matcher
1741+
.is_match("node_modules/minimatch/node_modules/brace-expansion/node_modules"),
1742+
"Should ignore sub node_modules under node_modules"
1743+
);
1744+
assert!(
1745+
matcher.is_match("packages/app/node_modules/@octokit/graphql/node_modules"),
1746+
"Should ignore sub node_modules under node_modules"
1747+
);
1748+
1749+
// Should keep the root scoped directory under node_modules
1750+
assert!(!matcher.is_match("node_modules/@types"), "Should NOT ignore scoped directory");
1751+
assert!(
1752+
matcher.is_match("node_modules/@types/node"),
1753+
"Should ignore scoped sub directory"
1754+
);
1755+
1756+
// Pnpm-specific files should NOT be ignored
1757+
assert!(
1758+
!matcher.is_match("pnpm-workspace.yaml"),
1759+
"Should NOT ignore pnpm-workspace.yaml"
1760+
);
1761+
assert!(!matcher.is_match("pnpm-lock.yaml"), "Should NOT ignore pnpm-lock.yaml");
1762+
assert!(!matcher.is_match(".pnpmfile.cjs"), "Should NOT ignore .pnpmfile.cjs");
1763+
assert!(!matcher.is_match("pnpmfile.cjs"), "Should NOT ignore pnpmfile.cjs");
1764+
assert!(!matcher.is_match(".pnp.cjs"), "Should NOT ignore .pnp.cjs");
1765+
assert!(!matcher.is_match(".npmrc"), "Should NOT ignore .npmrc");
1766+
1767+
// Other package manager files should be ignored
1768+
assert!(matcher.is_match("yarn.lock"), "Should ignore yarn.lock");
1769+
assert!(matcher.is_match("package-lock.json"), "Should ignore package-lock.json");
1770+
1771+
// Regular source files should be ignored
1772+
assert!(matcher.is_match("src/index.js"), "Should ignore source files");
1773+
assert!(matcher.is_match("dist/bundle.js"), "Should ignore build outputs");
1774+
}
1775+
1776+
#[test]
1777+
fn test_yarn_fingerprint_ignores() {
1778+
let pm = create_mock_package_manager(PackageManagerType::Yarn);
1779+
let ignores = pm.get_fingerprint_ignores();
1780+
let matcher = GlobPatternSet::new(&ignores).expect("Should compile patterns");
1781+
1782+
// Should ignore most files in node_modules
1783+
assert!(
1784+
matcher.is_match("node_modules/react/index.js"),
1785+
"Should ignore implementation files"
1786+
);
1787+
assert!(
1788+
matcher.is_match("node_modules/react/cjs/react.production.js"),
1789+
"Should ignore nested files"
1790+
);
1791+
1792+
// Should NOT ignore package.json files (including in node_modules)
1793+
assert!(!matcher.is_match("package.json"), "Should NOT ignore root package.json");
1794+
assert!(
1795+
!matcher.is_match("apps/web/package.json"),
1796+
"Should NOT ignore app package.json"
1797+
);
1798+
1799+
// Should ignore package.json files under node_modules
1800+
assert!(
1801+
matcher.is_match("node_modules/react/package.json"),
1802+
"Should ignore package.json in node_modules"
1803+
);
1804+
1805+
// Should keep node_modules directories
1806+
assert!(!matcher.is_match("node_modules"), "Should NOT ignore node_modules directory");
1807+
assert!(!matcher.is_match("node_modules/@types"), "Should NOT ignore scoped packages");
1808+
1809+
// Yarn-specific files should NOT be ignored
1810+
assert!(!matcher.is_match(".yarnrc"), "Should NOT ignore .yarnrc");
1811+
assert!(!matcher.is_match(".yarnrc.yml"), "Should NOT ignore .yarnrc.yml");
1812+
assert!(!matcher.is_match("yarn.config.cjs"), "Should NOT ignore yarn.config.cjs");
1813+
assert!(!matcher.is_match("yarn.lock"), "Should NOT ignore yarn.lock");
1814+
assert!(
1815+
!matcher.is_match(".yarn/releases/yarn-4.0.0.cjs"),
1816+
"Should NOT ignore .yarn contents"
1817+
);
1818+
assert!(
1819+
!matcher.is_match(".yarn/patches/package.patch"),
1820+
"Should NOT ignore .yarn patches"
1821+
);
1822+
assert!(
1823+
!matcher.is_match(".yarn/patches/yjs-npm-13.6.21-c9f1f3397c.patch"),
1824+
"Should NOT ignore .yarn patches"
1825+
);
1826+
assert!(!matcher.is_match(".pnp.cjs"), "Should NOT ignore .pnp.cjs");
1827+
assert!(!matcher.is_match(".npmrc"), "Should NOT ignore .npmrc");
1828+
1829+
// Other package manager files should be ignored
1830+
assert!(matcher.is_match("pnpm-lock.yaml"), "Should ignore pnpm-lock.yaml");
1831+
assert!(matcher.is_match("package-lock.json"), "Should ignore package-lock.json");
1832+
1833+
// Regular source files should be ignored
1834+
assert!(matcher.is_match("src/components/Button.tsx"), "Should ignore source files");
1835+
1836+
// Should ignore nested node_modules
1837+
assert!(
1838+
matcher.is_match(
1839+
"node_modules/@mixmark-io/domino/.yarn/plugins/@yarnpkg/plugin-version.cjs"
1840+
),
1841+
"Should ignore sub node_modules under node_modules"
1842+
);
1843+
assert!(
1844+
matcher.is_match("node_modules/touch/node_modules"),
1845+
"Should ignore sub node_modules under node_modules"
1846+
);
1847+
}
1848+
1849+
#[test]
1850+
fn test_npm_fingerprint_ignores() {
1851+
let pm = create_mock_package_manager(PackageManagerType::Npm);
1852+
let ignores = pm.get_fingerprint_ignores();
1853+
let matcher = GlobPatternSet::new(&ignores).expect("Should compile patterns");
1854+
1855+
// Should ignore most files in node_modules
1856+
assert!(
1857+
matcher.is_match("node_modules/express/index.js"),
1858+
"Should ignore implementation files"
1859+
);
1860+
assert!(
1861+
matcher.is_match("node_modules/express/lib/application.js"),
1862+
"Should ignore nested files"
1863+
);
1864+
1865+
// Should NOT ignore package.json files (including in node_modules)
1866+
assert!(!matcher.is_match("package.json"), "Should NOT ignore root package.json");
1867+
assert!(!matcher.is_match("src/package.json"), "Should NOT ignore nested package.json");
1868+
1869+
// Should ignore package.json files under node_modules
1870+
assert!(
1871+
matcher.is_match("node_modules/express/package.json"),
1872+
"Should ignore package.json in node_modules"
1873+
);
1874+
1875+
// Should keep node_modules directories
1876+
assert!(!matcher.is_match("node_modules"), "Should NOT ignore node_modules directory");
1877+
assert!(!matcher.is_match("node_modules/@babel"), "Should NOT ignore scoped packages");
1878+
1879+
// Npm-specific files should NOT be ignored
1880+
assert!(!matcher.is_match("package-lock.json"), "Should NOT ignore package-lock.json");
1881+
assert!(
1882+
!matcher.is_match("npm-shrinkwrap.json"),
1883+
"Should NOT ignore npm-shrinkwrap.json"
1884+
);
1885+
assert!(!matcher.is_match(".npmrc"), "Should NOT ignore .npmrc");
1886+
1887+
// Other package manager files should be ignored
1888+
assert!(matcher.is_match("pnpm-lock.yaml"), "Should ignore pnpm-lock.yaml");
1889+
assert!(matcher.is_match("yarn.lock"), "Should ignore yarn.lock");
1890+
1891+
// Regular files should be ignored
1892+
assert!(matcher.is_match("README.md"), "Should ignore docs");
1893+
assert!(matcher.is_match("src/app.ts"), "Should ignore source files");
1894+
}
1895+
}
16141896
}

crates/vite_task/src/install.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ impl InstallCommand {
6666
iter::once("install").chain(args.iter().map(String::as_str)),
6767
ResolveCommandResult { bin_path: resolve_command.bin_path, envs: resolve_command.envs },
6868
self.ignore_replay,
69-
None,
69+
Some(package_manager.get_fingerprint_ignores()),
7070
)?;
7171
let mut task_graph: StableGraph<ResolvedTask, ()> = Default::default();
7272
task_graph.add_node(resolved_task);

0 commit comments

Comments
 (0)