@vlandoss/clibuddy 0.6.2-git-e008b4d.0 → 0.6.2-git-908d2c0.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/index.d.mts +9 -19
- package/dist/index.mjs +4 -12
- package/package.json +1 -1
- package/src/task-board.ts +19 -45
package/dist/index.d.mts
CHANGED
|
@@ -91,39 +91,29 @@ declare function isNonZeroExitError(value: unknown): value is NonZeroExitError;
|
|
|
91
91
|
//#endregion
|
|
92
92
|
//#region src/task-board.d.ts
|
|
93
93
|
type TaskOutcome = {
|
|
94
|
-
/**
|
|
95
|
-
/**
|
|
96
|
-
* Output to flush, grouped under the task label, once the board settles. The
|
|
97
|
-
* board prints it verbatim whenever it's non-empty (pass or fail) — the caller
|
|
98
|
-
* decides what, if anything, to surface.
|
|
99
|
-
*/
|
|
94
|
+
ok: boolean; /** Output flushed (grouped under the label) once the board settles. */
|
|
100
95
|
detail?: string;
|
|
101
96
|
};
|
|
102
97
|
type BoardTask = {
|
|
103
|
-
|
|
98
|
+
label: string; /** Resolve to a `TaskOutcome`; a rejection renders as a failed row. */
|
|
104
99
|
run: () => Promise<TaskOutcome>;
|
|
105
100
|
};
|
|
106
101
|
type BoardOptions = {
|
|
107
|
-
/** Section header
|
|
102
|
+
/** Section header above the rows, e.g. `tsc · 16 packages` (multi-row only). */title?: string;
|
|
108
103
|
/**
|
|
109
|
-
* Force the
|
|
110
|
-
*
|
|
111
|
-
* command leaves it unset and renders compactly. Defaults to `tasks.length > 1`.
|
|
104
|
+
* Force the `┌ │ └` frame even for one task. `rr check` sets it to divide its
|
|
105
|
+
* sections; otherwise a board is unframed (compact for one task, plain for many).
|
|
112
106
|
*/
|
|
113
107
|
frame?: boolean;
|
|
114
108
|
};
|
|
115
109
|
type BoardResult = {
|
|
116
|
-
|
|
110
|
+
ok: boolean;
|
|
117
111
|
outcomes: TaskOutcome[];
|
|
118
112
|
};
|
|
119
113
|
/**
|
|
120
|
-
* Runs `tasks` in parallel
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* keeping logs deterministic. After every task settles, each task's captured
|
|
124
|
-
* detail is flushed grouped under its label, followed by a one-line summary.
|
|
125
|
-
* Parallelism is never sacrificed — the renderer only reflects work that is
|
|
126
|
-
* already running.
|
|
114
|
+
* Runs `tasks` in parallel, rendering one row each that collapses to ✔/✖, then
|
|
115
|
+
* flushes their captured detail and a one-line summary. On a TTY the rows update
|
|
116
|
+
* in place; otherwise each prints once on settle, keeping logs deterministic.
|
|
127
117
|
*/
|
|
128
118
|
declare function runTaskBoard(tasks: BoardTask[], options?: BoardOptions): Promise<BoardResult>;
|
|
129
119
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -237,13 +237,9 @@ const BAR_END = gray("└");
|
|
|
237
237
|
/** Failing output past this many lines is truncated with a "+N more" note. */
|
|
238
238
|
const MAX_DETAIL_LINES = 60;
|
|
239
239
|
/**
|
|
240
|
-
* Runs `tasks` in parallel
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* keeping logs deterministic. After every task settles, each task's captured
|
|
244
|
-
* detail is flushed grouped under its label, followed by a one-line summary.
|
|
245
|
-
* Parallelism is never sacrificed — the renderer only reflects work that is
|
|
246
|
-
* already running.
|
|
240
|
+
* Runs `tasks` in parallel, rendering one row each that collapses to ✔/✖, then
|
|
241
|
+
* flushes their captured detail and a one-line summary. On a TTY the rows update
|
|
242
|
+
* in place; otherwise each prints once on settle, keeping logs deterministic.
|
|
247
243
|
*/
|
|
248
244
|
async function runTaskBoard(tasks, options = {}) {
|
|
249
245
|
const live = hasTTY && !isCI;
|
|
@@ -369,11 +365,7 @@ function sharedLeadingLine(details) {
|
|
|
369
365
|
function stripLeadingLine(detail, line) {
|
|
370
366
|
return detail.startsWith(line) ? detail.slice(line.length).replace(/^\n/, "") : detail;
|
|
371
367
|
}
|
|
372
|
-
/**
|
|
373
|
-
* The leading decoration for a row. Framed: `│` per row, or `┌` for a framed
|
|
374
|
-
* single task (status rides the opening corner). Unframed: a 2-space indent
|
|
375
|
-
* under the title for a multi-row board, nothing for a lone compact row.
|
|
376
|
-
*/
|
|
368
|
+
/** A row's leading decoration: `│` (framed multi), `┌` (framed single, status rides the corner), or a plain indent. */
|
|
377
369
|
function rowPrefix(framed, multi) {
|
|
378
370
|
if (framed) return multi ? `${BAR} ` : `${BAR_START} `;
|
|
379
371
|
return multi ? " " : "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vlandoss/clibuddy",
|
|
3
|
-
"version": "0.6.2-git-
|
|
3
|
+
"version": "0.6.2-git-908d2c0.0",
|
|
4
4
|
"description": "A helper library to create CLIs in Variable Land",
|
|
5
5
|
"homepage": "https://github.com/variableland/dx/tree/main/shared/clibuddy#readme",
|
|
6
6
|
"bugs": {
|
package/src/task-board.ts
CHANGED
|
@@ -3,47 +3,35 @@ import { palette } from "./colors.ts";
|
|
|
3
3
|
import { hasTTY, isCI } from "./env.ts";
|
|
4
4
|
|
|
5
5
|
export type TaskOutcome = {
|
|
6
|
-
/** The task's verdict — typically the wrapped process's exit code === 0. */
|
|
7
6
|
ok: boolean;
|
|
8
|
-
/**
|
|
9
|
-
* Output to flush, grouped under the task label, once the board settles. The
|
|
10
|
-
* board prints it verbatim whenever it's non-empty (pass or fail) — the caller
|
|
11
|
-
* decides what, if anything, to surface.
|
|
12
|
-
*/
|
|
7
|
+
/** Output flushed (grouped under the label) once the board settles. */
|
|
13
8
|
detail?: string;
|
|
14
9
|
};
|
|
15
10
|
|
|
16
11
|
export type BoardTask = {
|
|
17
|
-
/** Stable identifier rendered on the row, e.g. a package name. */
|
|
18
12
|
label: string;
|
|
19
|
-
/**
|
|
13
|
+
/** Resolve to a `TaskOutcome`; a rejection renders as a failed row. */
|
|
20
14
|
run: () => Promise<TaskOutcome>;
|
|
21
15
|
};
|
|
22
16
|
|
|
23
17
|
export type BoardOptions = {
|
|
24
|
-
/** Section header
|
|
18
|
+
/** Section header above the rows, e.g. `tsc · 16 packages` (multi-row only). */
|
|
25
19
|
title?: string;
|
|
26
20
|
/**
|
|
27
|
-
* Force the
|
|
28
|
-
*
|
|
29
|
-
* command leaves it unset and renders compactly. Defaults to `tasks.length > 1`.
|
|
21
|
+
* Force the `┌ │ └` frame even for one task. `rr check` sets it to divide its
|
|
22
|
+
* sections; otherwise a board is unframed (compact for one task, plain for many).
|
|
30
23
|
*/
|
|
31
24
|
frame?: boolean;
|
|
32
25
|
};
|
|
33
26
|
|
|
34
27
|
export type BoardResult = {
|
|
35
|
-
/** False when any task ended not-ok. */
|
|
36
28
|
ok: boolean;
|
|
37
29
|
outcomes: TaskOutcome[];
|
|
38
30
|
};
|
|
39
31
|
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// (we can't tell a clean "Found 0 warnings" trailer from a real warning without
|
|
44
|
-
// parsing, so we don't pretend to — the tool's own output says which it is).
|
|
45
|
-
// The gutter uses the 16-color `gray` (not a fixed hex) so it adapts to the
|
|
46
|
-
// terminal theme and degrades on non-truecolor surfaces / CI log viewers.
|
|
32
|
+
// Glyphs mirror @clack/prompts (used by `rr plugins`) so the two flows read as
|
|
33
|
+
// one family: the ◒◐◓◑ spinner and a gray `│ ┌ └` gutter, settling to ✔/✖. The
|
|
34
|
+
// gray is 16-color (not a fixed hex) so it adapts to the terminal theme.
|
|
47
35
|
const FRAMES = ["◒", "◐", "◓", "◑"];
|
|
48
36
|
const TICK_MS = 80;
|
|
49
37
|
const PASS = green("✔");
|
|
@@ -63,19 +51,12 @@ type RowState = {
|
|
|
63
51
|
};
|
|
64
52
|
|
|
65
53
|
/**
|
|
66
|
-
* Runs `tasks` in parallel
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* keeping logs deterministic. After every task settles, each task's captured
|
|
70
|
-
* detail is flushed grouped under its label, followed by a one-line summary.
|
|
71
|
-
* Parallelism is never sacrificed — the renderer only reflects work that is
|
|
72
|
-
* already running.
|
|
54
|
+
* Runs `tasks` in parallel, rendering one row each that collapses to ✔/✖, then
|
|
55
|
+
* flushes their captured detail and a one-line summary. On a TTY the rows update
|
|
56
|
+
* in place; otherwise each prints once on settle, keeping logs deterministic.
|
|
73
57
|
*/
|
|
74
58
|
export async function runTaskBoard(tasks: BoardTask[], options: BoardOptions = {}): Promise<BoardResult> {
|
|
75
59
|
const live = hasTTY && !isCI;
|
|
76
|
-
// The `┌ │ └` frame is reserved for composition — `rr check` sets `frame: true`
|
|
77
|
-
// to divide its sections. A standalone command never frames, even a monorepo
|
|
78
|
-
// run with many rows (it's still one command): it gets a plain title + summary.
|
|
79
60
|
const framed = options.frame ?? false;
|
|
80
61
|
return live ? runLive(tasks, options, framed) : runStatic(tasks, options, framed);
|
|
81
62
|
}
|
|
@@ -122,7 +103,7 @@ async function runLive(tasks: BoardTask[], options: BoardOptions, framed: boolea
|
|
|
122
103
|
frame = (frame + 1) % FRAMES.length;
|
|
123
104
|
render();
|
|
124
105
|
}
|
|
125
|
-
render(); //
|
|
106
|
+
render(); // collapse every row to its final glyph
|
|
126
107
|
} finally {
|
|
127
108
|
out.write("\x1b[?25h"); // restore cursor
|
|
128
109
|
}
|
|
@@ -171,22 +152,19 @@ function finish(rows: RowState[], out: NodeJS.WriteStream, framed: boolean, mult
|
|
|
171
152
|
// package ran — so a monorepo shows the command once instead of per package.
|
|
172
153
|
const shared = blocks.length > 1 ? sharedLeadingLine(blocks.map((b) => b.detail)) : undefined;
|
|
173
154
|
|
|
174
|
-
// Framed
|
|
175
|
-
// frame) indents it. The spacer above each is the gutter bar or a blank line.
|
|
155
|
+
// Framed bodies sit inside the `│` gutter; a plain board indents instead.
|
|
176
156
|
const block = (text: string) => (framed ? gutter(text) : indent(text));
|
|
177
157
|
const spacer = () => out.write(framed ? `${BAR}\n` : "\n");
|
|
178
158
|
|
|
179
159
|
let flushed = false;
|
|
180
160
|
if (shared) {
|
|
181
161
|
spacer();
|
|
182
|
-
out.write(`${block(shared)}\n`);
|
|
162
|
+
out.write(`${block(shared)}\n`);
|
|
183
163
|
flushed = true;
|
|
184
164
|
}
|
|
185
165
|
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
// diagnostic reads. A per-task header keeps each block attributable, except a
|
|
189
|
-
// single task (the row above already names it).
|
|
166
|
+
// Passing output is dimmed (proof-of-work that recedes); a failure stays bright
|
|
167
|
+
// so the diagnostic reads. Multi-row blocks get a per-task header to attribute them.
|
|
190
168
|
for (const b of blocks) {
|
|
191
169
|
const rest = shared ? stripLeadingLine(b.detail, shared) : b.detail;
|
|
192
170
|
if (!rest.trim()) continue; // only the shared command → nothing package-specific
|
|
@@ -201,8 +179,8 @@ function finish(rows: RowState[], out: NodeJS.WriteStream, framed: boolean, mult
|
|
|
201
179
|
flushed = true;
|
|
202
180
|
}
|
|
203
181
|
|
|
204
|
-
//
|
|
205
|
-
//
|
|
182
|
+
// A framed section closes with `└ summary`; a plain multi-row board with a bare
|
|
183
|
+
// summary line. One compact task needs neither — its row already showed the verdict.
|
|
206
184
|
if (framed) {
|
|
207
185
|
if (flushed || multi) spacer();
|
|
208
186
|
out.write(`${BAR_END} ${summary(rows)}\n`);
|
|
@@ -224,11 +202,7 @@ function stripLeadingLine(detail: string, line: string): string {
|
|
|
224
202
|
return detail.startsWith(line) ? detail.slice(line.length).replace(/^\n/, "") : detail;
|
|
225
203
|
}
|
|
226
204
|
|
|
227
|
-
/**
|
|
228
|
-
* The leading decoration for a row. Framed: `│` per row, or `┌` for a framed
|
|
229
|
-
* single task (status rides the opening corner). Unframed: a 2-space indent
|
|
230
|
-
* under the title for a multi-row board, nothing for a lone compact row.
|
|
231
|
-
*/
|
|
205
|
+
/** A row's leading decoration: `│` (framed multi), `┌` (framed single, status rides the corner), or a plain indent. */
|
|
232
206
|
function rowPrefix(framed: boolean, multi: boolean): string {
|
|
233
207
|
if (framed) return multi ? `${BAR} ` : `${BAR_START} `;
|
|
234
208
|
return multi ? " " : "";
|