Reproduction: - Enable `frontend-design@claude-code-plugins` - Enable `frontend-design@claude-plugins-official` - Start OpenClaude with both marketplace plugins active - Both plugins load, but downstream command and skill scopes key off the short plugin name, so both collapse to `frontend-design` and can interfere with interactive startup Fix: - Collapse duplicate marketplace plugins by short name during merge - Keep the enabled copy when enabled state differs; otherwise keep the later config entry - Add regression coverage for both cases
72 lines
1.7 KiB
TypeScript
72 lines
1.7 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
|
|
import type { LoadedPlugin } from '../../types/plugin.js'
|
|
import { mergePluginSources } from './pluginLoader.js'
|
|
|
|
function marketplacePlugin(
|
|
name: string,
|
|
marketplace: string,
|
|
enabled: boolean,
|
|
): LoadedPlugin {
|
|
const pluginId = `${name}@${marketplace}`
|
|
return {
|
|
name,
|
|
manifest: { name } as LoadedPlugin['manifest'],
|
|
path: `/tmp/${pluginId}`,
|
|
source: pluginId,
|
|
repository: pluginId,
|
|
enabled,
|
|
}
|
|
}
|
|
|
|
describe('mergePluginSources', () => {
|
|
test('keeps the enabled copy when duplicate marketplace plugins disagree on enabled state', () => {
|
|
const enabledOfficial = marketplacePlugin(
|
|
'frontend-design',
|
|
'claude-plugins-official',
|
|
true,
|
|
)
|
|
const disabledLegacy = marketplacePlugin(
|
|
'frontend-design',
|
|
'claude-code-plugins',
|
|
false,
|
|
)
|
|
|
|
const result = mergePluginSources({
|
|
session: [],
|
|
marketplace: [disabledLegacy, enabledOfficial],
|
|
builtin: [],
|
|
})
|
|
|
|
expect(result.plugins).toEqual([enabledOfficial])
|
|
expect(result.errors).toEqual([])
|
|
})
|
|
|
|
test('keeps the later copy when duplicate marketplace plugins are both enabled', () => {
|
|
const legacy = marketplacePlugin(
|
|
'frontend-design',
|
|
'claude-code-plugins',
|
|
true,
|
|
)
|
|
const official = marketplacePlugin(
|
|
'frontend-design',
|
|
'claude-plugins-official',
|
|
true,
|
|
)
|
|
|
|
const result = mergePluginSources({
|
|
session: [],
|
|
marketplace: [legacy, official],
|
|
builtin: [],
|
|
})
|
|
|
|
expect(result.plugins).toEqual([official])
|
|
expect(result.errors).toHaveLength(1)
|
|
expect(result.errors[0]).toMatchObject({
|
|
type: 'generic-error',
|
|
source: legacy.source,
|
|
plugin: legacy.name,
|
|
})
|
|
})
|
|
})
|