fix: prune stale root chunks before rebuilds

This commit is contained in:
Peter Steinberger
2026-04-14 15:15:21 +01:00
parent 02a4dc1a91
commit 1795a426c9
2 changed files with 76 additions and 0 deletions

View File

@@ -16,6 +16,7 @@ const extraArgs = process.argv.slice(2);
const INEFFECTIVE_DYNAMIC_IMPORT_RE = /\[INEFFECTIVE_DYNAMIC_IMPORT\]/;
const UNRESOLVED_IMPORT_RE = /\[UNRESOLVED_IMPORT\]/;
const ANSI_ESCAPE_RE = new RegExp(String.raw`\u001B\[[0-9;]*m`, "g");
const HASHED_ROOT_JS_RE = /^(?<base>.+)-[A-Za-z0-9_-]+\.js$/u;
function removeDistPluginNodeModulesSymlinks(rootDir) {
const extensionsDir = path.join(rootDir, "extensions");
@@ -47,6 +48,34 @@ function pruneStaleRuntimeSymlinks() {
removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist-runtime"));
}
export function pruneStaleRootChunkFiles(params = {}) {
const cwd = params.cwd ?? process.cwd();
const fsImpl = params.fs ?? fs;
const roots = [path.join(cwd, "dist"), path.join(cwd, "dist-runtime")];
for (const root of roots) {
let entries = [];
try {
entries = fsImpl.readdirSync(root, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
if (!entry.isFile()) {
continue;
}
if (!HASHED_ROOT_JS_RE.test(entry.name)) {
continue;
}
try {
fsImpl.rmSync(path.join(root, entry.name), { force: true });
} catch {
// Best-effort cleanup. The subsequent build will overwrite any stragglers.
}
}
}
}
export function pruneSourceCheckoutBundledPluginNodeModules(params = {}) {
const cwd = params.cwd ?? process.cwd();
const logger = params.logger ?? console;
@@ -116,6 +145,7 @@ function isMainModule() {
if (isMainModule()) {
pruneSourceCheckoutBundledPluginNodeModules();
pruneStaleRuntimeSymlinks();
pruneStaleRootChunkFiles();
const invocation = resolveTsdownBuildInvocation();
const result = spawnSync(invocation.command, invocation.args, invocation.options);

View File

@@ -1,9 +1,15 @@
import fs from "node:fs";
import fsPromises from "node:fs/promises";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import {
pruneSourceCheckoutBundledPluginNodeModules,
pruneStaleRootChunkFiles,
resolveTsdownBuildInvocation,
} from "../../scripts/tsdown-build.mjs";
import { createScriptTestHarness } from "./test-helpers.js";
const { createTempDir } = createScriptTestHarness();
describe("resolveTsdownBuildInvocation", () => {
it("routes Windows tsdown builds through the pnpm runner instead of shell=true", () => {
@@ -56,4 +62,44 @@ describe("resolveTsdownBuildInvocation", () => {
warn.mockRestore();
rmSync.mockRestore();
});
it("prunes stale hashed root chunk files but keeps stable aliases and nested assets", async () => {
const rootDir = createTempDir("openclaw-tsdown-build-");
const distDir = path.join(rootDir, "dist");
const distRuntimeDir = path.join(rootDir, "dist-runtime");
await fsPromises.mkdir(path.join(distDir, "control-ui"), { recursive: true });
await fsPromises.mkdir(distRuntimeDir, { recursive: true });
await fsPromises.writeFile(path.join(distDir, "delegate-BPjCe4gC.js"), "old delegate\n");
await fsPromises.writeFile(path.join(distDir, "compact.runtime-2DiEmVcA.js"), "old runtime\n");
await fsPromises.writeFile(path.join(distDir, "compact.runtime.js"), "stable alias\n");
await fsPromises.writeFile(path.join(distDir, "entry.js"), "entry\n");
await fsPromises.writeFile(path.join(distDir, "control-ui", "index.html"), "asset\n");
await fsPromises.writeFile(
path.join(distRuntimeDir, "heartbeat-runner.runtime-fspOEj_1.js"),
"old runtime\n",
);
await fsPromises.writeFile(path.join(distRuntimeDir, "heartbeat-runner.runtime.js"), "alias\n");
pruneStaleRootChunkFiles({ cwd: rootDir });
await expect(
fsPromises.readFile(path.join(distDir, "compact.runtime.js"), "utf8"),
).resolves.toBe("stable alias\n");
await expect(fsPromises.readFile(path.join(distDir, "entry.js"), "utf8")).resolves.toBe(
"entry\n",
);
await expect(
fsPromises.readFile(path.join(distDir, "control-ui", "index.html"), "utf8"),
).resolves.toBe("asset\n");
await expect(
fsPromises.readFile(path.join(distRuntimeDir, "heartbeat-runner.runtime.js"), "utf8"),
).resolves.toBe("alias\n");
await expect(fsPromises.stat(path.join(distDir, "delegate-BPjCe4gC.js"))).rejects.toThrow();
await expect(
fsPromises.stat(path.join(distDir, "compact.runtime-2DiEmVcA.js")),
).rejects.toThrow();
await expect(
fsPromises.stat(path.join(distRuntimeDir, "heartbeat-runner.runtime-fspOEj_1.js")),
).rejects.toThrow();
});
});