inup 1.6.6 → 1.6.8

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 (110) hide show
  1. package/README.md +25 -2
  2. package/dist/{interactive-ui.js → app/interactive-ui.js} +34 -21
  3. package/dist/{core → app}/upgrade-runner.js +18 -22
  4. package/dist/cli.js +19 -18
  5. package/dist/{services → features/audit}/background-audit.js +2 -2
  6. package/dist/features/{headless/vulnerability-audit.js → audit/headless-audit.js} +8 -11
  7. package/dist/features/audit/index.js +36 -0
  8. package/dist/{ui/presenters/vulnerability.js → features/audit/presenter.js} +1 -3
  9. package/dist/{ui/controllers → features/audit}/vulnerability-audit-controller.js +14 -8
  10. package/dist/{services → features/audit}/vulnerability-checker.js +9 -10
  11. package/dist/features/changelog/clients/github-client.js +41 -21
  12. package/dist/features/changelog/clients/npm-registry-client.js +16 -2
  13. package/dist/features/changelog/index.js +1 -1
  14. package/dist/features/changelog/services/package-metadata-service.js +6 -2
  15. package/dist/features/changelog/services/release-notes-service.js +9 -12
  16. package/dist/features/changelog/types.js +3 -0
  17. package/dist/features/debug/index.js +3 -4
  18. package/dist/features/debug/{services/perf-logger.js → perf-logger.js} +15 -15
  19. package/dist/features/debug/types.js +3 -0
  20. package/dist/features/headless/headless-runner.js +32 -19
  21. package/dist/features/headless/index.js +2 -5
  22. package/dist/features/headless/report.js +2 -0
  23. package/dist/features/interactive/controllers/index.js +6 -0
  24. package/dist/{ui → features/interactive}/controllers/package-info-modal-controller.js +3 -3
  25. package/dist/features/interactive/index.js +21 -0
  26. package/dist/{ui → features/interactive}/input-handler.js +6 -6
  27. package/dist/{ui → features/interactive}/modal/index.js +1 -1
  28. package/dist/{ui → features/interactive}/modal/layout.js +5 -5
  29. package/dist/{ui/renderer/package-list.js → features/interactive/modal/package-info-sections/index.js} +3 -3
  30. package/dist/{ui → features/interactive}/modal/package-info-sections/release-notes.js +1 -1
  31. package/dist/{ui → features/interactive}/modal/package-info-sections/sections.js +55 -24
  32. package/dist/{ui → features/interactive}/modal/package-info-sections/text.js +6 -6
  33. package/dist/{ui → features/interactive}/modal/package-info.js +33 -18
  34. package/dist/features/interactive/modal/types.js +3 -0
  35. package/dist/{ui → features/interactive}/presenters/health.js +2 -2
  36. package/dist/{ui → features/interactive}/renderer/confirmation.js +6 -3
  37. package/dist/{ui → features/interactive}/renderer/help-modal.js +3 -3
  38. package/dist/{ui → features/interactive}/renderer/index.js +3 -4
  39. package/dist/{types.js → features/interactive/renderer/package-list/index.js} +3 -4
  40. package/dist/{ui → features/interactive}/renderer/package-list/interface.js +15 -16
  41. package/dist/{ui → features/interactive}/renderer/package-list/rows.js +31 -25
  42. package/dist/features/{debug → interactive}/renderer/performance-modal.js +3 -3
  43. package/dist/{ui/utils/version.js → features/interactive/renderer/version-format.js} +9 -13
  44. package/dist/{ui → features/interactive}/session/action-dispatcher.js +2 -2
  45. package/dist/{ui → features/interactive}/session/index.js +5 -4
  46. package/dist/{ui → features/interactive}/session/interactive-session.js +44 -23
  47. package/dist/{ui → features/interactive}/session/selection-state-builder.js +25 -9
  48. package/dist/{ui → features/interactive}/state/filter-manager.js +3 -3
  49. package/dist/{ui → features/interactive}/state/index.js +7 -7
  50. package/dist/{ui → features/interactive}/state/state-manager.js +10 -8
  51. package/dist/{ui → features/interactive}/state/theme-manager.js +7 -5
  52. package/dist/{ui → features/interactive}/themes-colors.js +3 -1
  53. package/dist/features/upgrade/index.js +8 -0
  54. package/dist/{core → features/upgrade}/package-detector.js +96 -37
  55. package/dist/{core → features/upgrade}/upgrader.js +84 -35
  56. package/dist/index.js +2 -2
  57. package/dist/{config → shared/config}/package-meta.js +1 -1
  58. package/dist/{config → shared/config}/project-config.js +10 -8
  59. package/dist/{utils/config.js → shared/config/user-config.js} +11 -11
  60. package/dist/{utils → shared}/debug-logger.js +9 -9
  61. package/dist/{utils → shared}/exec.js +4 -4
  62. package/dist/{ui/modal/package-info-sections.js → shared/fs/index.js} +4 -3
  63. package/dist/{utils/filesystem → shared/fs}/io.js +3 -4
  64. package/dist/shared/fs/paths.js +19 -0
  65. package/dist/{utils/filesystem → shared/fs}/scan.js +23 -20
  66. package/dist/{utils → shared}/git.js +3 -3
  67. package/dist/{services → shared}/http/etag-store.js +56 -4
  68. package/dist/{services → shared}/http/resizable-semaphore.js +3 -1
  69. package/dist/{utils → shared}/local-env.js +8 -8
  70. package/dist/{services/package-manager-detector.js → shared/package-manager.js} +26 -25
  71. package/dist/shared/pnpm-catalogs.js +138 -0
  72. package/dist/{services → shared/registry}/npm-registry.js +54 -33
  73. package/dist/shared/registry/registry-config.js +76 -0
  74. package/dist/{services → shared/registry}/version-checker.js +25 -26
  75. package/dist/{ui/utils → shared/terminal}/cursor.js +1 -1
  76. package/dist/{ui/utils → shared/terminal}/index.js +5 -7
  77. package/dist/{ui/utils → shared/terminal}/terminal-input.js +5 -0
  78. package/dist/shared/terminal/text.js +48 -0
  79. package/dist/shared/types/index.js +20 -0
  80. package/dist/{utils/version.js → shared/versions.js} +45 -30
  81. package/package.json +21 -8
  82. package/dist/core/index.js +0 -23
  83. package/dist/features/changelog/types/changelog.types.js +0 -3
  84. package/dist/features/debug/types/debug.types.js +0 -3
  85. package/dist/services/index.js +0 -25
  86. package/dist/ui/controllers/index.js +0 -8
  87. package/dist/ui/index.js +0 -27
  88. package/dist/ui/presenters/index.js +0 -11
  89. package/dist/ui/utils/text.js +0 -76
  90. package/dist/utils/filesystem/paths.js +0 -19
  91. package/dist/utils/filesystem.js +0 -20
  92. package/dist/utils/index.js +0 -34
  93. /package/dist/{ui/modal → features/audit}/types.js +0 -0
  94. /package/dist/features/debug/{services/performance-tracker.js → performance-tracker.js} +0 -0
  95. /package/dist/{ui → features/interactive}/keymap.js +0 -0
  96. /package/dist/{ui → features/interactive}/modal/theme-selector.js +0 -0
  97. /package/dist/{ui → features/interactive}/state/modal-manager.js +0 -0
  98. /package/dist/{ui → features/interactive}/state/navigation-manager.js +0 -0
  99. /package/dist/{ui → features/interactive}/themes.js +0 -0
  100. /package/dist/{config → shared/config}/constants.js +0 -0
  101. /package/dist/{config → shared/config}/index.js +0 -0
  102. /package/dist/{utils → shared}/engines.js +0 -0
  103. /package/dist/{services → shared}/http/adaptive-controller.js +0 -0
  104. /package/dist/{services → shared}/http/inflight.js +0 -0
  105. /package/dist/{services → shared}/http/retry.js +0 -0
  106. /package/dist/{utils → shared}/manifest.js +0 -0
  107. /package/dist/{utils → shared/terminal}/color.js +0 -0
  108. /package/dist/{types → shared/types}/domain.js +0 -0
  109. /package/dist/{types → shared/types}/streaming.js +0 -0
  110. /package/dist/{types → shared/types}/ui.js +0 -0
