fix(cli): routed plugin install local paths through link instead of npm
`omp plugin install .` (and any cwd-relative, absolute, or tilde-prefixed spec) failed with `Invalid package name: .` because `classifyInstallTarget` only emitted `marketplace` / `npm`, so local paths fell through to `validatePackageName`, which rejects every non-npm name. The classifier now emits a third `local` arm for `.`, `..`, `./…`, `..\…`, `~`, `~/…`, `~\…`, `/…`, `C:\…`, `C:/…`, and `\\unc` specs. `handleInstall` dispatches that arm to `PluginManager.link()` — the same code path as `omp plugin link <path>` — so the two verbs are interchangeable for local plugin directories. `--dry-run` short-circuits before any filesystem work, `--scope`/`--force` surface a warning since they are no-ops here (link is idempotent, and scope only governs marketplace installs). Coverage: thirteen-case classifier matrix in `marketplace/cli.test.ts` plus a new `plugin-install-local.test.ts` with spy-based routing checks for `./`, `../`, `/`, `~/`, plus a real-filesystem test that stages a plugin folder, invokes `runPluginCommand`, and verifies the resulting symlink + lockfile entry. The new test calls `mock.restore()` in `afterEach` so `piUtils` / `MarketplaceManager.prototype` spies do not leak into sibling suites. Fixes #1945
This commit is contained in:
@@ -354,6 +354,7 @@ async function handleInstall(
|
||||
console.error(chalk.dim(` ${APP_NAME} plugin install name@marketplace`));
|
||||
console.error(chalk.dim(` ${APP_NAME} plugin install github:user/repo`));
|
||||
console.error(chalk.dim(` ${APP_NAME} plugin install https://github.com/user/repo#v1.0`));
|
||||
console.error(chalk.dim(` ${APP_NAME} plugin install ./path/to/local/plugin`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -382,6 +383,49 @@ async function handleInstall(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (target.type === "local") {
|
||||
// Local paths route to link(): symlink the directory into the plugins
|
||||
// node_modules tree so source edits show up without a reinstall. Matches
|
||||
// `omp plugin link <path>` so users can use either verb interchangeably.
|
||||
if (flags.scope) {
|
||||
console.error(
|
||||
chalk.yellow(
|
||||
`Warning: --scope is only supported for marketplace installs (name@marketplace). Ignoring for ${spec}.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (flags.force) {
|
||||
console.error(
|
||||
chalk.yellow(
|
||||
`Warning: --force has no effect for local path installs (link is already idempotent). Ignoring for ${spec}.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (flags.dryRun) {
|
||||
if (flags.json) {
|
||||
console.log(JSON.stringify({ dryRun: true, action: "link", path: target.path }, null, 2));
|
||||
} else {
|
||||
console.log(chalk.dim(`[dry-run] Would link ${spec}`));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const result = await manager.link(target.path);
|
||||
if (flags.json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log(chalk.green(`${theme.status.success} Linked ${result.name} from ${spec}`));
|
||||
if (result.manifest.description) {
|
||||
console.log(chalk.dim(` ${result.manifest.description}`));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(chalk.red(`${theme.status.error} Failed to install ${spec}: ${err}`));
|
||||
process.exit(1);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// --scope only applies to marketplace installs; warn when it would be silently no-op'd for npm.
|
||||
if (flags.scope) {
|
||||
console.error(
|
||||
@@ -923,6 +967,7 @@ ${chalk.bold("Sources:")}
|
||||
github:user/repo[#ref] GitHub shorthand (also gitlab:, bitbucket:, codeberg:, sourcehut:)
|
||||
https://github.com/user/repo Full git URL (https, ssh, or git protocol)
|
||||
name@marketplace Marketplace plugin (see marketplace command)
|
||||
./path, ../path, /abs, ~/path Local plugin directory (symlinked, same as plugin link)
|
||||
|
||||
${chalk.bold("Config Subcommands:")}
|
||||
config list <pkg> List all settings
|
||||
|
||||
Reference in New Issue
Block a user