




















@@ -41,6 +41,10 @@ function normalizeOverrides(overrides) {
4141return normalizeOverrideValue(overrides);
4242}
434344+function isPlainObject(value) {
45+return value && typeof value === "object" && !Array.isArray(value);
46+}
47+4448function readWorkspaceOverrides() {
4549const workspace = parseYaml(readFileSync(path.join(ROOT_DIR, "pnpm-workspace.yaml"), "utf8"));
4650return normalizeOverrides(workspace?.overrides);
@@ -162,27 +166,169 @@ function readPnpmLockVersionOverrides() {
162166);
163167}
164168169+function addNestedOverride(overrides, parentSelector, dependencyName, version, conflicts) {
170+const current = overrides[parentSelector];
171+if (current !== undefined && !isPlainObject(current)) {
172+conflicts.add(parentSelector);
173+return;
174+}
175+const nested = current ?? {};
176+const existing = nested[dependencyName];
177+if (existing !== undefined && JSON.stringify(existing) !== JSON.stringify(version)) {
178+conflicts.add(parentSelector);
179+return;
180+}
181+nested[dependencyName] = version;
182+overrides[parentSelector] = nested;
183+}
184+185+function expandScopedOverrideValue(overrides, dependencyName, version, seen = new Set()) {
186+const childSelector = `${dependencyName}@${version}`;
187+if (seen.has(childSelector)) {
188+return version;
189+}
190+const childOverrides = overrides[childSelector];
191+if (!isPlainObject(childOverrides)) {
192+return version;
193+}
194+const childSeen = new Set(seen);
195+childSeen.add(childSelector);
196+return Object.fromEntries(
197+[
198+[".", version],
199+ ...Object.entries(childOverrides).map(([nestedName, nestedVersion]) => [
200+nestedName,
201+typeof nestedVersion === "string"
202+ ? expandScopedOverrideValue(overrides, nestedName, nestedVersion, childSeen)
203+ : nestedVersion,
204+]),
205+].toSorted(([left], [right]) => left.localeCompare(right)),
206+);
207+}
208+209+function expandScopedOverrideChildren(overrides) {
210+return Object.fromEntries(
211+Object.entries(overrides)
212+.map(([parentSelector, nestedOverrides]) => [
213+parentSelector,
214+isPlainObject(nestedOverrides)
215+ ? Object.fromEntries(
216+Object.entries(nestedOverrides)
217+.map(([dependencyName, version]) => [
218+dependencyName,
219+typeof version === "string"
220+ ? expandScopedOverrideValue(overrides, dependencyName, version)
221+ : version,
222+])
223+.toSorted(([left], [right]) => left.localeCompare(right)),
224+)
225+ : typeof nestedOverrides === "string" &&
226+exactVersionFromOverrideSpec(nestedOverrides) !== null
227+ ? isPlainObject(
228+overrides[`${parentSelector}@${exactVersionFromOverrideSpec(nestedOverrides)}`],
229+)
230+ ? expandScopedOverrideValue(
231+overrides,
232+parentSelector,
233+exactVersionFromOverrideSpec(nestedOverrides),
234+)
235+ : nestedOverrides
236+ : nestedOverrides,
237+])
238+.toSorted(([left], [right]) => left.localeCompare(right)),
239+);
240+}
241+242+function readPnpmLockScopedVersionOverrides() {
243+const lockfile = parseYaml(readFileSync(path.join(ROOT_DIR, "pnpm-lock.yaml"), "utf8"));
244+const versionsByName = collectPnpmLockPackageVersions(lockfile);
245+if (versionsByName.size === 0) {
246+throw new Error("pnpm-lock.yaml is missing package resolution data.");
247+}
248+const forkedPackageNames = new Set(
249+[...versionsByName.entries()]
250+.filter(
251+([, versions]) =>
252+versions.size > 1 && pnpmLockOverrideVersionForVersions(versions) === null,
253+)
254+.map(([name]) => name),
255+);
256+if (forkedPackageNames.size === 0) {
257+return {};
258+}
259+260+const overrides = {};
261+const conflicts = new Set();
262+for (const [snapshotKey, snapshot] of Object.entries(lockfile?.snapshots ?? {})) {
263+const parent = parsePnpmPackageKey(snapshotKey);
264+const dependencies = snapshot?.dependencies;
265+if (
266+!parent ||
267+!dependencies ||
268+typeof dependencies !== "object" ||
269+Array.isArray(dependencies)
270+) {
271+continue;
272+}
273+const parentSelector = `${parent.name}@${parent.version}`;
274+for (const [dependencyName, dependencySpec] of Object.entries(dependencies)) {
275+if (!forkedPackageNames.has(dependencyName)) {
276+continue;
277+}
278+const version = exactVersionFromOverrideSpec(String(dependencySpec));
279+if (!version || !versionsByName.get(dependencyName)?.has(version)) {
280+continue;
281+}
282+addNestedOverride(overrides, parentSelector, dependencyName, version, conflicts);
283+}
284+}
285+286+for (const parentSelector of conflicts) {
287+delete overrides[parentSelector];
288+}
289+return expandScopedOverrideChildren(overrides);
290+}
291+165292function setKey(values) {
166293return [...values].toSorted((left, right) => left.localeCompare(right)).join("\0");
167294}
168295296+function mergeOverrideEntry(merged, name, spec) {
297+const current = merged[name];
298+if (current === undefined) {
299+merged[name] = spec;
300+return;
301+}
302+if (isPlainObject(current) && isPlainObject(spec)) {
303+for (const [nestedName, nestedSpec] of Object.entries(spec)) {
304+mergeOverrideEntry(current, nestedName, nestedSpec);
305+}
306+return;
307+}
308+if (JSON.stringify(current) !== JSON.stringify(spec)) {
309+throw new Error(`package.json overrides.${name} conflicts with pnpm lock policy for ${name}`);
310+}
311+}
312+169313function mergeOverrides(packageOverrides, workspaceOverrides, pnpmLockOverrides) {
170314const merged = normalizeOverrides(packageOverrides);
171315for (const [name, spec] of [
172316 ...Object.entries(workspaceOverrides),
173317 ...Object.entries(pnpmLockOverrides),
174318]) {
175-const current = merged[name];
176-if (current !== undefined && JSON.stringify(current) !== JSON.stringify(spec)) {
177-throw new Error(`package.json overrides.${name} conflicts with pnpm lock policy for ${name}`);
178-}
179-merged[name] = spec;
319+mergeOverrideEntry(merged, name, spec);
180320}
181321return Object.keys(merged).length > 0 ? merged : undefined;
182322}
183323184324function readShrinkwrapOverrides() {
185-return mergeOverrides(undefined, readWorkspaceOverrides(), readPnpmLockVersionOverrides());
325+return expandScopedOverrideChildren(
326+mergeOverrides(
327+undefined,
328+readWorkspaceOverrides(),
329+mergeOverrides(readPnpmLockVersionOverrides(), readPnpmLockScopedVersionOverrides(), {}),
330+),
331+);
186332}
187333188334function packageJsonForShrinkwrap(packageJson, shrinkwrapOverrides) {
@@ -536,6 +682,46 @@ function declaredPackageDependencies(packageJson) {
536682return dependencies;
537683}
538684685+function packageNameForLockPath(lockPath) {
686+return parseLockPackagePath(lockPath).at(-1)?.name;
687+}
688+689+function dependencyCandidatePaths(parentLockPath, dependencyName) {
690+const candidates = new Set();
691+if (parentLockPath) {
692+candidates.add(`${parentLockPath}/node_modules/${dependencyName}`);
693+}
694+695+let current = parentLockPath;
696+while (current) {
697+const nestedNodeModulesIndex = current.lastIndexOf("/node_modules/");
698+if (nestedNodeModulesIndex === -1) {
699+candidates.add(`node_modules/${dependencyName}`);
700+break;
701+}
702+const ancestorPackagePath = current.slice(0, nestedNodeModulesIndex);
703+candidates.add(`${ancestorPackagePath}/node_modules/${dependencyName}`);
704+current = ancestorPackagePath;
705+}
706+if (!parentLockPath) {
707+candidates.add(`node_modules/${dependencyName}`);
708+}
709+return [...candidates];
710+}
711+712+function resolveShrinkwrapDependency(packages, parentLockPath, dependencyName) {
713+for (const candidatePath of dependencyCandidatePaths(parentLockPath, dependencyName)) {
714+const candidate = packages[candidatePath];
715+if (candidate?.version) {
716+return {
717+path: candidatePath,
718+version: candidate.version,
719+};
720+}
721+}
722+return null;
723+}
724+539725function collectCurrentShrinkwrapOverrides(
540726shrinkwrap,
541727declaredDependencies = new Set(),
@@ -550,7 +736,7 @@ function collectCurrentShrinkwrapOverrides(
550736if (lockPath === "" || !metadata || typeof metadata !== "object" || !metadata.version) {
551737continue;
552738}
553-const packageName = metadata.name ?? parseLockPackagePath(lockPath).at(-1)?.name;
739+const packageName = metadata.name ?? packageNameForLockPath(lockPath);
554740if (
555741!packageName ||
556742declaredDependencies.has(packageName) ||
@@ -562,12 +748,47 @@ function collectCurrentShrinkwrapOverrides(
562748versions.add(metadata.version);
563749versionsByName.set(packageName, versions);
564750}
565-return Object.fromEntries(
751+752+const overrides = Object.fromEntries(
566753[...versionsByName.entries()]
567754.filter(([, versions]) => versions.size === 1)
568755.map(([name, versions]) => [name, [...versions][0]])
569756.toSorted(([left], [right]) => left.localeCompare(right)),
570757);
758+const forkedPackageNames = new Set(
759+[...versionsByName.entries()].filter(([, versions]) => versions.size > 1).map(([name]) => name),
760+);
761+const conflicts = new Set();
762+for (const [lockPath, metadata] of Object.entries(packages)) {
763+if (lockPath === "" || !metadata || typeof metadata !== "object" || !metadata.version) {
764+continue;
765+}
766+const parentName = metadata.name ?? packageNameForLockPath(lockPath);
767+const dependencies = metadata.dependencies;
768+if (
769+!parentName ||
770+!dependencies ||
771+typeof dependencies !== "object" ||
772+Array.isArray(dependencies)
773+) {
774+continue;
775+}
776+const parentSelector = `${parentName}@${metadata.version}`;
777+for (const dependencyName of Object.keys(dependencies)) {
778+if (!forkedPackageNames.has(dependencyName)) {
779+continue;
780+}
781+const resolved = resolveShrinkwrapDependency(packages, lockPath, dependencyName);
782+if (!resolved || !pnpmLockPackages.has(`${dependencyName}@${resolved.version}`)) {
783+continue;
784+}
785+addNestedOverride(overrides, parentSelector, dependencyName, resolved.version, conflicts);
786+}
787+}
788+for (const parentSelector of conflicts) {
789+delete overrides[parentSelector];
790+}
791+return expandScopedOverrideChildren(overrides);
571792}
572793573794function readCurrentShrinkwrapOverrides(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。