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>/package.json` under those two well-known
directories, so it silently returned a null `loadedVersion`.

Add a `require.resolve("<pkg>/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
This commit is contained in:
PeterPonyu
2026-05-15 07:45:02 -04:00
parent bda0452b2a
commit 78ae240206
2 changed files with 67 additions and 2 deletions
@@ -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-")
+27 -2
View File
@@ -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 {