fix(release): isolate private Telegram QA harness (#120088)

This commit is contained in:
Vincent Koc
2026-08-07 10:03:14 +08:00
committed by GitHub
parent 22ae9bd368
commit 2013123472
6 changed files with 95 additions and 14 deletions

View File

@@ -231,6 +231,16 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
install-bun: "true"
- name: Build private QA harness runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
OPENCLAW_BUILD_PRIVATE_QA: "1"
run: |
set -euo pipefail
node scripts/build-all.mjs qaRuntime
test -f dist/plugin-sdk/qa-runtime.js
test -f dist/extensions/qa-lab/runtime-api.js
- name: Validate inputs and secrets
env:
PACKAGE_SPEC: ${{ inputs.package_spec }}

View File

@@ -930,7 +930,7 @@ jobs:
package_source_sha: ${{ needs.resolve_package.outputs.package_source_sha }}
package_version: ${{ needs.resolve_package.outputs.package_version }}
package_label: openclaw@${{ needs.resolve_package.outputs.package_version }}
harness_ref: ${{ needs.resolve_package.outputs.package_source_sha || inputs.workflow_ref }}
harness_ref: ${{ inputs.workflow_ref }}
provider_mode: ${{ needs.resolve_package.outputs.telegram_mode }}
scenario: ${{ inputs.telegram_scenarios }}
secrets:

View File

@@ -1,7 +1,14 @@
// Prepares package manifests for npm Telegram live E2E scenarios.
import fs from "node:fs";
for (const packageJsonPath of process.argv.slice(2)) {
const packageJsonPaths = process.argv.slice(2);
if (packageJsonPaths.length !== 2) {
throw new Error(
`expected exactly two ephemeral package manifests, got ${packageJsonPaths.length}`,
);
}
for (const packageJsonPath of packageJsonPaths) {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
pkg.exports = pkg.exports && typeof pkg.exports === "object" ? pkg.exports : {};
if (!pkg.exports["./plugin-sdk/gateway-runtime"]) {
@@ -10,5 +17,10 @@ for (const packageJsonPath of process.argv.slice(2)) {
default: "./dist/plugin-sdk/gateway-runtime.js",
};
}
if (!pkg.exports["./plugin-sdk/qa-runtime"]) {
pkg.exports["./plugin-sdk/qa-runtime"] = {
default: "./.openclaw-qa-harness-dist/plugin-sdk/qa-runtime.js",
};
}
fs.writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
}

View File

@@ -414,6 +414,7 @@ run_logged_print_heartbeat "npm-telegram-live-suite" 60 docker_e2e_run_with_harn
"${docker_env[@]}" \
-v "$ROOT_DIR/.artifacts:/app/.artifacts" \
-v "$OUTPUT_DIR_HOST:$OUTPUT_DIR_CONTAINER" \
-v "$ROOT_DIR/dist:/app/.openclaw-qa-harness-dist:ro" \
-v "$ROOT_DIR/extensions/qa-lab:/app/extensions/qa-lab:ro" \
-v "$ROOT_DIR/qa/scenarios:/app/qa/scenarios:ro" \
-v "$npm_prefix_host:/npm-global" \
@@ -453,6 +454,8 @@ rm -rf /app/node_modules/openclaw
ln -sfnT "$openclaw_package_dir" /app/node_modules/openclaw
rm -rf /app/dist
ln -sfnT "$openclaw_package_dir/dist" /app/dist
rm -rf "$openclaw_package_dir/.openclaw-qa-harness-dist"
ln -sfnT /app/.openclaw-qa-harness-dist "$openclaw_package_dir/.openclaw-qa-harness-dist"
cp "$openclaw_package_dir/package.json" /app/package.json
node scripts/e2e/lib/npm-telegram-live/prepare-package.mjs \
/app/package.json \

View File

@@ -1,4 +1,5 @@
// Npm Telegram Live tests cover npm telegram live script behavior.
import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
@@ -267,7 +268,7 @@ describe("package Telegram live Docker E2E", () => {
expect(script).toContain("OPENCLAW_NPM_TELEGRAM_RTT_CHECKS");
});
it("keeps private QA harness imports local while using the installed package dist", () => {
it("keeps candidate runtime authoritative while mounting private QA dist separately", () => {
const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8");
const preparePackage = readFileSync(PREPARE_PACKAGE_PATH, "utf8");
const gatewayRpcClient = readFileSync(
@@ -278,15 +279,12 @@ describe("package Telegram live Docker E2E", () => {
path.resolve(TEST_DIR, "../../extensions/qa-lab/src/runtime-api.ts"),
"utf8",
);
const qaHarnessSources = [
"extensions/qa-lab/api.ts",
"extensions/qa-lab/src/self-check.ts",
"extensions/qa-lab/src/live-transports/shared/live-transport-cli.ts",
"extensions/qa-lab/src/suite-launch.runtime.ts",
"extensions/qa-lab/src/suite.ts",
].map((relativePath) => readFileSync(path.resolve(TEST_DIR, "../..", relativePath), "utf8"));
expect(script).toContain('ln -sfnT "$openclaw_package_dir/dist" /app/dist');
expect(script).toContain('-v "$ROOT_DIR/dist:/app/.openclaw-qa-harness-dist:ro"');
expect(script).toContain(
'ln -sfnT /app/.openclaw-qa-harness-dist "$openclaw_package_dir/.openclaw-qa-harness-dist"',
);
expect(script).toContain('cp "$openclaw_package_dir/package.json" /app/package.json');
expect(script).toContain('-v "$ROOT_DIR/extensions/qa-lab:/app/extensions/qa-lab:ro"');
expect(script).toContain('-v "$ROOT_DIR/qa/scenarios:/app/qa/scenarios:ro"');
@@ -295,11 +293,55 @@ describe("package Telegram live Docker E2E", () => {
expect(script).toContain("/app/node_modules/openclaw/package.json");
expect(preparePackage).toContain('pkg.exports["./plugin-sdk/gateway-runtime"]');
expect(preparePackage).toContain('"./dist/plugin-sdk/gateway-runtime.js"');
expect(preparePackage).toContain('pkg.exports["./plugin-sdk/qa-runtime"]');
expect(preparePackage).toContain('"./.openclaw-qa-harness-dist/plugin-sdk/qa-runtime.js"');
expect(gatewayRpcClient).toContain('from "openclaw/plugin-sdk/gateway-runtime"');
expect(qaRuntimeApi).toContain('from "openclaw/plugin-sdk/gateway-runtime"');
for (const source of qaHarnessSources) {
expect(source).not.toContain('from "openclaw/plugin-sdk/qa-runtime"');
});
it("adds private harness exports only to two ephemeral manifests", () => {
const root = mkTempRoot();
const packageJsonPaths = ["root-package.json", "installed-package.json"].map((name) =>
path.join(root, name),
);
for (const packageJsonPath of packageJsonPaths) {
writeFileSync(packageJsonPath, JSON.stringify({ exports: { ".": "./dist/index.js" } }));
}
const result = spawnSync(process.execPath, [PREPARE_PACKAGE_PATH, ...packageJsonPaths], {
encoding: "utf8",
});
expect(result.status).toBe(0);
for (const packageJsonPath of packageJsonPaths) {
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
exports: Record<string, unknown>;
};
expect(pkg.exports).toMatchObject({
".": "./dist/index.js",
"./plugin-sdk/gateway-runtime": {
types: "./dist/plugin-sdk/gateway-runtime.d.ts",
default: "./dist/plugin-sdk/gateway-runtime.js",
},
"./plugin-sdk/qa-runtime": {
default: "./.openclaw-qa-harness-dist/plugin-sdk/qa-runtime.js",
},
});
}
const thirdPackageJsonPath = path.join(root, "third-package.json");
writeFileSync(thirdPackageJsonPath, JSON.stringify({ exports: { ".": "./dist/index.js" } }));
const rejected = spawnSync(
process.execPath,
[PREPARE_PACKAGE_PATH, ...packageJsonPaths, thirdPackageJsonPath],
{ encoding: "utf8" },
);
expect(rejected.status).toBe(1);
expect(rejected.stderr).toContain("expected exactly two ephemeral package manifests, got 3");
expect(JSON.parse(readFileSync(thirdPackageJsonPath, "utf8"))).toEqual({
exports: { ".": "./dist/index.js" },
});
});
it("exposes installed package dependencies to the mounted QA harness", () => {

View File

@@ -1574,6 +1574,10 @@ describe("package acceptance workflow", () => {
it("offers bounded product profiles and can run Telegram against the resolved artifact", () => {
const workflow = readFileSync(PACKAGE_ACCEPTANCE_WORKFLOW, "utf8");
const npmTelegramWorkflow = readFileSync(NPM_TELEGRAM_WORKFLOW, "utf8");
const packageTelegram = workflowJob(PACKAGE_ACCEPTANCE_WORKFLOW, "package_telegram");
const dockerAcceptance = workflowJob(PACKAGE_ACCEPTANCE_WORKFLOW, "docker_acceptance");
const npmTelegram = workflowJob(NPM_TELEGRAM_WORKFLOW, "run_package_telegram_e2e");
const buildPrivateQa = workflowStep(npmTelegram, "Build private QA harness runtime");
expect(workflow).toContain("suite_profile:");
expect(workflow).toContain("published_upgrade_survivor_baseline:");
@@ -1638,9 +1642,19 @@ describe("package acceptance workflow", () => {
expect(workflow).toContain(
"package_source_sha: ${{ steps.resolve.outputs.package_source_sha }}",
);
expect(workflow).toContain(
"harness_ref: ${{ needs.resolve_package.outputs.package_source_sha || inputs.workflow_ref }}",
expect(packageTelegram.with?.harness_ref).toBe("${{ inputs.workflow_ref }}");
expect(dockerAcceptance.with?.ref).toBe(
"${{ needs.resolve_package.outputs.package_source_sha || inputs.workflow_ref }}",
);
expect(buildPrivateQa.env).toMatchObject({
NODE_OPTIONS: "--max-old-space-size=8192",
OPENCLAW_BUILD_PRIVATE_QA: "1",
});
expectTextToIncludeAll(buildPrivateQa.run, [
"node scripts/build-all.mjs qaRuntime",
"test -f dist/plugin-sdk/qa-runtime.js",
"test -f dist/extensions/qa-lab/runtime-api.js",
]);
expect(workflow).toContain('fallback_version="$(npm view openclaw@latest version)"');
expect(workflow).toContain('echo "baseline=$fallback_baseline" >> "$GITHUB_OUTPUT"');
expect(workflow).toContain(