diff --git a/src/cli/config-manager/add-plugin-to-opencode-config.ts b/src/cli/config-manager/add-plugin-to-opencode-config.ts index 208abd56a..23c398873 100644 --- a/src/cli/config-manager/add-plugin-to-opencode-config.ts +++ b/src/cli/config-manager/add-plugin-to-opencode-config.ts @@ -79,12 +79,8 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise const normalizedPlugins = [...otherPlugins] - if (canonicalEntries.length > 0) { - normalizedPlugins.push(canonicalEntries[0]) - } else if (legacyEntries.length > 0) { - const versionMatch = legacyEntries[0].match(/@(.+)$/) - const preservedVersion = versionMatch ? versionMatch[1] : null - normalizedPlugins.push(preservedVersion ? `${PLUGIN_NAME}@${preservedVersion}` : pluginEntry) + if (canonicalEntries.length > 0 || legacyEntries.length > 0) { + normalizedPlugins.push(pluginEntry) } else { normalizedPlugins.push(pluginEntry) } diff --git a/src/cli/config-manager/plugin-detection.test.ts b/src/cli/config-manager/plugin-detection.test.ts index e03e63357..fcd6109f9 100644 --- a/src/cli/config-manager/plugin-detection.test.ts +++ b/src/cli/config-manager/plugin-detection.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it } from "bun:test" +import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test" import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" @@ -6,6 +6,7 @@ import { join } from "node:path" import { resetConfigContext } from "./config-context" import { detectCurrentConfig } from "./detect-current-config" import { addPluginToOpenCodeConfig } from "./add-plugin-to-opencode-config" +import * as pluginNameWithVersion from "./plugin-name-with-version" describe("detectCurrentConfig - single package detection", () => { let testConfigDir = "" @@ -109,17 +110,19 @@ describe("addPluginToOpenCodeConfig - single package writes", () => { expect(savedConfig.plugin).toEqual(["oh-my-openagent"]) }) - it("upgrades a version-pinned legacy entry to canonical", async () => { + it("updates a version-pinned legacy entry to the requested version", async () => { // given - writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-opencode@3.10.0"] }, null, 2) + "\n", "utf-8") + const getPluginNameWithVersionSpy = spyOn(pluginNameWithVersion, "getPluginNameWithVersion").mockResolvedValue("oh-my-openagent@3.16.0") + writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-opencode@3.15.0"] }, null, 2) + "\n", "utf-8") // when - const result = await addPluginToOpenCodeConfig("3.11.0") + const result = await addPluginToOpenCodeConfig("3.16.0") // then expect(result.success).toBe(true) const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8")) - expect(savedConfig.plugin).toEqual(["oh-my-openagent@3.10.0"]) + expect(savedConfig.plugin).toEqual(["oh-my-openagent@3.16.0"]) + getPluginNameWithVersionSpy.mockRestore() }) it("removes stale legacy entry when canonical and legacy entries both exist", async () => { @@ -135,17 +138,36 @@ describe("addPluginToOpenCodeConfig - single package writes", () => { expect(savedConfig.plugin).toEqual(["oh-my-openagent"]) }) - it("preserves a canonical entry when it already exists", async () => { + it("preserves a canonical entry when the same version is re-installed", async () => { // given + const getPluginNameWithVersionSpy = spyOn(pluginNameWithVersion, "getPluginNameWithVersion").mockResolvedValue("oh-my-openagent@3.10.0") writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-openagent@3.10.0"] }, null, 2) + "\n", "utf-8") // when - const result = await addPluginToOpenCodeConfig("3.11.0") + const result = await addPluginToOpenCodeConfig("3.10.0") // then expect(result.success).toBe(true) const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8")) expect(savedConfig.plugin).toEqual(["oh-my-openagent@3.10.0"]) + getPluginNameWithVersionSpy.mockRestore() + }) + + it("blocks a downgrade for a version-pinned canonical entry", async () => { + // given + const getPluginNameWithVersionSpy = spyOn(pluginNameWithVersion, "getPluginNameWithVersion").mockResolvedValue("oh-my-openagent@3.15.0") + writeFileSync(testConfigPath, JSON.stringify({ plugin: ["oh-my-openagent@3.16.0"] }, null, 2) + "\n", "utf-8") + + // when + const result = await addPluginToOpenCodeConfig("3.15.0") + + // then + expect(result.success).toBe(false) + expect(result.error).toContain("Downgrade") + + const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8")) + expect(savedConfig.plugin).toEqual(["oh-my-openagent@3.16.0"]) + getPluginNameWithVersionSpy.mockRestore() }) it("rewrites quoted jsonc plugin field in place", async () => {