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
@@ -11,6 +11,11 @@ exports.renderPackagesTable = renderPackagesTable;
11
11
  const chalk_1 = __importDefault(require("chalk"));
12
12
  const utils_1 = require("../utils");
13
13
  const themes_colors_1 = require("../themes-colors");
14
+ const vulnerability_1 = require("../presenters/vulnerability");
15
+ function padLineToWidth(line, terminalWidth) {
16
+ const padding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(line));
17
+ return line + ' '.repeat(padding);
18
+ }
14
19
  /**
15
20
  * Get type badge for dependency type (theme-aware)
16
21
  */
@@ -34,7 +39,7 @@ function getTypeBadge(type) {
34
39
  * @param isCurrentRow Whether this is the current/highlighted row
35
40
  * @param terminalWidth Terminal width for dynamic truncation (default 80)
36
41
  */
37
- function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
42
+ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80, options = {}) {
38
43
  const prefix = isCurrentRow ? (0, themes_colors_1.getThemeColor)('success')('❯ ') : ' ';
39
44
  // Package name with special formatting for scoped packages (@author/package)
40
45
  let packageName;
@@ -44,14 +49,18 @@ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
44
49
  const author = parts[0]; // @author
45
50
  const packagePart = parts.slice(1).join('/'); // package name
46
51
  if (isCurrentRow) {
47
- packageName = chalk_1.default.bold((0, themes_colors_1.getThemeColor)('packageAuthor')(author)) + (0, themes_colors_1.getThemeColor)('packageName')('/' + packagePart);
52
+ packageName =
53
+ chalk_1.default.bold((0, themes_colors_1.getThemeColor)('packageAuthor')(author)) +
54
+ (0, themes_colors_1.getThemeColor)('packageName')('/' + packagePart);
48
55
  }
49
56
  else {
50
57
  packageName = chalk_1.default.bold.white(author) + chalk_1.default.white('/' + packagePart);
51
58
  }
52
59
  }
53
60
  else {
54
- packageName = isCurrentRow ? (0, themes_colors_1.getThemeColor)('packageName')(state.name) : chalk_1.default.white(state.name);
61
+ packageName = isCurrentRow
62
+ ? (0, themes_colors_1.getThemeColor)('packageName')(state.name)
63
+ : chalk_1.default.white(state.name);
55
64
  }
56
65
  }
57
66
  else {
@@ -130,18 +139,26 @@ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
130
139
  const displayName = truncatedName !== state.name ? truncatedName : packageName;
131
140
  // Package name with dashes and badge at the end
132
141
  const typeBadge = getTypeBadge(state.type);
142
+ const shouldShowVulnerability = (0, vulnerability_1.shouldDisplayVulnerabilityForDependency)(state.type, options);
143
+ const vulnBadge = shouldShowVulnerability ? (0, vulnerability_1.getVulnerabilityBadge)(state.vulnerability) : '';
144
+ const vulnBadgeWidth = vulnBadge ? utils_1.VersionUtils.getVisualLength(vulnBadge) + 1 : 0; // +1 for space
133
145
  const nameLength = utils_1.VersionUtils.getVisualLength(truncatedName);
134
- const namePadding = Math.max(0, packageNameWidth - nameLength - 1 - badgeWidth); // -1 for space after package name, -badgeWidth for badge at end
135
- const nameDashes = shouldShowDashes(namePadding) ? dashColor('-').repeat(namePadding) : ' '.repeat(namePadding);
136
- // Place badge at the end of dashes: name ------[D]
146
+ const namePadding = Math.max(0, packageNameWidth - nameLength - 1 - badgeWidth - vulnBadgeWidth); // -1 for space after package name
147
+ const nameDashes = shouldShowDashes(namePadding)
148
+ ? dashColor('-').repeat(namePadding)
149
+ : ' '.repeat(namePadding);
150
+ // Place badges at the end of dashes: name ------⚠[D]
151
+ const vulnSuffix = vulnBadge ? ` ${vulnBadge}` : '';
137
152
  const packageNameSection = typeBadge
138
- ? `${displayName} ${nameDashes}${typeBadge}`
139
- : `${displayName} ${nameDashes}`;
153
+ ? `${displayName} ${nameDashes}${vulnSuffix}${typeBadge}`
154
+ : `${displayName} ${nameDashes}${vulnSuffix}`;
140
155
  // Current version section with dashes only if needed
141
156
  const currentSection = `${currentDot} ${currentVersion}`;
142
157
  const currentSectionLength = utils_1.VersionUtils.getVisualLength(currentSection) + 1; // +1 for space before padding
143
158
  const currentPadding = Math.max(0, currentColumnWidth - currentSectionLength);
144
- const currentPaddingText = shouldShowDashes(currentPadding) ? dashColor('-').repeat(currentPadding) : ' '.repeat(currentPadding);
159
+ const currentPaddingText = shouldShowDashes(currentPadding)
160
+ ? dashColor('-').repeat(currentPadding)
161
+ : ' '.repeat(currentPadding);
145
162
  const currentWithPadding = currentSection + ' ' + currentPaddingText;
146
163
  // Range version section with dashes only if needed
147
164
  let rangeSection = '';
@@ -149,7 +166,9 @@ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
149
166
  rangeSection = `${rangeDot} ${rangeVersionText}`;
150
167
  const rangeSectionLength = utils_1.VersionUtils.getVisualLength(rangeSection) + 1; // +1 for space before padding
151
168
  const rangePadding = Math.max(0, rangeColumnWidth - rangeSectionLength);
152
- const rangePaddingText = shouldShowDashes(rangePadding) ? dashColor('-').repeat(rangePadding) : ' '.repeat(rangePadding);
169
+ const rangePaddingText = shouldShowDashes(rangePadding)
170
+ ? dashColor('-').repeat(rangePadding)
171
+ : ' '.repeat(rangePadding);
153
172
  rangeSection += ' ' + rangePaddingText;
154
173
  }
