diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a3586fd181..31a02ff6245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai ### Changes ### Fixes +- Tests: make startup memory and startup bench smoke scripts build CLI startup artifacts when run from a fresh source checkout. - iMessage: mark authorized slash-command turns as text-sourced commands so `/status`, `/new`, and `/restart` acknowledgements return to the source conversation. (#82642) thanks @homer-byte. - Crabbox: install Corepack shims into the writable hydration `PNPM_HOME` so local AWS runner hydration no longer tries to overwrite `/usr/local/bin/pnpm`. diff --git a/package.json b/package.json index 2ae8ddf10ac..b273c045da1 100644 --- a/package.json +++ b/package.json @@ -1753,11 +1753,11 @@ "test:startup:bench": "node --import tsx scripts/bench-cli-startup.ts", "test:startup:bench:check": "node scripts/test-cli-startup-bench-budget.mjs", "test:startup:bench:save": "node --import tsx scripts/bench-cli-startup.ts --preset all --runs 5 --warmup 1 --output .artifacts/cli-startup-bench-all.json", - "test:startup:bench:smoke": "node --import tsx scripts/bench-cli-startup.ts --preset real --case gatewayStatusJson --runs 1 --warmup 0 --output .artifacts/cli-startup-bench-smoke.json", + "test:startup:bench:smoke": "node scripts/ensure-cli-startup-build.mjs && node --import tsx scripts/bench-cli-startup.ts --preset real --case gatewayStatusJson --runs 1 --warmup 0 --output .artifacts/cli-startup-bench-smoke.json", "test:startup:bench:update": "node scripts/test-update-cli-startup-bench.mjs", "test:startup:gateway": "node --import tsx scripts/bench-gateway-startup.ts", "test:restart:gateway": "node --import tsx scripts/bench-gateway-restart.ts", - "test:startup:memory": "node scripts/check-cli-startup-memory.mjs", + "test:startup:memory": "node scripts/ensure-cli-startup-build.mjs && node scripts/check-cli-startup-memory.mjs", "test:ui": "pnpm ui:i18n:check && pnpm lint:ui:no-raw-window-open && pnpm --dir ui test", "test:ui:e2e": "node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner", "test:unit": "pnpm test:unit:fast && node scripts/run-vitest.mjs run --config test/vitest/vitest.unit.config.ts", diff --git a/scripts/ensure-cli-startup-build.mjs b/scripts/ensure-cli-startup-build.mjs new file mode 100644 index 00000000000..9ee2083b671 --- /dev/null +++ b/scripts/ensure-cli-startup-build.mjs @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +import { spawnSync } from "node:child_process"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const entryCandidates = ["dist/entry.js", "dist/entry.mjs"]; + +export function hasCliStartupBuild(params = {}) { + const rootDir = params.rootDir ?? repoRoot; + const exists = params.existsSync ?? existsSync; + return entryCandidates.some((relativePath) => exists(path.join(rootDir, relativePath))); +} + +export function ensureCliStartupBuild(params = {}) { + const rootDir = params.rootDir ?? repoRoot; + if (hasCliStartupBuild({ rootDir, existsSync: params.existsSync })) { + return { built: false }; + } + + const nodeExecPath = params.nodeExecPath ?? process.execPath; + const spawn = params.spawnSync ?? spawnSync; + const buildScript = path.join(rootDir, "scripts", "build-all.mjs"); + + console.error("[cli-startup-build] dist/entry missing; running cliStartup build profile"); + const result = spawn(nodeExecPath, [buildScript, "cliStartup"], { + cwd: rootDir, + env: params.env ?? process.env, + stdio: params.stdio ?? "inherit", + }); + const status = result.status ?? (result.signal ? 1 : 0); + if (status !== 0) { + throw new Error(`cliStartup build profile failed with exit code ${status}`); + } + return { built: true }; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + try { + ensureCliStartupBuild(); + } catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} diff --git a/test/scripts/ensure-cli-startup-build.test.ts b/test/scripts/ensure-cli-startup-build.test.ts new file mode 100644 index 00000000000..9e81027e9a8 --- /dev/null +++ b/test/scripts/ensure-cli-startup-build.test.ts @@ -0,0 +1,88 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + ensureCliStartupBuild, + hasCliStartupBuild, +} from "../../scripts/ensure-cli-startup-build.mjs"; + +const tempRoots: string[] = []; + +function makeTempRoot(): string { + const root = mkdtempSync(path.join(tmpdir(), "openclaw-cli-startup-build-")); + tempRoots.push(root); + mkdirSync(path.join(root, "scripts"), { recursive: true }); + writeFileSync(path.join(root, "scripts", "build-all.mjs"), "", "utf8"); + return root; +} + +afterEach(() => { + for (const root of tempRoots.splice(0)) { + rmSync(root, { recursive: true, force: true }); + } +}); + +describe("ensure-cli-startup-build", () => { + it("detects an existing CLI startup build", () => { + const root = makeTempRoot(); + mkdirSync(path.join(root, "dist"), { recursive: true }); + writeFileSync(path.join(root, "dist", "entry.js"), "export {};\n", "utf8"); + + expect(hasCliStartupBuild({ rootDir: root })).toBe(true); + }); + + it("skips the build profile when dist entry output already exists", () => { + const root = makeTempRoot(); + mkdirSync(path.join(root, "dist"), { recursive: true }); + writeFileSync(path.join(root, "dist", "entry.mjs"), "export {};\n", "utf8"); + + const result = ensureCliStartupBuild({ + rootDir: root, + spawnSync: () => { + throw new Error("unexpected build"); + }, + }); + + expect(result).toEqual({ built: false }); + }); + + it("runs the cliStartup build profile when dist entry output is missing", () => { + const root = makeTempRoot(); + const calls: unknown[] = []; + + const result = ensureCliStartupBuild({ + rootDir: root, + nodeExecPath: "/node", + spawnSync: (command, args, options) => { + calls.push({ command, args, options }); + return { status: 0 }; + }, + stdio: "pipe", + }); + + expect(result).toEqual({ built: true }); + expect(calls).toEqual([ + { + command: "/node", + args: [path.join(root, "scripts", "build-all.mjs"), "cliStartup"], + options: expect.objectContaining({ + cwd: root, + stdio: "pipe", + }), + }, + ]); + }); + + it("fails when the cliStartup build profile fails", () => { + const root = makeTempRoot(); + + expect(() => + ensureCliStartupBuild({ + rootDir: root, + spawnSync: () => ({ status: 1 }), + stdio: "pipe", + }), + ).toThrow("cliStartup build profile failed with exit code 1"); + }); +});