Files
oh-my-pi/packages/coding-agent/test/marketplace/cli.test.ts
T
Miroslav Drbal 4e199f93f7 feat: add marketplace plugin system
Add Claude Code-compatible marketplace plugin infrastructure:

- Registry: types, ID helpers, atomic read/write for marketplaces.json
  and installed_plugins.json (Claude Code format with version: 2)
- Fetcher: classifySource (6 ordered rules), parseMarketplaceCatalog,
  fetchMarketplace (local sources; git/http stubs for Phase 2)
- Resolver + Cache: resolvePluginSource with pathIsWithin containment,
  cachePlugin, removeCachedPlugin, cleanOrphanedCache
- MarketplaceManager: orchestrates add/remove/update marketplaces,
  install/uninstall/enable plugins, clearPluginRootsCache on mutation
- CLI: omp plugin marketplace add|remove|update|list,
  omp plugin discover, classifyInstallTarget for name@marketplace
- Discovery: listClaudePluginRoots reads OMP registry alongside
  Claude's, OMP authoritative for duplicate plugin IDs
- Args: --plugin-dir repeatable flag (parsing only, runtime wiring TBD)
- Slash command: /reload-plugins clears fs + roots cache
- Test fixtures and 130 tests across 8 test files
2026-03-30 13:44:53 +02:00

53 lines
2.3 KiB
TypeScript

import { describe, expect, it } from "bun:test";
// Import from the zero-dep classify module — plugin-cli.ts transitively loads native addons.
import { classifyInstallTarget } from "../../src/cli/classify-install-target";
const KNOWN = new Set(["my-marketplace"]);
describe("classifyInstallTarget", () => {
it("classifies plugin@marketplace as marketplace when marketplace is registered", () => {
const result = classifyInstallTarget("hello@my-marketplace", KNOWN);
expect(result).toEqual({ type: "marketplace", name: "hello", marketplace: "my-marketplace" });
});
it("classifies scoped @scope/pkg as npm (rule 1: starts with @)", () => {
const result = classifyInstallTarget("@scope/pkg", KNOWN);
expect(result).toEqual({ type: "npm", spec: "@scope/pkg" });
});
it("classifies @scope/pkg@1.0.0 as npm (starts with @, rule 1 wins)", () => {
const result = classifyInstallTarget("@scope/pkg@1.0.0", KNOWN);
expect(result).toEqual({ type: "npm", spec: "@scope/pkg@1.0.0" });
});
it("classifies bare name with no @ as npm", () => {
const result = classifyInstallTarget("bare-name", KNOWN);
expect(result).toEqual({ type: "npm", spec: "bare-name" });
});
it("classifies pkg@version as npm when version is not a known marketplace", () => {
const result = classifyInstallTarget("pkg@1.2.3", KNOWN);
expect(result).toEqual({ type: "npm", spec: "pkg@1.2.3" });
});
it("classifies pkg@marketplace as npm when marketplace is not registered", () => {
const result = classifyInstallTarget("hello@my-marketplace", new Set());
expect(result).toEqual({ type: "npm", spec: "hello@my-marketplace" });
});
it("scoped @scope/pkg@marketplace is still npm — rule 1 wins", () => {
// Even though this starts with @, the rule only triggers when spec.startsWith("@")
// but @scope/pkg@my-marketplace DOES start with @ so rule 1 applies -> npm.
// This confirms rule 1 is absolute for scoped packages.
const result = classifyInstallTarget("@scope/pkg@my-marketplace", KNOWN);
expect(result).toEqual({ type: "npm", spec: "@scope/pkg@my-marketplace" });
});
it("splits on last @ for non-scoped multi-@ spec", () => {
// e.g. "some-pkg@my-marketplace" where my-marketplace is known
const result = classifyInstallTarget("some-pkg@my-marketplace", KNOWN);
expect(result).toEqual({ type: "marketplace", name: "some-pkg", marketplace: "my-marketplace" });
});
});