Skip to content

Commit 631b9b5

Browse files
committed
fix(metro-config): fix duplicate packages slipping through
1 parent 0626a22 commit 631b9b5

3 files changed

Lines changed: 63 additions & 7 deletions

File tree

.changeset/shaggy-dragons-fry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@rnx-kit/metro-config": patch
3+
---
4+
5+
Fix duplicate packages slipping through in a Yarn + pnpm setup

packages/metro-config/src/index.js

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,30 @@ function defaultWatchFolders() {
6363
}
6464
}
6565

66+
/**
67+
* Extracts unique parts from a Yarn store directory.
68+
* @param {string} p
69+
* @returns {[string, string]}
70+
*/
71+
function extractUniquePartsFromYarnStoreDir(p) {
72+
const parts = p.split("-");
73+
const length = parts.length;
74+
75+
// Example: node_modules/.store/react-native-virtual-3e97acc5aa
76+
if (parts[length - 2] === "virtual") {
77+
return [parts.slice(0, length - 1).join("-"), parts.slice(-1).join("-")];
78+
}
79+
80+
// Example: node_modules/.store/@react-native-assets-registry-npm-0.81.0-rc.5-d313abaf5e
81+
const index = parts.lastIndexOf("npm");
82+
if (index > 0) {
83+
const pos = index + 1;
84+
return [parts.slice(0, pos).join("-"), parts.slice(pos).join("-")];
85+
}
86+
87+
throw new Error(`Failed to parse Yarn store directory: ${p}`);
88+
}
89+
6690
/**
6791
* Returns the path to specified module; `undefined` if not found.
6892
*
@@ -107,14 +131,10 @@ function resolveUniqueModule(packageName, searchStartDir) {
107131
// - react-native -> node_modules/.store/react-native-virtual-3e97acc5aa/package
108132
if (path.basename(result) === "package" && result.includes(".store")) {
109133
const storePath = path.dirname(result);
110-
const hashIndex = storePath.lastIndexOf("-") + 1;
111-
const hashPart = storePath.substring(hashIndex);
112-
const parent = path
113-
.normalize(storePath.substring(0, hashIndex))
114-
.replaceAll("\\", "\\\\")
115-
.replaceAll(".", "\\.");
134+
const [pre, unique] = extractUniquePartsFromYarnStoreDir(storePath);
135+
const preEscaped = pre.replaceAll("\\", "\\\\").replaceAll(".", "\\.");
116136
const exclusionRE = new RegExp(
117-
`${parent}\\w+(?<!${hashPart})\\${path.sep}package\\${path.sep}.*`
137+
`${preEscaped}-[-.\\w]+(?<!${unique})\\${path.sep}package\\${path.sep}.*`
118138
);
119139
return [result, exclusionRE];
120140
}
@@ -278,6 +298,7 @@ module.exports = {
278298
defaultWatchFolders,
279299
excludeExtraCopiesOf,
280300
exclusionList,
301+
extractUniquePartsFromYarnStoreDir,
281302
resolveUniqueModule,
282303

283304
/**

packages/metro-config/test/index.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { enhanceMiddleware } from "../src/assetPluginForMonorepos";
66
import {
77
defaultWatchFolders,
88
exclusionList,
9+
extractUniquePartsFromYarnStoreDir,
910
makeMetroConfig,
1011
resolveUniqueModule,
1112
} from "../src/index";
@@ -55,6 +56,35 @@ describe("defaultWatchFolders()", () => {
5556
});
5657
});
5758

59+
describe("extractUniquePartsFromYarnStoreDir()", () => {
60+
it("returns version+hash from a versioned path", () => {
61+
const [pre, unique] = extractUniquePartsFromYarnStoreDir(
62+
"node_modules/.store/@babel-core-npm-7.27.1-0f1bf48e52"
63+
);
64+
65+
equal(pre, "node_modules/.store/@babel-core-npm");
66+
equal(unique, "7.27.1-0f1bf48e52");
67+
});
68+
69+
it("returns version+hash from a path with prerelease number", () => {
70+
const [pre, unique] = extractUniquePartsFromYarnStoreDir(
71+
"node_modules/.store/@react-native-assets-registry-npm-0.81.0-rc.5-d313abaf5e"
72+
);
73+
74+
equal(pre, "node_modules/.store/@react-native-assets-registry-npm");
75+
equal(unique, "0.81.0-rc.5-d313abaf5e");
76+
});
77+
78+
it("returns hash from a virtual path", () => {
79+
const [pre, unique] = extractUniquePartsFromYarnStoreDir(
80+
"node_modules/.store/react-native-virtual-3e97acc5aa"
81+
);
82+
83+
equal(pre, "node_modules/.store/react-native-virtual");
84+
equal(unique, "3e97acc5aa");
85+
});
86+
});
87+
5888
describe("resolveUniqueModule()", () => {
5989
afterEach(() => process.chdir(currentWorkingDir));
6090

0 commit comments

Comments
 (0)