mirror of https://github.com/openclaw/openclaw.git
247 lines
7.6 KiB
TypeScript
247 lines
7.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
collectControlUiPackErrors,
|
|
collectReleasePackageMetadataErrors,
|
|
collectReleaseTagErrors,
|
|
parseNpmPackJsonOutput,
|
|
parseReleaseTagVersion,
|
|
parseReleaseVersion,
|
|
resolveNpmCommandInvocation,
|
|
utcCalendarDayDistance,
|
|
} from "../scripts/openclaw-npm-release-check.ts";
|
|
|
|
describe("parseReleaseVersion", () => {
|
|
it("parses stable CalVer releases", () => {
|
|
expect(parseReleaseVersion("2026.3.10")).toMatchObject({
|
|
version: "2026.3.10",
|
|
baseVersion: "2026.3.10",
|
|
channel: "stable",
|
|
year: 2026,
|
|
month: 3,
|
|
day: 10,
|
|
});
|
|
});
|
|
|
|
it("parses beta CalVer releases", () => {
|
|
expect(parseReleaseVersion("2026.3.10-beta.2")).toMatchObject({
|
|
version: "2026.3.10-beta.2",
|
|
baseVersion: "2026.3.10",
|
|
channel: "beta",
|
|
year: 2026,
|
|
month: 3,
|
|
day: 10,
|
|
betaNumber: 2,
|
|
});
|
|
});
|
|
|
|
it("parses stable correction releases", () => {
|
|
expect(parseReleaseVersion("2026.3.10-1")).toMatchObject({
|
|
version: "2026.3.10-1",
|
|
baseVersion: "2026.3.10",
|
|
channel: "stable",
|
|
year: 2026,
|
|
month: 3,
|
|
day: 10,
|
|
correctionNumber: 1,
|
|
});
|
|
});
|
|
|
|
it("rejects legacy and malformed release formats", () => {
|
|
expect(parseReleaseVersion("2026.03.09")).toBeNull();
|
|
expect(parseReleaseVersion("v2026.3.10")).toBeNull();
|
|
expect(parseReleaseVersion("2026.2.30")).toBeNull();
|
|
expect(parseReleaseVersion("2026.3.10-0")).toBeNull();
|
|
expect(parseReleaseVersion("2.0.0-beta2")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("parseReleaseTagVersion", () => {
|
|
it("accepts correction release tags", () => {
|
|
expect(parseReleaseTagVersion("2026.3.10-2")).toMatchObject({
|
|
version: "2026.3.10-2",
|
|
packageVersion: "2026.3.10-2",
|
|
baseVersion: "2026.3.10",
|
|
channel: "stable",
|
|
correctionNumber: 2,
|
|
});
|
|
});
|
|
|
|
it("rejects beta correction tags and malformed correction tags", () => {
|
|
expect(parseReleaseTagVersion("2026.3.10-beta.1-1")).toBeNull();
|
|
expect(parseReleaseTagVersion("2026.3.10-0")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("utcCalendarDayDistance", () => {
|
|
it("compares UTC calendar days rather than wall-clock hours", () => {
|
|
const left = new Date("2026-03-09T23:59:59Z");
|
|
const right = new Date("2026-03-11T00:00:01Z");
|
|
expect(utcCalendarDayDistance(left, right)).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("resolveNpmCommandInvocation", () => {
|
|
it("uses npm_execpath when it points to npm", () => {
|
|
expect(
|
|
resolveNpmCommandInvocation({
|
|
npmExecPath: "/usr/local/lib/node_modules/npm/bin/npm-cli.js",
|
|
nodeExecPath: "/usr/local/bin/node",
|
|
platform: "linux",
|
|
}),
|
|
).toEqual({
|
|
command: "/usr/local/bin/node",
|
|
args: ["/usr/local/lib/node_modules/npm/bin/npm-cli.js"],
|
|
});
|
|
});
|
|
|
|
it("falls back to the npm command when npm_execpath points to pnpm", () => {
|
|
expect(
|
|
resolveNpmCommandInvocation({
|
|
npmExecPath: "/home/test/.cache/node/corepack/v1/pnpm/10.23.0/bin/pnpm.cjs",
|
|
nodeExecPath: "/usr/local/bin/node",
|
|
platform: "linux",
|
|
}),
|
|
).toEqual({
|
|
command: "npm",
|
|
args: [],
|
|
});
|
|
});
|
|
|
|
it("uses the platform npm command when npm_execpath is missing", () => {
|
|
expect(resolveNpmCommandInvocation({ platform: "win32" })).toEqual({
|
|
command: "npm.cmd",
|
|
args: [],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("parseNpmPackJsonOutput", () => {
|
|
it("parses a plain npm pack JSON array", () => {
|
|
expect(parseNpmPackJsonOutput('[{"filename":"openclaw.tgz","files":[]}]')).toEqual([
|
|
{ filename: "openclaw.tgz", files: [] },
|
|
]);
|
|
});
|
|
|
|
it("parses the trailing JSON payload after npm lifecycle logs", () => {
|
|
const stdout = [
|
|
'npm warn Unknown project config "node-linker".',
|
|
"",
|
|
"> openclaw@2026.3.23 prepack",
|
|
"> pnpm build && pnpm ui:build",
|
|
"",
|
|
"[copy-hook-metadata] Copied 4 hook metadata files.",
|
|
'[{"filename":"openclaw.tgz","files":[{"path":"dist/control-ui/index.html"}]}]',
|
|
].join("\n");
|
|
|
|
expect(parseNpmPackJsonOutput(stdout)).toEqual([
|
|
{
|
|
filename: "openclaw.tgz",
|
|
files: [{ path: "dist/control-ui/index.html" }],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("returns null when no JSON payload is present", () => {
|
|
expect(parseNpmPackJsonOutput("> openclaw@2026.3.23 prepack")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("collectControlUiPackErrors", () => {
|
|
it("rejects packs that ship the dashboard HTML without the asset payload", () => {
|
|
expect(collectControlUiPackErrors(["dist/control-ui/index.html"])).toEqual([
|
|
'npm package is missing Control UI asset payload under "dist/control-ui/assets/". Refuse release when the dashboard tarball would be empty.',
|
|
]);
|
|
});
|
|
|
|
it("accepts packs that ship dashboard HTML and bundled assets", () => {
|
|
expect(
|
|
collectControlUiPackErrors([
|
|
"dist/control-ui/index.html",
|
|
"dist/control-ui/assets/index-Bu8rSoJV.js",
|
|
"dist/control-ui/assets/index-BK0yXA_h.css",
|
|
]),
|
|
).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("collectReleaseTagErrors", () => {
|
|
it("accepts versions within the two-day CalVer window", () => {
|
|
expect(
|
|
collectReleaseTagErrors({
|
|
packageVersion: "2026.3.10",
|
|
releaseTag: "v2026.3.10",
|
|
now: new Date("2026-03-11T12:00:00Z"),
|
|
}),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("rejects versions outside the two-day CalVer window", () => {
|
|
expect(
|
|
collectReleaseTagErrors({
|
|
packageVersion: "2026.3.10",
|
|
releaseTag: "v2026.3.10",
|
|
now: new Date("2026-03-13T00:00:00Z"),
|
|
}),
|
|
).toContainEqual(expect.stringContaining("must be within 2 days"));
|
|
});
|
|
|
|
it("accepts fallback correction tags for stable package versions", () => {
|
|
expect(
|
|
collectReleaseTagErrors({
|
|
packageVersion: "2026.3.10",
|
|
releaseTag: "v2026.3.10-1",
|
|
now: new Date("2026-03-10T00:00:00Z"),
|
|
}),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("accepts correction package versions paired with matching correction tags", () => {
|
|
expect(
|
|
collectReleaseTagErrors({
|
|
packageVersion: "2026.3.10-1",
|
|
releaseTag: "v2026.3.10-1",
|
|
now: new Date("2026-03-10T00:00:00Z"),
|
|
}),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("rejects beta package versions paired with fallback correction tags", () => {
|
|
expect(
|
|
collectReleaseTagErrors({
|
|
packageVersion: "2026.3.10-beta.1",
|
|
releaseTag: "v2026.3.10-1",
|
|
now: new Date("2026-03-10T00:00:00Z"),
|
|
}),
|
|
).toContainEqual(expect.stringContaining("does not match package.json version"));
|
|
});
|
|
});
|
|
|
|
describe("collectReleasePackageMetadataErrors", () => {
|
|
it("validates the expected npm package metadata", () => {
|
|
expect(
|
|
collectReleasePackageMetadataErrors({
|
|
name: "openclaw",
|
|
description: "Multi-channel AI gateway with extensible messaging integrations",
|
|
license: "MIT",
|
|
repository: { url: "git+https://github.com/openclaw/openclaw.git" },
|
|
bin: { openclaw: "openclaw.mjs" },
|
|
peerDependencies: { "node-llama-cpp": "3.16.2" },
|
|
peerDependenciesMeta: { "node-llama-cpp": { optional: true } },
|
|
}),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it("requires node-llama-cpp to stay an optional peer", () => {
|
|
expect(
|
|
collectReleasePackageMetadataErrors({
|
|
name: "openclaw",
|
|
description: "Multi-channel AI gateway with extensible messaging integrations",
|
|
license: "MIT",
|
|
repository: { url: "git+https://github.com/openclaw/openclaw.git" },
|
|
bin: { openclaw: "openclaw.mjs" },
|
|
peerDependencies: { "node-llama-cpp": "3.16.2" },
|
|
}),
|
|
).toContain('package.json peerDependenciesMeta["node-llama-cpp"].optional must be true.');
|
|
});
|
|
});
|