fix(config): handle tuple-format plugin entries in opencode.json (fixes #3122)
OpenCode supports plugin entries as [string, object] tuples for passing options, but loadOpencodePlugins assumed all entries were strings. When a tuple entry hit matchesKnownPlugin, it called .toLowerCase() on an array, crashing the plugin on startup. Extract the string name from tuple entries and skip non-string values. Add regression test covering the tuple plugin format.
This commit is contained in:
@@ -36,7 +36,9 @@
|
|||||||
"agent-browser",
|
"agent-browser",
|
||||||
"dev-browser",
|
"dev-browser",
|
||||||
"frontend-ui-ux",
|
"frontend-ui-ux",
|
||||||
"git-master"
|
"git-master",
|
||||||
|
"review-work",
|
||||||
|
"ai-slop-remover"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -102,6 +102,32 @@ describe("external-plugin-detector", () => {
|
|||||||
expect(result.pluginName).toContain("opencode-notifier")
|
expect(result.pluginName).toContain("opencode-notifier")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should safely handle tuple-format plugin entries without crashing (fixes #3122)", () => {
|
||||||
|
// given - opencode.json with array/tuple plugin entries
|
||||||
|
const opencodeDir = path.join(tempDir, ".opencode")
|
||||||
|
fs.mkdirSync(opencodeDir, { recursive: true })
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(opencodeDir, "opencode.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
plugin: [
|
||||||
|
"oh-my-opencode",
|
||||||
|
["advanced-tuple-plugin", { debug: true }],
|
||||||
|
"opencode-notifier"
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = detectExternalNotificationPlugin(tempDir)
|
||||||
|
|
||||||
|
// then - should detect opencode-notifier without crashing on the tuple entry
|
||||||
|
expect(result.detected).toBe(true)
|
||||||
|
expect(result.pluginName).toBe("opencode-notifier")
|
||||||
|
expect(result.allPlugins).toContain("oh-my-opencode")
|
||||||
|
expect(result.allPlugins).toContain("advanced-tuple-plugin")
|
||||||
|
expect(result.allPlugins).not.toContain(["advanced-tuple-plugin", { debug: true }])
|
||||||
|
})
|
||||||
|
|
||||||
test("should handle JSONC format with comments", () => {
|
test("should handle JSONC format with comments", () => {
|
||||||
// given - opencode.jsonc with comments
|
// given - opencode.jsonc with comments
|
||||||
const opencodeDir = path.join(tempDir, ".opencode")
|
const opencodeDir = path.join(tempDir, ".opencode")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as path from "node:path"
|
|||||||
import { parseJsoncSafe } from "./jsonc-parser"
|
import { parseJsoncSafe } from "./jsonc-parser"
|
||||||
|
|
||||||
interface OpencodeConfig {
|
interface OpencodeConfig {
|
||||||
plugin?: string[]
|
plugin?: (string | [string, ...unknown[]])[]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWindowsAppdataDir(): string | null {
|
function getWindowsAppdataDir(): string | null {
|
||||||
@@ -44,7 +44,9 @@ export function loadOpencodePlugins(directory: string): string[] {
|
|||||||
const result = parseJsoncSafe<OpencodeConfig>(content)
|
const result = parseJsoncSafe<OpencodeConfig>(content)
|
||||||
const plugins = result.data?.plugin ?? []
|
const plugins = result.data?.plugin ?? []
|
||||||
|
|
||||||
for (const plugin of plugins) {
|
for (const rawPlugin of plugins) {
|
||||||
|
const plugin = typeof rawPlugin === "string" ? rawPlugin : Array.isArray(rawPlugin) ? rawPlugin[0] : null
|
||||||
|
if (typeof plugin !== "string") continue
|
||||||
if (seenPluginEntries.has(plugin)) continue
|
if (seenPluginEntries.has(plugin)) continue
|
||||||
seenPluginEntries.add(plugin)
|
seenPluginEntries.add(plugin)
|
||||||
pluginEntries.push(plugin)
|
pluginEntries.push(plugin)
|
||||||
|
|||||||
Reference in New Issue
Block a user