pi-extmgr 0.1.22 → 0.1.24
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 +25 -12
- package/package.json +1 -1
- package/src/commands/auto-update.ts +1 -1
- package/src/commands/cache.ts +5 -1
- package/src/commands/history.ts +4 -2
- package/src/commands/registry.ts +1 -1
- package/src/index.ts +6 -1
- package/src/packages/discovery.ts +53 -28
- package/src/packages/extensions.ts +171 -63
- package/src/packages/install.ts +118 -24
- package/src/packages/management.ts +58 -37
- package/src/ui/package-config.ts +157 -126
- package/src/ui/remote.ts +79 -54
- package/src/ui/unified.ts +222 -173
- package/src/utils/auto-update.ts +36 -31
- package/src/utils/command.ts +77 -1
- package/src/utils/format.ts +23 -3
- package/src/utils/history.ts +41 -2
- package/src/utils/mode.ts +56 -5
- package/src/utils/npm-exec.ts +47 -0
- package/src/utils/package-source.ts +43 -0
- package/src/utils/settings-list.ts +12 -0
- package/src/utils/settings.ts +35 -7
- package/src/utils/status.ts +10 -2
- package/src/utils/timer.ts +32 -8
- package/src/utils/ui-helpers.ts +2 -1
package/src/ui/package-config.ts
CHANGED
|
@@ -13,19 +13,20 @@ import {
|
|
|
13
13
|
type SettingItem,
|
|
14
14
|
} from "@mariozechner/pi-tui";
|
|
15
15
|
import type { InstalledPackage, PackageExtensionEntry, State } from "../types/index.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
applyPackageExtensionStateChanges,
|
|
18
|
+
discoverPackageExtensions,
|
|
19
|
+
validatePackageExtensionSettings,
|
|
20
|
+
} from "../packages/extensions.js";
|
|
17
21
|
import { notify } from "../utils/notify.js";
|
|
18
22
|
import { logExtensionToggle } from "../utils/history.js";
|
|
23
|
+
import { requireCustomUI, runCustomUI } from "../utils/mode.js";
|
|
19
24
|
import { getPackageSourceKind } from "../utils/package-source.js";
|
|
25
|
+
import { getSettingsListSelectedIndex } from "../utils/settings-list.js";
|
|
20
26
|
import { fileExists } from "../utils/fs.js";
|
|
21
27
|
import { UI } from "../constants.js";
|
|
22
28
|
import { getChangeMarker, getPackageIcon, getScopeIcon, getStatusIcon } from "./theme.js";
|
|
23
29
|
|
|
24
|
-
interface SelectableList {
|
|
25
|
-
selectedIndex?: number;
|
|
26
|
-
handleInput?(data: string): void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
30
|
export interface PackageConfigRow {
|
|
30
31
|
id: string;
|
|
31
32
|
extensionPath: string;
|
|
@@ -36,16 +37,6 @@ export interface PackageConfigRow {
|
|
|
36
37
|
|
|
37
38
|
type ConfigurePanelAction = { type: "cancel" } | { type: "save" };
|
|
38
39
|
|
|
39
|
-
function getSelectedIndex(settingsList: unknown): number | undefined {
|
|
40
|
-
if (settingsList && typeof settingsList === "object") {
|
|
41
|
-
const selectable = settingsList as SelectableList;
|
|
42
|
-
if (typeof selectable.selectedIndex === "number") {
|
|
43
|
-
return selectable.selectedIndex;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
40
|
export async function buildPackageConfigRows(
|
|
50
41
|
entries: PackageExtensionEntry[]
|
|
51
42
|
): Promise<PackageConfigRow[]> {
|
|
@@ -130,99 +121,116 @@ async function showConfigurePanel(
|
|
|
130
121
|
rows: PackageConfigRow[],
|
|
131
122
|
staged: Map<string, State>,
|
|
132
123
|
ctx: ExtensionCommandContext
|
|
133
|
-
): Promise<ConfigurePanelAction> {
|
|
134
|
-
return ctx
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
new Text(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (!row || !row.available) return;
|
|
163
|
-
|
|
164
|
-
const state = newValue as State;
|
|
165
|
-
staged.set(id, state);
|
|
166
|
-
|
|
167
|
-
const settingsItem = settingsItems.find((item) => item.id === id);
|
|
168
|
-
if (settingsItem) {
|
|
124
|
+
): Promise<ConfigurePanelAction | undefined> {
|
|
125
|
+
return runCustomUI(ctx, "Package extension configuration", () =>
|
|
126
|
+
ctx.ui.custom<ConfigurePanelAction>((tui, theme, _keybindings, done) => {
|
|
127
|
+
const container = new Container();
|
|
128
|
+
const titleText = new Text("", 2, 0);
|
|
129
|
+
const subtitleText = new Text("", 2, 0);
|
|
130
|
+
const footerText = new Text("", 2, 0);
|
|
131
|
+
|
|
132
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
133
|
+
container.addChild(titleText);
|
|
134
|
+
container.addChild(subtitleText);
|
|
135
|
+
container.addChild(new Spacer(1));
|
|
136
|
+
|
|
137
|
+
const settingsItems = buildSettingItems(rows, staged, pkg, theme);
|
|
138
|
+
const rowById = new Map(rows.map((row) => [row.id, row]));
|
|
139
|
+
const syncThemedContent = (): void => {
|
|
140
|
+
titleText.setText(theme.fg("accent", theme.bold(`Configure extensions: ${pkg.name}`)));
|
|
141
|
+
subtitleText.setText(
|
|
142
|
+
theme.fg(
|
|
143
|
+
"muted",
|
|
144
|
+
`${rows.length} extension path${rows.length === 1 ? "" : "s"} • Space/Enter toggle • S save • Esc cancel`
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
footerText.setText(theme.fg("dim", "↑↓ Navigate | Space/Enter Toggle | S Save | Esc Back"));
|
|
148
|
+
|
|
149
|
+
for (const settingsItem of settingsItems) {
|
|
150
|
+
const row = rowById.get(settingsItem.id);
|
|
151
|
+
if (!row) continue;
|
|
152
|
+
const currentState = staged.get(row.id) ?? row.originalState;
|
|
169
153
|
settingsItem.label = formatConfigRowLabel(
|
|
170
154
|
row,
|
|
171
|
-
|
|
155
|
+
currentState,
|
|
172
156
|
pkg,
|
|
173
157
|
theme,
|
|
174
|
-
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
tui.requestRender();
|
|
179
|
-
},
|
|
180
|
-
() => done({ type: "cancel" }),
|
|
181
|
-
{ enableSearch: rows.length > UI.searchThreshold }
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
container.addChild(settingsList);
|
|
185
|
-
container.addChild(new Spacer(1));
|
|
186
|
-
container.addChild(
|
|
187
|
-
new Text(theme.fg("dim", "↑↓ Navigate | Space/Enter Toggle | S Save | Esc Back"), 2, 0)
|
|
188
|
-
);
|
|
189
|
-
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
190
|
-
|
|
191
|
-
return {
|
|
192
|
-
render(width: number) {
|
|
193
|
-
return container.render(width);
|
|
194
|
-
},
|
|
195
|
-
invalidate() {
|
|
196
|
-
container.invalidate();
|
|
197
|
-
},
|
|
198
|
-
handleInput(data: string) {
|
|
199
|
-
if (matchesKey(data, Key.ctrl("s")) || data === "s" || data === "S") {
|
|
200
|
-
done({ type: "save" });
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const selectedIndex = getSelectedIndex(settingsList) ?? 0;
|
|
205
|
-
const selectedId = settingsItems[selectedIndex]?.id ?? settingsItems[0]?.id;
|
|
206
|
-
const selectedRow = selectedId ? rowById.get(selectedId) : undefined;
|
|
207
|
-
|
|
208
|
-
if (
|
|
209
|
-
selectedRow &&
|
|
210
|
-
!selectedRow.available &&
|
|
211
|
-
(data === " " || data === "\r" || data === "\n")
|
|
212
|
-
) {
|
|
213
|
-
notify(
|
|
214
|
-
ctx,
|
|
215
|
-
`${selectedRow.extensionPath} is missing on disk and cannot be toggled.`,
|
|
216
|
-
"warning"
|
|
158
|
+
currentState !== row.originalState
|
|
217
159
|
);
|
|
218
|
-
return;
|
|
219
160
|
}
|
|
161
|
+
};
|
|
162
|
+
syncThemedContent();
|
|
163
|
+
|
|
164
|
+
const settingsList = new SettingsList(
|
|
165
|
+
settingsItems,
|
|
166
|
+
Math.min(rows.length + 2, UI.maxListHeight),
|
|
167
|
+
getSettingsListTheme(),
|
|
168
|
+
(id: string, newValue: string) => {
|
|
169
|
+
const row = rowById.get(id);
|
|
170
|
+
if (!row || !row.available) return;
|
|
171
|
+
|
|
172
|
+
const state = newValue as State;
|
|
173
|
+
staged.set(id, state);
|
|
174
|
+
|
|
175
|
+
const settingsItem = settingsItems.find((item) => item.id === id);
|
|
176
|
+
if (settingsItem) {
|
|
177
|
+
settingsItem.label = formatConfigRowLabel(
|
|
178
|
+
row,
|
|
179
|
+
state,
|
|
180
|
+
pkg,
|
|
181
|
+
theme,
|
|
182
|
+
state !== row.originalState
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
tui.requestRender();
|
|
187
|
+
},
|
|
188
|
+
() => done({ type: "cancel" }),
|
|
189
|
+
{ enableSearch: rows.length > UI.searchThreshold }
|
|
190
|
+
);
|
|
220
191
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
192
|
+
container.addChild(settingsList);
|
|
193
|
+
container.addChild(new Spacer(1));
|
|
194
|
+
container.addChild(footerText);
|
|
195
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
render(width: number) {
|
|
199
|
+
return container.render(width);
|
|
200
|
+
},
|
|
201
|
+
invalidate() {
|
|
202
|
+
container.invalidate();
|
|
203
|
+
syncThemedContent();
|
|
204
|
+
},
|
|
205
|
+
handleInput(data: string) {
|
|
206
|
+
if (matchesKey(data, Key.ctrl("s")) || data === "s" || data === "S") {
|
|
207
|
+
done({ type: "save" });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const selectedIndex = getSettingsListSelectedIndex(settingsList) ?? 0;
|
|
212
|
+
const selectedId = settingsItems[selectedIndex]?.id ?? settingsItems[0]?.id;
|
|
213
|
+
const selectedRow = selectedId ? rowById.get(selectedId) : undefined;
|
|
214
|
+
|
|
215
|
+
if (
|
|
216
|
+
selectedRow &&
|
|
217
|
+
!selectedRow.available &&
|
|
218
|
+
(data === " " || data === "\r" || data === "\n")
|
|
219
|
+
) {
|
|
220
|
+
notify(
|
|
221
|
+
ctx,
|
|
222
|
+
`${selectedRow.extensionPath} is missing on disk and cannot be toggled.`,
|
|
223
|
+
"warning"
|
|
224
|
+
);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
settingsList.handleInput?.(data);
|
|
229
|
+
tui.requestRender();
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
})
|
|
233
|
+
);
|
|
226
234
|
}
|
|
227
235
|
|
|
228
236
|
export async function applyPackageExtensionChanges(
|
|
@@ -232,40 +240,50 @@ export async function applyPackageExtensionChanges(
|
|
|
232
240
|
cwd: string,
|
|
233
241
|
pi: ExtensionAPI
|
|
234
242
|
): Promise<{ changed: number; errors: string[] }> {
|
|
235
|
-
let changed = 0;
|
|
236
243
|
const errors: string[] = [];
|
|
244
|
+
const changedRows = [...rows]
|
|
245
|
+
.sort((a, b) => a.extensionPath.localeCompare(b.extensionPath))
|
|
246
|
+
.flatMap((row) => {
|
|
247
|
+
const target = staged.get(row.id) ?? row.originalState;
|
|
248
|
+
if (target === row.originalState) {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
237
251
|
|
|
238
|
-
|
|
252
|
+
if (!row.available) {
|
|
253
|
+
const error = `${row.extensionPath}: extension entrypoint is missing on disk`;
|
|
254
|
+
errors.push(error);
|
|
255
|
+
logExtensionToggle(pi, row.id, row.originalState, target, false, error);
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
239
258
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (target === row.originalState) continue;
|
|
259
|
+
return [{ row, target }];
|
|
260
|
+
});
|
|
243
261
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
logExtensionToggle(pi, row.id, row.originalState, target, false, error);
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
262
|
+
if (changedRows.length === 0) {
|
|
263
|
+
return { changed: 0, errors };
|
|
264
|
+
}
|
|
250
265
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
);
|
|
266
|
+
const result = await applyPackageExtensionStateChanges(
|
|
267
|
+
pkg.source,
|
|
268
|
+
pkg.scope,
|
|
269
|
+
changedRows.map(({ row, target }) => ({ extensionPath: row.extensionPath, target })),
|
|
270
|
+
cwd
|
|
271
|
+
);
|
|
258
272
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
errors.push(`${row.extensionPath}: ${result.error}`);
|
|
273
|
+
if (!result.ok) {
|
|
274
|
+
for (const { row, target } of changedRows) {
|
|
275
|
+
const error = `${row.extensionPath}: ${result.error}`;
|
|
276
|
+
errors.push(error);
|
|
264
277
|
logExtensionToggle(pi, row.id, row.originalState, target, false, result.error);
|
|
265
278
|
}
|
|
279
|
+
return { changed: 0, errors };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (const { row, target } of changedRows) {
|
|
283
|
+
logExtensionToggle(pi, row.id, row.originalState, target, true);
|
|
266
284
|
}
|
|
267
285
|
|
|
268
|
-
return { changed, errors };
|
|
286
|
+
return { changed: changedRows.length, errors };
|
|
269
287
|
}
|
|
270
288
|
|
|
271
289
|
async function promptRestartForPackageConfig(ctx: ExtensionCommandContext): Promise<boolean> {
|
|
@@ -302,6 +320,16 @@ export async function configurePackageExtensions(
|
|
|
302
320
|
ctx: ExtensionCommandContext,
|
|
303
321
|
pi: ExtensionAPI
|
|
304
322
|
): Promise<{ changed: number; reloaded: boolean }> {
|
|
323
|
+
if (!requireCustomUI(ctx, "Package extension configuration")) {
|
|
324
|
+
return { changed: 0, reloaded: false };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const validation = await validatePackageExtensionSettings(pkg.scope, ctx.cwd);
|
|
328
|
+
if (!validation.ok) {
|
|
329
|
+
notify(ctx, validation.error, "error");
|
|
330
|
+
return { changed: 0, reloaded: false };
|
|
331
|
+
}
|
|
332
|
+
|
|
305
333
|
const discovered = await discoverPackageExtensions([pkg], ctx.cwd);
|
|
306
334
|
const rows = await buildPackageConfigRows(discovered);
|
|
307
335
|
|
|
@@ -314,6 +342,9 @@ export async function configurePackageExtensions(
|
|
|
314
342
|
|
|
315
343
|
while (true) {
|
|
316
344
|
const action = await showConfigurePanel(pkg, rows, staged, ctx);
|
|
345
|
+
if (!action) {
|
|
346
|
+
return { changed: 0, reloaded: false };
|
|
347
|
+
}
|
|
317
348
|
|
|
318
349
|
if (action.type === "cancel") {
|
|
319
350
|
const pending = getPendingChangeCount(rows, staged);
|
package/src/ui/remote.ts
CHANGED
|
@@ -15,7 +15,9 @@ import {
|
|
|
15
15
|
isCacheValid,
|
|
16
16
|
} from "../packages/discovery.js";
|
|
17
17
|
import { installPackage, installPackageLocally } from "../packages/install.js";
|
|
18
|
+
import { execNpm } from "../utils/npm-exec.js";
|
|
18
19
|
import { notify } from "../utils/notify.js";
|
|
20
|
+
import { requireCustomUI, runCustomUI } from "../utils/mode.js";
|
|
19
21
|
|
|
20
22
|
interface PackageInfoCacheEntry {
|
|
21
23
|
timestamp: number;
|
|
@@ -64,8 +66,9 @@ class PackageInfoCache {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
set(name: string, entry: Omit<PackageInfoCacheEntry, "timestamp">): void {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
if (this.cache.has(name)) {
|
|
70
|
+
this.cache.delete(name);
|
|
71
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
69
72
|
const firstKey = this.cache.keys().next().value;
|
|
70
73
|
if (firstKey) {
|
|
71
74
|
this.cache.delete(firstKey);
|
|
@@ -81,10 +84,6 @@ class PackageInfoCache {
|
|
|
81
84
|
clear(): void {
|
|
82
85
|
this.cache.clear();
|
|
83
86
|
}
|
|
84
|
-
|
|
85
|
-
get size(): number {
|
|
86
|
-
return this.cache.size;
|
|
87
|
-
}
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
// Global LRU cache instance
|
|
@@ -93,6 +92,10 @@ const packageInfoCache = new PackageInfoCache(
|
|
|
93
92
|
CACHE_LIMITS.packageInfoTTL
|
|
94
93
|
);
|
|
95
94
|
|
|
95
|
+
export function clearRemotePackageInfoCache(): void {
|
|
96
|
+
packageInfoCache.clear();
|
|
97
|
+
}
|
|
98
|
+
|
|
96
99
|
const REMOTE_MENU_CHOICES = {
|
|
97
100
|
browse: "🔍 Browse pi packages",
|
|
98
101
|
search: "🔎 Search packages",
|
|
@@ -143,9 +146,8 @@ async function buildPackageInfoText(
|
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
const [infoRes, weeklyDownloads] = await Promise.all([
|
|
146
|
-
pi
|
|
149
|
+
execNpm(pi, ["view", packageName, "--json"], ctx, {
|
|
147
150
|
timeout: TIMEOUTS.npmView,
|
|
148
|
-
cwd: ctx.cwd,
|
|
149
151
|
}),
|
|
150
152
|
fetchWeeklyDownloads(packageName),
|
|
151
153
|
]);
|
|
@@ -269,50 +271,63 @@ async function selectBrowseAction(
|
|
|
269
271
|
items.push({ value: "nav:refresh", label: "🔄 Refresh search" });
|
|
270
272
|
items.push({ value: "nav:menu", label: "← Back to menu" });
|
|
271
273
|
|
|
272
|
-
return ctx
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
274
|
+
return runCustomUI(ctx, "Remote package browsing", () =>
|
|
275
|
+
ctx.ui.custom<BrowseAction>((tui, theme, _keybindings, done) => {
|
|
276
|
+
const container = new Container();
|
|
277
|
+
const title = new Text("", 1, 0);
|
|
278
|
+
const footer = new Text("", 1, 0);
|
|
279
|
+
const syncThemedContent = (): void => {
|
|
280
|
+
title.setText(theme.fg("accent", theme.bold(titleText)));
|
|
281
|
+
footer.setText(theme.fg("dim", "↑↓ wraps • enter select • esc cancel"));
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
285
|
+
container.addChild(title);
|
|
286
|
+
|
|
287
|
+
const selectList = new SelectList(items, Math.min(items.length, 12), {
|
|
288
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
289
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
290
|
+
description: (t) => theme.fg("muted", t),
|
|
291
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
292
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
selectList.onSelect = (item) => {
|
|
296
|
+
if (item.value === "nav:prev") {
|
|
297
|
+
done({ type: "prev" });
|
|
298
|
+
} else if (item.value === "nav:next") {
|
|
299
|
+
done({ type: "next" });
|
|
300
|
+
} else if (item.value === "nav:refresh") {
|
|
301
|
+
done({ type: "refresh" });
|
|
302
|
+
} else if (item.value === "nav:menu") {
|
|
303
|
+
done({ type: "menu" });
|
|
304
|
+
} else if (item.value.startsWith("pkg:")) {
|
|
305
|
+
done({ type: "package", name: item.value.slice(4) });
|
|
306
|
+
} else {
|
|
307
|
+
done({ type: "cancel" });
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
selectList.onCancel = () => done({ type: "cancel" });
|
|
312
|
+
|
|
313
|
+
syncThemedContent();
|
|
314
|
+
container.addChild(selectList);
|
|
315
|
+
container.addChild(footer);
|
|
316
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
render: (w: number) => container.render(w),
|
|
320
|
+
invalidate: () => {
|
|
321
|
+
container.invalidate();
|
|
322
|
+
syncThemedContent();
|
|
323
|
+
},
|
|
324
|
+
handleInput: (data: string) => {
|
|
325
|
+
selectList.handleInput(data);
|
|
326
|
+
tui.requestRender();
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
})
|
|
330
|
+
);
|
|
316
331
|
}
|
|
317
332
|
|
|
318
333
|
export async function browseRemotePackages(
|
|
@@ -321,6 +336,16 @@ export async function browseRemotePackages(
|
|
|
321
336
|
pi: ExtensionAPI,
|
|
322
337
|
offset = 0
|
|
323
338
|
): Promise<void> {
|
|
339
|
+
if (
|
|
340
|
+
!requireCustomUI(
|
|
341
|
+
ctx,
|
|
342
|
+
"Remote package browsing",
|
|
343
|
+
"Use `/extensions install <source>` to install directly outside the full interactive TUI."
|
|
344
|
+
)
|
|
345
|
+
) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
324
349
|
// Check cache first
|
|
325
350
|
let allPackages: NpmPackage[] = [];
|
|
326
351
|
|
|
@@ -375,8 +400,8 @@ export async function browseRemotePackages(
|
|
|
375
400
|
showLoadMore
|
|
376
401
|
);
|
|
377
402
|
|
|
378
|
-
if (!result) {
|
|
379
|
-
return;
|
|
403
|
+
if (!result || result.type === "cancel") {
|
|
404
|
+
return;
|
|
380
405
|
}
|
|
381
406
|
|
|
382
407
|
// Handle result
|