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/types/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface NpmPackage {
|
|
|
19
19
|
name: string;
|
|
20
20
|
version?: string | undefined;
|
|
21
21
|
description?: string | undefined;
|
|
22
|
+
author?: string | undefined;
|
|
22
23
|
keywords?: string[] | undefined;
|
|
23
24
|
date?: string | undefined;
|
|
24
25
|
size?: number | undefined; // Package size in bytes
|
|
@@ -46,25 +47,33 @@ export interface PackageExtensionEntry {
|
|
|
46
47
|
state: State;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
export interface
|
|
50
|
-
type: "local"
|
|
50
|
+
export interface LocalUnifiedItem {
|
|
51
|
+
type: "local";
|
|
51
52
|
id: string;
|
|
52
53
|
displayName: string;
|
|
53
54
|
summary: string;
|
|
54
55
|
scope: Scope;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
state: State;
|
|
57
|
+
activePath: string;
|
|
58
|
+
disabledPath: string;
|
|
59
|
+
originalState: State;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface PackageUnifiedItem {
|
|
63
|
+
type: "package";
|
|
64
|
+
id: string;
|
|
65
|
+
displayName: string;
|
|
66
|
+
scope: Scope;
|
|
67
|
+
source: string;
|
|
68
|
+
resolvedPath?: string | undefined;
|
|
62
69
|
version?: string | undefined;
|
|
63
70
|
description?: string | undefined;
|
|
64
71
|
size?: number | undefined; // Package size in bytes
|
|
65
72
|
updateAvailable?: boolean | undefined;
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
export type UnifiedItem = LocalUnifiedItem | PackageUnifiedItem;
|
|
76
|
+
|
|
68
77
|
export interface SearchCache {
|
|
69
78
|
query: string;
|
|
70
79
|
results: NpmPackage[];
|
|
@@ -90,7 +99,7 @@ export type BrowseAction =
|
|
|
90
99
|
| { type: "prev" }
|
|
91
100
|
| { type: "next" }
|
|
92
101
|
| { type: "refresh" }
|
|
102
|
+
| { type: "search"; query: string }
|
|
103
|
+
| { type: "install" }
|
|
93
104
|
| { type: "menu" }
|
|
94
|
-
| { type: "main" }
|
|
95
|
-
| { type: "help" }
|
|
96
105
|
| { type: "cancel" };
|
package/src/ui/async-task.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
DynamicBorder,
|
|
3
|
+
type ExtensionCommandContext,
|
|
4
|
+
type ExtensionContext,
|
|
5
|
+
type Theme,
|
|
5
6
|
} from "@mariozechner/pi-coding-agent";
|
|
6
|
-
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
|
7
7
|
import { CancellableLoader, Container, Loader, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
|
|
8
8
|
import { hasCustomUI } from "../utils/mode.js";
|
|
9
9
|
|
|
@@ -12,6 +12,8 @@ type AnyContext = ExtensionCommandContext | ExtensionContext;
|
|
|
12
12
|
const TASK_ABORTED = Symbol("task-aborted");
|
|
13
13
|
const TASK_FAILED = Symbol("task-failed");
|
|
14
14
|
|
|
15
|
+
type TaskSuccess<T> = { type: "ok"; value: T };
|
|
16
|
+
|
|
15
17
|
export interface TaskControls {
|
|
16
18
|
signal: AbortSignal;
|
|
17
19
|
setMessage: (message: string) => void;
|
|
@@ -21,6 +23,7 @@ interface LoaderConfig {
|
|
|
21
23
|
title: string;
|
|
22
24
|
message: string;
|
|
23
25
|
cancellable?: boolean;
|
|
26
|
+
fallbackWithoutLoader?: boolean;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
function createLoaderComponent(
|
|
@@ -69,82 +72,115 @@ function createLoaderComponent(
|
|
|
69
72
|
return { container, loader, signal };
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
function runTaskWithoutLoader<T>(task: (controls: TaskControls) => Promise<T>): Promise<T> {
|
|
76
|
+
return Promise.resolve().then(() =>
|
|
77
|
+
task({
|
|
78
|
+
signal: new AbortController().signal,
|
|
79
|
+
setMessage: () => undefined,
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
export async function runTaskWithLoader<T>(
|
|
73
85
|
ctx: AnyContext,
|
|
74
86
|
config: LoaderConfig,
|
|
75
87
|
task: (controls: TaskControls) => Promise<T>
|
|
76
88
|
): Promise<T | undefined> {
|
|
77
89
|
if (!hasCustomUI(ctx)) {
|
|
78
|
-
return task
|
|
79
|
-
signal: new AbortController().signal,
|
|
80
|
-
setMessage: () => undefined,
|
|
81
|
-
});
|
|
90
|
+
return runTaskWithoutLoader(task);
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
let taskError: unknown;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
let startedTask: Promise<T> | undefined;
|
|
95
|
+
let cleanupStartedTaskUI: (() => void) | undefined;
|
|
96
|
+
|
|
97
|
+
const result = await ctx.ui.custom<
|
|
98
|
+
TaskSuccess<T> | typeof TASK_ABORTED | typeof TASK_FAILED | undefined
|
|
99
|
+
>((tui, theme, _keybindings, done) => {
|
|
100
|
+
let finished = false;
|
|
101
|
+
const finish = (
|
|
102
|
+
value: TaskSuccess<T> | typeof TASK_ABORTED | typeof TASK_FAILED | undefined
|
|
103
|
+
): void => {
|
|
104
|
+
if (finished) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
finished = true;
|
|
108
|
+
done(value);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const { container, loader, signal } = createLoaderComponent(
|
|
112
|
+
tui,
|
|
113
|
+
theme,
|
|
114
|
+
config.title,
|
|
115
|
+
config.message,
|
|
116
|
+
config.cancellable ?? true,
|
|
117
|
+
() => finish(TASK_ABORTED)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
cleanupStartedTaskUI = () => {
|
|
121
|
+
if (loader instanceof CancellableLoader) {
|
|
122
|
+
loader.dispose();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
loader.stop();
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
startedTask = Promise.resolve().then(() =>
|
|
130
|
+
task({
|
|
107
131
|
signal,
|
|
108
132
|
setMessage: (message) => {
|
|
109
133
|
loader.setMessage(message);
|
|
110
134
|
tui.requestRender();
|
|
111
135
|
},
|
|
112
136
|
})
|
|
113
|
-
|
|
114
|
-
.catch((error) => {
|
|
115
|
-
if (signal.aborted) {
|
|
116
|
-
finish(TASK_ABORTED);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
taskError = error;
|
|
121
|
-
finish(TASK_FAILED);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
render(width: number) {
|
|
126
|
-
return container.render(width);
|
|
127
|
-
},
|
|
128
|
-
invalidate() {
|
|
129
|
-
container.invalidate();
|
|
130
|
-
},
|
|
131
|
-
handleInput(data: string) {
|
|
132
|
-
if (loader instanceof CancellableLoader) {
|
|
133
|
-
loader.handleInput(data);
|
|
134
|
-
tui.requestRender();
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
dispose() {
|
|
138
|
-
if (loader instanceof CancellableLoader) {
|
|
139
|
-
loader.dispose();
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
137
|
+
);
|
|
142
138
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
139
|
+
void startedTask
|
|
140
|
+
.then((value) => finish({ type: "ok", value }))
|
|
141
|
+
.catch((error) => {
|
|
142
|
+
if (signal.aborted) {
|
|
143
|
+
finish(TASK_ABORTED);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
taskError = error;
|
|
148
|
+
finish(TASK_FAILED);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
render(width: number) {
|
|
153
|
+
return container.render(width);
|
|
154
|
+
},
|
|
155
|
+
invalidate() {
|
|
156
|
+
container.invalidate();
|
|
157
|
+
},
|
|
158
|
+
handleInput(data: string) {
|
|
159
|
+
if (loader instanceof CancellableLoader) {
|
|
160
|
+
loader.handleInput(data);
|
|
161
|
+
tui.requestRender();
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
dispose() {
|
|
165
|
+
if (loader instanceof CancellableLoader) {
|
|
166
|
+
loader.dispose();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
loader.stop();
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (result === undefined) {
|
|
176
|
+
if (startedTask) {
|
|
177
|
+
return startedTask.finally(() => cleanupStartedTaskUI?.());
|
|
146
178
|
}
|
|
147
|
-
|
|
179
|
+
if (config.fallbackWithoutLoader) {
|
|
180
|
+
return runTaskWithoutLoader(task);
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
148
184
|
|
|
149
185
|
if (result === TASK_ABORTED) {
|
|
150
186
|
return undefined;
|
|
@@ -154,5 +190,5 @@ export async function runTaskWithLoader<T>(
|
|
|
154
190
|
throw taskError;
|
|
155
191
|
}
|
|
156
192
|
|
|
157
|
-
return result;
|
|
193
|
+
return result.value;
|
|
158
194
|
}
|
package/src/ui/footer.ts
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Footer helpers for the unified extension manager UI
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type State, type UnifiedItem } from "../types/index.js";
|
|
5
5
|
|
|
6
6
|
export interface FooterState {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
hasPackages: boolean;
|
|
7
|
+
selectedType?: UnifiedItem["type"];
|
|
8
|
+
pendingChanges: number;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
hasToggleRows: hasLocals,
|
|
20
|
-
hasLocals,
|
|
21
|
-
hasPackages: items.some((i) => i.type === "package"),
|
|
11
|
+
export function buildFooterState(
|
|
12
|
+
staged: Map<string, State>,
|
|
13
|
+
byId: Map<string, UnifiedItem>,
|
|
14
|
+
selectedItem?: UnifiedItem
|
|
15
|
+
): FooterState {
|
|
16
|
+
const state: FooterState = {
|
|
17
|
+
pendingChanges: getPendingToggleChangeCount(staged, byId),
|
|
22
18
|
};
|
|
19
|
+
|
|
20
|
+
if (selectedItem) {
|
|
21
|
+
state.selectedType = selectedItem.type;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return state;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function getPendingToggleChangeCount(
|
|
@@ -41,27 +43,41 @@ export function getPendingToggleChangeCount(
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
|
-
* Build keyboard shortcuts text for the footer.
|
|
46
|
+
* Build contextual keyboard shortcuts text for the footer.
|
|
45
47
|
*/
|
|
46
48
|
export function buildFooterShortcuts(state: FooterState): string {
|
|
47
49
|
const parts: string[] = [];
|
|
48
|
-
parts.push("↑↓ Navigate");
|
|
49
50
|
|
|
50
|
-
if (state.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
if (state.selectedType === "local") {
|
|
52
|
+
parts.push("Space toggle");
|
|
53
|
+
parts.push("Enter/A actions");
|
|
54
|
+
parts.push("V details");
|
|
55
|
+
parts.push("X remove");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (state.selectedType === "package") {
|
|
59
|
+
parts.push("Enter/A actions");
|
|
60
|
+
parts.push("V details");
|
|
61
|
+
parts.push("c configure");
|
|
62
|
+
parts.push("u update");
|
|
63
|
+
parts.push("X remove");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (state.pendingChanges > 0) {
|
|
67
|
+
parts.push(`S save (${state.pendingChanges})`);
|
|
68
|
+
}
|
|
56
69
|
|
|
57
|
-
parts.push("
|
|
58
|
-
parts.push("
|
|
59
|
-
parts.push("
|
|
60
|
-
parts.push("
|
|
61
|
-
parts.push("
|
|
62
|
-
parts.push("
|
|
63
|
-
parts.push("
|
|
64
|
-
parts.push("
|
|
70
|
+
parts.push("/ search");
|
|
71
|
+
parts.push("Tab filters");
|
|
72
|
+
parts.push("1-5 filters");
|
|
73
|
+
parts.push("i install");
|
|
74
|
+
parts.push("f remote search");
|
|
75
|
+
parts.push("U update all");
|
|
76
|
+
parts.push("t auto-update");
|
|
77
|
+
parts.push("P palette");
|
|
78
|
+
parts.push("R browse");
|
|
79
|
+
parts.push("? help");
|
|
80
|
+
parts.push("Esc clear/cancel");
|
|
65
81
|
|
|
66
|
-
return parts.join("
|
|
82
|
+
return parts.join(" · ");
|
|
67
83
|
}
|
package/src/ui/help.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Help display
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import { notify } from "../utils/notify.js";
|
|
5
6
|
|
|
6
7
|
export function showHelp(ctx: ExtensionCommandContext): void {
|
|
7
8
|
const lines = [
|
|
@@ -9,25 +10,33 @@ export function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
9
10
|
"",
|
|
10
11
|
"Unified View:",
|
|
11
12
|
" Local extensions and npm/git packages are displayed together",
|
|
13
|
+
" The list is grouped into Local extensions and Installed packages sections",
|
|
14
|
+
" Rows stay compact; details for the selected item appear below the list",
|
|
12
15
|
" Local extensions show ● enabled / ○ disabled with G/P scope",
|
|
13
|
-
" Packages show
|
|
16
|
+
" Packages show a source-type icon with name@version, scope, and size when known",
|
|
14
17
|
"",
|
|
15
18
|
"Navigation:",
|
|
16
19
|
" ↑↓ Navigate list",
|
|
17
|
-
"
|
|
20
|
+
" PageUp/Down Jump through longer lists",
|
|
21
|
+
" Home/End Jump to top or bottom",
|
|
22
|
+
" Space Toggle selected local extension enabled/disabled",
|
|
18
23
|
" S Save changes to local extensions",
|
|
19
|
-
" Enter/A Open actions for selected
|
|
20
|
-
"
|
|
24
|
+
" Enter/A Open actions for the selected item",
|
|
25
|
+
" / or Ctrl+F Search visible items",
|
|
26
|
+
" Tab/Shift+Tab Cycle filters",
|
|
27
|
+
" 1-5 Quick filters: All / Local / Packages / Updates / Disabled",
|
|
28
|
+
" c Configure selected package extensions (reload after save)",
|
|
21
29
|
" u Update selected package",
|
|
30
|
+
" V View full details for the selected item",
|
|
22
31
|
" X Remove selected item (package or local extension)",
|
|
23
32
|
" i Quick install by source",
|
|
24
|
-
" f
|
|
33
|
+
" f Remote package search",
|
|
25
34
|
" U Update all packages",
|
|
26
35
|
" t Auto-update wizard",
|
|
27
36
|
" P/M Quick actions palette",
|
|
28
37
|
" R Browse remote packages",
|
|
29
38
|
" ?/H Show this help",
|
|
30
|
-
" Esc
|
|
39
|
+
" Esc Clear search or cancel",
|
|
31
40
|
"",
|
|
32
41
|
"Extension Sources:",
|
|
33
42
|
" - ~/.pi/agent/extensions/ (global - G)",
|
|
@@ -49,10 +58,5 @@ export function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
49
58
|
" /extensions auto-update Show or change update schedule",
|
|
50
59
|
];
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
if (ctx.hasUI) {
|
|
54
|
-
ctx.ui.notify(output, "info");
|
|
55
|
-
} else {
|
|
56
|
-
console.log(output);
|
|
57
|
-
}
|
|
61
|
+
notify(ctx, lines.join("\n"), "info");
|
|
58
62
|
}
|
package/src/ui/package-config.ts
CHANGED
|
@@ -1,31 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Package extension configuration panel.
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
DynamicBorder,
|
|
6
|
+
type ExtensionAPI,
|
|
7
|
+
type ExtensionCommandContext,
|
|
8
|
+
getSettingsListTheme,
|
|
9
|
+
type Theme,
|
|
10
|
+
} from "@mariozechner/pi-coding-agent";
|
|
6
11
|
import {
|
|
7
12
|
Container,
|
|
8
13
|
Key,
|
|
9
14
|
matchesKey,
|
|
15
|
+
type SettingItem,
|
|
10
16
|
SettingsList,
|
|
11
17
|
Spacer,
|
|
12
18
|
Text,
|
|
13
|
-
type SettingItem,
|
|
14
19
|
} from "@mariozechner/pi-tui";
|
|
15
|
-
import
|
|
20
|
+
import { UI } from "../constants.js";
|
|
16
21
|
import {
|
|
17
22
|
applyPackageExtensionStateChanges,
|
|
18
23
|
discoverPackageExtensions,
|
|
19
24
|
validatePackageExtensionSettings,
|
|
20
25
|
} from "../packages/extensions.js";
|
|
21
|
-
import {
|
|
26
|
+
import { type InstalledPackage, type PackageExtensionEntry, type State } from "../types/index.js";
|
|
27
|
+
import { fileExists } from "../utils/fs.js";
|
|
22
28
|
import { logExtensionToggle } from "../utils/history.js";
|
|
23
29
|
import { requireCustomUI, runCustomUI } from "../utils/mode.js";
|
|
24
|
-
import {
|
|
30
|
+
import { notify } from "../utils/notify.js";
|
|
25
31
|
import { getPackageSourceKind } from "../utils/package-source.js";
|
|
26
32
|
import { getSettingsListSelectedIndex } from "../utils/settings-list.js";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
33
|
+
import { confirmReload } from "../utils/ui-helpers.js";
|
|
34
|
+
import { runTaskWithLoader } from "./async-task.js";
|
|
29
35
|
import { getChangeMarker, getPackageIcon, getScopeIcon, getStatusIcon } from "./theme.js";
|
|
30
36
|
|
|
31
37
|
export interface PackageConfigRow {
|
|
@@ -168,10 +174,14 @@ async function showConfigurePanel(
|
|
|
168
174
|
getSettingsListTheme(),
|
|
169
175
|
(id: string, newValue: string) => {
|
|
170
176
|
const row = rowById.get(id);
|
|
171
|
-
if (!row
|
|
177
|
+
if (!row?.available) return;
|
|
172
178
|
|
|
173
179
|
const state = newValue as State;
|
|
174
|
-
|
|
180
|
+
if (state === row.originalState) {
|
|
181
|
+
staged.delete(id);
|
|
182
|
+
} else {
|
|
183
|
+
staged.set(id, state);
|
|
184
|
+
}
|
|
175
185
|
|
|
176
186
|
const settingsItem = settingsItems.find((item) => item.id === id);
|
|
177
187
|
if (settingsItem) {
|
|
@@ -286,35 +296,6 @@ export async function applyPackageExtensionChanges(
|
|
|
286
296
|
return { changed: changedRows.length, errors };
|
|
287
297
|
}
|
|
288
298
|
|
|
289
|
-
async function promptRestartForPackageConfig(ctx: ExtensionCommandContext): Promise<boolean> {
|
|
290
|
-
if (!ctx.hasUI) {
|
|
291
|
-
notify(
|
|
292
|
-
ctx,
|
|
293
|
-
"Restart pi to apply package extension configuration changes. /reload may not be enough.",
|
|
294
|
-
"warning"
|
|
295
|
-
);
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const restartNow = await ctx.ui.confirm(
|
|
300
|
-
"Restart Required",
|
|
301
|
-
"Package extension configuration changed.\nA full pi restart is required to apply it.\nExit pi now?"
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
if (!restartNow) {
|
|
305
|
-
notify(
|
|
306
|
-
ctx,
|
|
307
|
-
"Restart pi manually to apply package extension configuration changes. /reload may not be enough.",
|
|
308
|
-
"warning"
|
|
309
|
-
);
|
|
310
|
-
return false;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
notify(ctx, "Shutting down pi. Start it again to apply changes.", "info");
|
|
314
|
-
ctx.shutdown();
|
|
315
|
-
return true;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
299
|
export async function configurePackageExtensions(
|
|
319
300
|
pkg: InstalledPackage,
|
|
320
301
|
ctx: ExtensionCommandContext,
|
|
@@ -393,24 +374,31 @@ export async function configurePackageExtensions(
|
|
|
393
374
|
|
|
394
375
|
const apply = await applyPackageExtensionChanges(rows, staged, pkg, ctx.cwd, pi);
|
|
395
376
|
|
|
377
|
+
if (apply.changed === 0) {
|
|
378
|
+
if (apply.errors.length > 0) {
|
|
379
|
+
notify(
|
|
380
|
+
ctx,
|
|
381
|
+
`Applied ${apply.changed} change(s), ${apply.errors.length} failed.\n${apply.errors.join("\n")}`,
|
|
382
|
+
"warning"
|
|
383
|
+
);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
notify(ctx, "No changes to apply.", "info");
|
|
388
|
+
return { changed: 0, reloaded: false };
|
|
389
|
+
}
|
|
390
|
+
|
|
396
391
|
if (apply.errors.length > 0) {
|
|
397
392
|
notify(
|
|
398
393
|
ctx,
|
|
399
394
|
`Applied ${apply.changed} change(s), ${apply.errors.length} failed.\n${apply.errors.join("\n")}`,
|
|
400
395
|
"warning"
|
|
401
396
|
);
|
|
402
|
-
} else if (apply.changed === 0) {
|
|
403
|
-
notify(ctx, "No changes to apply.", "info");
|
|
404
|
-
return { changed: 0, reloaded: false };
|
|
405
397
|
} else {
|
|
406
398
|
notify(ctx, `Applied ${apply.changed} package extension change(s).`, "info");
|
|
407
399
|
}
|
|
408
400
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const restarted = await promptRestartForPackageConfig(ctx);
|
|
414
|
-
return { changed: apply.changed, reloaded: restarted };
|
|
401
|
+
const reloaded = await confirmReload(ctx, "Package extension configuration changed.");
|
|
402
|
+
return { changed: apply.changed, reloaded };
|
|
415
403
|
}
|
|
416
404
|
}
|