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
package/dist/interactive-ui.js
CHANGED
|
@@ -1,49 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
3
|
exports.InteractiveUI = void 0;
|
|
40
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
-
const semver = __importStar(require("semver"));
|
|
42
4
|
const ui_1 = require("./ui");
|
|
43
5
|
const controllers_1 = require("./ui/controllers");
|
|
44
|
-
const
|
|
45
|
-
const themes_colors_1 = require("./ui/themes-colors");
|
|
46
|
-
const debug_1 = require("./features/debug");
|
|
6
|
+
const session_1 = require("./ui/session");
|
|
47
7
|
const DEFAULT_VULNERABILITY_DISPLAY_OPTIONS = {
|
|
48
8
|
showPeerDependencyVulnerabilities: false,
|
|
49
9
|
showOptionalDependencyVulnerabilities: false,
|
|
@@ -57,9 +17,13 @@ function normalizeVulnerabilityDisplayOptions(options) {
|
|
|
57
17
|
};
|
|
58
18
|
}
|
|
59
19
|
class InteractiveUI {
|
|
20
|
+
renderer;
|
|
21
|
+
packageManager;
|
|
22
|
+
options;
|
|
23
|
+
vulnerabilityAuditController = new controllers_1.VulnerabilityAuditController();
|
|
24
|
+
packageInfoModalController = new controllers_1.PackageInfoModalController();
|
|
25
|
+
refreshView;
|
|
60
26
|
constructor(packageManager, options) {
|
|
61
|
-
this.vulnerabilityAuditController = new controllers_1.VulnerabilityAuditController();
|
|
62
|
-
this.packageInfoModalController = new controllers_1.PackageInfoModalController();
|
|
63
27
|
this.renderer = new ui_1.UIRenderer();
|
|
64
28
|
this.packageManager = packageManager;
|
|
65
29
|
this.options = normalizeVulnerabilityDisplayOptions(options);
|
|
@@ -72,65 +36,14 @@ class InteractiveUI {
|
|
|
72
36
|
if (selectionStates.length === 0) {
|
|
73
37
|
return [];
|
|
74
38
|
}
|
|
75
|
-
const selectedStates = await this.
|
|
76
|
-
return
|
|
39
|
+
const selectedStates = await (0, session_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => { this.refreshView = refresh; });
|
|
40
|
+
return (0, session_1.createUpgradeChoices)(selectedStates);
|
|
77
41
|
}
|
|
78
42
|
createSelectionStates(packages, previousSelections, includeUpToDate = true) {
|
|
79
|
-
|
|
80
|
-
const uniquePackages = this.deduplicatePackages(relevantPackages);
|
|
81
|
-
return Array.from(uniquePackages.values()).map(({ pkg, packageJsonPaths }) => {
|
|
82
|
-
const currentClean = semver.coerce(pkg.currentVersion)?.version || pkg.currentVersion;
|
|
83
|
-
const rangeClean = semver.coerce(pkg.rangeVersion)?.version || pkg.rangeVersion;
|
|
84
|
-
const latestClean = semver.coerce(pkg.latestVersion)?.version || pkg.latestVersion;
|
|
85
|
-
const key = `${pkg.name}@${pkg.currentVersion}@${pkg.type}`;
|
|
86
|
-
const previousSelection = previousSelections?.get(key) || 'none';
|
|
87
|
-
return {
|
|
88
|
-
name: pkg.name,
|
|
89
|
-
packageJsonPath: pkg.packageJsonPath,
|
|
90
|
-
packageJsonPaths: Array.from(packageJsonPaths),
|
|
91
|
-
currentVersionSpecifier: pkg.currentVersion,
|
|
92
|
-
currentVersion: currentClean,
|
|
93
|
-
rangeVersion: rangeClean,
|
|
94
|
-
latestVersion: latestClean,
|
|
95
|
-
selectedOption: previousSelection,
|
|
96
|
-
loadState: 'ready',
|
|
97
|
-
hasRangeUpdate: pkg.hasRangeUpdate,
|
|
98
|
-
hasMajorUpdate: pkg.hasMajorUpdate,
|
|
99
|
-
type: pkg.type,
|
|
100
|
-
vulnerability: this.vulnerabilityAuditController.getCachedSummary(pkg.name, pkg.currentVersion, pkg.type),
|
|
101
|
-
allVersions: pkg.allVersions,
|
|
102
|
-
};
|
|
103
|
-
});
|
|
43
|
+
return (0, session_1.createSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections, includeUpToDate);
|
|
104
44
|
}
|
|
105
45
|
createPendingSelectionStates(packages, previousSelections) {
|
|
106
|
-
|
|
107
|
-
...pkg,
|
|
108
|
-
rangeVersion: pkg.currentVersion,
|
|
109
|
-
latestVersion: pkg.currentVersion,
|
|
110
|
-
isOutdated: false,
|
|
111
|
-
hasRangeUpdate: false,
|
|
112
|
-
hasMajorUpdate: false,
|
|
113
|
-
})));
|
|
114
|
-
return Array.from(uniquePackages.values()).map(({ pkg, packageJsonPaths }) => {
|
|
115
|
-
const currentClean = semver.coerce(pkg.currentVersion)?.version || pkg.currentVersion;
|
|
116
|
-
const key = `${pkg.name}@${pkg.currentVersion}@${pkg.type}`;
|
|
117
|
-
const previousSelection = previousSelections?.get(key) || 'none';
|
|
118
|
-
return {
|
|
119
|
-
name: pkg.name,
|
|
120
|
-
packageJsonPath: pkg.packageJsonPath,
|
|
121
|
-
packageJsonPaths: Array.from(packageJsonPaths),
|
|
122
|
-
currentVersionSpecifier: pkg.currentVersion,
|
|
123
|
-
currentVersion: currentClean,
|
|
124
|
-
rangeVersion: 'loading',
|
|
125
|
-
latestVersion: 'loading',
|
|
126
|
-
selectedOption: previousSelection,
|
|
127
|
-
loadState: 'pending',
|
|
128
|
-
hasRangeUpdate: false,
|
|
129
|
-
hasMajorUpdate: false,
|
|
130
|
-
type: pkg.type,
|
|
131
|
-
vulnerability: this.vulnerabilityAuditController.getCachedSummary(pkg.name, pkg.currentVersion, pkg.type),
|
|
132
|
-
};
|
|
133
|
-
});
|
|
46
|
+
return (0, session_1.createPendingSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections);
|
|
134
47
|
}
|
|
135
48
|
appendOutdatedBatchToSelectionStates(selectionStates, batch, previousSelections) {
|
|
136
49
|
const outdatedStates = this.createSelectionStates(batch.flatMap((batchItem) => batchItem.packageInfo).filter((pkg) => pkg.isOutdated), previousSelections, false);
|
|
@@ -149,498 +62,12 @@ class InteractiveUI {
|
|
|
149
62
|
}
|
|
150
63
|
async selectPackagesToUpgradeProgressive(selectionStates, progress, attachRefresh) {
|
|
151
64
|
this.enqueueSecurityAudit(selectionStates);
|
|
152
|
-
const selectedStates = await
|
|
153
|
-
return
|
|
65
|
+
const selectedStates = await (0, session_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => { this.refreshView = refresh; }, progress, attachRefresh);
|
|
66
|
+
return (0, session_1.createUpgradeChoices)(selectedStates);
|
|
154
67
|
}
|
|
155
68
|
enqueueSecurityAudit(selectionStates) {
|
|
156
69
|
this.vulnerabilityAuditController.enqueueStates(selectionStates, () => this.refreshView?.());
|
|
157
70
|
}
|
|
158
|
-
deduplicatePackages(packages) {
|
|
159
|
-
const uniquePackages = new Map();
|
|
160
|
-
for (const pkg of packages) {
|
|
161
|
-
const key = `${pkg.name}@${pkg.currentVersion}@${pkg.type}`;
|
|
162
|
-
if (!uniquePackages.has(key)) {
|
|
163
|
-
uniquePackages.set(key, {
|
|
164
|
-
pkg,
|
|
165
|
-
packageJsonPaths: new Set([pkg.packageJsonPath]),
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
uniquePackages.get(key).packageJsonPaths.add(pkg.packageJsonPath);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return new Map(Array.from(uniquePackages.entries()).sort(([, a], [, b]) => {
|
|
173
|
-
const aIsScoped = a.pkg.name.startsWith('@');
|
|
174
|
-
const bIsScoped = b.pkg.name.startsWith('@');
|
|
175
|
-
if (aIsScoped && !bIsScoped)
|
|
176
|
-
return -1;
|
|
177
|
-
if (!aIsScoped && bIsScoped)
|
|
178
|
-
return 1;
|
|
179
|
-
return a.pkg.name.localeCompare(b.pkg.name);
|
|
180
|
-
}));
|
|
181
|
-
}
|
|
182
|
-
createUpgradeChoices(selectedStates) {
|
|
183
|
-
const choices = [];
|
|
184
|
-
selectedStates
|
|
185
|
-
.filter((state) => state.loadState === 'ready' && state.selectedOption !== 'none')
|
|
186
|
-
.forEach((state) => {
|
|
187
|
-
const targetVersion = state.selectedOption === 'range' ? state.rangeVersion : state.latestVersion;
|
|
188
|
-
const targetVersionWithPrefix = ui_1.VersionUtils.applyVersionPrefix(state.currentVersionSpecifier, targetVersion);
|
|
189
|
-
const pathsToUpdate = state.packageJsonPaths || [state.packageJsonPath];
|
|
190
|
-
pathsToUpdate.forEach((packageJsonPath) => {
|
|
191
|
-
choices.push({
|
|
192
|
-
name: state.name,
|
|
193
|
-
packageJsonPath,
|
|
194
|
-
dependencyType: state.type,
|
|
195
|
-
upgradeType: state.selectedOption,
|
|
196
|
-
targetVersion: targetVersionWithPrefix,
|
|
197
|
-
currentVersionSpecifier: state.currentVersionSpecifier,
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
return choices;
|
|
202
|
-
}
|
|
203
|
-
getTerminalHeight() {
|
|
204
|
-
// Check if stdout is a TTY and has rows property
|
|
205
|
-
if (process.stdout.isTTY &&
|
|
206
|
-
typeof process.stdout.rows === 'number' &&
|
|
207
|
-
process.stdout.rows > 0) {
|
|
208
|
-
return process.stdout.rows;
|
|
209
|
-
}
|
|
210
|
-
return 24; // Fallback default
|
|
211
|
-
}
|
|
212
|
-
async interactiveTableSelector(selectionStates, loadingProgress, attachRefresh) {
|
|
213
|
-
return new Promise((resolve) => {
|
|
214
|
-
const states = selectionStates;
|
|
215
|
-
const stateManager = new ui_1.StateManager(0, this.getTerminalHeight());
|
|
216
|
-
let isResolved = false;
|
|
217
|
-
let ownsAlternateScreen = false;
|
|
218
|
-
const vulnerabilityDisplayOptions = this.options;
|
|
219
|
-
const claimInteractiveScreen = () => {
|
|
220
|
-
if (ownsAlternateScreen) {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
ui_1.ConsoleUtils.clearProgress();
|
|
224
|
-
ui_1.CursorUtils.enterAlternateScreen();
|
|
225
|
-
ui_1.CursorUtils.clearScreen();
|
|
226
|
-
ownsAlternateScreen = true;
|
|
227
|
-
};
|
|
228
|
-
const releaseInteractiveScreen = () => {
|
|
229
|
-
if (!ownsAlternateScreen) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
ui_1.CursorUtils.exitAlternateScreen();
|
|
233
|
-
ownsAlternateScreen = false;
|
|
234
|
-
};
|
|
235
|
-
// No grouping needed - packages are already filtered by type
|
|
236
|
-
// This simplifies scrolling and avoids rendering issues
|
|
237
|
-
stateManager.setRenderableItems([]);
|
|
238
|
-
// Track the current max scroll offset for the info modal
|
|
239
|
-
let infoModalMaxScrollOffset = 0;
|
|
240
|
-
// Track the current max scroll offset for the debug modal
|
|
241
|
-
let debugModalMaxScrollOffset = 0;
|
|
242
|
-
let previousViewportMode = null;
|
|
243
|
-
let previousModalViewportLineCount = null;
|
|
244
|
-
const handleAction = (action) => {
|
|
245
|
-
const uiState = stateManager.getUIState();
|
|
246
|
-
const filteredStates = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
|
|
247
|
-
switch (action.type) {
|
|
248
|
-
case 'navigate_up':
|
|
249
|
-
if (!uiState.showThemeModal) {
|
|
250
|
-
stateManager.navigateUp(filteredStates.length);
|
|
251
|
-
}
|
|
252
|
-
break;
|
|
253
|
-
case 'navigate_down':
|
|
254
|
-
if (!uiState.showThemeModal) {
|
|
255
|
-
stateManager.navigateDown(filteredStates.length);
|
|
256
|
-
}
|
|
257
|
-
break;
|
|
258
|
-
case 'select_left':
|
|
259
|
-
if (!uiState.showThemeModal) {
|
|
260
|
-
stateManager.updateSelection(filteredStates, 'left');
|
|
261
|
-
}
|
|
262
|
-
break;
|
|
263
|
-
case 'select_right':
|
|
264
|
-
if (!uiState.showThemeModal) {
|
|
265
|
-
stateManager.updateSelection(filteredStates, 'right');
|
|
266
|
-
}
|
|
267
|
-
break;
|
|
268
|
-
case 'bulk_select_minor':
|
|
269
|
-
if (!uiState.showThemeModal) {
|
|
270
|
-
stateManager.bulkSelectMinor(filteredStates);
|
|
271
|
-
}
|
|
272
|
-
break;
|
|
273
|
-
case 'bulk_select_latest':
|
|
274
|
-
if (!uiState.showThemeModal) {
|
|
275
|
-
stateManager.bulkSelectLatest(filteredStates);
|
|
276
|
-
}
|
|
277
|
-
break;
|
|
278
|
-
case 'bulk_unselect_all':
|
|
279
|
-
if (!uiState.showThemeModal) {
|
|
280
|
-
stateManager.bulkUnselectAll(filteredStates);
|
|
281
|
-
}
|
|
282
|
-
break;
|
|
283
|
-
case 'toggle_dep_type_filter':
|
|
284
|
-
if (!uiState.showThemeModal) {
|
|
285
|
-
stateManager.toggleDependencyTypeFilter(action.depType);
|
|
286
|
-
}
|
|
287
|
-
break;
|
|
288
|
-
case 'toggle_info_modal':
|
|
289
|
-
if (!uiState.showInfoModal) {
|
|
290
|
-
// Opening modal - load package info asynchronously
|
|
291
|
-
const modalSessionId = stateManager.toggleInfoModal();
|
|
292
|
-
const currentState = filteredStates[uiState.currentRow];
|
|
293
|
-
const canFetchMetadata = currentState?.loadState === 'ready';
|
|
294
|
-
stateManager.setModalLoading(canFetchMetadata, modalSessionId);
|
|
295
|
-
renderInterface();
|
|
296
|
-
if (currentState && canFetchMetadata) {
|
|
297
|
-
this.packageInfoModalController
|
|
298
|
-
.hydrate(currentState)
|
|
299
|
-
.then(() => {
|
|
300
|
-
if (isResolved || stateManager.getInfoModalSessionId() !== modalSessionId)
|
|
301
|
-
return;
|
|
302
|
-
stateManager.setModalLoading(false, modalSessionId);
|
|
303
|
-
renderInterface();
|
|
304
|
-
// Auto-load the first version's release notes
|
|
305
|
-
if (stateManager.getInfoModalSessionId() === modalSessionId &&
|
|
306
|
-
this.packageInfoModalController.getVersionCount(currentState) > 0) {
|
|
307
|
-
this.packageInfoModalController.loadVersionAtIndex(currentState, 0, () => {
|
|
308
|
-
if (!isResolved)
|
|
309
|
-
renderInterface();
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
})
|
|
313
|
-
.catch(() => {
|
|
314
|
-
if (isResolved || stateManager.getInfoModalSessionId() !== modalSessionId)
|
|
315
|
-
return;
|
|
316
|
-
stateManager.setModalLoading(false, modalSessionId);
|
|
317
|
-
renderInterface();
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
// Closing modal - cancel in-flight fetches
|
|
323
|
-
this.packageInfoModalController.cancel();
|
|
324
|
-
stateManager.toggleInfoModal();
|
|
325
|
-
renderInterface();
|
|
326
|
-
}
|
|
327
|
-
break;
|
|
328
|
-
case 'scroll_info_modal_up':
|
|
329
|
-
if (!stateManager.scrollInfoModalUp()) {
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
break;
|
|
333
|
-
case 'scroll_info_modal_down':
|
|
334
|
-
if (!stateManager.scrollInfoModalDown(infoModalMaxScrollOffset)) {
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
break;
|
|
338
|
-
case 'toggle_debug_modal':
|
|
339
|
-
stateManager.toggleDebugModal();
|
|
340
|
-
break;
|
|
341
|
-
case 'scroll_debug_modal_up':
|
|
342
|
-
if (!stateManager.scrollDebugModalUp()) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
break;
|
|
346
|
-
case 'scroll_debug_modal_down':
|
|
347
|
-
if (!stateManager.scrollDebugModalDown(debugModalMaxScrollOffset)) {
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
break;
|
|
351
|
-
case 'switch_info_modal_tab': {
|
|
352
|
-
const nextTab = stateManager.getInfoModalTab() === 'info' ? 'usedBy' : 'info';
|
|
353
|
-
stateManager.setInfoModalTab(nextTab);
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
case 'navigate_info_modal_version':
|
|
357
|
-
{
|
|
358
|
-
if (uiState.infoModalRow >= 0 && uiState.infoModalRow < filteredStates.length) {
|
|
359
|
-
const currentState = filteredStates[uiState.infoModalRow];
|
|
360
|
-
const newIndex = this.packageInfoModalController.navigateVersion(currentState, action.direction);
|
|
361
|
-
if (newIndex >= 0) {
|
|
362
|
-
// Reset scroll to top when switching versions
|
|
363
|
-
stateManager.resetInfoModalScroll();
|
|
364
|
-
// Load the version if not already loaded
|
|
365
|
-
if (!this.packageInfoModalController.isVersionLoaded(currentState, newIndex)) {
|
|
366
|
-
this.packageInfoModalController.loadVersionAtIndex(currentState, newIndex, () => {
|
|
367
|
-
if (!isResolved)
|
|
368
|
-
renderInterface();
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
break;
|
|
381
|
-
case 'enter_filter_mode':
|
|
382
|
-
stateManager.enterFilterMode(action.preserveQuery);
|
|
383
|
-
break;
|
|
384
|
-
case 'exit_filter_mode':
|
|
385
|
-
stateManager.exitFilterMode(action.clearQuery);
|
|
386
|
-
break;
|
|
387
|
-
case 'filter_input':
|
|
388
|
-
stateManager.appendToFilterQuery(action.char);
|
|
389
|
-
// Re-calculate filtered states after input
|
|
390
|
-
break;
|
|
391
|
-
case 'filter_backspace':
|
|
392
|
-
stateManager.deleteFromFilterQuery();
|
|
393
|
-
// Re-calculate filtered states after backspace
|
|
394
|
-
break;
|
|
395
|
-
case 'resize':
|
|
396
|
-
const heightChanged = stateManager.updateTerminalHeight(action.height);
|
|
397
|
-
if (heightChanged) {
|
|
398
|
-
stateManager.resetForResize(filteredStates.length);
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
// Even if height didn't change, width might have changed
|
|
402
|
-
// Force a full re-render to clear any wrapping issues
|
|
403
|
-
stateManager.setInitialRender(true);
|
|
404
|
-
}
|
|
405
|
-
break;
|
|
406
|
-
case 'toggle_theme_modal':
|
|
407
|
-
stateManager.toggleThemeModal();
|
|
408
|
-
break;
|
|
409
|
-
case 'theme_navigate_up': {
|
|
410
|
-
const themeManager = stateManager.getThemeManager();
|
|
411
|
-
const currentIndex = themes_1.themeNames.indexOf(themeManager.getPreviewTheme());
|
|
412
|
-
const themeArray = Object.keys(themes_1.themes);
|
|
413
|
-
const nextIndex = currentIndex > 0 ? currentIndex - 1 : themeArray.length - 1;
|
|
414
|
-
stateManager.previewTheme(themeArray[nextIndex]);
|
|
415
|
-
break;
|
|
416
|
-
}
|
|
417
|
-
case 'theme_navigate_down': {
|
|
418
|
-
const themeManager = stateManager.getThemeManager();
|
|
419
|
-
const currentIndex = themes_1.themeNames.indexOf(themeManager.getPreviewTheme());
|
|
420
|
-
const themeArray = Object.keys(themes_1.themes);
|
|
421
|
-
const nextIndex = currentIndex < themeArray.length - 1 ? currentIndex + 1 : 0;
|
|
422
|
-
stateManager.previewTheme(themeArray[nextIndex]);
|
|
423
|
-
break;
|
|
424
|
-
}
|
|
425
|
-
case 'theme_confirm':
|
|
426
|
-
stateManager.confirmTheme();
|
|
427
|
-
break;
|
|
428
|
-
case 'trigger_audit_scan':
|
|
429
|
-
if (!uiState.showThemeModal) {
|
|
430
|
-
const auditProgress = this.vulnerabilityAuditController.getProgress();
|
|
431
|
-
if (auditProgress.hasData) {
|
|
432
|
-
stateManager.toggleVulnerableFilter();
|
|
433
|
-
}
|
|
434
|
-
else if (!auditProgress.isRunning) {
|
|
435
|
-
this.enqueueSecurityAudit(states);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
break;
|
|
439
|
-
case 'cancel':
|
|
440
|
-
this.packageInfoModalController.cancel();
|
|
441
|
-
handleCancel();
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
if (action.type !== 'toggle_info_modal') {
|
|
445
|
-
renderInterface();
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
const handleConfirm = (selectedStates) => {
|
|
449
|
-
finalizeSelection(selectedStates);
|
|
450
|
-
};
|
|
451
|
-
const handleCancel = () => {
|
|
452
|
-
finalizeSelection(states.map((s) => ({ ...s, selectedOption: 'none' })));
|
|
453
|
-
};
|
|
454
|
-
const inputHandler = new ui_1.InputHandler(stateManager, handleAction, handleConfirm, handleCancel);
|
|
455
|
-
const resetAnsiPattern = /\x1b\[(?:0|49)m/g;
|
|
456
|
-
const packageListRenderOptions = {
|
|
457
|
-
showPeerDependencyVulnerabilities: this.options.showPeerDependencyVulnerabilities,
|
|
458
|
-
showOptionalDependencyVulnerabilities: this.options.showOptionalDependencyVulnerabilities,
|
|
459
|
-
};
|
|
460
|
-
const keypressHandler = (str, key) => inputHandler.handleKeypress(str, key, states);
|
|
461
|
-
const buildRemainingViewport = (terminalWidth, terminalHeight, usedLines) => {
|
|
462
|
-
const remainingLines = Math.max(0, terminalHeight - usedLines);
|
|
463
|
-
const blankLine = ' '.repeat(terminalWidth);
|
|
464
|
-
return Array.from({ length: remainingLines }, () => blankLine);
|
|
465
|
-
};
|
|
466
|
-
const applyBackgroundToLine = (line, bgCode) => `${bgCode}${line.replace(resetAnsiPattern, (match) => `${match}${bgCode}`)}${(0, themes_colors_1.getTerminalResetCode)()}`;
|
|
467
|
-
const writeFrame = (lines, bgCode) => {
|
|
468
|
-
if (lines.length === 0) {
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
process.stdout.write(lines.map((line) => applyBackgroundToLine(line, bgCode)).join('\n'));
|
|
472
|
-
};
|
|
473
|
-
const buildModalHeaderLines = (shortcutLabel) => [
|
|
474
|
-
' ' + chalk_1.default.bold.magenta('🚀 inup'),
|
|
475
|
-
'',
|
|
476
|
-
' ' + shortcutLabel,
|
|
477
|
-
'',
|
|
478
|
-
];
|
|
479
|
-
const renderViewport = (lines, terminalWidth, terminalHeight, bgCode) => {
|
|
480
|
-
const viewportLines = [
|
|
481
|
-
...lines,
|
|
482
|
-
...buildRemainingViewport(terminalWidth, terminalHeight, lines.length),
|
|
483
|
-
];
|
|
484
|
-
writeFrame(viewportLines, bgCode);
|
|
485
|
-
};
|
|
486
|
-
const renderModalViewport = (mode, shortcutLabel, modalLines, terminalWidth, terminalHeight, bgCode) => {
|
|
487
|
-
const viewportLineCount = buildModalHeaderLines(shortcutLabel).length + modalLines.length;
|
|
488
|
-
const shouldClearBeforeRender = previousViewportMode !== mode || previousModalViewportLineCount !== viewportLineCount;
|
|
489
|
-
if (shouldClearBeforeRender) {
|
|
490
|
-
ui_1.CursorUtils.clearScreen();
|
|
491
|
-
ui_1.CursorUtils.hide();
|
|
492
|
-
}
|
|
493
|
-
renderViewport([...buildModalHeaderLines(shortcutLabel), ...modalLines], terminalWidth, terminalHeight, bgCode);
|
|
494
|
-
previousViewportMode = mode;
|
|
495
|
-
previousModalViewportLineCount = viewportLineCount;
|
|
496
|
-
stateManager.markRendered([]);
|
|
497
|
-
};
|
|
498
|
-
let cleanupInteractiveSession = () => {
|
|
499
|
-
process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
|
|
500
|
-
ui_1.CursorUtils.show();
|
|
501
|
-
process.stdin.off('keypress', keypressHandler);
|
|
502
|
-
process.stdin.pause();
|
|
503
|
-
process.off('SIGWINCH', handleResize);
|
|
504
|
-
this.refreshView = undefined;
|
|
505
|
-
};
|
|
506
|
-
const finalizeSelection = (selectedStates) => {
|
|
507
|
-
isResolved = true;
|
|
508
|
-
this.packageInfoModalController.cancel();
|
|
509
|
-
releaseInteractiveScreen();
|
|
510
|
-
cleanupInteractiveSession();
|
|
511
|
-
resolve(selectedStates);
|
|
512
|
-
};
|
|
513
|
-
const renderInterface = () => {
|
|
514
|
-
const uiState = stateManager.getUIState();
|
|
515
|
-
const filteredStates = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
|
|
516
|
-
const auditProgress = this.vulnerabilityAuditController.getProgress();
|
|
517
|
-
// Apply terminal background color
|
|
518
|
-
const bgCode = (0, themes_colors_1.getTerminalBgColorCode)();
|
|
519
|
-
process.stdout.write(bgCode);
|
|
520
|
-
if (uiState.forceFullRender) {
|
|
521
|
-
ui_1.CursorUtils.clearScreen();
|
|
522
|
-
ui_1.CursorUtils.hide();
|
|
523
|
-
}
|
|
524
|
-
else {
|
|
525
|
-
ui_1.CursorUtils.moveToHome();
|
|
526
|
-
}
|
|
527
|
-
// If theme modal is open, render only the theme selector
|
|
528
|
-
if (uiState.showThemeModal) {
|
|
529
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
530
|
-
const terminalHeight = this.getTerminalHeight();
|
|
531
|
-
const themeManager = stateManager.getThemeManager();
|
|
532
|
-
const modalLines = this.renderer.renderThemeSelectorModal(themeManager.getCurrentTheme(), themeManager.getPreviewTheme(), terminalWidth, terminalHeight);
|
|
533
|
-
renderModalViewport('theme-modal', chalk_1.default.bold.white('T ') + chalk_1.default.gray('/ Esc Exit theme selector'), modalLines, terminalWidth, terminalHeight, bgCode);
|
|
534
|
-
}
|
|
535
|
-
else if (uiState.showDebugModal) {
|
|
536
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
537
|
-
const terminalHeight = this.getTerminalHeight();
|
|
538
|
-
const snapshot = (0, debug_1.getPerformanceTracker)().snapshot();
|
|
539
|
-
const result = (0, debug_1.renderPerformanceModal)(snapshot, terminalWidth, Math.max(8, terminalHeight - 4), uiState.debugModalScrollOffset);
|
|
540
|
-
debugModalMaxScrollOffset = result.maxScrollOffset;
|
|
541
|
-
stateManager.clampDebugModalScrollOffset(debugModalMaxScrollOffset);
|
|
542
|
-
const scrollHint = result.usesInternalScroll && result.maxScrollOffset > 0
|
|
543
|
-
? chalk_1.default.bold.white('↑/↓ ') + chalk_1.default.gray('Scroll · ')
|
|
544
|
-
: '';
|
|
545
|
-
renderModalViewport('info-modal', scrollHint + chalk_1.default.bold.white('! / Esc ') + chalk_1.default.gray('Close'), result.lines, terminalWidth, terminalHeight, bgCode);
|
|
546
|
-
}
|
|
547
|
-
else if (uiState.showInfoModal &&
|
|
548
|
-
uiState.infoModalRow >= 0 &&
|
|
549
|
-
uiState.infoModalRow < filteredStates.length) {
|
|
550
|
-
const selectedState = filteredStates[uiState.infoModalRow];
|
|
551
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
552
|
-
const terminalHeight = this.getTerminalHeight();
|
|
553
|
-
if (uiState.isLoadingModalInfo) {
|
|
554
|
-
// Show loading state
|
|
555
|
-
const result = this.renderer.renderPackageInfoLoading(selectedState, terminalWidth, Math.max(8, terminalHeight - 4));
|
|
556
|
-
infoModalMaxScrollOffset = result.maxScrollOffset;
|
|
557
|
-
renderModalViewport('info-modal', chalk_1.default.bold.white('I / Esc ') + chalk_1.default.gray('Exit this view'), result.lines, terminalWidth, terminalHeight, bgCode);
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
// Show full info with scroll support
|
|
561
|
-
const activeTab = uiState.infoModalTab;
|
|
562
|
-
const result = this.renderer.renderPackageInfoModal(selectedState, terminalWidth, Math.max(8, terminalHeight - 4), uiState.infoModalScrollOffset, activeTab);
|
|
563
|
-
infoModalMaxScrollOffset = result.maxScrollOffset;
|
|
564
|
-
stateManager.clampInfoModalScrollOffset(infoModalMaxScrollOffset);
|
|
565
|
-
const scrollHint = result.usesInternalScroll && result.maxScrollOffset > 0
|
|
566
|
-
? chalk_1.default.bold.white('↑/↓ ') + chalk_1.default.gray('Scroll · ')
|
|
567
|
-
: '';
|
|
568
|
-
const versionHint = activeTab === 'info'
|
|
569
|
-
? chalk_1.default.bold.white('←/→ ') + chalk_1.default.gray('Version · ')
|
|
570
|
-
: '';
|
|
571
|
-
renderModalViewport('info-modal', scrollHint +
|
|
572
|
-
versionHint +
|
|
573
|
-
chalk_1.default.bold.white('Tab ') +
|
|
574
|
-
chalk_1.default.gray('Switch tab · ') +
|
|
575
|
-
chalk_1.default.bold.white('I / Esc ') +
|
|
576
|
-
chalk_1.default.gray('Exit this view'), result.lines, terminalWidth, terminalHeight, bgCode);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
// Normal list view (flat rendering - no grouping)
|
|
581
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
582
|
-
const terminalHeight = this.getTerminalHeight();
|
|
583
|
-
const activeFilterLabel = stateManager.getActiveFilterLabel();
|
|
584
|
-
const lines = this.renderer.renderInterface(filteredStates, uiState.currentRow, uiState.scrollOffset, uiState.maxVisibleItems, uiState.forceFullRender, [], // No renderable items - use flat rendering
|
|
585
|
-
activeFilterLabel, // Show current dependency type filter state
|
|
586
|
-
this.packageManager, // Pass package manager info for header
|
|
587
|
-
uiState.filterMode, uiState.filterQuery, states.length, terminalWidth, loadingProgress, auditProgress, packageListRenderOptions);
|
|
588
|
-
renderViewport(lines, terminalWidth, terminalHeight, bgCode);
|
|
589
|
-
previousViewportMode = 'list';
|
|
590
|
-
previousModalViewportLineCount = null;
|
|
591
|
-
stateManager.markRendered(lines);
|
|
592
|
-
}
|
|
593
|
-
stateManager.setInitialRender(false);
|
|
594
|
-
};
|
|
595
|
-
const handleResize = () => {
|
|
596
|
-
// On resize (width or height change), always trigger a re-render
|
|
597
|
-
// This prevents layout breaking when terminal width changes
|
|
598
|
-
// The action handler will update height and force a full re-render
|
|
599
|
-
inputHandler.handleResize(this.getTerminalHeight());
|
|
600
|
-
};
|
|
601
|
-
// Setup keypress handling
|
|
602
|
-
try {
|
|
603
|
-
claimInteractiveScreen();
|
|
604
|
-
this.refreshView = () => {
|
|
605
|
-
if (!isResolved) {
|
|
606
|
-
renderInterface();
|
|
607
|
-
}
|
|
608
|
-
};
|
|
609
|
-
attachRefresh?.(() => {
|
|
610
|
-
if (!isResolved) {
|
|
611
|
-
renderInterface();
|
|
612
|
-
}
|
|
613
|
-
});
|
|
614
|
-
const keypressSession = ui_1.TerminalInput.startKeypressSession(keypressHandler);
|
|
615
|
-
const previousCleanup = cleanupInteractiveSession;
|
|
616
|
-
cleanupInteractiveSession = () => {
|
|
617
|
-
keypressSession.close();
|
|
618
|
-
previousCleanup();
|
|
619
|
-
};
|
|
620
|
-
// Setup resize handler
|
|
621
|
-
process.on('SIGWINCH', handleResize);
|
|
622
|
-
// Update terminal height directly before initial render to ensure correct dimensions
|
|
623
|
-
// This handles cases where process.stdout.rows might not be accurate at startup
|
|
624
|
-
const currentHeight = this.getTerminalHeight();
|
|
625
|
-
if (stateManager.updateTerminalHeight(currentHeight)) {
|
|
626
|
-
const initialFiltered = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
|
|
627
|
-
stateManager.resetForResize(initialFiltered.length);
|
|
628
|
-
}
|
|
629
|
-
// Initial render
|
|
630
|
-
renderInterface();
|
|
631
|
-
this.enqueueSecurityAudit(states);
|
|
632
|
-
}
|
|
633
|
-
catch (error) {
|
|
634
|
-
releaseInteractiveScreen();
|
|
635
|
-
// Reset terminal colors
|
|
636
|
-
process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
|
|
637
|
-
this.refreshView = undefined;
|
|
638
|
-
// Fallback to simple interface if raw mode fails
|
|
639
|
-
console.log(chalk_1.default.yellow('Raw mode not available, using fallback interface...'));
|
|
640
|
-
resolve(states);
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
71
|
async confirmUpgrade(choices) {
|
|
645
72
|
console.log(this.renderer.renderConfirmation(choices));
|
|
646
73
|
return new Promise((resolve) => {
|
|
@@ -651,7 +78,22 @@ class InteractiveUI {
|
|
|
651
78
|
cleanupConfirmationSession();
|
|
652
79
|
resolve(confirmed);
|
|
653
80
|
};
|
|
654
|
-
|
|
81
|
+
// Safety net for the same reason as selectPackages — synchronous restore on exit.
|
|
82
|
+
// The confirmation screen does not enter the alternate screen, so alt-screen
|
|
83
|
+
// restoration is intentionally omitted here. If that ever changes, mirror the
|
|
84
|
+
// ownsAlternateScreen-gated pattern from selectPackages.
|
|
85
|
+
const confirmEmergencyCleanup = () => {
|
|
86
|
+
process.stdout.write('\x1b[?25h');
|
|
87
|
+
if (process.stdin.setRawMode) {
|
|
88
|
+
process.stdin.setRawMode(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
process.on('exit', confirmEmergencyCleanup);
|
|
92
|
+
const handleConfirmWithCleanup = (confirmed) => {
|
|
93
|
+
handleConfirm(confirmed);
|
|
94
|
+
process.off('exit', confirmEmergencyCleanup);
|
|
95
|
+
};
|
|
96
|
+
const inputHandler = new ui_1.ConfirmationInputHandler(handleConfirmWithCleanup);
|
|
655
97
|
const keypressHandler = (str, key) => inputHandler.handleKeypress(str, key);
|
|
656
98
|
// Setup keypress handling
|
|
657
99
|
try {
|
|
@@ -663,6 +105,7 @@ class InteractiveUI {
|
|
|
663
105
|
ui_1.CursorUtils.hide();
|
|
664
106
|
}
|
|
665
107
|
catch (error) {
|
|
108
|
+
process.off('exit', confirmEmergencyCleanup);
|
|
666
109
|
ui_1.TerminalInput.promptForConfirmation('Proceed with upgrade? [Y/n] ')
|
|
667
110
|
.then(resolve)
|
|
668
111
|
.catch(() => resolve(false));
|