magus-cli 7.13.0 → 7.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/package.json +4 -4
- package/src/__tests__/market-r2-adapters.test.ts +190 -0
- package/src/__tests__/market-r2-company-cli.test.ts +350 -0
- package/src/__tests__/market-r2-company-unit.test.ts +224 -0
- package/src/__tests__/market-r2-local-items.test.ts +197 -0
- package/src/__tests__/market-r2-plugin-add.test.ts +470 -0
- package/src/__tests__/market-r2-plugin-publish.test.ts +568 -0
- package/src/__tests__/market-r2-routing.test.ts +127 -0
- package/src/__tests__/market-r2-skill-create-plugin.test.ts +315 -0
- package/src/__tests__/market-r2b-display-path.test.ts +180 -0
- package/src/__tests__/market-r2b-fit-row.test.ts +287 -0
- package/src/__tests__/market-r2b-local-items.test.ts +253 -0
- package/src/__tests__/market-r2b-plugin-add.test.ts +130 -0
- package/src/__tests__/market-router.test.ts +4 -4
- package/src/__tests__/scope-uninstall-wrong-scope.test.ts +341 -0
- package/src/cli/market-view.ts +59 -18
- package/src/cli/market.ts +128 -44
- package/src/cli/router.ts +42 -10
- package/src/data/marketplaces.ts +1 -0
- package/src/main.tsx +1 -1
- package/src/market-tui.tsx +1 -1
- package/src/services/claude-cli.ts +13 -0
- package/src/services/local-marketplace.ts +8 -0
- package/src/services/market-change.ts +6 -6
- package/src/services/market-local.ts +93 -0
- package/src/services/market-ops-plugin.ts +366 -47
- package/src/services/market-ops-publish.ts +363 -15
- package/src/services/market-ops-repo.ts +94 -13
- package/src/services/market-ops-skill.ts +93 -11
- package/src/services/market-ops.ts +72 -8
- package/src/services/market-publish.ts +10 -1
- package/src/services/market-types.ts +23 -0
- package/src/services/update-engine.ts +97 -1
- package/src/tui-runtime.tsx +2 -2
- package/src/types/index.ts +2 -0
- package/src/ui/adapters/marketAdapter.ts +123 -4
- package/src/ui/adapters/pluginsAdapter.ts +25 -1
- package/src/ui/components/primitives/ListCategoryRow.tsx +22 -2
- package/src/ui/market/MarketplaceScreen.tsx +55 -24
- package/src/ui/market/MarketplacesDetail.snowflake.tsx +60 -4
- package/src/ui/market/MarketplacesScreen.tsx +120 -29
- package/src/ui/market/PluginDetail.snowflake.tsx +12 -1
- package/src/ui/market/marketKeymap.ts +29 -2
- package/src/ui/market/useMarketState.ts +146 -5
- package/src/utils/display-path.ts +33 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* market-ops-plugin.ts — plans for `plugin create
|
|
2
|
+
* market-ops-plugin.ts — plans for `plugin create`, `plugin add` and
|
|
3
|
+
* `plugin delete`.
|
|
3
4
|
*
|
|
4
5
|
* Read-only planners. Each plan syncs magus's working clone in its apply and
|
|
5
6
|
* is planned again there before its first write.
|
|
@@ -7,6 +8,8 @@
|
|
|
7
8
|
|
|
8
9
|
import fs from "node:fs";
|
|
9
10
|
import path from "node:path";
|
|
11
|
+
import { requireClaudeConfigDir } from "../utils/config-dir.js";
|
|
12
|
+
import { displayPath } from "../utils/display-path.js";
|
|
10
13
|
import { checkIgnore } from "./git-write.js";
|
|
11
14
|
import { forgetBase, forgetBasesFor, recordBase } from "./market-bases.js";
|
|
12
15
|
import {
|
|
@@ -20,7 +23,12 @@ import {
|
|
|
20
23
|
writeJson,
|
|
21
24
|
} from "./market-change.js";
|
|
22
25
|
import { type Snapshot, cloneUrl } from "./market-clone.js";
|
|
23
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
copyFileUnder,
|
|
28
|
+
linkUnder,
|
|
29
|
+
refuseLinkUnder,
|
|
30
|
+
removeUnder,
|
|
31
|
+
} from "./market-fs.js";
|
|
24
32
|
import {
|
|
25
33
|
SYMLINK_MODE,
|
|
26
34
|
type SkillFile,
|
|
@@ -41,6 +49,7 @@ import {
|
|
|
41
49
|
} from "./market-layout.js";
|
|
42
50
|
import {
|
|
43
51
|
type MarketCommand,
|
|
52
|
+
companyByName,
|
|
44
53
|
computePlanId,
|
|
45
54
|
freshSnapshot,
|
|
46
55
|
manifestAt,
|
|
@@ -87,6 +96,96 @@ import {
|
|
|
87
96
|
|
|
88
97
|
// ─── plugin create ───────────────────────────────────────────────────────────
|
|
89
98
|
|
|
99
|
+
/** The version a plugin new to a marketplace starts at. */
|
|
100
|
+
export const NEW_PLUGIN_VERSION = "0.1.0";
|
|
101
|
+
|
|
102
|
+
/** Where a new plugin goes in a marketplace snapshot. */
|
|
103
|
+
export interface NewPluginLayout {
|
|
104
|
+
plugin: string;
|
|
105
|
+
/** The plugin's folder, repository-relative. */
|
|
106
|
+
dir: string;
|
|
107
|
+
pluginJsonPath: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check `plugin` can be added to the marketplace as read, and name the
|
|
112
|
+
* folder it goes in: under `metadata.pluginRoot`, else `plugins/<name>`.
|
|
113
|
+
* Read-only.
|
|
114
|
+
*/
|
|
115
|
+
export function newPluginLayout(
|
|
116
|
+
m: TrustedRegistration,
|
|
117
|
+
snap: Snapshot,
|
|
118
|
+
manifest: Record<string, unknown>,
|
|
119
|
+
plugin: string,
|
|
120
|
+
): NewPluginLayout {
|
|
121
|
+
if (!validPackageName(plugin)) {
|
|
122
|
+
throw new MarketError(
|
|
123
|
+
"invalid-name",
|
|
124
|
+
`${plugin} cannot be a plugin name (lower case letters, digits and dashes)`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (pluginEntries(manifest).some((p) => p.name === plugin)) {
|
|
128
|
+
throw new MarketError(
|
|
129
|
+
"name-taken",
|
|
130
|
+
`${m.name} already has a plugin named ${plugin}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const root = pluginRootOf(manifest);
|
|
134
|
+
const dir =
|
|
135
|
+
(typeof root === "string" ? pluginRelDir(plugin, root) : null) ??
|
|
136
|
+
joinRel("plugins", plugin);
|
|
137
|
+
if (filesUnder(snap, dir).length > 0) {
|
|
138
|
+
throw new MarketError(
|
|
139
|
+
"name-taken",
|
|
140
|
+
`${m.name} already has a folder ${dir}`,
|
|
141
|
+
"pick another plugin name",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
plugin,
|
|
146
|
+
dir,
|
|
147
|
+
pluginJsonPath: joinRel(dir, ".claude-plugin", "plugin.json"),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Write a new plugin's `plugin.json` and its marketplace entry into a
|
|
153
|
+
* working clone.
|
|
154
|
+
*/
|
|
155
|
+
export function writeNewPlugin(
|
|
156
|
+
clone: string,
|
|
157
|
+
m: TrustedRegistration,
|
|
158
|
+
manifest: Record<string, unknown>,
|
|
159
|
+
layout: NewPluginLayout,
|
|
160
|
+
description: string | undefined,
|
|
161
|
+
): void {
|
|
162
|
+
const owner = (manifest.owner as { name?: unknown } | undefined)?.name;
|
|
163
|
+
const words = description ?? `${layout.plugin} plugin`;
|
|
164
|
+
writeJson(
|
|
165
|
+
clone,
|
|
166
|
+
layout.pluginJsonPath,
|
|
167
|
+
{
|
|
168
|
+
name: layout.plugin,
|
|
169
|
+
version: NEW_PLUGIN_VERSION,
|
|
170
|
+
description: words,
|
|
171
|
+
author: { name: typeof owner === "string" ? owner : m.name },
|
|
172
|
+
},
|
|
173
|
+
m.name,
|
|
174
|
+
);
|
|
175
|
+
const current = readJson(clone, MANIFEST, m.name);
|
|
176
|
+
writeJson(
|
|
177
|
+
clone,
|
|
178
|
+
MANIFEST,
|
|
179
|
+
addPluginEntry(current, {
|
|
180
|
+
name: layout.plugin,
|
|
181
|
+
source: `./${layout.dir}`,
|
|
182
|
+
description: words,
|
|
183
|
+
version: NEW_PLUGIN_VERSION,
|
|
184
|
+
}),
|
|
185
|
+
m.name,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
90
189
|
export async function planPluginCreate(
|
|
91
190
|
ref: string,
|
|
92
191
|
description?: string,
|
|
@@ -102,26 +201,9 @@ export async function planPluginCreate(
|
|
|
102
201
|
}
|
|
103
202
|
const snap = await freshSnapshot(m);
|
|
104
203
|
const manifest = await manifestAt(snap, m.name);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
`${m.name} already has a plugin named ${plugin}`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
const root = pluginRootOf(manifest);
|
|
112
|
-
const dir =
|
|
113
|
-
(typeof root === "string" ? pluginRelDir(plugin, root) : null) ??
|
|
114
|
-
joinRel("plugins", plugin);
|
|
115
|
-
if (filesUnder(snap, dir).length > 0) {
|
|
116
|
-
throw new MarketError(
|
|
117
|
-
"name-taken",
|
|
118
|
-
`${m.name} already has a folder ${dir}`,
|
|
119
|
-
"pick another plugin name",
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
const owner = (manifest.owner as { name?: unknown } | undefined)?.name;
|
|
123
|
-
const pluginJsonPath = joinRel(dir, ".claude-plugin", "plugin.json");
|
|
124
|
-
const version = "0.1.0";
|
|
204
|
+
const layout = newPluginLayout(m, snap, manifest, plugin);
|
|
205
|
+
const { dir, pluginJsonPath } = layout;
|
|
206
|
+
const version = NEW_PLUGIN_VERSION;
|
|
125
207
|
const target = await deliveryTarget(m.source, m.name);
|
|
126
208
|
const files = [
|
|
127
209
|
{ path: MANIFEST, change: "modified" as const },
|
|
@@ -134,30 +216,7 @@ export async function planPluginCreate(
|
|
|
134
216
|
label: `wrote ${plugin}`,
|
|
135
217
|
outside: false,
|
|
136
218
|
async run(ctx) {
|
|
137
|
-
|
|
138
|
-
writeJson(
|
|
139
|
-
clone,
|
|
140
|
-
pluginJsonPath,
|
|
141
|
-
{
|
|
142
|
-
name: plugin,
|
|
143
|
-
version,
|
|
144
|
-
description: description ?? `${plugin} plugin`,
|
|
145
|
-
author: { name: typeof owner === "string" ? owner : m.name },
|
|
146
|
-
},
|
|
147
|
-
m.name,
|
|
148
|
-
);
|
|
149
|
-
const current = readJson(clone, MANIFEST, m.name);
|
|
150
|
-
writeJson(
|
|
151
|
-
clone,
|
|
152
|
-
MANIFEST,
|
|
153
|
-
addPluginEntry(current, {
|
|
154
|
-
name: plugin,
|
|
155
|
-
source: `./${dir}`,
|
|
156
|
-
description: description ?? `${plugin} plugin`,
|
|
157
|
-
version,
|
|
158
|
-
}),
|
|
159
|
-
m.name,
|
|
160
|
-
);
|
|
219
|
+
writeNewPlugin(cloneOf(ctx), m, manifest, layout, description);
|
|
161
220
|
return undefined;
|
|
162
221
|
},
|
|
163
222
|
},
|
|
@@ -210,7 +269,10 @@ export async function planPluginCreate(
|
|
|
210
269
|
}
|
|
211
270
|
|
|
212
271
|
/** After a push, bring Claude Code's clone forward so magus sees the change. */
|
|
213
|
-
function refreshStep(
|
|
272
|
+
export function refreshStep(
|
|
273
|
+
name: string,
|
|
274
|
+
state: ChangeSpec["state"],
|
|
275
|
+
): MarketStep {
|
|
214
276
|
return {
|
|
215
277
|
id: "refresh",
|
|
216
278
|
label: `brought Claude Code's copy of ${name} up to date`,
|
|
@@ -223,6 +285,263 @@ function refreshStep(name: string, state: ChangeSpec["state"]): MarketStep {
|
|
|
223
285
|
};
|
|
224
286
|
}
|
|
225
287
|
|
|
288
|
+
// ─── plugin add ──────────────────────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
export interface PluginAddArgs {
|
|
291
|
+
/** The plugin folder: absolute, or relative to the project. */
|
|
292
|
+
path: string;
|
|
293
|
+
/** The company marketplace; may be left out when there is one. */
|
|
294
|
+
to?: string;
|
|
295
|
+
/** The plugin's name there; its `plugin.json` name when left out. */
|
|
296
|
+
name?: string;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const PLUGIN_JSON = ".claude-plugin/plugin.json";
|
|
300
|
+
|
|
301
|
+
/** The marketplace a `plugin add` goes to. */
|
|
302
|
+
async function addTarget(to: string | undefined): Promise<TrustedRegistration> {
|
|
303
|
+
if (to) return companyByName(to);
|
|
304
|
+
const all = await listCompanyMarketplaces();
|
|
305
|
+
if (all.length === 1 && all[0]) return all[0];
|
|
306
|
+
if (all.length === 0) {
|
|
307
|
+
throw new MarketError(
|
|
308
|
+
"not-found",
|
|
309
|
+
"There is no company marketplace on this machine",
|
|
310
|
+
"run `magus market create` or `magus market connect` first",
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
throw new MarketError(
|
|
314
|
+
"ambiguous",
|
|
315
|
+
`More than one company marketplace is on this machine (${all.map((m) => m.name).join(", ")})`,
|
|
316
|
+
"name one with --to <marketplace>",
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** The local plugin folder, checked: a real folder holding plugin.json. */
|
|
321
|
+
function pluginFolder(
|
|
322
|
+
cwd: string,
|
|
323
|
+
given: string,
|
|
324
|
+
): {
|
|
325
|
+
folder: string;
|
|
326
|
+
json: Record<string, unknown>;
|
|
327
|
+
} {
|
|
328
|
+
const folder = path.resolve(cwd, given);
|
|
329
|
+
let stat: fs.Stats | null = null;
|
|
330
|
+
try {
|
|
331
|
+
stat = fs.lstatSync(folder);
|
|
332
|
+
} catch {
|
|
333
|
+
stat = null;
|
|
334
|
+
}
|
|
335
|
+
if (!stat) {
|
|
336
|
+
throw new MarketError("not-found", `There is no folder at ${given}`);
|
|
337
|
+
}
|
|
338
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
339
|
+
throw new MarketError(
|
|
340
|
+
"other",
|
|
341
|
+
`${given} is ${stat.isSymbolicLink() ? "a link to another folder" : "not a folder"}; magus adds only a plugin folder of its own`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
const installed = path.join(requireClaudeConfigDir(), "plugins");
|
|
345
|
+
if (folder === installed || folder.startsWith(`${installed}${path.sep}`)) {
|
|
346
|
+
throw new MarketError(
|
|
347
|
+
"other",
|
|
348
|
+
`${given} is a plugin Claude Code installed from a marketplace, not one of your own`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const link = linkUnder(folder, PLUGIN_JSON);
|
|
352
|
+
if (link !== null) {
|
|
353
|
+
throw new MarketError(
|
|
354
|
+
"other",
|
|
355
|
+
`${given} reaches its plugin.json through a link at ${link}; magus copies only files of the folder's own`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
let json: unknown;
|
|
359
|
+
try {
|
|
360
|
+
json = JSON.parse(fs.readFileSync(path.join(folder, PLUGIN_JSON), "utf8"));
|
|
361
|
+
} catch (error) {
|
|
362
|
+
throw new MarketError(
|
|
363
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
364
|
+
? "not-found"
|
|
365
|
+
: "other",
|
|
366
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
367
|
+
? `${given} is not a plugin: it has no ${PLUGIN_JSON}`
|
|
368
|
+
: `${given}'s ${PLUGIN_JSON} is not valid JSON`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
372
|
+
throw new MarketError(
|
|
373
|
+
"other",
|
|
374
|
+
`${given}'s ${PLUGIN_JSON} does not hold a JSON object`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
return { folder, json: json as Record<string, unknown> };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* `market plugin add <path>`: copy a plugin folder of your own into a company
|
|
382
|
+
* marketplace as a new plugin, at its own version (0.1.0 when it has none).
|
|
383
|
+
* The folder stays where it is. Read-only.
|
|
384
|
+
*/
|
|
385
|
+
export async function planPluginAdd(
|
|
386
|
+
args: PluginAddArgs,
|
|
387
|
+
cwd: string,
|
|
388
|
+
): Promise<PlanAnswer> {
|
|
389
|
+
const command: MarketCommand = "market plugin add";
|
|
390
|
+
try {
|
|
391
|
+
const { folder, json } = pluginFolder(cwd, args.path);
|
|
392
|
+
const own = typeof json.name === "string" ? json.name : undefined;
|
|
393
|
+
const plugin = args.name ?? own ?? path.basename(folder);
|
|
394
|
+
if (!validPackageName(plugin)) {
|
|
395
|
+
throw new MarketError(
|
|
396
|
+
"invalid-name",
|
|
397
|
+
`${plugin} cannot be a plugin name (lower case letters, digits and dashes)`,
|
|
398
|
+
"pass --name <name>",
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
const m = await addTarget(args.to);
|
|
402
|
+
const snap = await freshSnapshot(m);
|
|
403
|
+
const manifest = await manifestAt(snap, m.name);
|
|
404
|
+
const layout = newPluginLayout(m, snap, manifest, plugin);
|
|
405
|
+
const { dir, pluginJsonPath } = layout;
|
|
406
|
+
const theirVersion =
|
|
407
|
+
typeof json.version === "string" && nextVersion(json.version) !== null
|
|
408
|
+
? json.version
|
|
409
|
+
: null;
|
|
410
|
+
const version = theirVersion ?? NEW_PLUGIN_VERSION;
|
|
411
|
+
const sets = await readSourceSets(folder, snap.dir, dir, ".claude-plugin");
|
|
412
|
+
if (!sets.published.some((f) => f.path === PLUGIN_JSON)) {
|
|
413
|
+
throw new MarketError(
|
|
414
|
+
"other",
|
|
415
|
+
`${plugin}'s ${PLUGIN_JSON} would not be copied (${sets.notPublished.find((n) => n.path === PLUGIN_JSON)?.reason ?? "excluded"})`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
// plugin.json is written rather than copied when the marketplace needs
|
|
419
|
+
// a different name or a version it did not carry.
|
|
420
|
+
const rewrite = own !== plugin || theirVersion === null;
|
|
421
|
+
const description =
|
|
422
|
+
typeof json.description === "string" ? json.description : undefined;
|
|
423
|
+
const target = await deliveryTarget(m.source, m.name);
|
|
424
|
+
const files = [
|
|
425
|
+
{ path: MANIFEST, change: "modified" as const },
|
|
426
|
+
...sets.published.map((f) => ({
|
|
427
|
+
path: joinRel(dir, f.path),
|
|
428
|
+
change: "added" as const,
|
|
429
|
+
})),
|
|
430
|
+
];
|
|
431
|
+
const state: ChangeSpec["state"] = {};
|
|
432
|
+
const steps: MarketStep[] = [
|
|
433
|
+
{
|
|
434
|
+
id: "copy",
|
|
435
|
+
label: `copied ${plugin} into ${m.name}`,
|
|
436
|
+
outside: false,
|
|
437
|
+
async run(ctx) {
|
|
438
|
+
const clone = cloneOf(ctx);
|
|
439
|
+
const linked = linkRefusal(m.name);
|
|
440
|
+
for (const f of sets.published) {
|
|
441
|
+
refuseLinkUnder(clone, joinRel(dir, f.path), linked);
|
|
442
|
+
}
|
|
443
|
+
for (const f of sets.published) {
|
|
444
|
+
copyFileUnder(
|
|
445
|
+
clone,
|
|
446
|
+
joinRel(dir, f.path),
|
|
447
|
+
path.join(folder, f.path),
|
|
448
|
+
linked,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (rewrite) {
|
|
452
|
+
writeJson(
|
|
453
|
+
clone,
|
|
454
|
+
pluginJsonPath,
|
|
455
|
+
{ ...json, name: plugin, version },
|
|
456
|
+
m.name,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
const current = readJson(clone, MANIFEST, m.name);
|
|
460
|
+
writeJson(
|
|
461
|
+
clone,
|
|
462
|
+
MANIFEST,
|
|
463
|
+
addPluginEntry(current, {
|
|
464
|
+
name: plugin,
|
|
465
|
+
source: `./${dir}`,
|
|
466
|
+
...(description ? { description } : {}),
|
|
467
|
+
version,
|
|
468
|
+
}),
|
|
469
|
+
m.name,
|
|
470
|
+
);
|
|
471
|
+
return undefined;
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
...changeSteps({
|
|
475
|
+
label: m.name,
|
|
476
|
+
source: m.source,
|
|
477
|
+
branch: snap.branch,
|
|
478
|
+
target,
|
|
479
|
+
stage: [MANIFEST, dir],
|
|
480
|
+
files,
|
|
481
|
+
message: `${plugin}: add plugin (${version})`,
|
|
482
|
+
slug: `${plugin}-add`,
|
|
483
|
+
prBody: `Adds the plugin \`${plugin}\` (${version}) to ${m.name}.`,
|
|
484
|
+
...(rewrite ? {} : { verify: [{ dir, files: sets.published }] }),
|
|
485
|
+
state,
|
|
486
|
+
}),
|
|
487
|
+
refreshStep(m.name, state),
|
|
488
|
+
];
|
|
489
|
+
const lines = [
|
|
490
|
+
`Adds the plugin ${plugin} (${version}) from ${displayPath(folder, cwd)} to ${m.name}, in ${dir}`,
|
|
491
|
+
`Copies ${sets.published.length} file${sets.published.length === 1 ? "" : "s"}: ${sets.published.map((f) => f.path).join(", ")}`,
|
|
492
|
+
...(sets.untrackedFolder
|
|
493
|
+
? [
|
|
494
|
+
"This folder is not tracked in the project's git; every file is listed",
|
|
495
|
+
]
|
|
496
|
+
: []),
|
|
497
|
+
...(sets.notPublished.length > 0
|
|
498
|
+
? [
|
|
499
|
+
`Not copied: ${sets.notPublished.map((n) => `${n.path} (${n.reason})`).join(", ")}`,
|
|
500
|
+
]
|
|
501
|
+
: []),
|
|
502
|
+
...(rewrite
|
|
503
|
+
? [
|
|
504
|
+
`Writes its plugin.json with the name ${plugin} and the version ${version}`,
|
|
505
|
+
]
|
|
506
|
+
: []),
|
|
507
|
+
targetLine(target, snap.branch),
|
|
508
|
+
`Keeps that folder: install ${plugin}@${m.name} in magus to use the marketplace's copy, then remove the folder so it does not load twice`,
|
|
509
|
+
];
|
|
510
|
+
const plan: MarketPlan = {
|
|
511
|
+
command,
|
|
512
|
+
lines,
|
|
513
|
+
planId: computePlanId({
|
|
514
|
+
op: "plugin-add",
|
|
515
|
+
folder,
|
|
516
|
+
plugin,
|
|
517
|
+
marketplace: m.name,
|
|
518
|
+
commit: snap.commit,
|
|
519
|
+
hash: sets.hash,
|
|
520
|
+
notPublished: sets.notPublished,
|
|
521
|
+
version,
|
|
522
|
+
rewrite,
|
|
523
|
+
target,
|
|
524
|
+
}),
|
|
525
|
+
summary: {
|
|
526
|
+
marketplace: m.name,
|
|
527
|
+
plugin,
|
|
528
|
+
version: { from: null, to: version },
|
|
529
|
+
files,
|
|
530
|
+
notPublished: sets.notPublished,
|
|
531
|
+
target,
|
|
532
|
+
},
|
|
533
|
+
steps,
|
|
534
|
+
sync: { name: m.name, url: cloneUrl(m.source) },
|
|
535
|
+
lock: m.name,
|
|
536
|
+
replan: () => planPluginAdd({ ...args, to: m.name }, cwd),
|
|
537
|
+
doneMessage: `${plugin}@${m.name} ${version} is in the marketplace. Install it in magus to use it from there.`,
|
|
538
|
+
};
|
|
539
|
+
return { kind: "plan", plan };
|
|
540
|
+
} catch (error) {
|
|
541
|
+
return planError(command, error);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
226
545
|
// ─── plugin delete ───────────────────────────────────────────────────────────
|
|
227
546
|
|
|
228
547
|
export async function planPluginDelete(
|