pi-extmgr 0.1.28 → 0.2.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 +18 -8
- package/package.json +12 -4
- package/src/commands/auto-update.ts +1 -1
- package/src/commands/history.ts +2 -31
- package/src/constants.ts +0 -8
- package/src/extensions/discovery.ts +121 -39
- package/src/index.ts +0 -4
- package/src/packages/discovery.ts +34 -0
- package/src/packages/extensions.ts +55 -98
- package/src/packages/install.ts +85 -56
- package/src/packages/management.ts +25 -38
- package/src/types/index.ts +4 -2
- package/src/ui/footer.ts +49 -29
- package/src/ui/help.ts +15 -11
- package/src/ui/remote.ts +704 -112
- package/src/ui/unified.ts +922 -311
- package/src/utils/auto-update.ts +34 -29
- package/src/utils/cache.ts +205 -34
- package/src/utils/duration.ts +132 -0
- package/src/utils/format.ts +0 -30
- package/src/utils/fs.ts +8 -4
- package/src/utils/history.ts +43 -7
- package/src/utils/mode.ts +1 -1
- package/src/utils/notify.ts +0 -14
- package/src/utils/package-source.ts +2 -5
- package/src/utils/path-identity.ts +7 -0
- package/src/utils/relative-path-selection.ts +100 -0
- package/src/utils/settings.ts +4 -63
- package/src/utils/retry.ts +0 -49
|
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { type Dirent } from "node:fs";
|
|
3
3
|
import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { dirname, join,
|
|
5
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
8
|
import { getAgentDir } from "@mariozechner/pi-coding-agent";
|
|
@@ -14,6 +14,11 @@ import {
|
|
|
14
14
|
} from "../types/index.js";
|
|
15
15
|
import { parseNpmSource } from "../utils/format.js";
|
|
16
16
|
import { fileExists, readSummary } from "../utils/fs.js";
|
|
17
|
+
import {
|
|
18
|
+
matchesFilterPattern,
|
|
19
|
+
normalizeRelativePath,
|
|
20
|
+
resolveRelativePathSelection,
|
|
21
|
+
} from "../utils/relative-path-selection.js";
|
|
17
22
|
import { resolveNpmCommand } from "../utils/npm-exec.js";
|
|
18
23
|
|
|
19
24
|
interface PackageSettingsObject {
|
|
@@ -36,11 +41,6 @@ export interface PackageManifest {
|
|
|
36
41
|
const execFileAsync = promisify(execFile);
|
|
37
42
|
let globalNpmRootCache: string | null | undefined;
|
|
38
43
|
|
|
39
|
-
function normalizeRelativePath(value: string): string {
|
|
40
|
-
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
41
|
-
return normalized;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
44
|
function normalizeSource(source: string): string {
|
|
45
45
|
return source
|
|
46
46
|
.trim()
|
|
@@ -238,17 +238,17 @@ function toPackageSettingsObject(
|
|
|
238
238
|
packageSource: string
|
|
239
239
|
): PackageSettingsObject {
|
|
240
240
|
if (typeof existing === "string") {
|
|
241
|
-
return { source: existing
|
|
241
|
+
return { source: existing };
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
if (existing && typeof existing.source === "string") {
|
|
245
245
|
return {
|
|
246
246
|
source: existing.source,
|
|
247
|
-
|
|
247
|
+
...(Array.isArray(existing.extensions) ? { extensions: [...existing.extensions] } : {}),
|
|
248
248
|
};
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
return { source: packageSource
|
|
251
|
+
return { source: packageSource };
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
function updateExtensionMarkers(
|
|
@@ -276,7 +276,16 @@ function updateExtensionMarkers(
|
|
|
276
276
|
for (const [extensionPath, target] of Array.from(changes.entries()).sort((a, b) =>
|
|
277
277
|
a[0].localeCompare(b[0])
|
|
278
278
|
)) {
|
|
279
|
-
|
|
279
|
+
const baseFilters =
|
|
280
|
+
nextTokens.length > 0
|
|
281
|
+
? nextTokens
|
|
282
|
+
: existingTokens && existingTokens.length === 0
|
|
283
|
+
? []
|
|
284
|
+
: undefined;
|
|
285
|
+
const baseState = getPackageFilterState(baseFilters, extensionPath);
|
|
286
|
+
if (target !== baseState) {
|
|
287
|
+
nextTokens.push(`${target === "enabled" ? "+" : "-"}${extensionPath}`);
|
|
288
|
+
}
|
|
280
289
|
}
|
|
281
290
|
|
|
282
291
|
return nextTokens;
|
|
@@ -322,10 +331,13 @@ export async function applyPackageExtensionStateChanges(
|
|
|
322
331
|
|
|
323
332
|
packageEntry.extensions = updateExtensionMarkers(packageEntry.extensions, normalizedChanges);
|
|
324
333
|
|
|
334
|
+
const normalizedPackageEntry =
|
|
335
|
+
packageEntry.extensions.length > 0 ? packageEntry : packageEntry.source;
|
|
336
|
+
|
|
325
337
|
if (index === -1) {
|
|
326
|
-
packages.push(
|
|
338
|
+
packages.push(normalizedPackageEntry);
|
|
327
339
|
} else {
|
|
328
|
-
packages[index] =
|
|
340
|
+
packages[index] = normalizedPackageEntry;
|
|
329
341
|
}
|
|
330
342
|
|
|
331
343
|
settings.packages = packages;
|
|
@@ -340,22 +352,6 @@ export async function applyPackageExtensionStateChanges(
|
|
|
340
352
|
}
|
|
341
353
|
}
|
|
342
354
|
|
|
343
|
-
function safeMatchesGlob(targetPath: string, pattern: string): boolean {
|
|
344
|
-
try {
|
|
345
|
-
return matchesGlob(targetPath, pattern);
|
|
346
|
-
} catch {
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function matchesFilterPattern(targetPath: string, pattern: string): boolean {
|
|
352
|
-
const normalizedPattern = normalizeRelativePath(pattern.trim());
|
|
353
|
-
if (!normalizedPattern) return false;
|
|
354
|
-
if (targetPath === normalizedPattern) return true;
|
|
355
|
-
|
|
356
|
-
return safeMatchesGlob(targetPath, normalizedPattern);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
355
|
function getPackageFilterState(filters: string[] | undefined, extensionPath: string): State {
|
|
360
356
|
// Omitted key => all enabled (pi default).
|
|
361
357
|
if (filters === undefined) {
|
|
@@ -415,58 +411,37 @@ function getPackageFilterState(filters: string[] | undefined, extensionPath: str
|
|
|
415
411
|
return enabled ? "enabled" : "disabled";
|
|
416
412
|
}
|
|
417
413
|
|
|
418
|
-
async function
|
|
419
|
-
packageSource: string,
|
|
420
|
-
extensionPath: string,
|
|
414
|
+
async function readPackageFilterMap(
|
|
421
415
|
scope: Scope,
|
|
422
416
|
cwd: string
|
|
423
|
-
): Promise<
|
|
424
|
-
const
|
|
425
|
-
const settings = await readSettingsFile(settingsPath);
|
|
417
|
+
): Promise<Map<string, string[] | undefined>> {
|
|
418
|
+
const settings = await readSettingsFile(getSettingsPath(scope, cwd));
|
|
426
419
|
const packages = settings.packages ?? [];
|
|
427
|
-
const
|
|
420
|
+
const filterMap = new Map<string, string[] | undefined>();
|
|
428
421
|
|
|
429
|
-
const entry
|
|
430
|
-
if (typeof
|
|
431
|
-
|
|
422
|
+
for (const entry of packages) {
|
|
423
|
+
if (typeof entry === "string") {
|
|
424
|
+
filterMap.set(normalizeSource(entry), undefined);
|
|
425
|
+
continue;
|
|
432
426
|
}
|
|
433
|
-
return normalizeSource(pkg.source) === normalizedSource;
|
|
434
|
-
});
|
|
435
427
|
|
|
436
|
-
|
|
437
|
-
|
|
428
|
+
if (typeof entry.source !== "string") {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
filterMap.set(
|
|
433
|
+
normalizeSource(entry.source),
|
|
434
|
+
Array.isArray(entry.extensions) ? entry.extensions : undefined
|
|
435
|
+
);
|
|
438
436
|
}
|
|
439
437
|
|
|
440
|
-
return
|
|
438
|
+
return filterMap;
|
|
441
439
|
}
|
|
442
440
|
|
|
443
441
|
function isExtensionEntrypointPath(path: string): boolean {
|
|
444
442
|
return /\.(ts|js)$/i.test(path);
|
|
445
443
|
}
|
|
446
444
|
|
|
447
|
-
function hasGlobMagic(path: string): boolean {
|
|
448
|
-
return /[*?{}[\]]/.test(path);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function isSafeRelativePath(path: string): boolean {
|
|
452
|
-
return path !== "" && path !== ".." && !path.startsWith("../") && !path.includes("/../");
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
function selectDirectoryFiles(allFiles: string[], directoryPath: string): string[] {
|
|
456
|
-
const prefix = `${directoryPath}/`;
|
|
457
|
-
return allFiles.filter((file) => file.startsWith(prefix));
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function applySelection(selected: Set<string>, files: Iterable<string>, exclude: boolean): void {
|
|
461
|
-
for (const file of files) {
|
|
462
|
-
if (exclude) {
|
|
463
|
-
selected.delete(file);
|
|
464
|
-
} else {
|
|
465
|
-
selected.add(file);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
445
|
async function collectExtensionFilesFromDir(
|
|
471
446
|
packageRoot: string,
|
|
472
447
|
startDir: string
|
|
@@ -505,38 +480,12 @@ async function resolveManifestExtensionEntries(
|
|
|
505
480
|
packageRoot: string,
|
|
506
481
|
entries: string[]
|
|
507
482
|
): Promise<string[]> {
|
|
508
|
-
const selected = new Set<string>();
|
|
509
483
|
const allFiles = await collectExtensionFilesFromDir(packageRoot, packageRoot);
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const exclude = token.startsWith("!");
|
|
516
|
-
const normalizedToken = normalizeRelativePath(exclude ? token.slice(1) : token);
|
|
517
|
-
const pattern = normalizedToken.replace(/[\\/]+$/g, "");
|
|
518
|
-
if (!isSafeRelativePath(pattern)) {
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
if (hasGlobMagic(pattern)) {
|
|
523
|
-
const matchedFiles = allFiles.filter((file) => matchesFilterPattern(file, pattern));
|
|
524
|
-
applySelection(selected, matchedFiles, exclude);
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
const directoryFiles = selectDirectoryFiles(allFiles, pattern);
|
|
529
|
-
if (directoryFiles.length > 0) {
|
|
530
|
-
applySelection(selected, directoryFiles, exclude);
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (isExtensionEntrypointPath(pattern)) {
|
|
535
|
-
applySelection(selected, [pattern], exclude);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return Array.from(selected).sort((a, b) => a.localeCompare(b));
|
|
484
|
+
return resolveRelativePathSelection(
|
|
485
|
+
allFiles,
|
|
486
|
+
entries,
|
|
487
|
+
(path, files) => isExtensionEntrypointPath(path) && files.includes(path)
|
|
488
|
+
);
|
|
540
489
|
}
|
|
541
490
|
|
|
542
491
|
export async function readPackageManifest(
|
|
@@ -617,11 +566,19 @@ export async function discoverPackageExtensions(
|
|
|
617
566
|
cwd: string
|
|
618
567
|
): Promise<PackageExtensionEntry[]> {
|
|
619
568
|
const entries: PackageExtensionEntry[] = [];
|
|
569
|
+
const [globalFilterMap, projectFilterMap] = await Promise.all([
|
|
570
|
+
readPackageFilterMap("global", cwd),
|
|
571
|
+
readPackageFilterMap("project", cwd),
|
|
572
|
+
]);
|
|
620
573
|
|
|
621
574
|
for (const pkg of packages) {
|
|
622
575
|
const packageRoot = await toPackageRoot(pkg, cwd);
|
|
623
576
|
if (!packageRoot) continue;
|
|
624
577
|
|
|
578
|
+
const packageFilters =
|
|
579
|
+
(pkg.scope === "global" ? globalFilterMap : projectFilterMap).get(
|
|
580
|
+
normalizeSource(pkg.source)
|
|
581
|
+
) ?? undefined;
|
|
625
582
|
const extensionPaths = await discoverPackageExtensionEntrypoints(packageRoot);
|
|
626
583
|
for (const extensionPath of extensionPaths) {
|
|
627
584
|
const normalizedPath = normalizeRelativePath(extensionPath);
|
|
@@ -629,7 +586,7 @@ export async function discoverPackageExtensions(
|
|
|
629
586
|
const summary = (await fileExists(absolutePath))
|
|
630
587
|
? await readSummary(absolutePath)
|
|
631
588
|
: "package extension";
|
|
632
|
-
const state =
|
|
589
|
+
const state = getPackageFilterState(packageFilters, normalizedPath);
|
|
633
590
|
|
|
634
591
|
entries.push({
|
|
635
592
|
id: `pkg-ext:${pkg.scope}:${pkg.source}:${normalizedPath}`,
|
package/src/packages/install.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "@mariozechner/pi-coding-agent";
|
|
12
12
|
import { TIMEOUTS } from "../constants.js";
|
|
13
13
|
import { runTaskWithLoader } from "../ui/async-task.js";
|
|
14
|
+
import { parseChoiceByLabel } from "../utils/command.js";
|
|
14
15
|
import { normalizePackageSource } from "../utils/format.js";
|
|
15
16
|
import { fileExists } from "../utils/fs.js";
|
|
16
17
|
import { logPackageInstall } from "../utils/history.js";
|
|
@@ -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
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);
|
|
@@ -226,7 +213,7 @@ export async function installPackage(
|
|
|
226
213
|
logPackageInstall(pi, normalized, normalized, undefined, scope, false, errorMsg);
|
|
227
214
|
notifyError(ctx, errorMsg);
|
|
228
215
|
void updateExtmgrStatus(ctx, pi);
|
|
229
|
-
return;
|
|
216
|
+
return { installed: false, reloaded: false };
|
|
230
217
|
}
|
|
231
218
|
|
|
232
219
|
clearSearchCache();
|
|
@@ -238,6 +225,26 @@ export async function installPackage(
|
|
|
238
225
|
if (!reloaded) {
|
|
239
226
|
void updateExtmgrStatus(ctx, pi);
|
|
240
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);
|
|
241
248
|
}
|
|
242
249
|
|
|
243
250
|
export async function installFromUrl(
|
|
@@ -246,11 +253,11 @@ export async function installFromUrl(
|
|
|
246
253
|
ctx: ExtensionCommandContext,
|
|
247
254
|
pi: ExtensionAPI,
|
|
248
255
|
options?: InstallOptions
|
|
249
|
-
): Promise<
|
|
256
|
+
): Promise<InstallOutcome> {
|
|
250
257
|
const scope = await resolveInstallScope(ctx, options?.scope);
|
|
251
258
|
if (!scope) {
|
|
252
259
|
notify(ctx, "Installation cancelled.", "info");
|
|
253
|
-
return;
|
|
260
|
+
return { installed: false, reloaded: false };
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
const extensionDir = getExtensionInstallDir(ctx, scope);
|
|
@@ -263,7 +270,7 @@ export async function installFromUrl(
|
|
|
263
270
|
);
|
|
264
271
|
if (!confirmed) {
|
|
265
272
|
notify(ctx, "Installation cancelled.", "info");
|
|
266
|
-
return;
|
|
273
|
+
return { installed: false, reloaded: false };
|
|
267
274
|
}
|
|
268
275
|
|
|
269
276
|
const result = await tryOperation(
|
|
@@ -289,7 +296,7 @@ export async function installFromUrl(
|
|
|
289
296
|
if (!result) {
|
|
290
297
|
logPackageInstall(pi, url, fileName, undefined, scope, false, "Installation failed");
|
|
291
298
|
void updateExtmgrStatus(ctx, pi);
|
|
292
|
-
return;
|
|
299
|
+
return { installed: false, reloaded: false };
|
|
293
300
|
}
|
|
294
301
|
|
|
295
302
|
const { fileName: name, destPath } = result;
|
|
@@ -300,6 +307,8 @@ export async function installFromUrl(
|
|
|
300
307
|
if (!reloaded) {
|
|
301
308
|
void updateExtmgrStatus(ctx, pi);
|
|
302
309
|
}
|
|
310
|
+
|
|
311
|
+
return { installed: true, reloaded };
|
|
303
312
|
}
|
|
304
313
|
|
|
305
314
|
/**
|
|
@@ -324,16 +333,16 @@ function parsePackageInfo(viewOutput: string): { version: string; tarballUrl: st
|
|
|
324
333
|
}
|
|
325
334
|
}
|
|
326
335
|
|
|
327
|
-
|
|
336
|
+
async function installPackageLocallyInternal(
|
|
328
337
|
packageName: string,
|
|
329
338
|
ctx: ExtensionCommandContext,
|
|
330
339
|
pi: ExtensionAPI,
|
|
331
340
|
options?: InstallOptions
|
|
332
|
-
): Promise<
|
|
341
|
+
): Promise<InstallOutcome> {
|
|
333
342
|
const scope = await resolveInstallScope(ctx, options?.scope);
|
|
334
343
|
if (!scope) {
|
|
335
344
|
notify(ctx, "Installation cancelled.", "info");
|
|
336
|
-
return;
|
|
345
|
+
return { installed: false, reloaded: false };
|
|
337
346
|
}
|
|
338
347
|
|
|
339
348
|
const extensionDir = getExtensionInstallDir(ctx, scope);
|
|
@@ -346,7 +355,7 @@ export async function installPackageLocally(
|
|
|
346
355
|
);
|
|
347
356
|
if (!confirmed) {
|
|
348
357
|
notify(ctx, "Installation cancelled.", "info");
|
|
349
|
-
return;
|
|
358
|
+
return { installed: false, reloaded: false };
|
|
350
359
|
}
|
|
351
360
|
|
|
352
361
|
const result = await tryOperation(
|
|
@@ -384,7 +393,7 @@ export async function installPackageLocally(
|
|
|
384
393
|
"Failed to fetch package info"
|
|
385
394
|
);
|
|
386
395
|
void updateExtmgrStatus(ctx, pi);
|
|
387
|
-
return;
|
|
396
|
+
return { installed: false, reloaded: false };
|
|
388
397
|
}
|
|
389
398
|
const { version, tarballUrl } = result;
|
|
390
399
|
|
|
@@ -401,7 +410,7 @@ export async function installPackageLocally(
|
|
|
401
410
|
tarAvailability.error
|
|
402
411
|
);
|
|
403
412
|
void updateExtmgrStatus(ctx, pi);
|
|
404
|
-
return;
|
|
413
|
+
return { installed: false, reloaded: false };
|
|
405
414
|
}
|
|
406
415
|
|
|
407
416
|
// Download and extract
|
|
@@ -439,7 +448,7 @@ export async function installPackageLocally(
|
|
|
439
448
|
"Download failed"
|
|
440
449
|
);
|
|
441
450
|
void updateExtmgrStatus(ctx, pi);
|
|
442
|
-
return;
|
|
451
|
+
return { installed: false, reloaded: false };
|
|
443
452
|
}
|
|
444
453
|
const { tarballPath } = extractResult;
|
|
445
454
|
|
|
@@ -496,7 +505,7 @@ export async function installPackageLocally(
|
|
|
496
505
|
"Extraction failed"
|
|
497
506
|
);
|
|
498
507
|
void updateExtmgrStatus(ctx, pi);
|
|
499
|
-
return;
|
|
508
|
+
return { installed: false, reloaded: false };
|
|
500
509
|
}
|
|
501
510
|
|
|
502
511
|
// Copy to extensions dir
|
|
@@ -527,7 +536,7 @@ export async function installPackageLocally(
|
|
|
527
536
|
"Failed to copy extension"
|
|
528
537
|
);
|
|
529
538
|
void updateExtmgrStatus(ctx, pi);
|
|
530
|
-
return;
|
|
539
|
+
return { installed: false, reloaded: false };
|
|
531
540
|
}
|
|
532
541
|
|
|
533
542
|
clearSearchCache();
|
|
@@ -538,4 +547,24 @@ export async function installPackageLocally(
|
|
|
538
547
|
if (!reloaded) {
|
|
539
548
|
void updateExtmgrStatus(ctx, pi);
|
|
540
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);
|
|
541
570
|
}
|