Просмотр исходного кода

fix: support scoped npm plugins (#3785)

Co-authored-by: Aiden Cline <[email protected]>
Err 5 месяцев назад
Родитель
Сommit
6f0028644e
3 измененных файлов с 63 добавлено и 2 удалено
  1. 2 0
      .gitignore
  2. 4 2
      packages/opencode/src/plugin/index.ts
  3. 57 0
      packages/opencode/test/config/config.test.ts

+ 2 - 0
.gitignore

@@ -11,3 +11,5 @@ playground
 tmp
 dist
 .turbo
+**/.serena
+.serena/

+ 4 - 2
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)) {

+ 57 - 0
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)
+    },
+  })
+})