155
174
  else {
@@ -162,7 +181,9 @@ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
162
181
  latestSection = `${latestDot} ${latestVersionText}`;
163
182
  const latestSectionLength = utils_1.VersionUtils.getVisualLength(latestSection) + 1; // +1 for space before padding
164
183
  const latestPadding = Math.max(0, latestColumnWidth - latestSectionLength);
165
- const latestPaddingText = shouldShowDashes(latestPadding) ? dashColor('-').repeat(latestPadding) : ' '.repeat(latestPadding);
184
+ const latestPaddingText = shouldShowDashes(latestPadding)
185
+ ? dashColor('-').repeat(latestPadding)
186
+ : ' '.repeat(latestPadding);
166
187
  latestSection += ' ' + latestPaddingText;
167
188
  }
168
189
  else {
@@ -189,7 +210,7 @@ function renderSpacer() {
189
210
  /**
190
211
  * Render the main interface
191
212
  */
192
- function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems, activeFilterLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth = 80, loadingProgress) {
213
+ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems, activeFilterLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth = 80, loadingProgress, auditProgress, options = {}) {
193
214
  const output = [];
194
215
  // Header section (same for initial and incremental render)
195
216
  if (packageManager) {
@@ -204,20 +225,33 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
204
225
  // Each character in "inup" gets a different color
205
226
  const inupColors = [chalk_1.default.red, chalk_1.default.yellow, chalk_1.default.blue, chalk_1.default.magenta];
206
227
  const coloredInup = inupColors.map((color, i) => color.bold('inup'[i])).join('');
207
- const headerLine = ' ' + chalk_1.default.bold(pmColor('🚀')) + ' ' + coloredInup + (0, themes_colors_1.getThemeColor)('textSecondary')(` (${packageManager.displayName})`);
228
+ const headerLine = ' ' +
229
+ chalk_1.default.bold(pmColor('🚀')) +
230
+ ' ' +
231
+ coloredInup +
232
+ (0, themes_colors_1.getThemeColor)('textSecondary')(` (${packageManager.displayName})`);
208
233
  // Show filter state (always show, including "All")
209
234
  const fullHeaderLine = activeFilterLabel
210
- ? headerLine + (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') + (0, themes_colors_1.getThemeColor)('primary')(activeFilterLabel)
235
+ ? headerLine +
236
+ (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') +
237
+ (0, themes_colors_1.getThemeColor)('primary')(activeFilterLabel)
211
238
  : headerLine;
212
239
  // Pad to terminal width to clear any leftover characters
213
240
  const headerPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(fullHeaderLine));
214
241
  output.push(fullHeaderLine + ' '.repeat(headerPadding));
215
242
  }
216
243
  else {
217
- const headerLine = ' ' + chalk_1.default.bold.blue('🚀 ') + chalk_1.default.bold.red('i') + chalk_1.default.bold.yellow('n') + chalk_1.default.bold.blue('u') + chalk_1.default.bold.magenta('p');
244
+ const headerLine = ' ' +
245
+ chalk_1.default.bold.blue('🚀 ') +
246
+ chalk_1.default.bold.red('i') +
247
+ chalk_1.default.bold.yellow('n') +
248
+ chalk_1.default.bold.blue('u') +
249
+ chalk_1.default.bold.magenta('p');
218
250
  // Show filter state (always show, including "All")
219
251
  const fullHeaderLine = activeFilterLabel
220
- ? headerLine + (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') + (0, themes_colors_1.getThemeColor)('primary')(activeFilterLabel)
252
+ ? headerLine +
253
+ (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') +
254
+ (0, themes_colors_1.getThemeColor)('primary')(activeFilterLabel)
221
255
  : headerLine;
222
256
  // Pad to terminal width to clear any leftover characters
223
257
  const headerPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(fullHeaderLine));
@@ -226,14 +260,20 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
226
260
  output.push('');
227
261
  if (filterMode) {
228
262
  // Show filter input with cursor when actively filtering
229
- const filterDisplay = ' ' + chalk_1.default.bold.white('Search: ') + (0, themes_colors_1.getThemeColor)('primary')(filterQuery || '') + (0, themes_colors_1.getThemeColor)('border')('█');
263
+ const filterDisplay = ' ' +
264
+ chalk_1.default.bold.white('Search: ') +
265
+ (0, themes_colors_1.getThemeColor)('primary')(filterQuery || '') +
266
+ (0, themes_colors_1.getThemeColor)('border')('█');
230
267
  // Pad to terminal width to clear any leftover characters from backspace
231
268
  const padding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(filterDisplay));
232
269
  output.push(filterDisplay + ' '.repeat(padding));
233
270
  }
234
271
  else if (filterQuery) {
235
272
  // Show applied filter when not in filter mode but filter is active
236
- const filterDisplay = ' ' + chalk_1.default.bold.white('Search: ') + (0, themes_colors_1.getThemeColor)('primary')(filterQuery) + (0, themes_colors_1.getThemeColor)('textSecondary')(' (press / to edit)');
273
+ const filterDisplay = ' ' +
274
+ chalk_1.default.bold.white('Search: ') +
275
+ (0, themes_colors_1.getThemeColor)('primary')(filterQuery) +
276
+ (0, themes_colors_1.getThemeColor)('textSecondary')(' (press / to edit)');
237
277
  const padding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(filterDisplay));
238
278
  output.push(filterDisplay + ' '.repeat(padding));
239
279
  }
@@ -255,6 +295,9 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
255
295
  chalk_1.default.bold.white('I ') +
256
296
  (0, themes_colors_1.getThemeColor)('textSecondary')('Info') +
257
297
  ' ' +
298
+ chalk_1.default.bold.white('S ') +
299
+ (0, themes_colors_1.getThemeColor)('textSecondary')('Vulnerable') +
300
+ ' ' +
258
301
  chalk_1.default.bold.white('M ') +
259
302
  (0, themes_colors_1.getThemeColor)('textSecondary')('Minor') +
260
303
  ' ' +
@@ -275,67 +318,97 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
275
318
  if (filterMode) {
276
319
  // In filter mode, show Enter to apply and ESC to clear
277
320
  if (totalPackages === 0) {
278
- statusLine = (0, themes_colors_1.getThemeColor)('warning')(`No matches found`) +
279
- ' ' +
280
- chalk_1.default.bold.white('Esc ') + chalk_1.default.gray('Clear');
321
+ statusLine =
322
+ (0, themes_colors_1.getThemeColor)('warning')(`No matches found`) +
323
+ ' ' +
324
+ chalk_1.default.bold.white('Esc ') +
325
+ chalk_1.default.gray('Clear');
281
326
  }
282
327
  else if (totalVisualItems > maxVisibleItems) {
283
- statusLine = (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing ${chalk_1.default.white(startItem)}-${chalk_1.default.white(endItem)} of ${chalk_1.default.white(totalPackages)} matches`) +
284
- ' ' +
285
- chalk_1.default.bold.white('Enter ') + chalk_1.default.gray('Apply') +
286
- ' ' +
287
- chalk_1.default.bold.white('Esc ') + chalk_1.default.gray('Clear');
328
+ statusLine =
329
+ (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing ${chalk_1.default.white(startItem)}-${chalk_1.default.white(endItem)} of ${chalk_1.default.white(totalPackages)} matches`) +
330
+ ' ' +
331
+ chalk_1.default.bold.white('Enter ') +
332
+ chalk_1.default.gray('Apply') +
333
+ ' ' +
334
+ chalk_1.default.bold.white('Esc ') +
335
+ chalk_1.default.gray('Clear');
288
336
  }
289
337
  else {
290
- statusLine = (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing all ${chalk_1.default.white(totalPackages)} matches`) +
291
- ' ' +
292
- chalk_1.default.bold.white('Enter ') + chalk_1.default.gray('Apply') +
293
- ' ' +
294
- chalk_1.default.bold.white('Esc ') + chalk_1.default.gray('Clear');
338
+ statusLine =
339
+ (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing all ${chalk_1.default.white(totalPackages)} matches`) +
340
+ ' ' +
341
+ chalk_1.default.bold.white('Enter ') +
342
+ chalk_1.default.gray('Apply') +
343
+ ' ' +
344
+ chalk_1.default.bold.white('Esc ') +
345
+ chalk_1.default.gray('Clear');
295
346
  }
296
347
  }
297
348
  else if (totalPackages < totalBeforeFilter) {
298
349
  // Filter is applied but not in filter mode
299
350
  if (totalVisualItems > maxVisibleItems) {
300
- statusLine = (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing ${chalk_1.default.white(startItem)}-${chalk_1.default.white(endItem)} of ${chalk_1.default.white(totalPackages)} matches`) +
301
- ' ' +
302
- chalk_1.default.bold.white('D/P/O ') + chalk_1.default.gray('Filter') +
303
- ' ' +
304
- chalk_1.default.bold.white('M ') + chalk_1.default.gray('Minor') +
305
- ' ' +
306
- chalk_1.default.bold.white('L ') + chalk_1.default.gray('All') +
307
- ' ' +
308
- chalk_1.default.bold.white('U ') + chalk_1.default.gray('None') +
309
- ' ' +
310
- chalk_1.default.bold.white('Esc ') + chalk_1.default.gray('Clear');
351
+ statusLine =
352
+ (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing ${chalk_1.default.white(startItem)}-${chalk_1.default.white(endItem)} of ${chalk_1.default.white(totalPackages)} matches`) +
353
+ ' ' +
354
+ chalk_1.default.bold.white('D/P/O ') +
355
+ chalk_1.default.gray('Filter') +
356
+ ' ' +
357
+ chalk_1.default.bold.white('M ') +
358
+ chalk_1.default.gray('Minor') +
359
+ ' ' +
360
+ chalk_1.default.bold.white('L ') +
361
+ chalk_1.default.gray('All') +
362
+ ' ' +
363
+ chalk_1.default.bold.white('U ') +
364
+ chalk_1.default.gray('None') +
365
+ ' ' +
366
+ chalk_1.default.bold.white('Esc ') +
367
+ chalk_1.default.gray('Clear');
311
368
  }
312
369
  else {
313
- statusLine = (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing all ${chalk_1.default.white(totalPackages)} matches`) +
314
- ' ' +
315
- chalk_1.default.bold.white('D/P/O ') + chalk_1.default.gray('Filter') +
316
- ' ' +
317
- chalk_1.default.bold.white('M ') + chalk_1.default.gray('Minor') +
318
- ' ' +
319
- chalk_1.default.bold.white('L ') + chalk_1.default.gray('All') +
320
- ' ' +
321
- chalk_1.default.bold.white('U ') + chalk_1.default.gray('None') +
322
- ' ' +
323
- chalk_1.default.bold.white('Esc ') + chalk_1.default.gray('Clear');
370
+ statusLine =
371
+ (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing all ${chalk_1.default.white(totalPackages)} matches`) +
372
+ ' ' +
373
+ chalk_1.default.bold.white('D/P/O ') +
374
+ chalk_1.default.gray('Filter') +
375
+ ' ' +
376
+ chalk_1.default.bold.white('M ') +
377
+ chalk_1.default.gray('Minor') +
378
+ ' ' +
379
+ chalk_1.default.bold.white('L ') +
380
+ chalk_1.default.gray('All') +
381
+ ' ' +
382
+ chalk_1.default.bold.white('U ') +
383
+ chalk_1.default.gray('None') +
384
+ ' ' +
385
+ chalk_1.default.bold.white('Esc ') +
386
+ chalk_1.default.gray('Clear');
324
387
  }
325
388
  }
326
389
  else {
327
390
  // No filter applied
328
391
  if (totalVisualItems > maxVisibleItems) {
329
- statusLine = chalk_1.default.gray(`Showing ${chalk_1.default.white(startItem)}-${chalk_1.default.white(endItem)} of ${chalk_1.default.white(totalPackages)} packages`) +
330
- ' ' +
331
- chalk_1.default.bold.white('Enter ') + chalk_1.default.gray('Confirm');
392
+ statusLine =
393
+ chalk_1.default.gray(`Showing ${chalk_1.default.white(startItem)}-${chalk_1.default.white(endItem)} of ${chalk_1.default.white(totalPackages)} packages`) +
394
+ ' ' +
395
+ chalk_1.default.bold.white('Enter ') +
396
+ chalk_1.default.gray('Confirm');
332
397
  }
333
398
  else {
334
- statusLine = chalk_1.default.gray(`Showing all ${chalk_1.default.white(totalPackages)} packages`) +
335
- ' ' +
336
- chalk_1.default.bold.white('Enter ') + chalk_1.default.gray('Confirm');
399
+ statusLine =
400
+ chalk_1.default.gray(`Showing all ${chalk_1.default.white(totalPackages)} packages`) +
401
+ ' ' +
402
+ chalk_1.default.bold.white('Enter ') +
403
+ chalk_1.default.gray('Confirm');
337
404
  }
338
405
  }
406
+ if (auditProgress && auditProgress.total > 0) {
407
+ const auditLabel = auditProgress.isRunning
408
+ ? `Audit ${auditProgress.completed}/${auditProgress.total}`
409
+ : `Audit ${auditProgress.total}/${auditProgress.total}`;
410
+ statusLine += ' ' + (0, themes_colors_1.getThemeColor)('textSecondary')(auditLabel);
411
+ }
339
412
  // Pad status line to terminal width to clear any leftover characters
340
413
  const statusLineFull = ' ' + statusLine;
341
414
  const statusPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(statusLineFull));
@@ -353,7 +426,7 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
353
426
  output.push(renderSpacer());
354
427
  }
355
428
  else if (item.type === 'package') {
356
- const line = renderPackageLine(item.state, item.originalIndex, item.originalIndex === currentRow, terminalWidth);
429
+ const line = renderPackageLine(item.state, item.originalIndex, item.originalIndex === currentRow, terminalWidth, options);
357
430
  output.push(line);
358
431
  }
359
432
  }
