pi-extmgr 0.1.27 → 0.2.0
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 +21 -10
- package/package.json +21 -16
- package/src/commands/auto-update.ts +5 -5
- package/src/commands/cache.ts +1 -1
- package/src/commands/history.ts +5 -34
- package/src/commands/install.ts +2 -2
- package/src/commands/registry.ts +7 -7
- package/src/commands/types.ts +1 -1
- package/src/constants.ts +0 -8
- package/src/extensions/discovery.ts +125 -42
- package/src/index.ts +15 -15
- package/src/packages/catalog.ts +9 -8
- package/src/packages/discovery.ts +56 -19
- package/src/packages/extensions.ts +65 -103
- package/src/packages/install.ts +104 -74
- package/src/packages/management.ts +78 -65
- package/src/types/index.ts +20 -11
- package/src/ui/async-task.ts +101 -65
- package/src/ui/footer.ts +47 -31
- package/src/ui/help.ts +17 -13
- package/src/ui/package-config.ts +36 -48
- package/src/ui/remote.ts +714 -119
- package/src/ui/theme.ts +2 -2
- package/src/ui/unified.ts +964 -371
- package/src/utils/auto-update.ts +44 -39
- package/src/utils/cache.ts +208 -37
- package/src/utils/command.ts +1 -1
- package/src/utils/duration.ts +132 -0
- package/src/utils/format.ts +4 -33
- package/src/utils/fs.ts +8 -4
- package/src/utils/history.ts +47 -9
- package/src/utils/mode.ts +2 -2
- package/src/utils/notify.ts +1 -15
- package/src/utils/npm-exec.ts +1 -1
- package/src/utils/package-source.ts +35 -7
- package/src/utils/path-identity.ts +7 -0
- package/src/utils/relative-path-selection.ts +100 -0
- package/src/utils/settings.ts +11 -61
- package/src/utils/status.ts +12 -10
- package/src/utils/ui-helpers.ts +2 -2
- package/src/utils/retry.ts +0 -49
package/src/packages/install.ts
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Package installation logic
|
|
3
3
|
*/
|
|
4
|
-
import { mkdir, rm, writeFile
|
|
5
|
-
import { join } from "node:path";
|
|
4
|
+
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
6
5
|
import { homedir } from "node:os";
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import {
|
|
8
|
+
type ExtensionAPI,
|
|
9
|
+
type ExtensionCommandContext,
|
|
10
|
+
type ProgressEvent,
|
|
11
11
|
} from "@mariozechner/pi-coding-agent";
|
|
12
|
+
import { TIMEOUTS } from "../constants.js";
|
|
13
|
+
import { runTaskWithLoader } from "../ui/async-task.js";
|
|
14
|
+
import { parseChoiceByLabel } from "../utils/command.js";
|
|
12
15
|
import { normalizePackageSource } from "../utils/format.js";
|
|
13
16
|
import { fileExists } from "../utils/fs.js";
|
|
14
|
-
import { clearSearchCache } from "./discovery.js";
|
|
15
|
-
import { getPackageCatalog } from "./catalog.js";
|
|
16
|
-
import { discoverPackageExtensionEntrypoints, readPackageManifest } from "./extensions.js";
|
|
17
17
|
import { logPackageInstall } from "../utils/history.js";
|
|
18
|
-
import { clearUpdatesAvailable } from "../utils/settings.js";
|
|
19
|
-
import { notify, error as notifyError, success } from "../utils/notify.js";
|
|
20
|
-
import { confirmAction, confirmReload, showProgress } from "../utils/ui-helpers.js";
|
|
21
18
|
import { tryOperation } from "../utils/mode.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
19
|
+
import { fetchWithTimeout } from "../utils/network.js";
|
|
20
|
+
import { notify, error as notifyError, success } from "../utils/notify.js";
|
|
24
21
|
import { execNpm } from "../utils/npm-exec.js";
|
|
25
22
|
import { normalizePackageIdentity } from "../utils/package-source.js";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
23
|
+
import { clearUpdatesAvailable } from "../utils/settings.js";
|
|
24
|
+
import { updateExtmgrStatus } from "../utils/status.js";
|
|
25
|
+
import { confirmAction, confirmReload, showProgress } from "../utils/ui-helpers.js";
|
|
26
|
+
import { getPackageCatalog } from "./catalog.js";
|
|
27
|
+
import { clearSearchCache } from "./discovery.js";
|
|
28
|
+
import { discoverPackageExtensionEntrypoints, readPackageManifest } from "./extensions.js";
|
|
28
29
|
|
|
29
30
|
export type InstallScope = "global" | "project";
|
|
30
31
|
|
|
@@ -32,6 +33,17 @@ export interface InstallOptions {
|
|
|
32
33
|
scope?: InstallScope;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
export interface InstallOutcome {
|
|
37
|
+
installed: boolean;
|
|
38
|
+
reloaded: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const INSTALL_SCOPE_CHOICES = {
|
|
42
|
+
global: "Global (~/.pi/agent/settings.json)",
|
|
43
|
+
project: "Project (.pi/settings.json)",
|
|
44
|
+
cancel: "Cancel",
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
35
47
|
function getProgressMessage(event: ProgressEvent, fallback: string): string {
|
|
36
48
|
return event.message?.trim() || fallback;
|
|
37
49
|
}
|
|
@@ -44,14 +56,12 @@ async function resolveInstallScope(
|
|
|
44
56
|
|
|
45
57
|
if (!ctx.hasUI) return "global";
|
|
46
58
|
|
|
47
|
-
const choice =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
]);
|
|
59
|
+
const choice = parseChoiceByLabel(
|
|
60
|
+
INSTALL_SCOPE_CHOICES,
|
|
61
|
+
await ctx.ui.select("Install scope", Object.values(INSTALL_SCOPE_CHOICES))
|
|
62
|
+
);
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
return choice.startsWith("Project") ? "project" : "global";
|
|
64
|
+
return choice === "cancel" ? undefined : choice;
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
function getExtensionInstallDir(ctx: ExtensionCommandContext, scope: InstallScope): string {
|
|
@@ -61,28 +71,6 @@ function getExtensionInstallDir(ctx: ExtensionCommandContext, scope: InstallScop
|
|
|
61
71
|
return join(homedir(), ".pi", "agent", "extensions");
|
|
62
72
|
}
|
|
63
73
|
|
|
64
|
-
interface GithubUrlInfo {
|
|
65
|
-
owner: string;
|
|
66
|
-
repo: string;
|
|
67
|
-
branch: string;
|
|
68
|
-
filePath: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Safely extracts regex match groups with validation
|
|
73
|
-
*/
|
|
74
|
-
function safeExtractGithubMatch(match: RegExpMatchArray | null): GithubUrlInfo | undefined {
|
|
75
|
-
if (!match) return undefined;
|
|
76
|
-
|
|
77
|
-
const [, owner, repo, branch, filePath] = match;
|
|
78
|
-
|
|
79
|
-
if (!owner || !repo || !branch || !filePath) {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { owner, repo, branch, filePath };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
74
|
async function ensureTarAvailable(
|
|
87
75
|
pi: ExtensionAPI,
|
|
88
76
|
ctx: ExtensionCommandContext
|
|
@@ -157,36 +145,35 @@ async function cleanupStandaloneTempArtifacts(tempDir: string, extractDir?: stri
|
|
|
157
145
|
);
|
|
158
146
|
}
|
|
159
147
|
|
|
160
|
-
|
|
148
|
+
async function installPackageInternal(
|
|
161
149
|
source: string,
|
|
162
150
|
ctx: ExtensionCommandContext,
|
|
163
151
|
pi: ExtensionAPI,
|
|
164
152
|
options?: InstallOptions
|
|
165
|
-
): Promise<
|
|
153
|
+
): Promise<InstallOutcome> {
|
|
166
154
|
const scope = await resolveInstallScope(ctx, options?.scope);
|
|
167
155
|
if (!scope) {
|
|
168
156
|
notify(ctx, "Installation cancelled.", "info");
|
|
169
|
-
return;
|
|
157
|
+
return { installed: false, reloaded: false };
|
|
170
158
|
}
|
|
171
159
|
|
|
172
160
|
// Check if it's a GitHub URL to a .ts file - handle as direct download
|
|
173
161
|
const githubTsMatch = source.match(
|
|
174
|
-
/^https:\/\/github\.com\/([
|
|
162
|
+
/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+\.ts)$/
|
|
175
163
|
);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
164
|
+
if (githubTsMatch) {
|
|
165
|
+
const [, owner, repo, branch, filePath] = githubTsMatch;
|
|
166
|
+
if (owner && repo && branch && filePath) {
|
|
167
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}`;
|
|
168
|
+
const fileName = filePath.split("/").pop() || `${owner}-${repo}.ts`;
|
|
169
|
+
return await installFromUrl(rawUrl, fileName, ctx, pi, { scope });
|
|
170
|
+
}
|
|
183
171
|
}
|
|
184
172
|
|
|
185
173
|
// Check if it's already a raw URL to a .ts file
|
|
186
174
|
if (source.match(/^https:\/\/raw\.githubusercontent\.com\/.*\.ts$/)) {
|
|
187
175
|
const fileName = source.split("/").pop() || "extension.ts";
|
|
188
|
-
await installFromUrl(source, fileName, ctx, pi, { scope });
|
|
189
|
-
return;
|
|
176
|
+
return await installFromUrl(source, fileName, ctx, pi, { scope });
|
|
190
177
|
}
|
|
191
178
|
|
|
192
179
|
const normalized = normalizePackageSource(source);
|
|
@@ -199,7 +186,7 @@ export async function installPackage(
|
|
|
199
186
|
);
|
|
200
187
|
if (!confirmed) {
|
|
201
188
|
notify(ctx, "Installation cancelled.", "info");
|
|
202
|
-
return;
|
|
189
|
+
return { installed: false, reloaded: false };
|
|
203
190
|
}
|
|
204
191
|
|
|
205
192
|
showProgress(ctx, "Installing", normalized);
|
|
@@ -211,6 +198,7 @@ export async function installPackage(
|
|
|
211
198
|
title: "Install Package",
|
|
212
199
|
message: `Installing ${normalized}...`,
|
|
213
200
|
cancellable: false,
|
|
201
|
+
fallbackWithoutLoader: true,
|
|
214
202
|
},
|
|
215
203
|
async ({ setMessage }) => {
|
|
216
204
|
await getPackageCatalog(ctx.cwd).install(normalized, scope, (event) => {
|
|
@@ -225,18 +213,38 @@ export async function installPackage(
|
|
|
225
213
|
logPackageInstall(pi, normalized, normalized, undefined, scope, false, errorMsg);
|
|
226
214
|
notifyError(ctx, errorMsg);
|
|
227
215
|
void updateExtmgrStatus(ctx, pi);
|
|
228
|
-
return;
|
|
216
|
+
return { installed: false, reloaded: false };
|
|
229
217
|
}
|
|
230
218
|
|
|
231
219
|
clearSearchCache();
|
|
232
220
|
logPackageInstall(pi, normalized, normalized, undefined, scope, true);
|
|
233
221
|
success(ctx, `Installed ${normalized} (${scope})`);
|
|
234
|
-
clearUpdatesAvailable(pi, ctx, [normalizePackageIdentity(normalized)]);
|
|
222
|
+
clearUpdatesAvailable(pi, ctx, [normalizePackageIdentity(normalized, { cwd: ctx.cwd })]);
|
|
235
223
|
|
|
236
224
|
const reloaded = await confirmReload(ctx, "Package installed.");
|
|
237
225
|
if (!reloaded) {
|
|
238
226
|
void updateExtmgrStatus(ctx, pi);
|
|
239
227
|
}
|
|
228
|
+
|
|
229
|
+
return { installed: true, reloaded };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function installPackage(
|
|
233
|
+
source: string,
|
|
234
|
+
ctx: ExtensionCommandContext,
|
|
235
|
+
pi: ExtensionAPI,
|
|
236
|
+
options?: InstallOptions
|
|
237
|
+
): Promise<void> {
|
|
238
|
+
await installPackageInternal(source, ctx, pi, options);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function installPackageWithOutcome(
|
|
242
|
+
source: string,
|
|
243
|
+
ctx: ExtensionCommandContext,
|
|
244
|
+
pi: ExtensionAPI,
|
|
245
|
+
options?: InstallOptions
|
|
246
|
+
): Promise<InstallOutcome> {
|
|
247
|
+
return installPackageInternal(source, ctx, pi, options);
|
|
240
248
|
}
|
|
241
249
|
|
|
242
250
|
export async function installFromUrl(
|
|
@@ -245,11 +253,11 @@ export async function installFromUrl(
|
|
|
245
253
|
ctx: ExtensionCommandContext,
|
|
246
254
|
pi: ExtensionAPI,
|
|
247
255
|
options?: InstallOptions
|
|
248
|
-
): Promise<
|
|
256
|
+
): Promise<InstallOutcome> {
|
|
249
257
|
const scope = await resolveInstallScope(ctx, options?.scope);
|
|
250
258
|
if (!scope) {
|
|
251
259
|
notify(ctx, "Installation cancelled.", "info");
|
|
252
|
-
return;
|
|
260
|
+
return { installed: false, reloaded: false };
|
|
253
261
|
}
|
|
254
262
|
|
|
255
263
|
const extensionDir = getExtensionInstallDir(ctx, scope);
|
|
@@ -262,7 +270,7 @@ export async function installFromUrl(
|
|
|
262
270
|
);
|
|
263
271
|
if (!confirmed) {
|
|
264
272
|
notify(ctx, "Installation cancelled.", "info");
|
|
265
|
-
return;
|
|
273
|
+
return { installed: false, reloaded: false };
|
|
266
274
|
}
|
|
267
275
|
|
|
268
276
|
const result = await tryOperation(
|
|
@@ -288,7 +296,7 @@ export async function installFromUrl(
|
|
|
288
296
|
if (!result) {
|
|
289
297
|
logPackageInstall(pi, url, fileName, undefined, scope, false, "Installation failed");
|
|
290
298
|
void updateExtmgrStatus(ctx, pi);
|
|
291
|
-
return;
|
|
299
|
+
return { installed: false, reloaded: false };
|
|
292
300
|
}
|
|
293
301
|
|
|
294
302
|
const { fileName: name, destPath } = result;
|
|
@@ -299,6 +307,8 @@ export async function installFromUrl(
|
|
|
299
307
|
if (!reloaded) {
|
|
300
308
|
void updateExtmgrStatus(ctx, pi);
|
|
301
309
|
}
|
|
310
|
+
|
|
311
|
+
return { installed: true, reloaded };
|
|
302
312
|
}
|
|
303
313
|
|
|
304
314
|
/**
|
|
@@ -323,16 +333,16 @@ function parsePackageInfo(viewOutput: string): { version: string; tarballUrl: st
|
|
|
323
333
|
}
|
|
324
334
|
}
|
|
325
335
|
|
|
326
|
-
|
|
336
|
+
async function installPackageLocallyInternal(
|
|
327
337
|
packageName: string,
|
|
328
338
|
ctx: ExtensionCommandContext,
|
|
329
339
|
pi: ExtensionAPI,
|
|
330
340
|
options?: InstallOptions
|
|
331
|
-
): Promise<
|
|
341
|
+
): Promise<InstallOutcome> {
|
|
332
342
|
const scope = await resolveInstallScope(ctx, options?.scope);
|
|
333
343
|
if (!scope) {
|
|
334
344
|
notify(ctx, "Installation cancelled.", "info");
|
|
335
|
-
return;
|
|
345
|
+
return { installed: false, reloaded: false };
|
|
336
346
|
}
|
|
337
347
|
|
|
338
348
|
const extensionDir = getExtensionInstallDir(ctx, scope);
|
|
@@ -345,7 +355,7 @@ export async function installPackageLocally(
|
|
|
345
355
|
);
|
|
346
356
|
if (!confirmed) {
|
|
347
357
|
notify(ctx, "Installation cancelled.", "info");
|
|
348
|
-
return;
|
|
358
|
+
return { installed: false, reloaded: false };
|
|
349
359
|
}
|
|
350
360
|
|
|
351
361
|
const result = await tryOperation(
|
|
@@ -383,7 +393,7 @@ export async function installPackageLocally(
|
|
|
383
393
|
"Failed to fetch package info"
|
|
384
394
|
);
|
|
385
395
|
void updateExtmgrStatus(ctx, pi);
|
|
386
|
-
return;
|
|
396
|
+
return { installed: false, reloaded: false };
|
|
387
397
|
}
|
|
388
398
|
const { version, tarballUrl } = result;
|
|
389
399
|
|
|
@@ -400,7 +410,7 @@ export async function installPackageLocally(
|
|
|
400
410
|
tarAvailability.error
|
|
401
411
|
);
|
|
402
412
|
void updateExtmgrStatus(ctx, pi);
|
|
403
|
-
return;
|
|
413
|
+
return { installed: false, reloaded: false };
|
|
404
414
|
}
|
|
405
415
|
|
|
406
416
|
// Download and extract
|
|
@@ -438,7 +448,7 @@ export async function installPackageLocally(
|
|
|
438
448
|
"Download failed"
|
|
439
449
|
);
|
|
440
450
|
void updateExtmgrStatus(ctx, pi);
|
|
441
|
-
return;
|
|
451
|
+
return { installed: false, reloaded: false };
|
|
442
452
|
}
|
|
443
453
|
const { tarballPath } = extractResult;
|
|
444
454
|
|
|
@@ -495,7 +505,7 @@ export async function installPackageLocally(
|
|
|
495
505
|
"Extraction failed"
|
|
496
506
|
);
|
|
497
507
|
void updateExtmgrStatus(ctx, pi);
|
|
498
|
-
return;
|
|
508
|
+
return { installed: false, reloaded: false };
|
|
499
509
|
}
|
|
500
510
|
|
|
501
511
|
// Copy to extensions dir
|
|
@@ -526,7 +536,7 @@ export async function installPackageLocally(
|
|
|
526
536
|
"Failed to copy extension"
|
|
527
537
|
);
|
|
528
538
|
void updateExtmgrStatus(ctx, pi);
|
|
529
|
-
return;
|
|
539
|
+
return { installed: false, reloaded: false };
|
|
530
540
|
}
|
|
531
541
|
|
|
532
542
|
clearSearchCache();
|
|
@@ -537,4 +547,24 @@ export async function installPackageLocally(
|
|
|
537
547
|
if (!reloaded) {
|
|
538
548
|
void updateExtmgrStatus(ctx, pi);
|
|
539
549
|
}
|
|
550
|
+
|
|
551
|
+
return { installed: true, reloaded };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export async function installPackageLocally(
|
|
555
|
+
packageName: string,
|
|
556
|
+
ctx: ExtensionCommandContext,
|
|
557
|
+
pi: ExtensionAPI,
|
|
558
|
+
options?: InstallOptions
|
|
559
|
+
): Promise<void> {
|
|
560
|
+
await installPackageLocallyInternal(packageName, ctx, pi, options);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export async function installPackageLocallyWithOutcome(
|
|
564
|
+
packageName: string,
|
|
565
|
+
ctx: ExtensionCommandContext,
|
|
566
|
+
pi: ExtensionAPI,
|
|
567
|
+
options?: InstallOptions
|
|
568
|
+
): Promise<InstallOutcome> {
|
|
569
|
+
return installPackageLocallyInternal(packageName, ctx, pi, options);
|
|
540
570
|
}
|
|
@@ -1,49 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Package management (update, remove)
|
|
3
3
|
*/
|
|
4
|
-
import type {
|
|
5
|
-
ExtensionAPI,
|
|
6
|
-
ExtensionCommandContext,
|
|
7
|
-
ProgressEvent,
|
|
8
|
-
} from "@mariozechner/pi-coding-agent";
|
|
9
|
-
import type { InstalledPackage } from "../types/index.js";
|
|
10
4
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
type ExtensionAPI,
|
|
6
|
+
type ExtensionCommandContext,
|
|
7
|
+
getAgentDir,
|
|
8
|
+
type ProgressEvent,
|
|
9
|
+
} from "@mariozechner/pi-coding-agent";
|
|
10
|
+
import { UI } from "../constants.js";
|
|
11
|
+
import { type InstalledPackage } from "../types/index.js";
|
|
12
|
+
import { runTaskWithLoader } from "../ui/async-task.js";
|
|
13
|
+
import { parseChoiceByLabel } from "../utils/command.js";
|
|
16
14
|
import { formatInstalledPackageLabel } from "../utils/format.js";
|
|
15
|
+
import { logPackageRemove, logPackageUpdate } from "../utils/history.js";
|
|
16
|
+
import { requireUI } from "../utils/mode.js";
|
|
17
|
+
import { notify, error as notifyError, success } from "../utils/notify.js";
|
|
17
18
|
import { normalizePackageIdentity } from "../utils/package-source.js";
|
|
18
|
-
import { logPackageUpdate, logPackageRemove } from "../utils/history.js";
|
|
19
19
|
import { clearUpdatesAvailable } from "../utils/settings.js";
|
|
20
|
-
import {
|
|
20
|
+
import { updateExtmgrStatus } from "../utils/status.js";
|
|
21
21
|
import {
|
|
22
22
|
confirmAction,
|
|
23
23
|
confirmReload,
|
|
24
|
-
showProgress,
|
|
25
24
|
formatListOutput,
|
|
25
|
+
showProgress,
|
|
26
26
|
} from "../utils/ui-helpers.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
import { getPackageCatalog } from "./catalog.js";
|
|
28
|
+
import {
|
|
29
|
+
clearSearchCache,
|
|
30
|
+
getInstalledPackages,
|
|
31
|
+
getInstalledPackagesAllScopes,
|
|
32
|
+
} from "./discovery.js";
|
|
31
33
|
|
|
32
34
|
export interface PackageMutationOutcome {
|
|
33
35
|
reloaded: boolean;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
const NO_PACKAGE_MUTATION_OUTCOME: PackageMutationOutcome = {
|
|
37
|
-
reloaded: false,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
38
|
const BULK_UPDATE_LABEL = "all packages";
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
39
|
+
const REMOVAL_SCOPE_CHOICES = {
|
|
40
|
+
both: "Both global + project",
|
|
41
|
+
global: "Global only",
|
|
42
|
+
project: "Project only",
|
|
43
|
+
cancel: "Cancel",
|
|
44
|
+
} as const;
|
|
47
45
|
|
|
48
46
|
function getProgressMessage(event: ProgressEvent, fallback: string): string {
|
|
49
47
|
return event.message?.trim() || fallback;
|
|
@@ -56,7 +54,7 @@ async function updatePackageInternal(
|
|
|
56
54
|
): Promise<PackageMutationOutcome> {
|
|
57
55
|
showProgress(ctx, "Updating", source);
|
|
58
56
|
|
|
59
|
-
const updateIdentity = normalizePackageIdentity(source);
|
|
57
|
+
const updateIdentity = normalizePackageIdentity(source, { cwd: ctx.cwd });
|
|
60
58
|
|
|
61
59
|
try {
|
|
62
60
|
const updates = await getPackageCatalog(ctx.cwd).checkForAvailableUpdates();
|
|
@@ -69,7 +67,7 @@ async function updatePackageInternal(
|
|
|
69
67
|
logPackageUpdate(pi, source, source, undefined, true);
|
|
70
68
|
clearUpdatesAvailable(pi, ctx, [updateIdentity]);
|
|
71
69
|
void updateExtmgrStatus(ctx, pi);
|
|
72
|
-
return
|
|
70
|
+
return { reloaded: false };
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
await runTaskWithLoader(
|
|
@@ -78,6 +76,7 @@ async function updatePackageInternal(
|
|
|
78
76
|
title: "Update Package",
|
|
79
77
|
message: `Updating ${source}...`,
|
|
80
78
|
cancellable: false,
|
|
79
|
+
fallbackWithoutLoader: true,
|
|
81
80
|
},
|
|
82
81
|
async ({ setMessage }) => {
|
|
83
82
|
await getPackageCatalog(ctx.cwd).update(source, (event) => {
|
|
@@ -92,7 +91,7 @@ async function updatePackageInternal(
|
|
|
92
91
|
logPackageUpdate(pi, source, source, undefined, false, errorMsg);
|
|
93
92
|
notifyError(ctx, errorMsg);
|
|
94
93
|
void updateExtmgrStatus(ctx, pi);
|
|
95
|
-
return
|
|
94
|
+
return { reloaded: false };
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
logPackageUpdate(pi, source, source, undefined, true);
|
|
@@ -103,7 +102,7 @@ async function updatePackageInternal(
|
|
|
103
102
|
if (!reloaded) {
|
|
104
103
|
void updateExtmgrStatus(ctx, pi);
|
|
105
104
|
}
|
|
106
|
-
return
|
|
105
|
+
return { reloaded };
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
async function updatePackagesInternal(
|
|
@@ -119,7 +118,7 @@ async function updatePackagesInternal(
|
|
|
119
118
|
logPackageUpdate(pi, BULK_UPDATE_LABEL, BULK_UPDATE_LABEL, undefined, true);
|
|
120
119
|
clearUpdatesAvailable(pi, ctx);
|
|
121
120
|
void updateExtmgrStatus(ctx, pi);
|
|
122
|
-
return
|
|
121
|
+
return { reloaded: false };
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
await runTaskWithLoader(
|
|
@@ -128,6 +127,7 @@ async function updatePackagesInternal(
|
|
|
128
127
|
title: "Update Packages",
|
|
129
128
|
message: "Updating all packages...",
|
|
130
129
|
cancellable: false,
|
|
130
|
+
fallbackWithoutLoader: true,
|
|
131
131
|
},
|
|
132
132
|
async ({ setMessage }) => {
|
|
133
133
|
await getPackageCatalog(ctx.cwd).update(undefined, (event) => {
|
|
@@ -142,7 +142,7 @@ async function updatePackagesInternal(
|
|
|
142
142
|
logPackageUpdate(pi, BULK_UPDATE_LABEL, BULK_UPDATE_LABEL, undefined, false, errorMsg);
|
|
143
143
|
notifyError(ctx, errorMsg);
|
|
144
144
|
void updateExtmgrStatus(ctx, pi);
|
|
145
|
-
return
|
|
145
|
+
return { reloaded: false };
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
logPackageUpdate(pi, BULK_UPDATE_LABEL, BULK_UPDATE_LABEL, undefined, true);
|
|
@@ -153,7 +153,7 @@ async function updatePackagesInternal(
|
|
|
153
153
|
if (!reloaded) {
|
|
154
154
|
void updateExtmgrStatus(ctx, pi);
|
|
155
155
|
}
|
|
156
|
-
return
|
|
156
|
+
return { reloaded };
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
export async function updatePackage(
|
|
@@ -186,8 +186,31 @@ export async function updatePackagesWithOutcome(
|
|
|
186
186
|
return updatePackagesInternal(ctx, pi);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
function packageIdentity(
|
|
190
|
-
|
|
189
|
+
function packageIdentity(
|
|
190
|
+
source: string,
|
|
191
|
+
options?: { resolvedPath?: string; cwd?: string }
|
|
192
|
+
): string {
|
|
193
|
+
return normalizePackageIdentity(source, options);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function packageSourceIdentities(source: string, ctx: ExtensionCommandContext): Set<string> {
|
|
197
|
+
return new Set([
|
|
198
|
+
packageIdentity(source, { cwd: ctx.cwd }),
|
|
199
|
+
packageIdentity(source, { cwd: getAgentDir() }),
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function installedPackageMatchesSource(
|
|
204
|
+
pkg: InstalledPackage,
|
|
205
|
+
identities: Set<string>,
|
|
206
|
+
ctx: ExtensionCommandContext
|
|
207
|
+
): boolean {
|
|
208
|
+
return identities.has(
|
|
209
|
+
packageIdentity(pkg.source, {
|
|
210
|
+
...(pkg.resolvedPath ? { resolvedPath: pkg.resolvedPath } : {}),
|
|
211
|
+
cwd: pkg.scope === "project" ? ctx.cwd : getAgentDir(),
|
|
212
|
+
})
|
|
213
|
+
);
|
|
191
214
|
}
|
|
192
215
|
|
|
193
216
|
async function getInstalledPackagesAllScopesForRemoval(
|
|
@@ -204,25 +227,15 @@ interface RemovalTarget {
|
|
|
204
227
|
name: string;
|
|
205
228
|
}
|
|
206
229
|
|
|
207
|
-
function scopeChoiceFromLabel(choice: string | undefined): RemovalScopeChoice {
|
|
208
|
-
if (!choice || choice === "Cancel") return "cancel";
|
|
209
|
-
if (choice.includes("Both")) return "both";
|
|
210
|
-
if (choice.includes("Global")) return "global";
|
|
211
|
-
if (choice.includes("Project")) return "project";
|
|
212
|
-
return "cancel";
|
|
213
|
-
}
|
|
214
|
-
|
|
215
230
|
async function selectRemovalScope(ctx: ExtensionCommandContext): Promise<RemovalScopeChoice> {
|
|
216
231
|
if (!ctx.hasUI) return "global";
|
|
217
232
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return scopeChoiceFromLabel(choice);
|
|
233
|
+
return (
|
|
234
|
+
parseChoiceByLabel(
|
|
235
|
+
REMOVAL_SCOPE_CHOICES,
|
|
236
|
+
await ctx.ui.select("Remove scope", Object.values(REMOVAL_SCOPE_CHOICES))
|
|
237
|
+
) ?? "cancel"
|
|
238
|
+
);
|
|
226
239
|
}
|
|
227
240
|
|
|
228
241
|
function buildRemovalTargets(
|
|
@@ -244,7 +257,6 @@ function buildRemovalTargets(
|
|
|
244
257
|
return addTarget("global");
|
|
245
258
|
case "project":
|
|
246
259
|
return addTarget("project");
|
|
247
|
-
case "cancel":
|
|
248
260
|
default:
|
|
249
261
|
return [];
|
|
250
262
|
}
|
|
@@ -285,6 +297,7 @@ async function executeRemovalTargets(
|
|
|
285
297
|
title: "Remove Package",
|
|
286
298
|
message: `Removing ${target.source}...`,
|
|
287
299
|
cancellable: false,
|
|
300
|
+
fallbackWithoutLoader: true,
|
|
288
301
|
},
|
|
289
302
|
async ({ setMessage }) => {
|
|
290
303
|
await getPackageCatalog(ctx.cwd).remove(target.source, target.scope, (event) => {
|
|
@@ -338,8 +351,8 @@ async function removePackageInternal(
|
|
|
338
351
|
pi: ExtensionAPI
|
|
339
352
|
): Promise<PackageMutationOutcome> {
|
|
340
353
|
const installed = await getInstalledPackagesAllScopesForRemoval(ctx);
|
|
341
|
-
const
|
|
342
|
-
const matching = installed.filter((
|
|
354
|
+
const identities = packageSourceIdentities(source, ctx);
|
|
355
|
+
const matching = installed.filter((pkg) => installedPackageMatchesSource(pkg, identities, ctx));
|
|
343
356
|
|
|
344
357
|
const hasBothScopes =
|
|
345
358
|
matching.some((pkg) => pkg.scope === "global") &&
|
|
@@ -348,18 +361,18 @@ async function removePackageInternal(
|
|
|
348
361
|
|
|
349
362
|
if (scopeChoice === "cancel") {
|
|
350
363
|
notify(ctx, "Removal cancelled.", "info");
|
|
351
|
-
return
|
|
364
|
+
return { reloaded: false };
|
|
352
365
|
}
|
|
353
366
|
|
|
354
367
|
if (matching.length === 0) {
|
|
355
368
|
notify(ctx, `${source} is not installed.`, "info");
|
|
356
|
-
return
|
|
369
|
+
return { reloaded: false };
|
|
357
370
|
}
|
|
358
371
|
|
|
359
372
|
const targets = buildRemovalTargets(matching, ctx.hasUI, scopeChoice);
|
|
360
373
|
if (targets.length === 0) {
|
|
361
374
|
notify(ctx, "Nothing to remove.", "info");
|
|
362
|
-
return
|
|
375
|
+
return { reloaded: false };
|
|
363
376
|
}
|
|
364
377
|
|
|
365
378
|
const confirmed = await confirmAction(
|
|
@@ -370,7 +383,7 @@ async function removePackageInternal(
|
|
|
370
383
|
);
|
|
371
384
|
if (!confirmed) {
|
|
372
385
|
notify(ctx, "Removal cancelled.", "info");
|
|
373
|
-
return
|
|
386
|
+
return { reloaded: false };
|
|
374
387
|
}
|
|
375
388
|
|
|
376
389
|
const results = await executeRemovalTargets(targets, ctx, pi);
|
|
@@ -385,20 +398,20 @@ async function removePackageInternal(
|
|
|
385
398
|
.filter((result) => result.success)
|
|
386
399
|
.map((result) => result.target);
|
|
387
400
|
|
|
388
|
-
const remaining = (await getInstalledPackagesAllScopesForRemoval(ctx)).filter(
|
|
389
|
-
(
|
|
401
|
+
const remaining = (await getInstalledPackagesAllScopesForRemoval(ctx)).filter((pkg) =>
|
|
402
|
+
installedPackageMatchesSource(pkg, identities, ctx)
|
|
390
403
|
);
|
|
391
404
|
notifyRemovalSummary(source, remaining, failures, ctx);
|
|
392
405
|
|
|
393
406
|
if (failures.length === 0 && remaining.length === 0) {
|
|
394
|
-
clearUpdatesAvailable(pi, ctx,
|
|
407
|
+
clearUpdatesAvailable(pi, ctx, identities);
|
|
395
408
|
}
|
|
396
409
|
|
|
397
410
|
const successfulRemovalCount = successfulTargets.length;
|
|
398
411
|
|
|
399
412
|
if (successfulRemovalCount === 0) {
|
|
400
413
|
void updateExtmgrStatus(ctx, pi);
|
|
401
|
-
return
|
|
414
|
+
return { reloaded: false };
|
|
402
415
|
}
|
|
403
416
|
|
|
404
417
|
const reloaded = await confirmReload(ctx, "Removal complete.");
|
|
@@ -406,7 +419,7 @@ async function removePackageInternal(
|
|
|
406
419
|
void updateExtmgrStatus(ctx, pi);
|
|
407
420
|
}
|
|
408
421
|
|
|
409
|
-
return
|
|
422
|
+
return { reloaded };
|
|
410
423
|
}
|
|
411
424
|
|
|
412
425
|
export async function removePackage(
|