inup 1.4.10 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +1 -7
  2. package/dist/cli.js +2 -1
  3. package/dist/config/constants.js +1 -2
  4. package/dist/config/project-config.js +6 -0
  5. package/dist/core/package-detector.js +163 -89
  6. package/dist/core/upgrade-runner.js +68 -16
  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 +343 -161
  19. package/dist/services/background-audit.js +60 -0
  20. package/dist/services/index.js +3 -3
  21. package/dist/services/jsdelivr-registry.js +92 -176
  22. package/dist/services/npm-registry.js +97 -27
  23. package/dist/services/vulnerability-checker.js +133 -0
  24. package/dist/ui/controllers/index.js +8 -0
  25. package/dist/ui/controllers/package-info-modal-controller.js +237 -0
  26. package/dist/ui/controllers/vulnerability-audit-controller.js +82 -0
  27. package/dist/ui/index.js +3 -0
  28. package/dist/ui/input-handler.js +41 -10
  29. package/dist/ui/modal/index.js +22 -0
  30. package/dist/ui/modal/layout.js +84 -0
  31. package/dist/ui/modal/package-info-sections.js +327 -0
  32. package/dist/ui/modal/package-info.js +147 -0
  33. package/dist/ui/modal/theme-selector.js +46 -0
  34. package/dist/ui/modal/types.js +3 -0
  35. package/dist/ui/presenters/index.js +11 -0
  36. package/dist/ui/presenters/vulnerability.js +76 -0
  37. package/dist/ui/renderer/index.js +9 -11
  38. package/dist/ui/renderer/package-list.js +166 -66
  39. package/dist/ui/state/filter-manager.js +17 -2
  40. package/dist/ui/state/modal-manager.js +48 -6
  41. package/dist/ui/state/state-manager.js +49 -12
  42. package/dist/ui/utils/cursor.js +18 -0
  43. package/dist/ui/utils/index.js +8 -1
  44. package/dist/ui/utils/terminal-input.js +82 -0
  45. package/dist/ui/utils/text.js +75 -0
  46. package/dist/ui/utils/version.js +3 -2
  47. package/package.json +7 -11
  48. package/dist/services/changelog-fetcher.js +0 -190
  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 {
@@ -61,13 +70,23 @@ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
61
70
  const isCurrentSelected = state.selectedOption === 'none';
62
71
  const isRangeSelected = state.selectedOption === 'range';
63
72
  const isLatestSelected = state.selectedOption === 'latest';
73
+ const isPending = state.loadState === 'pending';
74
+ const isFailed = state.loadState === 'failed';
64
75
  // Current version dot and version (show original specifier with prefix)
65
76
  const currentDot = isCurrentSelected ? (0, themes_colors_1.getThemeColor)('dot')('●') : (0, themes_colors_1.getThemeColor)('dotEmpty')('○');
66
77
  const currentVersion = chalk_1.default.white(state.currentVersionSpecifier);
67
78
  // Range version dot and version
68
79
  let rangeDot = '';
69
80
  let rangeVersionText = '';
70
- if (state.hasRangeUpdate) {
81
+ if (isPending) {
82
+ rangeDot = (0, themes_colors_1.getThemeColor)('dotEmpty')('◌');
83
+ rangeVersionText = chalk_1.default.gray('loading');
84
+ }
85
+ else if (isFailed) {
86
+ rangeDot = (0, themes_colors_1.getThemeColor)('dotEmpty')('◌');
87
+ rangeVersionText = chalk_1.default.gray('unavailable');
88
+ }
89
+ else if (state.hasRangeUpdate) {
71
90
  rangeDot = isRangeSelected ? (0, themes_colors_1.getThemeColor)('dot')('●') : (0, themes_colors_1.getThemeColor)('dotEmpty')('○');
72
91
  const rangeVersionWithPrefix = utils_1.VersionUtils.applyVersionPrefix(state.currentVersionSpecifier, state.rangeVersion);
73
92
  rangeVersionText = (0, themes_colors_1.getThemeColor)('versionRange')(rangeVersionWithPrefix);
@@ -79,7 +98,15 @@ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
79
98
  // Latest version dot and version
80
99
  let latestDot = '';
81
100
  let latestVersionText = '';
82
- if (state.hasMajorUpdate) {
101
+ if (isPending) {
102
+ latestDot = (0, themes_colors_1.getThemeColor)('dotEmpty')('◌');
103
+ latestVersionText = chalk_1.default.gray('loading');
104
+ }
105
+ else if (isFailed) {
106
+ latestDot = (0, themes_colors_1.getThemeColor)('dotEmpty')('◌');
107
+ latestVersionText = chalk_1.default.gray('unavailable');
108
+ }
109
+ else if (state.hasMajorUpdate) {
83
110
  latestDot = isLatestSelected ? (0, themes_colors_1.getThemeColor)('dot')('●') : (0, themes_colors_1.getThemeColor)('dotEmpty')('○');
84
111
  const latestVersionWithPrefix = utils_1.VersionUtils.applyVersionPrefix(state.currentVersionSpecifier, state.latestVersion);
85
112
  latestVersionText = (0, themes_colors_1.getThemeColor)('versionLatest')(latestVersionWithPrefix);
@@ -112,26 +139,36 @@ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
112
139
  const displayName = truncatedName !== state.name ? truncatedName : packageName;
113
140
  // Package name with dashes and badge at the end
114
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
115
145
  const nameLength = utils_1.VersionUtils.getVisualLength(truncatedName);
116
- const namePadding = Math.max(0, packageNameWidth - nameLength - 1 - badgeWidth); // -1 for space after package name, -badgeWidth for badge at end
117
- const nameDashes = shouldShowDashes(namePadding) ? dashColor('-').repeat(namePadding) : ' '.repeat(namePadding);
118
- // 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}` : '';
119
152
  const packageNameSection = typeBadge
120
- ? `${displayName} ${nameDashes}${typeBadge}`
121
- : `${displayName} ${nameDashes}`;
153
+ ? `${displayName} ${nameDashes}${vulnSuffix}${typeBadge}`
154
+ : `${displayName} ${nameDashes}${vulnSuffix}`;
122
155
  // Current version section with dashes only if needed
123
156
  const currentSection = `${currentDot} ${currentVersion}`;
124
157
  const currentSectionLength = utils_1.VersionUtils.getVisualLength(currentSection) + 1; // +1 for space before padding
125
158
  const currentPadding = Math.max(0, currentColumnWidth - currentSectionLength);
126
- const currentPaddingText = shouldShowDashes(currentPadding) ? dashColor('-').repeat(currentPadding) : ' '.repeat(currentPadding);
159
+ const currentPaddingText = shouldShowDashes(currentPadding)
160
+ ? dashColor('-').repeat(currentPadding)
161
+ : ' '.repeat(currentPadding);
127
162
  const currentWithPadding = currentSection + ' ' + currentPaddingText;
128
163
  // Range version section with dashes only if needed
129
164
  let rangeSection = '';
130
- if (state.hasRangeUpdate) {
165
+ if (isPending || isFailed || state.hasRangeUpdate) {
131
166
  rangeSection = `${rangeDot} ${rangeVersionText}`;
132
167
  const rangeSectionLength = utils_1.VersionUtils.getVisualLength(rangeSection) + 1; // +1 for space before padding
133
168
  const rangePadding = Math.max(0, rangeColumnWidth - rangeSectionLength);
134
- const rangePaddingText = shouldShowDashes(rangePadding) ? dashColor('-').repeat(rangePadding) : ' '.repeat(rangePadding);
169
+ const rangePaddingText = shouldShowDashes(rangePadding)
170
+ ? dashColor('-').repeat(rangePadding)
171
+ : ' '.repeat(rangePadding);
135
172
  rangeSection += ' ' + rangePaddingText;
136
173
  }
137
174
  else {
@@ -140,11 +177,13 @@ function renderPackageLine(state, index, isCurrentRow, terminalWidth = 80) {
140
177
  }
141
178
  // Latest version section with dashes only if needed
142
179
  let latestSection = '';
143
- if (state.hasMajorUpdate) {
180
+ if (isPending || isFailed || state.hasMajorUpdate) {
144
181
  latestSection = `${latestDot} ${latestVersionText}`;
145
182
  const latestSectionLength = utils_1.VersionUtils.getVisualLength(latestSection) + 1; // +1 for space before padding
146
183
  const latestPadding = Math.max(0, latestColumnWidth - latestSectionLength);
147
- const latestPaddingText = shouldShowDashes(latestPadding) ? dashColor('-').repeat(latestPadding) : ' '.repeat(latestPadding);
184
+ const latestPaddingText = shouldShowDashes(latestPadding)
185
+ ? dashColor('-').repeat(latestPadding)
186
+ : ' '.repeat(latestPadding);
148
187
  latestSection += ' ' + latestPaddingText;
149
188
  }
150
189
  else {
@@ -171,7 +210,7 @@ function renderSpacer() {
171
210
  /**
172
211
  * Render the main interface
173
212
  */
174
- function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems, activeFilterLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth = 80) {
213
+ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forceFullRender, renderableItems, activeFilterLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth = 80, loadingProgress, auditProgress, options = {}) {
175
214
  const output = [];
176
215
  // Header section (same for initial and incremental render)
177
216
  if (packageManager) {
@@ -186,20 +225,33 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
186
225
  // Each character in "inup" gets a different color
187
226
  const inupColors = [chalk_1.default.red, chalk_1.default.yellow, chalk_1.default.blue, chalk_1.default.magenta];
188
227
  const coloredInup = inupColors.map((color, i) => color.bold('inup'[i])).join('');
189
- 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})`);
190
233
  // Show filter state (always show, including "All")
191
234
  const fullHeaderLine = activeFilterLabel
192
- ? 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)
193
238
  : headerLine;
194
239
  // Pad to terminal width to clear any leftover characters
195
240
  const headerPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(fullHeaderLine));
196
241
  output.push(fullHeaderLine + ' '.repeat(headerPadding));
197
242
  }
198
243
  else {
199
- 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');
200
250
  // Show filter state (always show, including "All")
201
251
  const fullHeaderLine = activeFilterLabel
202
- ? 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)
203
255
  : headerLine;
204
256
  // Pad to terminal width to clear any leftover characters
205
257
  const headerPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(fullHeaderLine));
@@ -208,14 +260,20 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
208
260
  output.push('');
209
261
  if (filterMode) {
210
262
  // Show filter input with cursor when actively filtering
211
- 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')('█');
212
267
  // Pad to terminal width to clear any leftover characters from backspace
213
268
  const padding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(filterDisplay));
214
269
  output.push(filterDisplay + ' '.repeat(padding));
215
270
  }
216
271
  else if (filterQuery) {
217
272
  // Show applied filter when not in filter mode but filter is active
218
- 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)');
219
277
  const padding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(filterDisplay));
220
278
  output.push(filterDisplay + ' '.repeat(padding));
221
279
  }
@@ -237,6 +295,9 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
237
295
  chalk_1.default.bold.white('I ') +
238
296
  (0, themes_colors_1.getThemeColor)('textSecondary')('Info') +
239
297
  ' ' +
298
+ chalk_1.default.bold.white('S ') +
299
+ (0, themes_colors_1.getThemeColor)('textSecondary')('Vulnerable') +
300
+ ' ' +
240
301
  chalk_1.default.bold.white('M ') +
241
302
  (0, themes_colors_1.getThemeColor)('textSecondary')('Minor') +
242
303
  ' ' +
@@ -257,67 +318,97 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
257
318
  if (filterMode) {
258
319
  // In filter mode, show Enter to apply and ESC to clear
259
320
  if (totalPackages === 0) {
260
- statusLine = (0, themes_colors_1.getThemeColor)('warning')(`No matches found`) +
261
- ' ' +
262
- 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');
263
326
  }
264
327
  else if (totalVisualItems > maxVisibleItems) {
265
- 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`) +
266
- ' ' +
267
- chalk_1.default.bold.white('Enter ') + chalk_1.default.gray('Apply') +
268
- ' ' +
269
- 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');
270
336
  }
271
337
  else {
272
- statusLine = (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing all ${chalk_1.default.white(totalPackages)} matches`) +
273
- ' ' +
274
- chalk_1.default.bold.white('Enter ') + chalk_1.default.gray('Apply') +
275
- ' ' +
276
- 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');
277
346
  }
278
347
  }
279
348
  else if (totalPackages < totalBeforeFilter) {
280
349
  // Filter is applied but not in filter mode
281
350
  if (totalVisualItems > maxVisibleItems) {
282
- 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`) +
283
- ' ' +
284
- chalk_1.default.bold.white('D/P/O ') + chalk_1.default.gray('Filter') +
285
- ' ' +
286
- chalk_1.default.bold.white('M ') + chalk_1.default.gray('Minor') +
287
- ' ' +
288
- chalk_1.default.bold.white('L ') + chalk_1.default.gray('All') +
289
- ' ' +
290
- chalk_1.default.bold.white('U ') + chalk_1.default.gray('None') +
291
- ' ' +
292
- 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');
293
368
  }
294
369
  else {
295
- statusLine = (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing all ${chalk_1.default.white(totalPackages)} matches`) +
296
- ' ' +
297
- chalk_1.default.bold.white('D/P/O ') + chalk_1.default.gray('Filter') +
298
- ' ' +
299
- chalk_1.default.bold.white('M ') + chalk_1.default.gray('Minor') +
300
- ' ' +
301
- chalk_1.default.bold.white('L ') + chalk_1.default.gray('All') +
302
- ' ' +
303
- chalk_1.default.bold.white('U ') + chalk_1.default.gray('None') +
304
- ' ' +
305
- 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');
306
387
  }
307
388
  }
308
389
  else {
309
390
  // No filter applied
310
391
  if (totalVisualItems > maxVisibleItems) {
311
- statusLine = chalk_1.default.gray(`Showing ${chalk_1.default.white(startItem)}-${chalk_1.default.white(endItem)} of ${chalk_1.default.white(totalPackages)} packages`) +
312
- ' ' +
313
- 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');
314
397
  }
315
398
  else {
316
- statusLine = chalk_1.default.gray(`Showing all ${chalk_1.default.white(totalPackages)} packages`) +
317
- ' ' +
318
- 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');
319
404
  }
320
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
+ }
321
412
  // Pad status line to terminal width to clear any leftover characters
322
413
  const statusLineFull = ' ' + statusLine;
323
414
  const statusPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(statusLineFull));
@@ -335,7 +426,7 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
335
426
  output.push(renderSpacer());
336
427
  }
337
428
  else if (item.type === 'package') {
338
- const line = renderPackageLine(item.state, item.originalIndex, item.originalIndex === currentRow, terminalWidth);
429
+ const line = renderPackageLine(item.state, item.originalIndex, item.originalIndex === currentRow, terminalWidth, options);
339
430
  output.push(line);
340
431
  }
341
432
  }
@@ -343,11 +434,20 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, forc
343
434
  else {
344
435
  // Fallback to flat rendering (legacy mode)
345
436
  for (let i = scrollOffset; i < Math.min(scrollOffset + maxVisibleItems, states.length); i++) {
346
- const line = renderPackageLine(states[i], i, i === currentRow, terminalWidth);
437
+ const line = renderPackageLine(states[i], i, i === currentRow, terminalWidth, options);
347
438
  output.push(line);
348
439
  }
349
440
  }
350
- return output;
441
+ if (loadingProgress?.isLoading) {
442
+ const loadingLabel = `Loading packages... (${loadingProgress.resolved}/${loadingProgress.total} checked)`;
443
+ const failedLabel = loadingProgress.failed > 0 ? ` ${loadingProgress.failed} unavailable` : '';
444
+ const loadingLine = ' ' +
445
+ (0, themes_colors_1.getThemeColor)('textSecondary')(loadingLabel) +
446
+ (failedLabel ? chalk_1.default.yellow(failedLabel) : '');
447
+ const loadingPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(loadingLine));
448
+ output.push(loadingLine + ' '.repeat(loadingPadding));
449
+ }
450
+ return output.map((line) => padLineToWidth(line, terminalWidth));
351
451
  }
352
452
  /**
353
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,
@@ -68,7 +69,7 @@ class StateManager {
68
69
  return;
69
70
  const currentRow = this.navigationManager.getCurrentRow();
70
71
  const currentState = states[currentRow];
71
- if (!currentState)
72
+ if (!currentState || currentState.loadState !== 'ready')
72
73
  return;
73
74
  if (direction === 'left') {
74
75
  // Move selection left with wraparound: latest -> range -> none -> latest
@@ -122,7 +123,7 @@ class StateManager {
122
123
  if (states.length === 0)
123
124
  return;
124
125
  states.forEach((state) => {
125
- if (state.hasRangeUpdate) {
126
+ if (state.loadState === 'ready' && state.hasRangeUpdate) {
126
127
  state.selectedOption = 'range';
127
128
  }
128
129
  });
@@ -131,10 +132,10 @@ class StateManager {
131
132
  if (states.length === 0)
132
133
  return;
133
134
  states.forEach((state) => {
134
- if (state.hasMajorUpdate) {
135
+ if (state.loadState === 'ready' && state.hasMajorUpdate) {
135
136
  state.selectedOption = 'latest';
136
137
  }
137
- else if (state.hasRangeUpdate) {
138
+ else if (state.loadState === 'ready' && state.hasRangeUpdate) {
138
139
  state.selectedOption = 'range';
139
140
  }
140
141
  });
@@ -143,22 +144,48 @@ class StateManager {
143
144
  if (states.length === 0)
144
145
  return;
145
146
  states.forEach((state) => {
146
- state.selectedOption = 'none';
147
+ if (state.loadState === 'ready') {
148
+ state.selectedOption = 'none';
149
+ }
147
150
  });
148
151
  }
149
152
  // Modal delegation
150
153
  toggleInfoModal() {
151
154
  const currentRow = this.navigationManager.getCurrentRow();
152
- this.modalManager.toggleInfoModal(currentRow);
155
+ const sessionId = this.modalManager.toggleInfoModal(currentRow);
153
156
  this.renderState.forceFullRender = true;
157
+ return sessionId;
154
158
  }
155
159
  closeInfoModal() {
156
160
  this.modalManager.closeInfoModal();
157
161
  this.renderState.forceFullRender = true;
158
162
  }
159
- setModalLoading(isLoading) {
160
- this.modalManager.setModalLoading(isLoading);
161
- 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);
162
189
  }
163
190
  // Filter delegation
164
191
  enterFilterMode(preserveQuery = false) {
@@ -188,8 +215,8 @@ class StateManager {
188
215
  this.navigationManager.setCurrentRow(0);
189
216
  this.navigationManager.setScrollOffset(0);
190
217
  }
191
- getFilteredStates(allStates) {
192
- return this.filterManager.getFilteredStates(allStates);
218
+ getFilteredStates(allStates, options) {
219
+ return this.filterManager.getFilteredStates(allStates, options);
193
220
  }
194
221
  toggleDependencyTypeFilter(type) {
195
222
  this.filterManager.toggleDependencyType(type);
@@ -198,6 +225,14 @@ class StateManager {
198
225
  this.navigationManager.setScrollOffset(0);
199
226
  // Use incremental render (no blink)
200
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
+ }
201
236
  getActiveFilterLabel() {
202
237
  return this.filterManager.getActiveFilterLabel();
203
238
  }
@@ -221,7 +256,9 @@ class StateManager {
221
256
  this.renderState.forceFullRender = isInitial;
222
257
  }
223
258
  resetForResize(totalFilteredItems) {
224
- const totalItems = totalFilteredItems || this.renderState.renderableItems.length || this.displayState.maxVisibleItems;
259
+ const totalItems = totalFilteredItems ||
260
+ this.renderState.renderableItems.length ||
261
+ this.displayState.maxVisibleItems;
225
262
  this.navigationManager.resetForResize(totalItems);
226
263
  this.renderState.forceFullRender = true;
227
264
  }