mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:20:42 +00:00
build: avoid ambiguous runtime aliases
(cherry picked from commit c96e62d5ab)
This commit is contained in:
@@ -16,6 +16,27 @@ export { copyStaticExtensionAssets, listStaticExtensionAssetOutputs };
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const ROOT_RUNTIME_ALIAS_PATTERN = /^(?<base>.+\.(?:runtime|contract))-[A-Za-z0-9_-]+\.js$/u;
|
||||
const ROOT_STABLE_RUNTIME_ALIAS_PATTERN = /^.+\.(?:runtime|contract)\.js$/u;
|
||||
const ROOT_RUNTIME_IMPORT_SPECIFIER_PATTERN =
|
||||
/(["'])\.\/([^"']+\.(?:runtime|contract)-[A-Za-z0-9_-]+\.js)\1/gu;
|
||||
const LEGACY_ROOT_RUNTIME_COMPAT_ALIASES = [
|
||||
// v2026.4.29 dispatch lazy chunks. Package updates used to replace the
|
||||
// dist tree before the live gateway had restarted, so an already-loaded old
|
||||
// dispatch chunk could still resolve these names after the swap.
|
||||
["abort.runtime-DX6vo4yJ.js", "abort.runtime.js"],
|
||||
["get-reply-from-config.runtime-uABrvCZ-.js", "get-reply-from-config.runtime.js"],
|
||||
["reply-media-paths.runtime-C5UnVaLF.js", "reply-media-paths.runtime.js"],
|
||||
["route-reply.runtime-D4PGzijU.js", "route-reply.runtime.js"],
|
||||
["runtime-plugins.runtime-fLHuT7Vs.js", "runtime-plugins.runtime.js"],
|
||||
["tts.runtime-66taD50M.js", "tts.runtime.js"],
|
||||
// v2026.5.2-beta.1 dispatch lazy chunks.
|
||||
["abort.runtime-CKviLU0L.js", "abort.runtime.js"],
|
||||
["get-reply-from-config.runtime-BzFAggVK.js", "get-reply-from-config.runtime.js"],
|
||||
["reply-media-paths.runtime-ZpULeITb.js", "reply-media-paths.runtime.js"],
|
||||
["route-reply.runtime-uzaOjbd1.js", "route-reply.runtime.js"],
|
||||
["runtime-plugins.runtime-CNAfmQRG.js", "runtime-plugins.runtime.js"],
|
||||
["tts.runtime-D-THXDsp.js", "tts.runtime.js"],
|
||||
];
|
||||
const LEGACY_CLI_EXIT_COMPAT_CHUNKS = [
|
||||
{
|
||||
dest: "dist/memory-state-CcqRgDZU.js",
|
||||
@@ -38,7 +59,8 @@ export function writeStableRootRuntimeAliases(params = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const candidatesByAlias = new Map();
|
||||
for (const entry of entries.toSorted((left, right) => left.name.localeCompare(right.name))) {
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
@@ -46,8 +68,97 @@ export function writeStableRootRuntimeAliases(params = {}) {
|
||||
if (!match?.groups?.base) {
|
||||
continue;
|
||||
}
|
||||
const aliasPath = path.join(distDir, `${match.groups.base}.js`);
|
||||
writeTextFileIfChanged(aliasPath, `export * from "./${entry.name}";\n`);
|
||||
const aliasFileName = `${match.groups.base}.js`;
|
||||
const candidates = candidatesByAlias.get(aliasFileName) ?? [];
|
||||
candidates.push(entry.name);
|
||||
candidatesByAlias.set(aliasFileName, candidates);
|
||||
}
|
||||
|
||||
for (const [aliasFileName, candidates] of candidatesByAlias) {
|
||||
const aliasPath = path.join(distDir, aliasFileName);
|
||||
if (candidates.length !== 1) {
|
||||
fsImpl.rmSync?.(aliasPath, { force: true });
|
||||
continue;
|
||||
}
|
||||
writeTextFileIfChanged(aliasPath, `export * from "./${candidates[0]}";\n`);
|
||||
}
|
||||
}
|
||||
|
||||
export function rewriteRootRuntimeImportsToStableAliases(params = {}) {
|
||||
const rootDir = params.rootDir ?? ROOT;
|
||||
const distDir = path.join(rootDir, "dist");
|
||||
const fsImpl = params.fs ?? fs;
|
||||
let entries = [];
|
||||
try {
|
||||
entries = fsImpl.readdirSync(distDir, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const candidatesByAlias = new Map();
|
||||
for (const entry of entries.toSorted((left, right) => left.name.localeCompare(right.name))) {
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
const match = entry.name.match(ROOT_RUNTIME_ALIAS_PATTERN);
|
||||
if (match?.groups?.base) {
|
||||
const aliasFileName = `${match.groups.base}.js`;
|
||||
const candidates = candidatesByAlias.get(aliasFileName) ?? [];
|
||||
candidates.push(entry.name);
|
||||
candidatesByAlias.set(aliasFileName, candidates);
|
||||
}
|
||||
}
|
||||
const runtimeAliasFiles = new Map();
|
||||
for (const [aliasFileName, candidates] of candidatesByAlias) {
|
||||
if (candidates.length !== 1) {
|
||||
continue;
|
||||
}
|
||||
runtimeAliasFiles.set(candidates[0], aliasFileName);
|
||||
}
|
||||
if (runtimeAliasFiles.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile() || !entry.name.endsWith(".js")) {
|
||||
continue;
|
||||
}
|
||||
if (ROOT_STABLE_RUNTIME_ALIAS_PATTERN.test(entry.name)) {
|
||||
continue;
|
||||
}
|
||||
const filePath = path.join(distDir, entry.name);
|
||||
let source;
|
||||
try {
|
||||
source = fsImpl.readFileSync(filePath, "utf8");
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const rewritten = source.replace(
|
||||
ROOT_RUNTIME_IMPORT_SPECIFIER_PATTERN,
|
||||
(specifier, quote, fileName) => {
|
||||
const aliasFileName = runtimeAliasFiles.get(fileName);
|
||||
return aliasFileName ? `${quote}./${aliasFileName}${quote}` : specifier;
|
||||
},
|
||||
);
|
||||
if (rewritten !== source) {
|
||||
writeTextFileIfChanged(filePath, rewritten);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function writeLegacyRootRuntimeCompatAliases(params = {}) {
|
||||
const rootDir = params.rootDir ?? ROOT;
|
||||
const distDir = path.join(rootDir, "dist");
|
||||
const fsImpl = params.fs ?? fs;
|
||||
for (const [legacyFileName, aliasFileName] of LEGACY_ROOT_RUNTIME_COMPAT_ALIASES) {
|
||||
const legacyPath = path.join(distDir, legacyFileName);
|
||||
if (fsImpl.existsSync(legacyPath)) {
|
||||
continue;
|
||||
}
|
||||
if (!fsImpl.existsSync(path.join(distDir, aliasFileName))) {
|
||||
continue;
|
||||
}
|
||||
writeTextFileIfChanged(legacyPath, `export * from "./${aliasFileName}";\n`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +187,9 @@ export function runRuntimePostBuild(params = {}) {
|
||||
runPhase("bundled plugin metadata", () => copyBundledPluginMetadata(params));
|
||||
runPhase("official channel catalog", () => writeOfficialChannelCatalog(params));
|
||||
runPhase("bundled plugin runtime overlay", () => stageBundledPluginRuntime(params));
|
||||
runPhase("stable root runtime imports", () => rewriteRootRuntimeImportsToStableAliases(params));
|
||||
runPhase("stable root runtime aliases", () => writeStableRootRuntimeAliases(params));
|
||||
runPhase("legacy root runtime compat aliases", () => writeLegacyRootRuntimeCompatAliases(params));
|
||||
runPhase("legacy CLI exit compat chunks", () => writeLegacyCliExitCompatChunks(params));
|
||||
runPhase("static extension assets", () =>
|
||||
copyStaticExtensionAssets({
|
||||
|
||||
@@ -5,7 +5,9 @@ import { discoverStaticExtensionAssets } from "../../scripts/lib/static-extensio
|
||||
import {
|
||||
copyStaticExtensionAssets,
|
||||
listStaticExtensionAssetOutputs,
|
||||
rewriteRootRuntimeImportsToStableAliases,
|
||||
writeLegacyCliExitCompatChunks,
|
||||
writeLegacyRootRuntimeCompatAliases,
|
||||
writeStableRootRuntimeAliases,
|
||||
} from "../../scripts/runtime-postbuild.mjs";
|
||||
import { createScriptTestHarness } from "./test-helpers.js";
|
||||
@@ -118,6 +120,138 @@ describe("runtime postbuild static assets", () => {
|
||||
await expect(fs.stat(path.join(distDir, "library.js"))).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("does not write ambiguous stable aliases for colliding root runtime chunks", async () => {
|
||||
const rootDir = createTempDir("openclaw-runtime-postbuild-");
|
||||
const distDir = path.join(rootDir, "dist");
|
||||
await fs.mkdir(distDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "install.runtime-Aaa111.js"),
|
||||
"export const pluginInstall = true;\n",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "install.runtime-Bbb222.js"),
|
||||
"export const daemonInstall = true;\n",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "install.runtime.js"),
|
||||
'export * from "./install.runtime-Stale.js";\n',
|
||||
"utf8",
|
||||
);
|
||||
|
||||
writeStableRootRuntimeAliases({ rootDir });
|
||||
|
||||
await expect(fs.stat(path.join(distDir, "install.runtime.js"))).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("rewrites root runtime imports to stable aliases", async () => {
|
||||
const rootDir = createTempDir("openclaw-runtime-postbuild-");
|
||||
const distDir = path.join(rootDir, "dist");
|
||||
await fs.mkdir(distDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "runtime-plugins.runtime-AbCd1234.js"),
|
||||
"export const ready = true;\n",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "dispatch-OldHash.js"),
|
||||
[
|
||||
'const lazy = () => import("./runtime-plugins.runtime-AbCd1234.js");',
|
||||
'import "./missing.runtime-Nope.js";',
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
rewriteRootRuntimeImportsToStableAliases({ rootDir });
|
||||
|
||||
expect(await fs.readFile(path.join(distDir, "dispatch-OldHash.js"), "utf8")).toBe(
|
||||
[
|
||||
'const lazy = () => import("./runtime-plugins.runtime.js");',
|
||||
'import "./missing.runtime-Nope.js";',
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps hashed imports when a stable runtime alias would collide", async () => {
|
||||
const rootDir = createTempDir("openclaw-runtime-postbuild-");
|
||||
const distDir = path.join(rootDir, "dist");
|
||||
await fs.mkdir(distDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "install.runtime-Aaa111.js"),
|
||||
"export const pluginInstall = true;\n",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "install.runtime-Bbb222.js"),
|
||||
"export const daemonInstall = true;\n",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "install-OldHash.js"),
|
||||
[
|
||||
'const pluginRuntime = () => import("./install.runtime-Aaa111.js");',
|
||||
'const daemonRuntime = () => import("./install.runtime-Bbb222.js");',
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
rewriteRootRuntimeImportsToStableAliases({ rootDir });
|
||||
|
||||
expect(await fs.readFile(path.join(distDir, "install-OldHash.js"), "utf8")).toBe(
|
||||
[
|
||||
'const pluginRuntime = () => import("./install.runtime-Aaa111.js");',
|
||||
'const daemonRuntime = () => import("./install.runtime-Bbb222.js");',
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves stable alias files pointing at their hashed runtime chunks", async () => {
|
||||
const rootDir = createTempDir("openclaw-runtime-postbuild-");
|
||||
const distDir = path.join(rootDir, "dist");
|
||||
await fs.mkdir(distDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "runtime-plugins.runtime-AbCd1234.js"),
|
||||
"export const ready = true;\n",
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "runtime-plugins.runtime.js"),
|
||||
'export * from "./runtime-plugins.runtime-AbCd1234.js";\n',
|
||||
"utf8",
|
||||
);
|
||||
|
||||
rewriteRootRuntimeImportsToStableAliases({ rootDir });
|
||||
|
||||
expect(await fs.readFile(path.join(distDir, "runtime-plugins.runtime.js"), "utf8")).toBe(
|
||||
'export * from "./runtime-plugins.runtime-AbCd1234.js";\n',
|
||||
);
|
||||
});
|
||||
|
||||
it("writes compatibility aliases for previous release runtime chunk names", async () => {
|
||||
const rootDir = createTempDir("openclaw-runtime-postbuild-");
|
||||
const distDir = path.join(rootDir, "dist");
|
||||
await fs.mkdir(distDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(distDir, "runtime-plugins.runtime.js"),
|
||||
'export * from "./runtime-plugins.runtime-NewHash.js";\n',
|
||||
"utf8",
|
||||
);
|
||||
|
||||
writeLegacyRootRuntimeCompatAliases({ rootDir });
|
||||
|
||||
expect(
|
||||
await fs.readFile(path.join(distDir, "runtime-plugins.runtime-fLHuT7Vs.js"), "utf8"),
|
||||
).toBe('export * from "./runtime-plugins.runtime.js";\n');
|
||||
expect(
|
||||
await fs.readFile(path.join(distDir, "runtime-plugins.runtime-CNAfmQRG.js"), "utf8"),
|
||||
).toBe('export * from "./runtime-plugins.runtime.js";\n');
|
||||
});
|
||||
|
||||
it("writes legacy CLI exit compatibility chunks", async () => {
|
||||
const rootDir = createTempDir("openclaw-runtime-postbuild-");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user