Add packed CLI smoke checks for release packaging (#70685)

* Add packed CLI smoke release checks

* Address PR review feedback

* Harden packed CLI smoke checks

* Tighten release verifier parsing

* Scan root dist module files in release verifier
This commit is contained in:
Tak Hoffman
2026-04-23 13:50:15 -05:00
committed by GitHub
parent c151956782
commit 7651a03424
4 changed files with 521 additions and 6 deletions

View File

@@ -7,6 +7,7 @@ import {
buildPublishedInstallCommandArgs,
buildPublishedInstallScenarios,
collectInstalledContextEngineRuntimeErrors,
collectInstalledRootDependencyManifestErrors,
collectInstalledMirroredRootDependencyManifestErrors,
collectInstalledPackageErrors,
normalizeInstalledBinaryVersion,
@@ -419,3 +420,179 @@ describe("collectInstalledMirroredRootDependencyManifestErrors", () => {
}
});
});
describe("collectInstalledRootDependencyManifestErrors", () => {
function makeInstalledPackageRoot(): string {
return mkdtempSync(join(tmpdir(), "openclaw-postpublish-root-deps-"));
}
function writePackageFile(root: string, relativePath: string, value: unknown): void {
const fullPath = join(root, relativePath);
mkdirSync(dirname(fullPath), { recursive: true });
writeFileSync(fullPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
it("flags root dist imports whose declared runtime package name is missing", () => {
const packageRoot = makeInstalledPackageRoot();
try {
writePackageFile(packageRoot, "package.json", {
version: "2026.4.22",
dependencies: {},
});
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "typebox-CXXonh2u.js"),
'import { Type } from "typebox";\nexport { Type };\n',
"utf8",
);
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([
"installed package root is missing declared runtime dependency 'typebox' for dist importers: typebox-CXXonh2u.js. Add it to package.json dependencies/optionalDependencies.",
]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
it("accepts root dist imports when the runtime package name is declared", () => {
const packageRoot = makeInstalledPackageRoot();
try {
writePackageFile(packageRoot, "package.json", {
version: "2026.4.22",
dependencies: {
typebox: "1.1.28",
},
});
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "typebox-CXXonh2u.js"),
'import { Type } from "typebox";\nexport { Type };\n',
"utf8",
);
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
it("flags undeclared imports from mjs and cjs root dist files", () => {
const packageRoot = makeInstalledPackageRoot();
try {
writePackageFile(packageRoot, "package.json", {
version: "2026.4.22",
dependencies: {},
});
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "esm-entry.mjs"),
'export { value } from "mjs-only";\n',
"utf8",
);
writeFileSync(
join(packageRoot, "dist", "cjs-entry.cjs"),
'const cjsOnly = require("cjs-only");\nmodule.exports = cjsOnly;\n',
"utf8",
);
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([
"installed package root is missing declared runtime dependency 'cjs-only' for dist importers: cjs-entry.cjs. Add it to package.json dependencies/optionalDependencies.",
"installed package root is missing declared runtime dependency 'mjs-only' for dist importers: esm-entry.mjs. Add it to package.json dependencies/optionalDependencies.",
]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
it("ignores import-like text inside comments", () => {
const packageRoot = makeInstalledPackageRoot();
try {
writePackageFile(packageRoot, "package.json", {
version: "2026.4.22",
dependencies: {},
});
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "comment-only.js"),
[
'// import "fake-package";',
'/* require("fake-package-two"); */',
"export const ok = true;",
"",
].join("\n"),
"utf8",
);
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
it("ignores import-like text inside string literals", () => {
const packageRoot = makeInstalledPackageRoot();
try {
writePackageFile(packageRoot, "package.json", {
version: "2026.4.22",
dependencies: {},
});
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "string-only.js"),
[
'export const help = "run import(\'fake-package\') after setup";',
'export const note = "from \\"fake-package-two\\"";',
"",
].join("\n"),
"utf8",
);
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
it("returns a structured error when installed package.json is invalid", () => {
const packageRoot = makeInstalledPackageRoot();
try {
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(join(packageRoot, "package.json"), "{not-json\n", "utf8");
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([
expect.stringMatching(/^installed package\.json could not be parsed:/u),
]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
it("refuses oversized root dist files", () => {
const packageRoot = makeInstalledPackageRoot();
try {
writePackageFile(packageRoot, "package.json", {
version: "2026.4.22",
dependencies: {},
});
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "dist", "oversized.js"),
"x".repeat(2 * 1024 * 1024 + 1),
"utf8",
);
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([
"installed package root dist file 'oversized.js' is invalid or exceeds 2097152 bytes.",
]);
} finally {
rmSync(packageRoot, { recursive: true, force: true });
}
});
});

View File

@@ -1,6 +1,6 @@
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { dirname, join } from "node:path";
import { describe, expect, it } from "vitest";
import { listBundledPluginPackArtifacts } from "../scripts/lib/bundled-plugin-build-entries.mjs";
import { listPluginSdkDistArtifacts } from "../scripts/lib/plugin-sdk-entries.mjs";
@@ -15,7 +15,9 @@ import {
collectForbiddenPackPaths,
collectMissingPackPaths,
collectPackUnpackedSizeErrors,
createPackedCliSmokeEnv,
createPackedBundledPluginPostinstallEnv,
PACKED_CLI_SMOKE_COMMANDS,
packageNameFromSpecifier,
} from "../scripts/release-check.ts";
import { PACKAGE_DIST_INVENTORY_RELATIVE_PATH } from "../src/infra/package-dist-inventory.ts";
@@ -54,6 +56,53 @@ describe("collectAppcastSparkleVersionErrors", () => {
});
});
describe("packed CLI smoke", () => {
it("keeps the expected packaged CLI smoke command list", () => {
expect(PACKED_CLI_SMOKE_COMMANDS).toEqual([
["--help"],
["status", "--json", "--timeout", "1"],
["config", "schema"],
["models", "list", "--provider", "amazon-bedrock"],
]);
});
it("builds a packed CLI smoke env with packaged-install guardrails", () => {
expect(
createPackedCliSmokeEnv(
{
PATH: "/usr/bin",
HOME: "/tmp/original-home",
USERPROFILE: "/tmp/original-profile",
TMPDIR: "/tmp/original-tmp",
SystemRoot: "C:\\Windows",
GITHUB_TOKEN: "redacted",
OPENAI_API_KEY: "real-secret",
},
{ HOME: "/tmp/smoke-home", OPENCLAW_STATE_DIR: "/tmp/smoke-state" },
),
).toEqual({
PATH:
process.platform === "win32"
? `${dirname(process.execPath)};C:\\Windows\\System32;C:\\Windows`
: `${dirname(process.execPath)}:/usr/bin:/bin`,
HOME: "/tmp/smoke-home",
USERPROFILE: "/tmp/smoke-home",
ComSpec: "C:\\Windows/System32/cmd.exe",
APPDATA: "/tmp/smoke-home/AppData/Roaming",
LOCALAPPDATA: "/tmp/smoke-home/AppData/Local",
AWS_EC2_METADATA_DISABLED: "true",
AWS_SHARED_CREDENTIALS_FILE: "/tmp/smoke-home/.aws/credentials",
AWS_CONFIG_FILE: "/tmp/smoke-home/.aws/config",
TMPDIR: "/tmp/original-tmp",
SystemRoot: "C:\\Windows",
OPENCLAW_DISABLE_BUNDLED_ENTRY_SOURCE_FALLBACK: "1",
OPENCLAW_NO_ONBOARD: "1",
OPENCLAW_SUPPRESS_NOTES: "1",
OPENCLAW_STATE_DIR: "/tmp/smoke-state",
});
});
});
describe("collectBundledExtensionManifestErrors", () => {
it("flags invalid bundled extension install metadata", () => {
expect(