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 1e8eb872d..82592fcf7 100644 --- a/src/cli/config-manager/add-plugin-to-opencode-config.ts +++ b/src/cli/config-manager/add-plugin-to-opencode-config.ts @@ -7,7 +7,8 @@ import { detectConfigFormat } from "./opencode-config-format" import { parseOpenCodeConfigFileWithError, type OpenCodeConfig } from "./parse-opencode-config-file" import { getPluginNameWithVersion } from "./plugin-name-with-version" -const PACKAGE_NAME = "oh-my-opencode" +const OLD_PACKAGE_NAME = "oh-my-opencode" +const NEW_PACKAGE_NAME = "oh-my-openagent" export async function addPluginToOpenCodeConfig(currentVersion: string): Promise { try { @@ -21,7 +22,7 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise } const { format, path } = detectConfigFormat() - const pluginEntry = await getPluginNameWithVersion(currentVersion) + const pluginEntry = await getPluginNameWithVersion(currentVersion, NEW_PACKAGE_NAME) try { if (format === "none") { @@ -41,7 +42,13 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise const config = parseResult.config const plugins = config.plugin ?? [] - const existingIndex = plugins.findIndex((p) => p === PACKAGE_NAME || p.startsWith(`${PACKAGE_NAME}@`)) + const existingIndex = plugins.findIndex( + (p) => + p === OLD_PACKAGE_NAME || + p.startsWith(`${OLD_PACKAGE_NAME}@`) || + p === NEW_PACKAGE_NAME || + p.startsWith(`${NEW_PACKAGE_NAME}@`) + ) if (existingIndex !== -1) { if (plugins[existingIndex] === pluginEntry) { diff --git a/src/cli/config-manager/detect-current-config.ts b/src/cli/config-manager/detect-current-config.ts index b8ac6569a..246d27c9e 100644 --- a/src/cli/config-manager/detect-current-config.ts +++ b/src/cli/config-manager/detect-current-config.ts @@ -61,7 +61,9 @@ hasKimiForCoding: false, const openCodeConfig = parseResult.config const plugins = openCodeConfig.plugin ?? [] - result.isInstalled = plugins.some((p) => p.startsWith("oh-my-opencode")) + const OLD_PACKAGE_NAME = "oh-my-opencode" +const NEW_PACKAGE_NAME = "oh-my-openagent" +result.isInstalled = plugins.some((p) => p.startsWith(OLD_PACKAGE_NAME) || p.startsWith(NEW_PACKAGE_NAME)) if (!result.isInstalled) { return result diff --git a/src/cli/config-manager/plugin-detection.test.ts b/src/cli/config-manager/plugin-detection.test.ts new file mode 100644 index 000000000..b95ba75fd --- /dev/null +++ b/src/cli/config-manager/plugin-detection.test.ts @@ -0,0 +1,196 @@ +import { afterEach, beforeEach, describe, expect, it } from "bun:test" +import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" + +import { resetConfigContext } from "./config-context" +import { detectCurrentConfig } from "./detect-current-config" +import { addPluginToOpenCodeConfig } from "./add-plugin-to-opencode-config" + +describe("detectCurrentConfig - dual name detection", () => { + let testConfigDir = "" + let testConfigPath = "" + + beforeEach(() => { + testConfigDir = join(tmpdir(), `omo-detect-config-${Date.now()}-${Math.random().toString(36).slice(2)}`) + testConfigPath = join(testConfigDir, "opencode.json") + + mkdirSync(testConfigDir, { recursive: true }) + process.env.OPENCODE_CONFIG_DIR = testConfigDir + resetConfigContext() + }) + + afterEach(() => { + rmSync(testConfigDir, { recursive: true, force: true }) + resetConfigContext() + delete process.env.OPENCODE_CONFIG_DIR + }) + + it("detects oh-my-opencode in plugin array", () => { + // given + const config = { plugin: ["oh-my-opencode"] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = detectCurrentConfig() + + // then + expect(result.isInstalled).toBe(true) + }) + + it("detects oh-my-openagent in plugin array", () => { + // given + const config = { plugin: ["oh-my-openagent"] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = detectCurrentConfig() + + // then + expect(result.isInstalled).toBe(true) + }) + + it("detects oh-my-opencode with version pin", () => { + // given + const config = { plugin: ["oh-my-opencode@3.11.0"] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = detectCurrentConfig() + + // then + expect(result.isInstalled).toBe(true) + }) + + it("detects oh-my-openagent with version pin", () => { + // given + const config = { plugin: ["oh-my-openagent@3.12.0"] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = detectCurrentConfig() + + // then + expect(result.isInstalled).toBe(true) + }) + + it("returns false when plugin not present", () => { + // given + const config = { plugin: ["some-other-plugin"] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = detectCurrentConfig() + + // then + expect(result.isInstalled).toBe(false) + }) +}) + +describe("addPluginToOpenCodeConfig - dual name detection", () => { + let testConfigDir = "" + let testConfigPath = "" + + beforeEach(() => { + testConfigDir = join(tmpdir(), `omo-add-plugin-${Date.now()}-${Math.random().toString(36).slice(2)}`) + testConfigPath = join(testConfigDir, "opencode.json") + + mkdirSync(testConfigDir, { recursive: true }) + process.env.OPENCODE_CONFIG_DIR = testConfigDir + resetConfigContext() + }) + + afterEach(() => { + rmSync(testConfigDir, { recursive: true, force: true }) + resetConfigContext() + delete process.env.OPENCODE_CONFIG_DIR + }) + + it("finds and replaces old oh-my-opencode with new name", async () => { + // given + const config = { plugin: ["oh-my-opencode"] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = await addPluginToOpenCodeConfig("3.11.0") + + // then + expect(result.success).toBe(true) + const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8")) + expect(savedConfig.plugin).toContain("oh-my-openagent") + expect(savedConfig.plugin).not.toContain("oh-my-opencode") + }) + + it("finds and replaces oh-my-openagent with new name", async () => { + // given + const config = { plugin: ["oh-my-openagent"] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = await addPluginToOpenCodeConfig("3.11.0") + + // then + expect(result.success).toBe(true) + const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8")) + expect(savedConfig.plugin).toContain("oh-my-openagent") + expect(savedConfig.plugin).not.toContain("oh-my-opencode") + }) + + it("finds and replaces version-pinned oh-my-opencode@X.Y.Z", async () => { + // given + const config = { plugin: ["oh-my-opencode@3.10.0"] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = await addPluginToOpenCodeConfig("3.11.0") + + // then + expect(result.success).toBe(true) + const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8")) + expect(savedConfig.plugin).toContain("oh-my-openagent") + expect(savedConfig.plugin).not.toContain("oh-my-opencode@3.10.0") + }) + + it("finds and replaces version-pinned oh-my-openagent@X.Y.Z", async () => { + // given + const config = { plugin: ["oh-my-openagent@3.10.0"] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = await addPluginToOpenCodeConfig("3.11.0") + + // then + expect(result.success).toBe(true) + const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8")) + expect(savedConfig.plugin).toContain("oh-my-openagent") + expect(savedConfig.plugin).not.toContain("oh-my-openagent@3.10.0") + }) + + it("adds new plugin when none exists", async () => { + // given - no plugin array + const config = {} + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = await addPluginToOpenCodeConfig("3.11.0") + + // then + expect(result.success).toBe(true) + const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8")) + expect(savedConfig.plugin).toContain("oh-my-openagent") + }) + + it("adds plugin when plugin array is empty", async () => { + // given - empty plugin array + const config = { plugin: [] } + writeFileSync(testConfigPath, JSON.stringify(config, null, 2) + "\n", "utf-8") + + // when + const result = await addPluginToOpenCodeConfig("3.11.0") + + // then + expect(result.success).toBe(true) + const savedConfig = JSON.parse(readFileSync(testConfigPath, "utf-8")) + expect(savedConfig.plugin).toContain("oh-my-openagent") + }) +}) diff --git a/src/cli/config-manager/plugin-name-with-version.ts b/src/cli/config-manager/plugin-name-with-version.ts index 501ccafba..eb9a85da0 100644 --- a/src/cli/config-manager/plugin-name-with-version.ts +++ b/src/cli/config-manager/plugin-name-with-version.ts @@ -1,28 +1,33 @@ import { fetchNpmDistTags } from "./npm-dist-tags" -const PACKAGE_NAME = "oh-my-opencode" +const DEFAULT_PACKAGE_NAME = "oh-my-opencode" +const NEW_PACKAGE_NAME = "oh-my-openagent" const PRIORITIZED_TAGS = ["latest", "beta", "next"] as const -function getFallbackEntry(version: string): string { +function getFallbackEntry(version: string, packageName: string): string { const prereleaseMatch = version.match(/-([a-zA-Z][a-zA-Z0-9-]*)(?:\.|$)/) if (prereleaseMatch) { - return `${PACKAGE_NAME}@${prereleaseMatch[1]}` + return `${packageName}@${prereleaseMatch[1]}` } - return PACKAGE_NAME + return packageName } -export async function getPluginNameWithVersion(currentVersion: string): Promise { - const distTags = await fetchNpmDistTags(PACKAGE_NAME) +export async function getPluginNameWithVersion( + currentVersion: string, + packageName: string = DEFAULT_PACKAGE_NAME +): Promise { + const distTags = await fetchNpmDistTags(NEW_PACKAGE_NAME) + if (distTags) { const allTags = new Set([...PRIORITIZED_TAGS, ...Object.keys(distTags)]) for (const tag of allTags) { if (distTags[tag] === currentVersion) { - return `${PACKAGE_NAME}@${tag}` + return `${packageName}@${tag}` } } } - return getFallbackEntry(currentVersion) + return getFallbackEntry(currentVersion, packageName) }