From 05cac5b980f60f2de9f27332c3bc55f6ff9f64e0 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Thu, 16 Apr 2026 03:44:40 -0400 Subject: [PATCH] QA: fix private dist freshness check --- scripts/run-node.mjs | 13 +++++++++---- src/infra/run-node.test.ts | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/scripts/run-node.mjs b/scripts/run-node.mjs index 4c9a41b69cc..f956f308592 100644 --- a/scripts/run-node.mjs +++ b/scripts/run-node.mjs @@ -90,6 +90,11 @@ const statMtime = (filePath, fsImpl = fs) => { } }; +const resolvePrivateQaRequiredDistEntries = (distRoot) => [ + path.join(distRoot, "plugin-sdk", "qa-lab.js"), + path.join(distRoot, "plugin-sdk", "qa-runtime.js"), +]; + const isExcludedSource = (filePath, sourceRoot, sourceRootName) => { const relativePath = normalizePath(path.relative(sourceRoot, filePath)); if (relativePath.startsWith("..")) { @@ -210,8 +215,9 @@ export const resolveBuildRequirement = (deps) => { } if ( deps.env.OPENCLAW_BUILD_PRIVATE_QA === "1" && - ((deps.privateQaDistEntry && statMtime(deps.privateQaDistEntry, deps.fs) == null) || - (deps.privateQaBundledCliEntry && statMtime(deps.privateQaBundledCliEntry, deps.fs) == null)) + (deps.privateQaRequiredDistEntries ?? resolvePrivateQaRequiredDistEntries(deps.distRoot)).some( + (entry) => statMtime(entry, deps.fs) == null, + ) ) { return { shouldBuild: true, reason: "missing_private_qa_dist" }; } @@ -397,8 +403,7 @@ export async function runNodeMain(params = {}) { path: path.join(deps.cwd, sourceRoot), })); deps.configFiles = runNodeConfigFiles.map((filePath) => path.join(deps.cwd, filePath)); - deps.privateQaDistEntry = path.join(deps.distRoot, "plugin-sdk", "qa-lab.js"); - deps.privateQaBundledCliEntry = path.join(deps.distRoot, "extensions", "qa-lab", "cli.js"); + deps.privateQaRequiredDistEntries = resolvePrivateQaRequiredDistEntries(deps.distRoot); if (deps.args[0] === "qa") { deps.env.OPENCLAW_BUILD_PRIVATE_QA = "1"; deps.env.OPENCLAW_ENABLE_PRIVATE_QA_CLI = "1"; diff --git a/src/infra/run-node.test.ts b/src/infra/run-node.test.ts index abe84094171..4a10a94f25c 100644 --- a/src/infra/run-node.test.ts +++ b/src/infra/run-node.test.ts @@ -20,7 +20,7 @@ const GENERATED_A2UI_BUNDLE_HASH = "src/canvas-host/a2ui/.bundle.hash"; const DIST_ENTRY = "dist/entry.js"; const BUILD_STAMP = "dist/.buildstamp"; const QA_LAB_PLUGIN_SDK_ENTRY = "dist/plugin-sdk/qa-lab.js"; -const QA_LAB_BUNDLED_CLI_ENTRY = "dist/extensions/qa-lab/cli.js"; +const QA_RUNTIME_PLUGIN_SDK_ENTRY = "dist/plugin-sdk/qa-runtime.js"; const EXTENSION_SRC = bundledPluginFile("demo", "src/index.ts"); const EXTENSION_MANIFEST = bundledPluginFile("demo", "openclaw.plugin.json"); const EXTENSION_PACKAGE = bundledPluginFile("demo", "package.json"); @@ -343,20 +343,20 @@ describe("run-node script", () => { }); }); - it("skips rebuilding for private QA commands when the QA CLI facade is present", async () => { + it("skips rebuilding for private QA commands when the private QA facades are present", async () => { await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => { await setupTrackedProject(tmp, { files: { [ROOT_SRC]: "export const value = 1;\n", [QA_LAB_PLUGIN_SDK_ENTRY]: "export const qaLab = true;\n", - [QA_LAB_BUNDLED_CLI_ENTRY]: "export const registerQaLabCli = () => {};\n", + [QA_RUNTIME_PLUGIN_SDK_ENTRY]: "export const qaRuntime = true;\n", }, oldPaths: [ ROOT_SRC, ROOT_TSCONFIG, ROOT_PACKAGE, QA_LAB_PLUGIN_SDK_ENTRY, - QA_LAB_BUNDLED_CLI_ENTRY, + QA_RUNTIME_PLUGIN_SDK_ENTRY, ], buildPaths: [DIST_ENTRY, BUILD_STAMP], }); @@ -383,7 +383,7 @@ describe("run-node script", () => { }); }); - it("rebuilds private QA commands when the QA bundled CLI surface is missing", async () => { + it("rebuilds private QA commands when the private QA runtime facade is missing", async () => { await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => { await setupTrackedProject(tmp, { files: { @@ -417,6 +417,32 @@ describe("run-node script", () => { }); }); + it("derives private QA facade checks from distRoot for direct freshness checks", async () => { + await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => { + await setupTrackedProject(tmp, { + files: { + [ROOT_SRC]: "export const value = 1;\n", + [QA_LAB_PLUGIN_SDK_ENTRY]: "export const qaLab = true;\n", + }, + oldPaths: [ROOT_SRC, ROOT_TSCONFIG, ROOT_PACKAGE, QA_LAB_PLUGIN_SDK_ENTRY], + buildPaths: [DIST_ENTRY, BUILD_STAMP], + }); + + const requirement = resolveBuildRequirement( + createBuildRequirementDeps(tmp, { + env: { OPENCLAW_BUILD_PRIVATE_QA: "1" }, + gitHead: "abc123\n", + gitStatus: "", + }), + ); + + expect(requirement).toEqual({ + shouldBuild: true, + reason: "missing_private_qa_dist", + }); + }); + }); + it("skips runtime postbuild restaging in watch mode when dist is already current", async () => { await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => { await setupTrackedProject(tmp, {