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", () => {
|
||||
//#given
|
||||
const pluginsHome = process.env.CLAUDE_PLUGINS_HOME as string
|
||||
|
||||
@@ -81,7 +81,14 @@ function loadPluginManifest(installPath: string): PluginManifest | null {
|
||||
|
||||
function derivePluginNameFromKey(pluginKey: string): string {
|
||||
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
|
||||
|
||||
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("\\")) {
|
||||
return basename(keyWithoutVersion)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user