fix(plugin-loader): preserve scoped npm package names in plugin key parsing
Scoped packages like @scope/pkg were truncated to just 'pkg' because basename() strips the scope prefix. Fix: - Detect scoped packages (starting with @) and find version separator after the scope slash, not at the leading @ - Return full scoped name (@scope/pkg) instead of calling basename - Add regression test for scoped package name preservation
This commit is contained in:
@@ -32,6 +32,41 @@ describe("discoverInstalledPlugins", () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("preserves scoped package name from npm plugin keys", () => {
|
||||||
|
//#given
|
||||||
|
const pluginsHome = process.env.CLAUDE_PLUGINS_HOME as string
|
||||||
|
const installPath = join(createTemporaryDirectory("omo-plugin-install-"), "@myorg", "my-plugin")
|
||||||
|
mkdirSync(installPath, { recursive: true })
|
||||||
|
|
||||||
|
const databasePath = join(pluginsHome, "installed_plugins.json")
|
||||||
|
writeFileSync(
|
||||||
|
databasePath,
|
||||||
|
JSON.stringify({
|
||||||
|
version: 2,
|
||||||
|
plugins: {
|
||||||
|
"@myorg/my-plugin@1.0.0": [
|
||||||
|
{
|
||||||
|
scope: "user",
|
||||||
|
installPath,
|
||||||
|
version: "1.0.0",
|
||||||
|
installedAt: "2026-03-25T00:00:00Z",
|
||||||
|
lastUpdated: "2026-03-25T00:00:00Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
"utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const discovered = discoverInstalledPlugins()
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(discovered.errors).toHaveLength(0)
|
||||||
|
expect(discovered.plugins).toHaveLength(1)
|
||||||
|
expect(discovered.plugins[0]?.name).toBe("@myorg/my-plugin")
|
||||||
|
})
|
||||||
|
|
||||||
it("derives package name from file URL plugin keys", () => {
|
it("derives package name from file URL plugin keys", () => {
|
||||||
//#given
|
//#given
|
||||||
const pluginsHome = process.env.CLAUDE_PLUGINS_HOME as string
|
const pluginsHome = process.env.CLAUDE_PLUGINS_HOME as string
|
||||||
|
|||||||
@@ -81,7 +81,14 @@ function loadPluginManifest(installPath: string): PluginManifest | null {
|
|||||||
|
|
||||||
function derivePluginNameFromKey(pluginKey: string): string {
|
function derivePluginNameFromKey(pluginKey: string): string {
|
||||||
const keyWithoutSource = pluginKey.startsWith("npm:") ? pluginKey.slice(4) : pluginKey
|
const keyWithoutSource = pluginKey.startsWith("npm:") ? pluginKey.slice(4) : pluginKey
|
||||||
const versionSeparator = keyWithoutSource.lastIndexOf("@")
|
|
||||||
|
let versionSeparator: number
|
||||||
|
if (keyWithoutSource.startsWith("@")) {
|
||||||
|
const scopeEnd = keyWithoutSource.indexOf("/")
|
||||||
|
versionSeparator = scopeEnd > 0 ? keyWithoutSource.indexOf("@", scopeEnd) : -1
|
||||||
|
} else {
|
||||||
|
versionSeparator = keyWithoutSource.lastIndexOf("@")
|
||||||
|
}
|
||||||
const keyWithoutVersion = versionSeparator > 0 ? keyWithoutSource.slice(0, versionSeparator) : keyWithoutSource
|
const keyWithoutVersion = versionSeparator > 0 ? keyWithoutSource.slice(0, versionSeparator) : keyWithoutSource
|
||||||
|
|
||||||
if (keyWithoutVersion.startsWith("file://")) {
|
if (keyWithoutVersion.startsWith("file://")) {
|
||||||
@@ -92,6 +99,10 @@ function derivePluginNameFromKey(pluginKey: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keyWithoutVersion.startsWith("@") && keyWithoutVersion.includes("/")) {
|
||||||
|
return keyWithoutVersion
|
||||||
|
}
|
||||||
|
|
||||||
if (keyWithoutVersion.includes("/") || keyWithoutVersion.includes("\\")) {
|
if (keyWithoutVersion.includes("/") || keyWithoutVersion.includes("\\")) {
|
||||||
return basename(keyWithoutVersion)
|
return basename(keyWithoutVersion)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user