@@ -361,7 +434,7 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
361
434
  else {
362
435
  // Fallback to flat rendering (legacy mode)
363
436
  for (let i = scrollOffset; i < Math.min(scrollOffset + maxVisibleItems, states.length); i++) {
364
- const line = renderPackageLine(states[i], i, i === currentRow, terminalWidth);
437
+ const line = renderPackageLine(states[i], i, i === currentRow, terminalWidth, options);
365
438
  output.push(line);
366
439
  }
367
440
  }
@@ -374,7 +447,7 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
374
447
  const loadingPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(loadingLine));
375
448
  output.push(loadingLine + ' '.repeat(loadingPadding));
376
449
  }
377
- return output;
450
+ return output.map((line) => padLineToWidth(line, terminalWidth));
378
451
  }
379
452
  /**
380
453
  * Render packages table
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FilterManager = void 0;
4
+ const vulnerability_1 = require("../presenters/vulnerability");
4
5
  class FilterManager {
5
6
  constructor() {
6
7
  this.state = {
@@ -10,6 +11,7 @@ class FilterManager {
10
11
  showDevDependencies: true,
11
12
  showPeerDependencies: true,
12
13
  showOptionalDependencies: true,
14
+ showOnlyVulnerable: false,
13
15
  };
14
16
  }
15
17
  getState() {
@@ -60,6 +62,12 @@ class FilterManager {
60
62
  break;
61
63
  }
62
64
  }
65
+ toggleVulnerableFilter() {
66
+ this.state.showOnlyVulnerable = !this.state.showOnlyVulnerable;
67
+ }
68
+ isVulnerableFilterActive() {
69
+ return this.state.showOnlyVulnerable;
70
+ }
63
71
  getActiveFilterLabel() {
64
72
  const activeTypes = [];
65
73
  if (this.state.showDependencies)
@@ -72,9 +80,10 @@ class FilterManager {
72
80
  activeTypes.push('Optional');
73
81
  if (activeTypes.length === 0)
74
82
  return 'None';
75
- return activeTypes.join(', ');
83
+ const label = activeTypes.join(', ');
84
+ return this.state.showOnlyVulnerable ? label + ' (vulnerable only)' : label;
76
85
  }
77
- getFilteredStates(allStates) {
86
+ getFilteredStates(allStates, options = {}) {
78
87
  let filtered = allStates;
79
88
  // Apply text filter
80
89
  if (this.state.filterQuery) {
@@ -96,6 +105,12 @@ class FilterManager {
96
105
  return true;
97
106
  }
98
107
  });
108
+ // Apply vulnerability filter
109
+ if (this.state.showOnlyVulnerable) {
110
+ filtered = filtered.filter((state) => (0, vulnerability_1.shouldDisplayVulnerabilityForDependency)(state.type, options) &&
111
+ !!state.vulnerability &&
112
+ state.vulnerability.count > 0);
113
+ }
99
114
  return filtered;
100
115
  }
101
116
  }
@@ -7,6 +7,8 @@ class ModalManager {
7
7
  showInfoModal: false,
8
8
  infoModalRow: -1,
9
9
  isLoadingModalInfo: false,
10
+ infoModalScrollOffset: 0,
11
+ infoModalSessionId: 0,
10
12
  };
11
13
  }
12
14
  getState() {
@@ -21,24 +23,64 @@ class ModalManager {
21
23
  isLoading() {
22
24
  return this.state.isLoadingModalInfo;
23
25
  }
26
+ getScrollOffset() {
27
+ return this.state.infoModalScrollOffset;
28
+ }
29
+ getSessionId() {
30
+ return this.state.infoModalSessionId;
31
+ }
32
+ clampScrollOffset(maxOffset) {
33
+ const nextOffset = Math.max(0, Math.min(this.state.infoModalScrollOffset, maxOffset));
34
+ if (nextOffset === this.state.infoModalScrollOffset) {
35
+ return false;
36
+ }
37
+ this.state.infoModalScrollOffset = nextOffset;
38
+ return true;
39
+ }
40
+ resetScroll() {
41
+ this.state.infoModalScrollOffset = 0;
42
+ }
43
+ scrollModalUp() {
44
+ if (this.state.infoModalScrollOffset > 0) {
45
+ this.state.infoModalScrollOffset--;
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+ scrollModalDown(maxOffset) {
51
+ if (this.state.infoModalScrollOffset < maxOffset) {
52
+ this.state.infoModalScrollOffset++;
53
+ return true;
54
+ }
55
+ return false;
56
+ }
24
57
  toggleInfoModal(currentRow) {
25
58
  if (this.state.showInfoModal) {
26
59
  // Close the modal
27
60
  this.closeInfoModal();
61
+ return this.state.infoModalSessionId;
28
62
  }
29
- else {
30
- // Open the modal for the current package
31
- this.state.showInfoModal = true;
32
- this.state.infoModalRow = currentRow;
33
- }
63
+ // Open the modal for the current package
64
+ this.state.showInfoModal = true;
65
+ this.state.infoModalRow = currentRow;
66
+ this.state.infoModalScrollOffset = 0;
67
+ this.state.isLoadingModalInfo = false;
68
+ this.state.infoModalSessionId += 1;
69
+ return this.state.infoModalSessionId;
34
70
  }
35
71
  closeInfoModal() {
36
72
  this.state.showInfoModal = false;
37
73
  this.state.infoModalRow = -1;
38
74
  this.state.isLoadingModalInfo = false;
75
+ this.state.infoModalScrollOffset = 0;
76
+ this.state.infoModalSessionId += 1;
39
77
  }
40
- setModalLoading(isLoading) {
78
+ setModalLoading(isLoading, sessionId) {
79
+ if (sessionId !== undefined && sessionId !== this.state.infoModalSessionId) {
80
+ return false;
81
+ }
41
82
  this.state.isLoadingModalInfo = isLoading;
83
+ return true;
42
84
  }
43
85
  }
44
86
  exports.ModalManager = ModalManager;
@@ -42,6 +42,7 @@ class StateManager {
42
42
  showInfoModal: modalState.showInfoModal,
43
43
  infoModalRow: modalState.infoModalRow,
44
44
  isLoadingModalInfo: modalState.isLoadingModalInfo,
45
+ infoModalScrollOffset: modalState.infoModalScrollOffset,
45
46
  filterMode: filterState.filterMode,
46
47
  filterQuery: filterState.filterQuery,
47
48
  showThemeModal: themeState.showThemeModal,
@@ -151,16 +152,40 @@ class StateManager {
151
152
  // Modal delegation
152
153
  toggleInfoModal() {
153
154
  const currentRow = this.navigationManager.getCurrentRow();
154
- this.modalManager.toggleInfoModal(currentRow);
155
+ const sessionId = this.modalManager.toggleInfoModal(currentRow);
155
156
  this.renderState.forceFullRender = true;
157
+ return sessionId;
156
158
  }
157
159
  closeInfoModal() {
158
160
  this.modalManager.closeInfoModal();
159
161
  this.renderState.forceFullRender = true;
160
162
  }
161
- setModalLoading(isLoading) {
162
- this.modalManager.setModalLoading(isLoading);
163
- this.renderState.forceFullRender = true;
163
+ setModalLoading(isLoading, sessionId) {
164
+ const updated = this.modalManager.setModalLoading(isLoading, sessionId);
165
+ if (updated) {
166
+ this.renderState.forceFullRender = true;
167
+ }
168
+ return updated;
169
+ }
170
+ getInfoModalSessionId() {
171
+ return this.modalManager.getSessionId();
172
+ }
173
+ resetInfoModalScroll() {
174
+ this.modalManager.resetScroll();
175
+ }
176
+ scrollInfoModalUp() {
177
+ return this.modalManager.scrollModalUp();
178
+ // Don't force full render — modal viewport handles its own overwrite
179
+ }
180
+ scrollInfoModalDown(maxOffset) {
181
+ return this.modalManager.scrollModalDown(maxOffset);
182
+ // Don't force full render — modal viewport handles its own overwrite
183
+ }
184
+ getInfoModalScrollOffset() {
185
+ return this.modalManager.getScrollOffset();
186
+ }
187
+ clampInfoModalScrollOffset(maxOffset) {
188
+ return this.modalManager.clampScrollOffset(maxOffset);
164
189
  }
165
190
  // Filter delegation
166
191
  enterFilterMode(preserveQuery = false) {
@@ -190,8 +215,8 @@ class StateManager {
190
215
  this.navigationManager.setCurrentRow(0);
191
216
  this.navigationManager.setScrollOffset(0);
192
217
  }
193
- getFilteredStates(allStates) {
194
- return this.filterManager.getFilteredStates(allStates);
218
+ getFilteredStates(allStates, options) {
219
+ return this.filterManager.getFilteredStates(allStates, options);
195
220
  }
196
221
  toggleDependencyTypeFilter(type) {
197
222
  this.filterManager.toggleDependencyType(type);
@@ -200,6 +225,14 @@ class StateManager {
200
225
  this.navigationManager.setScrollOffset(0);
201
226
  // Use incremental render (no blink)
202
227
  }
228
+ toggleVulnerableFilter() {
229
+ this.filterManager.toggleVulnerableFilter();
230
+ this.navigationManager.setCurrentRow(0);
231
+ this.navigationManager.setScrollOffset(0);
232
+ }
233
+ isVulnerableFilterActive() {
234
+ return this.filterManager.isVulnerableFilterActive();
235
+ }
203
236
  getActiveFilterLabel() {
204
237
  return this.filterManager.getActiveFilterLabel();
205
238
  }
@@ -223,7 +256,9 @@ class StateManager {
223
256
  this.renderState.forceFullRender = isInitial;
224
257
  }
225
258
  resetForResize(totalFilteredItems) {
226
- const totalItems = totalFilteredItems || this.renderState.renderableItems.length || this.displayState.maxVisibleItems;
259
+ const totalItems = totalFilteredItems ||
260
+ this.renderState.renderableItems.length ||
261
+ this.displayState.maxVisibleItems;
227
262
  this.navigationManager.resetForResize(totalItems);
228
263
  this.renderState.forceFullRender = true;
229
264
  }
@@ -5,6 +5,18 @@
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ConsoleUtils = exports.CursorUtils = void 0;
7
7
  exports.CursorUtils = {
8
+ /**
9
+ * Switch to the terminal alternate screen buffer.
10
+ */
11
+ enterAlternateScreen() {
12
+ process.stdout.write('\x1b[?1049h');
13
+ },
14
+ /**
15
+ * Return to the terminal primary screen buffer.
16
+ */
17
+ exitAlternateScreen() {
18
+ process.stdout.write('\x1b[?1049l');
19
+ },
8
20
  /**
9
21
  * Hide the cursor in the terminal
10
22
  */
