inup 1.5.5 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -10
- package/dist/cli.js +53 -31
- package/dist/config/constants.js +4 -6
- package/dist/config/package-meta.js +10 -0
- package/dist/config/project-config.js +11 -1
- package/dist/core/package-detector.js +37 -5
- package/dist/core/upgrade-runner.js +8 -10
- package/dist/core/upgrader.js +15 -11
- package/dist/features/changelog/clients/github-client.js +5 -6
- package/dist/features/changelog/services/changelog-service.js +2 -4
- package/dist/features/changelog/services/package-metadata-service.js +7 -23
- package/dist/features/changelog/services/release-notes-service.js +4 -29
- package/dist/features/debug/services/performance-tracker.js +6 -8
- package/dist/features/headless/headless-runner.js +53 -0
- package/dist/features/headless/index.js +27 -0
- package/dist/features/headless/report.js +64 -0
- package/dist/features/headless/types.js +6 -0
- package/dist/features/headless/vulnerability-audit.js +106 -0
- package/dist/index.js +1 -2
- package/dist/interactive-ui.js +15 -606
- package/dist/services/background-audit.js +3 -5
- package/dist/services/cache-manager.js +5 -7
- package/dist/services/http/inflight.js +22 -0
- package/dist/services/http/retry.js +30 -0
- package/dist/services/index.js +0 -1
- package/dist/services/npm-registry.js +20 -97
- package/dist/services/package-manager-detector.js +6 -3
- package/dist/services/persistent-cache.js +8 -13
- package/dist/types/domain.js +3 -0
- package/dist/types/streaming.js +3 -0
- package/dist/types/ui.js +3 -0
- package/dist/types.js +17 -0
- package/dist/ui/controllers/package-info-modal-controller.js +22 -31
- package/dist/ui/controllers/vulnerability-audit-controller.js +17 -8
- package/dist/ui/index.js +3 -0
- package/dist/ui/input-handler.js +58 -75
- package/dist/ui/keymap.js +208 -0
- package/dist/ui/modal/package-info-sections/release-notes.js +161 -0
- package/dist/ui/modal/package-info-sections/sections.js +174 -0
- package/dist/ui/modal/package-info-sections/text.js +75 -0
- package/dist/ui/modal/package-info-sections.js +15 -369
- package/dist/ui/modal/package-info.js +1 -1
- package/dist/ui/presenters/health.js +24 -0
- package/dist/ui/renderer/help-modal.js +75 -0
- package/dist/ui/renderer/index.js +2 -5
- package/dist/ui/renderer/package-list/interface.js +184 -0
- package/dist/ui/renderer/package-list/rows.js +177 -0
- package/dist/ui/renderer/package-list.js +15 -455
- package/dist/ui/session/action-dispatcher.js +233 -0
- package/dist/ui/session/index.js +13 -0
- package/dist/ui/session/interactive-session.js +280 -0
- package/dist/ui/session/selection-state-builder.js +149 -0
- package/dist/ui/state/filter-manager.js +17 -6
- package/dist/ui/state/modal-manager.js +35 -0
- package/dist/ui/state/navigation-manager.js +33 -4
- package/dist/ui/state/state-manager.js +68 -3
- package/dist/ui/state/theme-manager.js +1 -0
- package/dist/ui/themes-colors.js +16 -4
- package/dist/ui/themes.js +11 -19
- package/dist/ui/utils/cursor.js +12 -7
- package/dist/ui/utils/index.js +6 -1
- package/dist/ui/utils/text.js +3 -2
- package/dist/ui/utils/version.js +60 -86
- package/dist/utils/color.js +38 -0
- package/dist/utils/config.js +13 -1
- package/dist/utils/engines.js +63 -0
- package/dist/utils/filesystem/io.js +103 -0
- package/dist/utils/filesystem/paths.js +19 -0
- package/dist/utils/filesystem/scan.js +280 -0
- package/dist/utils/filesystem.js +17 -335
- package/dist/utils/index.js +3 -0
- package/dist/utils/manifest.js +35 -0
- package/dist/utils/version.js +38 -1
- package/package.json +8 -7
- package/dist/services/jsdelivr-registry.js +0 -395
package/dist/ui/input-handler.js
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConfirmationInputHandler = exports.InputHandler = void 0;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
|
+
const keymap_1 = require("./keymap");
|
|
5
6
|
class InputHandler {
|
|
7
|
+
stateManager;
|
|
8
|
+
onAction;
|
|
9
|
+
onConfirm;
|
|
10
|
+
// Must synchronously release terminal state (alt screen, raw mode, cursor) —
|
|
11
|
+
// it runs immediately before process.exit(0) on Ctrl+C, with no chance to await.
|
|
12
|
+
onCancel;
|
|
6
13
|
constructor(stateManager, onAction, onConfirm, onCancel) {
|
|
7
14
|
this.stateManager = stateManager;
|
|
8
15
|
this.onAction = onAction;
|
|
@@ -70,6 +77,24 @@ class InputHandler {
|
|
|
70
77
|
}
|
|
71
78
|
return;
|
|
72
79
|
}
|
|
80
|
+
// Handle help overlay input (close or scroll)
|
|
81
|
+
if (uiState.showHelpModal) {
|
|
82
|
+
if (str === '?' || (key && key.name === 'escape')) {
|
|
83
|
+
this.onAction({ type: 'toggle_help_modal' });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (key) {
|
|
87
|
+
if (key.name === 'up') {
|
|
88
|
+
this.onAction({ type: 'scroll_help_modal_up' });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (key.name === 'down') {
|
|
92
|
+
this.onAction({ type: 'scroll_help_modal_down' });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return; // consume all other keys while help is open
|
|
97
|
+
}
|
|
73
98
|
// Handle info modal input (scroll and close)
|
|
74
99
|
if (uiState.showInfoModal) {
|
|
75
100
|
if (key) {
|
|
@@ -111,6 +136,11 @@ class InputHandler {
|
|
|
111
136
|
this.onAction({ type: 'toggle_debug_modal' });
|
|
112
137
|
return;
|
|
113
138
|
}
|
|
139
|
+
// '?' opens the help overlay (outside of filter mode)
|
|
140
|
+
if (str === '?' && !uiState.filterMode) {
|
|
141
|
+
this.onAction({ type: 'toggle_help_modal' });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
114
144
|
// Check for '/' character to handle filter mode (only when not in modal)
|
|
115
145
|
if (str === '/') {
|
|
116
146
|
if (uiState.filterMode) {
|
|
@@ -169,84 +199,36 @@ class InputHandler {
|
|
|
169
199
|
}
|
|
170
200
|
return;
|
|
171
201
|
}
|
|
172
|
-
// Normal mode (not in filter mode)
|
|
173
|
-
|
|
202
|
+
// Normal mode (not in filter mode). Keys resolve through the keymap so that
|
|
203
|
+
// dispatch, the help overlay, the footer, and the README share one source.
|
|
204
|
+
if (!key || !key.name) {
|
|
174
205
|
return;
|
|
175
206
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.onAction({ type: '
|
|
182
|
-
break;
|
|
183
|
-
case 'left':
|
|
184
|
-
this.onAction({ type: 'select_left' });
|
|
185
|
-
break;
|
|
186
|
-
case 'right':
|
|
187
|
-
this.onAction({ type: 'select_right' });
|
|
188
|
-
break;
|
|
189
|
-
case 'return':
|
|
190
|
-
// Check if any packages are selected
|
|
191
|
-
const selectedCount = states.filter((s) => s.loadState === 'ready' && s.selectedOption !== 'none').length;
|
|
192
|
-
if (selectedCount === 0) {
|
|
193
|
-
// Do nothing if no packages selected
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
this.cleanup();
|
|
197
|
-
this.onConfirm(states);
|
|
207
|
+
// Enter confirms the selection (guarding against an empty selection). It runs
|
|
208
|
+
// the cleanup/confirm path directly rather than going through onAction.
|
|
209
|
+
if (key.name === 'return') {
|
|
210
|
+
const selectedCount = states.filter((s) => s.loadState === 'ready' && s.selectedOption !== 'none').length;
|
|
211
|
+
if (selectedCount === 0) {
|
|
212
|
+
this.onAction({ type: 'notify_empty_selection' });
|
|
198
213
|
return;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
case 'p':
|
|
218
|
-
case 'P':
|
|
219
|
-
if (!uiState.showThemeModal && !uiState.filterMode) {
|
|
220
|
-
this.onAction({ type: 'toggle_dep_type_filter', depType: 'peerDependencies' });
|
|
221
|
-
}
|
|
222
|
-
break;
|
|
223
|
-
case 'o':
|
|
224
|
-
case 'O':
|
|
225
|
-
if (!uiState.showThemeModal && !uiState.filterMode) {
|
|
226
|
-
this.onAction({ type: 'toggle_dep_type_filter', depType: 'optionalDependencies' });
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
case 'i':
|
|
230
|
-
case 'I':
|
|
231
|
-
this.onAction({ type: 'toggle_info_modal' });
|
|
232
|
-
break;
|
|
233
|
-
case 's':
|
|
234
|
-
case 'S':
|
|
235
|
-
if (!uiState.showThemeModal) {
|
|
236
|
-
this.onAction({ type: 'trigger_audit_scan' });
|
|
237
|
-
}
|
|
238
|
-
break;
|
|
239
|
-
case 't':
|
|
240
|
-
case 'T':
|
|
241
|
-
this.onAction({ type: 'toggle_theme_modal' });
|
|
242
|
-
break;
|
|
243
|
-
case 'escape':
|
|
244
|
-
if (uiState.filterQuery) {
|
|
245
|
-
// Clear filter if one is applied
|
|
246
|
-
this.onAction({ type: 'exit_filter_mode', clearQuery: true });
|
|
247
|
-
}
|
|
248
|
-
// Otherwise do nothing - Escape no longer exits the CLI
|
|
249
|
-
break;
|
|
214
|
+
}
|
|
215
|
+
this.cleanup();
|
|
216
|
+
this.onConfirm(states);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Escape clears an active search filter; otherwise it is a no-op (it no
|
|
220
|
+
// longer exits the CLI).
|
|
221
|
+
if (key.name === 'escape') {
|
|
222
|
+
if (uiState.filterQuery) {
|
|
223
|
+
this.onAction({ type: 'exit_filter_mode', clearQuery: true });
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Shifted letters become uppercase tokens so the vim pair g/G stays distinct.
|
|
228
|
+
const token = key.shift && /^[a-z]$/.test(key.name) ? key.name.toUpperCase() : key.name;
|
|
229
|
+
const binding = (0, keymap_1.findBinding)(token);
|
|
230
|
+
if (binding?.action) {
|
|
231
|
+
this.onAction(binding.action);
|
|
250
232
|
}
|
|
251
233
|
}
|
|
252
234
|
handleResize(height) {
|
|
@@ -258,6 +240,7 @@ class InputHandler {
|
|
|
258
240
|
}
|
|
259
241
|
exports.InputHandler = InputHandler;
|
|
260
242
|
class ConfirmationInputHandler {
|
|
243
|
+
onConfirm;
|
|
261
244
|
constructor(onConfirm) {
|
|
262
245
|
this.onConfirm = onConfirm;
|
|
263
246
|
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KEY_BINDINGS = exports.KEY_GROUPS = void 0;
|
|
4
|
+
exports.findBinding = findBinding;
|
|
5
|
+
exports.getFooterHints = getFooterHints;
|
|
6
|
+
exports.getHelpGroups = getHelpGroups;
|
|
7
|
+
exports.renderReadmeKeyTable = renderReadmeKeyTable;
|
|
8
|
+
/** Group display order for the help overlay and README. */
|
|
9
|
+
exports.KEY_GROUPS = ['Navigation', 'Selection', 'Filtering', 'View'];
|
|
10
|
+
exports.KEY_BINDINGS = [
|
|
11
|
+
// Navigation
|
|
12
|
+
{
|
|
13
|
+
tokens: ['up', 'k'],
|
|
14
|
+
action: { type: 'navigate_up' },
|
|
15
|
+
displayKeys: '↑ / k',
|
|
16
|
+
help: 'Move up',
|
|
17
|
+
group: 'Navigation',
|
|
18
|
+
footer: { keyLabel: '↑/↓', label: 'Move' },
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
tokens: ['down', 'j'],
|
|
22
|
+
action: { type: 'navigate_down' },
|
|
23
|
+
displayKeys: '↓ / j',
|
|
24
|
+
help: 'Move down',
|
|
25
|
+
group: 'Navigation',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
tokens: ['g'],
|
|
29
|
+
action: { type: 'navigate_top' },
|
|
30
|
+
displayKeys: 'g',
|
|
31
|
+
help: 'Jump to the first package',
|
|
32
|
+
group: 'Navigation',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
tokens: ['G'],
|
|
36
|
+
action: { type: 'navigate_bottom' },
|
|
37
|
+
displayKeys: 'G',
|
|
38
|
+
help: 'Jump to the last package',
|
|
39
|
+
group: 'Navigation',
|
|
40
|
+
},
|
|
41
|
+
// Selection
|
|
42
|
+
{
|
|
43
|
+
tokens: ['left'],
|
|
44
|
+
action: { type: 'select_left' },
|
|
45
|
+
displayKeys: '←',
|
|
46
|
+
help: 'Cycle selection left (none → range → latest)',
|
|
47
|
+
group: 'Selection',
|
|
48
|
+
footer: { keyLabel: '←/→', label: 'Select' },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
tokens: ['right'],
|
|
52
|
+
action: { type: 'select_right' },
|
|
53
|
+
displayKeys: '→',
|
|
54
|
+
help: 'Cycle selection right (none → range → latest)',
|
|
55
|
+
group: 'Selection',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
tokens: ['space'],
|
|
59
|
+
action: { type: 'toggle_selection' },
|
|
60
|
+
displayKeys: 'Space',
|
|
61
|
+
help: 'Toggle the current package on/off',
|
|
62
|
+
group: 'Selection',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
tokens: ['m'],
|
|
66
|
+
action: { type: 'bulk_select_minor' },
|
|
67
|
+
displayKeys: 'm',
|
|
68
|
+
help: 'Select all minor/patch updates',
|
|
69
|
+
group: 'Selection',
|
|
70
|
+
footer: { keyLabel: 'M', label: 'Minor' },
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
tokens: ['l'],
|
|
74
|
+
action: { type: 'bulk_select_latest' },
|
|
75
|
+
displayKeys: 'l',
|
|
76
|
+
help: 'Select all latest updates (including major)',
|
|
77
|
+
group: 'Selection',
|
|
78
|
+
footer: { keyLabel: 'L', label: 'All' },
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
tokens: ['u'],
|
|
82
|
+
action: { type: 'bulk_unselect_all' },
|
|
83
|
+
displayKeys: 'u',
|
|
84
|
+
help: 'Unselect all packages',
|
|
85
|
+
group: 'Selection',
|
|
86
|
+
footer: { keyLabel: 'U', label: 'None' },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
displayKeys: 'Enter',
|
|
90
|
+
help: 'Confirm selection and upgrade',
|
|
91
|
+
group: 'Selection',
|
|
92
|
+
},
|
|
93
|
+
// Filtering
|
|
94
|
+
{
|
|
95
|
+
displayKeys: '/',
|
|
96
|
+
help: 'Search packages by name',
|
|
97
|
+
group: 'Filtering',
|
|
98
|
+
footer: { keyLabel: '/', label: 'Search' },
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
tokens: ['d'],
|
|
102
|
+
action: { type: 'toggle_dep_type_filter', depType: 'devDependencies' },
|
|
103
|
+
displayKeys: 'd',
|
|
104
|
+
help: 'Toggle devDependencies',
|
|
105
|
+
group: 'Filtering',
|
|
106
|
+
footer: { keyLabel: 'D/P/O', label: 'Filter' },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
tokens: ['p'],
|
|
110
|
+
action: { type: 'toggle_dep_type_filter', depType: 'peerDependencies' },
|
|
111
|
+
displayKeys: 'p',
|
|
112
|
+
help: 'Toggle peerDependencies',
|
|
113
|
+
group: 'Filtering',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
tokens: ['o'],
|
|
117
|
+
action: { type: 'toggle_dep_type_filter', depType: 'optionalDependencies' },
|
|
118
|
+
displayKeys: 'o',
|
|
119
|
+
help: 'Toggle optionalDependencies',
|
|
120
|
+
group: 'Filtering',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
tokens: ['s'],
|
|
124
|
+
action: { type: 'trigger_audit_scan' },
|
|
125
|
+
displayKeys: 's',
|
|
126
|
+
help: 'Run the vulnerability audit',
|
|
127
|
+
group: 'Filtering',
|
|
128
|
+
footer: { keyLabel: 'S', label: 'Audit' },
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
tokens: ['v'],
|
|
132
|
+
action: { type: 'toggle_vulnerable_filter' },
|
|
133
|
+
displayKeys: 'v',
|
|
134
|
+
help: 'Show only vulnerable packages',
|
|
135
|
+
group: 'Filtering',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
displayKeys: 'Esc',
|
|
139
|
+
help: 'Clear the active search filter',
|
|
140
|
+
group: 'Filtering',
|
|
141
|
+
},
|
|
142
|
+
// View
|
|
143
|
+
{
|
|
144
|
+
tokens: ['i'],
|
|
145
|
+
action: { type: 'toggle_info_modal' },
|
|
146
|
+
displayKeys: 'i',
|
|
147
|
+
help: 'View package details and changelog',
|
|
148
|
+
group: 'View',
|
|
149
|
+
footer: { keyLabel: 'I', label: 'Info' },
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
tokens: ['t'],
|
|
153
|
+
action: { type: 'toggle_theme_modal' },
|
|
154
|
+
displayKeys: 't',
|
|
155
|
+
help: 'Change the color theme',
|
|
156
|
+
group: 'View',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
displayKeys: '?',
|
|
160
|
+
help: 'Show this help',
|
|
161
|
+
group: 'View',
|
|
162
|
+
footer: { keyLabel: '?', label: 'Help' },
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
displayKeys: '!',
|
|
166
|
+
help: 'Show the performance/debug panel',
|
|
167
|
+
group: 'View',
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
/**
|
|
171
|
+
* Resolve a pressed key token to its binding. Tries an exact match first so the
|
|
172
|
+
* case-sensitive vim pair `g`/`G` is honored, then falls back to the lowercase
|
|
173
|
+
* letter so shifted letters (e.g. `M`) still trigger their lowercase binding.
|
|
174
|
+
*/
|
|
175
|
+
function findBinding(token) {
|
|
176
|
+
const exact = exports.KEY_BINDINGS.find((binding) => binding.tokens?.includes(token));
|
|
177
|
+
if (exact)
|
|
178
|
+
return exact;
|
|
179
|
+
if (/^[A-Z]$/.test(token)) {
|
|
180
|
+
const lower = token.toLowerCase();
|
|
181
|
+
return exports.KEY_BINDINGS.find((binding) => binding.tokens?.includes(lower));
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
// Computed once at module load — KEY_BINDINGS is static so these never change.
|
|
186
|
+
const _footerHints = exports.KEY_BINDINGS.flatMap((binding) => binding.footer ? [binding.footer] : []);
|
|
187
|
+
const _helpGroups = exports.KEY_GROUPS.map((group) => ({
|
|
188
|
+
group,
|
|
189
|
+
bindings: exports.KEY_BINDINGS.filter((b) => b.group === group),
|
|
190
|
+
})).filter((entry) => entry.bindings.length > 0);
|
|
191
|
+
/** Curated short hints for the compact footer line, in display order. */
|
|
192
|
+
function getFooterHints() {
|
|
193
|
+
return _footerHints;
|
|
194
|
+
}
|
|
195
|
+
/** Bindings grouped for the `?` help overlay, preserving {@link KEY_GROUPS} order. */
|
|
196
|
+
function getHelpGroups() {
|
|
197
|
+
return _helpGroups;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Render the README "Keyboard Shortcuts" markdown table from the keymap. This is
|
|
201
|
+
* the body placed between the `<!-- KEYS:START -->`/`<!-- KEYS:END -->` markers;
|
|
202
|
+
* a test fails if the README drifts from it (see keymap-readme.test.ts).
|
|
203
|
+
*/
|
|
204
|
+
function renderReadmeKeyTable() {
|
|
205
|
+
const rows = exports.KEY_BINDINGS.map((binding) => `| \`${binding.displayKeys}\` | ${binding.help} |`);
|
|
206
|
+
return ['| Key | Action |', '|-----|--------|', ...rows].join('\n');
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=keymap.js.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildReleaseNotesSections = buildReleaseNotesSections;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const themes_colors_1 = require("../../themes-colors");
|
|
9
|
+
const text_1 = require("./text");
|
|
10
|
+
function formatReleaseNotesMarkdown(markdown, width, repositoryUrl) {
|
|
11
|
+
const lines = [];
|
|
12
|
+
const rawLines = markdown.split('\n');
|
|
13
|
+
let prevBlank = false;
|
|
14
|
+
for (const rawLine of rawLines) {
|
|
15
|
+
const trimmed = rawLine.trim();
|
|
16
|
+
if (trimmed === '') {
|
|
17
|
+
if (!prevBlank && lines.length > 0) {
|
|
18
|
+
lines.push('');
|
|
19
|
+
prevBlank = true;
|
|
20
|
+
}
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
prevBlank = false;
|
|
24
|
+
if (/^```/.test(trimmed) || /^---+$/.test(trimmed)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const cleaned = (0, text_1.sanitizeMarkdownText)(trimmed);
|
|
28
|
+
if (!cleaned) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const quoteMatch = trimmed.match(/^>\s*(.+)/);
|
|
32
|
+
if (quoteMatch) {
|
|
33
|
+
const quoteBody = (0, text_1.linkifyMarkdownText)((0, text_1.sanitizeMarkdownText)(quoteMatch[1]), repositoryUrl);
|
|
34
|
+
const admonitionMatch = quoteBody.match(/^\[!([A-Z]+)\]$/i);
|
|
35
|
+
if (admonitionMatch) {
|
|
36
|
+
const label = `${admonitionMatch[1][0]}${admonitionMatch[1].slice(1).toLowerCase()}`;
|
|
37
|
+
if (lines.length > 0 && lines[lines.length - 1] !== '') {
|
|
38
|
+
lines.push('');
|
|
39
|
+
}
|
|
40
|
+
lines.push(chalk_1.default.blue.bold(` ${label}`));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
(0, text_1.pushWrappedLines)(lines, quoteBody, width, ' ', ' ', chalk_1.default.gray);
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const headerMatch = cleaned.match(/^(#{1,6})\s+(.+)/);
|
|
48
|
+
if (headerMatch) {
|
|
49
|
+
const title = (0, text_1.sanitizeMarkdownText)(headerMatch[2]);
|
|
50
|
+
const lower = title.toLowerCase();
|
|
51
|
+
let style = chalk_1.default.white.bold;
|
|
52
|
+
if (lower.includes('breaking')) {
|
|
53
|
+
style = chalk_1.default.red.bold;
|
|
54
|
+
}
|
|
55
|
+
else if (lower.includes('feature') ||
|
|
56
|
+
lower.includes('added') ||
|
|
57
|
+
lower.includes('improvement')) {
|
|
58
|
+
style = chalk_1.default.green.bold;
|
|
59
|
+
}
|
|
60
|
+
else if (lower.includes('fix') || lower.includes('bug')) {
|
|
61
|
+
style = chalk_1.default.yellow.bold;
|
|
62
|
+
}
|
|
63
|
+
else if (lower.includes('deprecat')) {
|
|
64
|
+
style = chalk_1.default.magenta.bold;
|
|
65
|
+
}
|
|
66
|
+
if (lines.length > 0 && lines[lines.length - 1] !== '') {
|
|
67
|
+
lines.push('');
|
|
68
|
+
}
|
|
69
|
+
lines.push(style(` ${title}`));
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const bulletMatch = cleaned.match(/^(\s*)[*-]\s+(.+)/);
|
|
73
|
+
if (bulletMatch) {
|
|
74
|
+
const indentLevel = Math.min(2, Math.floor(bulletMatch[1].length / 2));
|
|
75
|
+
const prefix = ` ${' '.repeat(indentLevel)}${chalk_1.default.gray('•')} `;
|
|
76
|
+
const restPrefix = ` ${' '.repeat(indentLevel + 1)}`;
|
|
77
|
+
const style = /breaking/i.test(bulletMatch[2]) ? chalk_1.default.red : undefined;
|
|
78
|
+
(0, text_1.pushWrappedLines)(lines, (0, text_1.linkifyMarkdownText)((0, text_1.sanitizeMarkdownText)(bulletMatch[2]), repositoryUrl), width, prefix, restPrefix, style);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const orderedMatch = cleaned.match(/^(\s*)(\d+)\.\s+(.+)/);
|
|
82
|
+
if (orderedMatch) {
|
|
83
|
+
const indentLevel = Math.min(2, Math.floor(orderedMatch[1].length / 2));
|
|
84
|
+
const marker = `${orderedMatch[2]}.`;
|
|
85
|
+
const prefix = ` ${' '.repeat(indentLevel)}${marker} `;
|
|
86
|
+
const restPrefix = ` ${' '.repeat(indentLevel)}${' '.repeat(marker.length + 1)}`;
|
|
87
|
+
(0, text_1.pushWrappedLines)(lines, (0, text_1.linkifyMarkdownText)((0, text_1.sanitizeMarkdownText)(orderedMatch[3]), repositoryUrl), width, prefix, restPrefix);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const style = (0, text_1.isLowSignalTrailerLine)(cleaned) ? chalk_1.default.gray : undefined;
|
|
91
|
+
(0, text_1.pushWrappedLines)(lines, (0, text_1.linkifyMarkdownText)(cleaned, repositoryUrl), width, ' ', ' ', style);
|
|
92
|
+
}
|
|
93
|
+
while (lines.length > 0 && lines[lines.length - 1] === '') {
|
|
94
|
+
lines.pop();
|
|
95
|
+
}
|
|
96
|
+
return lines;
|
|
97
|
+
}
|
|
98
|
+
function buildReleaseNotesSections(state, modalWidth) {
|
|
99
|
+
const sections = [];
|
|
100
|
+
if (!state.releaseNotesVersions || state.releaseNotesVersions.length === 0) {
|
|
101
|
+
return sections;
|
|
102
|
+
}
|
|
103
|
+
const loaded = state.releaseNotesLoaded;
|
|
104
|
+
const viewIndex = state.releaseNotesViewIndex ?? 0;
|
|
105
|
+
const totalVersions = state.releaseNotesVersions.length;
|
|
106
|
+
const currentVersion = state.releaseNotesVersions[viewIndex];
|
|
107
|
+
if (!currentVersion)
|
|
108
|
+
return sections;
|
|
109
|
+
if (state.releaseNotesLoadingVersion === currentVersion) {
|
|
110
|
+
sections.push({
|
|
111
|
+
key: 'release-loading',
|
|
112
|
+
rows: [chalk_1.default.gray(`Loading release notes for v${currentVersion}...`)],
|
|
113
|
+
behavior: 'status',
|
|
114
|
+
});
|
|
115
|
+
return sections;
|
|
116
|
+
}
|
|
117
|
+
if (!loaded || !loaded.has(currentVersion)) {
|
|
118
|
+
sections.push({
|
|
119
|
+
key: 'release-pending',
|
|
120
|
+
rows: [chalk_1.default.gray(`Press ←/→ to load release notes for v${currentVersion}`)],
|
|
121
|
+
behavior: 'status',
|
|
122
|
+
});
|
|
123
|
+
return sections;
|
|
124
|
+
}
|
|
125
|
+
const content = loaded.get(currentVersion);
|
|
126
|
+
if (!content) {
|
|
127
|
+
sections.push({
|
|
128
|
+
key: 'release-none',
|
|
129
|
+
rows: [chalk_1.default.gray.italic(`No release notes found for v${currentVersion}`)],
|
|
130
|
+
behavior: 'status',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const versionHeader = (0, themes_colors_1.getThemeColor)('primary')(`Version ${currentVersion}`);
|
|
135
|
+
const navHint = totalVersions > 1 ? chalk_1.default.gray(` (${viewIndex + 1}/${totalVersions})`) : '';
|
|
136
|
+
const rows = [chalk_1.default.bold(versionHeader) + navHint];
|
|
137
|
+
const formatted = formatReleaseNotesMarkdown(content, modalWidth - 4, state.repository);
|
|
138
|
+
rows.push(...formatted);
|
|
139
|
+
sections.push({
|
|
140
|
+
key: `release-${currentVersion}`,
|
|
141
|
+
rows,
|
|
142
|
+
behavior: 'body',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const canGoNewer = viewIndex > 0;
|
|
146
|
+
const canGoOlder = viewIndex < totalVersions - 1;
|
|
147
|
+
if (canGoNewer || canGoOlder) {
|
|
148
|
+
const hints = [];
|
|
149
|
+
if (canGoNewer)
|
|
150
|
+
hints.push('← newer');
|
|
151
|
+
if (canGoOlder)
|
|
152
|
+
hints.push('→ older');
|
|
153
|
+
sections.push({
|
|
154
|
+
key: 'release-nav',
|
|
155
|
+
rows: [chalk_1.default.gray(hints.join(' · '))],
|
|
156
|
+
behavior: 'status',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return sections;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=release-notes.js.map
|