diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 141f6156985..70b51676e7c 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -1,6 +1,6 @@ import { Log } from "../util/log" import path from "path" -import { pathToFileURL, fileURLToPath } from "url" +import { pathToFileURL } from "url" import { createRequire } from "module" import os from "os" import z from "zod" @@ -473,7 +473,18 @@ export namespace Config { */ export function getPluginName(plugin: string): string { if (plugin.startsWith("file://")) { - return path.parse(new URL(plugin).pathname).name + const pathname = decodeURIComponent(new URL(plugin).pathname) + const parts = pathname.split("/") + const nodeModules = parts.lastIndexOf("node_modules") + if (nodeModules > -1) { + const name = parts[nodeModules + 1] + if (name?.startsWith("@")) { + const scoped = parts[nodeModules + 2] + if (scoped) return `${name}/${scoped}` + } + if (name) return name + } + return path.posix.parse(pathname).name } const lastAt = plugin.lastIndexOf("@") if (lastAt > 0) { diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index f245dc3493d..ed7cb5ae52e 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -1542,6 +1542,14 @@ describe("getPluginName", () => { expect(Config.getPluginName("file:///some/path/my-plugin.js")).toBe("my-plugin") }) + test("extracts package name from node_modules file:// URL", () => { + const basic = pathToFileURL(path.join("/tmp", "project", "node_modules", "foo", "index.js")).href + const scoped = pathToFileURL(path.join("/tmp", "project", "node_modules", "@scope", "pkg", "dist", "index.js")).href + + expect(Config.getPluginName(basic)).toBe("foo") + expect(Config.getPluginName(scoped)).toBe("@scope/pkg") + }) + test("extracts name from npm package with version", () => { expect(Config.getPluginName("oh-my-opencode@2.4.3")).toBe("oh-my-opencode") expect(Config.getPluginName("some-plugin@1.0.0")).toBe("some-plugin") @@ -1589,6 +1597,17 @@ describe("deduplicatePlugins", () => { expect(result).toEqual(["a-plugin@1.0.0", "b-plugin@1.0.0", "c-plugin@1.0.0"]) }) + test("keeps multiple npm plugins resolved as file:// URLs", () => { + const plugins = [ + pathToFileURL(path.join("/tmp", "project", "node_modules", "foo", "index.js")).href, + pathToFileURL(path.join("/tmp", "project", "node_modules", "bar", "index.js")).href, + ] + + const result = Config.deduplicatePlugins(plugins) + + expect(result).toEqual(plugins) + }) + test("local plugin directory overrides global opencode.json plugin", async () => { await using tmp = await tmpdir({ init: async (dir) => {