fix(plugin-loader): support installed_plugins.json v1 format for backward compatibility (#288)

The installed_plugins.json file has two versions:
- v1: plugins stored as direct objects
- v2: plugins stored as arrays

Use discriminated union types (InstalledPluginsDatabaseV1/V2) for proper
type narrowing based on version field.

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2025-12-28 02:33:14 +09:00
parent 262f0c3f1f
commit 1c12925c9e
2 changed files with 31 additions and 7 deletions

View File

@@ -14,6 +14,7 @@ import type { AgentFrontmatter } from "../claude-code-agent-loader/types"
import type { ClaudeCodeMcpConfig, McpServerConfig } from "../claude-code-mcp-loader/types" import type { ClaudeCodeMcpConfig, McpServerConfig } from "../claude-code-mcp-loader/types"
import type { import type {
InstalledPluginsDatabase, InstalledPluginsDatabase,
PluginInstallation,
PluginManifest, PluginManifest,
LoadedPlugin, LoadedPlugin,
PluginLoadResult, PluginLoadResult,
@@ -134,6 +135,15 @@ function isPluginEnabled(
return true return true
} }
function extractPluginEntries(
db: InstalledPluginsDatabase
): Array<[string, PluginInstallation | undefined]> {
if (db.version === 1) {
return Object.entries(db.plugins).map(([key, installation]) => [key, installation])
}
return Object.entries(db.plugins).map(([key, installations]) => [key, installations[0]])
}
export function discoverInstalledPlugins(options?: PluginLoaderOptions): PluginLoadResult { export function discoverInstalledPlugins(options?: PluginLoaderOptions): PluginLoadResult {
const db = loadInstalledPlugins() const db = loadInstalledPlugins()
const settings = loadClaudeSettings() const settings = loadClaudeSettings()
@@ -147,15 +157,14 @@ export function discoverInstalledPlugins(options?: PluginLoaderOptions): PluginL
const settingsEnabledPlugins = settings?.enabledPlugins const settingsEnabledPlugins = settings?.enabledPlugins
const overrideEnabledPlugins = options?.enabledPluginsOverride const overrideEnabledPlugins = options?.enabledPluginsOverride
for (const [pluginKey, installations] of Object.entries(db.plugins)) { for (const [pluginKey, installation] of extractPluginEntries(db)) {
if (!installations || installations.length === 0) continue if (!installation) continue
if (!isPluginEnabled(pluginKey, settingsEnabledPlugins, overrideEnabledPlugins)) { if (!isPluginEnabled(pluginKey, settingsEnabledPlugins, overrideEnabledPlugins)) {
log(`Plugin disabled: ${pluginKey}`) log(`Plugin disabled: ${pluginKey}`)
continue continue
} }
const installation = installations[0]
const { installPath, scope, version } = installation const { installPath, scope, version } = installation
if (!existsSync(installPath)) { if (!existsSync(installPath)) {

View File

@@ -20,14 +20,29 @@ export interface PluginInstallation {
isLocal?: boolean isLocal?: boolean
} }
/**
* Installed plugins database v1 (legacy)
* plugins stored as direct objects
*/
export interface InstalledPluginsDatabaseV1 {
version: 1
plugins: Record<string, PluginInstallation>
}
/**
* Installed plugins database v2 (current)
* plugins stored as arrays
*/
export interface InstalledPluginsDatabaseV2 {
version: 2
plugins: Record<string, PluginInstallation[]>
}
/** /**
* Installed plugins database structure * Installed plugins database structure
* Located at ~/.claude/plugins/installed_plugins.json * Located at ~/.claude/plugins/installed_plugins.json
*/ */
export interface InstalledPluginsDatabase { export type InstalledPluginsDatabase = InstalledPluginsDatabaseV1 | InstalledPluginsDatabaseV2
version: number
plugins: Record<string, PluginInstallation[]>
}
/** /**
* Plugin author information * Plugin author information