From 58a4d537fc7c4d2ca0ed4ad4bdfa8395674f628d Mon Sep 17 00:00:00 2001 From: masonxhuang Date: Thu, 2 Apr 2026 21:57:41 +0800 Subject: [PATCH] Plugins: cover non-object JSON5 manifest inputs --- src/plugins/bundle-manifest.test.ts | 33 ++++++++++++++++++++ src/plugins/manifest.json5-tolerance.test.ts | 10 ++++++ 2 files changed, 43 insertions(+) diff --git a/src/plugins/bundle-manifest.test.ts b/src/plugins/bundle-manifest.test.ts index c9961c56eeb..3bb1b0503ef 100644 --- a/src/plugins/bundle-manifest.test.ts +++ b/src/plugins/bundle-manifest.test.ts @@ -373,6 +373,39 @@ describe("bundle manifest parsing", () => { }, ); + it.each([ + { + name: "rejects JSON5 Codex bundle manifests that parse to non-objects", + bundleFormat: "codex" as const, + manifestRelativePath: CODEX_BUNDLE_MANIFEST_RELATIVE_PATH, + }, + { + name: "rejects JSON5 Claude bundle manifests that parse to non-objects", + bundleFormat: "claude" as const, + manifestRelativePath: CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH, + }, + { + name: "rejects JSON5 Cursor bundle manifests that parse to non-objects", + bundleFormat: "cursor" as const, + manifestRelativePath: CURSOR_BUNDLE_MANIFEST_RELATIVE_PATH, + }, + ] as const)("$name", ({ bundleFormat, manifestRelativePath }) => { + const rootDir = makeTempDir(); + setupBundleFixture({ + rootDir, + dirs: [path.dirname(manifestRelativePath)], + textFiles: { + [manifestRelativePath]: "'still not an object'", + }, + }); + + const result = loadBundleManifest({ rootDir, bundleFormat }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain("plugin manifest must be an object"); + } + }); + it.each([ { name: "resolves Claude bundle hooks from default and declared paths", diff --git a/src/plugins/manifest.json5-tolerance.test.ts b/src/plugins/manifest.json5-tolerance.test.ts index 75420b0b749..815026c13ca 100644 --- a/src/plugins/manifest.json5-tolerance.test.ts +++ b/src/plugins/manifest.json5-tolerance.test.ts @@ -90,4 +90,14 @@ describe("loadPluginManifest JSON5 tolerance", () => { expect(result.error).toContain("failed to parse plugin manifest"); } }); + + it("rejects JSON5 values that parse but are not objects", () => { + const dir = makeTempDir(); + fs.writeFileSync(path.join(dir, "openclaw.plugin.json"), "'just a string'", "utf-8"); + const result = loadPluginManifest(dir, false); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain("plugin manifest must be an object"); + } + }); });