inup 1.5.5 → 1.6.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.
Files changed (75) hide show
  1. package/README.md +53 -10
  2. package/dist/cli.js +53 -31
  3. package/dist/config/constants.js +4 -6
  4. package/dist/config/package-meta.js +10 -0
  5. package/dist/config/project-config.js +11 -1
  6. package/dist/core/package-detector.js +37 -5
  7. package/dist/core/upgrade-runner.js +8 -10
  8. package/dist/core/upgrader.js +15 -11
  9. package/dist/features/changelog/clients/github-client.js +5 -6
  10. package/dist/features/changelog/services/changelog-service.js +2 -4
  11. package/dist/features/changelog/services/package-metadata-service.js +7 -23
  12. package/dist/features/changelog/services/release-notes-service.js +4 -29
  13. package/dist/features/debug/services/performance-tracker.js +6 -8
  14. package/dist/features/headless/headless-runner.js +53 -0
  15. package/dist/features/headless/index.js +27 -0
  16. package/dist/features/headless/report.js +64 -0
  17. package/dist/features/headless/types.js +6 -0
  18. package/dist/features/headless/vulnerability-audit.js +106 -0
  19. package/dist/index.js +1 -2
  20. package/dist/interactive-ui.js +15 -606
  21. package/dist/services/background-audit.js +3 -5
  22. package/dist/services/cache-manager.js +5 -7
  23. package/dist/services/http/inflight.js +22 -0
  24. package/dist/services/http/retry.js +30 -0
  25. package/dist/services/index.js +0 -1
  26. package/dist/services/npm-registry.js +20 -97
  27. package/dist/services/package-manager-detector.js +6 -3
  28. package/dist/services/persistent-cache.js +8 -13
  29. package/dist/types/domain.js +3 -0
  30. package/dist/types/streaming.js +3 -0
  31. package/dist/types/ui.js +3 -0
  32. package/dist/types.js +17 -0
  33. package/dist/ui/controllers/package-info-modal-controller.js +22 -31
  34. package/dist/ui/controllers/vulnerability-audit-controller.js +17 -8
  35. package/dist/ui/index.js +3 -0
  36. package/dist/ui/input-handler.js +58 -75
  37. package/dist/ui/keymap.js +208 -0
  38. package/dist/ui/modal/package-info-sections/release-notes.js +161 -0
  39. package/dist/ui/modal/package-info-sections/sections.js +174 -0
  40. package/dist/ui/modal/package-info-sections/text.js +75 -0
  41. package/dist/ui/modal/package-info-sections.js +15 -369
  42. package/dist/ui/modal/package-info.js +1 -1
  43. package/dist/ui/presenters/health.js +24 -0
  44. package/dist/ui/renderer/help-modal.js +75 -0
  45. package/dist/ui/renderer/index.js +2 -5
  46. package/dist/ui/renderer/package-list/interface.js +184 -0
  47. package/dist/ui/renderer/package-list/rows.js +177 -0
  48. package/dist/ui/renderer/package-list.js +15 -455
  49. package/dist/ui/session/action-dispatcher.js +233 -0
  50. package/dist/ui/session/index.js +13 -0
  51. package/dist/ui/session/interactive-session.js +280 -0
  52. package/dist/ui/session/selection-state-builder.js +149 -0
  53. package/dist/ui/state/filter-manager.js +17 -6
  54. package/dist/ui/state/modal-manager.js +35 -0
  55. package/dist/ui/state/navigation-manager.js +33 -4
  56. package/dist/ui/state/state-manager.js +68 -3
  57. package/dist/ui/state/theme-manager.js +1 -0
  58. package/dist/ui/themes-colors.js +16 -4
  59. package/dist/ui/themes.js +11 -19
  60. package/dist/ui/utils/cursor.js +12 -7
  61. package/dist/ui/utils/index.js +6 -1
  62. package/dist/ui/utils/text.js +3 -2
  63. package/dist/ui/utils/version.js +60 -86
  64. package/dist/utils/color.js +38 -0
  65. package/dist/utils/config.js +13 -1
  66. package/dist/utils/engines.js +63 -0
  67. package/dist/utils/filesystem/io.js +103 -0
  68. package/dist/utils/filesystem/paths.js +19 -0
  69. package/dist/utils/filesystem/scan.js +280 -0
  70. package/dist/utils/filesystem.js +17 -335
  71. package/dist/utils/index.js +3 -0
  72. package/dist/utils/manifest.js +35 -0
  73. package/dist/utils/version.js +38 -1
  74. package/package.json +8 -7
  75. package/dist/services/jsdelivr-registry.js +0 -395
