From 981d5721328ef731ad76d6805640ab0d75751fb9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 15 Feb 2026 02:52:32 +0000 Subject: [PATCH] fix: support file: npm specs in plugin install --- src/cli/plugins-cli.ts | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/cli/plugins-cli.ts b/src/cli/plugins-cli.ts index 09ce204354d..eba4020e1ae 100644 --- a/src/cli/plugins-cli.ts +++ b/src/cli/plugins-cli.ts @@ -43,6 +43,34 @@ export type PluginUninstallOptions = { dryRun?: boolean; }; +function resolveFileNpmSpecToLocalPath( + raw: string, +): { ok: true; path: string } | { ok: false; error: string } | null { + const trimmed = raw.trim(); + if (!trimmed.toLowerCase().startsWith("file:")) { + return null; + } + const rest = trimmed.slice("file:".length); + if (!rest) { + return { ok: false, error: "unsupported file: spec: missing path" }; + } + if (rest.startsWith("///")) { + // file:///abs/path -> /abs/path + return { ok: true, path: rest.slice(2) }; + } + if (rest.startsWith("//localhost/")) { + // file://localhost/abs/path -> /abs/path + return { ok: true, path: rest.slice("//localhost".length) }; + } + if (rest.startsWith("//")) { + return { + ok: false, + error: 'unsupported file: URL host (expected "file:" or "file:///abs/path")', + }; + } + return { ok: true, path: rest }; +} + function formatPluginLine(plugin: PluginRecord, verbose = false): string { const status = plugin.status === "loaded" @@ -484,7 +512,13 @@ export function registerPluginsCli(program: Command) { .argument("", "Path (.ts/.js/.zip/.tgz/.tar.gz) or an npm package spec") .option("-l, --link", "Link a local path instead of copying", false) .action(async (raw: string, opts: { link?: boolean }) => { - const resolved = resolveUserPath(raw); + const fileSpec = resolveFileNpmSpecToLocalPath(raw); + if (fileSpec && !fileSpec.ok) { + defaultRuntime.error(fileSpec.error); + process.exit(1); + } + const normalized = fileSpec && fileSpec.ok ? fileSpec.path : raw; + const resolved = resolveUserPath(normalized); const cfg = loadConfig(); if (fs.existsSync(resolved)) {