fix(auto-update): match both canonical and legacy plugin names in entry finder

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-04-08 17:17:41 +09:00
parent 359f74132a
commit 15e3b14cca
2 changed files with 70 additions and 7 deletions
@@ -4,6 +4,7 @@ import * as fs from "node:fs"
import * as os from "node:os"
import * as path from "node:path"
import { PACKAGE_NAME } from "../constants"
import { LEGACY_PLUGIN_NAME, PLUGIN_NAME } from "../../../shared/plugin-identity"
type PluginEntryResult = {
entry: string
@@ -120,6 +121,64 @@ describe("findPluginEntry", () => {
expect(pluginInfo?.pinnedVersion).toBe("3.5.2")
})
test("finds preferred plugin entry", async () => {
// #given preferred plugin entry is configured
fs.writeFileSync(configPath, JSON.stringify({ plugin: [PLUGIN_NAME] }))
// #when plugin entry is detected
const execution = runFindPluginEntry(temporaryDirectory)
// #then preferred entry is returned
expect(execution.status).toBe(0)
const pluginInfo = JSON.parse(execution.stdout.trim()) as PluginEntryResult
expect(pluginInfo?.entry).toBe(PLUGIN_NAME)
expect(pluginInfo?.isPinned).toBe(false)
expect(pluginInfo?.pinnedVersion).toBeNull()
})
test("finds legacy plugin entry", async () => {
// #given legacy plugin entry is configured
fs.writeFileSync(configPath, JSON.stringify({ plugin: [LEGACY_PLUGIN_NAME] }))
// #when plugin entry is detected
const execution = runFindPluginEntry(temporaryDirectory)
// #then legacy entry is returned
expect(execution.status).toBe(0)
const pluginInfo = JSON.parse(execution.stdout.trim()) as PluginEntryResult
expect(pluginInfo?.entry).toBe(LEGACY_PLUGIN_NAME)
expect(pluginInfo?.isPinned).toBe(false)
expect(pluginInfo?.pinnedVersion).toBeNull()
})
test("finds preferred plugin entry with pinned version", async () => {
// #given preferred plugin entry includes semver version
fs.writeFileSync(configPath, JSON.stringify({ plugin: [`${PLUGIN_NAME}@3.15.0`] }))
// #when plugin entry is detected
const execution = runFindPluginEntry(temporaryDirectory)
// #then preferred versioned entry is returned
expect(execution.status).toBe(0)
const pluginInfo = JSON.parse(execution.stdout.trim()) as PluginEntryResult
expect(pluginInfo?.entry).toBe(`${PLUGIN_NAME}@3.15.0`)
expect(pluginInfo?.isPinned).toBe(true)
expect(pluginInfo?.pinnedVersion).toBe("3.15.0")
})
test("returns null for unrelated plugin entry", async () => {
// #given unrelated plugin entry is configured
fs.writeFileSync(configPath, JSON.stringify({ plugin: ["some-other-plugin"] }))
// #when plugin entry is detected
const execution = runFindPluginEntry(temporaryDirectory)
// #then no matching entry is returned
expect(execution.status).toBe(0)
const pluginInfo = JSON.parse(execution.stdout.trim()) as PluginEntryResult
expect(pluginInfo).toBeNull()
})
test("reads user config from profile dir even when OPENCODE_CONFIG_DIR changes after import", async () => {
// #given profile-specific user config after module import
const profileConfigDir = path.join(temporaryDirectory, "profiles", "today")
@@ -3,6 +3,7 @@ import type { OpencodeConfig } from "../types"
import { PACKAGE_NAME } from "../constants"
import { getConfigPaths } from "./config-paths"
import { stripJsonComments } from "./jsonc-strip"
import { LEGACY_PLUGIN_NAME, PLUGIN_NAME } from "../../../shared/plugin-identity"
export interface PluginEntryInfo {
entry: string
@@ -12,6 +13,7 @@ export interface PluginEntryInfo {
}
const EXACT_SEMVER_REGEX = /^\d+\.\d+\.\d+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/
const MATCH_PLUGIN_NAMES = [PACKAGE_NAME, PLUGIN_NAME, LEGACY_PLUGIN_NAME]
export function findPluginEntry(directory: string): PluginEntryInfo | null {
for (const configPath of getConfigPaths(directory)) {
@@ -22,13 +24,15 @@ export function findPluginEntry(directory: string): PluginEntryInfo | null {
const plugins = config.plugin ?? []
for (const entry of plugins) {
if (entry === PACKAGE_NAME) {
return { entry, isPinned: false, pinnedVersion: null, configPath }
}
if (entry.startsWith(`${PACKAGE_NAME}@`)) {
const pinnedVersion = entry.slice(PACKAGE_NAME.length + 1)
const isPinned = EXACT_SEMVER_REGEX.test(pinnedVersion.trim())
return { entry, isPinned, pinnedVersion, configPath }
for (const pluginName of MATCH_PLUGIN_NAMES) {
if (entry === pluginName) {
return { entry, isPinned: false, pinnedVersion: null, configPath }
}
if (entry.startsWith(`${pluginName}@`)) {
const pinnedVersion = entry.slice(pluginName.length + 1)
const isPinned = EXACT_SEMVER_REGEX.test(pinnedVersion.trim())
return { entry, isPinned, pinnedVersion, configPath }
}
}
}
} catch {