From 78ae240206c989c14e80e7a58c84973c6a74f24e Mon Sep 17 00:00:00 2001 From: PeterPonyu Date: Fri, 15 May 2026 07:45:02 -0400 Subject: [PATCH] fix(doctor): detect plugin version via require.resolve fallback The doctor command reported "unknown" for the plugin version whenever the package was installed somewhere outside the config dir or XDG cache dir (for example, when running via `bunx oh-my-openagent`, which keeps the plugin in its own ephemeral cache). The existing scan only checked `node_modules//package.json` under those two well-known directories, so it silently returned a null `loadedVersion`. Add a `require.resolve("/package.json")` fallback that uses Node's own module resolution to locate whichever copy of the plugin is actually loaded. The fallback only runs when neither candidate path exists, so existing behavior is preserved for the common case. Closes #3822 --- .../checks/system-loaded-version.test.ts | 40 +++++++++++++++++++ .../doctor/checks/system-loaded-version.ts | 29 +++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/cli/doctor/checks/system-loaded-version.test.ts b/src/cli/doctor/checks/system-loaded-version.test.ts index f20b4a797..aeefa3b2f 100644 --- a/src/cli/doctor/checks/system-loaded-version.test.ts +++ b/src/cli/doctor/checks/system-loaded-version.test.ts @@ -129,6 +129,46 @@ describe("system loaded version", () => { expect(loadedVersion.loadedVersion).toBe("5.6.7") }) + it("falls back to require.resolve when neither config nor cache directory has an install", () => { + //#given + const configDir = createTemporaryDirectory("omo-config-") + const cacheHome = createTemporaryDirectory("omo-cache-") + + process.env.OPENCODE_CONFIG_DIR = configDir + process.env.XDG_CACHE_HOME = cacheHome + + //#when + const loadedVersion = getLoadedPluginVersion() + + //#then + expect(loadedVersion.loadedVersion).not.toBeNull() + expect(loadedVersion.loadedVersion).toMatch(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/) + expect(loadedVersion.installedPackagePath).toContain("package.json") + }) + + it("prefers candidate install path over require.resolve fallback when candidate exists", () => { + //#given + const configDir = createTemporaryDirectory("omo-config-") + const cacheHome = createTemporaryDirectory("omo-cache-") + + process.env.OPENCODE_CONFIG_DIR = configDir + process.env.XDG_CACHE_HOME = cacheHome + + writeJson(join(configDir, "package.json"), { + dependencies: { [PACKAGE_NAME]: "7.7.7" }, + }) + writeJson(join(configDir, "node_modules", PACKAGE_NAME, "package.json"), { + version: "7.7.7", + }) + + //#when + const loadedVersion = getLoadedPluginVersion() + + //#then + expect(loadedVersion.installedPackagePath).toBe(join(configDir, "node_modules", PACKAGE_NAME, "package.json")) + expect(loadedVersion.loadedVersion).toBe("7.7.7") + }) + it("resolves symlinked config directories before selecting install path", () => { //#given const realConfigDir = createTemporaryDirectory("omo-real-config-") diff --git a/src/cli/doctor/checks/system-loaded-version.ts b/src/cli/doctor/checks/system-loaded-version.ts index 25d7baccf..80f38982f 100644 --- a/src/cli/doctor/checks/system-loaded-version.ts +++ b/src/cli/doctor/checks/system-loaded-version.ts @@ -1,4 +1,5 @@ import { existsSync, readFileSync } from "node:fs" +import { createRequire } from "node:module" import { homedir } from "node:os" import { join } from "node:path" import { resolveSymlink } from "../../../shared/file-utils" @@ -86,6 +87,25 @@ function getExpectedVersion(cachePackage: PackageJsonShape | null, packageName: ?? normalizeVersion(cachePackage?.dependencies?.[PACKAGE_NAME]) } +function resolveInstalledPackageJsonPath(): { packageName: string; packageJsonPath: string } | null { + try { + const require = createRequire(import.meta.url) + for (const packageName of ACCEPTED_PACKAGE_NAMES) { + try { + const packageJsonPath = require.resolve(`${packageName}/package.json`) + if (existsSync(packageJsonPath)) { + return { packageName, packageJsonPath } + } + } catch { + continue + } + } + } catch { + return null + } + return null +} + export function getLoadedPluginVersion(): LoadedVersionInfo { const configPaths = getOpenCodeConfigPaths({ binary: "opencode" }) const configDir = resolveExistingDir(configPaths.configDir) @@ -108,12 +128,17 @@ export function getLoadedPluginVersion(): LoadedVersionInfo { const { cacheDir: selectedDir, cachePackagePath } = selectedCandidate const selectedPackage = selectInstalledPackage(selectedCandidate) - const installedPackagePath = selectedPackage.installedPackagePath + const candidateInstalledPath = selectedPackage.installedPackagePath + const candidateExists = existsSync(candidateInstalledPath) + + const resolvedFallback = candidateExists ? null : resolveInstalledPackageJsonPath() + const installedPackagePath = resolvedFallback?.packageJsonPath ?? candidateInstalledPath + const resolvedPackageName = resolvedFallback?.packageName ?? selectedPackage.packageName const cachePackage = readPackageJson(cachePackagePath) const installedPackage = readPackageJson(installedPackagePath) - const expectedVersion = getExpectedVersion(cachePackage, selectedPackage.packageName) + const expectedVersion = getExpectedVersion(cachePackage, resolvedPackageName) const loadedVersion = normalizeVersion(installedPackage?.version) return {