fix(release): scope launcher compile cache

This commit is contained in:
Peter Steinberger
2026-04-28 15:17:41 +01:00
parent e09ddbd8b6
commit 13327dbaef
3 changed files with 103 additions and 4 deletions

View File

@@ -1,9 +1,11 @@
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { existsSync, readFileSync, statSync } from "node:fs";
import { access } from "node:fs/promises";
import module from "node:module";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
const MIN_NODE_MAJOR = 22;
@@ -46,6 +48,41 @@ const isSourceCheckoutLauncher = () =>
const isNodeCompileCacheDisabled = () => process.env.NODE_DISABLE_COMPILE_CACHE !== undefined;
const isNodeCompileCacheRequested = () =>
process.env.NODE_COMPILE_CACHE !== undefined && !isNodeCompileCacheDisabled();
const sanitizeCompileCachePathSegment = (value) => {
const normalized = value.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
return normalized.length > 0 ? normalized : "unknown";
};
const readPackageVersion = () => {
try {
const parsed = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
if (typeof parsed?.version === "string" && parsed.version.trim().length > 0) {
return parsed.version;
}
} catch {
// Fall through to an install-metadata-only cache key.
}
return "unknown";
};
const resolvePackagedCompileCacheDirectory = () => {
const packageJsonUrl = new URL("./package.json", import.meta.url);
const version = sanitizeCompileCachePathSegment(readPackageVersion());
let installMarker = "no-package-json";
try {
const stat = statSync(packageJsonUrl);
installMarker = `${Math.trunc(stat.mtimeMs)}-${stat.size}`;
} catch {
// Package archives should always have package.json, but keep startup best-effort.
}
const baseDirectory = isNodeCompileCacheRequested()
? process.env.NODE_COMPILE_CACHE
: path.join(os.tmpdir(), "node-compile-cache");
return path.join(
baseDirectory,
"openclaw",
version,
sanitizeCompileCachePathSegment(installMarker),
);
};
const respawnWithoutCompileCacheIfNeeded = () => {
if (!isSourceCheckoutLauncher()) {
@@ -79,10 +116,46 @@ const respawnWithoutCompileCacheIfNeeded = () => {
respawnWithoutCompileCacheIfNeeded();
const respawnWithPackagedCompileCacheIfNeeded = () => {
if (isSourceCheckoutLauncher() || isNodeCompileCacheDisabled()) {
return false;
}
if (process.env.OPENCLAW_PACKAGED_COMPILE_CACHE_RESPAWNED === "1") {
return false;
}
const currentDirectory = module.getCompileCacheDir?.();
if (!currentDirectory) {
return false;
}
const desiredDirectory = resolvePackagedCompileCacheDirectory();
if (path.resolve(currentDirectory) === path.resolve(desiredDirectory)) {
return false;
}
const env = {
...process.env,
NODE_COMPILE_CACHE: desiredDirectory,
OPENCLAW_PACKAGED_COMPILE_CACHE_RESPAWNED: "1",
};
const result = spawnSync(
process.execPath,
[...process.execArgv, fileURLToPath(import.meta.url), ...process.argv.slice(2)],
{
stdio: "inherit",
env,
},
);
if (result.error) {
throw result.error;
}
process.exit(result.status ?? 1);
};
respawnWithPackagedCompileCacheIfNeeded();
// https://nodejs.org/api/module.html#module-compile-cache
if (module.enableCompileCache && !isNodeCompileCacheDisabled() && !isSourceCheckoutLauncher()) {
try {
module.enableCompileCache();
module.enableCompileCache(resolvePackagedCompileCacheDirectory());
} catch {
// Ignore errors
}

View File

@@ -1078,7 +1078,7 @@ gateway_listener_ready() {
gateway_log_ready() {
latest="\$(/bin/ls -t /tmp/openclaw/openclaw-*.log 2>/dev/null | /usr/bin/head -n 1 || true)"
[ -n "\$latest" ] || return 1
/usr/bin/tail -n 160 "\$latest" | /usr/bin/grep -q 'ready ('
/usr/bin/tail -n 160 "\$latest" | /usr/bin/grep -Eq 'ready( \(|[[:space:]]*\$)'
}
gateway_smoke_ready() {
gateway_listener_ready && gateway_log_ready
@@ -1682,7 +1682,7 @@ gateway_listener_ready() {
gateway_log_ready() {
latest="\$(/bin/ls -t /tmp/openclaw/openclaw-*.log 2>/dev/null | /usr/bin/head -n 1 || true)"
[ -n "\$latest" ] || return 1
/usr/bin/tail -n 160 "\$latest" | /usr/bin/grep -q 'ready ('
/usr/bin/tail -n 160 "\$latest" | /usr/bin/grep -Eq 'ready( \(|[[:space:]]*\$)'
}
gateway_smoke_ready() {
gateway_listener_ready && gateway_log_ready

View File

@@ -177,6 +177,32 @@ describe("openclaw launcher", () => {
expect(result.stdout).toBe("cache:enabled;respawn:0");
});
it("scopes packaged launcher compile cache inside configured cache roots", async () => {
const fixtureRoot = await makeLauncherFixture(fixtureRoots);
await fs.writeFile(path.join(fixtureRoot, "package.json"), '{"version":"2026.4.27-beta.1"}\n');
await fs.writeFile(
path.join(fixtureRoot, "dist", "entry.js"),
[
'import module from "node:module";',
'process.stdout.write(module.getCompileCacheDir?.() ?? "cache:disabled");',
].join("\n"),
"utf8",
);
const result = spawnSync(process.execPath, [path.join(fixtureRoot, "openclaw.mjs")], {
cwd: fixtureRoot,
env: launcherEnv({
NODE_COMPILE_CACHE: path.join(fixtureRoot, ".node-compile-cache"),
}),
encoding: "utf8",
});
expect(result.status).toBe(0);
expect(result.stdout).toContain(
path.join(".node-compile-cache", "openclaw", "2026.4.27-beta.1"),
);
});
it("enables compile cache for packaged launchers", async () => {
const fixtureRoot = await makeLauncherFixture(fixtureRoots);
await addCompileCacheProbe(fixtureRoot);