From 110f92a0ef1c88889b09f80801421998a114988b Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Thu, 30 Jul 2026 23:28:55 -0700 Subject: [PATCH] fix: normalize nested plugin manifests (#3343) --- .../package-inspector-nightly-scan.test.ts | 9 +++++++++ scripts/package-inspector-nightly-scan.ts | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/scripts/package-inspector-nightly-scan.test.ts b/scripts/package-inspector-nightly-scan.test.ts index 975f521a..c4413021 100644 --- a/scripts/package-inspector-nightly-scan.test.ts +++ b/scripts/package-inspector-nightly-scan.test.ts @@ -171,6 +171,12 @@ describe("package-inspector-nightly-scan", () => { path.join(pluginRoot, "openclaw.plugin.json"), '\uFEFF{"id":"eu-compliance-skill"}\n', ); + const nestedPackageRoot = path.join(pluginRoot, "showmethemoney-skill", "demo-backend"); + await mkdir(nestedPackageRoot, { recursive: true }); + await writeFile( + path.join(nestedPackageRoot, "package.json"), + '\uFEFF{"name":"stablepay-demo-backend"}\n', + ); await expect( prepareExtractedPluginRoot(pluginRoot, "npm-pack", "eu-compliance-skill"), @@ -185,6 +191,9 @@ describe("package-inspector-nightly-scan", () => { expect(await readFile(path.join(pluginRoot, "openclaw.plugin.json"), "utf8")).toBe( '{"id":"eu-compliance-skill"}\n', ); + expect(await readFile(path.join(nestedPackageRoot, "package.json"), "utf8")).toBe( + '{"name":"stablepay-demo-backend"}\n', + ); }); it.each([ diff --git a/scripts/package-inspector-nightly-scan.ts b/scripts/package-inspector-nightly-scan.ts index d4d75f45..90bdba47 100644 --- a/scripts/package-inspector-nightly-scan.ts +++ b/scripts/package-inspector-nightly-scan.ts @@ -376,7 +376,7 @@ export async function prepareExtractedPluginRoot( if (artifactKind === "legacy-zip") { await removePosixArchiveMetadata(scanRoot); } - await readJsonIfExists(path.join(scanRoot, "openclaw.plugin.json")); + await normalizePluginJsonManifests(scanRoot); await writeSyntheticConfigIfNeeded(scanRoot, packageName); return scanRoot; } @@ -493,6 +493,23 @@ async function writeSyntheticConfigIfNeeded(root: string, packageName: string) { ); } +async function normalizePluginJsonManifests(root: string): Promise { + const entries = await readdir(root, { withFileTypes: true }); + for (const entry of entries) { + const entryPath = path.join(root, entry.name); + if (entry.isDirectory()) { + await normalizePluginJsonManifests(entryPath); + continue; + } + if ( + entry.isFile() && + (entry.name === "package.json" || entry.name === "openclaw.plugin.json") + ) { + await readJsonIfExists(entryPath); + } + } +} + async function readJsonIfExists(filePath: string) { if (!existsSync(filePath)) return null; const contents = await readFile(filePath, "utf8");