fix(plugins): declare host peer in npm runtime packs

This commit is contained in:
Vincent Koc
2026-05-02 23:17:37 -07:00
parent 25ceffbf25
commit 188c3b74ba
4 changed files with 85 additions and 0 deletions

View File

@@ -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,

View File

@@ -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),
};
}

View File

@@ -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);
});

View File

@@ -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);
}
});