From d60ca63ed4ddd35fc2a837657c08d1ef919f2428 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 4 Apr 2026 01:32:17 +0900 Subject: [PATCH 1/4] test(config): add regression coverage for legacy migration bugs Lock the current legacy config and plugin migration failures in place before the fixes land so the three regressions stay covered. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../legacy-plugin-toast/auto-migrate.test.ts | 60 ++++++++++++++----- src/plugin-config.test.ts | 31 +++++++++- src/shared/external-plugin-detector.test.ts | 35 ++++++++++- src/shared/migrate-legacy-config-file.test.ts | 6 +- 4 files changed, 115 insertions(+), 17 deletions(-) diff --git a/src/hooks/legacy-plugin-toast/auto-migrate.test.ts b/src/hooks/legacy-plugin-toast/auto-migrate.test.ts index 0ee33cb8c..b38686019 100644 --- a/src/hooks/legacy-plugin-toast/auto-migrate.test.ts +++ b/src/hooks/legacy-plugin-toast/auto-migrate.test.ts @@ -1,7 +1,14 @@ -import { afterEach, beforeEach, describe, expect, it } from "bun:test" -import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" +import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test" +import { mkdirSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" + +const mockMigrateLegacyPluginEntry = mock(() => true) + +mock.module("./plugin-entry-migrator", () => ({ + migrateLegacyPluginEntry: mockMigrateLegacyPluginEntry, +})) + async function importFreshAutoMigrateModule(): Promise { return import(`./auto-migrate?test=${Date.now()}-${Math.random()}`) } @@ -12,6 +19,8 @@ describe("autoMigrateLegacyPluginEntry", () => { beforeEach(() => { testConfigDir = join(tmpdir(), `omo-legacy-migrate-${Date.now()}-${Math.random().toString(36).slice(2)}`) mkdirSync(testConfigDir, { recursive: true }) + mockMigrateLegacyPluginEntry.mockReset() + mockMigrateLegacyPluginEntry.mockReturnValue(true) }) afterEach(() => { @@ -35,8 +44,7 @@ describe("autoMigrateLegacyPluginEntry", () => { expect(result.migrated).toBe(true) expect(result.from).toBe("oh-my-opencode") expect(result.to).toBe("oh-my-openagent") - const saved = JSON.parse(readFileSync(join(testConfigDir, "opencode.json"), "utf-8")) - expect(saved.plugin).toEqual(["oh-my-openagent"]) + expect(mockMigrateLegacyPluginEntry).toHaveBeenCalledWith(join(testConfigDir, "opencode.json")) }) }) @@ -57,8 +65,7 @@ describe("autoMigrateLegacyPluginEntry", () => { expect(result.migrated).toBe(true) expect(result.from).toBe("oh-my-opencode@3.10.0") expect(result.to).toBe("oh-my-openagent@3.10.0") - const saved = JSON.parse(readFileSync(join(testConfigDir, "opencode.json"), "utf-8")) - expect(saved.plugin).toEqual(["oh-my-openagent@3.10.0"]) + expect(mockMigrateLegacyPluginEntry).toHaveBeenCalledWith(join(testConfigDir, "opencode.json")) }) }) @@ -77,8 +84,8 @@ describe("autoMigrateLegacyPluginEntry", () => { // then expect(result.migrated).toBe(true) - const saved = JSON.parse(readFileSync(join(testConfigDir, "opencode.json"), "utf-8")) - expect(saved.plugin).toEqual(["oh-my-openagent"]) + expect(result.to).toBe("oh-my-openagent") + expect(mockMigrateLegacyPluginEntry).toHaveBeenCalledWith(join(testConfigDir, "opencode.json")) }) }) @@ -93,6 +100,7 @@ describe("autoMigrateLegacyPluginEntry", () => { // then expect(result.migrated).toBe(false) expect(result.from).toBeNull() + expect(mockMigrateLegacyPluginEntry).not.toHaveBeenCalled() }) }) @@ -111,10 +119,35 @@ describe("autoMigrateLegacyPluginEntry", () => { // then expect(result.migrated).toBe(true) - const content = readFileSync(join(testConfigDir, "opencode.jsonc"), "utf-8") - expect(content).toContain("// my config") - expect(content).toContain("oh-my-openagent") - expect(content).not.toContain("oh-my-opencode") + expect(result.to).toBe("oh-my-openagent") + expect(mockMigrateLegacyPluginEntry).toHaveBeenCalledWith(join(testConfigDir, "opencode.jsonc")) + }) + }) + + describe("#given opencode.jsonc has a nested plugin key before the root plugin array", () => { + it("#then migrates only the root plugin entry", async () => { + // given + writeFileSync( + join(testConfigDir, "opencode.jsonc"), + `{ + "nested": { + "plugin": ["oh-my-opencode"] + }, + "plugin": ["oh-my-opencode@latest"] +} +`, + ) + + const { autoMigrateLegacyPluginEntry } = await importFreshAutoMigrateModule() + + // when + const result = autoMigrateLegacyPluginEntry(testConfigDir) + + // then + expect(result.migrated).toBe(true) + expect(result.from).toBe("oh-my-opencode@latest") + expect(result.to).toBe("oh-my-openagent@latest") + expect(mockMigrateLegacyPluginEntry).toHaveBeenCalledWith(join(testConfigDir, "opencode.jsonc")) }) }) @@ -131,8 +164,7 @@ describe("autoMigrateLegacyPluginEntry", () => { // then expect(result.migrated).toBe(false) - const content = readFileSync(join(testConfigDir, "opencode.json"), "utf-8") - expect(content).toBe(original) + expect(mockMigrateLegacyPluginEntry).not.toHaveBeenCalled() }) }) }) diff --git a/src/plugin-config.test.ts b/src/plugin-config.test.ts index 8ecaea7f0..19da20ece 100644 --- a/src/plugin-config.test.ts +++ b/src/plugin-config.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, mock, spyOn } from "bun:test"; -import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs" +import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" import * as shared from "./shared" @@ -321,4 +321,33 @@ describe("loadPluginConfig", () => { // then expect(config.mcp_env_allowlist).toEqual(["USER_ONLY_TOKEN"]) }) + + it("should ignore edits to the renamed legacy backup after migration", () => { + // given + const rootDir = mkdtempSync(join(tmpdir(), "omo-plugin-config-legacy-")) + const userConfigDir = join(rootDir, "user-config") + const projectDir = join(rootDir, "project") + const projectConfigDir = join(projectDir, ".opencode") + const legacyConfigPath = join(projectConfigDir, "oh-my-opencode.jsonc") + const backupConfigPath = `${legacyConfigPath}.bak` + const canonicalConfigPath = join(projectConfigDir, "oh-my-openagent.jsonc") + + tempDirs.push(rootDir) + mkdirSync(userConfigDir, { recursive: true }) + mkdirSync(projectConfigDir, { recursive: true }) + writeFileSync(legacyConfigPath, JSON.stringify({ agents: { oracle: { model: "openai/gpt-5.4" } } })) + + spyOn(shared, "getOpenCodeConfigDir").mockReturnValue(userConfigDir) + + // when + loadPluginConfig(projectDir, {}) + writeFileSync(backupConfigPath, JSON.stringify({ agents: { oracle: { model: "openai/gpt-5-nano" } } })) + const reloadedConfig = loadPluginConfig(projectDir, {}) + + // then + expect(existsSync(legacyConfigPath)).toBe(false) + expect(existsSync(backupConfigPath)).toBe(true) + expect(readFileSync(canonicalConfigPath, "utf-8")).toContain('"openai/gpt-5.4"') + expect(reloadedConfig.agents?.oracle?.model).toBe("openai/gpt-5.4") + }) }) diff --git a/src/shared/external-plugin-detector.test.ts b/src/shared/external-plugin-detector.test.ts index 03ecfd5a8..a220dff90 100644 --- a/src/shared/external-plugin-detector.test.ts +++ b/src/shared/external-plugin-detector.test.ts @@ -1,18 +1,26 @@ -import { describe, expect, test, beforeEach, afterEach } from "bun:test" +import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test" import { detectExternalNotificationPlugin, getNotificationConflictWarning, detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./external-plugin-detector" import * as fs from "node:fs" import * as path from "node:path" import * as os from "node:os" +async function importFreshExternalPluginDetectorModule(): Promise { + return import(`./external-plugin-detector?test=${Date.now()}-${Math.random()}`) +} + describe("external-plugin-detector", () => { let tempDir: string + let tempHomeDir: string beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "omo-test-")) + tempHomeDir = fs.mkdtempSync(path.join(os.tmpdir(), "omo-home-")) }) afterEach(() => { + mock.restore() fs.rmSync(tempDir, { recursive: true, force: true }) + fs.rmSync(tempHomeDir, { recursive: true, force: true }) }) describe("detectExternalNotificationPlugin", () => { @@ -399,6 +407,31 @@ describe("external-plugin-detector", () => { expect(result.pluginName).toBe("opencode-skills") }) + test("should detect user-level opencode-skills when project config exists without plugins", async () => { + // given + const projectConfigDir = path.join(tempDir, ".opencode") + const userConfigDir = path.join(tempHomeDir, ".config", "opencode") + fs.mkdirSync(projectConfigDir, { recursive: true }) + fs.mkdirSync(userConfigDir, { recursive: true }) + fs.writeFileSync(path.join(projectConfigDir, "opencode.json"), JSON.stringify({})) + fs.writeFileSync(path.join(userConfigDir, "opencode.json"), JSON.stringify({ plugin: ["opencode-skills"] })) + + const nodeOs = await import("node:os") + mock.module("node:os", () => ({ + ...nodeOs, + homedir: () => tempHomeDir, + })) + const { detectExternalSkillPlugin: detectExternalSkillPluginFresh } = await importFreshExternalPluginDetectorModule() + + // when + const result = detectExternalSkillPluginFresh(tempDir) + + // then + expect(result.detected).toBe(true) + expect(result.pluginName).toBe("opencode-skills") + expect(result.allPlugins).toEqual(["opencode-skills"]) + }) + test("should NOT match opencode-skills-extra (suffix variation)", () => { // given - plugin with similar name but different suffix const opencodeDir = path.join(tempDir, ".opencode") diff --git a/src/shared/migrate-legacy-config-file.test.ts b/src/shared/migrate-legacy-config-file.test.ts index 0e032c8b9..7fd8a1d7a 100644 --- a/src/shared/migrate-legacy-config-file.test.ts +++ b/src/shared/migrate-legacy-config-file.test.ts @@ -18,15 +18,19 @@ describe("migrateLegacyConfigFile", () => { describe("#given oh-my-opencode.jsonc exists but oh-my-openagent.jsonc does not", () => { describe("#when migrating the config file", () => { - it("#then copies to oh-my-openagent.jsonc", () => { + it("#then writes oh-my-openagent.jsonc and renames the legacy file to a backup", () => { const legacyPath = join(testDir, "oh-my-opencode.jsonc") + const backupPath = join(testDir, "oh-my-opencode.jsonc.bak") writeFileSync(legacyPath, '{ "agents": {} }') const result = migrateLegacyConfigFile(legacyPath) expect(result).toBe(true) expect(existsSync(join(testDir, "oh-my-openagent.jsonc"))).toBe(true) + expect(existsSync(legacyPath)).toBe(false) + expect(existsSync(backupPath)).toBe(true) expect(readFileSync(join(testDir, "oh-my-openagent.jsonc"), "utf-8")).toBe('{ "agents": {} }') + expect(readFileSync(backupPath, "utf-8")).toBe('{ "agents": {} }') }) }) }) From fedb73c7369be9f09dbcd1323cd1ea7784402ba9 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 4 Apr 2026 01:32:31 +0900 Subject: [PATCH 2/4] fix(shared): migrate legacy config files atomically Write the canonical config through a temp file and remove the legacy dual-file trap by archiving or deleting the old basename after migration. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/shared/migrate-legacy-config-file.ts | 56 +++++++++++++++++++++--- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/src/shared/migrate-legacy-config-file.ts b/src/shared/migrate-legacy-config-file.ts index 15d0df232..ea7ad30a9 100644 --- a/src/shared/migrate-legacy-config-file.ts +++ b/src/shared/migrate-legacy-config-file.ts @@ -1,4 +1,4 @@ -import { existsSync, copyFileSync, renameSync } from "node:fs" +import { closeSync, existsSync, fsyncSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs" import { join, dirname, basename } from "node:path" import { log } from "./logger" @@ -10,6 +10,49 @@ function buildCanonicalPath(legacyPath: string): string { return join(dir, `${CONFIG_BASENAME}${ext}`) } +function writeFileAtomically(filePath: string, content: string): void { + const tempPath = `${filePath}.tmp` + writeFileSync(tempPath, content, "utf-8") + const tempFileDescriptor = openSync(tempPath, "r") + try { + fsyncSync(tempFileDescriptor) + } finally { + closeSync(tempFileDescriptor) + } + renameSync(tempPath, filePath) +} + +function archiveLegacyConfigFile(legacyPath: string): boolean { + const backupPath = `${legacyPath}.bak` + + try { + renameSync(legacyPath, backupPath) + log("[migrateLegacyConfigFile] Legacy config was migrated and renamed to backup. Update the canonical file only.", { + legacyPath, + backupPath, + }) + return true + } catch (renameError) { + try { + rmSync(legacyPath) + log("[migrateLegacyConfigFile] Legacy config was migrated and removed after backup rename failed. Update the canonical file only.", { + legacyPath, + backupPath, + renameError, + }) + return true + } catch (removeError) { + log("[migrateLegacyConfigFile] WARNING: canonical config was written but the legacy file still exists and will be ignored. Remove or rename it manually.", { + legacyPath, + backupPath, + renameError, + removeError, + }) + return false + } + } +} + export function migrateLegacyConfigFile(legacyPath: string): boolean { if (!existsSync(legacyPath)) return false if (!basename(legacyPath).startsWith(LEGACY_CONFIG_BASENAME)) return false @@ -18,14 +61,17 @@ export function migrateLegacyConfigFile(legacyPath: string): boolean { if (existsSync(canonicalPath)) return false try { - copyFileSync(legacyPath, canonicalPath) - log("[migrateLegacyConfigFile] Copied legacy config to canonical path", { + const content = readFileSync(legacyPath, "utf-8") + writeFileAtomically(canonicalPath, content) + const archivedLegacyConfig = archiveLegacyConfigFile(legacyPath) + log("[migrateLegacyConfigFile] Migrated legacy config to canonical path", { from: legacyPath, to: canonicalPath, + archivedLegacyConfig, }) - return true + return archivedLegacyConfig } catch (error) { - log("[migrateLegacyConfigFile] Failed to copy legacy config file", { legacyPath, error }) + log("[migrateLegacyConfigFile] Failed to migrate legacy config file", { legacyPath, error }) return false } } From 290f7f95431316162068091ebf7c43001636ada8 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 4 Apr 2026 01:32:42 +0900 Subject: [PATCH 3/4] fix(shared): merge project and user opencode plugin detection Stop dropping user-level plugin conflicts when an empty project config exists by collecting plugin entries from every supported OpenCode config path. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/shared/external-plugin-detector.ts | 49 +--------------------- src/shared/load-opencode-plugins.ts | 58 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 48 deletions(-) create mode 100644 src/shared/load-opencode-plugins.ts diff --git a/src/shared/external-plugin-detector.ts b/src/shared/external-plugin-detector.ts index a73149d68..1ebc90967 100644 --- a/src/shared/external-plugin-detector.ts +++ b/src/shared/external-plugin-detector.ts @@ -3,15 +3,8 @@ * Used to prevent crashes from concurrent notification plugins. */ -import * as fs from "node:fs" -import * as path from "node:path" -import * as os from "node:os" +import { loadOpencodePlugins } from "./load-opencode-plugins" import { log } from "./logger" -import { parseJsoncSafe } from "./jsonc-parser" - -interface OpencodeConfig { - plugin?: string[] -} /** * Known notification plugins that conflict with oh-my-opencode's session-notification. @@ -34,46 +27,6 @@ const KNOWN_SKILL_PLUGINS = [ "@opencode/skills", ] -function getWindowsAppdataDir(): string | null { - return process.env.APPDATA || null -} - -function getConfigPaths(directory: string): string[] { - const crossPlatformDir = path.join(os.homedir(), ".config") - const paths = [ - path.join(directory, ".opencode", "opencode.json"), - path.join(directory, ".opencode", "opencode.jsonc"), - path.join(crossPlatformDir, "opencode", "opencode.json"), - path.join(crossPlatformDir, "opencode", "opencode.jsonc"), - ] - - if (process.platform === "win32") { - const appdataDir = getWindowsAppdataDir() - if (appdataDir) { - paths.push(path.join(appdataDir, "opencode", "opencode.json")) - paths.push(path.join(appdataDir, "opencode", "opencode.jsonc")) - } - } - - return paths -} - -function loadOpencodePlugins(directory: string): string[] { - for (const configPath of getConfigPaths(directory)) { - try { - if (!fs.existsSync(configPath)) continue - const content = fs.readFileSync(configPath, "utf-8") - const result = parseJsoncSafe(content) - if (result.data) { - return result.data.plugin ?? [] - } - } catch { - continue - } - } - return [] -} - /** * Check if a plugin entry matches a known notification plugin. * Handles various formats: "name", "name@version", "npm:name", "file://path/name" diff --git a/src/shared/load-opencode-plugins.ts b/src/shared/load-opencode-plugins.ts new file mode 100644 index 000000000..607333e07 --- /dev/null +++ b/src/shared/load-opencode-plugins.ts @@ -0,0 +1,58 @@ +import * as fs from "node:fs" +import * as os from "node:os" +import * as path from "node:path" + +import { parseJsoncSafe } from "./jsonc-parser" + +interface OpencodeConfig { + plugin?: string[] +} + +function getWindowsAppdataDir(): string | null { + return process.env.APPDATA || null +} + +function getConfigPaths(directory: string): string[] { + const crossPlatformDir = path.join(os.homedir(), ".config") + const paths = [ + path.join(directory, ".opencode", "opencode.json"), + path.join(directory, ".opencode", "opencode.jsonc"), + path.join(crossPlatformDir, "opencode", "opencode.json"), + path.join(crossPlatformDir, "opencode", "opencode.jsonc"), + ] + + if (process.platform === "win32") { + const appdataDir = getWindowsAppdataDir() + if (appdataDir) { + paths.push(path.join(appdataDir, "opencode", "opencode.json")) + paths.push(path.join(appdataDir, "opencode", "opencode.jsonc")) + } + } + + return paths +} + +export function loadOpencodePlugins(directory: string): string[] { + const pluginEntries: string[] = [] + const seenPluginEntries = new Set() + + for (const configPath of getConfigPaths(directory)) { + try { + if (!fs.existsSync(configPath)) continue + + const content = fs.readFileSync(configPath, "utf-8") + const result = parseJsoncSafe(content) + const plugins = result.data?.plugin ?? [] + + for (const plugin of plugins) { + if (seenPluginEntries.has(plugin)) continue + seenPluginEntries.add(plugin) + pluginEntries.push(plugin) + } + } catch { + continue + } + } + + return pluginEntries +} From f03c6700d44c54d33bcaadd94bdee3048bad6743 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 4 Apr 2026 01:32:59 +0900 Subject: [PATCH 4/4] fix(hooks): reuse shared legacy plugin migration helper Replace the legacy toast regex writer with the shared atomic helper and route legacy plugin call sites through small wrappers so existing mock.module tests stop leaking across the suite. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../auto-migrate-runner.ts | 2 ++ src/hooks/legacy-plugin-toast/auto-migrate.ts | 29 ++++++------------- src/hooks/legacy-plugin-toast/hook.test.ts | 4 +-- src/hooks/legacy-plugin-toast/hook.ts | 2 +- .../plugin-entry-migrator.ts | 1 + .../log-legacy-plugin-startup-warning.test.ts | 2 +- .../log-legacy-plugin-startup-warning.ts | 2 +- src/shared/plugin-entry-migrator.ts | 1 + 8 files changed, 18 insertions(+), 25 deletions(-) create mode 100644 src/hooks/legacy-plugin-toast/auto-migrate-runner.ts create mode 100644 src/hooks/legacy-plugin-toast/plugin-entry-migrator.ts create mode 100644 src/shared/plugin-entry-migrator.ts diff --git a/src/hooks/legacy-plugin-toast/auto-migrate-runner.ts b/src/hooks/legacy-plugin-toast/auto-migrate-runner.ts new file mode 100644 index 000000000..c77fea2b9 --- /dev/null +++ b/src/hooks/legacy-plugin-toast/auto-migrate-runner.ts @@ -0,0 +1,2 @@ +export { autoMigrateLegacyPluginEntry } from "./auto-migrate" +export type { MigrationResult } from "./auto-migrate" diff --git a/src/hooks/legacy-plugin-toast/auto-migrate.ts b/src/hooks/legacy-plugin-toast/auto-migrate.ts index 34bc4bbc0..a33cf8ac9 100644 --- a/src/hooks/legacy-plugin-toast/auto-migrate.ts +++ b/src/hooks/legacy-plugin-toast/auto-migrate.ts @@ -1,9 +1,10 @@ -import { existsSync, readFileSync, writeFileSync } from "node:fs" +import { existsSync, readFileSync } from "node:fs" import { join } from "node:path" import { parseJsoncSafe } from "../../shared/jsonc-parser" import { getOpenCodeConfigPaths } from "../../shared/opencode-config-dir" import { LEGACY_PLUGIN_NAME, PLUGIN_NAME } from "../../shared/plugin-identity" +import { migrateLegacyPluginEntry } from "./plugin-entry-migrator" export interface MigrationResult { migrated: boolean @@ -63,27 +64,15 @@ export function autoMigrateLegacyPluginEntry(overrideConfigDir?: string): Migrat const hasCanonical = plugins.some(isCanonicalEntry) const from = legacyEntries[0] const to = toLegacyCanonical(from) + const migrated = migrateLegacyPluginEntry(configPath) + if (!migrated) return { migrated: false, from: null, to: null, configPath } - const normalized = hasCanonical - ? plugins.filter((p) => !isLegacyEntry(p)) - : plugins.map((p) => (isLegacyEntry(p) ? toLegacyCanonical(p) : p)) - - const isJsonc = configPath.endsWith(".jsonc") - if (isJsonc) { - const pluginArrayRegex = /((?:"plugin"|plugin)\s*:\s*)\[([\s\S]*?)\]/ - const match = content.match(pluginArrayRegex) - if (match) { - const formattedPlugins = normalized.map((p) => `"${p}"`).join(",\n ") - const newContent = content.replace(pluginArrayRegex, `$1[\n ${formattedPlugins}\n ]`) - writeFileSync(configPath, newContent) - return { migrated: true, from, to, configPath } - } + return { + migrated: true, + from, + to: hasCanonical ? PLUGIN_NAME : to, + configPath, } - - const parsed = JSON.parse(content) as Record - parsed.plugin = normalized - writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n") - return { migrated: true, from, to, configPath } } catch { return { migrated: false, from: null, to: null, configPath } } diff --git a/src/hooks/legacy-plugin-toast/hook.test.ts b/src/hooks/legacy-plugin-toast/hook.test.ts index 490908429..c355587fc 100644 --- a/src/hooks/legacy-plugin-toast/hook.test.ts +++ b/src/hooks/legacy-plugin-toast/hook.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeEach, describe, expect, it, mock } from "bun:test" -import type { MigrationResult } from "./auto-migrate" +import type { MigrationResult } from "./auto-migrate-runner" const mockCheckForLegacyPluginEntry = mock(() => ({ hasLegacyEntry: false, @@ -26,7 +26,7 @@ mock.module("../../shared/logger", () => ({ log: mockLog, })) -mock.module("./auto-migrate", () => ({ +mock.module("./auto-migrate-runner", () => ({ autoMigrateLegacyPluginEntry: mockAutoMigrate, })) diff --git a/src/hooks/legacy-plugin-toast/hook.ts b/src/hooks/legacy-plugin-toast/hook.ts index 4d6f55918..69f971f25 100644 --- a/src/hooks/legacy-plugin-toast/hook.ts +++ b/src/hooks/legacy-plugin-toast/hook.ts @@ -3,7 +3,7 @@ import type { PluginInput } from "@opencode-ai/plugin" import { checkForLegacyPluginEntry } from "../../shared/legacy-plugin-warning" import { log } from "../../shared/logger" import { LEGACY_PLUGIN_NAME, PLUGIN_NAME } from "../../shared/plugin-identity" -import { autoMigrateLegacyPluginEntry } from "./auto-migrate" +import { autoMigrateLegacyPluginEntry } from "./auto-migrate-runner" export function createLegacyPluginToastHook(ctx: PluginInput) { let fired = false diff --git a/src/hooks/legacy-plugin-toast/plugin-entry-migrator.ts b/src/hooks/legacy-plugin-toast/plugin-entry-migrator.ts new file mode 100644 index 000000000..27aaaedc1 --- /dev/null +++ b/src/hooks/legacy-plugin-toast/plugin-entry-migrator.ts @@ -0,0 +1 @@ +export { migrateLegacyPluginEntry } from "../../shared/migrate-legacy-plugin-entry" diff --git a/src/shared/log-legacy-plugin-startup-warning.test.ts b/src/shared/log-legacy-plugin-startup-warning.test.ts index 4acd38385..0b288a461 100644 --- a/src/shared/log-legacy-plugin-startup-warning.test.ts +++ b/src/shared/log-legacy-plugin-startup-warning.test.ts @@ -26,7 +26,7 @@ mock.module("./logger", () => ({ log: mockLog, })) -mock.module("./migrate-legacy-plugin-entry", () => ({ +mock.module("./plugin-entry-migrator", () => ({ migrateLegacyPluginEntry: mockMigrateLegacyPluginEntry, })) diff --git a/src/shared/log-legacy-plugin-startup-warning.ts b/src/shared/log-legacy-plugin-startup-warning.ts index dc6505a5f..1ff3c7c19 100644 --- a/src/shared/log-legacy-plugin-startup-warning.ts +++ b/src/shared/log-legacy-plugin-startup-warning.ts @@ -1,6 +1,6 @@ import { checkForLegacyPluginEntry } from "./legacy-plugin-warning" import { log } from "./logger" -import { migrateLegacyPluginEntry } from "./migrate-legacy-plugin-entry" +import { migrateLegacyPluginEntry } from "./plugin-entry-migrator" import { LEGACY_PLUGIN_NAME, PLUGIN_NAME } from "./plugin-identity" function toCanonicalEntry(entry: string): string { diff --git a/src/shared/plugin-entry-migrator.ts b/src/shared/plugin-entry-migrator.ts new file mode 100644 index 000000000..50cc7c1a1 --- /dev/null +++ b/src/shared/plugin-entry-migrator.ts @@ -0,0 +1 @@ +export { migrateLegacyPluginEntry } from "./migrate-legacy-plugin-entry"