Files
openclaw/test/plugin-npm-release.test.ts
Onur Solmaz d41c9ad4cb Release: add plugin npm publish workflow (#47678)
* Release: add plugin npm publish workflow

* Release: make plugin publish scope explicit
2026-03-18 13:44:23 +01:00

218 lines
6.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
collectChangedExtensionIdsFromPaths,
collectPublishablePluginPackageErrors,
parsePluginReleaseArgs,
parsePluginReleaseSelection,
parsePluginReleaseSelectionMode,
resolveChangedPublishablePluginPackages,
resolveSelectedPublishablePluginPackages,
type PublishablePluginPackage,
} from "../scripts/lib/plugin-npm-release.ts";
describe("parsePluginReleaseSelection", () => {
it("returns an empty list for blank input", () => {
expect(parsePluginReleaseSelection("")).toEqual([]);
expect(parsePluginReleaseSelection(" ")).toEqual([]);
expect(parsePluginReleaseSelection(undefined)).toEqual([]);
});
it("dedupes and sorts comma or whitespace separated package names", () => {
expect(
parsePluginReleaseSelection(" @openclaw/zalo, @openclaw/feishu @openclaw/zalo "),
).toEqual(["@openclaw/feishu", "@openclaw/zalo"]);
});
});
describe("parsePluginReleaseSelectionMode", () => {
it("accepts the supported explicit selection modes", () => {
expect(parsePluginReleaseSelectionMode("selected")).toBe("selected");
expect(parsePluginReleaseSelectionMode("all-publishable")).toBe("all-publishable");
});
it("rejects unsupported selection modes", () => {
expect(() => parsePluginReleaseSelectionMode("all")).toThrowError(
'Unknown selection mode: all. Expected "selected" or "all-publishable".',
);
});
});
describe("parsePluginReleaseArgs", () => {
it("rejects blank explicit plugin selections", () => {
expect(() => parsePluginReleaseArgs(["--plugins", " "])).toThrowError(
"`--plugins` must include at least one package name.",
);
});
it("requires plugin names for selected explicit publish mode", () => {
expect(() => parsePluginReleaseArgs(["--selection-mode", "selected"])).toThrowError(
"`--selection-mode selected` requires `--plugins`.",
);
});
it("rejects plugin names when all-publishable mode is selected", () => {
expect(() =>
parsePluginReleaseArgs([
"--selection-mode",
"all-publishable",
"--plugins",
"@openclaw/zalo",
]),
).toThrowError("`--selection-mode all-publishable` must not be combined with `--plugins`.");
});
it("parses explicit all-publishable mode", () => {
expect(parsePluginReleaseArgs(["--selection-mode", "all-publishable"])).toMatchObject({
selectionMode: "all-publishable",
selection: [],
pluginsFlagProvided: false,
});
});
});
describe("collectPublishablePluginPackageErrors", () => {
it("accepts a valid publishable plugin package candidate", () => {
expect(
collectPublishablePluginPackageErrors({
extensionId: "zalo",
packageDir: "extensions/zalo",
packageJson: {
name: "@openclaw/zalo",
version: "2026.3.15",
openclaw: {
extensions: ["./index.ts"],
release: {
publishToNpm: true,
},
},
},
}),
).toEqual([]);
});
it("flags invalid publishable plugin metadata", () => {
expect(
collectPublishablePluginPackageErrors({
extensionId: "broken",
packageDir: "extensions/broken",
packageJson: {
name: "broken",
version: "latest",
private: true,
openclaw: {
extensions: [""],
release: {
publishToNpm: true,
},
},
},
}),
).toEqual([
'package name must start with "@openclaw/"; found "broken".',
"package.json private must not be true.",
'package.json version must match YYYY.M.D or YYYY.M.D-beta.N; found "latest".',
"openclaw.extensions must contain only non-empty strings.",
]);
});
});
describe("resolveSelectedPublishablePluginPackages", () => {
const publishablePlugins: PublishablePluginPackage[] = [
{
extensionId: "feishu",
packageDir: "extensions/feishu",
packageName: "@openclaw/feishu",
version: "2026.3.15",
channel: "stable",
publishTag: "latest",
},
{
extensionId: "zalo",
packageDir: "extensions/zalo",
packageName: "@openclaw/zalo",
version: "2026.3.15-beta.1",
channel: "beta",
publishTag: "beta",
},
];
it("returns all publishable plugins when no selection is provided", () => {
expect(
resolveSelectedPublishablePluginPackages({
plugins: publishablePlugins,
selection: [],
}),
).toEqual(publishablePlugins);
});
it("filters by selected publishable package names", () => {
expect(
resolveSelectedPublishablePluginPackages({
plugins: publishablePlugins,
selection: ["@openclaw/zalo"],
}),
).toEqual([publishablePlugins[1]]);
});
it("throws when the selection contains an unknown package name", () => {
expect(() =>
resolveSelectedPublishablePluginPackages({
plugins: publishablePlugins,
selection: ["@openclaw/missing"],
}),
).toThrowError("Unknown or non-publishable plugin package selection: @openclaw/missing.");
});
});
describe("collectChangedExtensionIdsFromPaths", () => {
it("extracts unique extension ids from changed extension paths", () => {
expect(
collectChangedExtensionIdsFromPaths([
"extensions/zalo/index.ts",
"extensions/zalo/package.json",
"extensions/feishu/src/client.ts",
"docs/reference/RELEASING.md",
]),
).toEqual(["feishu", "zalo"]);
});
});
describe("resolveChangedPublishablePluginPackages", () => {
const publishablePlugins: PublishablePluginPackage[] = [
{
extensionId: "feishu",
packageDir: "extensions/feishu",
packageName: "@openclaw/feishu",
version: "2026.3.15",
channel: "stable",
publishTag: "latest",
},
{
extensionId: "zalo",
packageDir: "extensions/zalo",
packageName: "@openclaw/zalo",
version: "2026.3.15-beta.1",
channel: "beta",
publishTag: "beta",
},
];
it("returns only changed publishable plugins", () => {
expect(
resolveChangedPublishablePluginPackages({
plugins: publishablePlugins,
changedExtensionIds: ["zalo"],
}),
).toEqual([publishablePlugins[1]]);
});
it("returns an empty list when no publishable plugins changed", () => {
expect(
resolveChangedPublishablePluginPackages({
plugins: publishablePlugins,
changedExtensionIds: [],
}),
).toEqual([]);
});
});