inup 1.4.12 → 1.5.1

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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -30
  3. package/dist/cli.js +29 -14
  4. package/dist/config/project-config.js +6 -0
  5. package/dist/core/package-detector.js +3 -2
  6. package/dist/core/upgrade-runner.js +6 -3
  7. package/dist/features/changelog/clients/github-client.js +134 -0
  8. package/dist/features/changelog/clients/npm-registry-client.js +53 -0
  9. package/dist/features/changelog/index.js +19 -0
  10. package/dist/features/changelog/parsers/changelog-parser.js +68 -0
  11. package/dist/features/changelog/parsers/github-release-html-parser.js +61 -0
  12. package/dist/features/changelog/parsers/package-metadata.js +34 -0
  13. package/dist/features/changelog/parsers/repository-ref.js +26 -0
  14. package/dist/features/changelog/services/changelog-service.js +30 -0
  15. package/dist/features/changelog/services/package-metadata-service.js +108 -0
  16. package/dist/features/changelog/services/release-notes-service.js +180 -0
  17. package/dist/features/changelog/types/changelog.types.js +3 -0
  18. package/dist/interactive-ui.js +242 -114
  19. package/dist/services/background-audit.js +60 -0
  20. package/dist/services/index.js +3 -1
  21. package/dist/services/vulnerability-checker.js +133 -0
  22. package/dist/ui/controllers/index.js +8 -0
  23. package/dist/ui/controllers/package-info-modal-controller.js +237 -0
  24. package/dist/ui/controllers/vulnerability-audit-controller.js +82 -0
  25. package/dist/ui/index.js +3 -0
  26. package/dist/ui/input-handler.js +40 -9
  27. package/dist/ui/modal/index.js +22 -0
  28. package/dist/ui/modal/layout.js +84 -0
  29. package/dist/ui/modal/package-info-sections.js +327 -0
  30. package/dist/ui/modal/package-info.js +147 -0
  31. package/dist/ui/modal/theme-selector.js +46 -0
  32. package/dist/ui/modal/types.js +3 -0
  33. package/dist/ui/presenters/index.js +11 -0
  34. package/dist/ui/presenters/vulnerability.js +76 -0
  35. package/dist/ui/renderer/index.js +9 -11
  36. package/dist/ui/renderer/package-list.js +135 -62
  37. package/dist/ui/state/filter-manager.js +17 -2
  38. package/dist/ui/state/modal-manager.js +48 -6
  39. package/dist/ui/state/state-manager.js +42 -7
  40. package/dist/ui/utils/cursor.js +18 -0
  41. package/dist/ui/utils/index.js +8 -1
  42. package/dist/ui/utils/terminal-input.js +125 -0
  43. package/dist/ui/utils/text.js +75 -0
  44. package/dist/ui/utils/version.js +3 -2
  45. package/dist/utils/git.js +33 -0
  46. package/dist/utils/index.js +1 -0
  47. package/package.json +22 -19
  48. package/dist/services/changelog-fetcher.js +0 -215
  49. package/dist/ui/renderer/modal.js +0 -190
  50. package/dist/ui/renderer/theme-selector.js +0 -83
@@ -37,18 +37,31 @@ 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 services_1 = require("./services");
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));
@@ -83,6 +96,8 @@ class InteractiveUI {
83
96
  hasRangeUpdate: pkg.hasRangeUpdate,
84
97
  hasMajorUpdate: pkg.hasMajorUpdate,
85
98
  type: pkg.type,
99
+ vulnerability: this.vulnerabilityAuditController.getCachedSummary(pkg.name, pkg.currentVersion, pkg.type),
100
+ allVersions: pkg.allVersions,
86
101
  };
87
102
  });
88
103
  }
@@ -112,6 +127,7 @@ class InteractiveUI {
112
127
  hasRangeUpdate: false,
113
128
  hasMajorUpdate: false,
114
129
  type: pkg.type,
130
+ vulnerability: this.vulnerabilityAuditController.getCachedSummary(pkg.name, pkg.currentVersion, pkg.type),
115
131
  };