@@ -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 themes_1 = require("./ui/themes");
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,12 +17,18 @@ function normalizeVulnerabilityDisplayOptions(options) {
57
17
  };
58
18
  }
59
19
  class InteractiveUI {
20
+ renderer;
21
+ packageManager;
22
+ options;
23
+ saveExact;
24
+ vulnerabilityAuditController = new controllers_1.VulnerabilityAuditController();
25
+ packageInfoModalController = new controllers_1.PackageInfoModalController();
26
+ refreshView;
60
27
  constructor(packageManager, options) {
61
- this.vulnerabilityAuditController = new controllers_1.VulnerabilityAuditController();
62
- this.packageInfoModalController = new controllers_1.PackageInfoModalController();
63
28
  this.renderer = new ui_1.UIRenderer();
64
29
  this.packageManager = packageManager;
65
30
  this.options = normalizeVulnerabilityDisplayOptions(options);
31
+ this.saveExact = options?.saveExact ?? false;
66
32
  }
67
33
  async displayPackagesTable(packages) {
68
34
  console.log(this.renderer.renderPackagesTable(packages));
@@ -72,65 +38,14 @@ class InteractiveUI {
72
38
  if (selectionStates.length === 0) {
73
39
  return [];
74
40
  }
75
- const selectedStates = await this.interactiveTableSelector(selectionStates);
76
- return this.createUpgradeChoices(selectedStates);
41
+ const selectedStates = await (0, session_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => { this.refreshView = refresh; });
42
+ return (0, session_1.createUpgradeChoices)(selectedStates, this.saveExact);
77
43
  }
78
44
  createSelectionStates(packages, previousSelections, includeUpToDate = true) {
79
- const relevantPackages = includeUpToDate ? packages : packages.filter((p) => p.isOutdated);
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
- });
45
+ return (0, session_1.createSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections, includeUpToDate);
104
46
  }
