pi-extmgr 0.1.11 → 0.1.12
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/package.json +1 -1
- package/src/packages/discovery.ts +47 -9
- package/src/packages/management.ts +95 -27
- package/src/ui/unified.ts +40 -22
- package/src/utils/package-source.ts +38 -0
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Package discovery and listing
|
|
3
3
|
*/
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
4
6
|
import type {
|
|
5
7
|
ExtensionAPI,
|
|
6
8
|
ExtensionCommandContext,
|
|
@@ -10,7 +12,7 @@ import type { InstalledPackage, NpmPackage, SearchCache } from "../types/index.j
|
|
|
10
12
|
import { CACHE_TTL, TIMEOUTS } from "../constants.js";
|
|
11
13
|
import { readSummary } from "../utils/fs.js";
|
|
12
14
|
import { parseNpmSource } from "../utils/format.js";
|
|
13
|
-
import { splitGitRepoAndRef } from "../utils/package-source.js";
|
|
15
|
+
import { getPackageSourceKind, splitGitRepoAndRef } from "../utils/package-source.js";
|
|
14
16
|
|
|
15
17
|
let searchCache: SearchCache | null = null;
|
|
16
18
|
|
|
@@ -280,8 +282,9 @@ function parsePackageNameAndVersion(fullSource: string): {
|
|
|
280
282
|
return parsedNpm;
|
|
281
283
|
}
|
|
282
284
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
+
const sourceKind = getPackageSourceKind(fullSource);
|
|
286
|
+
if (sourceKind === "git") {
|
|
287
|
+
const gitSpec = fullSource.startsWith("git:") ? fullSource.slice(4) : fullSource;
|
|
285
288
|
const { repo } = splitGitRepoAndRef(gitSpec);
|
|
286
289
|
return { name: extractGitPackageName(repo) };
|
|
287
290
|
}
|
|
@@ -298,6 +301,45 @@ function parsePackageNameAndVersion(fullSource: string): {
|
|
|
298
301
|
return { name: fileName || fullSource };
|
|
299
302
|
}
|
|
300
303
|
|
|
304
|
+
async function hydratePackageFromResolvedPath(pkg: InstalledPackage): Promise<void> {
|
|
305
|
+
if (!pkg.resolvedPath) return;
|
|
306
|
+
|
|
307
|
+
const manifestPath = /(?:^|[\\/])package\.json$/i.test(pkg.resolvedPath)
|
|
308
|
+
? pkg.resolvedPath
|
|
309
|
+
: join(pkg.resolvedPath, "package.json");
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const raw = await readFile(manifestPath, "utf8");
|
|
313
|
+
const manifest = JSON.parse(raw) as {
|
|
314
|
+
name?: unknown;
|
|
315
|
+
version?: unknown;
|
|
316
|
+
description?: unknown;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
if (!pkg.version && typeof manifest.version === "string" && manifest.version.trim()) {
|
|
320
|
+
pkg.version = manifest.version.trim();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (
|
|
324
|
+
!pkg.description &&
|
|
325
|
+
typeof manifest.description === "string" &&
|
|
326
|
+
manifest.description.trim()
|
|
327
|
+
) {
|
|
328
|
+
pkg.description = manifest.description.trim();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (
|
|
332
|
+
(!pkg.name || pkg.name === pkg.source) &&
|
|
333
|
+
typeof manifest.name === "string" &&
|
|
334
|
+
manifest.name.trim()
|
|
335
|
+
) {
|
|
336
|
+
pkg.name = manifest.name.trim();
|
|
337
|
+
}
|
|
338
|
+
} catch {
|
|
339
|
+
// ignore
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
301
343
|
/**
|
|
302
344
|
* Fetch package size from npm view
|
|
303
345
|
*/
|
|
@@ -356,7 +398,8 @@ async function addPackageMetadata(
|
|
|
356
398
|
|
|
357
399
|
await Promise.all(
|
|
358
400
|
batch.map(async (pkg) => {
|
|
359
|
-
|
|
401
|
+
await hydratePackageFromResolvedPath(pkg);
|
|
402
|
+
|
|
360
403
|
const needsDescription = !pkg.description;
|
|
361
404
|
const needsSize = pkg.size === undefined && pkg.source.startsWith("npm:");
|
|
362
405
|
|
|
@@ -364,7 +407,6 @@ async function addPackageMetadata(
|
|
|
364
407
|
|
|
365
408
|
try {
|
|
366
409
|
if (pkg.source.endsWith(".ts") || pkg.source.endsWith(".js")) {
|
|
367
|
-
// For local files, read description from file
|
|
368
410
|
if (needsDescription) {
|
|
369
411
|
pkg.description = await readSummary(pkg.source);
|
|
370
412
|
}
|
|
@@ -373,13 +415,11 @@ async function addPackageMetadata(
|
|
|
373
415
|
const pkgName = parsed?.name;
|
|
374
416
|
|
|
375
417
|
if (pkgName) {
|
|
376
|
-
// Get description
|
|
377
418
|
if (needsDescription) {
|
|
378
419
|
const cached = await getCachedPackage(pkgName);
|
|
379
420
|
if (cached?.description) {
|
|
380
421
|
pkg.description = cached.description;
|
|
381
422
|
} else {
|
|
382
|
-
// Fetch from npm and cache it
|
|
383
423
|
const res = await pi.exec("npm", ["view", pkgName, "description", "--json"], {
|
|
384
424
|
timeout: TIMEOUTS.npmView,
|
|
385
425
|
cwd: ctx.cwd,
|
|
@@ -389,7 +429,6 @@ async function addPackageMetadata(
|
|
|
389
429
|
const desc = JSON.parse(res.stdout) as string;
|
|
390
430
|
if (typeof desc === "string" && desc) {
|
|
391
431
|
pkg.description = desc;
|
|
392
|
-
// Cache the description
|
|
393
432
|
await setCachedPackage(pkgName, {
|
|
394
433
|
name: pkgName,
|
|
395
434
|
description: desc,
|
|
@@ -402,7 +441,6 @@ async function addPackageMetadata(
|
|
|
402
441
|
}
|
|
403
442
|
}
|
|
404
443
|
|
|
405
|
-
// Get size
|
|
406
444
|
if (needsSize) {
|
|
407
445
|
pkg.size = await fetchPackageSize(pkgName, ctx, pi);
|
|
408
446
|
}
|
|
@@ -23,11 +23,27 @@ import { requireUI } from "../utils/mode.js";
|
|
|
23
23
|
import { updateExtmgrStatus } from "../utils/status.js";
|
|
24
24
|
import { TIMEOUTS, UI } from "../constants.js";
|
|
25
25
|
|
|
26
|
-
export
|
|
26
|
+
export interface PackageMutationOutcome {
|
|
27
|
+
reloaded: boolean;
|
|
28
|
+
restartRequested: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const NO_PACKAGE_MUTATION_OUTCOME: PackageMutationOutcome = {
|
|
32
|
+
reloaded: false,
|
|
33
|
+
restartRequested: false,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function packageMutationOutcome(
|
|
37
|
+
overrides: Partial<PackageMutationOutcome>
|
|
38
|
+
): PackageMutationOutcome {
|
|
39
|
+
return { ...NO_PACKAGE_MUTATION_OUTCOME, ...overrides };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function updatePackageInternal(
|
|
27
43
|
source: string,
|
|
28
44
|
ctx: ExtensionCommandContext,
|
|
29
45
|
pi: ExtensionAPI
|
|
30
|
-
): Promise<
|
|
46
|
+
): Promise<PackageMutationOutcome> {
|
|
31
47
|
showProgress(ctx, "Updating", source);
|
|
32
48
|
|
|
33
49
|
const res = await pi.exec("pi", ["update", source], {
|
|
@@ -40,28 +56,29 @@ export async function updatePackage(
|
|
|
40
56
|
logPackageUpdate(pi, source, source, undefined, undefined, false, errorMsg);
|
|
41
57
|
notifyError(ctx, errorMsg);
|
|
42
58
|
void updateExtmgrStatus(ctx, pi);
|
|
43
|
-
return;
|
|
59
|
+
return NO_PACKAGE_MUTATION_OUTCOME;
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
const stdout = res.stdout || "";
|
|
47
63
|
if (stdout.includes("already up to date") || stdout.includes("pinned")) {
|
|
48
64
|
notify(ctx, `${source} is already up to date (or pinned).`, "info");
|
|
49
65
|
logPackageUpdate(pi, source, source, undefined, undefined, true);
|
|
50
|
-
} else {
|
|
51
|
-
logPackageUpdate(pi, source, source, undefined, undefined, true);
|
|
52
|
-
success(ctx, `Updated ${source}`);
|
|
53
66
|
void updateExtmgrStatus(ctx, pi);
|
|
54
|
-
|
|
55
|
-
return;
|
|
67
|
+
return NO_PACKAGE_MUTATION_OUTCOME;
|
|
56
68
|
}
|
|
57
69
|
|
|
70
|
+
logPackageUpdate(pi, source, source, undefined, undefined, true);
|
|
71
|
+
success(ctx, `Updated ${source}`);
|
|
58
72
|
void updateExtmgrStatus(ctx, pi);
|
|
73
|
+
|
|
74
|
+
const reloaded = await confirmReload(ctx, "Package updated.");
|
|
75
|
+
return packageMutationOutcome({ reloaded });
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
|
|
78
|
+
async function updatePackagesInternal(
|
|
62
79
|
ctx: ExtensionCommandContext,
|
|
63
80
|
pi: ExtensionAPI
|
|
64
|
-
): Promise<
|
|
81
|
+
): Promise<PackageMutationOutcome> {
|
|
65
82
|
showProgress(ctx, "Updating", "all packages");
|
|
66
83
|
|
|
67
84
|
const res = await pi.exec("pi", ["update"], { timeout: 300000, cwd: ctx.cwd });
|
|
@@ -69,20 +86,51 @@ export async function updatePackages(
|
|
|
69
86
|
if (res.code !== 0) {
|
|
70
87
|
notifyError(ctx, `Update failed: ${res.stderr || res.stdout || `exit ${res.code}`}`);
|
|
71
88
|
void updateExtmgrStatus(ctx, pi);
|
|
72
|
-
return;
|
|
89
|
+
return NO_PACKAGE_MUTATION_OUTCOME;
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
const stdout = res.stdout || "";
|
|
76
93
|
if (stdout.includes("already up to date") || stdout.trim() === "") {
|
|
77
94
|
notify(ctx, "All packages are already up to date.", "info");
|
|
78
|
-
} else {
|
|
79
|
-
success(ctx, "Packages updated");
|
|
80
95
|
void updateExtmgrStatus(ctx, pi);
|
|
81
|
-
|
|
82
|
-
return;
|
|
96
|
+
return NO_PACKAGE_MUTATION_OUTCOME;
|
|
83
97
|
}
|
|
84
98
|
|
|
99
|
+
success(ctx, "Packages updated");
|
|
85
100
|
void updateExtmgrStatus(ctx, pi);
|
|
101
|
+
|
|
102
|
+
const reloaded = await confirmReload(ctx, "Packages updated.");
|
|
103
|
+
return packageMutationOutcome({ reloaded });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function updatePackage(
|
|
107
|
+
source: string,
|
|
108
|
+
ctx: ExtensionCommandContext,
|
|
109
|
+
pi: ExtensionAPI
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
await updatePackageInternal(source, ctx, pi);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function updatePackageWithOutcome(
|
|
115
|
+
source: string,
|
|
116
|
+
ctx: ExtensionCommandContext,
|
|
117
|
+
pi: ExtensionAPI
|
|
118
|
+
): Promise<PackageMutationOutcome> {
|
|
119
|
+
return updatePackageInternal(source, ctx, pi);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function updatePackages(
|
|
123
|
+
ctx: ExtensionCommandContext,
|
|
124
|
+
pi: ExtensionAPI
|
|
125
|
+
): Promise<void> {
|
|
126
|
+
await updatePackagesInternal(ctx, pi);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function updatePackagesWithOutcome(
|
|
130
|
+
ctx: ExtensionCommandContext,
|
|
131
|
+
pi: ExtensionAPI
|
|
132
|
+
): Promise<PackageMutationOutcome> {
|
|
133
|
+
return updatePackagesInternal(ctx, pi);
|
|
86
134
|
}
|
|
87
135
|
|
|
88
136
|
function packageIdentity(source: string, fallbackName?: string): string {
|
|
@@ -234,11 +282,11 @@ function notifyRemovalSummary(
|
|
|
234
282
|
}
|
|
235
283
|
}
|
|
236
284
|
|
|
237
|
-
|
|
285
|
+
async function removePackageInternal(
|
|
238
286
|
source: string,
|
|
239
287
|
ctx: ExtensionCommandContext,
|
|
240
288
|
pi: ExtensionAPI
|
|
241
|
-
): Promise<
|
|
289
|
+
): Promise<PackageMutationOutcome> {
|
|
242
290
|
const installed = await getInstalledPackagesAllScopes(ctx, pi);
|
|
243
291
|
const direct = installed.find((p) => p.source === source);
|
|
244
292
|
const identity = packageIdentity(source, direct?.name);
|
|
@@ -251,13 +299,13 @@ export async function removePackage(
|
|
|
251
299
|
|
|
252
300
|
if (scopeChoice === "cancel") {
|
|
253
301
|
notify(ctx, "Removal cancelled.", "info");
|
|
254
|
-
return;
|
|
302
|
+
return NO_PACKAGE_MUTATION_OUTCOME;
|
|
255
303
|
}
|
|
256
304
|
|
|
257
305
|
const targets = buildRemovalTargets(matching, source, ctx.hasUI, scopeChoice);
|
|
258
306
|
if (targets.length === 0) {
|
|
259
307
|
notify(ctx, "Nothing to remove.", "info");
|
|
260
|
-
return;
|
|
308
|
+
return NO_PACKAGE_MUTATION_OUTCOME;
|
|
261
309
|
}
|
|
262
310
|
|
|
263
311
|
const confirmed = await confirmAction(
|
|
@@ -268,7 +316,7 @@ export async function removePackage(
|
|
|
268
316
|
);
|
|
269
317
|
if (!confirmed) {
|
|
270
318
|
notify(ctx, "Removal cancelled.", "info");
|
|
271
|
-
return;
|
|
319
|
+
return NO_PACKAGE_MUTATION_OUTCOME;
|
|
272
320
|
}
|
|
273
321
|
|
|
274
322
|
const failures = await executeRemovalTargets(targets, ctx, pi);
|
|
@@ -281,10 +329,28 @@ export async function removePackage(
|
|
|
281
329
|
|
|
282
330
|
void updateExtmgrStatus(ctx, pi);
|
|
283
331
|
|
|
284
|
-
await confirmRestart(
|
|
332
|
+
const restartRequested = await confirmRestart(
|
|
285
333
|
ctx,
|
|
286
334
|
`Removal complete.\n\n⚠️ Extensions/prompts/skills/themes from removed packages are fully unloaded after restarting pi.`
|
|
287
335
|
);
|
|
336
|
+
|
|
337
|
+
return packageMutationOutcome({ restartRequested });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export async function removePackage(
|
|
341
|
+
source: string,
|
|
342
|
+
ctx: ExtensionCommandContext,
|
|
343
|
+
pi: ExtensionAPI
|
|
344
|
+
): Promise<void> {
|
|
345
|
+
await removePackageInternal(source, ctx, pi);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function removePackageWithOutcome(
|
|
349
|
+
source: string,
|
|
350
|
+
ctx: ExtensionCommandContext,
|
|
351
|
+
pi: ExtensionAPI
|
|
352
|
+
): Promise<PackageMutationOutcome> {
|
|
353
|
+
return removePackageInternal(source, ctx, pi);
|
|
288
354
|
}
|
|
289
355
|
|
|
290
356
|
export async function promptRemove(ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
@@ -344,12 +410,14 @@ export async function showPackageActions(
|
|
|
344
410
|
: "back";
|
|
345
411
|
|
|
346
412
|
switch (action) {
|
|
347
|
-
case "remove":
|
|
348
|
-
await
|
|
349
|
-
return
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
413
|
+
case "remove": {
|
|
414
|
+
const outcome = await removePackageWithOutcome(pkg.source, ctx, pi);
|
|
415
|
+
return outcome.reloaded || outcome.restartRequested;
|
|
416
|
+
}
|
|
417
|
+
case "update": {
|
|
418
|
+
const outcome = await updatePackageWithOutcome(pkg.source, ctx, pi);
|
|
419
|
+
return outcome.reloaded || outcome.restartRequested;
|
|
420
|
+
}
|
|
353
421
|
case "details": {
|
|
354
422
|
const sizeStr = pkg.size !== undefined ? `\nSize: ${formatBytes(pkg.size)}` : "";
|
|
355
423
|
notify(
|
package/src/ui/unified.ts
CHANGED
|
@@ -30,9 +30,9 @@ import { getInstalledPackages } from "../packages/discovery.js";
|
|
|
30
30
|
import { discoverPackageExtensions, setPackageExtensionState } from "../packages/extensions.js";
|
|
31
31
|
import {
|
|
32
32
|
showPackageActions,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
updatePackageWithOutcome,
|
|
34
|
+
removePackageWithOutcome,
|
|
35
|
+
updatePackagesWithOutcome,
|
|
36
36
|
} from "../packages/management.js";
|
|
37
37
|
import { showRemote } from "./remote.js";
|
|
38
38
|
import { showHelp } from "./help.js";
|
|
@@ -48,6 +48,7 @@ import { logExtensionToggle } from "../utils/history.js";
|
|
|
48
48
|
import { getKnownUpdates, promptAutoUpdateWizard } from "../utils/auto-update.js";
|
|
49
49
|
import { updateExtmgrStatus } from "../utils/status.js";
|
|
50
50
|
import { parseChoiceByLabel } from "../utils/command.js";
|
|
51
|
+
import { getPackageSourceKind } from "../utils/package-source.js";
|
|
51
52
|
import { UI } from "../constants.js";
|
|
52
53
|
|
|
53
54
|
// Type guard for SettingsList with selectedIndex
|
|
@@ -277,8 +278,6 @@ function buildUnifiedItems(
|
|
|
277
278
|
const items: UnifiedItem[] = [];
|
|
278
279
|
const localPaths = new Set<string>();
|
|
279
280
|
|
|
280
|
-
const packageSourcesWithExtensions = new Set<string>();
|
|
281
|
-
|
|
282
281
|
// Add local extensions
|
|
283
282
|
for (const entry of localEntries) {
|
|
284
283
|
localPaths.add(entry.activePath?.toLowerCase() ?? "");
|
|
@@ -296,7 +295,6 @@ function buildUnifiedItems(
|
|
|
296
295
|
}
|
|
297
296
|
|
|
298
297
|
for (const entry of packageExtensions) {
|
|
299
|
-
packageSourcesWithExtensions.add(entry.packageSource.toLowerCase());
|
|
300
298
|
items.push({
|
|
301
299
|
type: "package-extension",
|
|
302
300
|
id: entry.id,
|
|
@@ -314,8 +312,6 @@ function buildUnifiedItems(
|
|
|
314
312
|
const pkgSourceLower = pkg.source.toLowerCase();
|
|
315
313
|
const pkgResolvedLower = pkg.resolvedPath?.toLowerCase() ?? "";
|
|
316
314
|
|
|
317
|
-
if (packageSourcesWithExtensions.has(pkgSourceLower)) continue;
|
|
318
|
-
|
|
319
315
|
let isDuplicate = false;
|
|
320
316
|
for (const localPath of localPaths) {
|
|
321
317
|
if (pkgSourceLower === localPath || pkgResolvedLower === localPath) {
|
|
@@ -355,7 +351,7 @@ function buildUnifiedItems(
|
|
|
355
351
|
items.sort((a, b) => {
|
|
356
352
|
const rank = (type: UnifiedItem["type"]): number => {
|
|
357
353
|
if (type === "local") return 0;
|
|
358
|
-
if (type === "package
|
|
354
|
+
if (type === "package") return 1;
|
|
359
355
|
return 2;
|
|
360
356
|
};
|
|
361
357
|
|
|
@@ -427,7 +423,7 @@ function formatUnifiedItemLabel(
|
|
|
427
423
|
theme: Theme,
|
|
428
424
|
changed = false
|
|
429
425
|
): string {
|
|
430
|
-
if (item.type === "local"
|
|
426
|
+
if (item.type === "local") {
|
|
431
427
|
const statusIcon = getStatusIcon(theme, state === "enabled" ? "enabled" : "disabled");
|
|
432
428
|
const scopeIcon = getScopeIcon(theme, item.scope as "global" | "project");
|
|
433
429
|
const changeMarker = getChangeMarker(theme, changed);
|
|
@@ -436,7 +432,26 @@ function formatUnifiedItemLabel(
|
|
|
436
432
|
return `${statusIcon} [${scopeIcon}] ${name} - ${summary}${changeMarker}`;
|
|
437
433
|
}
|
|
438
434
|
|
|
439
|
-
|
|
435
|
+
if (item.type === "package-extension") {
|
|
436
|
+
const statusIcon = getStatusIcon(theme, state === "enabled" ? "enabled" : "disabled");
|
|
437
|
+
const scopeIcon = getScopeIcon(theme, item.scope as "global" | "project");
|
|
438
|
+
const sourceKind = getPackageSourceKind(item.packageSource ?? "");
|
|
439
|
+
const pkgIcon = getPackageIcon(
|
|
440
|
+
theme,
|
|
441
|
+
sourceKind === "npm" || sourceKind === "git" || sourceKind === "local" ? sourceKind : "local"
|
|
442
|
+
);
|
|
443
|
+
const sourceLabel = sourceKind === "unknown" ? "package" : `${sourceKind} package`;
|
|
444
|
+
const changeMarker = getChangeMarker(theme, changed);
|
|
445
|
+
const name = theme.bold(item.displayName);
|
|
446
|
+
const summary = theme.fg("dim", `${item.summary} • ${sourceLabel}`);
|
|
447
|
+
return `${statusIcon} ${pkgIcon} [${scopeIcon}] ${name} - ${summary}${changeMarker}`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const sourceKind = getPackageSourceKind(item.source ?? "");
|
|
451
|
+
const pkgIcon = getPackageIcon(
|
|
452
|
+
theme,
|
|
453
|
+
sourceKind === "npm" || sourceKind === "git" || sourceKind === "local" ? sourceKind : "local"
|
|
454
|
+
);
|
|
440
455
|
const scopeIcon = getScopeIcon(theme, item.scope as "global" | "project");
|
|
441
456
|
const name = theme.bold(item.displayName);
|
|
442
457
|
const version = item.version ? theme.fg("dim", `@${item.version}`) : "";
|
|
@@ -449,9 +464,9 @@ function formatUnifiedItemLabel(
|
|
|
449
464
|
// Reserved space: icon (2) + scope (3) + name (~25) + version (~10) + separator (3) = ~43 chars
|
|
450
465
|
if (item.description) {
|
|
451
466
|
infoParts.push(dynamicTruncate(item.description, 43));
|
|
452
|
-
} else if (
|
|
467
|
+
} else if (sourceKind === "npm") {
|
|
453
468
|
infoParts.push("npm");
|
|
454
|
-
} else if (
|
|
469
|
+
} else if (sourceKind === "git") {
|
|
455
470
|
infoParts.push("git");
|
|
456
471
|
} else {
|
|
457
472
|
infoParts.push("local");
|
|
@@ -616,9 +631,10 @@ async function navigateWithPendingGuard(
|
|
|
616
631
|
case "browse":
|
|
617
632
|
await showRemote("", ctx, pi);
|
|
618
633
|
return "done";
|
|
619
|
-
case "update-all":
|
|
620
|
-
await
|
|
621
|
-
return "done";
|
|
634
|
+
case "update-all": {
|
|
635
|
+
const outcome = await updatePackagesWithOutcome(ctx, pi);
|
|
636
|
+
return outcome.reloaded || outcome.restartRequested ? "exit" : "done";
|
|
637
|
+
}
|
|
622
638
|
case "auto-update":
|
|
623
639
|
await promptAutoUpdateWizard(pi, ctx, (packages) => {
|
|
624
640
|
ctx.ui.notify(
|
|
@@ -784,12 +800,14 @@ async function handleUnifiedAction(
|
|
|
784
800
|
};
|
|
785
801
|
|
|
786
802
|
switch (result.action) {
|
|
787
|
-
case "update":
|
|
788
|
-
await
|
|
789
|
-
return
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
803
|
+
case "update": {
|
|
804
|
+
const outcome = await updatePackageWithOutcome(pkg.source, ctx, pi);
|
|
805
|
+
return outcome.reloaded || outcome.restartRequested;
|
|
806
|
+
}
|
|
807
|
+
case "remove": {
|
|
808
|
+
const outcome = await removePackageWithOutcome(pkg.source, ctx, pi);
|
|
809
|
+
return outcome.reloaded || outcome.restartRequested;
|
|
810
|
+
}
|
|
793
811
|
case "details": {
|
|
794
812
|
const sizeStr = pkg.size !== undefined ? `\nSize: ${formatBytes(pkg.size)}` : "";
|
|
795
813
|
ctx.ui.notify(
|
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
* Package source parsing helpers shared across discovery/management flows.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
export type PackageSourceKind = "npm" | "git" | "local" | "unknown";
|
|
6
|
+
|
|
7
|
+
function sanitizeSource(source: string): string {
|
|
8
|
+
return source
|
|
9
|
+
.trim()
|
|
10
|
+
.replace(/\s+\((filtered|pinned)\)$/i, "")
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getPackageSourceKind(source: string): PackageSourceKind {
|
|
15
|
+
const normalized = sanitizeSource(source);
|
|
16
|
+
|
|
17
|
+
if (normalized.startsWith("npm:")) return "npm";
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
normalized.startsWith("git:") ||
|
|
21
|
+
normalized.startsWith("http://") ||
|
|
22
|
+
normalized.startsWith("https://") ||
|
|
23
|
+
normalized.startsWith("ssh://") ||
|
|
24
|
+
/^git@[^\s:]+:.+/.test(normalized)
|
|
25
|
+
) {
|
|
26
|
+
return "git";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (
|
|
30
|
+
normalized.startsWith("/") ||
|
|
31
|
+
normalized.startsWith("./") ||
|
|
32
|
+
normalized.startsWith("../") ||
|
|
33
|
+
normalized.startsWith("~/") ||
|
|
34
|
+
/^[a-zA-Z]:[\\/]/.test(normalized) ||
|
|
35
|
+
normalized.startsWith("\\\\")
|
|
36
|
+
) {
|
|
37
|
+
return "local";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return "unknown";
|
|
41
|
+
}
|
|
42
|
+
|
|
5
43
|
export function splitGitRepoAndRef(gitSpec: string): { repo: string; ref?: string | undefined } {
|
|
6
44
|
const lastAt = gitSpec.lastIndexOf("@");
|
|
7
45
|
if (lastAt <= 0) {
|