|
1 | | -import { test, expect, mock, afterEach } from "bun:test" |
| 1 | +import { test, expect, describe, mock } from "bun:test" |
2 | 2 | import { Config } from "../../src/config/config" |
3 | 3 | import { Instance } from "../../src/project/instance" |
4 | 4 | import { Auth } from "../../src/auth" |
@@ -1145,3 +1145,91 @@ test("project config overrides remote well-known config", async () => { |
1145 | 1145 | Auth.all = originalAuthAll |
1146 | 1146 | } |
1147 | 1147 | }) |
| 1148 | + |
| 1149 | +describe("getPluginName", () => { |
| 1150 | + test("extracts name from file:// URL", () => { |
| 1151 | + expect(Config.getPluginName("file:///path/to/plugin/foo.js")).toBe("foo") |
| 1152 | + expect(Config.getPluginName("file:///path/to/plugin/bar.ts")).toBe("bar") |
| 1153 | + expect(Config.getPluginName("file:///some/path/my-plugin.js")).toBe("my-plugin") |
| 1154 | + }) |
| 1155 | + |
| 1156 | + test("extracts name from npm package with version", () => { |
| 1157 | + expect(Config.getPluginName("oh-my-opencode@2.4.3")).toBe("oh-my-opencode") |
| 1158 | + expect(Config.getPluginName("some-plugin@1.0.0")).toBe("some-plugin") |
| 1159 | + expect(Config.getPluginName("plugin@latest")).toBe("plugin") |
| 1160 | + }) |
| 1161 | + |
| 1162 | + test("extracts name from scoped npm package", () => { |
| 1163 | + expect(Config.getPluginName("@scope/pkg@1.0.0")).toBe("@scope/pkg") |
| 1164 | + expect(Config.getPluginName("@opencode/plugin@2.0.0")).toBe("@opencode/plugin") |
| 1165 | + }) |
| 1166 | + |
| 1167 | + test("returns full string for package without version", () => { |
| 1168 | + expect(Config.getPluginName("some-plugin")).toBe("some-plugin") |
| 1169 | + expect(Config.getPluginName("@scope/pkg")).toBe("@scope/pkg") |
| 1170 | + }) |
| 1171 | +}) |
| 1172 | + |
| 1173 | +describe("deduplicatePlugins", () => { |
| 1174 | + test("removes duplicates keeping higher priority (later entries)", () => { |
| 1175 | + const plugins = ["global-plugin@1.0.0", "shared-plugin@1.0.0", "local-plugin@2.0.0", "shared-plugin@2.0.0"] |
| 1176 | + |
| 1177 | + const result = Config.deduplicatePlugins(plugins) |
| 1178 | + |
| 1179 | + expect(result).toContain("global-plugin@1.0.0") |
| 1180 | + expect(result).toContain("local-plugin@2.0.0") |
| 1181 | + expect(result).toContain("shared-plugin@2.0.0") |
| 1182 | + expect(result).not.toContain("shared-plugin@1.0.0") |
| 1183 | + expect(result.length).toBe(3) |
| 1184 | + }) |
| 1185 | + |
| 1186 | + test("prefers local file over npm package with same name", () => { |
| 1187 | + const plugins = ["oh-my-opencode@2.4.3", "file:///project/.opencode/plugin/oh-my-opencode.js"] |
| 1188 | + |
| 1189 | + const result = Config.deduplicatePlugins(plugins) |
| 1190 | + |
| 1191 | + expect(result.length).toBe(1) |
| 1192 | + expect(result[0]).toBe("file:///project/.opencode/plugin/oh-my-opencode.js") |
| 1193 | + }) |
| 1194 | + |
| 1195 | + test("preserves order of remaining plugins", () => { |
| 1196 | + const plugins = ["a-plugin@1.0.0", "b-plugin@1.0.0", "c-plugin@1.0.0"] |
| 1197 | + |
| 1198 | + const result = Config.deduplicatePlugins(plugins) |
| 1199 | + |
| 1200 | + expect(result).toEqual(["a-plugin@1.0.0", "b-plugin@1.0.0", "c-plugin@1.0.0"]) |
| 1201 | + }) |
| 1202 | + |
| 1203 | + test("local plugin directory overrides global opencode.json plugin", async () => { |
| 1204 | + await using tmp = await tmpdir({ |
| 1205 | + init: async (dir) => { |
| 1206 | + const projectDir = path.join(dir, "project") |
| 1207 | + const opencodeDir = path.join(projectDir, ".opencode") |
| 1208 | + const pluginDir = path.join(opencodeDir, "plugin") |
| 1209 | + await fs.mkdir(pluginDir, { recursive: true }) |
| 1210 | + |
| 1211 | + await Bun.write( |
| 1212 | + path.join(dir, "opencode.json"), |
| 1213 | + JSON.stringify({ |
| 1214 | + $schema: "https://opencode.ai/config.json", |
| 1215 | + plugin: ["my-plugin@1.0.0"], |
| 1216 | + }), |
| 1217 | + ) |
| 1218 | + |
| 1219 | + await Bun.write(path.join(pluginDir, "my-plugin.js"), "export default {}") |
| 1220 | + }, |
| 1221 | + }) |
| 1222 | + |
| 1223 | + await Instance.provide({ |
| 1224 | + directory: path.join(tmp.path, "project"), |
| 1225 | + fn: async () => { |
| 1226 | + const config = await Config.get() |
| 1227 | + const plugins = config.plugin ?? [] |
| 1228 | + |
| 1229 | + const myPlugins = plugins.filter((p) => Config.getPluginName(p) === "my-plugin") |
| 1230 | + expect(myPlugins.length).toBe(1) |
| 1231 | + expect(myPlugins[0].startsWith("file://")).toBe(true) |
| 1232 | + }, |
| 1233 | + }) |
| 1234 | + }) |
| 1235 | +}) |
0 commit comments