105
47
  createPendingSelectionStates(packages, previousSelections) {
106
- const uniquePackages = this.deduplicatePackages(packages.map((pkg) => ({
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
- });
48
+ return (0, session_1.createPendingSelectionStates)(packages, (name, version, type) => this.vulnerabilityAuditController.getCachedSummary(name, version, type), previousSelections);
134
49
  }
135
50
  appendOutdatedBatchToSelectionStates(selectionStates, batch, previousSelections) {
136
51
  const outdatedStates = this.createSelectionStates(batch.flatMap((batchItem) => batchItem.packageInfo).filter((pkg) => pkg.isOutdated), previousSelections, false);
@@ -149,518 +64,12 @@ class InteractiveUI {
149
64
  }
150
65
  async selectPackagesToUpgradeProgressive(selectionStates, progress, attachRefresh) {
151
66
  this.enqueueSecurityAudit(selectionStates);
152
- const selectedStates = await this.interactiveTableSelector(selectionStates, progress, attachRefresh);
153
- return this.createUpgradeChoices(selectedStates);
67
+ const selectedStates = await (0, session_1.runInteractiveSession)(selectionStates, this.packageManager, this.renderer, this.packageInfoModalController, this.vulnerabilityAuditController, this.options, (refresh) => { this.refreshView = refresh; }, progress, attachRefresh);
68
+ return (0, session_1.createUpgradeChoices)(selectedStates, this.saveExact);
154
69
  }
155
70
  enqueueSecurityAudit(selectionStates) {
156
71
  this.vulnerabilityAuditController.enqueueStates(selectionStates, () => this.refreshView?.());
157
72
  }
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 key = (text) => chalk_1.default.bold.white(text);
474
- const hint = (text) => chalk_1.default.gray(text);
475
- const sep = hint(' · ');
476
- const SHORTCUTS = {
477
- scroll: key('↑/↓ ') + hint('Scroll'),
478
- version: key('←/→ ') + hint('Version'),
479
- switchTab: key('Tab ') + hint('Switch tab'),
480
- closeInfo: key('I / Esc ') + hint('Close'),
481
- closeTheme: key('T / Esc ') + hint('Close'),
482
- };
483
- const buildModalHeaderLines = (shortcutLabel) => [
484
- ' ' + chalk_1.default.bold('🚀 ') + (0, themes_colors_1.coloredInupLogo)(),
485
- '',
486
- ' ' + shortcutLabel,
487
- '',
488
- ];
489
- const renderViewport = (lines, terminalWidth, terminalHeight, bgCode) => {
490
- const viewportLines = [
491
- ...lines,
492
- ...buildRemainingViewport(terminalWidth, terminalHeight, lines.length),
493
- ];
494
- writeFrame(viewportLines, bgCode);
495
- };
496
- const renderModalViewport = (mode, shortcutLabel, modalLines, terminalWidth, terminalHeight, bgCode) => {
497
- const viewportLineCount = buildModalHeaderLines(shortcutLabel).length + modalLines.length;
498
- const shouldClearBeforeRender = previousViewportMode !== mode || previousModalViewportLineCount !== viewportLineCount;
499
- if (shouldClearBeforeRender) {
500
- ui_1.CursorUtils.clearScreen();
501
- ui_1.CursorUtils.hide();
502
- }
503
- renderViewport([...buildModalHeaderLines(shortcutLabel), ...modalLines], terminalWidth, terminalHeight, bgCode);
504
- previousViewportMode = mode;
505
- previousModalViewportLineCount = viewportLineCount;
506
- stateManager.markRendered([]);
507
- };
508
- let cleanupInteractiveSession = () => {
509
- process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
510
- ui_1.CursorUtils.show();
511
- process.stdin.off('keypress', keypressHandler);
512
- process.stdin.pause();
513
- process.off('SIGWINCH', handleResize);
514
- this.refreshView = undefined;
515
- };
516
- // Safety net: restore terminal if the process exits without going through finalizeSelection.
517
- // Only synchronous writes work in an 'exit' handler, but that's all we need here.
518
- const emergencyCleanup = () => {
519
- if (ownsAlternateScreen) {
520
- process.stdout.write('\x1b[?1049l');
521
- }
522
- process.stdout.write('\x1b[?25h');
523
- if (process.stdin.setRawMode) {
524
- process.stdin.setRawMode(false);
525
- }
526
- };
527
- process.on('exit', emergencyCleanup);
528
- const finalizeSelection = (selectedStates) => {
529
- isResolved = true;
530
- this.packageInfoModalController.cancel();
531
- releaseInteractiveScreen();
532
- cleanupInteractiveSession();
533
- process.off('exit', emergencyCleanup);
534
- resolve(selectedStates);
535
- };
536
- const renderInterface = () => {
537
- const uiState = stateManager.getUIState();
538
- const filteredStates = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
539
- const auditProgress = this.vulnerabilityAuditController.getProgress();
540
- // Apply terminal background color
541
- const bgCode = (0, themes_colors_1.getTerminalBgColorCode)();
542
- process.stdout.write(bgCode);
543
- if (uiState.forceFullRender) {
544
- ui_1.CursorUtils.clearScreen();
545
- ui_1.CursorUtils.hide();
546
- }
547
- else {
548
- ui_1.CursorUtils.moveToHome();
549
- }
550
- // If theme modal is open, render only the theme selector
551
- if (uiState.showThemeModal) {
552
- const terminalWidth = process.stdout.columns || 80;
553
- const terminalHeight = this.getTerminalHeight();
554
- const themeManager = stateManager.getThemeManager();
555
- const modalLines = this.renderer.renderThemeSelectorModal(themeManager.getCurrentTheme(), themeManager.getPreviewTheme(), terminalWidth, terminalHeight);
556
- renderModalViewport('theme-modal', SHORTCUTS.closeTheme, modalLines, terminalWidth, terminalHeight, bgCode);
557
- }
558
- else if (uiState.showDebugModal) {
559
- const terminalWidth = process.stdout.columns || 80;
560
- const terminalHeight = this.getTerminalHeight();
561
- const snapshot = (0, debug_1.getPerformanceTracker)().snapshot();
562
- const result = (0, debug_1.renderPerformanceModal)(snapshot, terminalWidth, Math.max(8, terminalHeight - 4), uiState.debugModalScrollOffset);
563
- debugModalMaxScrollOffset = result.maxScrollOffset;
564
- stateManager.clampDebugModalScrollOffset(debugModalMaxScrollOffset);
565
- const debugHints = [
566
- result.usesInternalScroll && result.maxScrollOffset > 0 ? SHORTCUTS.scroll : '',
567
- SHORTCUTS.closeInfo,
568
- ].filter(Boolean).join(sep);
569
- renderModalViewport('info-modal', debugHints, result.lines, terminalWidth, terminalHeight, bgCode);
570
- }
571
- else if (uiState.showInfoModal &&
572
- uiState.infoModalRow >= 0 &&
573
- uiState.infoModalRow < filteredStates.length) {
574
- const selectedState = filteredStates[uiState.infoModalRow];
575
- const terminalWidth = process.stdout.columns || 80;
576
- const terminalHeight = this.getTerminalHeight();
577
- if (uiState.isLoadingModalInfo) {
578
- // Show loading state
579
- const result = this.renderer.renderPackageInfoLoading(selectedState, terminalWidth, Math.max(8, terminalHeight - 4));
580
- infoModalMaxScrollOffset = result.maxScrollOffset;
581
- renderModalViewport('info-modal', SHORTCUTS.closeInfo, result.lines, terminalWidth, terminalHeight, bgCode);
582
- }
583
- else {
584
- // Show full info with scroll support
585
- const activeTab = uiState.infoModalTab;
586
- const result = this.renderer.renderPackageInfoModal(selectedState, terminalWidth, Math.max(8, terminalHeight - 4), uiState.infoModalScrollOffset, activeTab);
587
- infoModalMaxScrollOffset = result.maxScrollOffset;
588
- stateManager.clampInfoModalScrollOffset(infoModalMaxScrollOffset);
589
- const hints = [
590
- result.usesInternalScroll && result.maxScrollOffset > 0 ? SHORTCUTS.scroll : '',
591
- activeTab === 'info' ? SHORTCUTS.version : '',
592
- SHORTCUTS.switchTab,
593
- SHORTCUTS.closeInfo,
594
- ].filter(Boolean).join(sep);
595
- renderModalViewport('info-modal', hints, result.lines, terminalWidth, terminalHeight, bgCode);
596
- }
597
- }
598
- else {
599
- // Normal list view (flat rendering - no grouping)
600
- const terminalWidth = process.stdout.columns || 80;
601
- const terminalHeight = this.getTerminalHeight();
602
- const activeFilterLabel = stateManager.getActiveFilterLabel();
603
- const lines = this.renderer.renderInterface(filteredStates, uiState.currentRow, uiState.scrollOffset, uiState.maxVisibleItems, uiState.forceFullRender, [], // No renderable items - use flat rendering
604
- activeFilterLabel, // Show current dependency type filter state
605
- this.packageManager, // Pass package manager info for header
606
- uiState.filterMode, uiState.filterQuery, states.length, terminalWidth, loadingProgress, auditProgress, packageListRenderOptions);
607
- renderViewport(lines, terminalWidth, terminalHeight, bgCode);
608
- previousViewportMode = 'list';
609
- previousModalViewportLineCount = null;
610
- stateManager.markRendered(lines);
611
- }
612
- stateManager.setInitialRender(false);
613
- };
614
- const handleResize = () => {
615
- // On resize (width or height change), always trigger a re-render
616
- // This prevents layout breaking when terminal width changes
617
- // The action handler will update height and force a full re-render
618
- inputHandler.handleResize(this.getTerminalHeight());
619
- };
620
- // Setup keypress handling
621
- try {
622
- claimInteractiveScreen();
623
- this.refreshView = () => {
624
- if (!isResolved) {
625
- renderInterface();
626
- }
627
- };
628
- attachRefresh?.(() => {
629
- if (!isResolved) {
630
- renderInterface();
631
- }
632
- });
633
- const keypressSession = ui_1.TerminalInput.startKeypressSession(keypressHandler);
634
- const previousCleanup = cleanupInteractiveSession;
635
- cleanupInteractiveSession = () => {
636
- keypressSession.close();
637
- previousCleanup();
638
- };
639
- // Setup resize handler
640
- process.on('SIGWINCH', handleResize);
641
- // Update terminal height directly before initial render to ensure correct dimensions
642
- // This handles cases where process.stdout.rows might not be accurate at startup
643
- const currentHeight = this.getTerminalHeight();
644
- if (stateManager.updateTerminalHeight(currentHeight)) {
645
- const initialFiltered = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
646
- stateManager.resetForResize(initialFiltered.length);
647
- }
648
- // Initial render
649
- renderInterface();
650
- this.enqueueSecurityAudit(states);
651
- }
652
- catch (error) {
653
- process.off('exit', emergencyCleanup);
654
- releaseInteractiveScreen();
655
- // Reset terminal colors
656
- process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
657
- this.refreshView = undefined;
658
- // Fallback to simple interface if raw mode fails
659
- console.log(chalk_1.default.yellow('Raw mode not available, using fallback interface...'));
660
- resolve(states);
661
- }
662
- });
663
- }
664
73
  async confirmUpgrade(choices) {
665
74
  console.log(this.renderer.renderConfirmation(choices));
666
75
  return new Promise((resolve) => {