@@ -7,25 +7,30 @@ exports.buildUsedBySections = buildUsedBySections;
7
7
  exports.buildPackageInfoSections = buildPackageInfoSections;
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
+ const engines_1 = require("../../../../shared/engines");
11
+ const terminal_1 = require("../../../../shared/terminal");
12
+ const audit_1 = require("../../../audit");
10
13
  const themes_colors_1 = require("../../themes-colors");
11
- const vulnerability_1 = require("../../presenters/vulnerability");
12
- const utils_1 = require("../../utils");
13
- const utils_2 = require("../../../utils");
14
14
  const release_notes_1 = require("./release-notes");
15
15
  function formatNumber(num) {
16
16
  if (!num)
17
17
  return 'N/A';
18
18
  if (num >= 1000000)
19
- return (num / 1000000).toFixed(1) + 'M';
19
+ return `${(num / 1000000).toFixed(1)}M`;
20
20
  if (num >= 1000)
21
- return (num / 1000).toFixed(1) + 'K';
21
+ return `${(num / 1000).toFixed(1)}K`;
22
22
  return num.toString();
23
23
  }
24
24
  function getUsedByPaths(state) {
25
+ // Catalog entries live in pnpm-workspace.yaml; the interesting "used by"
26
+ // answer is the member packages that reference the catalog entry.
27
+ if (state.catalog && state.catalogReferencedBy?.length) {
28
+ return state.catalogReferencedBy;
29
+ }
25
30
  return state.packageJsonPaths ?? [state.packageJsonPath];
26
31
  }
