




















@@ -5,13 +5,14 @@ import { tmpdir } from "node:os";
55import path from "node:path";
66import { fileURLToPath } from "node:url";
77import { parse as parseYaml } from "yaml";
8+import { listChangedPathsFromGit, listStagedChangedPaths } from "./changed-lanes.mjs";
89910const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
1011const EXACT_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u;
11121213function usage() {
1314return [
14-"Usage: node scripts/generate-npm-shrinkwrap.mjs [--check] [--all|--plugins|--package-dir <dir>]",
15+"Usage: node scripts/generate-npm-shrinkwrap.mjs [--check] [--all|--plugins|--changed|--package-dir <dir>] [--base <ref>] [--head <ref>] [--staged]",
1516" default: root package only",
1617].join("\n");
1718}
@@ -81,11 +82,10 @@ function readPnpmLockPackages() {
8182);
8283}
838484-function readPnpmLockSingleVersionOverrides() {
85-const lockfile = parseYaml(readFileSync(path.join(ROOT_DIR, "pnpm-lock.yaml"), "utf8"));
85+function collectPnpmLockPackageVersions(lockfile) {
8686const packages = lockfile?.packages;
8787if (!packages || typeof packages !== "object" || Array.isArray(packages)) {
88-throw new Error("pnpm-lock.yaml is missing package resolution data.");
88+return new Map();
8989}
9090const versionsByName = new Map();
9191for (const packageKey of Object.keys(packages)) {
@@ -97,6 +97,15 @@ function readPnpmLockSingleVersionOverrides() {
9797versions.add(parsed.version);
9898versionsByName.set(parsed.name, versions);
9999}
100+return versionsByName;
101+}
102+103+function readPnpmLockSingleVersionOverrides() {
104+const lockfile = parseYaml(readFileSync(path.join(ROOT_DIR, "pnpm-lock.yaml"), "utf8"));
105+const versionsByName = collectPnpmLockPackageVersions(lockfile);
106+if (versionsByName.size === 0) {
107+throw new Error("pnpm-lock.yaml is missing package resolution data.");
108+}
100109return Object.fromEntries(
101110[...versionsByName.entries()]
102111.filter(([, versions]) => versions.size === 1)
@@ -105,6 +114,10 @@ function readPnpmLockSingleVersionOverrides() {
105114);
106115}
107116117+function setKey(values) {
118+return [...values].toSorted((left, right) => left.localeCompare(right)).join("\0");
119+}
120+108121function mergeOverrides(packageOverrides, workspaceOverrides, pnpmLockOverrides) {
109122const merged = normalizeOverrides(packageOverrides);
110123for (const [name, spec] of [
@@ -113,9 +126,7 @@ function mergeOverrides(packageOverrides, workspaceOverrides, pnpmLockOverrides)
113126]) {
114127const current = merged[name];
115128if (current !== undefined && JSON.stringify(current) !== JSON.stringify(spec)) {
116-throw new Error(
117-`package.json overrides.${name} conflicts with pnpm lock policy for ${name}`,
118-);
129+throw new Error(`package.json overrides.${name} conflicts with pnpm lock policy for ${name}`);
119130}
120131merged[name] = spec;
121132}
@@ -394,21 +405,86 @@ function listPublishablePluginPackageDirs() {
394405.toSorted((left, right) => left.localeCompare(right));
395406}
396407408+function shrinkwrapPackageDirsForChangedPaths(changedPaths) {
409+const packageDirs = new Set();
410+const publishablePluginPackageDirs = new Set(listPublishablePluginPackageDirs());
411+let hasAmbiguousDependencyPolicyChange = false;
412+let hasLockfileChange = false;
413+414+for (const rawPath of changedPaths) {
415+const changedPath = String(rawPath ?? "")
416+.trim()
417+.replaceAll("\\", "/")
418+.replace(/^\.\/+/u, "");
419+if (!changedPath) {
420+continue;
421+}
422+if (changedPath === "package.json" || changedPath === "npm-shrinkwrap.json") {
423+packageDirs.add(ROOT_DIR);
424+continue;
425+}
426+const extensionMatch = changedPath.match(
427+/^(extensions\/[^/]+)\/(?:package\.json|npm-shrinkwrap\.json)$/u,
428+);
429+if (extensionMatch && publishablePluginPackageDirs.has(extensionMatch[1])) {
430+packageDirs.add(path.resolve(ROOT_DIR, extensionMatch[1]));
431+continue;
432+}
433+if (changedPath === "pnpm-lock.yaml") {
434+hasLockfileChange = true;
435+continue;
436+}
437+if (
438+changedPath === "pnpm-workspace.yaml" ||
439+changedPath === "scripts/generate-npm-shrinkwrap.mjs"
440+) {
441+hasAmbiguousDependencyPolicyChange = true;
442+}
443+}
444+445+if (hasAmbiguousDependencyPolicyChange) {
446+return [
447+ROOT_DIR,
448+ ...listPublishablePluginPackageDirs().map((dir) => path.resolve(ROOT_DIR, dir)),
449+];
450+}
451+452+if (hasLockfileChange) {
453+return [
454+ROOT_DIR,
455+ ...listPublishablePluginPackageDirs().map((dir) => path.resolve(ROOT_DIR, dir)),
456+];
457+}
458+return [...packageDirs].toSorted((left, right) =>
459+packageLabel(left).localeCompare(packageLabel(right)),
460+);
461+}
462+397463function resolvePackageDirs(args) {
398464const packageDirs = [];
399465const check = args.includes("--check");
400466const all = args.includes("--all");
401467const plugins = args.includes("--plugins");
468+const changed = args.includes("--changed");
469+const staged = args.includes("--staged");
402470const packageDirIndex = args.indexOf("--package-dir");
403-if (packageDirIndex !== -1 && (all || plugins)) {
404-throw new Error("--package-dir cannot be combined with --all or --plugins.");
471+const baseIndex = args.indexOf("--base");
472+const headIndex = args.indexOf("--head");
473+if (packageDirIndex !== -1 && (all || plugins || changed)) {
474+throw new Error("--package-dir cannot be combined with --all, --plugins, or --changed.");
405475}
406-if (all && plugins) {
407-throw new Error("--all cannot be combined with --plugins.");
476+if ([all, plugins, changed].filter(Boolean).length > 1) {
477+throw new Error("--all, --plugins, and --changed cannot be combined.");
408478}
409479for (let index = 0; index < args.length; index += 1) {
410480const arg = args[index];
411-if (arg === "--check" || arg === "--all" || arg === "--plugins") {
481+if (
482+arg === "--check" ||
483+arg === "--all" ||
484+arg === "--plugins" ||
485+arg === "--changed" ||
486+arg === "--staged"
487+) {
412488continue;
413489}
414490if (arg === "--package-dir") {
@@ -420,9 +496,21 @@ function resolvePackageDirs(args) {
420496index += 1;
421497continue;
422498}
499+if (arg === "--base" || arg === "--head") {
500+const value = args[index + 1];
501+if (!value || value.startsWith("--")) {
502+throw new Error(`${arg} requires a git ref.`);
503+}
504+index += 1;
505+continue;
506+}
423507throw new Error(usage());
424508}
425509510+if (!changed && (baseIndex !== -1 || headIndex !== -1 || staged)) {
511+throw new Error("--base, --head, and --staged require --changed.");
512+}
513+426514if (all) {
427515return {
428516 check,
@@ -438,6 +526,20 @@ function resolvePackageDirs(args) {
438526packageDirs: listPublishablePluginPackageDirs().map((dir) => path.resolve(ROOT_DIR, dir)),
439527};
440528}
529+if (changed) {
530+const base = baseIndex === -1 ? "origin/main" : args[baseIndex + 1];
531+const head = headIndex === -1 ? "HEAD" : args[headIndex + 1];
532+const changedPaths = staged
533+ ? listStagedChangedPaths()
534+ : listChangedPathsFromGit({
535+ base,
536+ head,
537+});
538+return {
539+ check,
540+packageDirs: shrinkwrapPackageDirsForChangedPaths(changedPaths),
541+};
542+}
441543return { check, packageDirs: packageDirs.length > 0 ? packageDirs : [ROOT_DIR] };
442544}
443545@@ -469,6 +571,10 @@ function updateOrCheckPackage(packageDir, check) {
469571470572function main() {
471573const { check, packageDirs } = resolvePackageDirs(process.argv.slice(2));
574+if (packageDirs.length === 0) {
575+process.stdout.write("No shrinkwrap-managed package changes detected.\n");
576+return;
577+}
472578for (const packageDir of packageDirs) {
473579updateOrCheckPackage(packageDir, check);
474580}
@@ -492,4 +598,5 @@ export {
492598normalizeNpmVersionDrift,
493599parsePnpmPackageKey,
494600parseLockPackagePath,
601+shrinkwrapPackageDirsForChangedPaths,
495602};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。