depfresh 0.9.2
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/LICENSE +21 -0
- package/README.md +279 -0
- package/dist/chunks/config.mjs +126 -0
- package/dist/chunks/index.mjs +21 -0
- package/dist/chunks/index2.mjs +624 -0
- package/dist/chunks/index3.mjs +145 -0
- package/dist/chunks/interactive.mjs +85 -0
- package/dist/chunks/normalize-args.mjs +59 -0
- package/dist/cli.d.mts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.mjs +202 -0
- package/dist/index.d.mts +204 -0
- package/dist/index.d.ts +204 -0
- package/dist/index.mjs +21 -0
- package/dist/shared/depfresh.B1o7OHO_.mjs +63 -0
- package/dist/shared/depfresh.ClIHCCxD.mjs +1938 -0
- package/package.json +86 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
import * as readline from 'node:readline';
|
|
2
|
+
import * as semver from 'semver';
|
|
3
|
+
import { g as getVersionPrefix, f as applyVersionPrefix, h as getDiff, t as timeDifference, s as stripAnsi, i as truncate, j as padEnd, b as colorizeVersionDiff, a as arrow, e as colorDiff } from '../shared/depfresh.ClIHCCxD.mjs';
|
|
4
|
+
import c from 'ansis';
|
|
5
|
+
import '../shared/depfresh.B1o7OHO_.mjs';
|
|
6
|
+
import 'node:fs';
|
|
7
|
+
import 'node:os';
|
|
8
|
+
import 'find-up-simple';
|
|
9
|
+
import 'ini';
|
|
10
|
+
import 'pathe';
|
|
11
|
+
import 'node:child_process';
|
|
12
|
+
import 'detect-indent';
|
|
13
|
+
import 'pnpm-workspace-yaml';
|
|
14
|
+
import 'yaml';
|
|
15
|
+
import 'p-limit';
|
|
16
|
+
import 'better-sqlite3';
|
|
17
|
+
import 'tinyglobby';
|
|
18
|
+
|
|
19
|
+
const MAX_VERSIONS = 20;
|
|
20
|
+
function prepareDetailVersions(dep, explain) {
|
|
21
|
+
const { pkgData, currentVersion } = dep;
|
|
22
|
+
const current = semver.coerce(currentVersion);
|
|
23
|
+
if (!current) return [];
|
|
24
|
+
const newer = pkgData.versions.filter((v) => {
|
|
25
|
+
const parsed = semver.valid(v);
|
|
26
|
+
return parsed && semver.gt(v, current);
|
|
27
|
+
}).sort((a, b) => semver.gt(a, b) ? -1 : 1).slice(0, MAX_VERSIONS);
|
|
28
|
+
const tagsByVersion = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const [tag, ver] of Object.entries(pkgData.distTags)) {
|
|
30
|
+
tagsByVersion.set(ver, tag);
|
|
31
|
+
}
|
|
32
|
+
return newer.map((version) => {
|
|
33
|
+
const diff = getDiff(currentVersion, version);
|
|
34
|
+
const publishedAt = pkgData.time?.[version];
|
|
35
|
+
const age = timeDifference(publishedAt);
|
|
36
|
+
const distTag = tagsByVersion.get(version);
|
|
37
|
+
const deprecated = pkgData.deprecated?.[version];
|
|
38
|
+
const nodeEngines = pkgData.engines?.[version];
|
|
39
|
+
const provenance = pkgData.provenance?.[version];
|
|
40
|
+
const result = { version, diff };
|
|
41
|
+
if (age) result.age = age;
|
|
42
|
+
if (distTag) result.distTag = distTag;
|
|
43
|
+
if (deprecated) result.deprecated = deprecated;
|
|
44
|
+
if (nodeEngines) result.nodeEngines = nodeEngines;
|
|
45
|
+
if (provenance) result.provenance = provenance;
|
|
46
|
+
if (explain) {
|
|
47
|
+
result.explain = getExplanation(diff, deprecated, provenance === "none");
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function getExplanation(diff, deprecated, provenanceDowngrade, nodeIncompat) {
|
|
53
|
+
const parts = [];
|
|
54
|
+
switch (diff) {
|
|
55
|
+
case "major":
|
|
56
|
+
parts.push("Breaking change. Check migration guide.");
|
|
57
|
+
break;
|
|
58
|
+
case "minor":
|
|
59
|
+
parts.push("New features. Backwards compatible.");
|
|
60
|
+
break;
|
|
61
|
+
case "patch":
|
|
62
|
+
parts.push("Bug fixes only. Safe to update.");
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
if (deprecated) parts.push("Deprecated.");
|
|
66
|
+
if (provenanceDowngrade) parts.push("Provenance downgrade.");
|
|
67
|
+
return parts.join(" ");
|
|
68
|
+
}
|
|
69
|
+
function applyVersionSelection(dep, selectedVersion) {
|
|
70
|
+
const prefix = getVersionPrefix(dep.currentVersion);
|
|
71
|
+
dep.targetVersion = applyVersionPrefix(selectedVersion, prefix);
|
|
72
|
+
dep.diff = getDiff(dep.currentVersion, selectedVersion);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function safeTermSize(value, fallback) {
|
|
76
|
+
return typeof value === "number" && value > 0 ? value : fallback;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const CHROME_LINES = 6;
|
|
80
|
+
function getViewportHeight(termRows, chrome) {
|
|
81
|
+
return Math.max(1, termRows - (chrome ?? CHROME_LINES));
|
|
82
|
+
}
|
|
83
|
+
function calculateScrollOffset(cursor, viewportHeight, totalItems, currentOffset) {
|
|
84
|
+
const maxOffset = Math.max(0, totalItems - viewportHeight);
|
|
85
|
+
let offset = currentOffset;
|
|
86
|
+
if (cursor < offset) {
|
|
87
|
+
offset = cursor;
|
|
88
|
+
}
|
|
89
|
+
if (cursor >= offset + viewportHeight) {
|
|
90
|
+
offset = cursor - viewportHeight + 1;
|
|
91
|
+
}
|
|
92
|
+
return Math.min(offset, maxOffset);
|
|
93
|
+
}
|
|
94
|
+
function getVisibleRange(scrollOffset, viewportHeight, totalItems) {
|
|
95
|
+
const start = scrollOffset;
|
|
96
|
+
const end = Math.min(scrollOffset + viewportHeight, totalItems);
|
|
97
|
+
return { start, end };
|
|
98
|
+
}
|
|
99
|
+
function hasOverflowAbove(scrollOffset) {
|
|
100
|
+
return scrollOffset > 0;
|
|
101
|
+
}
|
|
102
|
+
function hasOverflowBelow(scrollOffset, viewportHeight, totalItems) {
|
|
103
|
+
return scrollOffset + viewportHeight < totalItems;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function updateListScroll(state, cursor, termRows) {
|
|
107
|
+
const viewport = getViewportHeight(termRows);
|
|
108
|
+
return calculateScrollOffset(cursor, viewport, state.items.length, state.scrollOffset);
|
|
109
|
+
}
|
|
110
|
+
function updateDetailScroll(state, detailCursor, termRows, currentOffset) {
|
|
111
|
+
const viewport = getViewportHeight(termRows);
|
|
112
|
+
return calculateScrollOffset(detailCursor, viewport, state.detailVersions.length, currentOffset);
|
|
113
|
+
}
|
|
114
|
+
function resize(state, rows, cols) {
|
|
115
|
+
const termRows = safeTermSize(rows, state.termRows);
|
|
116
|
+
const termCols = safeTermSize(cols, state.termCols);
|
|
117
|
+
const resized = {
|
|
118
|
+
...state,
|
|
119
|
+
termRows,
|
|
120
|
+
termCols
|
|
121
|
+
};
|
|
122
|
+
if (resized.view === "detail") {
|
|
123
|
+
return {
|
|
124
|
+
...resized,
|
|
125
|
+
detailScrollOffset: updateDetailScroll(
|
|
126
|
+
resized,
|
|
127
|
+
resized.detailCursor,
|
|
128
|
+
termRows,
|
|
129
|
+
resized.detailScrollOffset
|
|
130
|
+
)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
...resized,
|
|
135
|
+
scrollOffset: updateListScroll(resized, resized.cursor, termRows)
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function enterDetail(state) {
|
|
140
|
+
if (state.view !== "list") return state;
|
|
141
|
+
const item = state.items[state.cursor];
|
|
142
|
+
if (item?.type !== "dep" || !item.dep) return state;
|
|
143
|
+
const detailVersions = prepareDetailVersions(item.dep, state.explain);
|
|
144
|
+
if (detailVersions.length === 0) return state;
|
|
145
|
+
return {
|
|
146
|
+
...state,
|
|
147
|
+
view: "detail",
|
|
148
|
+
detailDep: item.dep,
|
|
149
|
+
detailVersions,
|
|
150
|
+
detailCursor: 0,
|
|
151
|
+
detailScrollOffset: calculateScrollOffset(
|
|
152
|
+
0,
|
|
153
|
+
getViewportHeight(state.termRows),
|
|
154
|
+
detailVersions.length,
|
|
155
|
+
0
|
|
156
|
+
)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function exitDetail(state) {
|
|
160
|
+
if (state.view !== "detail") return state;
|
|
161
|
+
return {
|
|
162
|
+
...state,
|
|
163
|
+
view: "list",
|
|
164
|
+
detailDep: null,
|
|
165
|
+
detailVersions: [],
|
|
166
|
+
detailCursor: 0,
|
|
167
|
+
detailScrollOffset: 0
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function selectDetailVersion(state) {
|
|
171
|
+
if (state.view !== "detail" || !state.detailDep || state.detailVersions.length === 0) return state;
|
|
172
|
+
const selected = state.detailVersions[state.detailCursor];
|
|
173
|
+
if (!selected) return state;
|
|
174
|
+
applyVersionSelection(state.detailDep, selected.version);
|
|
175
|
+
const selectedNames = new Set(state.selectedNames);
|
|
176
|
+
selectedNames.add(state.detailDep.name);
|
|
177
|
+
return {
|
|
178
|
+
...state,
|
|
179
|
+
view: "list",
|
|
180
|
+
detailDep: null,
|
|
181
|
+
detailVersions: [],
|
|
182
|
+
detailCursor: 0,
|
|
183
|
+
detailScrollOffset: 0,
|
|
184
|
+
selectedNames
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function moveDetailCursor(state, delta) {
|
|
188
|
+
if (state.view !== "detail" || state.detailVersions.length === 0 || delta === 0) return state;
|
|
189
|
+
const max = state.detailVersions.length - 1;
|
|
190
|
+
const next = Math.min(max, Math.max(0, state.detailCursor + delta));
|
|
191
|
+
if (next === state.detailCursor) return state;
|
|
192
|
+
return {
|
|
193
|
+
...state,
|
|
194
|
+
detailCursor: next,
|
|
195
|
+
detailScrollOffset: updateDetailScroll(state, next, state.termRows, state.detailScrollOffset)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function findDepIndex(items, start, direction) {
|
|
200
|
+
let i = start;
|
|
201
|
+
while (i >= 0 && i < items.length) {
|
|
202
|
+
const item = items[i];
|
|
203
|
+
if (item?.type === "dep") return i;
|
|
204
|
+
i += direction;
|
|
205
|
+
}
|
|
206
|
+
return -1;
|
|
207
|
+
}
|
|
208
|
+
function createInitialState(updates, options = {}) {
|
|
209
|
+
const termRows = safeTermSize(options.termRows, 24);
|
|
210
|
+
const termCols = safeTermSize(options.termCols, 80);
|
|
211
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
212
|
+
for (const [depIndex, dep] of updates.entries()) {
|
|
213
|
+
const existing = grouped.get(dep.source);
|
|
214
|
+
if (existing) {
|
|
215
|
+
existing.push({ dep, depIndex });
|
|
216
|
+
} else {
|
|
217
|
+
grouped.set(dep.source, [{ dep, depIndex }]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const items = [];
|
|
221
|
+
let index = 0;
|
|
222
|
+
for (const [groupLabel, deps] of grouped.entries()) {
|
|
223
|
+
items.push({
|
|
224
|
+
type: "group-header",
|
|
225
|
+
groupLabel,
|
|
226
|
+
index
|
|
227
|
+
});
|
|
228
|
+
index++;
|
|
229
|
+
for (const entry of deps) {
|
|
230
|
+
items.push({
|
|
231
|
+
type: "dep",
|
|
232
|
+
dep: entry.dep,
|
|
233
|
+
depIndex: entry.depIndex,
|
|
234
|
+
index
|
|
235
|
+
});
|
|
236
|
+
index++;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const firstDepCursor = findDepIndex(items, 0, 1);
|
|
240
|
+
const cursor = firstDepCursor >= 0 ? firstDepCursor : 0;
|
|
241
|
+
const initial = {
|
|
242
|
+
view: "list",
|
|
243
|
+
items,
|
|
244
|
+
cursor,
|
|
245
|
+
scrollOffset: 0,
|
|
246
|
+
detailDep: null,
|
|
247
|
+
detailVersions: [],
|
|
248
|
+
detailCursor: 0,
|
|
249
|
+
detailScrollOffset: 0,
|
|
250
|
+
selectedNames: /* @__PURE__ */ new Set(),
|
|
251
|
+
termRows,
|
|
252
|
+
termCols,
|
|
253
|
+
explain: options.explain ?? false,
|
|
254
|
+
confirmed: false,
|
|
255
|
+
cancelled: false
|
|
256
|
+
};
|
|
257
|
+
return {
|
|
258
|
+
...initial,
|
|
259
|
+
scrollOffset: updateListScroll(initial, cursor, termRows)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function moveCursor(state, delta) {
|
|
263
|
+
if (state.view !== "list" || delta === 0 || state.items.length === 0) return state;
|
|
264
|
+
const direction = delta > 0 ? 1 : -1;
|
|
265
|
+
let cursor = state.cursor;
|
|
266
|
+
const steps = Math.max(1, Math.abs(delta));
|
|
267
|
+
for (let i = 0; i < steps; i++) {
|
|
268
|
+
const next = findDepIndex(state.items, cursor + direction, direction);
|
|
269
|
+
if (next === -1) break;
|
|
270
|
+
cursor = next;
|
|
271
|
+
}
|
|
272
|
+
if (cursor === state.cursor) return state;
|
|
273
|
+
return {
|
|
274
|
+
...state,
|
|
275
|
+
cursor,
|
|
276
|
+
scrollOffset: updateListScroll(state, cursor, state.termRows)
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function jumpToFirst(state) {
|
|
280
|
+
if (state.view !== "list") return state;
|
|
281
|
+
const cursor = findDepIndex(state.items, 0, 1);
|
|
282
|
+
if (cursor === -1 || cursor === state.cursor) return state;
|
|
283
|
+
return {
|
|
284
|
+
...state,
|
|
285
|
+
cursor,
|
|
286
|
+
scrollOffset: updateListScroll(state, cursor, state.termRows)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function jumpToLast(state) {
|
|
290
|
+
if (state.view !== "list") return state;
|
|
291
|
+
const cursor = findDepIndex(state.items, state.items.length - 1, -1);
|
|
292
|
+
if (cursor === -1 || cursor === state.cursor) return state;
|
|
293
|
+
return {
|
|
294
|
+
...state,
|
|
295
|
+
cursor,
|
|
296
|
+
scrollOffset: updateListScroll(state, cursor, state.termRows)
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function pageMove(state, direction) {
|
|
300
|
+
if (state.view !== "list") return state;
|
|
301
|
+
const distance = getViewportHeight(state.termRows);
|
|
302
|
+
return moveCursor(state, direction * distance);
|
|
303
|
+
}
|
|
304
|
+
function toggleSelection(state) {
|
|
305
|
+
if (state.view !== "list") return state;
|
|
306
|
+
const item = state.items[state.cursor];
|
|
307
|
+
if (item?.type !== "dep" || !item.dep) return state;
|
|
308
|
+
const selectedNames = new Set(state.selectedNames);
|
|
309
|
+
if (selectedNames.has(item.dep.name)) {
|
|
310
|
+
selectedNames.delete(item.dep.name);
|
|
311
|
+
} else {
|
|
312
|
+
selectedNames.add(item.dep.name);
|
|
313
|
+
}
|
|
314
|
+
return { ...state, selectedNames };
|
|
315
|
+
}
|
|
316
|
+
function toggleAll(state) {
|
|
317
|
+
const depNames = Array.from(
|
|
318
|
+
new Set(
|
|
319
|
+
state.items.filter(
|
|
320
|
+
(item) => item.type === "dep" && !!item.dep
|
|
321
|
+
).map((item) => item.dep.name)
|
|
322
|
+
)
|
|
323
|
+
);
|
|
324
|
+
if (depNames.length === 0) return state;
|
|
325
|
+
const allSelected = depNames.every((name) => state.selectedNames.has(name));
|
|
326
|
+
return {
|
|
327
|
+
...state,
|
|
328
|
+
selectedNames: allSelected ? /* @__PURE__ */ new Set() : new Set(depNames)
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function confirm(state) {
|
|
333
|
+
return { ...state, confirmed: true };
|
|
334
|
+
}
|
|
335
|
+
function cancel(state) {
|
|
336
|
+
return { ...state, cancelled: true };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function isShiftG(key) {
|
|
340
|
+
return key.name === "G" || key.name === "g" && key.shift || key.sequence === "G";
|
|
341
|
+
}
|
|
342
|
+
function handleListViewKeypress(state, key) {
|
|
343
|
+
switch (key.name) {
|
|
344
|
+
case "down":
|
|
345
|
+
case "j":
|
|
346
|
+
return moveCursor(state, 1);
|
|
347
|
+
case "up":
|
|
348
|
+
case "k":
|
|
349
|
+
return moveCursor(state, -1);
|
|
350
|
+
case "space":
|
|
351
|
+
return toggleSelection(state);
|
|
352
|
+
case "a":
|
|
353
|
+
return toggleAll(state);
|
|
354
|
+
case "right":
|
|
355
|
+
case "l":
|
|
356
|
+
return enterDetail(state);
|
|
357
|
+
case "return":
|
|
358
|
+
return confirm(state);
|
|
359
|
+
case "escape":
|
|
360
|
+
case "q":
|
|
361
|
+
return cancel(state);
|
|
362
|
+
case "pagedown":
|
|
363
|
+
return pageMove(state, 1);
|
|
364
|
+
case "pageup":
|
|
365
|
+
return pageMove(state, -1);
|
|
366
|
+
case "g":
|
|
367
|
+
return isShiftG(key) ? jumpToLast(state) : jumpToFirst(state);
|
|
368
|
+
default:
|
|
369
|
+
return isShiftG(key) ? jumpToLast(state) : state;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function handleDetailViewKeypress(state, key) {
|
|
373
|
+
switch (key.name) {
|
|
374
|
+
case "down":
|
|
375
|
+
case "j":
|
|
376
|
+
return moveDetailCursor(state, 1);
|
|
377
|
+
case "up":
|
|
378
|
+
case "k":
|
|
379
|
+
return moveDetailCursor(state, -1);
|
|
380
|
+
case "space":
|
|
381
|
+
case "return":
|
|
382
|
+
return selectDetailVersion(state);
|
|
383
|
+
case "left":
|
|
384
|
+
case "h":
|
|
385
|
+
case "escape":
|
|
386
|
+
case "q":
|
|
387
|
+
return exitDetail(state);
|
|
388
|
+
default:
|
|
389
|
+
return state;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function handleKeypress(state, key) {
|
|
393
|
+
if (key.ctrl && key.name === "c") {
|
|
394
|
+
return cancel(state);
|
|
395
|
+
}
|
|
396
|
+
if (state.view === "detail") {
|
|
397
|
+
return handleDetailViewKeypress(state, key);
|
|
398
|
+
}
|
|
399
|
+
return handleListViewKeypress(state, key);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const LIST_HELP = " up/down navigate Space toggle -> versions a all Enter confirm Esc cancel";
|
|
403
|
+
const DETAIL_HELP = " up/down navigate Space/Enter select left/Esc back";
|
|
404
|
+
const DETAIL_CHROME_LINES = 8;
|
|
405
|
+
function colorAge(age) {
|
|
406
|
+
if (!age) return "";
|
|
407
|
+
if (age.color === "green") return c.green(age.text);
|
|
408
|
+
if (age.color === "yellow") return c.yellow(age.text);
|
|
409
|
+
return c.red(age.text);
|
|
410
|
+
}
|
|
411
|
+
function fitLine(line, termCols) {
|
|
412
|
+
if (termCols <= 0) return line;
|
|
413
|
+
const visible = stripAnsi(line);
|
|
414
|
+
if (visible.length <= termCols) return line;
|
|
415
|
+
return truncate(visible, termCols);
|
|
416
|
+
}
|
|
417
|
+
function getDepItems(state) {
|
|
418
|
+
return state.items.filter(
|
|
419
|
+
(item) => item.type === "dep" && !!item.dep
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function renderListView(state) {
|
|
424
|
+
const lines = [];
|
|
425
|
+
const depItems = getDepItems(state);
|
|
426
|
+
const nameWidth = Math.min(
|
|
427
|
+
depItems.reduce((max, item) => Math.max(max, item.dep.name.length), 4),
|
|
428
|
+
24
|
|
429
|
+
);
|
|
430
|
+
const viewportHeight = getViewportHeight(state.termRows);
|
|
431
|
+
const { start, end } = getVisibleRange(state.scrollOffset, viewportHeight, state.items.length);
|
|
432
|
+
const visibleItems = state.items.slice(start, end);
|
|
433
|
+
lines.push(fitLine(" Select dependencies to update", state.termCols));
|
|
434
|
+
lines.push("");
|
|
435
|
+
if (hasOverflowAbove(state.scrollOffset)) {
|
|
436
|
+
lines.push(fitLine(` ${c.gray("^ more")}`, state.termCols));
|
|
437
|
+
}
|
|
438
|
+
for (const item of visibleItems) {
|
|
439
|
+
lines.push(renderListLine(state, item, nameWidth));
|
|
440
|
+
}
|
|
441
|
+
if (hasOverflowBelow(state.scrollOffset, viewportHeight, state.items.length)) {
|
|
442
|
+
lines.push(fitLine(` ${c.gray("v more")}`, state.termCols));
|
|
443
|
+
}
|
|
444
|
+
const totalDeps = depItems.length;
|
|
445
|
+
const selectedCount = depItems.reduce(
|
|
446
|
+
(count, item) => state.selectedNames.has(item.dep.name) ? count + 1 : count,
|
|
447
|
+
0
|
|
448
|
+
);
|
|
449
|
+
lines.push(fitLine(` ${selectedCount}/${totalDeps} selected`, state.termCols));
|
|
450
|
+
lines.push("");
|
|
451
|
+
lines.push(fitLine(LIST_HELP, state.termCols));
|
|
452
|
+
return lines;
|
|
453
|
+
}
|
|
454
|
+
function renderListLine(state, item, nameWidth) {
|
|
455
|
+
if (item.type === "group-header") {
|
|
456
|
+
return fitLine(` ${c.gray(item.groupLabel ?? "")}`, state.termCols);
|
|
457
|
+
}
|
|
458
|
+
if (!item.dep) return "";
|
|
459
|
+
return renderListDepLine(
|
|
460
|
+
item.dep,
|
|
461
|
+
item.index === state.cursor,
|
|
462
|
+
state.selectedNames.has(item.dep.name),
|
|
463
|
+
nameWidth,
|
|
464
|
+
state.termCols
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
function renderListDepLine(dep, focused, selected, nameWidth, termCols) {
|
|
468
|
+
const pointer = focused ? c.cyan(">") : " ";
|
|
469
|
+
const selectedMark = selected ? c.green("*") : c.gray("o");
|
|
470
|
+
const name = padEnd(truncate(dep.name, nameWidth), nameWidth);
|
|
471
|
+
const target = colorizeVersionDiff(dep.currentVersion, dep.targetVersion, dep.diff);
|
|
472
|
+
const age = timeDifference(dep.publishedAt);
|
|
473
|
+
let line = ` ${pointer} ${selectedMark} ${name} ${dep.currentVersion}${arrow()}${target} ${colorDiff(dep.diff)}`;
|
|
474
|
+
if (age) line += ` ${colorAge(age)}`;
|
|
475
|
+
return fitLine(line, termCols);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function renderDetailView(state) {
|
|
479
|
+
const lines = [];
|
|
480
|
+
const dep = state.detailDep;
|
|
481
|
+
if (!dep) return renderListView(state);
|
|
482
|
+
const header = ` ${dep.name} ${dep.currentVersion}${arrow()}?`;
|
|
483
|
+
lines.push(fitLine(header, state.termCols));
|
|
484
|
+
lines.push("");
|
|
485
|
+
const versionWidth = Math.min(
|
|
486
|
+
state.detailVersions.reduce((max, item) => Math.max(max, item.version.length), 6),
|
|
487
|
+
18
|
|
488
|
+
);
|
|
489
|
+
const viewportHeight = getViewportHeight(state.termRows, DETAIL_CHROME_LINES);
|
|
490
|
+
const { start, end } = getVisibleRange(
|
|
491
|
+
state.detailScrollOffset,
|
|
492
|
+
viewportHeight,
|
|
493
|
+
state.detailVersions.length
|
|
494
|
+
);
|
|
495
|
+
const visible = state.detailVersions.slice(start, end);
|
|
496
|
+
if (hasOverflowAbove(state.detailScrollOffset)) {
|
|
497
|
+
lines.push(fitLine(` ${c.gray("^ more")}`, state.termCols));
|
|
498
|
+
}
|
|
499
|
+
for (const [index, version] of visible.entries()) {
|
|
500
|
+
lines.push(renderDetailVersionLine(state, version, start + index, versionWidth));
|
|
501
|
+
}
|
|
502
|
+
if (hasOverflowBelow(state.detailScrollOffset, viewportHeight, state.detailVersions.length)) {
|
|
503
|
+
lines.push(fitLine(` ${c.gray("v more")}`, state.termCols));
|
|
504
|
+
}
|
|
505
|
+
const distTags = Object.entries(dep.pkgData.distTags).map(([tag, version]) => `${tag}${arrow()}${version}`).join(" | ");
|
|
506
|
+
lines.push("");
|
|
507
|
+
if (distTags) {
|
|
508
|
+
lines.push(fitLine(` dist-tags: ${distTags}`, state.termCols));
|
|
509
|
+
}
|
|
510
|
+
if (dep.pkgData.homepage) {
|
|
511
|
+
lines.push(fitLine(` Homepage: ${dep.pkgData.homepage}`, state.termCols));
|
|
512
|
+
}
|
|
513
|
+
lines.push("");
|
|
514
|
+
lines.push(fitLine(DETAIL_HELP, state.termCols));
|
|
515
|
+
return lines;
|
|
516
|
+
}
|
|
517
|
+
function renderDetailVersionLine(state, version, idx, versionWidth) {
|
|
518
|
+
const pointer = idx === state.detailCursor ? c.cyan(">") : " ";
|
|
519
|
+
const versionText = padEnd(version.version, versionWidth);
|
|
520
|
+
let line = ` ${pointer} ${versionText} ${colorDiff(version.diff)}`;
|
|
521
|
+
if (version.age) line += ` ${colorAge(version.age)}`;
|
|
522
|
+
if (version.distTag) line += ` ${c.cyan(version.distTag)}`;
|
|
523
|
+
if (version.explain) line += ` ${c.gray(version.explain)}`;
|
|
524
|
+
if (version.deprecated) line += ` ${c.red("deprecated")}`;
|
|
525
|
+
if (version.provenance === "none") line += ` ${c.yellow("no-provenance")}`;
|
|
526
|
+
if (version.nodeEngines) line += ` ${c.gray(`node ${version.nodeEngines}`)}`;
|
|
527
|
+
return fitLine(line, state.termCols);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function renderFrame(state) {
|
|
531
|
+
const lines = state.view === "detail" ? renderDetailView(state) : renderListView(state);
|
|
532
|
+
return `${lines.join("\n")}
|
|
533
|
+
`;
|
|
534
|
+
}
|
|
535
|
+
function eraseLines(count) {
|
|
536
|
+
if (count <= 0) return "";
|
|
537
|
+
return `\x1B[${count}A\x1B[0J`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const HIDE_CURSOR = "\x1B[?25l";
|
|
541
|
+
const SHOW_CURSOR = "\x1B[?25h";
|
|
542
|
+
function getRows() {
|
|
543
|
+
return process.stdout.rows ?? 24;
|
|
544
|
+
}
|
|
545
|
+
function getCols() {
|
|
546
|
+
return process.stdout.columns ?? 80;
|
|
547
|
+
}
|
|
548
|
+
function countFrameLines(frame) {
|
|
549
|
+
const parts = frame.split("\n");
|
|
550
|
+
return frame.endsWith("\n") ? parts.length - 1 : parts.length;
|
|
551
|
+
}
|
|
552
|
+
function getSelectedUpdates(state, updates) {
|
|
553
|
+
return updates.filter((dep) => state.selectedNames.has(dep.name));
|
|
554
|
+
}
|
|
555
|
+
async function createInteractiveTUI(updates, options) {
|
|
556
|
+
if (updates.length === 0) return [];
|
|
557
|
+
const input = process.stdin;
|
|
558
|
+
const output = process.stdout;
|
|
559
|
+
if (typeof input.setRawMode !== "function") {
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
return new Promise((resolve, reject) => {
|
|
563
|
+
let state = createInitialState(updates, {
|
|
564
|
+
termRows: getRows(),
|
|
565
|
+
termCols: getCols(),
|
|
566
|
+
explain: options.explain
|
|
567
|
+
});
|
|
568
|
+
let lastFrameLineCount = 0;
|
|
569
|
+
let finished = false;
|
|
570
|
+
const render = () => {
|
|
571
|
+
const frame = renderFrame(state);
|
|
572
|
+
if (lastFrameLineCount > 0) {
|
|
573
|
+
output.write(eraseLines(lastFrameLineCount));
|
|
574
|
+
}
|
|
575
|
+
output.write(frame);
|
|
576
|
+
lastFrameLineCount = countFrameLines(frame);
|
|
577
|
+
};
|
|
578
|
+
const cleanup = () => {
|
|
579
|
+
input.off("keypress", onKeypress);
|
|
580
|
+
output.off("resize", onResize);
|
|
581
|
+
if (lastFrameLineCount > 0) {
|
|
582
|
+
output.write(eraseLines(lastFrameLineCount));
|
|
583
|
+
}
|
|
584
|
+
output.write(SHOW_CURSOR);
|
|
585
|
+
input.setRawMode(false);
|
|
586
|
+
input.pause();
|
|
587
|
+
};
|
|
588
|
+
const finish = (result) => {
|
|
589
|
+
if (finished) return;
|
|
590
|
+
finished = true;
|
|
591
|
+
cleanup();
|
|
592
|
+
resolve(result);
|
|
593
|
+
};
|
|
594
|
+
const onResize = () => {
|
|
595
|
+
state = resize(state, getRows(), getCols());
|
|
596
|
+
render();
|
|
597
|
+
};
|
|
598
|
+
const onKeypress = (_sequence, key) => {
|
|
599
|
+
state = handleKeypress(state, key);
|
|
600
|
+
render();
|
|
601
|
+
if (state.cancelled) {
|
|
602
|
+
finish([]);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (state.confirmed) {
|
|
606
|
+
finish(getSelectedUpdates(state, updates));
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
try {
|
|
610
|
+
readline.emitKeypressEvents(input);
|
|
611
|
+
input.setRawMode(true);
|
|
612
|
+
input.resume();
|
|
613
|
+
output.write(HIDE_CURSOR);
|
|
614
|
+
render();
|
|
615
|
+
input.on("keypress", onKeypress);
|
|
616
|
+
output.on("resize", onResize);
|
|
617
|
+
} catch (error) {
|
|
618
|
+
cleanup();
|
|
619
|
+
reject(error);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export { createInteractiveTUI };
|