fix(cli): accepted npm protocol plugin installs
Allowed npm:<package> install specs to validate against the resolved package name while still forwarding the original spec to Bun. Added regression coverage for installing npm:pi-figma-remote-auth through PluginManager. Fixes #4310
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `omp install npm:<package>` rejecting Pi package specs before Bun could resolve them. ([#4310](https://github.com/can1357/oh-my-pi/issues/4310))
|
||||
|
||||
## [16.3.1] - 2026-07-02
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -86,20 +86,22 @@ export function formatPluginSpec(spec: ParsedPluginSpec): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the base package name without version specifier.
|
||||
* Extract the dependency key from an npm package specifier.
|
||||
* Used for path lookups after npm install.
|
||||
*
|
||||
* @example
|
||||
* extractPackageName("lodash@4.17.21") // "lodash"
|
||||
* extractPackageName("@scope/pkg@1.0.0") // "@scope/pkg"
|
||||
* extractPackageName("@scope/pkg") // "@scope/pkg"
|
||||
* extractPackageName("npm:lodash") // "lodash"
|
||||
*/
|
||||
export function extractPackageName(specifier: string): string {
|
||||
const npmSpecifier = specifier.replace(/^npm:/i, "");
|
||||
// Handle scoped packages: @scope/name@version -> @scope/name
|
||||
if (specifier.startsWith("@")) {
|
||||
const match = specifier.match(/^(@[^/]+\/[^@]+)/);
|
||||
return match ? match[1] : specifier;
|
||||
if (npmSpecifier.startsWith("@")) {
|
||||
const match = npmSpecifier.match(/^(@[^/]+\/[^@]+)/);
|
||||
return match ? match[1] : npmSpecifier;
|
||||
}
|
||||
// Unscoped: name@version -> name
|
||||
return specifier.replace(/@[^@]+$/, "");
|
||||
return npmSpecifier.replace(/@[^@]+$/, "");
|
||||
}
|
||||
|
||||
@@ -68,6 +68,45 @@ describe("PluginManager.install load validation", () => {
|
||||
await removeWithRetries(tmpRoot);
|
||||
});
|
||||
|
||||
test("installs npm protocol specs with the resolved package name", async () => {
|
||||
vi.spyOn(Bun, "spawn").mockImplementation(((cmd: string[]) => {
|
||||
expect(cmd).toEqual(["bun", "install", "npm:pi-figma-remote-auth"]);
|
||||
|
||||
const prepare = (async () => {
|
||||
await Bun.write(
|
||||
pluginsPkgJson,
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "omp-plugins",
|
||||
private: true,
|
||||
dependencies: { "pi-figma-remote-auth": "npm:pi-figma-remote-auth" },
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
await writePluginPackage(pluginsNodeModules, "pi-figma-remote-auth", {
|
||||
version: "1.2.3",
|
||||
source:
|
||||
'export default function(pi) { pi.registerCommand("figma-auth", { handler: async () => {} }); }\n',
|
||||
});
|
||||
})();
|
||||
|
||||
return {
|
||||
pid: 1,
|
||||
stdout: emptyStream(),
|
||||
stderr: emptyStream(),
|
||||
exited: prepare.then(() => 0),
|
||||
} as Subprocess;
|
||||
}) as typeof Bun.spawn);
|
||||
|
||||
const result = await new PluginManager(tmpRoot).install("npm:pi-figma-remote-auth");
|
||||
|
||||
expect(result.name).toBe("pi-figma-remote-auth");
|
||||
expect(result.version).toBe("1.2.3");
|
||||
expect(result.path).toBe(path.join(pluginsNodeModules, "pi-figma-remote-auth"));
|
||||
});
|
||||
|
||||
test("rejects an install whose extension entry cannot resolve its dependencies", async () => {
|
||||
vi.spyOn(Bun, "spawn").mockImplementation(((cmd: string[]) => {
|
||||
expect(cmd).toEqual(["bun", "install", "broken-plugin"]);
|
||||
|
||||
Reference in New Issue
Block a user