summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--packages/opencode/src/plugin/index.ts6
-rw-r--r--packages/opencode/test/config/config.test.ts57
3 files changed, 63 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 81bdb9929..f69a70796 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,5 @@ playground
tmp
dist
.turbo
+**/.serena
+.serena/
diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts
index 0d66b469f..1d433628d 100644
--- a/packages/opencode/src/plugin/index.ts
+++ b/packages/opencode/src/plugin/index.ts
@@ -34,8 +34,10 @@ export namespace Plugin {
for (let plugin of plugins) {
log.info("loading plugin", { path: plugin })
if (!plugin.startsWith("file://")) {
- const [pkg, version] = plugin.split("@")
- plugin = await BunProc.install(pkg, version ?? "latest")
+ const lastAtIndex = plugin.lastIndexOf("@")
+ const pkg = lastAtIndex > 0 ? plugin.substring(0, lastAtIndex) : plugin
+ const version = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest"
+ plugin = await BunProc.install(pkg, version)
}
const mod = await import(plugin)
for (const [_name, fn] of Object.entries<PluginInstance>(mod)) {
diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts
index 1c1755a8a..75b41fc00 100644
--- a/packages/opencode/test/config/config.test.ts
+++ b/packages/opencode/test/config/config.test.ts
@@ -4,6 +4,7 @@ import { Instance } from "../../src/project/instance"
import { tmpdir } from "../fixture/fixture"
import path from "path"
import fs from "fs/promises"
+import { pathToFileURL } from "url"
test("loads config with defaults when no files exist", async () => {
await using tmp = await tmpdir()
@@ -350,3 +351,59 @@ test("gets config directories", async () => {
},
})
})
+
+test("resolves scoped npm plugins in config", async () => {
+ await using tmp = await tmpdir({
+ init: async (dir) => {
+ const pluginDir = path.join(dir, "node_modules", "@scope", "plugin")
+ await fs.mkdir(pluginDir, { recursive: true })
+
+ await Bun.write(
+ path.join(dir, "package.json"),
+ JSON.stringify({ name: "config-fixture", version: "1.0.0", type: "module" }, null, 2),
+ )
+
+ await Bun.write(
+ path.join(pluginDir, "package.json"),
+ JSON.stringify(
+ {
+ name: "@scope/plugin",
+ version: "1.0.0",
+ type: "module",
+ main: "./index.js",
+ },
+ null,
+ 2,
+ ),
+ )
+
+ await Bun.write(path.join(pluginDir, "index.js"), "export default {}\n")
+
+ await Bun.write(
+ path.join(dir, "opencode.json"),
+ JSON.stringify(
+ { $schema: "https://opencode.ai/config.json", plugin: ["@scope/plugin"] },
+ null,
+ 2,
+ ),
+ )
+ },
+ })
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ const config = await Config.get()
+ const pluginEntries = config.plugin ?? []
+
+ const baseUrl = pathToFileURL(path.join(tmp.path, "opencode.json")).href
+ const expected = import.meta.resolve("@scope/plugin", baseUrl)
+
+ expect(pluginEntries.includes(expected)).toBe(true)
+
+ const scopedEntry = pluginEntries.find((entry) => entry === expected)
+ expect(scopedEntry).toBeDefined()
+ expect(scopedEntry?.includes("/node_modules/@scope/plugin/")).toBe(true)
+ },
+ })
+})