116
132
  });
117
133
  }
@@ -128,11 +144,16 @@ class InteractiveUI {
128
144
  seen.add(key);
129
145
  }
130
146
  });
147
+ this.enqueueSecurityAudit(selectionStates);
131
148
  }
132
149
  async selectPackagesToUpgradeProgressive(selectionStates, progress, attachRefresh) {
150
+ this.enqueueSecurityAudit(selectionStates);
133
151
  const selectedStates = await this.interactiveTableSelector(selectionStates, progress, attachRefresh);
134
152
  return this.createUpgradeChoices(selectedStates);
135
153
  }
154
+ enqueueSecurityAudit(selectionStates) {
155
+ this.vulnerabilityAuditController.enqueueStates(selectionStates, () => this.refreshView?.());
156
+ }
136
157
  deduplicatePackages(packages) {
137
158
  const uniquePackages = new Map();
138
159
  for (const pkg of packages) {
@@ -180,7 +201,9 @@ class InteractiveUI {
180
201
  }
181
202
  getTerminalHeight() {
182
203
  // Check if stdout is a TTY and has rows property
183
- if (process.stdout.isTTY && typeof process.stdout.rows === 'number' && process.stdout.rows > 0) {
204
+ if (process.stdout.isTTY &&
205
+ typeof process.stdout.rows === 'number' &&
206
+ process.stdout.rows > 0) {
184
207
  return process.stdout.rows;
185
208
  }
186
209
  return 24; // Fallback default
@@ -190,84 +213,150 @@ class InteractiveUI {
190
213
  const states = selectionStates;
191
214
  const stateManager = new ui_1.StateManager(0, this.getTerminalHeight());
192
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
+ };
193
234
  // No grouping needed - packages are already filtered by type
194
235
  // This simplifies scrolling and avoids rendering issues
195
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;
196
241
  const handleAction = (action) => {
197
242
  const uiState = stateManager.getUIState();
198
- const filteredStates = stateManager.getFilteredStates(states);
243
+ const filteredStates = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
199
244
  switch (action.type) {
200
245
  case 'navigate_up':
201
- if (!uiState.showInfoModal && !uiState.showThemeModal) {
246
+ if (!uiState.showThemeModal) {
202
247
  stateManager.navigateUp(filteredStates.length);
203
248
  }
204
249
  break;
205
250
  case 'navigate_down':
206
- if (!uiState.showInfoModal && !uiState.showThemeModal) {
251
+ if (!uiState.showThemeModal) {
207
252
  stateManager.navigateDown(filteredStates.length);
208
253
  }
209
254
  break;
210
255
  case 'select_left':
211
- if (!uiState.showInfoModal && !uiState.showThemeModal) {
256
+ if (!uiState.showThemeModal) {
212
257
  stateManager.updateSelection(filteredStates, 'left');
213
258
  }
214
259
  break;
215
260
  case 'select_right':
216
- if (!uiState.showInfoModal && !uiState.showThemeModal) {
261
+ if (!uiState.showThemeModal) {
217
262
  stateManager.updateSelection(filteredStates, 'right');
218
263
  }
219
264
  break;
220
265
  case 'bulk_select_minor':
221
- if (!uiState.showInfoModal && !uiState.showThemeModal) {
266
+ if (!uiState.showThemeModal) {
222
267
  stateManager.bulkSelectMinor(filteredStates);
223
268
  }
224
269
  break;
225
270
  case 'bulk_select_latest':
226
- if (!uiState.showInfoModal && !uiState.showThemeModal) {
271
+ if (!uiState.showThemeModal) {
227
272
  stateManager.bulkSelectLatest(filteredStates);
228
273
  }
229
274
  break;
230
275
  case 'bulk_unselect_all':
231
- if (!uiState.showInfoModal && !uiState.showThemeModal) {
276
+ if (!uiState.showThemeModal) {
232
277
  stateManager.bulkUnselectAll(filteredStates);
233
278
  }
234
279
  break;
235
280
  case 'toggle_dep_type_filter':
236
- if (!uiState.showInfoModal && !uiState.showThemeModal) {
281
+ if (!uiState.showThemeModal) {
237
282
  stateManager.toggleDependencyTypeFilter(action.depType);
238
283
  }
239
284
  break;
240
285
  case 'toggle_info_modal':
241
286
  if (!uiState.showInfoModal) {
242
287
  // Opening modal - load package info asynchronously
243
- stateManager.toggleInfoModal();
288
+ const modalSessionId = stateManager.toggleInfoModal();
244
289
  const currentState = filteredStates[uiState.currentRow];
245
290
  const canFetchMetadata = currentState?.loadState === 'ready';
246
- stateManager.setModalLoading(canFetchMetadata);
291
+ stateManager.setModalLoading(canFetchMetadata, modalSessionId);
247
292
  renderInterface();
248
293
  if (currentState && canFetchMetadata) {
249
- services_1.changelogFetcher
250
- .fetchPackageMetadata(currentState.name, currentState.latestVersion)
251
- .then((metadata) => {
252
- if (metadata) {
253
- currentState.description = metadata.description;
254
- currentState.homepage = metadata.homepage;
255
- currentState.repository = metadata.releaseNotes;
256
- currentState.weeklyDownloads = metadata.weeklyDownloads;
257
- currentState.author = metadata.author;
258
- currentState.license = metadata.license;
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
+ });
259
308
  }
260
- stateManager.setModalLoading(false);
309
+ })
310
+ .catch(() => {
311
+ if (isResolved || stateManager.getInfoModalSessionId() !== modalSessionId)
312
+ return;
313
+ stateManager.setModalLoading(false, modalSessionId);
261
314
  renderInterface();
262
315
  });
263
316
  }
264
317
  }
265
318
  else {
266
- // Closing modal
319
+ // Closing modal - cancel in-flight fetches
320
+ this.packageInfoModalController.cancel();
267
321
  stateManager.toggleInfoModal();
268
322
  renderInterface();
269
323
  }
270
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;
271
360
  case 'enter_filter_mode':
272
361
  stateManager.enterFilterMode(action.preserveQuery);
273
362
  break;
@@ -315,7 +404,19 @@ class InteractiveUI {
315
404
  case 'theme_confirm':
316
405
  stateManager.confirmTheme();
317
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;
318
418
  case 'cancel':
419
+ this.packageInfoModalController.cancel();
319
420
  handleCancel();
320
421
  return;
321
422
  }
@@ -324,42 +425,79 @@ class InteractiveUI {
324
425
  }
325
426
  };
326
427
  const handleConfirm = (selectedStates) => {
327
- isResolved = true;
328
- // Reset terminal colors
329
- process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
330
- ui_1.CursorUtils.show();
331
- // Clean up listeners
332
- if (process.stdin.setRawMode) {
333
- process.stdin.setRawMode(false);
334
- }
335
- process.stdin.removeAllListeners('keypress');
336
- process.stdin.pause();
337
- process.removeAllListeners('SIGWINCH');
338
- resolve(selectedStates);
428
+ finalizeSelection(selectedStates);
339
429
  };
340
430
  const handleCancel = () => {
341
- isResolved = true;
342
- // Reset terminal colors
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 = () => {
343
478
  process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
344
479
  ui_1.CursorUtils.show();
345
- // Clean up listeners
346
- if (process.stdin.setRawMode) {
347
- process.stdin.setRawMode(false);
348
- }
349
- process.stdin.removeAllListeners('keypress');
480
+ process.stdin.off('keypress', keypressHandler);
350
481
  process.stdin.pause();
351
- process.removeAllListeners('SIGWINCH');
352
- resolve(states.map((s) => ({ ...s, selectedOption: 'none' })));
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);
353
491
  };
354
- const inputHandler = new ui_1.InputHandler(stateManager, handleAction, handleConfirm, handleCancel);
355
492
  const renderInterface = () => {
356
493
  const uiState = stateManager.getUIState();
357
- const filteredStates = stateManager.getFilteredStates(states);
494
+ const filteredStates = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
495
+ const auditProgress = this.vulnerabilityAuditController.getProgress();
358
496
  // Apply terminal background color
359
497
  const bgCode = (0, themes_colors_1.getTerminalBgColorCode)();
360
498
  process.stdout.write(bgCode);
361
499
  if (uiState.forceFullRender) {
362
- console.clear();
500
+ ui_1.CursorUtils.clearScreen();
363
501
  ui_1.CursorUtils.hide();
364
502
  }
365
503
  else {
@@ -370,62 +508,48 @@ class InteractiveUI {
370
508
  const terminalWidth = process.stdout.columns || 80;
371
509
  const terminalHeight = this.getTerminalHeight();
372
510
  const themeManager = stateManager.getThemeManager();
373
- // Render header
374
- const headerLines = [];
375
- headerLines.push(' ' + chalk_1.default.bold.magenta('🚀 inup'));
376
- headerLines.push('');
377
- headerLines.push(' ' +
378
- chalk_1.default.bold.white('T ') +
379
- chalk_1.default.gray('/ Esc Exit theme selector'));
380
- headerLines.push('');
381
- headerLines.forEach((line) => console.log(line));
382
511
  const modalLines = this.renderer.renderThemeSelectorModal(themeManager.getCurrentTheme(), themeManager.getPreviewTheme(), terminalWidth, terminalHeight);
383
- modalLines.forEach((line) => console.log(line));
384
- // Clear any remaining lines from previous render
385
- ui_1.CursorUtils.clearToEndOfScreen();
386
- stateManager.markRendered([]);
512
+ renderModalViewport('theme-modal', chalk_1.default.bold.white('T ') + chalk_1.default.gray('/ Esc Exit theme selector'), modalLines, terminalWidth, terminalHeight, bgCode);
387
513
  }
388
- else if (uiState.showInfoModal && uiState.infoModalRow >= 0 && uiState.infoModalRow < filteredStates.length) {
514
+ else if (uiState.showInfoModal &&
515
+ uiState.infoModalRow >= 0 &&
516
+ uiState.infoModalRow < filteredStates.length) {
389
517
  const selectedState = filteredStates[uiState.infoModalRow];
390
518
  const terminalWidth = process.stdout.columns || 80;
391
519
  const terminalHeight = this.getTerminalHeight();
392
- // Render header
393
- const headerLines = [];
394
- headerLines.push(' ' + chalk_1.default.bold.magenta('🚀 inup'));
395
- headerLines.push('');
396
- headerLines.push(' ' +
397
- chalk_1.default.bold.white('I / Esc ') +
398
- chalk_1.default.gray('Exit this view'));
399
- headerLines.push('');
400
- headerLines.forEach((line) => console.log(line));
401
520
  if (uiState.isLoadingModalInfo) {
402
521
  // Show loading state
403
- const modalLines = this.renderer.renderPackageInfoLoading(selectedState, terminalWidth, terminalHeight);
404
- modalLines.forEach((line) => console.log(line));
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);
405
525
  }
406
526
  else {
407
- // Show full info
408
- const modalLines = this.renderer.renderPackageInfoModal(selectedState, terminalWidth, terminalHeight);
409
- modalLines.forEach((line) => console.log(line));
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);
410
539
  }
411
- // Clear any remaining lines from previous render
412
- ui_1.CursorUtils.clearToEndOfScreen();
413
- stateManager.markRendered([]);
414
540
  }
415
541
  else {
416
542
  // Normal list view (flat rendering - no grouping)
417
543
  const terminalWidth = process.stdout.columns || 80;
544
+ const terminalHeight = this.getTerminalHeight();
418
545
  const activeFilterLabel = stateManager.getActiveFilterLabel();
419
546
  const lines = this.renderer.renderInterface(filteredStates, uiState.currentRow, uiState.scrollOffset, uiState.maxVisibleItems, uiState.forceFullRender, [], // No renderable items - use flat rendering
420
547
  activeFilterLabel, // Show current dependency type filter state
421
548
  this.packageManager, // Pass package manager info for header
422
- uiState.filterMode, uiState.filterQuery, states.length, terminalWidth, loadingProgress);
423
- // Print all lines
424
- lines.forEach((line) => console.log(line));
425
- // Clear any remaining lines from previous render
426
- if (!uiState.forceFullRender) {
427
- ui_1.CursorUtils.clearToEndOfScreen();
428
- }
549
+ uiState.filterMode, uiState.filterQuery, states.length, terminalWidth, loadingProgress, auditProgress, packageListRenderOptions);
550
+ renderViewport(lines, terminalWidth, terminalHeight, bgCode);
551
+ previousViewportMode = 'list';
552
+ previousModalViewportLineCount = null;
429
553
  stateManager.markRendered(lines);
430
554
  }
431
555
  stateManager.setInitialRender(false);
@@ -438,32 +562,41 @@ class InteractiveUI {
438
562
  };
439
563
  // Setup keypress handling
440
564
  try {
565
+ claimInteractiveScreen();
566
+ this.refreshView = () => {
567
+ if (!isResolved) {
568
+ renderInterface();
569
+ }
570
+ };
441
571
  attachRefresh?.(() => {
442
572
  if (!isResolved) {
443
573
  renderInterface();
444
574
  }
445
575
  });
446
- keypress(process.stdin);
447
- if (process.stdin.setRawMode) {
448
- process.stdin.setRawMode(true);
449
- }
450
- process.stdin.resume();
451
- process.stdin.on('keypress', (str, key) => inputHandler.handleKeypress(str, key, states));
576
+ const keypressSession = ui_1.TerminalInput.startKeypressSession(keypressHandler);
577
+ const previousCleanup = cleanupInteractiveSession;
578
+ cleanupInteractiveSession = () => {
579
+ keypressSession.close();
580
+ previousCleanup();
581
+ };
452
582
  // Setup resize handler
453
583
  process.on('SIGWINCH', handleResize);
454
584
  // Update terminal height directly before initial render to ensure correct dimensions
455
585
  // This handles cases where process.stdout.rows might not be accurate at startup
456
586
  const currentHeight = this.getTerminalHeight();
457
587
  if (stateManager.updateTerminalHeight(currentHeight)) {
458
- const initialFiltered = stateManager.getFilteredStates(states);
588
+ const initialFiltered = stateManager.getFilteredStates(states, vulnerabilityDisplayOptions);
459
589
  stateManager.resetForResize(initialFiltered.length);
460
590
  }
461
591
  // Initial render
462
592
  renderInterface();
593
+ this.enqueueSecurityAudit(states);
463
594
  }
464
595
  catch (error) {
596
+ releaseInteractiveScreen();
465
597
  // Reset terminal colors
466
598
  process.stdout.write((0, themes_colors_1.getTerminalResetCode)());
599
+ this.refreshView = undefined;
467
600
  // Fallback to simple interface if raw mode fails
468
601
  console.log(chalk_1.default.yellow('Raw mode not available, using fallback interface...'));
469
602
  resolve(states);
@@ -473,32 +606,27 @@ class InteractiveUI {
473
606
  async confirmUpgrade(choices) {
474
607
  console.log(this.renderer.renderConfirmation(choices));
475
608
  return new Promise((resolve) => {
609
+ let cleanupConfirmationSession = () => {
610
+ ui_1.CursorUtils.show();
611
+ };
476
612
  const handleConfirm = (confirmed) => {
613
+ cleanupConfirmationSession();
477
614
  resolve(confirmed);
478
615
  };
479
616
  const inputHandler = new ui_1.ConfirmationInputHandler(handleConfirm);
617
+ const keypressHandler = (str, key) => inputHandler.handleKeypress(str, key);
480
618
  // Setup keypress handling
481
619
  try {
482
- keypress(process.stdin);
483
- if (process.stdin.setRawMode) {
484
- process.stdin.setRawMode(true);
485
- }
620
+ const keypressSession = ui_1.TerminalInput.startKeypressSession(keypressHandler);
621
+ cleanupConfirmationSession = () => {
622
+ keypressSession.close();
623
+ ui_1.CursorUtils.show();
624
+ };
486
625
  ui_1.CursorUtils.hide();
487
- process.stdin.resume();
488
- process.stdin.on('keypress', (str, key) => inputHandler.handleKeypress(str, key));
489
626
  }
490
627
  catch (error) {
491
- // Fallback to inquirer
492
- inquirer_1.default
493
- .prompt([
494
- {
495
- type: 'confirm',
496
- name: 'proceed',
497
- message: 'Proceed with upgrade?',
498
- default: true,
499
- },
500
- ])
501
- .then((answer) => resolve(answer.proceed))
628
+ ui_1.TerminalInput.promptForConfirmation('Proceed with upgrade? [Y/n] ')
629
+ .then(resolve)
502
630
  .catch(() => resolve(false));
503
631
  }
504
632
  });
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BackgroundAuditTracker = void 0;
4
+ const utils_1 = require("../utils");
5
+ class BackgroundAuditTracker {
6
+ constructor() {
7
+ this.pending = new Map();
8
+ this.inFlight = new Set();
9
+ this.completed = new Set();
10
+ }
11
+ enqueue(packages) {
12
+ let added = 0;
13
+ for (const pkg of packages) {
14
+ if (!pkg.name || !pkg.version)
15
+ continue;
16
+ if (this.pending.has(pkg.name) ||
17
+ this.inFlight.has(pkg.name) ||
18
+ this.completed.has(pkg.name)) {
19
+ continue;
20
+ }
21
+ this.pending.set(pkg.name, pkg.version);
22
+ added++;
23
+ }
24
+ if (added > 0) {
25
+ utils_1.debugLog.info('background-audit', `queued ${added} package(s)`);
26
+ }
27
+ return added;
28
+ }
29
+ reserveNextBatch(limit = 20) {
30
+ const packages = new Map();
31
+ const packageNames = [];
32
+ for (const [name, version] of this.pending) {
33
+ packages.set(name, version);
34
+ packageNames.push(name);
35
+ this.pending.delete(name);
36
+ this.inFlight.add(name);
37
+ if (packageNames.length >= limit) {
38
+ break;
39
+ }
40
+ }
41
+ return { packages, packageNames };
42
+ }
43
+ markCompleted(packageNames) {
44
+ for (const packageName of packageNames) {
45
+ this.inFlight.delete(packageName);
46
+ this.completed.add(packageName);
47
+ }
48
+ }
49
+ getProgress() {
50
+ const total = this.pending.size + this.inFlight.size + this.completed.size;
51
+ return {
52
+ completed: this.completed.size,
53
+ total,
54
+ isRunning: this.pending.size > 0 || this.inFlight.size > 0,
55
+ hasData: this.completed.size > 0,
56
+ };
57
+ }
58
+ }
59
+ exports.BackgroundAuditTracker = BackgroundAuditTracker;
60
+ //# sourceMappingURL=background-audit.js.map
@@ -19,6 +19,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  __exportStar(require("./npm-registry"), exports);
21
21
  __exportStar(require("./jsdelivr-registry"), exports);
22
- __exportStar(require("./changelog-fetcher"), exports);
22
+ __exportStar(require("../features/changelog"), exports);
23
23
  __exportStar(require("./version-checker"), exports);
24
+ __exportStar(require("./vulnerability-checker"), exports);
25
+ __exportStar(require("./background-audit"), exports);
24
26
  //# sourceMappingURL=index.js.map