fix(release): stabilize full validation lanes

This commit is contained in:
Peter Steinberger
2026-04-27 07:46:07 +01:00
parent ddcd9d62c4
commit 8811112ab3
7 changed files with 125 additions and 3 deletions

View File

@@ -227,6 +227,25 @@ openclaw_package_dir="/npm-global/lib/node_modules/openclaw"
# point those imports at the installed package without copying source into the test image.
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
cp "$openclaw_package_dir/package.json" /app/package.json
node --input-type=module <<'NODE'
import fs from "node:fs";
const packageJsonPath = "/app/package.json";
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
pkg.exports = pkg.exports && typeof pkg.exports === "object" ? pkg.exports : {};
pkg.exports["./plugin-sdk/qa-channel"] = {
types: "./extensions/qa-channel/api.ts",
default: "./extensions/qa-channel/api.ts",
};
pkg.exports["./plugin-sdk/qa-channel-protocol"] = {
types: "./extensions/qa-channel/src/protocol.ts",
default: "./extensions/qa-channel/src/protocol.ts",
};
fs.writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
NODE
for deps_dir in "$openclaw_package_dir/node_modules" /npm-global/lib/node_modules; do
[ -d "$deps_dir" ] || continue
for dependency_dir in "$deps_dir"/*; do

View File

@@ -60,6 +60,9 @@ const OMITTED_QA_EXTENSION_PREFIXES = [
];
export const CROSS_OS_DASHBOARD_SMOKE_TIMEOUT_MS = 120_000;
export const CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS = 10_000;
export const CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS = 30_000;
export const CROSS_OS_GATEWAY_READY_TIMEOUT_MS = 3 * 60_000;
export const CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS = 5 * 60_000;
if (isMainModule()) {
try {
@@ -1629,7 +1632,14 @@ async function resolveInstalledGatewayStatusArgs(params) {
requireRpc &&
(help.stdout.includes("--require-rpc") || help.stderr.includes("--require-rpc"))
) {
return ["gateway", "status", "--deep", "--require-rpc", "--timeout", "5000"];
return [
"gateway",
"status",
"--deep",
"--require-rpc",
"--timeout",
String(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS),
];
}
return ["gateway", "status", "--deep"];
}
@@ -2370,7 +2380,9 @@ async function waitForGateway(params) {
}
function gatewayReadyDeadlineMs() {
return process.platform === "win32" ? 5 * 60 * 1000 : 90_000;
return process.platform === "win32"
? CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS
: CROSS_OS_GATEWAY_READY_TIMEOUT_MS;
}
async function resolveGatewayStatusArgs(lane, env, logPath) {
@@ -2383,7 +2395,14 @@ async function resolveGatewayStatusArgs(lane, env, logPath) {
check: false,
});
if (help.stdout.includes("--require-rpc") || help.stderr.includes("--require-rpc")) {
return ["gateway", "status", "--deep", "--require-rpc", "--timeout", "5000"];
return [
"gateway",
"status",
"--deep",
"--require-rpc",
"--timeout",
String(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS),
];
}
return ["gateway", "status", "--deep"];
}

View File

@@ -107,4 +107,56 @@ describe("prepareBundledPluginRuntimeRoot", () => {
expect(fs.lstatSync(staleMirrorChunk).isSymbolicLink()).toBe(false);
expect(fs.readFileSync(staleMirrorChunk, "utf8")).toContain("playwright-core");
});
it("does not copy staged runtime mirror dist files onto themselves", () => {
const stageDir = makeTempRoot();
const installRoot = path.join(stageDir, "openclaw-2026.4.26-alpha");
const pluginRoot = path.join(installRoot, "dist", "extensions", "qqbot");
const distChunk = path.join(installRoot, "dist", "accounts-abc123.js");
const env = { ...process.env, OPENCLAW_PLUGIN_STAGE_DIR: stageDir };
fs.mkdirSync(pluginRoot, { recursive: true });
fs.writeFileSync(
path.join(installRoot, "package.json"),
JSON.stringify({ name: "openclaw", version: "2026.4.26", type: "module" }),
"utf8",
);
fs.writeFileSync(distChunk, "export const marker = 'same-root';\n", "utf8");
fs.writeFileSync(
path.join(pluginRoot, "index.js"),
`import { marker } from "../../accounts-abc123.js"; export default { id: "qqbot", marker };\n`,
"utf8",
);
fs.writeFileSync(
path.join(pluginRoot, "package.json"),
JSON.stringify(
{
name: "@openclaw/qqbot",
version: "1.0.0",
type: "module",
dependencies: { "qqbot-runtime": "1.0.0" },
openclaw: { extensions: ["./index.js"] },
},
null,
2,
),
"utf8",
);
fs.mkdirSync(path.join(installRoot, "node_modules", "qqbot-runtime"), { recursive: true });
fs.writeFileSync(
path.join(installRoot, "node_modules", "qqbot-runtime", "package.json"),
JSON.stringify({ name: "qqbot-runtime", version: "1.0.0", type: "module" }),
"utf8",
);
const prepared = prepareBundledPluginRuntimeRoot({
pluginId: "qqbot",
pluginRoot,
modulePath: path.join(pluginRoot, "index.js"),
env,
});
expect(prepared.pluginRoot).toBe(pluginRoot);
expect(prepared.modulePath).toBe(path.join(pluginRoot, "index.js"));
expect(fs.readFileSync(distChunk, "utf8")).toContain("same-root");
});
});

View File

@@ -142,6 +142,9 @@ function prepareBundledPluginRuntimeDistMirror(params: {
}
const sourcePath = path.join(sourceDistRoot, entry.name);
const targetPath = path.join(mirrorDistRoot, entry.name);
if (path.resolve(sourcePath) === path.resolve(targetPath)) {
continue;
}
if (entry.isFile() && shouldMaterializeBundledRuntimeMirrorDistFile(sourcePath)) {
materializeBundledRuntimeMirrorDistFile(sourcePath, targetPath);
continue;
@@ -175,6 +178,9 @@ function ensureBundledRuntimeDistPackageJson(mirrorDistRoot: string): void {
}
function copyBundledPluginRuntimeRoot(sourceRoot: string, targetRoot: string): void {
if (path.resolve(sourceRoot) === path.resolve(targetRoot)) {
return;
}
fs.mkdirSync(targetRoot, { recursive: true, mode: 0o755 });
for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
if (entry.name === "node_modules") {

View File

@@ -791,6 +791,9 @@ function mirrorBundledRuntimeDistRootEntries(params: {
}
const sourcePath = path.join(params.sourceDistRoot, entry.name);
const targetPath = path.join(params.mirrorDistRoot, entry.name);
if (path.resolve(sourcePath) === path.resolve(targetPath)) {
continue;
}
if (entry.isFile() && shouldMaterializeBundledRuntimeMirrorDistFile(sourcePath)) {
materializeBundledRuntimeMirrorDistFile(sourcePath, targetPath);
continue;
@@ -860,6 +863,9 @@ function ensureBundledRuntimeDistPackageJson(mirrorDistRoot: string): void {
}
function copyBundledPluginRuntimeRoot(sourceRoot: string, targetRoot: string): void {
if (path.resolve(sourceRoot) === path.resolve(targetRoot)) {
return;
}
fs.mkdirSync(targetRoot, { recursive: true, mode: 0o755 });
for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
if (entry.name === "node_modules") {

View File

@@ -55,6 +55,17 @@ describe("package Telegram live Docker E2E", () => {
);
});
it("keeps private QA harness imports local while using the installed package dist", () => {
const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8");
expect(script).toContain('ln -sfnT "$openclaw_package_dir/dist" /app/dist');
expect(script).toContain('cp "$openclaw_package_dir/package.json" /app/package.json');
expect(script).toContain('pkg.exports["./plugin-sdk/qa-channel"]');
expect(script).toContain('"./extensions/qa-channel/api.ts"');
expect(script).toContain('pkg.exports["./plugin-sdk/qa-channel-protocol"]');
expect(script).toContain('"./extensions/qa-channel/src/protocol.ts"');
});
it("lets npm-specific credential aliases override shared QA env", () => {
expect(
__testing.resolveCredentialSource({

View File

@@ -12,6 +12,9 @@ import {
canConnectToLoopbackPort,
buildDiscordSmokeGuildsConfig,
buildRealUpdateEnv,
CROSS_OS_GATEWAY_READY_TIMEOUT_MS,
CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS,
CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS,
CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS,
CROSS_OS_DASHBOARD_SMOKE_TIMEOUT_MS,
isImmutableReleaseRef,
@@ -46,6 +49,12 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
expect(CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS).toBeGreaterThanOrEqual(10_000);
});
it("keeps gateway RPC status probes patient enough for live release startup", () => {
expect(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS).toBeGreaterThanOrEqual(30_000);
expect(CROSS_OS_GATEWAY_READY_TIMEOUT_MS).toBeGreaterThanOrEqual(180_000);
expect(CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS).toBeGreaterThanOrEqual(300_000);
});
it("accepts OK agent output from the captured log when stdout is empty", () => {
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-agent-output-"));
try {