flagstaff 0.0.1 → 0.2.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/LICENSE +21 -0
- package/README.md +372 -25
- package/THIRD-PARTY.md +44 -0
- package/dist/box.d.ts +22 -0
- package/dist/box.js +59 -0
- package/dist/boxen.d.ts +56 -0
- package/dist/boxen.js +251 -0
- package/dist/builtins.d.ts +13 -0
- package/dist/builtins.js +19 -0
- package/dist/cli-table3.d.ts +138 -0
- package/dist/cli-table3.js +785 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +128 -0
- package/dist/cursor.d.ts +13 -0
- package/dist/cursor.js +49 -0
- package/dist/import.d.ts +50 -0
- package/dist/import.js +20 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +8 -7
- package/dist/log-update.d.ts +29 -0
- package/dist/log-update.js +173 -0
- package/dist/loop.d.ts +30 -0
- package/dist/loop.js +67 -0
- package/dist/ora.d.ts +82 -0
- package/dist/ora.js +515 -0
- package/dist/plugin.d.ts +107 -0
- package/dist/plugin.js +163 -0
- package/dist/progress.d.ts +19 -0
- package/dist/progress.js +25 -0
- package/dist/projection.d.ts +46 -0
- package/dist/projection.js +83 -0
- package/dist/schema.json +1 -0
- package/dist/spinner.d.ts +9 -0
- package/dist/spinner.js +24 -0
- package/dist/spinners.json +1 -0
- package/dist/table.d.ts +18 -0
- package/dist/table.js +69 -0
- package/dist/tasks.d.ts +18 -0
- package/dist/tasks.js +31 -0
- package/package.json +77 -4
package/dist/table.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { width } from 'linegauge';
|
|
2
|
+
import { wrap } from 'linegauge/wrap';
|
|
3
|
+
import { heading, muted } from 'roundel/tokens';
|
|
4
|
+
const DEFAULT_WIDTH = 80;
|
|
5
|
+
const CHROME_PER_COLUMN = 3;
|
|
6
|
+
const CHROME_FIXED = 1;
|
|
7
|
+
const MIN_COLUMN = 3;
|
|
8
|
+
const V = '│';
|
|
9
|
+
const H = '─';
|
|
10
|
+
const CORNERS = { topLeft: '┌', topRight: '┐', bottomLeft: '└', bottomRight: '┘', top: '┬', bottom: '┴', left: '├', right: '┤', cross: '┼' };
|
|
11
|
+
const padTo = (line, cells, align) => {
|
|
12
|
+
const gap = ' '.repeat(Math.max(0, cells - width(line)));
|
|
13
|
+
return align === 'right' ? gap + line : line + gap;
|
|
14
|
+
};
|
|
15
|
+
function naturalWidths(rows, columns) {
|
|
16
|
+
const widths = Array.from({ length: columns }, () => 0);
|
|
17
|
+
for (const row of rows) {
|
|
18
|
+
for (let index = 0; index < columns; index += 1)
|
|
19
|
+
widths[index] = Math.max(widths[index] ?? 0, width(row[index] ?? ''));
|
|
20
|
+
}
|
|
21
|
+
return widths;
|
|
22
|
+
}
|
|
23
|
+
function fitWidths(natural, available) {
|
|
24
|
+
const widths = [...natural];
|
|
25
|
+
let total = widths.reduce((sum, w) => sum + w, 0);
|
|
26
|
+
while (total > available) {
|
|
27
|
+
let widest = 0;
|
|
28
|
+
for (let index = 1; index < widths.length; index += 1)
|
|
29
|
+
if ((widths[index] ?? 0) > (widths[widest] ?? 0))
|
|
30
|
+
widest = index;
|
|
31
|
+
if ((widths[widest] ?? 0) <= MIN_COLUMN)
|
|
32
|
+
break;
|
|
33
|
+
widths[widest] = (widths[widest] ?? 0) - 1;
|
|
34
|
+
total -= 1;
|
|
35
|
+
}
|
|
36
|
+
return widths;
|
|
37
|
+
}
|
|
38
|
+
function layoutRow(row, widths, align) {
|
|
39
|
+
const cells = widths.map((w, index) => wrap(row[index] ?? '', w, { hard: true, trim: false }).split('\n'));
|
|
40
|
+
const height = Math.max(1, ...cells.map((lines) => lines.length));
|
|
41
|
+
return Array.from({ length: height }, (_, line) => `${V} ${widths.map((w, index) => padTo(cells[index]?.[line] ?? '', w, align[index] ?? 'left')).join(` ${V} `)} ${V}`);
|
|
42
|
+
}
|
|
43
|
+
const rule = (widths, left, mid, right) => left + widths.map((w) => H.repeat(w + 2)).join(mid) + right;
|
|
44
|
+
export function table(rows, options = {}) {
|
|
45
|
+
const columns = Math.max(options.head?.length ?? 0, ...rows.map((row) => row.length), 1);
|
|
46
|
+
const total = options.width ?? DEFAULT_WIDTH;
|
|
47
|
+
const align = options.align ?? [];
|
|
48
|
+
const available = Math.max(columns * MIN_COLUMN, total - columns * CHROME_PER_COLUMN - CHROME_FIXED);
|
|
49
|
+
const all = options.head === undefined ? rows : [options.head, ...rows];
|
|
50
|
+
const widths = fitWidths(naturalWidths(all, columns), available);
|
|
51
|
+
const out = [rule(widths, CORNERS.topLeft, CORNERS.top, CORNERS.topRight)];
|
|
52
|
+
if (options.head !== undefined) {
|
|
53
|
+
out.push(...layoutRow(options.head.map((cell) => heading(cell)), widths, align), rule(widths, CORNERS.left, CORNERS.cross, CORNERS.right));
|
|
54
|
+
}
|
|
55
|
+
for (const row of rows)
|
|
56
|
+
out.push(...layoutRow(row, widths, align));
|
|
57
|
+
out.push(rule(widths, CORNERS.bottomLeft, CORNERS.bottom, CORNERS.bottomRight));
|
|
58
|
+
return out.join('\n');
|
|
59
|
+
}
|
|
60
|
+
export function tableComponent(options = {}) {
|
|
61
|
+
return {
|
|
62
|
+
name: 'table',
|
|
63
|
+
static: (state) => {
|
|
64
|
+
const head = state.head ?? options.head;
|
|
65
|
+
return state.rows.map((row) => (head === undefined ? row.join('\t') : row.map((cell, index) => `${head[index] ?? index}: ${cell}`).join(', '))).join('\n');
|
|
66
|
+
},
|
|
67
|
+
frame: (_t, state) => muted(table(state.rows, state.head === undefined ? options : { ...options, head: state.head })),
|
|
68
|
+
};
|
|
69
|
+
}
|
package/dist/tasks.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Component } from './plugin.js';
|
|
2
|
+
export type TaskStatus = 'pending' | 'running' | 'ok' | 'fail' | 'warn' | 'info';
|
|
3
|
+
export interface Task {
|
|
4
|
+
title: string;
|
|
5
|
+
/** `pending` when omitted. */
|
|
6
|
+
status?: TaskStatus;
|
|
7
|
+
/** A line under the title — the step's current detail, shown only on a terminal. */
|
|
8
|
+
detail?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TasksState {
|
|
11
|
+
tasks: Task[];
|
|
12
|
+
}
|
|
13
|
+
export interface TasksOptions {
|
|
14
|
+
/** The spinner style the running task is drawn with. Default `dots`. */
|
|
15
|
+
spinner?: string;
|
|
16
|
+
}
|
|
17
|
+
/** A task list: settled tasks one line each off a terminal, the whole list drawn on one. */
|
|
18
|
+
export declare function tasks({ spinner }?: TasksOptions): Component<TasksState>;
|
package/dist/tasks.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { error, hint, muted, ok, warn } from 'roundel/tokens';
|
|
2
|
+
import { glyph, lookupSpinner } from './plugin.js';
|
|
3
|
+
const PAINT = { ok, fail: error, warn, info: hint };
|
|
4
|
+
const PENDING = ' ';
|
|
5
|
+
const settled = (status) => status !== 'pending' && status !== 'running';
|
|
6
|
+
export function tasks({ spinner = 'dots' } = {}) {
|
|
7
|
+
const style = lookupSpinner(spinner);
|
|
8
|
+
const symbol = (status, t) => {
|
|
9
|
+
if (settled(status))
|
|
10
|
+
return PAINT[status](glyph(status));
|
|
11
|
+
if (status === 'pending')
|
|
12
|
+
return muted(PENDING);
|
|
13
|
+
const at = Math.floor(t / style.interval) % style.frames.length;
|
|
14
|
+
return style.frames[at] ?? '';
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
name: 'tasks',
|
|
18
|
+
interval: style.interval,
|
|
19
|
+
static: (state) => state.tasks
|
|
20
|
+
.filter((task) => settled(task.status ?? 'pending'))
|
|
21
|
+
.map((task) => `${PAINT[task.status](glyph(task.status))} ${task.title}`)
|
|
22
|
+
.join('\n'),
|
|
23
|
+
frame: (t, state) => state.tasks
|
|
24
|
+
.flatMap((task) => {
|
|
25
|
+
const status = task.status ?? 'pending';
|
|
26
|
+
const title = `${symbol(status, t)} ${status === 'pending' ? muted(task.title) : task.title}`;
|
|
27
|
+
return task.detail === undefined || status !== 'running' ? [title] : [title, ` ${muted(task.detail)}`];
|
|
28
|
+
})
|
|
29
|
+
.join('\n'),
|
|
30
|
+
};
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagstaff",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The staff the flag flies from. A terminal frame loop with a static projection for agents and screen readers, and a plugin host for spinners, progress, boxes and tables. Drop-in paths for ora, log-update, boxen and cli-table3. Zero dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
7
8
|
"engines": {
|
|
8
9
|
"node": ">=24"
|
|
9
10
|
},
|
|
@@ -12,17 +13,83 @@
|
|
|
12
13
|
"types": "./dist/index.d.ts",
|
|
13
14
|
"import": "./dist/index.js",
|
|
14
15
|
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./ora": {
|
|
18
|
+
"types": "./dist/ora.d.ts",
|
|
19
|
+
"import": "./dist/ora.js",
|
|
20
|
+
"default": "./dist/ora.js"
|
|
21
|
+
},
|
|
22
|
+
"./boxen": {
|
|
23
|
+
"types": "./dist/boxen.d.ts",
|
|
24
|
+
"import": "./dist/boxen.js",
|
|
25
|
+
"default": "./dist/boxen.js"
|
|
26
|
+
},
|
|
27
|
+
"./cli-table3": {
|
|
28
|
+
"types": "./dist/cli-table3.d.ts",
|
|
29
|
+
"import": "./dist/cli-table3.js",
|
|
30
|
+
"default": "./dist/cli-table3.js"
|
|
31
|
+
},
|
|
32
|
+
"./log-update": {
|
|
33
|
+
"types": "./dist/log-update.d.ts",
|
|
34
|
+
"import": "./dist/log-update.js",
|
|
35
|
+
"default": "./dist/log-update.js"
|
|
36
|
+
},
|
|
37
|
+
"./loop": {
|
|
38
|
+
"types": "./dist/loop.d.ts",
|
|
39
|
+
"import": "./dist/loop.js",
|
|
40
|
+
"default": "./dist/loop.js"
|
|
41
|
+
},
|
|
42
|
+
"./schema.json": "./dist/schema.json",
|
|
43
|
+
"./plugin": {
|
|
44
|
+
"types": "./dist/plugin.d.ts",
|
|
45
|
+
"import": "./dist/plugin.js",
|
|
46
|
+
"default": "./dist/plugin.js"
|
|
47
|
+
},
|
|
48
|
+
"./import": {
|
|
49
|
+
"types": "./dist/import.d.ts",
|
|
50
|
+
"import": "./dist/import.js",
|
|
51
|
+
"default": "./dist/import.js"
|
|
52
|
+
},
|
|
53
|
+
"./spinner": {
|
|
54
|
+
"types": "./dist/spinner.d.ts",
|
|
55
|
+
"import": "./dist/spinner.js",
|
|
56
|
+
"default": "./dist/spinner.js"
|
|
57
|
+
},
|
|
58
|
+
"./progress": {
|
|
59
|
+
"types": "./dist/progress.d.ts",
|
|
60
|
+
"import": "./dist/progress.js",
|
|
61
|
+
"default": "./dist/progress.js"
|
|
62
|
+
},
|
|
63
|
+
"./tasks": {
|
|
64
|
+
"types": "./dist/tasks.d.ts",
|
|
65
|
+
"import": "./dist/tasks.js",
|
|
66
|
+
"default": "./dist/tasks.js"
|
|
67
|
+
},
|
|
68
|
+
"./box": {
|
|
69
|
+
"types": "./dist/box.d.ts",
|
|
70
|
+
"import": "./dist/box.js",
|
|
71
|
+
"default": "./dist/box.js"
|
|
72
|
+
},
|
|
73
|
+
"./table": {
|
|
74
|
+
"types": "./dist/table.d.ts",
|
|
75
|
+
"import": "./dist/table.js",
|
|
76
|
+
"default": "./dist/table.js"
|
|
15
77
|
}
|
|
16
78
|
},
|
|
79
|
+
"bin": {
|
|
80
|
+
"flagstaff": "./dist/cli.js"
|
|
81
|
+
},
|
|
17
82
|
"files": [
|
|
18
83
|
"dist",
|
|
19
84
|
"!dist/**/*.map",
|
|
20
|
-
"!dist/**/*.test.*"
|
|
85
|
+
"!dist/**/*.test.*",
|
|
86
|
+
"THIRD-PARTY.md"
|
|
21
87
|
],
|
|
22
88
|
"scripts": {
|
|
23
|
-
"build": "tsc -p tsconfig.build.json",
|
|
89
|
+
"build": "tsc -p tsconfig.build.json && node ../../scripts/strip-comments.mjs dist",
|
|
24
90
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
25
91
|
"test": "vitest run --passWithNoTests",
|
|
92
|
+
"coverage": "vitest run --coverage.enabled",
|
|
26
93
|
"lint": "eslint src"
|
|
27
94
|
},
|
|
28
95
|
"repository": {
|
|
@@ -47,7 +114,13 @@
|
|
|
47
114
|
"cli-table3",
|
|
48
115
|
"plugins"
|
|
49
116
|
],
|
|
117
|
+
"dependencies": {
|
|
118
|
+
"linegauge": "^0.1.0",
|
|
119
|
+
"roundel": "^0.2.0"
|
|
120
|
+
},
|
|
50
121
|
"devDependencies": {
|
|
51
|
-
"
|
|
122
|
+
"cli-boxes": "^4.0.1",
|
|
123
|
+
"cli-spinners": "^3.2.0",
|
|
124
|
+
"vitest": "^5.0.0"
|
|
52
125
|
}
|
|
53
126
|
}
|