mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 03:11:39 +00:00
fix(packaging): require the code mode worker in shipped tarballs (#115178)
This commit is contained in:
committed by
GitHub
parent
2c5aff01f6
commit
981a3dfad3
@@ -8,6 +8,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { gte as semverGte, valid as validSemver } from "semver";
|
||||
import { LOCAL_BUILD_METADATA_DIST_PATHS } from "./lib/local-build-metadata-paths.mjs";
|
||||
import {
|
||||
collectPackageDistImports,
|
||||
@@ -323,6 +324,8 @@ const normalized = entries.map((entry) => entry.replace(/^package\//u, ""));
|
||||
const entrySet = new Set(normalized);
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
const CODE_MODE_WORKER_PATH = "dist/agents/code-mode.worker.js";
|
||||
const FIRST_CODE_MODE_WORKER_VERSION = "2026.5.14-beta.2";
|
||||
const REQUIRED_TARBALL_ENTRIES = ["dist/control-ui/index.html", ...WORKSPACE_TEMPLATE_PACK_PATHS];
|
||||
const PACKAGE_INSTALL_GUARD_RELATIVE_PATH = "dist/openclaw-install-guard";
|
||||
const REQUIRED_TARBALL_ENTRY_PREFIXES = ["dist/control-ui/assets/"];
|
||||
@@ -474,6 +477,12 @@ if (entrySet.has("package.json")) {
|
||||
packageVersion = "";
|
||||
}
|
||||
}
|
||||
const validPackageVersion = validSemver(packageVersion);
|
||||
const requiresCodeModeWorker =
|
||||
validPackageVersion !== null && semverGte(validPackageVersion, FIRST_CODE_MODE_WORKER_VERSION);
|
||||
if (requiresCodeModeWorker && !entrySet.has(CODE_MODE_WORKER_PATH)) {
|
||||
errors.push(`missing required tar entry ${CODE_MODE_WORKER_PATH}`);
|
||||
}
|
||||
if (entrySet.has("package-lock.json")) {
|
||||
errors.push("package tarball must not contain package-lock.json");
|
||||
}
|
||||
@@ -570,6 +579,9 @@ if (entrySet.has("dist/postinstall-inventory.json")) {
|
||||
} else {
|
||||
const normalizedInventory = inventory.map((entry) => entry.replace(/\\/gu, "/"));
|
||||
const normalizedInventorySet = new Set(normalizedInventory);
|
||||
if (requiresCodeModeWorker && !normalizedInventorySet.has(CODE_MODE_WORKER_PATH)) {
|
||||
errors.push(`postinstall inventory omits ${CODE_MODE_WORKER_PATH}`);
|
||||
}
|
||||
if (normalizedInventorySet.has(PACKAGE_INSTALL_GUARD_RELATIVE_PATH)) {
|
||||
errors.push(
|
||||
`package dist inventory must omit install guard ${PACKAGE_INSTALL_GUARD_RELATIVE_PATH}`,
|
||||
|
||||
@@ -16,6 +16,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { WORKSPACE_TEMPLATE_PACK_PATHS } from "../../scripts/lib/workspace-bootstrap-smoke.mjs";
|
||||
|
||||
const CONTROL_UI_INDEX = "dist/control-ui/index.html";
|
||||
const CODE_MODE_WORKER_PATH = "dist/agents/code-mode.worker.js";
|
||||
const CONTROL_UI_ASSETS = [
|
||||
"dist/control-ui/assets/app.js",
|
||||
"dist/control-ui/assets/app.js.br",
|
||||
@@ -65,8 +66,13 @@ function withPackedPackage(
|
||||
}),
|
||||
}),
|
||||
);
|
||||
writeFixtureFile(packageRoot, "dist/postinstall-inventory.json", JSON.stringify(inventory));
|
||||
writeFixtureFile(
|
||||
packageRoot,
|
||||
"dist/postinstall-inventory.json",
|
||||
JSON.stringify([...new Set([...inventory, CODE_MODE_WORKER_PATH])]),
|
||||
);
|
||||
writeFixtureFile(packageRoot, "dist/index.js", "export {};\n");
|
||||
writeFixtureFile(packageRoot, CODE_MODE_WORKER_PATH, "export {};\n");
|
||||
writeFixtureFile(
|
||||
packageRoot,
|
||||
CONTROL_UI_INDEX,
|
||||
@@ -215,6 +221,7 @@ describe("packaged Control UI postinstall inventory", () => {
|
||||
expect(result.stdout).toContain("OpenClaw package tarball integrity passed.");
|
||||
|
||||
const installedPackageRoot = installPackedPackage(root, tarball);
|
||||
expect(existsSync(join(installedPackageRoot, CODE_MODE_WORKER_PATH))).toBe(true);
|
||||
for (const relativePath of CONTROL_UI_FILES) {
|
||||
expect(existsSync(join(installedPackageRoot, relativePath))).toBe(true);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { delimiter, dirname, join } from "node:path";
|
||||
import { gte as semverGte, valid as validSemver } from "semver";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { LOCAL_BUILD_METADATA_DIST_PATHS } from "../../scripts/lib/local-build-metadata-paths.mjs";
|
||||
import { PACKAGE_INSTALL_GUARD_RELATIVE_PATH } from "../../scripts/lib/package-dist-inventory.ts";
|
||||
@@ -18,6 +19,8 @@ import { WORKSPACE_TEMPLATE_PACK_PATHS } from "../../scripts/lib/workspace-boots
|
||||
|
||||
const CHECK_SCRIPT = "scripts/check-openclaw-package-tarball.mjs";
|
||||
const NODE_DEFAULT_SPAWN_MAX_BUFFER_BYTES = 1024 * 1024;
|
||||
const CODE_MODE_WORKER_PATH = "dist/agents/code-mode.worker.js";
|
||||
const FIRST_CODE_MODE_WORKER_VERSION = "2026.5.14-beta.2";
|
||||
const FLAT_PLUGIN_SDK_DECLARATION = "dist/plugin-sdk/provider-entry.d.ts";
|
||||
const DEEP_PLUGIN_SDK_DECLARATION = "dist/plugin-sdk/src/plugin-sdk/provider-entry.d.ts";
|
||||
const AI_RUNTIME_PACKAGE_JSON = JSON.stringify({
|
||||
@@ -55,6 +58,8 @@ function withTarball(
|
||||
testBody: (tarball: string) => void,
|
||||
version = "2026.7.2",
|
||||
options: {
|
||||
includeCodeModeWorker?: boolean;
|
||||
includeCodeModeWorkerInInventory?: boolean;
|
||||
includeControlUi?: boolean;
|
||||
includeInstallGuard?: boolean;
|
||||
includeShrinkwrap?: boolean;
|
||||
@@ -64,6 +69,15 @@ function withTarball(
|
||||
) {
|
||||
const root = mkdtempSync(join(tmpdir(), "openclaw-package-tarball-test-"));
|
||||
try {
|
||||
const validVersion = validSemver(version);
|
||||
const includeCodeModeWorker =
|
||||
options.includeCodeModeWorker ??
|
||||
(validVersion !== null && semverGte(validVersion, FIRST_CODE_MODE_WORKER_VERSION));
|
||||
const includeCodeModeWorkerInInventory =
|
||||
options.includeCodeModeWorkerInInventory ?? includeCodeModeWorker;
|
||||
const packageInventory = includeCodeModeWorkerInInventory
|
||||
? [...new Set([...inventory, CODE_MODE_WORKER_PATH])]
|
||||
: inventory;
|
||||
const packageRoot = join(root, "package");
|
||||
mkdirSync(join(packageRoot, "dist"), { recursive: true });
|
||||
writeFileSync(
|
||||
@@ -72,7 +86,7 @@ function withTarball(
|
||||
);
|
||||
writeFileSync(
|
||||
join(packageRoot, "dist", "postinstall-inventory.json"),
|
||||
JSON.stringify(inventory),
|
||||
JSON.stringify(packageInventory),
|
||||
);
|
||||
const workspaceTemplates =
|
||||
options.includeWorkspaceTemplates === false
|
||||
@@ -113,6 +127,7 @@ function withTarball(
|
||||
...controlUiFiles,
|
||||
...installGuardFile,
|
||||
...shrinkwrapFile,
|
||||
...(includeCodeModeWorker ? { [CODE_MODE_WORKER_PATH]: "export {};\n" } : {}),
|
||||
...files,
|
||||
};
|
||||
for (const [relativePath, body] of Object.entries(tarFiles)) {
|
||||
@@ -349,6 +364,64 @@ describe("check-openclaw-package-tarball", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts historical packages published before the Code Mode worker existed", () => {
|
||||
withTarball(
|
||||
["dist/index.js"],
|
||||
{ "dist/index.js": "export {};\n" },
|
||||
(tarball) => {
|
||||
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
||||
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
expect(result.stdout).toContain("OpenClaw package tarball integrity passed.");
|
||||
},
|
||||
"2026.5.14-beta.1",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects Code Mode packages that omit the dynamically loaded worker", () => {
|
||||
withTarball(
|
||||
["dist/index.js"],
|
||||
{ "dist/index.js": "export {};\n" },
|
||||
(tarball) => {
|
||||
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain(`missing required tar entry ${CODE_MODE_WORKER_PATH}`);
|
||||
},
|
||||
FIRST_CODE_MODE_WORKER_VERSION,
|
||||
{ includeCodeModeWorker: false },
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects Code Mode workers that postinstall would remove", () => {
|
||||
withTarball(
|
||||
["dist/index.js"],
|
||||
{ "dist/index.js": "export {};\n" },
|
||||
(tarball) => {
|
||||
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain(`postinstall inventory omits ${CODE_MODE_WORKER_PATH}`);
|
||||
},
|
||||
FIRST_CODE_MODE_WORKER_VERSION,
|
||||
{ includeCodeModeWorkerInInventory: false },
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts Code Mode packages whose worker survives postinstall", () => {
|
||||
withTarball(
|
||||
["dist/index.js"],
|
||||
{ "dist/index.js": "export {};\n" },
|
||||
(tarball) => {
|
||||
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
||||
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
expect(result.stdout).toContain("OpenClaw package tarball integrity passed.");
|
||||
},
|
||||
FIRST_CODE_MODE_WORKER_VERSION,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects dist files that import missing relative chunks", () => {
|
||||
withTarball(
|
||||
["dist/cli/run-main.js"],
|
||||
|
||||
Reference in New Issue
Block a user