Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
484 lines
16 KiB
TypeScript
484 lines
16 KiB
TypeScript
// Check Openclaw Package Tarball tests cover check openclaw package tarball script behavior.
|
|
import { spawnSync } from "node:child_process";
|
|
import {
|
|
chmodSync,
|
|
existsSync,
|
|
mkdtempSync,
|
|
mkdirSync,
|
|
readFileSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { delimiter, dirname, join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { LOCAL_BUILD_METADATA_DIST_PATHS } from "../../scripts/lib/local-build-metadata-paths.mjs";
|
|
|
|
const CHECK_SCRIPT = "scripts/check-openclaw-package-tarball.mjs";
|
|
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";
|
|
|
|
function withTarball(
|
|
inventory: string[],
|
|
files: Record<string, string>,
|
|
testBody: (tarball: string) => void,
|
|
version = "0.0.0",
|
|
options: { includeControlUi?: boolean; includeShrinkwrap?: boolean } = {},
|
|
) {
|
|
const root = mkdtempSync(join(tmpdir(), "openclaw-package-tarball-test-"));
|
|
try {
|
|
const packageRoot = join(root, "package");
|
|
mkdirSync(join(packageRoot, "dist"), { recursive: true });
|
|
writeFileSync(join(packageRoot, "package.json"), JSON.stringify({ name: "openclaw", version }));
|
|
if (options.includeShrinkwrap !== false) {
|
|
writeFileSync(
|
|
join(packageRoot, "npm-shrinkwrap.json"),
|
|
JSON.stringify({
|
|
name: "openclaw",
|
|
version,
|
|
lockfileVersion: 3,
|
|
packages: {
|
|
"": {
|
|
name: "openclaw",
|
|
version,
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
writeFileSync(
|
|
join(packageRoot, "dist", "postinstall-inventory.json"),
|
|
JSON.stringify(inventory),
|
|
);
|
|
const tarFiles =
|
|
options.includeControlUi === false
|
|
? files
|
|
: {
|
|
"dist/control-ui/index.html": "<!doctype html><openclaw-app></openclaw-app>",
|
|
"dist/control-ui/assets/app.js": "console.log('ok');\n",
|
|
...files,
|
|
};
|
|
for (const [relativePath, body] of Object.entries(tarFiles)) {
|
|
const filePath = join(packageRoot, relativePath);
|
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
writeFileSync(filePath, body);
|
|
}
|
|
|
|
const tarball = join(root, "openclaw.tgz");
|
|
const pack = spawnSync("tar", ["-czf", tarball, "-C", root, "package"], {
|
|
encoding: "utf8",
|
|
});
|
|
expect(pack.status, pack.stderr).toBe(0);
|
|
testBody(tarball);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
describe("check-openclaw-package-tarball", () => {
|
|
it("prints help before touching tarball state", () => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, "--help"], { encoding: "utf8" });
|
|
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(result.stdout).toContain(
|
|
"Usage: node scripts/check-openclaw-package-tarball.mjs <openclaw.tgz>",
|
|
);
|
|
expect(result.stderr).toBe("");
|
|
});
|
|
|
|
it("rejects option-like and extra arguments before tar inspection", () => {
|
|
const unknown = spawnSync("node", [CHECK_SCRIPT, "--tag"], { encoding: "utf8" });
|
|
|
|
expect(unknown.status).not.toBe(0);
|
|
expect(unknown.stderr).toContain("Unknown OpenClaw package tarball check option: --tag");
|
|
expect(unknown.stderr).not.toContain("OpenClaw package tarball does not exist");
|
|
|
|
const extra = spawnSync("node", [CHECK_SCRIPT, "openclaw.tgz", "extra"], {
|
|
encoding: "utf8",
|
|
});
|
|
|
|
expect(extra.status).not.toBe(0);
|
|
expect(extra.stderr).toContain("Unexpected OpenClaw package tarball check argument: extra");
|
|
expect(extra.stderr).not.toContain("OpenClaw package tarball does not exist");
|
|
});
|
|
|
|
it.runIf(process.platform !== "win32")(
|
|
"removes the extract dir when tar extraction fails",
|
|
() => {
|
|
const root = mkdtempSync(join(tmpdir(), "openclaw-package-tarball-extract-fail-"));
|
|
try {
|
|
const fakeBin = join(root, "bin");
|
|
mkdirSync(fakeBin);
|
|
const extractDirFile = join(root, "extract-dir.txt");
|
|
const fakeTar = join(fakeBin, "tar");
|
|
writeFileSync(
|
|
fakeTar,
|
|
[
|
|
"#!/usr/bin/env node",
|
|
"const fs = require('node:fs');",
|
|
"const args = process.argv.slice(2);",
|
|
"if (args[0] === '-tf') { console.log('package/package.json'); process.exit(0); }",
|
|
"const outputDir = args[args.indexOf('-C') + 1];",
|
|
"fs.writeFileSync(process.env.OPENCLAW_TEST_EXTRACT_DIR_FILE, outputDir);",
|
|
"console.error('extract denied');",
|
|
"process.exit(7);",
|
|
].join("\n"),
|
|
);
|
|
chmodSync(fakeTar, 0o755);
|
|
const tarball = join(root, "openclaw.tgz");
|
|
writeFileSync(tarball, "not used by fake tar");
|
|
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], {
|
|
encoding: "utf8",
|
|
env: {
|
|
...process.env,
|
|
OPENCLAW_TEST_EXTRACT_DIR_FILE: extractDirFile,
|
|
PATH: `${fakeBin}${delimiter}${process.env.PATH ?? ""}`,
|
|
},
|
|
});
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("extract denied");
|
|
expect(existsSync(readFileSync(extractDirFile, "utf8"))).toBe(false);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|
|
|
|
it("allows legacy private QA inventory entries omitted from shipped tarballs through 2026.4.25", () => {
|
|
withTarball(
|
|
["dist/index.js", "dist/extensions/qa-channel/runtime-api.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.stderr).toContain("legacy inventory references omitted private QA");
|
|
expect(result.stdout).toContain("OpenClaw package tarball integrity passed.");
|
|
},
|
|
"2026.4.25-beta.10",
|
|
);
|
|
});
|
|
|
|
it("rejects legacy private QA inventory omissions for newer packages", () => {
|
|
withTarball(
|
|
["dist/index.js", "dist/extensions/qa-channel/runtime-api.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(
|
|
"inventory references missing tar entry dist/extensions/qa-channel/runtime-api.js",
|
|
);
|
|
expect(result.stderr).not.toContain("legacy inventory references omitted private QA");
|
|
},
|
|
"2026.4.26",
|
|
);
|
|
});
|
|
|
|
it("still rejects non-legacy missing inventory entries", () => {
|
|
withTarball(
|
|
["dist/index.js", "dist/cli.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("inventory references missing tar entry dist/cli.js");
|
|
},
|
|
);
|
|
});
|
|
|
|
it("rejects stale deep plugin SDK declaration inventory entries", () => {
|
|
withTarball(
|
|
[FLAT_PLUGIN_SDK_DECLARATION, DEEP_PLUGIN_SDK_DECLARATION],
|
|
{ [FLAT_PLUGIN_SDK_DECLARATION]: "export {};\n" },
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
`inventory references missing tar entry ${DEEP_PLUGIN_SDK_DECLARATION}`,
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
it("accepts flat plugin SDK declaration inventory without the old deep tree", () => {
|
|
withTarball(
|
|
[FLAT_PLUGIN_SDK_DECLARATION],
|
|
{ [FLAT_PLUGIN_SDK_DECLARATION]: "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.");
|
|
},
|
|
);
|
|
});
|
|
|
|
it("rejects dist files that import missing relative chunks", () => {
|
|
withTarball(
|
|
["dist/cli/run-main.js"],
|
|
{ "dist/cli/run-main.js": 'await import("../memory-state-old.js");\n' },
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
"dist/cli/run-main.js imports missing dist/memory-state-old.js",
|
|
);
|
|
},
|
|
"2026.4.27",
|
|
);
|
|
});
|
|
|
|
it("accepts dist files whose relative chunks are present", () => {
|
|
withTarball(
|
|
["dist/cli/run-main.js", "dist/memory-state-current.js"],
|
|
{
|
|
"dist/cli/run-main.js": 'await import("../memory-state-current.js");\n',
|
|
"dist/memory-state-current.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.4.27",
|
|
);
|
|
});
|
|
|
|
it("rejects imported dist chunks omitted from the postinstall inventory", () => {
|
|
withTarball(
|
|
["dist/cli/run-main.js"],
|
|
{
|
|
"dist/cli/run-main.js": 'await import("../memory-state-current.js");\n',
|
|
"dist/memory-state-current.js": "export {};\n",
|
|
},
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
"inventory omits imported dist file dist/memory-state-current.js",
|
|
);
|
|
},
|
|
"2026.4.27",
|
|
);
|
|
});
|
|
|
|
it("rejects CommonJS require chunks omitted from the postinstall inventory", () => {
|
|
withTarball(
|
|
["dist/index.cjs"],
|
|
{
|
|
"dist/index.cjs": 'module.exports = require("./chunk.cjs");\n',
|
|
"dist/chunk.cjs": "module.exports = {};\n",
|
|
},
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("inventory omits imported dist file dist/chunk.cjs");
|
|
},
|
|
"2026.4.27",
|
|
);
|
|
});
|
|
|
|
it("rejects dist files with missing import.meta.url URL dependencies", () => {
|
|
withTarball(
|
|
["dist/index.js"],
|
|
{ "dist/index.js": 'const worker = new URL("./worker.js", import.meta.url);\n' },
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("dist/index.js imports missing dist/worker.js");
|
|
},
|
|
"2026.4.27",
|
|
);
|
|
});
|
|
|
|
it("rejects formatted import.meta.url URL dependencies", () => {
|
|
withTarball(
|
|
["dist/index.js"],
|
|
{
|
|
"dist/index.js": [
|
|
"const worker = new URL(",
|
|
' "./worker.js",',
|
|
" import.meta.url,",
|
|
");",
|
|
"",
|
|
].join("\n"),
|
|
},
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("dist/index.js imports missing dist/worker.js");
|
|
},
|
|
"2026.4.27",
|
|
);
|
|
});
|
|
|
|
it("rejects import.meta.url URL dependencies omitted from the postinstall inventory", () => {
|
|
withTarball(
|
|
["dist/index.js"],
|
|
{
|
|
"dist/index.js": 'const worker = new URL("./worker.js", import.meta.url);\n',
|
|
"dist/worker.js": "export {};\n",
|
|
},
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("inventory omits imported dist file dist/worker.js");
|
|
},
|
|
"2026.4.27",
|
|
);
|
|
});
|
|
|
|
it("allows import.meta.url package-root probes", () => {
|
|
withTarball(
|
|
["dist/index.js"],
|
|
{ "dist/index.js": 'const root = new URL("../..", import.meta.url);\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.4.27",
|
|
);
|
|
});
|
|
|
|
it("allows import.meta.url source helper probes", () => {
|
|
withTarball(
|
|
["dist/index.js"],
|
|
{
|
|
"dist/index.js":
|
|
'const shim = new URL("./capability-runtime-vitest-shims/config-runtime.ts", import.meta.url);\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.4.27",
|
|
);
|
|
});
|
|
|
|
it("rejects missing Control UI assets", () => {
|
|
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 dist/control-ui/index.html");
|
|
expect(result.stderr).toContain(
|
|
"missing required tar entries under dist/control-ui/assets/",
|
|
);
|
|
},
|
|
"2026.4.27",
|
|
{ includeControlUi: false },
|
|
);
|
|
});
|
|
|
|
it("allows legacy package tarballs without shrinkwrap", () => {
|
|
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.stderr).toContain("legacy package omits npm-shrinkwrap.json");
|
|
},
|
|
"2026.5.20",
|
|
{ includeShrinkwrap: false },
|
|
);
|
|
});
|
|
|
|
it("rejects new package tarballs without shrinkwrap", () => {
|
|
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 npm-shrinkwrap.json");
|
|
},
|
|
"2026.5.21",
|
|
{ includeShrinkwrap: false },
|
|
);
|
|
});
|
|
|
|
it("rejects package-lock.json in package tarballs", () => {
|
|
withTarball(
|
|
["dist/index.js"],
|
|
{ "dist/index.js": "export {};\n", "package-lock.json": "{}\n" },
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
"package tarball must ship npm-shrinkwrap.json, not package-lock.json",
|
|
);
|
|
},
|
|
"2026.4.27",
|
|
);
|
|
});
|
|
|
|
it("rejects local build metadata entries in package tarballs", () => {
|
|
withTarball(
|
|
["dist/index.js", ...LOCAL_BUILD_METADATA_DIST_PATHS],
|
|
{
|
|
"dist/index.js": "export {};\n",
|
|
...Object.fromEntries(LOCAL_BUILD_METADATA_DIST_PATHS.map((entry) => [entry, "{}\n"])),
|
|
},
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
"forbidden local build metadata tar entry dist/.buildstamp",
|
|
);
|
|
expect(result.stderr).toContain(
|
|
"forbidden local build metadata tar entry dist/.runtime-postbuildstamp",
|
|
);
|
|
},
|
|
"2026.4.27",
|
|
);
|
|
});
|
|
|
|
it("allows local build metadata in already published legacy packages through 2026.4.26", () => {
|
|
withTarball(
|
|
["dist/index.js", ...LOCAL_BUILD_METADATA_DIST_PATHS],
|
|
{
|
|
"dist/index.js": "export {};\n",
|
|
...Object.fromEntries(LOCAL_BUILD_METADATA_DIST_PATHS.map((entry) => [entry, "{}\n"])),
|
|
},
|
|
(tarball) => {
|
|
const result = spawnSync("node", [CHECK_SCRIPT, tarball], { encoding: "utf8" });
|
|
|
|
expect(result.status, result.stderr).toBe(0);
|
|
expect(result.stderr).toContain(
|
|
"legacy package includes local build metadata tar entry dist/.buildstamp",
|
|
);
|
|
expect(result.stderr).toContain(
|
|
"legacy package includes local build metadata tar entry dist/.runtime-postbuildstamp",
|
|
);
|
|
expect(result.stdout).toContain("OpenClaw package tarball integrity passed.");
|
|
},
|
|
"2026.4.26",
|
|
);
|
|
});
|
|
});
|