inup 1.4.10 → 1.5.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 +1 -7
- package/dist/cli.js +2 -1
- package/dist/config/constants.js +1 -2
- package/dist/config/project-config.js +6 -0
- package/dist/core/package-detector.js +163 -89
- package/dist/core/upgrade-runner.js +68 -16
- package/dist/features/changelog/clients/github-client.js +134 -0
- package/dist/features/changelog/clients/npm-registry-client.js +53 -0
- package/dist/features/changelog/index.js +19 -0
- package/dist/features/changelog/parsers/changelog-parser.js +68 -0
- package/dist/features/changelog/parsers/github-release-html-parser.js +61 -0
- package/dist/features/changelog/parsers/package-metadata.js +34 -0
- package/dist/features/changelog/parsers/repository-ref.js +26 -0
- package/dist/features/changelog/services/changelog-service.js +30 -0
- package/dist/features/changelog/services/package-metadata-service.js +108 -0
- package/dist/features/changelog/services/release-notes-service.js +180 -0
- package/dist/features/changelog/types/changelog.types.js +3 -0
- package/dist/interactive-ui.js +343 -161
- package/dist/services/background-audit.js +60 -0
- package/dist/services/index.js +3 -3
- package/dist/services/jsdelivr-registry.js +92 -176
- package/dist/services/npm-registry.js +97 -27
- package/dist/services/vulnerability-checker.js +133 -0
- package/dist/ui/controllers/index.js +8 -0
- package/dist/ui/controllers/package-info-modal-controller.js +237 -0
- package/dist/ui/controllers/vulnerability-audit-controller.js +82 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/input-handler.js +41 -10
- package/dist/ui/modal/index.js +22 -0
- package/dist/ui/modal/layout.js +84 -0
- package/dist/ui/modal/package-info-sections.js +327 -0
- package/dist/ui/modal/package-info.js +147 -0
- package/dist/ui/modal/theme-selector.js +46 -0
- package/dist/ui/modal/types.js +3 -0
- package/dist/ui/presenters/index.js +11 -0
- package/dist/ui/presenters/vulnerability.js +76 -0
- package/dist/ui/renderer/index.js +9 -11
- package/dist/ui/renderer/package-list.js +166 -66
- package/dist/ui/state/filter-manager.js +17 -2
- package/dist/ui/state/modal-manager.js +48 -6
- package/dist/ui/state/state-manager.js +49 -12
- package/dist/ui/utils/cursor.js +18 -0
- package/dist/ui/utils/index.js +8 -1
- package/dist/ui/utils/terminal-input.js +82 -0
- package/dist/ui/utils/text.js +75 -0
- package/dist/ui/utils/version.js +3 -2
- package/package.json +7 -11
- package/dist/services/changelog-fetcher.js +0 -190
- package/dist/ui/renderer/modal.js +0 -190
- package/dist/ui/renderer/theme-selector.js +0 -83
package/dist/interactive-ui.js
CHANGED
|
@@ -37,91 +37,154 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.InteractiveUI = void 0;
|
|
40
|
-
const inquirer_1 = __importDefault(require("inquirer"));
|
|
41
40
|
const chalk_1 = __importDefault(require("chalk"));
|
|
42
41
|
const semver = __importStar(require("semver"));
|
|
43
|
-
const keypress = require('keypress');
|
|
44
42
|
const ui_1 = require("./ui");
|
|
45
|
-
const
|
|
43
|
+
const controllers_1 = require("./ui/controllers");
|
|
46
44
|
const themes_1 = require("./ui/themes");
|
|
47
45
|
const themes_colors_1 = require("./ui/themes-colors");
|
|
46
|
+
const DEFAULT_VULNERABILITY_DISPLAY_OPTIONS = {
|
|
47
|
+
showPeerDependencyVulnerabilities: false,
|
|
48
|
+
showOptionalDependencyVulnerabilities: false,
|
|
49
|
+
};
|
|
50
|
+
function normalizeVulnerabilityDisplayOptions(options) {
|
|
51
|
+
return {
|
|
52
|
+
showPeerDependencyVulnerabilities: options?.showPeerDependencyVulnerabilities ??
|
|
53
|
+
DEFAULT_VULNERABILITY_DISPLAY_OPTIONS.showPeerDependencyVulnerabilities,
|
|
54
|
+
showOptionalDependencyVulnerabilities: options?.showOptionalDependencyVulnerabilities ??
|
|
55
|
+
DEFAULT_VULNERABILITY_DISPLAY_OPTIONS.showOptionalDependencyVulnerabilities,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
48
58
|
class InteractiveUI {
|
|
49
|
-
constructor(packageManager) {
|
|
59
|
+
constructor(packageManager, options) {
|
|
60
|
+
this.vulnerabilityAuditController = new controllers_1.VulnerabilityAuditController();
|
|
61
|
+
this.packageInfoModalController = new controllers_1.PackageInfoModalController();
|
|
50
62
|
this.renderer = new ui_1.UIRenderer();
|
|
51
63
|
this.packageManager = packageManager;
|
|
64
|
+
this.options = normalizeVulnerabilityDisplayOptions(options);
|
|
52
65
|
}
|
|
53
66
|
async displayPackagesTable(packages) {
|
|
54
67
|
console.log(this.renderer.renderPackagesTable(packages));
|
|
55
68
|
}
|
|
56
69
|
async selectPackagesToUpgrade(packages, previousSelections) {
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
70
|
+
const selectionStates = this.createSelectionStates(packages, previousSelections, false);
|
|
71
|
+
if (selectionStates.length === 0) {
|
|
59
72
|
return [];
|
|
60
73
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
packageJsonPaths: new Set([pkg.packageJsonPath]),
|
|
69
|
-
type: pkg.type,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
uniquePackages.get(key).packageJsonPaths.add(pkg.packageJsonPath);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// Convert to array and sort alphabetically by name (@scoped packages first, then unscoped)
|
|
77
|
-
const deduplicatedPackages = Array.from(uniquePackages.values()).map(({ pkg, packageJsonPaths, type }) => ({
|
|
78
|
-
...pkg,
|
|
79
|
-
packageJsonPaths: Array.from(packageJsonPaths),
|
|
80
|
-
type,
|
|
81
|
-
}));
|
|
82
|
-
deduplicatedPackages.sort((a, b) => {
|
|
83
|
-
const aIsScoped = a.name.startsWith('@');
|
|
84
|
-
const bIsScoped = b.name.startsWith('@');
|
|
85
|
-
// If one is scoped and the other isn't, scoped comes first
|
|
86
|
-
if (aIsScoped && !bIsScoped)
|
|
87
|
-
return -1;
|
|
88
|
-
if (!aIsScoped && bIsScoped)
|
|
89
|
-
return 1;
|
|
90
|
-
// Both scoped or both unscoped - sort alphabetically
|
|
91
|
-
return a.name.localeCompare(b.name);
|
|
92
|
-
});
|
|
93
|
-
// Create selection states for each unique package
|
|
94
|
-
const selectionStates = deduplicatedPackages.map((pkg) => {
|
|
74
|
+
const selectedStates = await this.interactiveTableSelector(selectionStates);
|
|
75
|
+
return this.createUpgradeChoices(selectedStates);
|
|
76
|
+
}
|
|
77
|
+
createSelectionStates(packages, previousSelections, includeUpToDate = true) {
|
|
78
|
+
const relevantPackages = includeUpToDate ? packages : packages.filter((p) => p.isOutdated);
|
|
79
|
+
const uniquePackages = this.deduplicatePackages(relevantPackages);
|
|
80
|
+
return Array.from(uniquePackages.values()).map(({ pkg, packageJsonPaths }) => {
|
|
95
81
|
const currentClean = semver.coerce(pkg.currentVersion)?.version || pkg.currentVersion;
|
|
96
82
|
const rangeClean = semver.coerce(pkg.rangeVersion)?.version || pkg.rangeVersion;
|
|
97
83
|
const latestClean = semver.coerce(pkg.latestVersion)?.version || pkg.latestVersion;
|
|
98
|
-
// Use previous selection if available, otherwise default to 'none'
|
|
99
84
|
const key = `${pkg.name}@${pkg.currentVersion}@${pkg.type}`;
|
|
100
85
|
const previousSelection = previousSelections?.get(key) || 'none';
|
|
101
86
|
return {
|
|
102
87
|
name: pkg.name,
|
|
103
|
-
packageJsonPath: pkg.
|
|
104
|
-
packageJsonPaths:
|
|
105
|
-
currentVersionSpecifier: pkg.currentVersion,
|
|
88
|
+
packageJsonPath: pkg.packageJsonPath,
|
|
89
|
+
packageJsonPaths: Array.from(packageJsonPaths),
|
|
90
|
+
currentVersionSpecifier: pkg.currentVersion,
|
|
106
91
|
currentVersion: currentClean,
|
|
107
92
|
rangeVersion: rangeClean,
|
|
108
93
|
latestVersion: latestClean,
|
|
109
94
|
selectedOption: previousSelection,
|
|
95
|
+
loadState: 'ready',
|
|
110
96
|
hasRangeUpdate: pkg.hasRangeUpdate,
|
|
111
97
|
hasMajorUpdate: pkg.hasMajorUpdate,
|
|
112
98
|
type: pkg.type,
|
|
99
|
+
vulnerability: this.vulnerabilityAuditController.getCachedSummary(pkg.name, pkg.currentVersion, pkg.type),
|
|
100
|
+
allVersions: pkg.allVersions,
|
|
113
101
|
};
|
|
114
102
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
103
|
+
}
|
|
104
|
+
createPendingSelectionStates(packages, previousSelections) {
|
|
105
|
+
const uniquePackages = this.deduplicatePackages(packages.map((pkg) => ({
|
|
106
|
+
...pkg,
|
|
107
|
+
rangeVersion: pkg.currentVersion,
|
|
108
|
+
latestVersion: pkg.currentVersion,
|
|
109
|
+
isOutdated: false,
|
|
110
|
+
hasRangeUpdate: false,
|
|
111
|
+
hasMajorUpdate: false,
|
|
112
|
+
})));
|
|
113
|
+
return Array.from(uniquePackages.values()).map(({ pkg, packageJsonPaths }) => {
|
|
114
|
+
const currentClean = semver.coerce(pkg.currentVersion)?.version || pkg.currentVersion;
|
|
115
|
+
const key = `${pkg.name}@${pkg.currentVersion}@${pkg.type}`;
|
|
116
|
+
const previousSelection = previousSelections?.get(key) || 'none';
|
|
117
|
+
return {
|
|
118
|
+
name: pkg.name,
|
|
119
|
+
packageJsonPath: pkg.packageJsonPath,
|
|
120
|
+
packageJsonPaths: Array.from(packageJsonPaths),
|
|
121
|
+
currentVersionSpecifier: pkg.currentVersion,
|
|
122
|
+
currentVersion: currentClean,
|
|
123
|
+
rangeVersion: 'loading',
|
|
124
|
+
latestVersion: 'loading',
|
|
125
|
+
selectedOption: previousSelection,
|
|
126
|
+
loadState: 'pending',
|
|
127
|
+
hasRangeUpdate: false,
|
|
128
|
+
hasMajorUpdate: false,
|
|
129
|
+
type: pkg.type,
|
|
130
|
+
vulnerability: this.vulnerabilityAuditController.getCachedSummary(pkg.name, pkg.currentVersion, pkg.type),
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
appendOutdatedBatchToSelectionStates(selectionStates, batch, previousSelections) {
|
|
135
|
+
const outdatedStates = this.createSelectionStates(batch.flatMap((batchItem) => batchItem.packageInfo).filter((pkg) => pkg.isOutdated), previousSelections, false);
|
|
136
|
+
if (outdatedStates.length === 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const seen = new Set(selectionStates.map((state) => `${state.name}@${state.currentVersionSpecifier}@${state.type}`));
|
|
140
|
+
outdatedStates.forEach((state) => {
|
|
141
|
+
const key = `${state.name}@${state.currentVersionSpecifier}@${state.type}`;
|
|
142
|
+
if (!seen.has(key)) {
|
|
143
|
+
selectionStates.push(state);
|
|
144
|
+
seen.add(key);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
this.enqueueSecurityAudit(selectionStates);
|
|
148
|
+
}
|
|
149
|
+
async selectPackagesToUpgradeProgressive(selectionStates, progress, attachRefresh) {
|
|
150
|
+
this.enqueueSecurityAudit(selectionStates);
|
|
151
|
+
const selectedStates = await this.interactiveTableSelector(selectionStates, progress, attachRefresh);
|
|
152
|
+
return this.createUpgradeChoices(selectedStates);
|
|
153
|
+
}
|
|
154
|
+
enqueueSecurityAudit(selectionStates) {
|
|
155
|
+
this.vulnerabilityAuditController.enqueueStates(selectionStates, () => this.refreshView?.());
|
|
156
|
+
}
|
|
157
|
+
deduplicatePackages(packages) {
|
|
158
|
+
const uniquePackages = new Map();
|
|
159
|
+
for (const pkg of packages) {
|
|
160
|
+
const key = `${pkg.name}@${pkg.currentVersion}@${pkg.type}`;
|
|
161
|
+
if (!uniquePackages.has(key)) {
|
|
162
|
+
uniquePackages.set(key, {
|
|
163
|
+
pkg,
|
|
164
|
+
packageJsonPaths: new Set([pkg.packageJsonPath]),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
uniquePackages.get(key).packageJsonPaths.add(pkg.packageJsonPath);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return new Map(Array.from(uniquePackages.entries()).sort(([, a], [, b]) => {
|
|
172
|
+
const aIsScoped = a.pkg.name.startsWith('@');
|
|
173
|
+
const bIsScoped = b.pkg.name.startsWith('@');
|
|
174
|
+
if (aIsScoped && !bIsScoped)
|
|
175
|
+
return -1;
|
|
176
|
+
if (!aIsScoped && bIsScoped)
|
|
177
|
+
return 1;
|
|
178
|
+
return a.pkg.name.localeCompare(b.pkg.name);
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
createUpgradeChoices(selectedStates) {
|
|
118
182
|
const choices = [];
|
|
119
183
|
selectedStates
|
|
120
|
-
.filter((state) => state.selectedOption !== 'none')
|
|
184
|
+
.filter((state) => state.loadState === 'ready' && state.selectedOption !== 'none')
|
|
121
185
|
.forEach((state) => {
|
|
122
186
|
const targetVersion = state.selectedOption === 'range' ? state.rangeVersion : state.latestVersion;
|
|
123
187
|
const targetVersionWithPrefix = ui_1.VersionUtils.applyVersionPrefix(state.currentVersionSpecifier, targetVersion);
|
|
124
|
-
// Create a choice for each package.json path where this package appears
|
|
125
188
|
const pathsToUpdate = state.packageJsonPaths || [state.packageJsonPath];
|
|
126
189
|
pathsToUpdate.forEach((packageJsonPath) => {
|
|
127
190
|
choices.push({
|
|
@@ -138,89 +201,162 @@ class InteractiveUI {
|
|
|
138
201
|
}
|
|
139
202
|
getTerminalHeight() {
|
|
140
203
|
// Check if stdout is a TTY and has rows property
|
|
141
|
-
if (process.stdout.isTTY &&
|
|
204
|
+
if (process.stdout.isTTY &&
|
|
205
|
+
typeof process.stdout.rows === 'number' &&
|
|
206
|
+
process.stdout.rows > 0) {
|
|
142
207
|
return process.stdout.rows;
|
|
143
208
|
}
|
|
144
209
|
return 24; // Fallback default
|
|
145
210
|
}
|
|
146
|
-
async interactiveTableSelector(selectionStates) {
|
|
211
|
+
async interactiveTableSelector(selectionStates, loadingProgress, attachRefresh) {
|
|
147
212
|
return new Promise((resolve) => {
|
|
148
|
-
const states =
|
|
213
|
+
const states = selectionStates;
|
|
149
214
|
const stateManager = new ui_1.StateManager(0, this.getTerminalHeight());
|
|
215
|
+
let isResolved = false;
|
|
216
|
+
let ownsAlternateScreen = false;
|
|
217
|
+
const vulnerabilityDisplayOptions = this.options;
|
|
218
|
+
const claimInteractiveScreen = () => {
|
|
219
|
+
if (ownsAlternateScreen) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
ui_1.ConsoleUtils.clearProgress();
|
|
223
|
+
ui_1.CursorUtils.enterAlternateScreen();
|
|
224
|
+
ui_1.CursorUtils.clearScreen();
|
|
225
|
+
ownsAlternateScreen = true;
|
|
226
|
+
};
|
|
227
|
+
const releaseInteractiveScreen = () => {
|
|
228
|
+
if (!ownsAlternateScreen) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
ui_1.CursorUtils.exitAlternateScreen();
|
|
232
|
+
ownsAlternateScreen = false;
|
|
233
|
+
};
|
|
150
234
|
// No grouping needed - packages are already filtered by type
|
|
151
235
|
// This simplifies scrolling and avoids rendering issues
|
|
152
236
|
stateManager.setRenderableItems([]);
|
|
237
|
+
// Track the current max scroll offset for the info modal
|
|
238
|
+
let infoModalMaxScrollOffset = 0;
|
|
239
|
+
let previousViewportMode = null;
|
|
240
|
+
let previousModalViewportLineCount = null;
|
|
153
241
|
const handleAction = (action) => {
|
|
154
242
|
const uiState = stateManager.getUIState();
|
|
155
|
-
const filteredStates = stateManager.getFilteredStates(states);
|
|
243
|
+
const filteredStates = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
|
|
156
244
|
switch (action.type) {
|
|
157
245
|
case 'navigate_up':
|
|
158
|
-
if (!uiState.
|
|
246
|
+
if (!uiState.showThemeModal) {
|
|
159
247
|
stateManager.navigateUp(filteredStates.length);
|
|
160
248
|
}
|
|
161
249
|
break;
|
|
162
250
|
case 'navigate_down':
|
|
163
|
-
if (!uiState.
|
|
251
|
+
if (!uiState.showThemeModal) {
|
|
164
252
|
stateManager.navigateDown(filteredStates.length);
|
|
165
253
|
}
|
|
166
254
|
break;
|
|
167
255
|
case 'select_left':
|
|
168
|
-
if (!uiState.
|
|
256
|
+
if (!uiState.showThemeModal) {
|
|
169
257
|
stateManager.updateSelection(filteredStates, 'left');
|
|
170
258
|
}
|
|
171
259
|
break;
|
|
172
260
|
case 'select_right':
|
|
173
|
-
if (!uiState.
|
|
261
|
+
if (!uiState.showThemeModal) {
|
|
174
262
|
stateManager.updateSelection(filteredStates, 'right');
|
|
175
263
|
}
|
|
176
264
|
break;
|
|
177
265
|
case 'bulk_select_minor':
|
|
178
|
-
if (!uiState.
|
|
266
|
+
if (!uiState.showThemeModal) {
|
|
179
267
|
stateManager.bulkSelectMinor(filteredStates);
|
|
180
268
|
}
|
|
181
269
|
break;
|
|
182
270
|
case 'bulk_select_latest':
|
|
183
|
-
if (!uiState.
|
|
271
|
+
if (!uiState.showThemeModal) {
|
|
184
272
|
stateManager.bulkSelectLatest(filteredStates);
|
|
185
273
|
}
|
|
186
274
|
break;
|
|
187
275
|
case 'bulk_unselect_all':
|
|
188
|
-
if (!uiState.
|
|
276
|
+
if (!uiState.showThemeModal) {
|
|
189
277
|
stateManager.bulkUnselectAll(filteredStates);
|
|
190
278
|
}
|
|
191
279
|
break;
|
|
192
280
|
case 'toggle_dep_type_filter':
|
|
193
|
-
if (!uiState.
|
|
281
|
+
if (!uiState.showThemeModal) {
|
|
194
282
|
stateManager.toggleDependencyTypeFilter(action.depType);
|
|
195
283
|
}
|
|
196
284
|
break;
|
|
197
285
|
case 'toggle_info_modal':
|
|
198
286
|
if (!uiState.showInfoModal) {
|
|
199
287
|
// Opening modal - load package info asynchronously
|
|
200
|
-
stateManager.toggleInfoModal();
|
|
288
|
+
const modalSessionId = stateManager.toggleInfoModal();
|
|
201
289
|
const currentState = filteredStates[uiState.currentRow];
|
|
202
|
-
|
|
290
|
+
const canFetchMetadata = currentState?.loadState === 'ready';
|
|
291
|
+
stateManager.setModalLoading(canFetchMetadata, modalSessionId);
|
|
203
292
|
renderInterface();
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
293
|
+
if (currentState && canFetchMetadata) {
|
|
294
|
+
this.packageInfoModalController
|
|
295
|
+
.hydrate(currentState)
|
|
296
|
+
.then(() => {
|
|
297
|
+
if (isResolved || stateManager.getInfoModalSessionId() !== modalSessionId)
|
|
298
|
+
return;
|
|
299
|
+
stateManager.setModalLoading(false, modalSessionId);
|
|
300
|
+
renderInterface();
|
|
301
|
+
// Auto-load the first version's release notes
|
|
302
|
+
if (stateManager.getInfoModalSessionId() === modalSessionId &&
|
|
303
|
+
this.packageInfoModalController.getVersionCount(currentState) > 0) {
|
|
304
|
+
this.packageInfoModalController.loadVersionAtIndex(currentState, 0, () => {
|
|
305
|
+
if (!isResolved)
|
|
306
|
+
renderInterface();
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
.catch(() => {
|
|
311
|
+
if (isResolved || stateManager.getInfoModalSessionId() !== modalSessionId)
|
|
312
|
+
return;
|
|
313
|
+
stateManager.setModalLoading(false, modalSessionId);
|
|
314
|
+
renderInterface();
|
|
315
|
+
});
|
|
316
|
+
}
|
|
217
317
|
}
|
|
218
318
|
else {
|
|
219
|
-
// Closing modal
|
|
319
|
+
// Closing modal - cancel in-flight fetches
|
|
320
|
+
this.packageInfoModalController.cancel();
|
|
220
321
|
stateManager.toggleInfoModal();
|
|
221
322
|
renderInterface();
|
|
222
323
|
}
|
|
223
324
|
break;
|
|
325
|
+
case 'scroll_info_modal_up':
|
|
326
|
+
if (!stateManager.scrollInfoModalUp()) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
case 'scroll_info_modal_down':
|
|
331
|
+
if (!stateManager.scrollInfoModalDown(infoModalMaxScrollOffset)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
335
|
+
case 'navigate_info_modal_version':
|
|
336
|
+
{
|
|
337
|
+
if (uiState.infoModalRow >= 0 && uiState.infoModalRow < filteredStates.length) {
|
|
338
|
+
const currentState = filteredStates[uiState.infoModalRow];
|
|
339
|
+
const newIndex = this.packageInfoModalController.navigateVersion(currentState, action.direction);
|
|
340
|
+
if (newIndex >= 0) {
|
|
341
|
+
// Reset scroll to top when switching versions
|
|
342
|
+
stateManager.resetInfoModalScroll();
|
|
343
|
+
// Load the version if not already loaded
|
|
344
|
+
if (!this.packageInfoModalController.isVersionLoaded(currentState, newIndex)) {
|
|
345
|
+
this.packageInfoModalController.loadVersionAtIndex(currentState, newIndex, () => {
|
|
346
|
+
if (!isResolved)
|
|
347
|
+
renderInterface();
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
224
360
|
case 'enter_filter_mode':
|
|
225
361
|
stateManager.enterFilterMode(action.preserveQuery);
|
|
226
362
|
break;
|
|
@@ -268,7 +404,19 @@ class InteractiveUI {
|
|
|
268
404
|
case 'theme_confirm':
|
|
269
405
|
stateManager.confirmTheme();
|
|
270
406
|
break;
|
|
407
|
+
case 'trigger_audit_scan':
|
|
408
|
+
if (!uiState.showThemeModal) {
|
|
409
|
+
const auditProgress = this.vulnerabilityAuditController.getProgress();
|
|
410
|
+
if (auditProgress.hasData) {
|
|
411
|
+
stateManager.toggleVulnerableFilter();
|
|
412
|
+
}
|
|
413
|
+
else if (!auditProgress.isRunning) {
|
|
414
|
+
this.enqueueSecurityAudit(states);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
break;
|
|
271
418
|
case 'cancel':
|
|
419
|
+
this.packageInfoModalController.cancel();
|
|
272
420
|
handleCancel();
|
|
273
421
|
return;
|
|
274
422
|
}
|
|
@@ -277,40 +425,79 @@ class InteractiveUI {
|
|
|
277
425
|
}
|
|
278
426
|
};
|
|
279
427
|
const handleConfirm = (selectedStates) => {
|
|
280
|
-
|
|
281
|
-
process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
|
|
282
|
-
ui_1.CursorUtils.show();
|
|
283
|
-
// Clean up listeners
|
|
284
|
-
if (process.stdin.setRawMode) {
|
|
285
|
-
process.stdin.setRawMode(false);
|
|
286
|
-
}
|
|
287
|
-
process.stdin.removeAllListeners('keypress');
|
|
288
|
-
process.stdin.pause();
|
|
289
|
-
process.removeAllListeners('SIGWINCH');
|
|
290
|
-
resolve(selectedStates);
|
|
428
|
+
finalizeSelection(selectedStates);
|
|
291
429
|
};
|
|
292
430
|
const handleCancel = () => {
|
|
293
|
-
|
|
431
|
+
finalizeSelection(states.map((s) => ({ ...s, selectedOption: 'none' })));
|
|
432
|
+
};
|
|
433
|
+
const inputHandler = new ui_1.InputHandler(stateManager, handleAction, handleConfirm, handleCancel);
|
|
434
|
+
const resetAnsiPattern = /\x1b\[(?:0|49)m/g;
|
|
435
|
+
const packageListRenderOptions = {
|
|
436
|
+
showPeerDependencyVulnerabilities: this.options.showPeerDependencyVulnerabilities,
|
|
437
|
+
showOptionalDependencyVulnerabilities: this.options.showOptionalDependencyVulnerabilities,
|
|
438
|
+
};
|
|
439
|
+
const keypressHandler = (str, key) => inputHandler.handleKeypress(str, key, states);
|
|
440
|
+
const buildRemainingViewport = (terminalWidth, terminalHeight, usedLines) => {
|
|
441
|
+
const remainingLines = Math.max(0, terminalHeight - usedLines);
|
|
442
|
+
const blankLine = ' '.repeat(terminalWidth);
|
|
443
|
+
return Array.from({ length: remainingLines }, () => blankLine);
|
|
444
|
+
};
|
|
445
|
+
const applyBackgroundToLine = (line, bgCode) => `${bgCode}${line.replace(resetAnsiPattern, (match) => `${match}${bgCode}`)}${(0, themes_colors_1.getTerminalResetCode)()}`;
|
|
446
|
+
const writeFrame = (lines, bgCode) => {
|
|
447
|
+
if (lines.length === 0) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
process.stdout.write(lines.map((line) => applyBackgroundToLine(line, bgCode)).join('\n'));
|
|
451
|
+
};
|
|
452
|
+
const buildModalHeaderLines = (shortcutLabel) => [
|
|
453
|
+
' ' + chalk_1.default.bold.magenta('🚀 inup'),
|
|
454
|
+
'',
|
|
455
|
+
' ' + shortcutLabel,
|
|
456
|
+
'',
|
|
457
|
+
];
|
|
458
|
+
const renderViewport = (lines, terminalWidth, terminalHeight, bgCode) => {
|
|
459
|
+
const viewportLines = [
|
|
460
|
+
...lines,
|
|
461
|
+
...buildRemainingViewport(terminalWidth, terminalHeight, lines.length),
|
|
462
|
+
];
|
|
463
|
+
writeFrame(viewportLines, bgCode);
|
|
464
|
+
};
|
|
465
|
+
const renderModalViewport = (mode, shortcutLabel, modalLines, terminalWidth, terminalHeight, bgCode) => {
|
|
466
|
+
const viewportLineCount = buildModalHeaderLines(shortcutLabel).length + modalLines.length;
|
|
467
|
+
const shouldClearBeforeRender = previousViewportMode !== mode || previousModalViewportLineCount !== viewportLineCount;
|
|
468
|
+
if (shouldClearBeforeRender) {
|
|
469
|
+
ui_1.CursorUtils.clearScreen();
|
|
470
|
+
ui_1.CursorUtils.hide();
|
|
471
|
+
}
|
|
472
|
+
renderViewport([...buildModalHeaderLines(shortcutLabel), ...modalLines], terminalWidth, terminalHeight, bgCode);
|
|
473
|
+
previousViewportMode = mode;
|
|
474
|
+
previousModalViewportLineCount = viewportLineCount;
|
|
475
|
+
stateManager.markRendered([]);
|
|
476
|
+
};
|
|
477
|
+
let cleanupInteractiveSession = () => {
|
|
294
478
|
process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
|
|
295
479
|
ui_1.CursorUtils.show();
|
|
296
|
-
|
|
297
|
-
if (process.stdin.setRawMode) {
|
|
298
|
-
process.stdin.setRawMode(false);
|
|
299
|
-
}
|
|
300
|
-
process.stdin.removeAllListeners('keypress');
|
|
480
|
+
process.stdin.off('keypress', keypressHandler);
|
|
301
481
|
process.stdin.pause();
|
|
302
|
-
process.
|
|
303
|
-
|
|
482
|
+
process.off('SIGWINCH', handleResize);
|
|
483
|
+
this.refreshView = undefined;
|
|
484
|
+
};
|
|
485
|
+
const finalizeSelection = (selectedStates) => {
|
|
486
|
+
isResolved = true;
|
|
487
|
+
this.packageInfoModalController.cancel();
|
|
488
|
+
releaseInteractiveScreen();
|
|
489
|
+
cleanupInteractiveSession();
|
|
490
|
+
resolve(selectedStates);
|
|
304
491
|
};
|
|
305
|
-
const inputHandler = new ui_1.InputHandler(stateManager, handleAction, handleConfirm, handleCancel);
|
|
306
492
|
const renderInterface = () => {
|
|
307
493
|
const uiState = stateManager.getUIState();
|
|
308
|
-
const filteredStates = stateManager.getFilteredStates(states);
|
|
494
|
+
const filteredStates = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
|
|
495
|
+
const auditProgress = this.vulnerabilityAuditController.getProgress();
|
|
309
496
|
// Apply terminal background color
|
|
310
497
|
const bgCode = (0, themes_colors_1.getTerminalBgColorCode)();
|
|
311
498
|
process.stdout.write(bgCode);
|
|
312
499
|
if (uiState.forceFullRender) {
|
|
313
|
-
|
|
500
|
+
ui_1.CursorUtils.clearScreen();
|
|
314
501
|
ui_1.CursorUtils.hide();
|
|
315
502
|
}
|
|
316
503
|
else {
|
|
@@ -321,62 +508,48 @@ class InteractiveUI {
|
|
|
321
508
|
const terminalWidth = process.stdout.columns || 80;
|
|
322
509
|
const terminalHeight = this.getTerminalHeight();
|
|
323
510
|
const themeManager = stateManager.getThemeManager();
|
|
324
|
-
// Render header
|
|
325
|
-
const headerLines = [];
|
|
326
|
-
headerLines.push(' ' + chalk_1.default.bold.magenta('🚀 inup'));
|
|
327
|
-
headerLines.push('');
|
|
328
|
-
headerLines.push(' ' +
|
|
329
|
-
chalk_1.default.bold.white('T ') +
|
|
330
|
-
chalk_1.default.gray('/ Esc Exit theme selector'));
|
|
331
|
-
headerLines.push('');
|
|
332
|
-
headerLines.forEach((line) => console.log(line));
|
|
333
511
|
const modalLines = this.renderer.renderThemeSelectorModal(themeManager.getCurrentTheme(), themeManager.getPreviewTheme(), terminalWidth, terminalHeight);
|
|
334
|
-
|
|
335
|
-
// Clear any remaining lines from previous render
|
|
336
|
-
ui_1.CursorUtils.clearToEndOfScreen();
|
|
337
|
-
stateManager.markRendered([]);
|
|
512
|
+
renderModalViewport('theme-modal', chalk_1.default.bold.white('T ') + chalk_1.default.gray('/ Esc Exit theme selector'), modalLines, terminalWidth, terminalHeight, bgCode);
|
|
338
513
|
}
|
|
339
|
-
else if (uiState.showInfoModal &&
|
|
514
|
+
else if (uiState.showInfoModal &&
|
|
515
|
+
uiState.infoModalRow >= 0 &&
|
|
516
|
+
uiState.infoModalRow < filteredStates.length) {
|
|
340
517
|
const selectedState = filteredStates[uiState.infoModalRow];
|
|
341
518
|
const terminalWidth = process.stdout.columns || 80;
|
|
342
519
|
const terminalHeight = this.getTerminalHeight();
|
|
343
|
-
// Render header
|
|
344
|
-
const headerLines = [];
|
|
345
|
-
headerLines.push(' ' + chalk_1.default.bold.magenta('🚀 inup'));
|
|
346
|
-
headerLines.push('');
|
|
347
|
-
headerLines.push(' ' +
|
|
348
|
-
chalk_1.default.bold.white('I / Esc ') +
|
|
349
|
-
chalk_1.default.gray('Exit this view'));
|
|
350
|
-
headerLines.push('');
|
|
351
|
-
headerLines.forEach((line) => console.log(line));
|
|
352
520
|
if (uiState.isLoadingModalInfo) {
|
|
353
521
|
// Show loading state
|
|
354
|
-
const
|
|
355
|
-
|
|
522
|
+
const result = this.renderer.renderPackageInfoLoading(selectedState, terminalWidth, Math.max(8, terminalHeight - 4));
|
|
523
|
+
infoModalMaxScrollOffset = result.maxScrollOffset;
|
|
524
|
+
renderModalViewport('info-modal', chalk_1.default.bold.white('I / Esc ') + chalk_1.default.gray('Exit this view'), result.lines, terminalWidth, terminalHeight, bgCode);
|
|
356
525
|
}
|
|
357
526
|
else {
|
|
358
|
-
// Show full info
|
|
359
|
-
const
|
|
360
|
-
|
|
527
|
+
// Show full info with scroll support
|
|
528
|
+
const result = this.renderer.renderPackageInfoModal(selectedState, terminalWidth, Math.max(8, terminalHeight - 4), uiState.infoModalScrollOffset);
|
|
529
|
+
infoModalMaxScrollOffset = result.maxScrollOffset;
|
|
530
|
+
stateManager.clampInfoModalScrollOffset(infoModalMaxScrollOffset);
|
|
531
|
+
const scrollHint = result.usesInternalScroll && result.maxScrollOffset > 0
|
|
532
|
+
? chalk_1.default.bold.white('↑/↓ ') + chalk_1.default.gray('Scroll · ')
|
|
533
|
+
: '';
|
|
534
|
+
renderModalViewport('info-modal', scrollHint +
|
|
535
|
+
chalk_1.default.bold.white('←/→ ') +
|
|
536
|
+
chalk_1.default.gray('Version · ') +
|
|
537
|
+
chalk_1.default.bold.white('I / Esc ') +
|
|
538
|
+
chalk_1.default.gray('Exit this view'), result.lines, terminalWidth, terminalHeight, bgCode);
|
|
361
539
|
}
|
|
362
|
-
// Clear any remaining lines from previous render
|
|
363
|
-
ui_1.CursorUtils.clearToEndOfScreen();
|
|
364
|
-
stateManager.markRendered([]);
|
|
365
540
|
}
|
|
366
541
|
else {
|
|
367
542
|
// Normal list view (flat rendering - no grouping)
|
|
368
543
|
const terminalWidth = process.stdout.columns || 80;
|
|
544
|
+
const terminalHeight = this.getTerminalHeight();
|
|
369
545
|
const activeFilterLabel = stateManager.getActiveFilterLabel();
|
|
370
546
|
const lines = this.renderer.renderInterface(filteredStates, uiState.currentRow, uiState.scrollOffset, uiState.maxVisibleItems, uiState.forceFullRender, [], // No renderable items - use flat rendering
|
|
371
547
|
activeFilterLabel, // Show current dependency type filter state
|
|
372
548
|
this.packageManager, // Pass package manager info for header
|
|
373
|
-
uiState.filterMode, uiState.filterQuery, states.length, terminalWidth);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (!uiState.forceFullRender) {
|
|
378
|
-
ui_1.CursorUtils.clearToEndOfScreen();
|
|
379
|
-
}
|
|
549
|
+
uiState.filterMode, uiState.filterQuery, states.length, terminalWidth, loadingProgress, auditProgress, packageListRenderOptions);
|
|
550
|
+
renderViewport(lines, terminalWidth, terminalHeight, bgCode);
|
|
551
|
+
previousViewportMode = 'list';
|
|
552
|
+
previousModalViewportLineCount = null;
|
|
380
553
|
stateManager.markRendered(lines);
|
|
381
554
|
}
|
|
382
555
|
stateManager.setInitialRender(false);
|
|
@@ -389,27 +562,41 @@ class InteractiveUI {
|
|
|
389
562
|
};
|
|
390
563
|
// Setup keypress handling
|
|
391
564
|
try {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
565
|
+
claimInteractiveScreen();
|
|
566
|
+
this.refreshView = () => {
|
|
567
|
+
if (!isResolved) {
|
|
568
|
+
renderInterface();
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
attachRefresh?.(() => {
|
|
572
|
+
if (!isResolved) {
|
|
573
|
+
renderInterface();
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
const keypressSession = ui_1.TerminalInput.startKeypressSession(keypressHandler);
|
|
577
|
+
const previousCleanup = cleanupInteractiveSession;
|
|
578
|
+
cleanupInteractiveSession = () => {
|
|
579
|
+
keypressSession.close();
|
|
580
|
+
previousCleanup();
|
|
581
|
+
};
|
|
398
582
|
// Setup resize handler
|
|
399
583
|
process.on('SIGWINCH', handleResize);
|
|
400
584
|
// Update terminal height directly before initial render to ensure correct dimensions
|
|
401
585
|
// This handles cases where process.stdout.rows might not be accurate at startup
|
|
402
586
|
const currentHeight = this.getTerminalHeight();
|
|
403
587
|
if (stateManager.updateTerminalHeight(currentHeight)) {
|
|
404
|
-
const initialFiltered = stateManager.getFilteredStates(states);
|
|
588
|
+
const initialFiltered = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
|
|
405
589
|
stateManager.resetForResize(initialFiltered.length);
|
|
406
590
|
}
|
|
407
591
|
// Initial render
|
|
408
592
|
renderInterface();
|
|
593
|
+
this.enqueueSecurityAudit(states);
|
|
409
594
|
}
|
|
410
595
|
catch (error) {
|
|
596
|
+
releaseInteractiveScreen();
|
|
411
597
|
// Reset terminal colors
|
|
412
598
|
process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
|
|
599
|
+
this.refreshView = undefined;
|
|
413
600
|
// Fallback to simple interface if raw mode fails
|
|
414
601
|
console.log(chalk_1.default.yellow('Raw mode not available, using fallback interface...'));
|
|
415
602
|
resolve(states);
|
|
@@ -419,32 +606,27 @@ class InteractiveUI {
|
|
|
419
606
|
async confirmUpgrade(choices) {
|
|
420
607
|
console.log(this.renderer.renderConfirmation(choices));
|
|
421
608
|
return new Promise((resolve) => {
|
|
609
|
+
let cleanupConfirmationSession = () => {
|
|
610
|
+
ui_1.CursorUtils.show();
|
|
611
|
+
};
|
|
422
612
|
const handleConfirm = (confirmed) => {
|
|
613
|
+
cleanupConfirmationSession();
|
|
423
614
|
resolve(confirmed);
|
|
424
615
|
};
|
|
425
616
|
const inputHandler = new ui_1.ConfirmationInputHandler(handleConfirm);
|
|
617
|
+
const keypressHandler = (str, key) => inputHandler.handleKeypress(str, key);
|
|
426
618
|
// Setup keypress handling
|
|
427
619
|
try {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
620
|
+
const keypressSession = ui_1.TerminalInput.startKeypressSession(keypressHandler);
|
|
621
|
+
cleanupConfirmationSession = () => {
|
|
622
|
+
keypressSession.close();
|
|
623
|
+
ui_1.CursorUtils.show();
|
|
624
|
+
};
|
|
432
625
|
ui_1.CursorUtils.hide();
|
|
433
|
-
process.stdin.resume();
|
|
434
|
-
process.stdin.on('keypress', (str, key) => inputHandler.handleKeypress(str, key));
|
|
435
626
|
}
|
|
436
627
|
catch (error) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
.prompt([
|
|
440
|
-
{
|
|
441
|
-
type: 'confirm',
|
|
442
|
-
name: 'proceed',
|
|
443
|
-
message: 'Proceed with upgrade?',
|
|
444
|
-
default: true,
|
|
445
|
-
},
|
|
446
|
-
])
|
|
447
|
-
.then((answer) => resolve(answer.proceed))
|
|
628
|
+
ui_1.TerminalInput.promptForConfirmation('Proceed with upgrade? [Y/n] ')
|
|
629
|
+
.then(resolve)
|
|
448
630
|
.catch(() => resolve(false));
|
|
449
631
|
}
|
|
450
632
|
});
|