fix(build): refresh cached CLI help per build

This commit is contained in:
Peter Steinberger
2026-07-14 00:15:09 -07:00
parent 18ec9ce8f7
commit 63f497a428
3 changed files with 105 additions and 22 deletions
@@ -0,0 +1,42 @@
// Resolves the generated root-help bundle identity for CLI startup metadata caching.
import { createHash } from "node:crypto";
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
export function resolveCliStartupRootHelpBundleIdentity(
distDir: string,
): { bundleName: string; signature: string } | null {
const bundleName = readdirSync(distDir).find(
(entry) =>
entry.startsWith("root-help-") &&
!entry.startsWith("root-help-metadata-") &&
entry.endsWith(".js"),
);
if (!bundleName) {
return null;
}
const bundleContents = readFileSync(path.join(distDir, bundleName), "utf8");
const buildInfo = readBuildIdentity(distDir);
return {
bundleName,
signature: createHash("sha1")
.update(bundleContents)
.update(JSON.stringify(buildInfo))
.digest("hex"),
};
}
function readBuildIdentity(distDir: string): { version: string | null; commit: string | null } {
try {
const parsed = JSON.parse(readFileSync(path.join(distDir, "build-info.json"), "utf8")) as {
commit?: unknown;
version?: unknown;
};
return {
version: typeof parsed.version === "string" ? parsed.version : null,
commit: typeof parsed.commit === "string" ? parsed.commit : null,
};
} catch {
return { version: null, commit: null };
}
}
+3 -22
View File
@@ -7,6 +7,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
import pMap from "p-map";
import type { RootHelpRenderOptions } from "../src/cli/program/root-help.js";
import type { OpenClawConfig } from "../src/config/config.js";
import { resolveCliStartupRootHelpBundleIdentity } from "./lib/cli-startup-root-help-bundle.js";
import { resolveWindowsTaskkillPath } from "./lib/windows-taskkill.mjs";
function dedupe(values: string[]): string[] {
@@ -155,26 +156,6 @@ function signalCliStartupMetadataProcessTree(
child.kill(signal);
}
function resolveRootHelpBundleIdentity(
distDirOverride: string = distDir,
): { bundleName: string; signature: string } | null {
const bundleName = readdirSync(distDirOverride).find(
(entry) =>
entry.startsWith("root-help-") &&
!entry.startsWith("root-help-metadata-") &&
entry.endsWith(".js"),
);
if (!bundleName) {
return null;
}
const bundlePath = path.join(distDirOverride, bundleName);
const raw = readFileSync(bundlePath, "utf8");
return {
bundleName,
signature: createHash("sha1").update(raw).digest("hex"),
};
}
function updateHashFromFiles(
hash: ReturnType<typeof createHash>,
files: string[],
@@ -591,7 +572,7 @@ export async function renderBundledRootHelpText(
: extensionsDir,
),
): Promise<string> {
const bundleIdentity = resolveRootHelpBundleIdentity(_distDirOverride);
const bundleIdentity = resolveCliStartupRootHelpBundleIdentity(_distDirOverride);
if (!bundleIdentity) {
throw new Error("No root-help bundle found in dist; cannot write CLI startup metadata.");
}
@@ -778,7 +759,7 @@ export async function writeCliStartupMetadata(options?: {
const resolvedExtensionsDir = options?.extensionsDir ?? extensionsDir;
const resolvedSourceRootDir = options?.sourceRootDir ?? rootDir;
const channelCatalog = readBundledChannelCatalog(resolvedExtensionsDir);
const bundleIdentity = resolveRootHelpBundleIdentity(resolvedDistDir);
const bundleIdentity = resolveCliStartupRootHelpBundleIdentity(resolvedDistDir);
const browserHelpSourceSignature = resolveBrowserHelpSourceSignature(resolvedSourceRootDir);
const secretsHelpSourceSignature = resolveSecretsHelpSourceSignature(resolvedSourceRootDir);
const nodesHelpSourceSignature = resolveNodesHelpSourceSignature(resolvedSourceRootDir);
@@ -592,4 +592,64 @@ describe("write-cli-startup-metadata", () => {
expect(nodesRenderCount).toBe(3);
expect(written.nodesHelpText).toContain("openclaw nodes 3");
});
it("regenerates help when build version or commit changes", async () => {
const tempRoot = createTempDir("openclaw-startup-metadata-build-identity-");
const distDir = path.join(tempRoot, "dist");
const extensionsDir = path.join(tempRoot, "extensions");
const outputPath = path.join(distDir, "cli-startup-metadata.json");
let renderCount = 0;
writeStartupMetadataSourceSignatureFixture(tempRoot);
writeFixtureFile(distDir, "root-help-fixture.js", "export function outputRootHelp() {}\n");
const writeMetadata = async (): Promise<void> => {
await writeCliStartupMetadata({
distDir,
outputPath,
extensionsDir,
sourceRootDir: tempRoot,
renderBundledRootHelpText: async () => {
renderCount += 1;
return `Usage: openclaw ${renderCount}\n`;
},
renderSourceBrowserHelpText: () => "Usage: openclaw browser\n",
renderSourceSecretsHelpText: () => "Usage: openclaw secrets\n",
renderSourceNodesHelpText: () => "Usage: openclaw nodes\n",
renderSourceSubcommandHelpTextRecord: () => ({
doctor: "Usage: openclaw doctor\n",
gateway: "Usage: openclaw gateway\n",
models: "Usage: openclaw models\n",
plugins: "Usage: openclaw plugins\n",
sessions: "Usage: openclaw sessions\n",
tasks: "Usage: openclaw tasks\n",
}),
});
};
writeFixtureFile(
distDir,
"build-info.json",
JSON.stringify({ version: "2026.7.2", commit: "a".repeat(40) }),
);
await writeMetadata();
await writeMetadata();
expect(renderCount).toBe(1);
writeFixtureFile(
distDir,
"build-info.json",
JSON.stringify({ version: "2026.7.2", commit: "b".repeat(40) }),
);
await writeMetadata();
expect(renderCount).toBe(2);
writeFixtureFile(
distDir,
"build-info.json",
JSON.stringify({ version: "2026.7.3", commit: "b".repeat(40) }),
);
await writeMetadata();
expect(renderCount).toBe(3);
});
});