Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { installPlugin, uninstallPlugin, updateAll, listPlugins } = require('./in
const { diagnose } = require('./doctor');
const pkg = require('../package.json');

const VALUE_FLAGS = new Set(['repo', 'ref', 'marketplace', 'targets', 'target']);
const VALUE_FLAGS = new Set(['repo', 'ref', 'marketplace', 'targets']);
const BOOL_FLAGS = new Set(['force', 'yes', 'long', 'verbose', 'quiet', 'json', 'help', 'version']);

// A plugin id longer than this is treated as an outlier when sizing the list grid.
Expand Down Expand Up @@ -97,7 +97,6 @@ Options
-y, --yes Accept every detected harness without asking
--force Replace a plugin installed from another marketplace
--long Show plugin descriptions (list)
--target <name> Show install status for one editor only (list): ${NAMES.join(', ')}
--json Machine-readable output (list, installed)
--verbose Show underlying git / CLI detail
--quiet Suppress progress output
Expand All @@ -114,7 +113,6 @@ Examples
${bin} install acme-payments --repo acme/plugin-marketplace
${bin} install paypal --targets cursor,vscode --ref v1.2.0
${bin} uninstall paypal
${bin} list --target cursor
`.trimStart();
}

Expand Down Expand Up @@ -199,21 +197,19 @@ async function run(argv = process.argv.slice(2), profile = {}) {
return result.failed.length ? 1 : 0;
}
case 'list': {
const target = flags.target || null;
const result = await listPlugins({ brand, target });
const result = await listPlugins({ brand });
if (flags.json) {
log.plain(JSON.stringify(result, null, 2));
return 0;
}
const plugins = [...result.plugins].sort((a, b) => a.name.localeCompare(b.name));
const scope = target ? ` installed in ${byName(target).title}` : '';
log.banner(`${log.plural(plugins.length, 'plugin')} in ${result.label}${scope}`);
log.banner(`${log.plural(plugins.length, 'plugin')} in ${result.label}`);
log.plain('');

if (flags.long) {
// Full detail, one plugin per block. The mark answers "installed anywhere?"
// (or, with --target, "installed there?"); the line under it always names
// the actual editors on record, so it never reads as installed everywhere.
// Full detail, one plugin per block. The mark answers "installed anywhere?";
// the line under it always names the actual editors on record, so it never
// reads as installed everywhere.
for (const p of plugins) {
const mark = p.installed ? log.MARK : ' ';
log.plain(` ${mark} ${log.bold(p.name)}`);
Expand Down Expand Up @@ -248,7 +244,7 @@ async function run(argv = process.argv.slice(2), profile = {}) {
log.plain('');
const count = plugins.filter((p) => p.installed).length;
if (count) {
log.info(`${log.MARK} installed${target ? ` in ${byName(target).title}` : ' on this machine'} (${count})`);
log.info(`${log.MARK} installed on this machine (${count})`);
}
if (!flags.long) log.info(`Run \`${bin} list --long\` for descriptions.`);
log.info(`Install one with \`${bin} install <plugin>\`.`);
Expand Down
11 changes: 4 additions & 7 deletions src/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,19 +352,16 @@ async function updateAll({ brand, force = false, deps = {}, pathOpts } = {}) {
return { updated, failed };
}

async function listPlugins({ brand, deps = {}, pathOpts, target } = {}) {
if (target && !NAMES.includes(target)) {
throw new UserError(`Unknown target: ${target}`, { hint: `Valid targets: ${NAMES.join(', ')}` });
}
async function listPlugins({ brand, deps = {}, pathOpts } = {}) {
const catalog = await loadCatalog({ repo: brand.repo, ref: brand.ref, deps });
if (!catalog) {
throw new UserError(`Could not read ${brand.label}.`, {
hint: 'Check --repo, or the branch you pointed at with --ref.',
});
}
// Per-plugin, not per-machine: a plugin recorded with targets: ['cursor'] is only
// installed in Cursor, so `installed` (and an optional --target filter) must read
// that list rather than "does this plugin appear anywhere in the manifest".
// installed in Cursor, so `installed` must read that list rather than "does this
// plugin appear anywhere in the manifest".
const targetsByPlugin = new Map(
manifest
.list(paths.manifestPath(pathOpts))
Expand All @@ -382,7 +379,7 @@ async function listPlugins({ brand, deps = {}, pathOpts, target } = {}) {
name,
description: (typeof p === 'object' && p.description) || '',
targets,
installed: target ? targets.includes(target) : targets.length > 0,
installed: targets.length > 0,
};
}),
};
Expand Down
19 changes: 4 additions & 15 deletions test/install.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ test('list marks what is installed on this machine', async () => {
);
});

test('list scopes installed status to the editor it was actually installed into', async () => {
test('list reports the editors a plugin was actually installed into', async () => {
const m = machine();
const repo = 'context-plugins/plugin-marketplace';
const srcDir = pluginSource();
Expand All @@ -601,18 +601,7 @@ test('list scopes installed status to the editor it was actually installed into'
installPlugin({ brand, plugin: 'my-sdk', targets: ['cursor'], deps: d, pathOpts: m.pathOpts }),
);

const unscoped = await listPlugins({ brand, deps: d, pathOpts: m.pathOpts });
assert.deepEqual(unscoped.plugins[0].targets, ['cursor']);
assert.equal(unscoped.plugins[0].installed, true, 'installed somewhere');

const inCursor = await listPlugins({ brand, deps: d, pathOpts: m.pathOpts, target: 'cursor' });
assert.equal(inCursor.plugins[0].installed, true);

const inVscode = await listPlugins({ brand, deps: d, pathOpts: m.pathOpts, target: 'vscode' });
assert.equal(inVscode.plugins[0].installed, false, 'never installed into VS Code');

await assert.rejects(
() => listPlugins({ brand, deps: d, pathOpts: m.pathOpts, target: 'not-a-real-target' }),
UserError,
);
const listing = await listPlugins({ brand, deps: d, pathOpts: m.pathOpts });
assert.deepEqual(listing.plugins[0].targets, ['cursor']);
assert.equal(listing.plugins[0].installed, true, 'installed somewhere');
});
Loading