From 428bae632c9220b097dbc7ebb1cf4fb1de4238e9 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 18 Apr 2026 14:09:46 +0900 Subject: [PATCH 1/2] test(shared): cover loadOpencodePlugins memoization Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/shared/load-opencode-plugins.test.ts | 89 ++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/shared/load-opencode-plugins.test.ts diff --git a/src/shared/load-opencode-plugins.test.ts b/src/shared/load-opencode-plugins.test.ts new file mode 100644 index 000000000..9723c1cd3 --- /dev/null +++ b/src/shared/load-opencode-plugins.test.ts @@ -0,0 +1,89 @@ +/// + +import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test" +import * as fs from "node:fs" + +type LoadOpencodePluginsModule = { + loadOpencodePlugins: (directory: string) => string[] + clearOpencodePluginsCache?: () => void +} + +const existsSyncMock = mock((_path: string) => true) +const readFileSyncMock = mock((_path: string, _encoding?: string) => `{ + "plugin": ["plugin-a", "plugin-b"] +}`) + +async function importFreshLoadOpencodePluginsModule(): Promise { + const modulePath = `${new URL("./load-opencode-plugins.ts", import.meta.url).pathname}?test=${Date.now()}-${Math.random()}` + return import(modulePath) +} + +describe("loadOpencodePlugins", () => { + beforeEach(() => { + existsSyncMock.mockReset() + existsSyncMock.mockImplementation((_path: string) => true) + readFileSyncMock.mockReset() + readFileSyncMock.mockImplementation((_path: string, _encoding?: string) => `{ + "plugin": ["plugin-a", "plugin-b"] +}`) + + mock.module("node:fs", () => ({ + ...fs, + existsSync: existsSyncMock, + readFileSync: readFileSyncMock, + })) + }) + + afterEach(() => { + mock.restore() + }) + + describe("#given the same directory is loaded twice", () => { + describe("#when loading plugins repeatedly", () => { + it("#then does not call readFileSync on the second load", async () => { + // given + const { loadOpencodePlugins } = await importFreshLoadOpencodePluginsModule() + + // when + const firstResult = loadOpencodePlugins("/some/fake/dir") + const readCountAfterFirstLoad = readFileSyncMock.mock.calls.length + const secondResult = loadOpencodePlugins("/some/fake/dir") + const readCountAfterSecondLoad = readFileSyncMock.mock.calls.length + + // then + expect(firstResult).toEqual(["plugin-a", "plugin-b"]) + expect(secondResult).toEqual(["plugin-a", "plugin-b"]) + expect(readCountAfterFirstLoad).toBeGreaterThan(0) + expect(readCountAfterSecondLoad - readCountAfterFirstLoad).toBe(0) + }) + }) + }) + + describe("#given the plugin cache was cleared", () => { + describe("#when loading the same directory again", () => { + it("#then re-reads plugin config files from disk", async () => { + // given + const { loadOpencodePlugins, clearOpencodePluginsCache } = await importFreshLoadOpencodePluginsModule() + + if (typeof clearOpencodePluginsCache !== "function") { + throw new Error("clearOpencodePluginsCache export is missing") + } + + // when + const firstResult = loadOpencodePlugins("/some/fake/dir") + const readCountAfterFirstLoad = readFileSyncMock.mock.calls.length + loadOpencodePlugins("/some/fake/dir") + const readCountAfterSecondLoad = readFileSyncMock.mock.calls.length + clearOpencodePluginsCache() + const thirdResult = loadOpencodePlugins("/some/fake/dir") + const readCountAfterThirdLoad = readFileSyncMock.mock.calls.length + + // then + expect(firstResult).toEqual(["plugin-a", "plugin-b"]) + expect(thirdResult).toEqual(["plugin-a", "plugin-b"]) + expect(readCountAfterSecondLoad - readCountAfterFirstLoad).toBe(0) + expect(readCountAfterThirdLoad - readCountAfterSecondLoad).toBeGreaterThan(0) + }) + }) + }) +}) From ac2686ffdea52cb29bbf831976888d8df5cafe6b Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 18 Apr 2026 14:10:45 +0900 Subject: [PATCH 2/2] fix(shared): memoize loadOpencodePlugins by directory Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/shared/load-opencode-plugins.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/shared/load-opencode-plugins.ts b/src/shared/load-opencode-plugins.ts index 5517c74b1..a6beffdcf 100644 --- a/src/shared/load-opencode-plugins.ts +++ b/src/shared/load-opencode-plugins.ts @@ -8,6 +8,8 @@ interface OpencodeConfig { plugin?: (string | [string, ...unknown[]])[] } +const opencodePluginsCache = new Map() + function getWindowsAppdataDir(): string | null { return process.env.APPDATA || null } @@ -33,6 +35,11 @@ function getConfigPaths(directory: string): string[] { } export function loadOpencodePlugins(directory: string): string[] { + const cachedPluginEntries = opencodePluginsCache.get(directory) + if (cachedPluginEntries) { + return cachedPluginEntries + } + const pluginEntries: string[] = [] const seenPluginEntries = new Set() @@ -56,5 +63,10 @@ export function loadOpencodePlugins(directory: string): string[] { } } + opencodePluginsCache.set(directory, pluginEntries) return pluginEntries } + +export function clearOpencodePluginsCache(): void { + opencodePluginsCache.clear() +}