perf(plugins): stabilize warm boundary compile skips

This commit is contained in:
Vincent Koc
2026-04-07 12:35:41 +01:00
parent 87e0353b06
commit 12864e3b21
2 changed files with 63 additions and 8 deletions

View File

@@ -106,6 +106,20 @@ export function formatBoundaryCheckSuccessSummary(params = {}) {
return `${lines.join("\n")}\n`;
}
export function formatSkippedCompileProgress(params = {}) {
const skippedCount = params.skippedCount ?? 0;
const totalCount = params.totalCount ?? 0;
if (!Number.isInteger(skippedCount) || skippedCount <= 0) {
return "";
}
const staleCount = Math.max(0, totalCount - skippedCount);
if (staleCount > 0) {
return `skipped ${skippedCount} fresh plugin compiles before running ${staleCount} stale plugin checks\n`;
}
return `skipped ${skippedCount} fresh plugin compiles\n`;
}
export function formatStepFailure(label, params = {}) {
const stdoutSection = summarizeOutputSection("stdout", params.stdout ?? "");
const stderrSection = summarizeOutputSection("stderr", params.stderr ?? "");
@@ -226,11 +240,16 @@ export function isBoundaryCompileFresh(extensionId, params = {}) {
collectNewestMtime(resolve(rootDir, "packages/plugin-sdk/dist")),
);
const oldestOutputMtimeMs = collectOldestMtime([
resolve(rootDir, "extensions", extensionId, "dist", ".boundary-tsc.tsbuildinfo"),
resolveBoundaryTsStampPath(extensionId, rootDir),
]);
return oldestOutputMtimeMs !== null && oldestOutputMtimeMs >= newestInputMtimeMs;
}
function writeStampFile(filePath) {
mkdirSync(dirname(filePath), { recursive: true });
writeFileSync(filePath, `${new Date().toISOString()}\n`, "utf8");
}
function runNodeStep(label, args, timeoutMs) {
const startedAt = Date.now();
const result = spawnSync(process.execPath, args, {
@@ -429,6 +448,7 @@ export async function runNodeStepsWithConcurrency(steps, concurrency) {
firstFailure ??= error;
},
});
step.onSuccess?.();
}
});
await Promise.allSettled(workers);
@@ -474,6 +494,10 @@ function resolveBoundaryTsBuildInfoPath(extensionId) {
return resolve(repoRoot, "extensions", extensionId, "dist", ".boundary-tsc.tsbuildinfo");
}
function resolveBoundaryTsStampPath(extensionId, rootDir = repoRoot) {
return resolve(rootDir, "extensions", extensionId, "dist", ".boundary-tsc.stamp");
}
export function resolveBoundaryCheckLockPath(rootDir = repoRoot) {
return resolve(rootDir, "dist", ".extension-package-boundary.lock");
}
@@ -525,6 +549,7 @@ async function runCompileCheck(extensionIds) {
runNodeStep("plugin-sdk boundary prep", [prepareBoundaryArtifactsBin], 420_000);
const prepElapsedMs = Date.now() - prepStartedAt;
const concurrency = resolveCompileConcurrency();
const verboseFreshLogs = process.env.OPENCLAW_EXTENSION_BOUNDARY_VERBOSE_FRESH === "1";
process.stdout.write(`compile concurrency ${concurrency}\n`);
const compileStartedAt = Date.now();
let skippedCompileCount = 0;
@@ -534,9 +559,11 @@ async function runCompileCheck(extensionIds) {
mkdirSync(dirname(tsBuildInfoPath), { recursive: true });
if (isBoundaryCompileFresh(extensionId)) {
skippedCompileCount += 1;
process.stdout.write(
`[${index + 1}/${extensionIds.length}] ${extensionId} (fresh; skipping)\n`,
);
if (verboseFreshLogs) {
process.stdout.write(
`[${index + 1}/${extensionIds.length}] ${extensionId} (fresh; skipping)\n`,
);
}
return null;
}
return {
@@ -544,6 +571,9 @@ async function runCompileCheck(extensionIds) {
onStart() {
process.stdout.write(`[${index + 1}/${extensionIds.length}] ${extensionId}\n`);
},
onSuccess() {
writeStampFile(resolveBoundaryTsStampPath(extensionId));
},
args: [
tscBin,
"-p",
@@ -557,6 +587,14 @@ async function runCompileCheck(extensionIds) {
};
})
.filter(Boolean);
if (!verboseFreshLogs && skippedCompileCount > 0) {
process.stdout.write(
formatSkippedCompileProgress({
skippedCount: skippedCompileCount,
totalCount: extensionIds.length,
}),
);
}
if (steps.length > 0) {
await runNodeStepsWithConcurrency(steps, concurrency);
}

View File

@@ -7,6 +7,7 @@ import {
acquireBoundaryCheckLock,
cleanupCanaryArtifactsForExtensions,
formatBoundaryCheckSuccessSummary,
formatSkippedCompileProgress,
formatStepFailure,
installCanaryArtifactCleanup,
isBoundaryCompileFresh,
@@ -189,11 +190,27 @@ describe("check-extension-package-tsc-boundary", () => {
);
});
it("formats skipped compile progress concisely", () => {
expect(
formatSkippedCompileProgress({
skippedCount: 13,
totalCount: 97,
}),
).toBe("skipped 13 fresh plugin compiles before running 84 stale plugin checks\n");
expect(
formatSkippedCompileProgress({
skippedCount: 97,
totalCount: 97,
}),
).toBe("skipped 97 fresh plugin compiles\n");
});
it("treats a plugin compile as fresh only when its outputs are newer than plugin and sdk inputs", () => {
const { rootDir, extensionRoot } = createTempExtensionRoot();
const extensionSourcePath = path.join(extensionRoot, "index.ts");
const extensionTsconfigPath = path.join(extensionRoot, "tsconfig.json");
const buildInfoPath = path.join(extensionRoot, "dist", ".boundary-tsc.tsbuildinfo");
const stampPath = path.join(extensionRoot, "dist", ".boundary-tsc.stamp");
const rootSdkBuildInfoPath = path.join(rootDir, "dist", "plugin-sdk", ".tsbuildinfo");
const packageSdkBuildInfoPath = path.join(
rootDir,
@@ -210,7 +227,7 @@ describe("check-extension-package-tsc-boundary", () => {
);
fs.mkdirSync(path.dirname(extensionSourcePath), { recursive: true });
fs.mkdirSync(path.dirname(buildInfoPath), { recursive: true });
fs.mkdirSync(path.dirname(stampPath), { recursive: true });
fs.mkdirSync(path.dirname(rootSdkBuildInfoPath), { recursive: true });
fs.mkdirSync(path.dirname(packageSdkBuildInfoPath), { recursive: true });
@@ -220,7 +237,7 @@ describe("check-extension-package-tsc-boundary", () => {
'{ "extends": "../tsconfig.package-boundary.base.json" }\n',
"utf8",
);
fs.writeFileSync(buildInfoPath, "ok\n", "utf8");
fs.writeFileSync(stampPath, "ok\n", "utf8");
fs.writeFileSync(rootSdkBuildInfoPath, "ok\n", "utf8");
fs.writeFileSync(packageSdkBuildInfoPath, "ok\n", "utf8");
fs.writeFileSync(entryShimStampPath, "ok\n", "utf8");
@@ -230,7 +247,7 @@ describe("check-extension-package-tsc-boundary", () => {
fs.utimesSync(rootSdkBuildInfoPath, new Date(2_000), new Date(2_000));
fs.utimesSync(packageSdkBuildInfoPath, new Date(2_000), new Date(2_000));
fs.utimesSync(entryShimStampPath, new Date(2_000), new Date(2_000));
fs.utimesSync(buildInfoPath, new Date(3_000), new Date(3_000));
fs.utimesSync(stampPath, new Date(3_000), new Date(3_000));
expect(isBoundaryCompileFresh("demo", { rootDir })).toBe(true);