test: require plugin npm provenance repository

This commit is contained in:
Peter Steinberger
2026-05-02 00:48:42 +01:00
parent 106f8a4288
commit 632b9f697e
2 changed files with 52 additions and 0 deletions

View File

@@ -10,6 +10,12 @@ export type PluginPackageJson = {
name?: string;
version?: string;
private?: boolean;
repository?:
| string
| {
type?: string;
url?: string;
};
openclaw?: {
extensions?: string[];
install?: {
@@ -64,6 +70,8 @@ export type PublishablePluginPackageCandidate<
packageJson: TPackageJson;
};
export const OPENCLAW_PLUGIN_NPM_REPOSITORY_URL = "https://github.com/openclaw/openclaw";
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Release helper preserves caller-specific package.json shape.
function readPluginPackageJson<TPackageJson extends PluginPackageJson = PluginPackageJson>(
path: string,
@@ -210,6 +218,10 @@ export function collectPublishablePluginPackageErrors(
const errors: string[] = [];
const packageName = packageJson.name?.trim() ?? "";
const packageVersion = packageJson.version?.trim() ?? "";
const repositoryUrl =
typeof packageJson.repository === "string"
? packageJson.repository.trim()
: (packageJson.repository?.url?.trim() ?? "");
const extensions = packageJson.openclaw?.extensions ?? [];
if (!packageName.startsWith("@openclaw/")) {
@@ -220,6 +232,11 @@ export function collectPublishablePluginPackageErrors(
if (packageJson.private === true) {
errors.push("package.json private must not be true.");
}
if (repositoryUrl !== OPENCLAW_PLUGIN_NPM_REPOSITORY_URL) {
errors.push(
`package.json repository.url must be "${OPENCLAW_PLUGIN_NPM_REPOSITORY_URL}" so npm provenance can validate GitHub trusted publishing; found "${repositoryUrl || "<missing>"}".`,
);
}
if (!packageVersion) {
errors.push("package.json version must be non-empty.");
} else if (parseReleaseVersion(packageVersion) === null) {

View File

@@ -6,6 +6,7 @@ import {
collectPublishablePluginPackages,
collectChangedExtensionIdsFromPaths,
collectPublishablePluginPackageErrors,
OPENCLAW_PLUGIN_NPM_REPOSITORY_URL,
parsePluginReleaseArgs,
parsePluginReleaseSelection,
parsePluginReleaseSelectionMode,
@@ -90,6 +91,10 @@ describe("collectPublishablePluginPackageErrors", () => {
packageJson: {
name: "@openclaw/zalo",
version: "2026.3.15",
repository: {
type: "git",
url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL,
},
openclaw: {
extensions: ["./index.ts"],
release: {
@@ -121,10 +126,32 @@ describe("collectPublishablePluginPackageErrors", () => {
).toEqual([
'package name must start with "@openclaw/"; found "broken".',
"package.json private must not be true.",
`package.json repository.url must be "${OPENCLAW_PLUGIN_NPM_REPOSITORY_URL}" so npm provenance can validate GitHub trusted publishing; found "<missing>".`,
'package.json version must match YYYY.M.D, YYYY.M.D-N, or YYYY.M.D-beta.N; found "latest".',
"openclaw.extensions must contain only non-empty strings.",
]);
});
it("requires the GitHub repository URL npm provenance validates for trusted publishing", () => {
expect(
collectPublishablePluginPackageErrors({
extensionId: "twitch",
packageDir: bundledPluginRoot("twitch"),
packageJson: {
name: "@openclaw/twitch",
version: "2026.5.1-beta.1",
openclaw: {
extensions: ["./index.ts"],
release: {
publishToNpm: true,
},
},
},
}),
).toEqual([
`package.json repository.url must be "${OPENCLAW_PLUGIN_NPM_REPOSITORY_URL}" so npm provenance can validate GitHub trusted publishing; found "<missing>".`,
]);
});
});
describe("collectPublishablePluginPackages", () => {
@@ -134,6 +161,10 @@ describe("collectPublishablePluginPackages", () => {
writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), {
name: "@openclaw/demo-plugin",
version: "2026.4.10",
repository: {
type: "git",
url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL,
},
openclaw: {
extensions: ["./index.ts"],
install: {
@@ -164,6 +195,10 @@ describe("collectPublishablePluginPackages", () => {
writeJsonFile(join(repoDir, "extensions", "demo-plugin", "package.json"), {
name: "@openclaw/demo-plugin",
version: "2026.4.10-beta.1",
repository: {
type: "git",
url: OPENCLAW_PLUGIN_NPM_REPOSITORY_URL,
},
openclaw: {
extensions: ["./index.ts"],
release: {