From 0e4040837573145ac7cc19464ea5f72004c1d960 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 00:41:32 +0100 Subject: [PATCH] perf: speed up launcher version output --- openclaw.mjs | 153 +++++++++++++++++++++++++++++ test/openclaw-launcher.e2e.test.ts | 72 ++++++++++++++ 2 files changed, 225 insertions(+) diff --git a/openclaw.mjs b/openclaw.mjs index 807fe2edc77d..a26e6a2cba7b 100755 --- a/openclaw.mjs +++ b/openclaw.mjs @@ -41,6 +41,10 @@ const ensureSupportedNodeVersion = () => { ensureSupportedNodeVersion(); +if (tryOutputLauncherVersion(process.argv)) { + process.exit(0); +} + const isSourceCheckoutLauncher = () => existsSync(new URL("./.git", import.meta.url)) || existsSync(new URL("./src/entry.ts", import.meta.url)); @@ -445,6 +449,155 @@ const loadPrecomputedHelpText = (key) => { } }; +function tryOutputLauncherVersion(argv) { + try { + if (normalizeLauncherMetadataValue(process.env.OPENCLAW_CONTAINER)) { + return false; + } + if (!isLauncherVersionFastPathArgv(argv)) { + return false; + } + const version = resolveLauncherVersion(); + const commit = resolveLauncherCommit(); + process.stdout.write(commit ? `OpenClaw ${version} (${commit})\n` : `OpenClaw ${version}\n`); + return true; + } catch { + return false; + } +} + +function isLauncherVersionFastPathArgv(argv) { + return argv.length === 3 && (argv[2] === "--version" || argv[2] === "-V" || argv[2] === "-v"); +} + +function normalizeLauncherMetadataValue(value) { + const trimmed = typeof value === "string" ? value.trim() : ""; + return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : undefined; +} + +function readLauncherJson(relativePath) { + try { + return JSON.parse(readFileSync(new URL(relativePath, import.meta.url), "utf8")); + } catch { + return null; + } +} + +function resolveLauncherVersion() { + const packageJson = readLauncherJson("./package.json"); + const packageVersion = normalizeLauncherMetadataValue(packageJson?.version); + if (packageVersion) { + return packageVersion; + } + const buildInfo = readLauncherJson("./dist/build-info.json"); + const buildVersion = normalizeLauncherMetadataValue(buildInfo?.version); + if (buildVersion) { + return buildVersion; + } + return normalizeLauncherMetadataValue(process.env.OPENCLAW_BUNDLED_VERSION) ?? "0.0.0"; +} + +function resolveLauncherCommit() { + const envCommit = formatLauncherCommit(process.env.GIT_COMMIT ?? process.env.GIT_SHA); + if (envCommit) { + return envCommit; + } + return ( + readLauncherGitCommit() ?? + formatLauncherCommit(readLauncherJson("./dist/build-info.json")?.commit) ?? + formatLauncherCommit(readLauncherJson("./package.json")?.gitHead) ?? + formatLauncherCommit(readLauncherJson("./package.json")?.githead) + ); +} + +function formatLauncherCommit(value) { + if (typeof value !== "string") { + return null; + } + const match = value.trim().match(/[0-9a-fA-F]{7,40}/); + return match ? match[0].slice(0, 7).toLowerCase() : null; +} + +function readLauncherGitCommit() { + try { + const gitPath = fileURLToPath(new URL("./.git", import.meta.url)); + const headPath = resolveLauncherGitHeadPath(gitPath); + if (!headPath) { + return null; + } + const head = readFileSync(headPath, "utf8").trim(); + if (!head) { + return null; + } + if (!head.startsWith("ref:")) { + return formatLauncherCommit(head); + } + const ref = head.replace(/^ref:\s*/i, "").trim(); + if (!ref.startsWith("refs/") || path.isAbsolute(ref) || ref.split("/").includes("..")) { + return null; + } + const refsBase = resolveLauncherGitRefsBase(headPath); + const refPath = path.resolve(refsBase, ref); + const rel = path.relative(refsBase, refPath); + if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) { + return null; + } + try { + return formatLauncherCommit(readFileSync(refPath, "utf8")); + } catch { + return readLauncherPackedRef(refsBase, ref); + } + } catch { + return null; + } +} + +function resolveLauncherGitHeadPath(gitPath) { + try { + if (statSync(gitPath).isDirectory()) { + return path.join(gitPath, "HEAD"); + } + const raw = readFileSync(gitPath, "utf8").trim(); + if (!raw.startsWith("gitdir:")) { + return null; + } + return path.join( + path.resolve(path.dirname(gitPath), raw.slice("gitdir:".length).trim()), + "HEAD", + ); + } catch { + return null; + } +} + +function resolveLauncherGitRefsBase(headPath) { + const gitDir = path.dirname(headPath); + try { + const commonDir = readFileSync(path.join(gitDir, "commondir"), "utf8").trim(); + return commonDir ? path.resolve(gitDir, commonDir) : gitDir; + } catch { + return gitDir; + } +} + +function readLauncherPackedRef(refsBase, ref) { + try { + const packedRefs = readFileSync(path.join(refsBase, "packed-refs"), "utf8"); + for (const line of packedRefs.split("\n")) { + if (!line || line.startsWith("#") || line.startsWith("^")) { + continue; + } + const [commit, packedRef] = line.trim().split(/\s+/, 2); + if (packedRef === ref) { + return formatLauncherCommit(commit); + } + } + } catch { + // fall through + } + return null; +} + const tryOutputBareRootHelp = async () => { if (!isBareRootHelpInvocation(process.argv)) { return false; diff --git a/test/openclaw-launcher.e2e.test.ts b/test/openclaw-launcher.e2e.test.ts index 318419a30766..24bfed6ca010 100644 --- a/test/openclaw-launcher.e2e.test.ts +++ b/test/openclaw-launcher.e2e.test.ts @@ -231,6 +231,78 @@ describe("openclaw launcher", () => { expect(result.stderr).toContain("missing dist/entry.(m)js"); }); + it("prints root version without importing the runtime entry", async () => { + const fixtureRoot = await makeLauncherFixture(fixtureRoots); + await fs.writeFile( + path.join(fixtureRoot, "package.json"), + JSON.stringify({ + name: "openclaw", + version: "1.2.3-test", + gitHead: "abcdef0123456789", + }), + "utf8", + ); + await fs.writeFile( + path.join(fixtureRoot, "dist", "entry.js"), + "throw new Error('runtime entry should not load for --version');\n", + "utf8", + ); + + const result = spawnSync( + process.execPath, + [path.join(fixtureRoot, "openclaw.mjs"), "--version"], + { + cwd: fixtureRoot, + env: launcherEnv(), + encoding: "utf8", + }, + ); + + expect(result.status).toBe(0); + expect(result.stdout).toBe("OpenClaw 1.2.3-test (abcdef0)\n"); + expect(result.stderr).toBe(""); + }); + + it("defers container-targeted root version to the runtime entry", async () => { + const fixtureRoot = await makeLauncherFixture(fixtureRoots); + await fs.writeFile( + path.join(fixtureRoot, "package.json"), + JSON.stringify({ name: "openclaw", version: "1.2.3-test" }), + "utf8", + ); + await fs.writeFile( + path.join(fixtureRoot, "dist", "entry.js"), + "process.stdout.write('RUNTIME ENTRY\\n');\n", + "utf8", + ); + + const result = spawnSync( + process.execPath, + [path.join(fixtureRoot, "openclaw.mjs"), "--container", "demo", "--version"], + { + cwd: fixtureRoot, + env: launcherEnv(), + encoding: "utf8", + }, + ); + + expect(result.status).toBe(0); + expect(result.stdout).toBe("RUNTIME ENTRY\n"); + + const envResult = spawnSync( + process.execPath, + [path.join(fixtureRoot, "openclaw.mjs"), "--version"], + { + cwd: fixtureRoot, + env: launcherEnv({ OPENCLAW_CONTAINER: "demo" }), + encoding: "utf8", + }, + ); + + expect(envResult.status).toBe(0); + expect(envResult.stdout).toBe("RUNTIME ENTRY\n"); + }); + it("treats Bun direct optional import misses as direct launcher misses", async () => { const fixtureRoot = await makeLauncherProbeFixture( fixtureRoots,