inup 1.5.4 → 1.5.6
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 +24 -10
- package/dist/cli.js +20 -22
- package/dist/config/constants.js +4 -2
- package/dist/config/package-meta.js +10 -0
- package/dist/config/project-config.js +6 -1
- package/dist/core/package-detector.js +8 -4
- package/dist/core/upgrade-runner.js +7 -10
- package/dist/core/upgrader.js +6 -8
- package/dist/features/changelog/clients/github-client.js +5 -6
- package/dist/features/changelog/services/changelog-service.js +2 -4
- package/dist/features/changelog/services/package-metadata-service.js +6 -11
- package/dist/features/changelog/services/release-notes-service.js +4 -2
- package/dist/features/debug/services/performance-tracker.js +6 -8
- package/dist/index.js +1 -2
- package/dist/interactive-ui.js +30 -587
- package/dist/services/background-audit.js +3 -5
- package/dist/services/cache-manager.js +5 -7
- package/dist/services/http/inflight.js +22 -0
- package/dist/services/http/retry.js +30 -0
- package/dist/services/jsdelivr/client.js +191 -0
- package/dist/services/jsdelivr/manifest.js +136 -0
- package/dist/services/jsdelivr-registry.js +6 -392
- package/dist/services/npm-registry.js +12 -81
- package/dist/services/persistent-cache.js +8 -13
- package/dist/types/domain.js +3 -0
- package/dist/types/streaming.js +3 -0
- package/dist/types/ui.js +3 -0
- package/dist/types.js +17 -0
- package/dist/ui/controllers/package-info-modal-controller.js +22 -31
- package/dist/ui/controllers/vulnerability-audit-controller.js +17 -8
- package/dist/ui/index.js +3 -0
- package/dist/ui/input-handler.js +62 -77
- package/dist/ui/keymap.js +208 -0
- package/dist/ui/modal/package-info-sections/release-notes.js +161 -0
- package/dist/ui/modal/package-info-sections/sections.js +150 -0
- package/dist/ui/modal/package-info-sections/text.js +75 -0
- package/dist/ui/modal/package-info-sections.js +15 -368
- package/dist/ui/modal/package-info.js +1 -1
- package/dist/ui/renderer/help-modal.js +75 -0
- package/dist/ui/renderer/index.js +2 -5
- package/dist/ui/renderer/package-list/interface.js +184 -0
- package/dist/ui/renderer/package-list/rows.js +172 -0
- package/dist/ui/renderer/package-list.js +15 -462
- package/dist/ui/session/action-dispatcher.js +233 -0
- package/dist/ui/session/index.js +13 -0
- package/dist/ui/session/interactive-session.js +280 -0
- package/dist/ui/session/selection-state-builder.js +144 -0
- package/dist/ui/state/filter-manager.js +17 -6
- package/dist/ui/state/modal-manager.js +35 -0
- package/dist/ui/state/navigation-manager.js +33 -4
- package/dist/ui/state/state-manager.js +68 -3
- package/dist/ui/state/theme-manager.js +1 -0
- package/dist/ui/themes-colors.js +8 -0
- package/dist/ui/themes.js +11 -19
- package/dist/ui/utils/cursor.js +3 -4
- package/dist/ui/utils/index.js +6 -1
- package/dist/ui/utils/text.js +3 -2
- package/dist/ui/utils/version.js +60 -86
- package/dist/utils/config.js +13 -1
- package/dist/utils/filesystem/io.js +87 -0
- package/dist/utils/filesystem/paths.js +19 -0
- package/dist/utils/filesystem/scan.js +205 -0
- package/dist/utils/filesystem.js +17 -335
- package/dist/utils/version.js +31 -1
- package/package.json +10 -9
|
@@ -38,12 +38,10 @@ const semver = __importStar(require("semver"));
|
|
|
38
38
|
const services_1 = require("../../services");
|
|
39
39
|
const RELEASE_NOTES_LOAD_DEBOUNCE_MS = 120;
|
|
40
40
|
class PackageInfoModalController {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.resolveDebouncedLoad = null;
|
|
46
|
-
}
|
|
41
|
+
abortController = null;
|
|
42
|
+
pendingReleaseNotesVersion = null;
|
|
43
|
+
releaseNotesDebounceTimer = null;
|
|
44
|
+
resolveDebouncedLoad = null;
|
|
47
45
|
/**
|
|
48
46
|
* Cancel any in-flight hydrate/release-notes fetches.
|
|
49
47
|
* Safe to call multiple times or when nothing is in flight.
|
|
@@ -68,32 +66,11 @@ class PackageInfoModalController {
|
|
|
68
66
|
if (!metadata) {
|
|
69
67
|
return null;
|
|
70
68
|
}
|
|
71
|
-
const result = {
|
|
72
|
-
description: metadata.description,
|
|
73
|
-
homepage: metadata.homepage,
|
|
74
|
-
repository: metadata.releaseNotes,
|
|
75
|
-
weeklyDownloads: metadata.weeklyDownloads,
|
|
76
|
-
author: metadata.author,
|
|
77
|
-
license: metadata.license,
|
|
78
|
-
};
|
|
79
|
-
state.description = result.description;
|
|
80
|
-
state.homepage = result.homepage;
|
|
81
|
-
state.repository = result.repository;
|
|
82
|
-
state.weeklyDownloads = result.weeklyDownloads;
|
|
83
|
-
state.author = result.author;
|
|
84
|
-
state.license = result.license;
|
|
85
69
|
// Compute the version range for release notes
|
|
86
70
|
const targetVersion = state.selectedOption === 'range' ? state.rangeVersion : state.latestVersion;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
else {
|
|
91
|
-
// No allVersions available — just show the target version
|
|
92
|
-
state.releaseNotesVersions = [targetVersion];
|
|
93
|
-
}
|
|
94
|
-
state.releaseNotesLoaded = new Map();
|
|
95
|
-
state.releaseNotesViewIndex = 0;
|
|
96
|
-
state.releaseNotesLoadingVersion = undefined;
|
|
71
|
+
const releaseNotesVersions = state.allVersions && state.allVersions.length > 0
|
|
72
|
+
? this.buildReleaseNotesVersionQueue(state.allVersions, state.currentVersion, targetVersion)
|
|
73
|
+
: [targetVersion];
|
|
97
74
|
this.pendingReleaseNotesVersion = null;
|
|
98
75
|
if (this.releaseNotesDebounceTimer) {
|
|
99
76
|
clearTimeout(this.releaseNotesDebounceTimer);
|
|
@@ -101,7 +78,21 @@ class PackageInfoModalController {
|
|
|
101
78
|
}
|
|
102
79
|
this.resolveDebouncedLoad?.(false);
|
|
103
80
|
this.resolveDebouncedLoad = null;
|
|
104
|
-
return
|
|
81
|
+
return {
|
|
82
|
+
name: state.name,
|
|
83
|
+
patch: {
|
|
84
|
+
description: metadata.description,
|
|
85
|
+
homepage: metadata.homepage,
|
|
86
|
+
repository: metadata.releaseNotes,
|
|
87
|
+
weeklyDownloads: metadata.weeklyDownloads,
|
|
88
|
+
author: metadata.author,
|
|
89
|
+
license: metadata.license,
|
|
90
|
+
releaseNotesVersions,
|
|
91
|
+
releaseNotesLoaded: new Map(),
|
|
92
|
+
releaseNotesViewIndex: 0,
|
|
93
|
+
releaseNotesLoadingVersion: undefined,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
105
96
|
}
|
|
106
97
|
/**
|
|
107
98
|
* Load release notes for a specific version by index.
|
|
@@ -4,11 +4,9 @@ exports.VulnerabilityAuditController = void 0;
|
|
|
4
4
|
const services_1 = require("../../services");
|
|
5
5
|
const vulnerability_1 = require("../presenters/vulnerability");
|
|
6
6
|
class VulnerabilityAuditController {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
this.drainPromise = null;
|
|
11
|
-
}
|
|
7
|
+
tracker = new services_1.BackgroundAuditTracker();
|
|
8
|
+
cache = new Map();
|
|
9
|
+
drainPromise = null;
|
|
12
10
|
getCachedSummary(packageName, currentVersionSpecifier, type) {
|
|
13
11
|
return this.cache.get(this.getCacheKey(packageName, currentVersionSpecifier, type));
|
|
14
12
|
}
|
|
@@ -25,16 +23,18 @@ class VulnerabilityAuditController {
|
|
|
25
23
|
this.drain(selectionStates, onUpdate);
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
|
-
drain(selectionStates, onUpdate) {
|
|
26
|
+
async drain(selectionStates, onUpdate) {
|
|
29
27
|
if (this.drainPromise) {
|
|
30
|
-
return;
|
|
28
|
+
return [];
|
|
31
29
|
}
|
|
30
|
+
const allUpdates = [];
|
|
32
31
|
this.drainPromise = (async () => {
|
|
33
32
|
while (true) {
|
|
34
33
|
const batch = this.tracker.reserveNextBatch(20);
|
|
35
34
|
if (batch.packageNames.length === 0) {
|
|
36
35
|
break;
|
|
37
36
|
}
|
|
37
|
+
const batchUpdates = [];
|
|
38
38
|
try {
|
|
39
39
|
const vulnerabilityData = await (0, services_1.fetchVulnerabilities)(batch.packages);
|
|
40
40
|
const batchNames = new Set(batch.packageNames);
|
|
@@ -55,8 +55,8 @@ class VulnerabilityAuditController {
|
|
|
55
55
|
url: item.url,
|
|
56
56
|
})), vulnerability.highestSeverity);
|
|
57
57
|
const merged = (0, vulnerability_1.mergeVulnerabilitySummary)(state.vulnerability, summary);
|
|
58
|
-
state.vulnerability = merged;
|
|
59
58
|
this.cache.set(this.getCacheKey(state.name, state.currentVersionSpecifier, state.type), merged);
|
|
59
|
+
batchUpdates.push({ name: state.name, patch: { vulnerability: merged } });
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
catch {
|
|
@@ -64,6 +64,13 @@ class VulnerabilityAuditController {
|
|
|
64
64
|
}
|
|
65
65
|
finally {
|
|
66
66
|
this.tracker.markCompleted(batch.packageNames);
|
|
67
|
+
for (const update of batchUpdates) {
|
|
68
|
+
for (const state of selectionStates) {
|
|
69
|
+
if (state.name === update.name)
|
|
70
|
+
Object.assign(state, update.patch);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
allUpdates.push(...batchUpdates);
|
|
67
74
|
onUpdate?.();
|
|
68
75
|
}
|
|
69
76
|
}
|
|
@@ -73,6 +80,8 @@ class VulnerabilityAuditController {
|
|
|
73
80
|
this.drain(selectionStates, onUpdate);
|
|
74
81
|
}
|
|
75
82
|
});
|
|
83
|
+
await this.drainPromise;
|
|
84
|
+
return allUpdates;
|
|
76
85
|
}
|
|
77
86
|
getCacheKey(packageName, currentVersionSpecifier, type) {
|
|
78
87
|
return `${packageName}@${currentVersionSpecifier}@${type}`;
|
package/dist/ui/index.js
CHANGED
|
@@ -21,4 +21,7 @@ __exportStar(require("./modal"), exports);
|
|
|
21
21
|
__exportStar(require("./presenters"), exports);
|
|
22
22
|
__exportStar(require("./controllers"), exports);
|
|
23
23
|
__exportStar(require("./input-handler"), exports);
|
|
24
|
+
__exportStar(require("./keymap"), exports);
|
|
25
|
+
__exportStar(require("./themes"), exports);
|
|
26
|
+
__exportStar(require("./themes-colors"), exports);
|
|
24
27
|
//# sourceMappingURL=index.js.map
|
package/dist/ui/input-handler.js
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConfirmationInputHandler = exports.InputHandler = void 0;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
|
+
const keymap_1 = require("./keymap");
|
|
5
6
|
class InputHandler {
|
|
7
|
+
stateManager;
|
|
8
|
+
onAction;
|
|
9
|
+
onConfirm;
|
|
10
|
+
// Must synchronously release terminal state (alt screen, raw mode, cursor) —
|
|
11
|
+
// it runs immediately before process.exit(0) on Ctrl+C, with no chance to await.
|
|
12
|
+
onCancel;
|
|
6
13
|
constructor(stateManager, onAction, onConfirm, onCancel) {
|
|
7
14
|
this.stateManager = stateManager;
|
|
8
15
|
this.onAction = onAction;
|
|
@@ -15,7 +22,7 @@ class InputHandler {
|
|
|
15
22
|
return;
|
|
16
23
|
}
|
|
17
24
|
if (key && key.ctrl && key.name === 'c') {
|
|
18
|
-
|
|
25
|
+
this.onCancel();
|
|
19
26
|
process.exit(0);
|
|
20
27
|
}
|
|
21
28
|
const uiState = this.stateManager.getUIState();
|
|
@@ -70,6 +77,24 @@ class InputHandler {
|
|
|
70
77
|
}
|
|
71
78
|
return;
|
|
72
79
|
}
|
|
80
|
+
// Handle help overlay input (close or scroll)
|
|
81
|
+
if (uiState.showHelpModal) {
|
|
82
|
+
if (str === '?' || (key && key.name === 'escape')) {
|
|
83
|
+
this.onAction({ type: 'toggle_help_modal' });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (key) {
|
|
87
|
+
if (key.name === 'up') {
|
|
88
|
+
this.onAction({ type: 'scroll_help_modal_up' });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (key.name === 'down') {
|
|
92
|
+
this.onAction({ type: 'scroll_help_modal_down' });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return; // consume all other keys while help is open
|
|
97
|
+
}
|
|
73
98
|
// Handle info modal input (scroll and close)
|
|
74
99
|
if (uiState.showInfoModal) {
|
|
75
100
|
if (key) {
|
|
@@ -111,6 +136,11 @@ class InputHandler {
|
|
|
111
136
|
this.onAction({ type: 'toggle_debug_modal' });
|
|
112
137
|
return;
|
|
113
138
|
}
|
|
139
|
+
// '?' opens the help overlay (outside of filter mode)
|
|
140
|
+
if (str === '?' && !uiState.filterMode) {
|
|
141
|
+
this.onAction({ type: 'toggle_help_modal' });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
114
144
|
// Check for '/' character to handle filter mode (only when not in modal)
|
|
115
145
|
if (str === '/') {
|
|
116
146
|
if (uiState.filterMode) {
|
|
@@ -169,84 +199,36 @@ class InputHandler {
|
|
|
169
199
|
}
|
|
170
200
|
return;
|
|
171
201
|
}
|
|
172
|
-
// Normal mode (not in filter mode)
|
|
173
|
-
|
|
202
|
+
// Normal mode (not in filter mode). Keys resolve through the keymap so that
|
|
203
|
+
// dispatch, the help overlay, the footer, and the README share one source.
|
|
204
|
+
if (!key || !key.name) {
|
|
174
205
|
return;
|
|
175
206
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.onAction({ type: '
|
|
182
|
-
break;
|
|
183
|
-
case 'left':
|
|
184
|
-
this.onAction({ type: 'select_left' });
|
|
185
|
-
break;
|
|
186
|
-
case 'right':
|
|
187
|
-
this.onAction({ type: 'select_right' });
|
|
188
|
-
break;
|
|
189
|
-
case 'return':
|
|
190
|
-
// Check if any packages are selected
|
|
191
|
-
const selectedCount = states.filter((s) => s.loadState === 'ready' && s.selectedOption !== 'none').length;
|
|
192
|
-
if (selectedCount === 0) {
|
|
193
|
-
// Do nothing if no packages selected
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
this.cleanup();
|
|
197
|
-
this.onConfirm(states);
|
|
207
|
+
// Enter confirms the selection (guarding against an empty selection). It runs
|
|
208
|
+
// the cleanup/confirm path directly rather than going through onAction.
|
|
209
|
+
if (key.name === 'return') {
|
|
210
|
+
const selectedCount = states.filter((s) => s.loadState === 'ready' && s.selectedOption !== 'none').length;
|
|
211
|
+
if (selectedCount === 0) {
|
|
212
|
+
this.onAction({ type: 'notify_empty_selection' });
|
|
198
213
|
return;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
case 'p':
|
|
218
|
-
case 'P':
|
|
219
|
-
if (!uiState.showThemeModal && !uiState.filterMode) {
|
|
220
|
-
this.onAction({ type: 'toggle_dep_type_filter', depType: 'peerDependencies' });
|
|
221
|
-
}
|
|
222
|
-
break;
|
|
223
|
-
case 'o':
|
|
224
|
-
case 'O':
|
|
225
|
-
if (!uiState.showThemeModal && !uiState.filterMode) {
|
|
226
|
-
this.onAction({ type: 'toggle_dep_type_filter', depType: 'optionalDependencies' });
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
case 'i':
|
|
230
|
-
case 'I':
|
|
231
|
-
this.onAction({ type: 'toggle_info_modal' });
|
|
232
|
-
break;
|
|
233
|
-
case 's':
|
|
234
|
-
case 'S':
|
|
235
|
-
if (!uiState.showThemeModal) {
|
|
236
|
-
this.onAction({ type: 'trigger_audit_scan' });
|
|
237
|
-
}
|
|
238
|
-
break;
|
|
239
|
-
case 't':
|
|
240
|
-
case 'T':
|
|
241
|
-
this.onAction({ type: 'toggle_theme_modal' });
|
|
242
|
-
break;
|
|
243
|
-
case 'escape':
|
|
244
|
-
if (uiState.filterQuery) {
|
|
245
|
-
// Clear filter if one is applied
|
|
246
|
-
this.onAction({ type: 'exit_filter_mode', clearQuery: true });
|
|
247
|
-
}
|
|
248
|
-
// Otherwise do nothing - Escape no longer exits the CLI
|
|
249
|
-
break;
|
|
214
|
+
}
|
|
215
|
+
this.cleanup();
|
|
216
|
+
this.onConfirm(states);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Escape clears an active search filter; otherwise it is a no-op (it no
|
|
220
|
+
// longer exits the CLI).
|
|
221
|
+
if (key.name === 'escape') {
|
|
222
|
+
if (uiState.filterQuery) {
|
|
223
|
+
this.onAction({ type: 'exit_filter_mode', clearQuery: true });
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Shifted letters become uppercase tokens so the vim pair g/G stays distinct.
|
|
228
|
+
const token = key.shift && /^[a-z]$/.test(key.name) ? key.name.toUpperCase() : key.name;
|
|
229
|
+
const binding = (0, keymap_1.findBinding)(token);
|
|
230
|
+
if (binding?.action) {
|
|
231
|
+
this.onAction(binding.action);
|
|
250
232
|
}
|
|
251
233
|
}
|
|
252
234
|
handleResize(height) {
|
|
@@ -258,6 +240,7 @@ class InputHandler {
|
|
|
258
240
|
}
|
|
259
241
|
exports.InputHandler = InputHandler;
|
|
260
242
|
class ConfirmationInputHandler {
|
|
243
|
+
onConfirm;
|
|
261
244
|
constructor(onConfirm) {
|
|
262
245
|
this.onConfirm = onConfirm;
|
|
263
246
|
}
|
|
@@ -267,7 +250,9 @@ class ConfirmationInputHandler {
|
|
|
267
250
|
return;
|
|
268
251
|
}
|
|
269
252
|
if (key && key.ctrl && key.name === 'c') {
|
|
270
|
-
|
|
253
|
+
// onConfirm runs the normal cleanup path (cursor show, raw-mode off, listener
|
|
254
|
+
// removal). The 'exit' listener registered by confirmUpgrade is a final backstop.
|
|
255
|
+
this.onConfirm(false);
|
|
271
256
|
process.exit(0);
|
|
272
257
|
}
|
|
273
258
|
if (!key) {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KEY_BINDINGS = exports.KEY_GROUPS = void 0;
|
|
4
|
+
exports.findBinding = findBinding;
|
|
5
|
+
exports.getFooterHints = getFooterHints;
|
|
6
|
+
exports.getHelpGroups = getHelpGroups;
|
|
7
|
+
exports.renderReadmeKeyTable = renderReadmeKeyTable;
|
|
8
|
+
/** Group display order for the help overlay and README. */
|
|
9
|
+
exports.KEY_GROUPS = ['Navigation', 'Selection', 'Filtering', 'View'];
|
|
10
|
+
exports.KEY_BINDINGS = [
|
|
11
|
+
// Navigation
|
|
12
|
+
{
|
|
13
|
+
tokens: ['up', 'k'],
|
|
14
|
+
action: { type: 'navigate_up' },
|
|
15
|
+
displayKeys: '↑ / k',
|
|
16
|
+
help: 'Move up',
|
|
17
|
+
group: 'Navigation',
|
|
18
|
+
footer: { keyLabel: '↑/↓', label: 'Move' },
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
tokens: ['down', 'j'],
|
|
22
|
+
action: { type: 'navigate_down' },
|
|
23
|
+
displayKeys: '↓ / j',
|
|
24
|
+
help: 'Move down',
|
|
25
|
+
group: 'Navigation',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
tokens: ['g'],
|
|
29
|
+
action: { type: 'navigate_top' },
|
|
30
|
+
displayKeys: 'g',
|
|
31
|
+
help: 'Jump to the first package',
|
|
32
|
+
group: 'Navigation',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
tokens: ['G'],
|
|
36
|
+
action: { type: 'navigate_bottom' },
|
|
37
|
+
displayKeys: 'G',
|
|
38
|
+
help: 'Jump to the last package',
|
|
39
|
+
group: 'Navigation',
|
|
40
|
+
},
|
|
41
|
+
// Selection
|
|
42
|
+
{
|
|
43
|
+
tokens: ['left'],
|
|
44
|
+
action: { type: 'select_left' },
|
|
45
|
+
displayKeys: '←',
|
|
46
|
+
help: 'Cycle selection left (none → range → latest)',
|
|
47
|
+
group: 'Selection',
|
|
48
|
+
footer: { keyLabel: '←/→', label: 'Select' },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
tokens: ['right'],
|
|
52
|
+
action: { type: 'select_right' },
|
|
53
|
+
displayKeys: '→',
|
|
54
|
+
help: 'Cycle selection right (none → range → latest)',
|
|
55
|
+
group: 'Selection',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
tokens: ['space'],
|
|
59
|
+
action: { type: 'toggle_selection' },
|
|
60
|
+
displayKeys: 'Space',
|
|
61
|
+
help: 'Toggle the current package on/off',
|
|
62
|
+
group: 'Selection',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
tokens: ['m'],
|
|
66
|
+
action: { type: 'bulk_select_minor' },
|
|
67
|
+
displayKeys: 'm',
|
|
68
|
+
help: 'Select all minor/patch updates',
|
|
69
|
+
group: 'Selection',
|
|
70
|
+
footer: { keyLabel: 'M', label: 'Minor' },
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
tokens: ['l'],
|
|
74
|
+
action: { type: 'bulk_select_latest' },
|
|
75
|
+
displayKeys: 'l',
|
|
76
|
+
help: 'Select all latest updates (including major)',
|
|
77
|
+
group: 'Selection',
|
|
78
|
+
footer: { keyLabel: 'L', label: 'All' },
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
tokens: ['u'],
|
|
82
|
+
action: { type: 'bulk_unselect_all' },
|
|
83
|
+
displayKeys: 'u',
|
|
84
|
+
help: 'Unselect all packages',
|
|
85
|
+
group: 'Selection',
|
|
86
|
+
footer: { keyLabel: 'U', label: 'None' },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
displayKeys: 'Enter',
|
|
90
|
+
help: 'Confirm selection and upgrade',
|
|
91
|
+
group: 'Selection',
|
|
92
|
+
},
|
|
93
|
+
// Filtering
|
|
94
|
+
{
|
|
95
|
+
displayKeys: '/',
|
|
96
|
+
help: 'Search packages by name',
|
|
97
|
+
group: 'Filtering',
|
|
98
|
+
footer: { keyLabel: '/', label: 'Search' },
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
tokens: ['d'],
|
|
102
|
+
action: { type: 'toggle_dep_type_filter', depType: 'devDependencies' },
|
|
103
|
+
displayKeys: 'd',
|
|
104
|
+
help: 'Toggle devDependencies',
|
|
105
|
+
group: 'Filtering',
|
|
106
|
+
footer: { keyLabel: 'D/P/O', label: 'Filter' },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
tokens: ['p'],
|
|
110
|
+
action: { type: 'toggle_dep_type_filter', depType: 'peerDependencies' },
|
|
111
|
+
displayKeys: 'p',
|
|
112
|
+
help: 'Toggle peerDependencies',
|
|
113
|
+
group: 'Filtering',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
tokens: ['o'],
|
|
117
|
+
action: { type: 'toggle_dep_type_filter', depType: 'optionalDependencies' },
|
|
118
|
+
displayKeys: 'o',
|
|
119
|
+
help: 'Toggle optionalDependencies',
|
|
120
|
+
group: 'Filtering',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
tokens: ['s'],
|
|
124
|
+
action: { type: 'trigger_audit_scan' },
|
|
125
|
+
displayKeys: 's',
|
|
126
|
+
help: 'Run the vulnerability audit',
|
|
127
|
+
group: 'Filtering',
|
|
128
|
+
footer: { keyLabel: 'S', label: 'Audit' },
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
tokens: ['v'],
|
|
132
|
+
action: { type: 'toggle_vulnerable_filter' },
|
|
133
|
+
displayKeys: 'v',
|
|
134
|
+
help: 'Show only vulnerable packages',
|
|
135
|
+
group: 'Filtering',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
displayKeys: 'Esc',
|
|
139
|
+
help: 'Clear the active search filter',
|
|
140
|
+
group: 'Filtering',
|
|
141
|
+
},
|
|
142
|
+
// View
|
|
143
|
+
{
|
|
144
|
+
tokens: ['i'],
|
|
145
|
+
action: { type: 'toggle_info_modal' },
|
|
146
|
+
displayKeys: 'i',
|
|
147
|
+
help: 'View package details and changelog',
|
|
148
|
+
group: 'View',
|
|
149
|
+
footer: { keyLabel: 'I', label: 'Info' },
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
tokens: ['t'],
|
|
153
|
+
action: { type: 'toggle_theme_modal' },
|
|
154
|
+
displayKeys: 't',
|
|
155
|
+
help: 'Change the color theme',
|
|
156
|
+
group: 'View',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
displayKeys: '?',
|
|
160
|
+
help: 'Show this help',
|
|
161
|
+
group: 'View',
|
|
162
|
+
footer: { keyLabel: '?', label: 'Help' },
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
displayKeys: '!',
|
|
166
|
+
help: 'Show the performance/debug panel',
|
|
167
|
+
group: 'View',
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
/**
|
|
171
|
+
* Resolve a pressed key token to its binding. Tries an exact match first so the
|
|
172
|
+
* case-sensitive vim pair `g`/`G` is honored, then falls back to the lowercase
|
|
173
|
+
* letter so shifted letters (e.g. `M`) still trigger their lowercase binding.
|
|
174
|
+
*/
|
|
175
|
+
function findBinding(token) {
|
|
176
|
+
const exact = exports.KEY_BINDINGS.find((binding) => binding.tokens?.includes(token));
|
|
177
|
+
if (exact)
|
|
178
|
+
return exact;
|
|
179
|
+
if (/^[A-Z]$/.test(token)) {
|
|
180
|
+
const lower = token.toLowerCase();
|
|
181
|
+
return exports.KEY_BINDINGS.find((binding) => binding.tokens?.includes(lower));
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
// Computed once at module load — KEY_BINDINGS is static so these never change.
|
|
186
|
+
const _footerHints = exports.KEY_BINDINGS.flatMap((binding) => binding.footer ? [binding.footer] : []);
|
|
187
|
+
const _helpGroups = exports.KEY_GROUPS.map((group) => ({
|
|
188
|
+
group,
|
|
189
|
+
bindings: exports.KEY_BINDINGS.filter((b) => b.group === group),
|
|
190
|
+
})).filter((entry) => entry.bindings.length > 0);
|
|
191
|
+
/** Curated short hints for the compact footer line, in display order. */
|
|
192
|
+
function getFooterHints() {
|
|
193
|
+
return _footerHints;
|
|
194
|
+
}
|
|
195
|
+
/** Bindings grouped for the `?` help overlay, preserving {@link KEY_GROUPS} order. */
|
|
196
|
+
function getHelpGroups() {
|
|
197
|
+
return _helpGroups;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Render the README "Keyboard Shortcuts" markdown table from the keymap. This is
|
|
201
|
+
* the body placed between the `<!-- KEYS:START -->`/`<!-- KEYS:END -->` markers;
|
|
202
|
+
* a test fails if the README drifts from it (see keymap-readme.test.ts).
|
|
203
|
+
*/
|
|
204
|
+
function renderReadmeKeyTable() {
|
|
205
|
+
const rows = exports.KEY_BINDINGS.map((binding) => `| \`${binding.displayKeys}\` | ${binding.help} |`);
|
|
206
|
+
return ['| Key | Action |', '|-----|--------|', ...rows].join('\n');
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=keymap.js.map
|