mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:40:49 +00:00
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:
@@ -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 });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user