@@ -23,6 +35,12 @@ exports.CursorUtils = {
23
35
  moveToHome() {
24
36
  process.stdout.write('\x1b[H');
25
37
  },
38
+ /**
39
+ * Clear the full screen and move the cursor to the top-left corner.
40
+ */
41
+ clearScreen() {
42
+ process.stdout.write('\x1b[2J\x1b[H');
43
+ },
26
44
  /**
27
45
  * Clear display from cursor to end of screen
28
46
  */
@@ -1,9 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ConsoleUtils = exports.CursorUtils = exports.VersionUtils = void 0;
3
+ exports.wrapPlainText = exports.truncatePlainText = exports.stripAnsi = exports.getVisualLength = exports.TerminalInput = exports.ConsoleUtils = exports.CursorUtils = exports.VersionUtils = void 0;
4
4
  var version_1 = require("./version");
5
5
  Object.defineProperty(exports, "VersionUtils", { enumerable: true, get: function () { return version_1.VersionUtils; } });
6
6
  var cursor_1 = require("./cursor");
7
7
  Object.defineProperty(exports, "CursorUtils", { enumerable: true, get: function () { return cursor_1.CursorUtils; } });
8
8
  Object.defineProperty(exports, "ConsoleUtils", { enumerable: true, get: function () { return cursor_1.ConsoleUtils; } });
9
+ var terminal_input_1 = require("./terminal-input");
10
+ Object.defineProperty(exports, "TerminalInput", { enumerable: true, get: function () { return terminal_input_1.TerminalInput; } });
11
+ var text_1 = require("./text");
12
+ Object.defineProperty(exports, "getVisualLength", { enumerable: true, get: function () { return text_1.getVisualLength; } });
13
+ Object.defineProperty(exports, "stripAnsi", { enumerable: true, get: function () { return text_1.stripAnsi; } });
14
+ Object.defineProperty(exports, "truncatePlainText", { enumerable: true, get: function () { return text_1.truncatePlainText; } });
15
+ Object.defineProperty(exports, "wrapPlainText", { enumerable: true, get: function () { return text_1.wrapPlainText; } });
9
16
  //# sourceMappingURL=index.js.map