import { describe, expect, it } from "vitest"; import { collectAppcastSparkleVersionErrors, collectBundledExtensionManifestErrors, collectForbiddenPackPaths, collectPackUnpackedSizeErrors, } from "../scripts/release-check.ts"; function makeItem(shortVersion: string, sparkleVersion: string): string { return `${shortVersion}${shortVersion}${sparkleVersion}`; } function makePackResult(filename: string, unpackedSize: number) { return { filename, unpackedSize }; } describe("collectAppcastSparkleVersionErrors", () => { it("accepts legacy 9-digit calver builds before lane-floor cutover", () => { const xml = `${makeItem("2026.2.26", "202602260")}`; expect(collectAppcastSparkleVersionErrors(xml)).toEqual([]); }); it("requires lane-floor builds on and after lane-floor cutover", () => { const xml = `${makeItem("2026.3.1", "202603010")}`; expect(collectAppcastSparkleVersionErrors(xml)).toEqual([ "appcast item '2026.3.1' has sparkle:version 202603010 below lane floor 2026030190.", ]); }); it("accepts canonical stable lane builds on and after lane-floor cutover", () => { const xml = `${makeItem("2026.3.1", "2026030190")}`; expect(collectAppcastSparkleVersionErrors(xml)).toEqual([]); }); }); describe("collectBundledExtensionManifestErrors", () => { it("flags invalid bundled extension install metadata", () => { expect( collectBundledExtensionManifestErrors([ { id: "broken", packageJson: { openclaw: { install: { npmSpec: " " }, }, }, }, ]), ).toEqual([ "bundled extension 'broken' manifest invalid | openclaw.install.npmSpec must be a non-empty string", ]); }); it("flags invalid bundled extension minHostVersion metadata", () => { expect( collectBundledExtensionManifestErrors([ { id: "broken", packageJson: { openclaw: { install: { npmSpec: "@openclaw/broken", minHostVersion: "2026.3.14" }, }, }, }, ]), ).toEqual([ "bundled extension 'broken' manifest invalid | openclaw.install.minHostVersion must use a semver floor in the form \">=x.y.z\"", ]); }); it("allows install metadata without npmSpec when only non-publish metadata is present", () => { expect( collectBundledExtensionManifestErrors([ { id: "irc", packageJson: { openclaw: { install: { minHostVersion: ">=2026.3.14" }, }, }, }, ]), ).toEqual([]); }); it("flags non-object install metadata instead of throwing", () => { expect( collectBundledExtensionManifestErrors([ { id: "broken", packageJson: { openclaw: { install: 123, }, }, }, ]), ).toEqual(["bundled extension 'broken' manifest invalid | openclaw.install must be an object"]); }); }); describe("collectForbiddenPackPaths", () => { it("allows bundled plugin runtime deps under dist/extensions but still blocks other node_modules", () => { expect( collectForbiddenPackPaths([ "dist/index.js", "dist/extensions/discord/node_modules/@buape/carbon/index.js", "extensions/tlon/node_modules/.bin/tlon", "node_modules/.bin/openclaw", ]), ).toEqual(["extensions/tlon/node_modules/.bin/tlon", "node_modules/.bin/openclaw"]); }); }); describe("collectPackUnpackedSizeErrors", () => { it("accepts pack results within the unpacked size budget", () => { expect( collectPackUnpackedSizeErrors([makePackResult("openclaw-2026.3.14.tgz", 120_354_302)]), ).toEqual([]); }); it("flags oversized pack results that risk low-memory startup failures", () => { expect( collectPackUnpackedSizeErrors([makePackResult("openclaw-2026.3.12.tgz", 224_002_564)]), ).toEqual([ "openclaw-2026.3.12.tgz unpackedSize 224002564 bytes (213.6 MiB) exceeds budget 167772160 bytes (160.0 MiB). Investigate duplicate channel shims, copied extension trees, or other accidental pack bloat before release.", ]); }); it("fails closed when npm pack output omits unpackedSize for every result", () => { expect( collectPackUnpackedSizeErrors([ { filename: "openclaw-2026.3.14.tgz" }, { filename: "openclaw-extra.tgz", unpackedSize: Number.NaN }, ]), ).toEqual([ "npm pack --dry-run produced no unpackedSize data; pack size budget was not verified.", ]); }); });