






















@@ -1,5 +1,5 @@
11import { execFileSync } from "node:child_process";
2-import { appendFileSync } from "node:fs";
2+import { appendFileSync, existsSync, readFileSync } from "node:fs";
33import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mjs";
4455const DOCS_PATH_RE = /^(?:docs\/|README\.md$|AGENTS\.md$|.*\.mdx?$)/u;
@@ -12,6 +12,7 @@ const ROOT_GLOBAL_PATH_RE =
1212/^(?:package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|tsdown\.config\.ts$|vitest\.config\.ts$)/u;
1313const LIVE_DOCKER_TOOLING_PATH_RE =
1414/^(?:scripts\/test-docker-all\.mjs|scripts\/test-docker-all\.sh|scripts\/lib\/live-docker-auth\.sh|scripts\/test-live-(?:acp-bind|cli-backend|codex-harness|gateway-models|models)-docker\.sh|src\/gateway\/gateway-acp-bind\.live\.test\.ts|src\/gateway\/live-agent-probes\.test\.ts)$/u;
15+const LIVE_DOCKER_PACKAGE_SCRIPT_RE = /^test:docker:live-[\w:-]+$/u;
1516const TEST_PATH_RE =
1617/(?:^|\/)(?:test|__tests__)\/|(?:\.|\/)(?:test|spec|e2e|browser\.test)\.[cm]?[jt]sx?$/u;
1718const PUBLIC_EXTENSION_CONTRACT_RE =
@@ -66,23 +67,27 @@ export function createEmptyChangedLanes() {
66676768/**
6869 * @param {string[]} changedPaths
70+ * @param {{ packageJsonChangeKind?: "liveDockerTooling" | null }} [options]
6971 * @returns {ChangedLaneResult}
7072 */
71-export function detectChangedLanes(changedPaths) {
73+export function detectChangedLanes(changedPaths, options = {}) {
7274const paths = [...new Set(changedPaths.map(normalizeChangedPath).filter(Boolean))]
7375.toSorted((left, right) => left.localeCompare(right))
7476.filter((changedPath) => changedPath !== "--");
7577const lanes = createEmptyChangedLanes();
7678const reasons = [];
7779let extensionImpactFromCore = false;
7880let hasNonDocs = false;
81+const packageJsonIsLiveDockerTooling =
82+paths.includes("package.json") && options.packageJsonChangeKind === "liveDockerTooling";
79838084if (paths.length === 0) {
8185reasons.push("no changed paths");
8286return { paths, lanes, extensionImpactFromCore: false, docsOnly: false, reasons };
8387}
84888589if (
90+!packageJsonIsLiveDockerTooling &&
8691paths.some((changedPath) => RELEASE_METADATA_PATHS.has(changedPath)) &&
8792paths.every(
8893(changedPath) => RELEASE_METADATA_PATHS.has(changedPath) || DOCS_PATH_RE.test(changedPath),
@@ -104,6 +109,12 @@ export function detectChangedLanes(changedPaths) {
104109105110hasNonDocs = true;
106111112+if (changedPath === "package.json" && packageJsonIsLiveDockerTooling) {
113+lanes.liveDockerTooling = true;
114+reasons.push(`${changedPath}: live Docker package scripts`);
115+continue;
116+}
117+107118if (LIVE_DOCKER_TOOLING_PATH_RE.test(changedPath)) {
108119lanes.liveDockerTooling = true;
109120reasons.push(`${changedPath}: live Docker tooling surface`);
@@ -231,6 +242,94 @@ export function listStagedChangedPaths() {
231242return output.split("\n").map(normalizeChangedPath).filter(Boolean);
232243}
233244245+export function classifyPackageJsonChangeFromGit(params) {
246+try {
247+const { before, after } = readPackageJsonBeforeAfter(params);
248+return isLiveDockerPackageScriptOnlyChange(before, after) ? "liveDockerTooling" : null;
249+} catch {
250+return null;
251+}
252+}
253+254+export function isLiveDockerPackageScriptOnlyChange(before, after) {
255+const beforePackage = JSON.parse(before);
256+const afterPackage = JSON.parse(after);
257+const beforeAllowed = extractLiveDockerPackageScripts(beforePackage);
258+const afterAllowed = extractLiveDockerPackageScripts(afterPackage);
259+const beforeStripped = stripLiveDockerPackageScripts(beforePackage);
260+const afterStripped = stripLiveDockerPackageScripts(afterPackage);
261+262+return (
263+stableJson(beforeStripped) === stableJson(afterStripped) &&
264+stableJson(beforeAllowed) !== stableJson(afterAllowed)
265+);
266+}
267+268+function readPackageJsonBeforeAfter(params) {
269+const before = readGitText(params.staged ? "HEAD" : params.base, "package.json");
270+if (params.staged) {
271+return { before, after: readGitText("INDEX", "package.json") };
272+}
273+274+let after = readGitText(params.head ?? "HEAD", "package.json");
275+if (params.includeWorktree !== false && existsSync("package.json")) {
276+const worktree = readGitText("WORKTREE", "package.json");
277+if (worktree !== after) {
278+after = worktree;
279+}
280+}
281+return { before, after };
282+}
283+284+function readGitText(ref, filePath) {
285+if (ref === "WORKTREE") {
286+return readFileSync(filePath, "utf8");
287+}
288+const spec = ref === "INDEX" ? `:${filePath}` : `${ref}:${filePath}`;
289+return execFileSync("git", ["show", spec], {
290+stdio: ["ignore", "pipe", "pipe"],
291+encoding: "utf8",
292+maxBuffer: 16 * 1024 * 1024,
293+});
294+}
295+296+function extractLiveDockerPackageScripts(packageJson) {
297+const scripts = packageJson?.scripts;
298+if (!scripts || typeof scripts !== "object" || Array.isArray(scripts)) {
299+return {};
300+}
301+return Object.fromEntries(
302+Object.entries(scripts).filter(([name]) => LIVE_DOCKER_PACKAGE_SCRIPT_RE.test(name)),
303+);
304+}
305+306+function stripLiveDockerPackageScripts(packageJson) {
307+const clone = JSON.parse(JSON.stringify(packageJson));
308+const scripts = clone.scripts;
309+if (!scripts || typeof scripts !== "object" || Array.isArray(scripts)) {
310+return clone;
311+}
312+for (const name of Object.keys(scripts)) {
313+if (LIVE_DOCKER_PACKAGE_SCRIPT_RE.test(name)) {
314+delete scripts[name];
315+}
316+}
317+return clone;
318+}
319+320+function stableJson(value) {
321+if (Array.isArray(value)) {
322+return `[${value.map(stableJson).join(",")}]`;
323+}
324+if (value && typeof value === "object") {
325+return `{${Object.keys(value)
326+ .toSorted((left, right) => left.localeCompare(right))
327+ .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
328+ .join(",")}}`;
329+}
330+return JSON.stringify(value);
331+}
332+234333export function writeChangedLaneGitHubOutput(result, outputPath = process.env.GITHUB_OUTPUT) {
235334if (!outputPath) {
236335throw new Error("GITHUB_OUTPUT is required");
@@ -319,7 +418,14 @@ if (isDirectRun()) {
319418 : args.staged
320419 ? listStagedChangedPaths()
321420 : listChangedPathsFromGit({ base: args.base, head: args.head });
322-const result = detectChangedLanes(paths);
421+const packageJsonChangeKind = paths.includes("package.json")
422+ ? classifyPackageJsonChangeFromGit({
423+base: args.base,
424+head: args.head,
425+staged: args.staged,
426+})
427+ : null;
428+const result = detectChangedLanes(paths, { packageJsonChangeKind });
323429if (args.githubOutput) {
324430writeChangedLaneGitHubOutput(result);
325431}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。