27
32
  function buildTabBarSuffix(activeTab, usedByCount) {
28
- const styleFor = (tab) => tab === activeTab ? chalk_1.default.bold.underline : chalk_1.default.gray;
33
+ const styleFor = (tab) => (tab === activeTab ? chalk_1.default.bold.underline : chalk_1.default.gray);
29
34
  const usedByLabel = `Used by${usedByCount > 0 ? ` (${usedByCount})` : ''}`;
30
35
  return (' ' +
31
36
  chalk_1.default.gray('[ ') +
@@ -40,14 +45,16 @@ function buildUsedBySections(state, modalWidth) {
40
45
  const contentWidth = Math.max(10, modalWidth - 4);
41
46
  const formatRelative = (absolutePath) => {
42
47
  const display = node_path_1.default.relative(cwd, absolutePath) || absolutePath;
43
- return (0, utils_1.truncatePlainText)(display, contentWidth);
48
+ return (0, terminal_1.truncatePlainText)(display, contentWidth);
44
49
  };
45
- return [
50
+ const sections = [
46
51
  {
47
52
  key: 'used-by-summary',
48
53
  rows: [
49
54
  chalk_1.default.bold(`${paths.length} package.json file${paths.length === 1 ? '' : 's'} depend on ${state.name}`),
50
- chalk_1.default.gray(`Type: ${state.type}`),
55
+ chalk_1.default.gray(state.catalog
56
+ ? `Type: ${state.type} • via catalog:${state.catalog}`
57
+ : `Type: ${state.type}`),
51
58
  ],
52
59
  required: true,
53
60
  behavior: 'pinned',
@@ -58,6 +65,25 @@ function buildUsedBySections(state, modalWidth) {
58
65
  behavior: 'body',
59
66
  },
60
67
  ];
68
+ // For catalog entries, also show what else the catalog pins — the whole
69
+ // point of a catalog is that these versions move together.
70
+ if (state.catalog && state.catalogEntries?.length) {
71
+ sections.push({
72
+ key: 'catalog-contents',
73
+ rows: [
74
+ chalk_1.default.bold(`Catalog "${state.catalog}"`) + chalk_1.default.gray(' — pnpm-workspace.yaml'),
75
+ ...state.catalogEntries.map((entry) => {
76
+ const isCurrent = entry.name === state.name;
77
+ const label = (0, terminal_1.truncatePlainText)(`${entry.name}: ${entry.range}`, contentWidth - 2);
78
+ return isCurrent
79
+ ? `${(0, themes_colors_1.getThemeColor)('packageName')('▸')} ${chalk_1.default.bold(label)}`
80
+ : `${chalk_1.default.gray('•')} ${label}`;
81
+ }),
82
+ ],
83
+ behavior: 'body',
84
+ });
85
+ }
86
+ return sections;
61
87
  }
62
88
  function buildPackageInfoSections(state, modalWidth, activeTab) {
63
89
  const title = chalk_1.default.bold('Package: ') +
@@ -82,6 +108,11 @@ function buildPackageInfoSections(state, modalWidth, activeTab) {
82
108
  key: 'meta',
83
109
  rows: [
84
110
  `Current: ${currentVersion} Target: ${targetVersion}`,
111
+ ...(state.catalog
112
+ ? [
113
+ (0, themes_colors_1.getThemeColor)('textSecondary')(`Catalog: ${state.catalog} — range lives in pnpm-workspace.yaml`),
114
+ ]
115
+ : []),
85
116
  ...(state.weeklyDownloads !== undefined
86
117
  ? [(0, themes_colors_1.getThemeColor)('primary')(`Downloads/week: ${formatNumber(state.weeklyDownloads)}`)]
87
118
  : []),
@@ -95,13 +126,13 @@ function buildPackageInfoSections(state, modalWidth, activeTab) {
95
126
  // Wrap (don't truncate) so a deprecation URL stays whole and clickable —
96
127
  // truncation with "..." produces a dead link. `wrapPlainText` breaks on
97
128
  // spaces only, so the URL keeps its own intact line. No emoji marker:
98
- // `getVisualLength` scores glyphs like as width 2 while many terminals
99
- // render them as width 1, which throws off the modal's border alignment.
100
- for (const line of (0, utils_1.wrapPlainText)(`Deprecated: ${state.deprecated}`, warningContentWidth)) {
129
+ // terminal fonts disagree on whether such glyphs render one or two columns
130
+ // wide, which throws off the modal's border alignment.
131
+ for (const line of (0, terminal_1.wrapPlainText)(`Deprecated: ${state.deprecated}`, warningContentWidth)) {
101
132
  warningRows.push((0, themes_colors_1.getThemeColor)('warning')(line));
102
133
  }
103
134
  }
104
- const engineWarning = (0, utils_2.checkNodeEngineCompatibility)(state.enginesNode);
135
+ const engineWarning = (0, engines_1.checkNodeEngineCompatibility)(state.enginesNode);
105
136
  if (engineWarning) {
106
137
  warningRows.push((0, themes_colors_1.getThemeColor)('warning')(`Hold: ${engineWarning}`));
107
138
  }
@@ -116,7 +147,7 @@ function buildPackageInfoSections(state, modalWidth, activeTab) {
116
147
  sections.push({
117
148
  key: 'homepage',
118
149
  rows: [
119
- `Homepage: ${chalk_1.default.underline((0, themes_colors_1.getThemeColor)('primary')((0, utils_1.truncatePlainText)(state.homepage, modalWidth - 14)))}`,
150
+ `Homepage: ${chalk_1.default.underline((0, themes_colors_1.getThemeColor)('primary')((0, terminal_1.truncatePlainText)(state.homepage, modalWidth - 14)))}`,
120
151
  ],
121
152
  behavior: 'pinned',
122
153
  });
@@ -124,30 +155,30 @@ function buildPackageInfoSections(state, modalWidth, activeTab) {
124
155
  if (state.description) {
125
156
  sections.push({
126
157
  key: 'description',
127
- rows: (0, utils_1.wrapPlainText)(state.description, modalWidth - 4)
158
+ rows: (0, terminal_1.wrapPlainText)(state.description, modalWidth - 4)
128
159
  .slice(0, 4)
129
160
  .map((line, index, rows) => index === rows.length - 1 && rows.length === 4
130
- ? (0, utils_1.truncatePlainText)(line, modalWidth - 4)
161
+ ? (0, terminal_1.truncatePlainText)(line, modalWidth - 4)
131
162
  : line),
132
163
  behavior: 'pinned',
133
164
  });
134
165
  }
135
166
  if (state.vulnerability && state.vulnerability.count > 0) {
136
- const representative = (0, vulnerability_1.selectRepresentativeAdvisory)(state.vulnerability);
137
- const severityColor = (0, vulnerability_1.getVulnerabilitySeverityColor)(state.vulnerability.highestSeverity);
167
+ const representative = (0, audit_1.selectRepresentativeAdvisory)(state.vulnerability);
168
+ const severityColor = (0, audit_1.getVulnerabilitySeverityColor)(state.vulnerability.highestSeverity);
138
169
  const vulnerabilityRows = [
139
170
  chalk_1.default.red.bold(`${state.vulnerability.count} known vulnerabilit${state.vulnerability.count === 1 ? 'y' : 'ies'} (${severityColor(state.vulnerability.highestSeverity.toUpperCase())})`),
140
171
  ];
141
172
  if (representative) {
142
173
  const severityLabel = ` ${severityColor(`[${representative.severity.toUpperCase()}]`)} `;
143
- const availableTitleWidth = Math.max(0, modalWidth - 4 - (0, utils_1.getVisualLength)(severityLabel));
144
- vulnerabilityRows.push(`${severityLabel}${(0, utils_1.truncatePlainText)(representative.title, availableTitleWidth)}`);
174
+ const availableTitleWidth = Math.max(0, modalWidth - 4 - (0, terminal_1.getVisualLength)(severityLabel));
175
+ vulnerabilityRows.push(`${severityLabel}${(0, terminal_1.truncatePlainText)(representative.title, availableTitleWidth)}`);
145
176
  }
146
177
  const detailsUrl = state.vulnerability.detailsUrl || representative?.url;
147
178
  if (detailsUrl) {
148
- const linkPrefix = ` ${(0, vulnerability_1.getVulnerabilityLinkLabel)(detailsUrl)} `;
149
- const availableLinkWidth = Math.max(0, modalWidth - 4 - (0, utils_1.getVisualLength)(linkPrefix));
150
- vulnerabilityRows.push(`${linkPrefix}${chalk_1.default.underline((0, themes_colors_1.getThemeColor)('primary')((0, utils_1.truncatePlainText)(detailsUrl, availableLinkWidth)))}`);
179
+ const linkPrefix = ` ${(0, audit_1.getVulnerabilityLinkLabel)(detailsUrl)} `;
180
+ const availableLinkWidth = Math.max(0, modalWidth - 4 - (0, terminal_1.getVisualLength)(linkPrefix));
181
+ vulnerabilityRows.push(`${linkPrefix}${chalk_1.default.underline((0, themes_colors_1.getThemeColor)('primary')((0, terminal_1.truncatePlainText)(detailsUrl, availableLinkWidth)))}`);
151
182
  }
152
183
  if (state.vulnerability.count > 1) {
153
184
  vulnerabilityRows.push(chalk_1.default.gray(` ... and ${state.vulnerability.count - 1} more`));
@@ -163,7 +194,7 @@ function buildPackageInfoSections(state, modalWidth, activeTab) {
163
194
  sections.push({
164
195
  key: 'changelog',
165
196
  rows: [
166
- `Changelog: ${chalk_1.default.underline((0, themes_colors_1.getThemeColor)('primary')((0, utils_1.truncatePlainText)(state.repository, modalWidth - 15)))}`,
197
+ `Changelog: ${chalk_1.default.underline((0, themes_colors_1.getThemeColor)('primary')((0, terminal_1.truncatePlainText)(state.repository, modalWidth - 15)))}`,
167
198
  ],
168
199
  behavior: 'pinned',
169
200
  });
@@ -8,7 +8,7 @@ exports.linkifyRepositoryReferences = linkifyRepositoryReferences;
8
8
  exports.linkifyMarkdownText = linkifyMarkdownText;
9
9
  exports.pushWrappedLines = pushWrappedLines;
10
10
  exports.isLowSignalTrailerLine = isLowSignalTrailerLine;
11
- const utils_1 = require("../../utils");
11
+ const terminal_1 = require("../../../../shared/terminal");
12
12
  function formatTerminalLink(label, url) {
13
13
  return `]8;;${url}${label}]8;;`;
14
14
  }
@@ -33,7 +33,7 @@ function linkifyContributorMentions(text) {
33
33
  }
34
34
  function linkifyRepositoryReferences(text, repositoryUrl) {
35
35
  const repoBaseUrl = getRepositoryBaseUrl(repositoryUrl);
36
- if (!repoBaseUrl || !repoBaseUrl.includes('github.com')) {
36
+ if (!repoBaseUrl?.includes('github.com')) {
37
37
  return text;
38
38
  }
39
39
  return text
@@ -48,16 +48,16 @@ function linkifyMarkdownText(text, repositoryUrl) {
48
48
  return linkifyRepositoryReferences(linkifyContributorMentions(text), repositoryUrl);
49
49
  }
50
50
  function pushWrappedLines(lines, text, width, firstPrefix, restPrefix = firstPrefix, style) {
51
- const firstWidth = Math.max(1, width - (0, utils_1.getVisualLength)(firstPrefix));
52
- const restWidth = Math.max(1, width - (0, utils_1.getVisualLength)(restPrefix));
53
- const segments = (0, utils_1.wrapPlainText)(text, firstWidth);
51
+ const firstWidth = Math.max(1, width - (0, terminal_1.getVisualLength)(firstPrefix));
52
+ const restWidth = Math.max(1, width - (0, terminal_1.getVisualLength)(restPrefix));
53
+ const segments = (0, terminal_1.wrapPlainText)(text, firstWidth);
54
54
  if (segments.length === 0) {
55
55
  lines.push(firstPrefix.trimEnd());
56
56
  return;
57
57
  }
58
58
  lines.push(firstPrefix + (style ? style(segments[0]) : segments[0]));
59
59
  for (let i = 1; i < segments.length; i++) {
60
- const wrappedSegments = (0, utils_1.wrapPlainText)(segments[i], restWidth);
60
+ const wrappedSegments = (0, terminal_1.wrapPlainText)(segments[i], restWidth);
61
61
  for (const segment of wrappedSegments) {
62
62
  lines.push(restPrefix + (style ? style(segment) : segment));
63
63
  }
@@ -51,14 +51,20 @@ function renderPackageInfoModal(state, terminalWidth = 80, terminalHeight = 24,
51
51
  }
52
52
  const fixedModalHeight = maxHeight;
53
53
  const padding = Math.floor((terminalWidth - modalWidth) / 2);
54
- const pinnedSections = allSections.filter((section) => (section.behavior ?? 'pinned') === 'pinned');
55
- const bodySections = allSections.filter((section) => (section.behavior ?? 'pinned') !== 'pinned');
54
+ // Every section built by buildPackageInfoSections declares a behavior, so we
55
+ // partition directly: 'pinned' stays fixed, everything else scrolls.
56
+ const pinnedSections = allSections.filter((section) => section.behavior === 'pinned');
57
+ const bodySections = allSections.filter((section) => section.behavior !== 'pinned');
56
58
  const minBodyRows = 3;
57
- const reservedBodyRows = minBodyRows + (bodySections.length > 0 ? 1 : 0);
59
+ // hasScrollableBody guaranteed a 'body' section above, so bodySections is
60
+ // non-empty and always reserves its separator row.
61
+ const reservedBodyRows = minBodyRows + 1;
58
62
  const maxPinnedHeight = Math.max(6, fixedModalHeight - reservedBodyRows);
59
63
  const fittedPinned = (0, layout_1.fitModalSections)(pinnedSections, maxPinnedHeight, trimOrder);
60
64
  const pinnedRowCount = (0, layout_1.getModalSectionRowCount)(fittedPinned);
61
- const availableForBody = Math.max(minBodyRows, fixedModalHeight - 2 - pinnedRowCount - (bodySections.length > 0 ? 1 : 0));
65
+ // bodySections is non-empty here (see reservedBodyRows), so its separator
66
+ // row is always subtracted.
67
+ const availableForBody = Math.max(minBodyRows, fixedModalHeight - 2 - pinnedRowCount - 1);
62
68
  const bodyRows = [];
63
69
  bodySections.forEach((section, index) => {
64
70
  if (index > 0) {
@@ -91,18 +97,24 @@ function renderPackageInfoModal(state, terminalWidth = 80, terminalHeight = 24,
91
97
  ? clampedOffset < maxScroll
92
98
  ? chalk_1.default.gray(`Lines ${clampedOffset + 1}-${Math.min(clampedOffset + visibleBodyRows, totalScrollableRows)} of ${totalScrollableRows}`)
93
99
  : chalk_1.default.gray('End of release notes')
94
- : canGoNewer || canGoOlder
95
- ? chalk_1.default.gray([canGoNewer ? '← newer version' : null, canGoOlder ? '→ older version' : null]
96
- .filter((hint) => Boolean(hint))
97
- .join(' · '))
98
- : null;
100
+ : // A truthy hint footer shrinks visibleBodyRows, which forces
101
+ // maxScroll 1 whenever the scroll path is entered so this arm can
102
+ // only resolve to the hints when maxScroll is 0, which cannot happen.
103
+ // Kept as a safety net for future layout changes.
104
+ /* v8 ignore start */
105
+ canGoNewer || canGoOlder
106
+ ? chalk_1.default.gray([canGoNewer ? '← newer version' : null, canGoOlder ? '→ older version' : null]
107
+ .filter((hint) => Boolean(hint))
108
+ .join(' · '))
109
+ : null;
110
+ /* v8 ignore stop */
99
111
  const visibleSlice = bodyRows.slice(clampedOffset, clampedOffset + visibleBodyRows);
100
112
  const lines = [];
101
113
  const topPadding = Math.max(0, Math.floor((terminalHeight - fixedModalHeight) / 2));
102
114
  for (let i = 0; i < topPadding; i++) {
103
115
  lines.push('');
104
116
  }
105
- lines.push(' '.repeat(padding) + chalk_1.default.gray('╭' + '─'.repeat(modalWidth - 2) + '╮'));
117
+ lines.push(' '.repeat(padding) + chalk_1.default.gray(`╭${'─'.repeat(modalWidth - 2)}╮`));
106
118
  fittedPinned.forEach((section, sectionIndex) => {
107
119
  if (sectionIndex > 0) {
108
120
  lines.push((0, layout_1.renderModalSeparator)(padding, modalWidth));
@@ -111,9 +123,9 @@ function renderPackageInfoModal(state, terminalWidth = 80, terminalHeight = 24,
111
123
  lines.push((0, layout_1.renderModalRow)(padding, modalWidth, row));
112
124
  }
113
125
  });
114
- if (bodySections.length > 0) {
115
- lines.push((0, layout_1.renderModalSeparator)(padding, modalWidth));
116
- }
126
+ // bodySections is never empty on the scroll path (see above), so a
127
+ // separator between the pinned rows and the body is always drawn.
128
+ lines.push((0, layout_1.renderModalSeparator)(padding, modalWidth));
117
129
  let renderedScrollRows = 0;
118
130
  for (const entry of visibleSlice) {
119
131
  if (entry.row === '__SEPARATOR__') {
@@ -124,19 +136,22 @@ function renderPackageInfoModal(state, terminalWidth = 80, terminalHeight = 24,
124
136
  }
125
137
  renderedScrollRows++;
126
138
  }
127
- const usedContentRows = pinnedRowCount +
128
- (bodySections.length > 0 ? 1 : 0) +
129
- renderedScrollRows +
130
- (resolvedFooterStatus ? 1 : 0);
139
+ const usedContentRows =
140
+ // bodySections is non-empty on this path, so its separator row always counts.
141
+ pinnedRowCount + 1 + renderedScrollRows + (resolvedFooterStatus ? 1 : 0);
131
142
  const totalContentSlots = fixedModalHeight - 2;
143
+ // The scroll path is only entered when content overflows the frame, so the
144
+ // window is always full and there are never filler rows to add.
145
+ /* v8 ignore start */
132
146
  const emptyRows = Math.max(0, totalContentSlots - usedContentRows);
133
147
  for (let i = 0; i < emptyRows; i++) {
134
148
  lines.push((0, layout_1.renderModalRow)(padding, modalWidth, ''));
135
149
  }
150
+ /* v8 ignore stop */
136
151
  if (resolvedFooterStatus) {
137
152
  lines.push((0, layout_1.renderModalRow)(padding, modalWidth, resolvedFooterStatus));
138
153
  }
139
- lines.push(' '.repeat(padding) + chalk_1.default.gray('╰' + '─'.repeat(modalWidth - 2) + '╯'));
154
+ lines.push(' '.repeat(padding) + chalk_1.default.gray(`╰${'─'.repeat(modalWidth - 2)}╯`));
140
155
  return {
141
156
  lines,
142
157
  maxScrollOffset: maxScroll,
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getHealthBadge = getHealthBadge;
4
- const utils_1 = require("../../utils");
4
+ const engines_1 = require("../../../shared/engines");
5
5
  const themes_colors_1 = require("../themes-colors");
6
6
  /**
7
7
  * A compact badge flagging a package's health in the list:
@@ -16,7 +16,7 @@ function getHealthBadge(state) {
16
16
  if (state.deprecated) {
17
17
  return (0, themes_colors_1.getThemeColor)('warning')('[DEPR]');
18
18
  }
19
- if ((0, utils_1.checkNodeEngineCompatibility)(state.enginesNode)) {
19
+ if ((0, engines_1.checkNodeEngineCompatibility)(state.enginesNode)) {
20
20
  return (0, themes_colors_1.getThemeColor)('warning')('[ENG]');
21
21
  }
22
22
  return '';
@@ -15,10 +15,13 @@ function renderConfirmation(choices) {
15
15
  // Group choices by package name to show unique packages
16
16
  const packagesByName = new Map();
17
17
  choices.forEach((choice) => {
18
- if (!packagesByName.has(choice.name)) {
19
- packagesByName.set(choice.name, []);
18
+ const group = packagesByName.get(choice.name);
19
+ if (group) {
20
+ group.push(choice);
21
+ }
22
+ else {
23
+ packagesByName.set(choice.name, [choice]);
20
24
  }
21
- packagesByName.get(choice.name).push(choice);
22
25
  });
23
26
  let output = chalk_1.default.bold(`\n🚀 Ready to upgrade ${packagesByName.size} package(s):\n`);
24
27
  packagesByName.forEach((packageChoices, packageName) => {
@@ -5,8 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.renderHelpModal = renderHelpModal;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
- const modal_1 = require("../modal");
9
8
  const keymap_1 = require("../keymap");
9
+ const modal_1 = require("../modal");
10
10
  const themes_colors_1 = require("../themes-colors");
11
11
  const KEY_COLUMN_WIDTH = 9;
12
12
  /**
@@ -46,7 +46,7 @@ function renderHelpModal(terminalWidth, terminalHeight, scrollOffset = 0) {
46
46
  const topPadding = Math.max(0, Math.floor((terminalHeight - fixedModalHeight) / 2));
47
47
  for (let i = 0; i < topPadding; i++)
48
48
  lines.push('');
49
- lines.push(' '.repeat(padding) + chalk_1.default.gray('╭' + '─'.repeat(modalWidth - 2) + '╮'));
49
+ lines.push(' '.repeat(padding) + chalk_1.default.gray(`╭${'─'.repeat(modalWidth - 2)}╮`));
50
50
  for (const row of pinnedRows) {
51
51
  lines.push((0, modal_1.renderModalRow)(padding, modalWidth, row));
52
52
  }
@@ -64,7 +64,7 @@ function renderHelpModal(terminalWidth, terminalHeight, scrollOffset = 0) {
64
64
  lines.push((0, modal_1.renderModalRow)(padding, modalWidth, ''));
65
65
  if (footer)
66
66
  lines.push((0, modal_1.renderModalRow)(padding, modalWidth, footer));
67
- lines.push(' '.repeat(padding) + chalk_1.default.gray('╰' + '─'.repeat(modalWidth - 2) + '╯'));
67
+ lines.push(' '.repeat(padding) + chalk_1.default.gray(`╰${'─'.repeat(modalWidth - 2)}╯`));
68
68
  return {
69
69
  lines,
70
70
  maxScrollOffset: maxScroll,
@@ -37,9 +37,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.UIRenderer = void 0;
40
- const PackageList = __importStar(require("./package-list"));
41
- const Confirmation = __importStar(require("./confirmation"));
42
40
  const Modal = __importStar(require("../modal"));
41
+ const Confirmation = __importStar(require("./confirmation"));
42
+ const PackageList = __importStar(require("./package-list"));
43
43
  /**
44
44
  * Main UI renderer class that composes all rendering parts
45
45
  */
@@ -70,8 +70,7 @@ class UIRenderer {
70
70
  }
71
71
  }
72
72
  exports.UIRenderer = UIRenderer;
73
+ __exportStar(require("./confirmation"), exports);
73
74
  // Re-export all functions for direct use if needed
74
75
  __exportStar(require("./package-list"), exports);
75
- __exportStar(require("./confirmation"), exports);
76
- __exportStar(require("../modal"), exports);
77
76
  //# sourceMappingURL=index.js.map
@@ -14,7 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./types/domain"), exports);
18
- __exportStar(require("./types/ui"), exports);
19
- __exportStar(require("./types/streaming"), exports);
20
- //# sourceMappingURL=types.js.map
17
+ __exportStar(require("./interface"), exports);
18
+ __exportStar(require("./rows"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -6,11 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.renderInterface = renderInterface;
7
7
  exports.renderPackagesTable = renderPackagesTable;
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
- const utils_1 = require("../../utils");
9
+ const config_1 = require("../../../../shared/config");
10
+ const keymap_1 = require("../../keymap");
10
11
  const themes_colors_1 = require("../../themes-colors");
11
- const config_1 = require("../../../config");
12
+ const version_format_1 = require("../version-format");
12
13
  const rows_1 = require("./rows");
13
- const keymap_1 = require("../../keymap");
14
14
  function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, _forceFullRender, renderableItems, activeFilterLabel, packageManager, filterMode, filterQuery, totalPackagesBeforeFilter, terminalWidth = 80, loadingProgress, auditProgress, options = {}, notice) {
15
15
  const output = [];
16
16
  if (packageManager) {
@@ -31,17 +31,17 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, _for
31
31
  (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') +
32
32
  (0, themes_colors_1.getThemeColor)('primary')(activeFilterLabel)
33
33
  : headerLine;
34
- const headerPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(fullHeaderLine));
34
+ const headerPadding = Math.max(0, terminalWidth - version_format_1.VersionUtils.getVisualLength(fullHeaderLine));
35
35
  output.push(fullHeaderLine + ' '.repeat(headerPadding));
36
36
  }
37
37
  else {
38
- const headerLine = ' ' + chalk_1.default.bold.blue('🚀 ') + (0, themes_colors_1.coloredInupLogo)();
38
+ const headerLine = ` ${chalk_1.default.bold.blue('🚀 ')}${(0, themes_colors_1.coloredInupLogo)()}`;
39
39
  const fullHeaderLine = activeFilterLabel
40
40
  ? headerLine +
41
41
  (0, themes_colors_1.getThemeColor)('textSecondary')(' - ') +
42
42
  (0, themes_colors_1.getThemeColor)('primary')(activeFilterLabel)
43
43
  : headerLine;
44
- const headerPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(fullHeaderLine));
44
+ const headerPadding = Math.max(0, terminalWidth - version_format_1.VersionUtils.getVisualLength(fullHeaderLine));
45
45
  output.push(fullHeaderLine + ' '.repeat(headerPadding));
46
46
  }
47
47
  output.push('');
@@ -50,7 +50,7 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, _for
50
50
  chalk_1.default.bold.white('Search: ') +
51
51
  (0, themes_colors_1.getThemeColor)('primary')(filterQuery || '') +
52
52
  (0, themes_colors_1.getThemeColor)('border')('█');
53
- const padding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(filterDisplay));
53
+ const padding = Math.max(0, terminalWidth - version_format_1.VersionUtils.getVisualLength(filterDisplay));
54
54
  output.push(filterDisplay + ' '.repeat(padding));
55
55
  }
56
56
  else if (filterQuery) {
@@ -58,14 +58,14 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, _for
58
58
  chalk_1.default.bold.white('Search: ') +
59
59
  (0, themes_colors_1.getThemeColor)('primary')(filterQuery) +
60
60
  (0, themes_colors_1.getThemeColor)('textSecondary')(' (press / to edit)');
61
- const padding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(filterDisplay));
61
+ const padding = Math.max(0, terminalWidth - version_format_1.VersionUtils.getVisualLength(filterDisplay));
62
62
  output.push(filterDisplay + ' '.repeat(padding));
63
63
  }
64
64
  else {
65
65
  const hintLine = (0, keymap_1.getFooterHints)()
66
- .map(({ keyLabel, label }) => chalk_1.default.bold.white(keyLabel + ' ') + (0, themes_colors_1.getThemeColor)('textSecondary')(label))
66
+ .map(({ keyLabel, label }) => chalk_1.default.bold.white(`${keyLabel} `) + (0, themes_colors_1.getThemeColor)('textSecondary')(label))
67
67
  .join(' ');
68
- output.push(' ' + hintLine);
68
+ output.push(` ${hintLine}`);
69
69
  }
70
70
  const totalPackages = states.length;
71
71
  const totalBeforeFilter = totalPackagesBeforeFilter || totalPackages;
@@ -107,8 +107,7 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, _for
107
107
  const matchCount = totalVisualItems > maxVisibleItems
108
108
  ? (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing ${chalk_1.default.white(startItem)}-${chalk_1.default.white(endItem)} of ${chalk_1.default.white(totalPackages)} matches`)
109
109
  : (0, themes_colors_1.getThemeColor)('textSecondary')(`Showing all ${chalk_1.default.white(totalPackages)} matches`);
110
- statusLine =
111
- matchCount + ' ' + chalk_1.default.bold.white('Esc ') + chalk_1.default.gray('Clear filter');
110
+ statusLine = `${matchCount} ${chalk_1.default.bold.white('Esc ')}${chalk_1.default.gray('Clear filter')}`;
112
111
  }
113
112
  else {
114
113
  if (totalVisualItems > maxVisibleItems) {
@@ -130,13 +129,13 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, _for
130
129
  const auditLabel = auditProgress.isRunning
131
130
  ? `Audit ${auditProgress.completed}/${auditProgress.total}`
132
131
  : `Audit ${auditProgress.total}/${auditProgress.total}`;
133
- statusLine += ' ' + (0, themes_colors_1.getThemeColor)('textSecondary')(auditLabel);
132
+ statusLine += ` ${(0, themes_colors_1.getThemeColor)('textSecondary')(auditLabel)}`;
134
133
  }
135
134
  // A one-shot notice (e.g. "nothing selected") replaces the status line for a
136
135
  // single render so the layout height stays constant.
137
136
  const statusContent = notice ? (0, themes_colors_1.getThemeColor)('warning')(notice) : statusLine;
138
- const statusLineFull = ' ' + statusContent;
139
- const statusPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(statusLineFull));
137
+ const statusLineFull = ` ${statusContent}`;
138
+ const statusPadding = Math.max(0, terminalWidth - version_format_1.VersionUtils.getVisualLength(statusLineFull));
140
139
  output.push(statusLineFull + ' '.repeat(statusPadding));
141
140
  output.push('');
142
141
  if (renderableItems && renderableItems.length > 0) {
@@ -166,7 +165,7 @@ function renderInterface(states, currentRow, scrollOffset, maxVisibleItems, _for
166
165
  const loadingLine = ' ' +
167
166
  (0, themes_colors_1.getThemeColor)('textSecondary')(loadingLabel) +
168
167
  (failedLabel ? chalk_1.default.yellow(failedLabel) : '');
169
- const loadingPadding = Math.max(0, terminalWidth - utils_1.VersionUtils.getVisualLength(loadingLine));
168
+ const loadingPadding = Math.max(0, terminalWidth - version_format_1.VersionUtils.getVisualLength(loadingLine));
170
169
  output.push(loadingLine + ' '.repeat(loadingPadding));
171
170
  }
172
171
  return output.map((line) => (0, rows_1.padLineToWidth)(line, terminalWidth));