fix(build): restore exact cache output snapshots (#122837)

This commit is contained in:
Peter Steinberger
2026-08-12 15:20:11 -07:00
committed by GitHub
parent 87b503675a
commit 93f5e0f1f6
2 changed files with 33 additions and 9 deletions
+8
View File
@@ -853,6 +853,14 @@ export function restoreBuildAllStepCacheOutputs(
} }
const fsImpl = params.fs ?? fs; const fsImpl = params.fs ?? fs;
const rootDir = params.rootDir ?? process.cwd(); const rootDir = params.rootDir ?? process.cwd();
const stampedOutputSet = new Set(cacheState.stampedOutputs);
// A restored snapshot owns its declared output set. Remove older checkout
// outputs first so cache hits cannot combine declarations from two builds.
for (const relativeFile of cacheState.relativeOutputFiles ?? []) {
if (!stampedOutputSet.has(normalizePortablePath(relativeFile))) {
fsImpl.rmSync(path.resolve(rootDir, relativeFile), { force: true });
}
}
for (const relativeFile of cacheState.stampedOutputs) { for (const relativeFile of cacheState.stampedOutputs) {
copyFileSync( copyFileSync(
fsImpl, fsImpl,
+25 -9
View File
@@ -778,15 +778,15 @@ describe("build-all timing output", () => {
}); });
describe("resolveBuildAllStepCacheState", () => { describe("resolveBuildAllStepCacheState", () => {
it("shares content-addressed outputs across checkout roots", () => { it("restores exact declaration snapshots across checkout roots", () => {
const cacheRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shared-build-cache-")); const cacheRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-shared-build-cache-"));
const firstRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-build-cache-source-")); const firstRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-build-cache-source-"));
const secondRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-build-cache-target-")); const secondRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-build-cache-target-"));
const step = { const step = {
label: "cached", label: "tsdown-unified",
cache: { cache: {
inputs: ["src"], inputs: ["src"],
outputs: ["dist"], outputs: [{ path: "dist", extensions: [".d.ts", ".d.mts", ".d.cts"] }],
restore: "always" as const, restore: "always" as const,
}, },
}; };
@@ -795,10 +795,18 @@ describe("resolveBuildAllStepCacheState", () => {
try { try {
for (const rootDir of [firstRoot, secondRoot]) { for (const rootDir of [firstRoot, secondRoot]) {
fs.mkdirSync(path.join(rootDir, "src"), { recursive: true }); fs.mkdirSync(path.join(rootDir, "src"), { recursive: true });
fs.mkdirSync(path.join(rootDir, "dist/plugin-sdk"), { recursive: true });
fs.writeFileSync(path.join(rootDir, "src/input.ts"), "same input"); fs.writeFileSync(path.join(rootDir, "src/input.ts"), "same input");
} }
fs.mkdirSync(path.join(firstRoot, "dist"), { recursive: true }); const currentDts = path.join(secondRoot, "dist/plugin-sdk/current.d.ts");
fs.writeFileSync(path.join(firstRoot, "dist/output.js"), "cached output"); const removedDts = path.join(secondRoot, "dist/plugin-sdk/removed-facade.d.ts");
const removedJs = path.join(secondRoot, "dist/plugin-sdk/removed-facade.js");
fs.writeFileSync(
path.join(firstRoot, "dist/plugin-sdk/current.d.ts"),
"export declare const current: true;",
);
fs.writeFileSync(removedDts, "export declare const removed: true;");
fs.writeFileSync(removedJs, "export const removed = true;");
const sourceState = resolveBuildAllStepCacheState(step, { rootDir: firstRoot, env }); const sourceState = resolveBuildAllStepCacheState(step, { rootDir: firstRoot, env });
writeBuildAllStepCacheStamp( writeBuildAllStepCacheStamp(
@@ -809,11 +817,19 @@ describe("resolveBuildAllStepCacheState", () => {
const targetState = resolveBuildAllStepCacheState(step, { rootDir: secondRoot, env }); const targetState = resolveBuildAllStepCacheState(step, { rootDir: secondRoot, env });
expect(targetState).toMatchObject({ fresh: true, restorable: true }); expect(targetState).toMatchObject({ fresh: true, restorable: true });
expect(targetState.outputRoot).toBe(path.join(cacheRoot, "cached", "outputs")); expect(targetState.outputRoot).toBe(path.join(cacheRoot, "tsdown-unified", "outputs"));
expect(restoreBuildAllStepCacheOutputs(targetState, { rootDir: secondRoot })).toBe(true); expect(restoreBuildAllStepCacheOutputs(targetState, { rootDir: secondRoot })).toBe(true);
expect(fs.readFileSync(path.join(secondRoot, "dist/output.js"), "utf8")).toBe( fs.rmSync(removedJs);
"cached output",
); expect({
current: fs.readFileSync(currentDts, "utf8"),
declaration: fs.existsSync(removedDts),
runtime: fs.existsSync(removedJs),
}).toEqual({
current: "export declare const current: true;",
declaration: false,
runtime: false,
});
} finally { } finally {
fs.rmSync(cacheRoot, { force: true, recursive: true }); fs.rmSync(cacheRoot, { force: true, recursive: true });
fs.rmSync(firstRoot, { force: true, recursive: true }); fs.rmSync(firstRoot, { force: true, recursive: true });