From 632b9f697eba2ce88bee3119414d201b494c2856 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 00:48:42 +0100 Subject: [PATCH] test: require plugin npm provenance repository --- scripts/lib/plugin-npm-release.ts | 17 +++++++++++++++ test/plugin-npm-release.test.ts | 35 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/scripts/lib/plugin-npm-release.ts b/scripts/lib/plugin-npm-release.ts index c540f16b484..75732ef08af 100644 --- a/scripts/lib/plugin-npm-release.ts +++ b/scripts/lib/plugin-npm-release.ts @@ -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( 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 || ""}".`, + ); + } if (!packageVersion) { errors.push("package.json version must be non-empty."); } else if (parseReleaseVersion(packageVersion) === null) { diff --git a/test/plugin-npm-release.test.ts b/test/plugin-npm-release.test.ts index c54adc03b6b..f2e49909fab 100644 --- a/test/plugin-npm-release.test.ts +++ b/test/plugin-npm-release.test.ts @@ -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 "".`, '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 "".`, + ]); + }); }); 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: {