@nmakarov/cli-toolkit 0.79.0 → 0.81.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/dist/cli-runner.cjs +194 -57
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +194 -57
- package/dist/cli-runner.js.map +1 -1
- package/dist/db.cjs +105 -25
- package/dist/db.cjs.map +1 -1
- package/dist/db.js +105 -25
- package/dist/db.js.map +1 -1
- package/dist/index.cjs +179 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +175 -56
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +89 -32
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +89 -32
- package/dist/init.js.map +1 -1
- package/dist/logger.cjs +6 -5
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js +6 -5
- package/dist/logger.js.map +1 -1
- package/dist/screen.cjs +59 -26
- package/dist/screen.cjs.map +1 -1
- package/dist/screen.js +55 -26
- package/dist/screen.js.map +1 -1
- package/package.json +1 -1
package/dist/init.cjs
CHANGED
|
@@ -1018,6 +1018,30 @@ var init_ui_elements = __esm({
|
|
|
1018
1018
|
}
|
|
1019
1019
|
});
|
|
1020
1020
|
|
|
1021
|
+
// src/screen/follow-scroll.js
|
|
1022
|
+
function clampScroll(scrollTop, maxScroll) {
|
|
1023
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
1024
|
+
const top = Number(scrollTop) || 0;
|
|
1025
|
+
return Math.min(Math.max(0, top), max);
|
|
1026
|
+
}
|
|
1027
|
+
function isScrolledToBottom(scrollTop, maxScroll) {
|
|
1028
|
+
return clampScroll(scrollTop, maxScroll) >= Math.max(0, Number(maxScroll) || 0);
|
|
1029
|
+
}
|
|
1030
|
+
function nextScrollAfterUserMove(scrollTop, maxScroll, delta) {
|
|
1031
|
+
const next = clampScroll((Number(scrollTop) || 0) + (Number(delta) || 0), maxScroll);
|
|
1032
|
+
return { scrollTop: next, following: isScrolledToBottom(next, maxScroll) };
|
|
1033
|
+
}
|
|
1034
|
+
function nextScrollAfterContentChange({ following, scrollTop, maxScroll }) {
|
|
1035
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
1036
|
+
if (following) return { scrollTop: max, following: true };
|
|
1037
|
+
const next = clampScroll(scrollTop, max);
|
|
1038
|
+
return { scrollTop: next, following: isScrolledToBottom(next, max) };
|
|
1039
|
+
}
|
|
1040
|
+
var init_follow_scroll = __esm({
|
|
1041
|
+
"src/screen/follow-scroll.js"() {
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1021
1045
|
// src/screen/scrollable-text.js
|
|
1022
1046
|
function wrapTextLines(text, cols) {
|
|
1023
1047
|
const w = Math.max(1, Math.floor(Number(cols) || 1));
|
|
@@ -1050,9 +1074,11 @@ function ScrollableText({
|
|
|
1050
1074
|
showScrollbar = true,
|
|
1051
1075
|
showStatus = true,
|
|
1052
1076
|
bindKeys = true,
|
|
1053
|
-
header = null
|
|
1077
|
+
header = null,
|
|
1078
|
+
followBottom = false
|
|
1054
1079
|
}) {
|
|
1055
1080
|
const [scrollTop, setScrollTop] = (0, import_react5.useState)(0);
|
|
1081
|
+
const [following, setFollowing] = (0, import_react5.useState)(() => !!followBottom);
|
|
1056
1082
|
const [, bump] = (0, import_react5.useState)(0);
|
|
1057
1083
|
const termRows = process.stdout.rows || 24;
|
|
1058
1084
|
const viewportRows = Math.max(
|
|
@@ -1071,39 +1097,40 @@ function ScrollableText({
|
|
|
1071
1097
|
const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
|
|
1072
1098
|
const visible = allLines.slice(clamped, clamped + viewportRows);
|
|
1073
1099
|
const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
|
|
1074
|
-
(0, import_react5.useEffect)(() => {
|
|
1075
|
-
setScrollTop((s) => Math.min(s, maxScroll));
|
|
1076
|
-
}, [maxScroll]);
|
|
1077
1100
|
const maxScrollRef = (0, import_react5.useRef)(maxScroll);
|
|
1078
1101
|
const pageSizeRef = (0, import_react5.useRef)(viewportRows);
|
|
1102
|
+
const scrollTopRef = (0, import_react5.useRef)(scrollTop);
|
|
1103
|
+
const followingRef = (0, import_react5.useRef)(following);
|
|
1079
1104
|
maxScrollRef.current = maxScroll;
|
|
1080
1105
|
pageSizeRef.current = viewportRows;
|
|
1106
|
+
scrollTopRef.current = scrollTop;
|
|
1107
|
+
followingRef.current = following;
|
|
1108
|
+
(0, import_react5.useEffect)(() => {
|
|
1109
|
+
const next = nextScrollAfterContentChange({
|
|
1110
|
+
following: followBottom && followingRef.current,
|
|
1111
|
+
scrollTop: scrollTopRef.current,
|
|
1112
|
+
maxScroll
|
|
1113
|
+
});
|
|
1114
|
+
if (next.scrollTop !== scrollTopRef.current) setScrollTop(next.scrollTop);
|
|
1115
|
+
if (followBottom && next.following !== followingRef.current) setFollowing(next.following);
|
|
1116
|
+
}, [maxScroll, followBottom]);
|
|
1117
|
+
const applyUserScroll = (delta) => {
|
|
1118
|
+
const next = nextScrollAfterUserMove(scrollTopRef.current, maxScrollRef.current, delta);
|
|
1119
|
+
setScrollTop(next.scrollTop);
|
|
1120
|
+
if (followBottom) setFollowing(next.following);
|
|
1121
|
+
bump((n) => n + 1);
|
|
1122
|
+
ctx?.update?.();
|
|
1123
|
+
};
|
|
1081
1124
|
(0, import_react5.useEffect)(() => {
|
|
1082
1125
|
if (!ctx || !bindKeys) return void 0;
|
|
1083
1126
|
ctx.setKeyBinding(SCROLL_KEYS);
|
|
1084
|
-
ctx.setAction("scrollUp", () =>
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
});
|
|
1089
|
-
ctx.setAction("scrollDown", () => {
|
|
1090
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
|
|
1091
|
-
bump((n) => n + 1);
|
|
1092
|
-
ctx.update?.();
|
|
1093
|
-
});
|
|
1094
|
-
ctx.setAction("pageUp", () => {
|
|
1095
|
-
setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
|
|
1096
|
-
bump((n) => n + 1);
|
|
1097
|
-
ctx.update?.();
|
|
1098
|
-
});
|
|
1099
|
-
ctx.setAction("pageDown", () => {
|
|
1100
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
|
|
1101
|
-
bump((n) => n + 1);
|
|
1102
|
-
ctx.update?.();
|
|
1103
|
-
});
|
|
1127
|
+
ctx.setAction("scrollUp", () => applyUserScroll(-1));
|
|
1128
|
+
ctx.setAction("scrollDown", () => applyUserScroll(1));
|
|
1129
|
+
ctx.setAction("pageUp", () => applyUserScroll(-pageSizeRef.current));
|
|
1130
|
+
ctx.setAction("pageDown", () => applyUserScroll(pageSizeRef.current));
|
|
1104
1131
|
return void 0;
|
|
1105
|
-
}, [ctx, bindKeys]);
|
|
1106
|
-
const status = allLines.length === 0 ? "empty" : `lines ${clamped + 1}-${Math.min(clamped + visible.length, allLines.length)} of ${allLines.length}` + (needsBar ? " \xB7 \u2325\u2191/\u2193 or PgUp/Dn page" : "");
|
|
1132
|
+
}, [ctx, bindKeys, followBottom]);
|
|
1133
|
+
const status = allLines.length === 0 ? "empty" : `lines ${clamped + 1}-${Math.min(clamped + visible.length, allLines.length)} of ${allLines.length}` + (needsBar ? " \xB7 \u2325\u2191/\u2193 or PgUp/Dn page" : "") + (followBottom && following ? " \xB7 follow" : followBottom ? " \xB7 follow off" : "");
|
|
1107
1134
|
const rowNodes = visible.map((line, i) => {
|
|
1108
1135
|
const body = padEndVisible(line, textWidth);
|
|
1109
1136
|
const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
|
|
@@ -1143,6 +1170,7 @@ var init_scrollable_text = __esm({
|
|
|
1143
1170
|
import_ink5 = require("ink");
|
|
1144
1171
|
init_components();
|
|
1145
1172
|
init_scrollbar();
|
|
1173
|
+
init_follow_scroll();
|
|
1146
1174
|
init_scrollbar();
|
|
1147
1175
|
h5 = import_react5.createElement;
|
|
1148
1176
|
SCROLL_KEYS = [
|
|
@@ -1311,10 +1339,14 @@ __export(screen_exports, {
|
|
|
1311
1339
|
buildBreadcrumb: () => buildBreadcrumb,
|
|
1312
1340
|
buildDetailBreadcrumb: () => buildDetailBreadcrumb,
|
|
1313
1341
|
buildFooter: () => buildFooter,
|
|
1342
|
+
clampScroll: () => clampScroll,
|
|
1314
1343
|
formatBindingKey: () => formatBindingKey,
|
|
1315
1344
|
h: () => import_react6.createElement,
|
|
1345
|
+
isScrolledToBottom: () => isScrolledToBottom,
|
|
1316
1346
|
load: () => load,
|
|
1317
1347
|
memo: () => import_react6.memo,
|
|
1348
|
+
nextScrollAfterContentChange: () => nextScrollAfterContentChange,
|
|
1349
|
+
nextScrollAfterUserMove: () => nextScrollAfterUserMove,
|
|
1318
1350
|
organizeFooterMessages: () => organizeFooterMessages,
|
|
1319
1351
|
scrollbarGlyphs: () => scrollbarGlyphs,
|
|
1320
1352
|
showListScreen: () => showListScreen,
|
|
@@ -1351,6 +1383,7 @@ var init_screen = __esm({
|
|
|
1351
1383
|
init_components();
|
|
1352
1384
|
init_ui_elements();
|
|
1353
1385
|
init_scrollable_text();
|
|
1386
|
+
init_follow_scroll();
|
|
1354
1387
|
init_scrollbar();
|
|
1355
1388
|
init_key_bindings();
|
|
1356
1389
|
init_utils();
|
|
@@ -2668,13 +2701,14 @@ var Logger = class _Logger {
|
|
|
2668
2701
|
*/
|
|
2669
2702
|
progress(message, opts) {
|
|
2670
2703
|
const { prefix, count, total } = opts;
|
|
2671
|
-
const
|
|
2704
|
+
const displayTotal = Math.max(Number(total) || 0, Number(count) || 0);
|
|
2705
|
+
const paddedTotal = String(displayTotal).length;
|
|
2672
2706
|
const paddedCount = String(count).padStart(paddedTotal, " ");
|
|
2673
2707
|
const payload = {
|
|
2674
2708
|
level: "progress",
|
|
2675
2709
|
message,
|
|
2676
2710
|
count: paddedCount,
|
|
2677
|
-
total,
|
|
2711
|
+
total: displayTotal,
|
|
2678
2712
|
prefix
|
|
2679
2713
|
};
|
|
2680
2714
|
const key = prefix ?? "";
|
|
@@ -2691,7 +2725,7 @@ var Logger = class _Logger {
|
|
|
2691
2725
|
if (wantTimes) {
|
|
2692
2726
|
let remaining = -1;
|
|
2693
2727
|
if (itemsPerSec > 0) {
|
|
2694
|
-
remaining = (
|
|
2728
|
+
remaining = Math.max(0, (displayTotal - count) / itemsPerSec);
|
|
2695
2729
|
}
|
|
2696
2730
|
payload.elapsed = this.round(elapsedSeconds, 2);
|
|
2697
2731
|
payload.remaining = remaining >= 0 ? this.round(remaining, 2) : remaining;
|
|
@@ -2700,12 +2734,12 @@ var Logger = class _Logger {
|
|
|
2700
2734
|
payload.rate = itemsPerSec >= 0 ? this.round(itemsPerSec, 2) : itemsPerSec;
|
|
2701
2735
|
}
|
|
2702
2736
|
}
|
|
2703
|
-
if (count
|
|
2737
|
+
if (count === total) {
|
|
2704
2738
|
delete this.startTimes[key];
|
|
2705
2739
|
delete this.startCounts[key];
|
|
2706
2740
|
delete this.lastProgressTimes[key];
|
|
2707
2741
|
}
|
|
2708
|
-
if (this.shouldOutputProgress(prefix ?? "", count,
|
|
2742
|
+
if (this.shouldOutputProgress(prefix ?? "", count, displayTotal)) {
|
|
2709
2743
|
this.out(payload);
|
|
2710
2744
|
if (this.options.progressThrottle && prefix) {
|
|
2711
2745
|
this.lastProgressTimes[prefix] = Date.now();
|
|
@@ -2864,6 +2898,8 @@ function setup(opts = {}) {
|
|
|
2864
2898
|
// a quick "show me the figured params and quit" that skips the flow's
|
|
2865
2899
|
// actual work. Like --stopAfter=init, this is a hard exit(0) (registered
|
|
2866
2900
|
// cleanups are skipped); call it once components/params are resolved.
|
|
2901
|
+
_requestExitCode: null,
|
|
2902
|
+
requestExit: null,
|
|
2867
2903
|
showUsedParamsIfNeeded: () => {
|
|
2868
2904
|
const mode = params.getShowUsedParamsMode?.();
|
|
2869
2905
|
if (mode !== "top" && mode !== "stop") return;
|
|
@@ -2874,6 +2910,9 @@ function setup(opts = {}) {
|
|
|
2874
2910
|
}
|
|
2875
2911
|
}
|
|
2876
2912
|
};
|
|
2913
|
+
context.requestExit = (code = 0) => {
|
|
2914
|
+
context._requestExitCode = code;
|
|
2915
|
+
};
|
|
2877
2916
|
logger.debug("[setup] completed successfully");
|
|
2878
2917
|
return context;
|
|
2879
2918
|
}
|
|
@@ -2909,9 +2948,25 @@ async function init(flow, opts = {}) {
|
|
|
2909
2948
|
if (cleanupRan) return;
|
|
2910
2949
|
cleanupRan = true;
|
|
2911
2950
|
const fns = [...ctx.cleanupFunctions].reverse();
|
|
2951
|
+
const budgetMs = 5e3;
|
|
2952
|
+
const started = Date.now();
|
|
2912
2953
|
for (const fn of fns) {
|
|
2954
|
+
const left = budgetMs - (Date.now() - started);
|
|
2955
|
+
if (left <= 0) {
|
|
2956
|
+
ctx.logger.warn("[cleanup] budget exhausted \u2014 skipping remaining cleanup");
|
|
2957
|
+
break;
|
|
2958
|
+
}
|
|
2913
2959
|
try {
|
|
2914
|
-
await
|
|
2960
|
+
await Promise.race([
|
|
2961
|
+
Promise.resolve(fn(ctx)),
|
|
2962
|
+
new Promise((_, reject) => {
|
|
2963
|
+
const t = setTimeout(
|
|
2964
|
+
() => reject(new Error(`cleanup timed out after ${left}ms`)),
|
|
2965
|
+
left
|
|
2966
|
+
);
|
|
2967
|
+
t.unref?.();
|
|
2968
|
+
})
|
|
2969
|
+
]);
|
|
2915
2970
|
} catch (error) {
|
|
2916
2971
|
ctx.logger.warn("[cleanup] error in cleanup function:", error);
|
|
2917
2972
|
}
|
|
@@ -3007,6 +3062,8 @@ async function init(flow, opts = {}) {
|
|
|
3007
3062
|
process.exit(process.exitCode);
|
|
3008
3063
|
} else if (stop || kill) {
|
|
3009
3064
|
process.exit(0);
|
|
3065
|
+
} else if (context._requestExitCode != null) {
|
|
3066
|
+
process.exit(context._requestExitCode);
|
|
3010
3067
|
}
|
|
3011
3068
|
}
|
|
3012
3069
|
}
|