|
|
@@ -1,4 +1,4 @@
|
|
|
-import { test, expect, mock, afterEach } from "bun:test"
|
|
|
+import { test, expect, describe, mock } from "bun:test"
|
|
|
import { Config } from "../../src/config/config"
|
|
|
import { Instance } from "../../src/project/instance"
|
|
|
import { Auth } from "../../src/auth"
|
|
|
@@ -1145,3 +1145,91 @@ test("project config overrides remote well-known config", async () => {
|
|
|
Auth.all = originalAuthAll
|
|
|
}
|
|
|
})
|
|
|
+
|
|
|
+describe("getPluginName", () => {
|
|
|
+ test("extracts name from file:// URL", () => {
|
|
|
+ expect(Config.getPluginName("file:///path/to/plugin/foo.js")).toBe("foo")
|
|
|
+ expect(Config.getPluginName("file:///path/to/plugin/bar.ts")).toBe("bar")
|
|
|
+ expect(Config.getPluginName("file:///some/path/my-plugin.js")).toBe("my-plugin")
|
|
|
+ })
|
|
|
+
|
|
|
+ test("extracts name from npm package with version", () => {
|
|
|
+ expect(Config.getPluginName("[email protected]")).toBe("oh-my-opencode")
|
|
|
+ expect(Config.getPluginName("[email protected]")).toBe("some-plugin")
|
|
|
+ expect(Config.getPluginName("plugin@latest")).toBe("plugin")
|
|
|
+ })
|
|
|
+
|
|
|
+ test("extracts name from scoped npm package", () => {
|
|
|
+ expect(Config.getPluginName("@scope/[email protected]")).toBe("@scope/pkg")
|
|
|
+ expect(Config.getPluginName("@opencode/[email protected]")).toBe("@opencode/plugin")
|
|
|
+ })
|
|
|
+
|
|
|
+ test("returns full string for package without version", () => {
|
|
|
+ expect(Config.getPluginName("some-plugin")).toBe("some-plugin")
|
|
|
+ expect(Config.getPluginName("@scope/pkg")).toBe("@scope/pkg")
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+describe("deduplicatePlugins", () => {
|
|
|
+ test("removes duplicates keeping higher priority (later entries)", () => {
|
|
|
+ const plugins = ["[email protected]", "[email protected]", "[email protected]", "[email protected]"]
|
|
|
+
|
|
|
+ const result = Config.deduplicatePlugins(plugins)
|
|
|
+
|
|
|
+ expect(result).toContain("[email protected]")
|
|
|
+ expect(result).toContain("[email protected]")
|
|
|
+ expect(result).toContain("[email protected]")
|
|
|
+ expect(result).not.toContain("[email protected]")
|
|
|
+ expect(result.length).toBe(3)
|
|
|
+ })
|
|
|
+
|
|
|
+ test("prefers local file over npm package with same name", () => {
|
|
|
+ const plugins = ["[email protected]", "file:///project/.opencode/plugin/oh-my-opencode.js"]
|
|
|
+
|
|
|
+ const result = Config.deduplicatePlugins(plugins)
|
|
|
+
|
|
|
+ expect(result.length).toBe(1)
|
|
|
+ expect(result[0]).toBe("file:///project/.opencode/plugin/oh-my-opencode.js")
|
|
|
+ })
|
|
|
+
|
|
|
+ test("preserves order of remaining plugins", () => {
|
|
|
+ const plugins = ["[email protected]", "[email protected]", "[email protected]"]
|
|
|
+
|
|
|
+ const result = Config.deduplicatePlugins(plugins)
|
|
|
+
|
|
|
+ expect(result).toEqual(["[email protected]", "[email protected]", "[email protected]"])
|
|
|
+ })
|
|
|
+
|
|
|
+ test("local plugin directory overrides global opencode.json plugin", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ const projectDir = path.join(dir, "project")
|
|
|
+ const opencodeDir = path.join(projectDir, ".opencode")
|
|
|
+ const pluginDir = path.join(opencodeDir, "plugin")
|
|
|
+ await fs.mkdir(pluginDir, { recursive: true })
|
|
|
+
|
|
|
+ await Bun.write(
|
|
|
+ path.join(dir, "opencode.json"),
|
|
|
+ JSON.stringify({
|
|
|
+ $schema: "https://opencode.ai/config.json",
|
|
|
+ plugin: ["[email protected]"],
|
|
|
+ }),
|
|
|
+ )
|
|
|
+
|
|
|
+ await Bun.write(path.join(pluginDir, "my-plugin.js"), "export default {}")
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ await Instance.provide({
|
|
|
+ directory: path.join(tmp.path, "project"),
|
|
|
+ fn: async () => {
|
|
|
+ const config = await Config.get()
|
|
|
+ const plugins = config.plugin ?? []
|
|
|
+
|
|
|
+ const myPlugins = plugins.filter((p) => Config.getPluginName(p) === "my-plugin")
|
|
|
+ expect(myPlugins.length).toBe(1)
|
|
|
+ expect(myPlugins[0].startsWith("file://")).toBe(true)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|