From 188c3b74badc593c7a3b8152ee02c23f44a16294 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 23:17:37 -0700 Subject: [PATCH] fix(plugins): declare host peer in npm runtime packs --- scripts/lib/plugin-npm-package-manifest.mjs | 2 + scripts/lib/plugin-npm-runtime-build.mjs | 66 +++++++++++++++++++++ test/plugin-npm-package-manifest.test.ts | 13 ++++ test/plugin-npm-runtime-build.test.ts | 4 ++ 4 files changed, 85 insertions(+) diff --git a/scripts/lib/plugin-npm-package-manifest.mjs b/scripts/lib/plugin-npm-package-manifest.mjs index b1d1b4daa19..edf27f086c6 100644 --- a/scripts/lib/plugin-npm-package-manifest.mjs +++ b/scripts/lib/plugin-npm-package-manifest.mjs @@ -76,6 +76,8 @@ export function resolveAugmentedPluginNpmPackageJson(params) { const packageJson = { ...plan.packageJson, files: plan.packageFiles, + peerDependencies: plan.packagePeerMetadata.peerDependencies, + peerDependenciesMeta: plan.packagePeerMetadata.peerDependenciesMeta, openclaw: { ...plan.packageJson.openclaw, runtimeExtensions: plan.runtimeExtensions, diff --git a/scripts/lib/plugin-npm-runtime-build.mjs b/scripts/lib/plugin-npm-runtime-build.mjs index fbff5d162ec..3db7acc5a39 100644 --- a/scripts/lib/plugin-npm-runtime-build.mjs +++ b/scripts/lib/plugin-npm-runtime-build.mjs @@ -43,6 +43,21 @@ function collectExternalDependencyNames(packageJson) { ); } +function getStringRecord(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return {}; + } + return Object.fromEntries( + Object.entries(value).filter( + ([, entryValue]) => typeof entryValue === "string" && entryValue.trim().length > 0, + ), + ); +} + +function getRecord(value) { + return value && typeof value === "object" && !Array.isArray(value) ? value : {}; +} + function createNeverBundleDependencyMatcher(packageJson) { const externalDependencies = collectExternalDependencyNames(packageJson); return (id) => { @@ -116,6 +131,51 @@ export function resolvePluginNpmRuntimePackageFiles(plan) { return [...merged]; } +function normalizeOpenClawPeerRange(value) { + const normalized = normalizePackageEntry(value); + if (!normalized) { + return ""; + } + return /^[<>=~^*]|^(?:workspace|npm|file|link|portal|catalog):/u.test(normalized) + ? normalized + : `>=${normalized}`; +} + +function resolveOpenClawPeerRange(packageJson, rootPackageJson) { + return ( + normalizeOpenClawPeerRange(packageJson.openclaw?.compat?.pluginApi) || + normalizeOpenClawPeerRange(packageJson.peerDependencies?.openclaw) || + normalizeOpenClawPeerRange(packageJson.openclaw?.build?.openclawVersion) || + normalizeOpenClawPeerRange(rootPackageJson?.version) || + normalizeOpenClawPeerRange(packageJson.version) + ); +} + +export function resolvePluginNpmRuntimePackagePeerMetadata(plan) { + const openclawPeerRange = resolveOpenClawPeerRange(plan.packageJson, plan.rootPackageJson); + if (!openclawPeerRange) { + throw new Error( + `cannot infer openclaw peerDependency range for ${plan.pluginDir}; set openclaw.compat.pluginApi or package version`, + ); + } + const existingPeerDependencies = getStringRecord(plan.packageJson.peerDependencies); + const existingPeerDependenciesMeta = getRecord(plan.packageJson.peerDependenciesMeta); + const existingOpenClawMeta = getRecord(existingPeerDependenciesMeta.openclaw); + return { + peerDependencies: { + ...existingPeerDependencies, + openclaw: openclawPeerRange, + }, + peerDependenciesMeta: { + ...existingPeerDependenciesMeta, + openclaw: { + ...existingOpenClawMeta, + optional: true, + }, + }, + }; +} + export function resolvePluginNpmRuntimeBuildPlan(params) { const repoRoot = path.resolve(params.repoRoot ?? "."); const packageDir = resolvePackageDir(repoRoot, params.packageDir); @@ -124,6 +184,10 @@ export function resolvePluginNpmRuntimeBuildPlan(params) { return null; } const packageJson = readJsonFile(packageJsonPath); + const rootPackageJsonPath = path.join(repoRoot, "package.json"); + const rootPackageJson = fs.existsSync(rootPackageJsonPath) + ? readJsonFile(rootPackageJsonPath) + : undefined; if (!isPublishablePluginPackage(packageJson)) { return null; } @@ -153,6 +217,7 @@ export function resolvePluginNpmRuntimeBuildPlan(params) { packageDir, pluginDir, packageJson, + rootPackageJson, sourceEntries, entry, outDir: path.join(packageDir, "dist"), @@ -171,6 +236,7 @@ export function resolvePluginNpmRuntimeBuildPlan(params) { ...plan, runtimeBuildOutputs: listPluginNpmRuntimeBuildOutputs(plan), packageFiles: resolvePluginNpmRuntimePackageFiles(plan), + packagePeerMetadata: resolvePluginNpmRuntimePackagePeerMetadata(plan), }; } diff --git a/test/plugin-npm-package-manifest.test.ts b/test/plugin-npm-package-manifest.test.ts index 5988b774015..dd8859c3d71 100644 --- a/test/plugin-npm-package-manifest.test.ts +++ b/test/plugin-npm-package-manifest.test.ts @@ -59,6 +59,9 @@ function writePublishablePluginPackage(repoDir: string): string { openclaw: { extensions: ["./index.ts"], setupEntry: "./setup-entry.ts", + compat: { + pluginApi: ">=2026.4.30", + }, release: { publishToNpm: true, }, @@ -127,6 +130,14 @@ describe("plugin npm package manifest staging", () => { expect(resolved.changed).toBe(true); expect(resolved.packageJson).toMatchObject({ files: ["dist/**", "openclaw.plugin.json", "README.md", "SKILL.md", "skills/**"], + peerDependencies: { + openclaw: ">=2026.4.30", + }, + peerDependenciesMeta: { + openclaw: { + optional: true, + }, + }, openclaw: { runtimeExtensions: ["./dist/index.js"], runtimeSetupEntry: "./dist/setup-entry.js", @@ -141,6 +152,8 @@ describe("plugin npm package manifest staging", () => { expect(stagedPackageJson.openclaw.runtimeSetupEntry).toBe("./dist/setup-entry.js"); expect(stagedPackageJson.files).toContain("dist/**"); expect(stagedPackageJson.files).toContain("skills/**"); + expect(stagedPackageJson.peerDependencies.openclaw).toBe(">=2026.4.30"); + expect(stagedPackageJson.peerDependenciesMeta.openclaw.optional).toBe(true); }); expect(readFileSync(join(packageDir, "package.json"), "utf8")).toBe(originalText); }); diff --git a/test/plugin-npm-runtime-build.test.ts b/test/plugin-npm-runtime-build.test.ts index b1f3102e24c..3ba4a175a0e 100644 --- a/test/plugin-npm-runtime-build.test.ts +++ b/test/plugin-npm-runtime-build.test.ts @@ -26,6 +26,10 @@ describe("plugin npm runtime build planning", () => { expect(plan?.runtimeExtensions.every((entry) => entry.startsWith("./dist/"))).toBe(true); expect(plan?.runtimeBuildOutputs.every((entry) => entry.startsWith("./dist/"))).toBe(true); expect(plan?.packageFiles).toContain("dist/**"); + expect(plan?.packagePeerMetadata.peerDependencies.openclaw).toBe( + plan?.packageJson.openclaw.compat.pluginApi, + ); + expect(plan?.packagePeerMetadata.peerDependenciesMeta.openclaw.optional).toBe(true); } });