reslop 0.2.6 → 0.3.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/CHANGELOG.md +27 -1
- package/README.md +16 -14
- package/bin/reslop.js +8 -5
- package/lib/agents/agents.js +504 -0
- package/lib/agents/allow.js +71 -0
- package/lib/agents/allow.json +16 -0
- package/lib/agents/models.js +460 -0
- package/lib/api.js +15 -12
- package/lib/cli.js +61 -66
- package/lib/{files.js → common/files.js} +51 -52
- package/lib/common/find.js +61 -0
- package/lib/common/format.js +101 -0
- package/lib/{utilities.js → common/process.js} +7 -95
- package/lib/common/tasks.json +27 -0
- package/lib/common/utilities.js +112 -0
- package/lib/dashboard/git.js +9 -16
- package/lib/dashboard/model.js +117 -51
- package/lib/dashboard/npm.js +133 -32
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +41 -31
- package/lib/dashboard/workspace.js +151 -0
- package/lib/{deps.js → deps/deps.js} +31 -45
- package/lib/{deps-diff.js → deps/diff.js} +35 -25
- package/lib/{manifest.js → deps/manifest.js} +47 -30
- package/lib/diff/align.js +1 -2
- package/lib/diff/diff.js +2 -2
- package/lib/diff/display.js +29 -5
- package/lib/diff/inline.js +10 -14
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +39 -3
- package/lib/diff/unit.js +45 -16
- package/lib/{git-branches.js → git/branches.js} +53 -48
- package/lib/{git-deps.js → git/deps.js} +123 -95
- package/lib/git/git.js +71 -0
- package/lib/git/worktree.js +475 -0
- package/lib/highlight/bash.js +1 -2
- package/lib/highlight/csv.js +1 -2
- package/lib/highlight/dot.js +1 -2
- package/lib/{highlight.js → highlight/highlight.js} +42 -11
- package/lib/highlight/html.js +20 -18
- package/lib/highlight/json.js +1 -2
- package/lib/highlight/log.js +1 -2
- package/lib/highlight/txt.js +1 -2
- package/lib/{session → input}/actions.js +240 -71
- package/lib/{editor.js → input/editor.js} +7 -8
- package/lib/{keys.js → input/keys.js} +36 -39
- package/lib/input/line-edit.js +77 -0
- package/lib/input/menu-pick.js +70 -0
- package/lib/render/agents.js +640 -0
- package/lib/render/branches.js +4 -5
- package/lib/render/commits.js +54 -64
- package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
- package/lib/render/dashboard/blocks.js +490 -0
- package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
- package/lib/render/diff.js +166 -45
- package/lib/render/field.js +131 -0
- package/lib/render/files.js +22 -8
- package/lib/render/footer.js +383 -0
- package/lib/render/frame.js +127 -25
- package/lib/render/header.js +176 -0
- package/lib/render/log.js +319 -0
- package/lib/render/menu.js +60 -0
- package/lib/render/notes.js +29 -13
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +89 -0
- package/lib/render/primitives.js +109 -16
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +214 -0
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +19 -61
- package/lib/render/tiles.js +203 -47
- package/lib/render/unit.js +32 -13
- package/lib/{report-model.js → report/model.js} +4 -6
- package/lib/{report-parse.js → report/parse.js} +9 -14
- package/lib/{report-render.js → report/render.js} +46 -14
- package/lib/{report-run.js → report/run.js} +42 -30
- package/lib/{report-test.js → report/test.js} +56 -55
- package/lib/{report-tools.js → report/tools.js} +24 -33
- package/lib/review/agent-instructions.md +9 -0
- package/lib/review/format.js +426 -0
- package/lib/{review-merge.js → review/merge.js} +9 -8
- package/lib/review/review.js +190 -0
- package/lib/review/store.js +281 -0
- package/lib/review/templates.js +83 -0
- package/lib/runs/commands.js +14 -0
- package/lib/runs/logs.js +299 -0
- package/lib/runs/output.js +174 -0
- package/lib/runs/process.js +113 -0
- package/lib/{runs.js → runs/runs.js} +84 -36
- package/lib/runs/scripts.js +150 -0
- package/lib/session/agents/agents.js +958 -0
- package/lib/session/agents/job.js +320 -0
- package/lib/session/agents/jobs.js +355 -0
- package/lib/session/agents/options.js +153 -0
- package/lib/session/agents/plans.js +83 -0
- package/lib/session/branches.js +36 -44
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +122 -56
- package/lib/session/compose.js +197 -146
- package/lib/session/dashboard.js +183 -37
- package/lib/session/dispatch.js +120 -22
- package/lib/session/files-pane.js +60 -24
- package/lib/session/find.js +81 -0
- package/lib/session/import.js +82 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +96 -66
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +8 -3
- package/lib/session/npm/history.js +249 -0
- package/lib/session/npm/npm.js +815 -0
- package/lib/session/npm/packages.js +150 -0
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +216 -10
- package/lib/session/prefs.js +112 -0
- package/lib/session/reads.js +109 -0
- package/lib/session/review.js +28 -10
- package/lib/session/run-panel.js +40 -0
- package/lib/{session.js → session/session.js} +166 -59
- package/lib/session/tasks.js +321 -66
- package/lib/session/templates.js +1 -1
- package/lib/session/terminal.js +94 -12
- package/lib/session/unit.js +35 -30
- package/lib/session/update.js +194 -6
- package/lib/session/view.js +72 -59
- package/lib/session/workspace.js +416 -0
- package/lib/{capabilities.js → source/capabilities.js} +3 -12
- package/lib/{github.js → source/github.js} +102 -32
- package/lib/{gitlab.js → source/gitlab.js} +87 -10
- package/lib/{remote.js → source/remote.js} +144 -40
- package/lib/source/review-threads.graphql +20 -0
- package/lib/source/source.js +74 -0
- package/lib/term/ansi.js +396 -0
- package/lib/{clipboard.js → term/clipboard.js} +3 -6
- package/lib/{select.js → term/select.js} +2 -2
- package/lib/term/themes/dark.json +122 -0
- package/lib/term/themes/light.json +122 -0
- package/lib/term/wide.txt +21 -0
- package/lib/{wrap.js → term/wrap.js} +4 -4
- package/package.json +2 -2
- package/lib/ansi.js +0 -505
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -437
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -628
- package/lib/render/dash-blocks.js +0 -245
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/review.js +0 -744
- package/lib/session/accessors.js +0 -26
- package/lib/session/edit-click.js +0 -57
- package/lib/session/npm.js +0 -452
- package/lib/session/progress.js +0 -41
- package/lib/source.js +0 -36
- package/lib/update.js +0 -192
- /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { shortAge, num, size } = require('../../common/format.js');
|
|
4
|
+
const { TOP_PACKAGES } = require('../../dashboard/npm.js');
|
|
5
|
+
const { clamp } = require('../../common/utilities.js');
|
|
6
|
+
const { visibleWidth, truncateVisible } = require('../../term/ansi.js');
|
|
7
|
+
const { minuteOf } = require('../../dashboard/model.js');
|
|
8
|
+
const { seg, barSegments, spinner } = require('../tiles.js');
|
|
9
|
+
const table = require('../table.js');
|
|
10
|
+
const { delta, waiting, notice, cell } = table;
|
|
11
|
+
const { cellSegs, flexCell, tableLines, pickGroups, labelOf } = table;
|
|
12
|
+
const { titleAside, captionOver } = table;
|
|
13
|
+
const { cmpText, hasRange, inRange } = require('../../deps/manifest.js');
|
|
14
|
+
|
|
15
|
+
const BAR_W = 6;
|
|
16
|
+
const FOLDER = '📁';
|
|
17
|
+
const MARK_GAP = 1;
|
|
18
|
+
const ACTIVITY_LABEL = 'activity';
|
|
19
|
+
const PULSE = '∙';
|
|
20
|
+
const TRACE = '─';
|
|
21
|
+
const ACTIVITY_MARK = [
|
|
22
|
+
['fail', '⨯'],
|
|
23
|
+
['commit', '◉'],
|
|
24
|
+
['pass', '*'],
|
|
25
|
+
['abort', '!'],
|
|
26
|
+
['branch', '⎇'],
|
|
27
|
+
['rebase', '↻'],
|
|
28
|
+
['npm', '✦'],
|
|
29
|
+
];
|
|
30
|
+
const MARK_TONE = {
|
|
31
|
+
'⨯': 'error',
|
|
32
|
+
'◉': 'key',
|
|
33
|
+
'*': 'add',
|
|
34
|
+
'!': 'error',
|
|
35
|
+
'⎇': 'sha',
|
|
36
|
+
'↻': 'sha',
|
|
37
|
+
'✦': 'key',
|
|
38
|
+
'+': 'add',
|
|
39
|
+
'-': 'del',
|
|
40
|
+
};
|
|
41
|
+
const UNKNOWN = '…';
|
|
42
|
+
|
|
43
|
+
const share = (value, total) => (total > 0 ? value / total : 0);
|
|
44
|
+
|
|
45
|
+
const markCell = (isDir) => cell(isDir ? FOLDER : '');
|
|
46
|
+
|
|
47
|
+
const nameCell = (key, isDir, hot = false) => {
|
|
48
|
+
const name = cell(labelOf(key, isDir), hot ? 'warn' : 'sha', 'l', hot);
|
|
49
|
+
name.gap = MARK_GAP;
|
|
50
|
+
return name;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const fileName = (key, isDir, hot) => {
|
|
54
|
+
const name = nameCell(key, isDir, hot);
|
|
55
|
+
name.flex = true;
|
|
56
|
+
name.keep = true;
|
|
57
|
+
return name;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const fileRow = (group, isDir, data) => {
|
|
61
|
+
const hot = (isDir ? data.hot.dirs : data.hot.exts).has(group.key);
|
|
62
|
+
const ratio = share(group.bytes, data.total.bytes);
|
|
63
|
+
return [
|
|
64
|
+
markCell(isDir),
|
|
65
|
+
fileName(group.key, isDir, hot),
|
|
66
|
+
cell(`${group.files}`, 'muted', 'r'),
|
|
67
|
+
cell(size(group.bytes), 'text', 'r'),
|
|
68
|
+
cell(num(group.lines), 'muted', 'r'),
|
|
69
|
+
cellSegs(barSegments(ratio, BAR_W, 'sha')),
|
|
70
|
+
];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const filesTotal = (data, ctx) => {
|
|
74
|
+
const { total } = data;
|
|
75
|
+
const name = cell('');
|
|
76
|
+
name.flex = true;
|
|
77
|
+
name.gap = MARK_GAP;
|
|
78
|
+
const row = [
|
|
79
|
+
cell(''),
|
|
80
|
+
name,
|
|
81
|
+
cell(`${total.files}`, 'text', 'r', true),
|
|
82
|
+
cell(size(total.bytes), 'text', 'r', true),
|
|
83
|
+
cell(num(total.lines), 'text', 'r', true),
|
|
84
|
+
cell(''),
|
|
85
|
+
];
|
|
86
|
+
const moved = data.delta ? data.delta.lines : 0;
|
|
87
|
+
const change = delta(moved, ctx.now, data.delta?.at);
|
|
88
|
+
if (change) row.push(cellSegs([change]));
|
|
89
|
+
return row;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const filesBlock = (model, width, height, ctx, tile) => {
|
|
93
|
+
const data = model.files;
|
|
94
|
+
if (!data.ready) return waiting('scanning…');
|
|
95
|
+
if (!data.total.files) return notice('no files');
|
|
96
|
+
const picked = pickGroups(data.dirs, data.exts, height - 1);
|
|
97
|
+
const labels = [
|
|
98
|
+
cell(''),
|
|
99
|
+
cell(''),
|
|
100
|
+
cell('files', 'caption', 'r'),
|
|
101
|
+
cell('size', 'caption', 'r'),
|
|
102
|
+
cell('lines', 'caption', 'r'),
|
|
103
|
+
cell('share', 'caption', 'r'),
|
|
104
|
+
];
|
|
105
|
+
const rows = [labels];
|
|
106
|
+
for (const group of picked.dirs) rows.push(fileRow(group, true, data));
|
|
107
|
+
for (const group of picked.exts) rows.push(fileRow(group, false, data));
|
|
108
|
+
rows.push(filesTotal(data, ctx));
|
|
109
|
+
const lines = tableLines(rows, width);
|
|
110
|
+
return {
|
|
111
|
+
titleLine: captionOver(tile, lines[0]),
|
|
112
|
+
lines: lines.slice(1, -1),
|
|
113
|
+
footer: lines.at(-1),
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const ratio = (sign, part, total) => `${sign}${part}/${total}`;
|
|
118
|
+
|
|
119
|
+
const changeCells = (group, bold = false) => [
|
|
120
|
+
cell(ratio('+', group.stagedAdded, group.added), 'add', 'r', bold),
|
|
121
|
+
cell(ratio('-', group.stagedRemoved, group.removed), 'del', 'r', bold),
|
|
122
|
+
cell(`${group.staged}/${group.remaining}`, 'text', 'r', bold),
|
|
123
|
+
cell(shortAge(group.date), 'muted', 'r'),
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const diffRow = (group, isDir) => [
|
|
127
|
+
markCell(isDir),
|
|
128
|
+
nameCell(group.key, isDir),
|
|
129
|
+
...changeCells(group),
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
const diffsHeader = (data, ctx) => {
|
|
133
|
+
const name = cell('');
|
|
134
|
+
name.gap = MARK_GAP;
|
|
135
|
+
const row = [cell(''), name, ...changeCells(data.totals, true)];
|
|
136
|
+
const moved = data.delta ? data.delta.added + data.delta.removed : 0;
|
|
137
|
+
const change = delta(moved, ctx.now, data.delta?.at);
|
|
138
|
+
if (change) row[row.length - 1] = cellSegs([change], 'r');
|
|
139
|
+
return row;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const heatIndex = (level) => {
|
|
143
|
+
if (level >= 301) return 4;
|
|
144
|
+
if (level >= 81) return 3;
|
|
145
|
+
if (level >= 16) return 2;
|
|
146
|
+
if (level >= 1) return 1;
|
|
147
|
+
return 0;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const markGlyph = (marks) => {
|
|
151
|
+
const list = marks ?? [];
|
|
152
|
+
for (const [kind, glyph] of ACTIVITY_MARK) {
|
|
153
|
+
if (list.includes(kind)) return glyph;
|
|
154
|
+
}
|
|
155
|
+
return '';
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const diffGlyph = (bucket) => {
|
|
159
|
+
if (!bucket) return '';
|
|
160
|
+
const added = bucket.added || 0;
|
|
161
|
+
const removed = bucket.removed || 0;
|
|
162
|
+
if (removed > added) return '-';
|
|
163
|
+
if (added > 0 || removed > 0 || bucket.level > 0) return '+';
|
|
164
|
+
return '';
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const pulseGlyph = (level) => {
|
|
168
|
+
const index = heatIndex(level);
|
|
169
|
+
if (index >= 4) return '●';
|
|
170
|
+
if (index >= 3) return '•';
|
|
171
|
+
return '◦';
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const activityCell = (bucket, blink, pulse) => {
|
|
175
|
+
const level = bucket ? bucket.level : 0;
|
|
176
|
+
const glyph = markGlyph(bucket ? bucket.marks : null);
|
|
177
|
+
const mark = glyph || diffGlyph(bucket);
|
|
178
|
+
const phase = blink ? pulseGlyph(level) : PULSE;
|
|
179
|
+
const ch = pulse ? phase : mark || TRACE;
|
|
180
|
+
if (blink) return seg(ch, 'key', true);
|
|
181
|
+
if (!pulse && mark) {
|
|
182
|
+
return seg(ch, MARK_TONE[mark] || 'sha', Boolean(glyph));
|
|
183
|
+
}
|
|
184
|
+
return seg(ch, 'sha');
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const activityCells = (activity, ctx, width) => {
|
|
188
|
+
const minutes = activity && activity.minutes ? activity.minutes : [];
|
|
189
|
+
const byAt = new Map();
|
|
190
|
+
for (const row of minutes) {
|
|
191
|
+
byAt.set(row.at, row);
|
|
192
|
+
}
|
|
193
|
+
const current = minuteOf(ctx.now);
|
|
194
|
+
const flash = Boolean(activity && activity.live);
|
|
195
|
+
const on = Math.abs(ctx.frame ?? 0) % 2 === 1;
|
|
196
|
+
const body = Math.max(0, width - 2);
|
|
197
|
+
const cells = [];
|
|
198
|
+
for (let i = 0; i < body; i++) {
|
|
199
|
+
const at = current - (body - 1 - i);
|
|
200
|
+
const last = i === body - 1;
|
|
201
|
+
const pulse = flash && last;
|
|
202
|
+
const blink = pulse && on;
|
|
203
|
+
cells.push(activityCell(byAt.get(at), blink, pulse));
|
|
204
|
+
}
|
|
205
|
+
return [seg(' ', 'muted'), ...cells, seg(' ', 'muted')];
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const activityFooter = (data, width, ctx) => {
|
|
209
|
+
const lineWidth = width + 1;
|
|
210
|
+
const room = lineWidth - ACTIVITY_LABEL.length;
|
|
211
|
+
if (room < 3) return null;
|
|
212
|
+
const cells = activityCells(data.activity, ctx, room);
|
|
213
|
+
return [seg(ACTIVITY_LABEL, 'sha'), ...cells];
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const diffsBlock = (model, width, height, ctx, tile) => {
|
|
217
|
+
const data = model.diffs;
|
|
218
|
+
if (!data.files) return notice('working tree clean');
|
|
219
|
+
const footer = activityFooter(data, width, ctx);
|
|
220
|
+
const room = footer ? height - 1 : height;
|
|
221
|
+
const picked = pickGroups(data.dirs, data.exts, room);
|
|
222
|
+
const rows = [diffsHeader(data, ctx)];
|
|
223
|
+
for (const group of picked.dirs) rows.push(diffRow(group, true));
|
|
224
|
+
for (const group of picked.exts) rows.push(diffRow(group, false));
|
|
225
|
+
const lines = tableLines(rows, width, 2, true);
|
|
226
|
+
const titleLine = captionOver(tile, lines[0]);
|
|
227
|
+
const body = lines.slice(1);
|
|
228
|
+
const footerFlush = true;
|
|
229
|
+
return { titleLine, lines: body, footer, footerFlush };
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const compactSize = (bytes) => size(bytes).replaceAll(' ', '');
|
|
233
|
+
|
|
234
|
+
const alertSegs = (value, tone, mark) => {
|
|
235
|
+
if (!value) return [];
|
|
236
|
+
return [seg(' '), seg(mark, tone), seg(` ${value}`, tone, true)];
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const npmAside = (data) => {
|
|
240
|
+
const modules = data.modules;
|
|
241
|
+
const bytes = compactSize(modules.bytes);
|
|
242
|
+
const counts = `deps:${data.deps} dev:${data.dev}`;
|
|
243
|
+
const text = `${counts} all:${modules.count}/${bytes}`;
|
|
244
|
+
return [
|
|
245
|
+
seg(text, 'caption'),
|
|
246
|
+
...alertSegs(data.audit, 'error', '🚨'),
|
|
247
|
+
...alertSegs(data.outdated, 'warn', '⚠️'),
|
|
248
|
+
];
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const npmStatus = (data) => {
|
|
252
|
+
if (!data || !data.ready || !data.hasManifest) return '';
|
|
253
|
+
const modules = data.modules || { count: 0, bytes: 0 };
|
|
254
|
+
let text = `deps: ${data.deps} dev: ${data.dev}`;
|
|
255
|
+
text += ` all: ${modules.count} (${size(modules.bytes)})`;
|
|
256
|
+
if (data.audit) text += ` 🚨 ${data.audit}`;
|
|
257
|
+
if (data.outdated) text += ` ⚠️ ${data.outdated}`;
|
|
258
|
+
return text;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const vulnTone = (text, pkg) => {
|
|
262
|
+
if (!pkg.audit || !text || text === UNKNOWN) return '';
|
|
263
|
+
const fix = pkg.fix || '';
|
|
264
|
+
if (fix) {
|
|
265
|
+
const cmp = cmpText(text, fix);
|
|
266
|
+
if (cmp === null) return text === pkg.current ? 'error' : '';
|
|
267
|
+
return cmp < 0 ? 'error' : 'add';
|
|
268
|
+
}
|
|
269
|
+
const range = pkg.range || '';
|
|
270
|
+
if (hasRange(range)) {
|
|
271
|
+
if (inRange(text, range)) return 'error';
|
|
272
|
+
const cmp = cmpText(text, pkg.current);
|
|
273
|
+
if (cmp !== null && cmp > 0) return 'add';
|
|
274
|
+
}
|
|
275
|
+
return text === pkg.current ? 'error' : '';
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const isOutdated = (pkg) => {
|
|
279
|
+
const current = pkg.current || '';
|
|
280
|
+
const wanted = pkg.wanted || '';
|
|
281
|
+
const latest = pkg.latest || '';
|
|
282
|
+
if (!current || current === UNKNOWN) return false;
|
|
283
|
+
if (wanted && wanted !== UNKNOWN && wanted !== current) return true;
|
|
284
|
+
return Boolean(latest && latest !== UNKNOWN && latest !== current);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const fixesOutdated = (text, pkg) => {
|
|
288
|
+
if (!isOutdated(pkg) || !text || text === UNKNOWN) return false;
|
|
289
|
+
const current = pkg.current || '';
|
|
290
|
+
const wanted = pkg.wanted || '';
|
|
291
|
+
const latest = pkg.latest || '';
|
|
292
|
+
if (text === wanted && wanted !== current) return true;
|
|
293
|
+
return text === latest && latest !== current;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const versionTone = (text, pkg) => {
|
|
297
|
+
const vuln = vulnTone(text, pkg);
|
|
298
|
+
if (vuln) return vuln;
|
|
299
|
+
if (fixesOutdated(text, pkg)) return 'add';
|
|
300
|
+
if (text === pkg.current && isOutdated(pkg)) return 'warn';
|
|
301
|
+
return 'muted';
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const packageMark = (pkg, marks) => {
|
|
305
|
+
if (!marks) return cell('');
|
|
306
|
+
const segs = [];
|
|
307
|
+
if (pkg.audit) segs.push(seg('🛑 vulnerability detected', 'error'));
|
|
308
|
+
if (isOutdated(pkg)) {
|
|
309
|
+
if (segs.length) segs.push(seg(' '));
|
|
310
|
+
segs.push(seg('⚠️ outdated', 'warn'));
|
|
311
|
+
}
|
|
312
|
+
if (!segs.length) return cell('');
|
|
313
|
+
return cellSegs(segs);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const nameTone = (pkg) => {
|
|
317
|
+
if (pkg.transitive) return 'pkg';
|
|
318
|
+
if (pkg.dev) return 'dev';
|
|
319
|
+
return 'dep';
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const packageName = (pkg) => {
|
|
323
|
+
const segs = [seg(pkg.name, nameTone(pkg))];
|
|
324
|
+
if (pkg.chain) segs.push(seg(` 🢒 ${pkg.chain}`, 'chain'));
|
|
325
|
+
const align = 'l';
|
|
326
|
+
const flex = true;
|
|
327
|
+
const keep = true;
|
|
328
|
+
const measure = visibleWidth(pkg.name);
|
|
329
|
+
return { segs, align, flex, keep, measure };
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const versionCell = (text, pkg) => cell(text, versionTone(text, pkg), 'r');
|
|
333
|
+
|
|
334
|
+
const packageHead = () => {
|
|
335
|
+
const title = flexCell('name');
|
|
336
|
+
title.keep = true;
|
|
337
|
+
return [
|
|
338
|
+
title,
|
|
339
|
+
cell(''),
|
|
340
|
+
cell('current', 'muted', 'r'),
|
|
341
|
+
cell('wanted', 'muted', 'r'),
|
|
342
|
+
cell('latest', 'muted', 'r'),
|
|
343
|
+
cell('size', 'muted', 'r'),
|
|
344
|
+
];
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const packageRow = (pkg, marks) => {
|
|
348
|
+
const current = pkg.current || UNKNOWN;
|
|
349
|
+
const wanted = pkg.wanted || UNKNOWN;
|
|
350
|
+
const latest = pkg.latest || UNKNOWN;
|
|
351
|
+
return [
|
|
352
|
+
packageName(pkg),
|
|
353
|
+
packageMark(pkg, marks),
|
|
354
|
+
versionCell(current, pkg),
|
|
355
|
+
versionCell(wanted, pkg),
|
|
356
|
+
versionCell(latest, pkg),
|
|
357
|
+
cell(size(pkg.bytes), 'muted', 'r'),
|
|
358
|
+
];
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const packageLines = (packages, width, limit) => {
|
|
362
|
+
const all = limit === null;
|
|
363
|
+
const headed = packages.length > 0 && (all || limit > 1);
|
|
364
|
+
const head = headed ? 1 : 0;
|
|
365
|
+
const room = all ? packages.length : Math.max(0, limit - head);
|
|
366
|
+
const shown = packages.slice(0, room);
|
|
367
|
+
const rows = shown.map((pkg) => packageRow(pkg, all));
|
|
368
|
+
if (headed) rows.unshift(packageHead());
|
|
369
|
+
return tableLines(rows, width);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const npmContent = (data, width, limit, ctx, tile) => {
|
|
373
|
+
if (!data || !data.ready) return waiting('reading package.json…');
|
|
374
|
+
if (!data.hasManifest) return waiting('no package.json');
|
|
375
|
+
const badge = data.running ? [seg(spinner(ctx.frame), 'warn', true)] : [];
|
|
376
|
+
const titleLine = titleAside(tile, npmAside(data), width, badge);
|
|
377
|
+
const listed = data.modules && data.modules.packages;
|
|
378
|
+
const packages = listed ? listed : [];
|
|
379
|
+
if (!packages.length) return notice('no packages', titleLine);
|
|
380
|
+
const short = limit === null ? packages : packages.slice(0, TOP_PACKAGES);
|
|
381
|
+
return { titleLine, lines: packageLines(short, width, limit) };
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const npmBlock = (model, width, height, ctx, tile) =>
|
|
385
|
+
npmContent(model.npm, width, height, ctx, tile);
|
|
386
|
+
|
|
387
|
+
const NAME_GAP = 2;
|
|
388
|
+
const RATIO_GAP = 1;
|
|
389
|
+
|
|
390
|
+
const ratioLabel = (done, total) => `${done}/${total}`;
|
|
391
|
+
|
|
392
|
+
const taskSpecs = (data) => {
|
|
393
|
+
const rows = [];
|
|
394
|
+
for (const kind of data.kinds) {
|
|
395
|
+
const { title, done, total } = kind;
|
|
396
|
+
const bold = false;
|
|
397
|
+
const row = { title, done, total, bold };
|
|
398
|
+
rows.push(row);
|
|
399
|
+
}
|
|
400
|
+
const title = 'Total';
|
|
401
|
+
const titleTone = 'add';
|
|
402
|
+
const { done, total } = data;
|
|
403
|
+
const bold = true;
|
|
404
|
+
const row = { title, titleTone, done, total, bold };
|
|
405
|
+
rows.push(row);
|
|
406
|
+
return rows;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const taskRows = (specs, room) => {
|
|
410
|
+
if (room < 1) return [];
|
|
411
|
+
const total = specs.at(-1);
|
|
412
|
+
const kinds = specs.slice(0, -1).slice(0, room - 1);
|
|
413
|
+
return [...kinds, total];
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const planAside = (tile, plan, width) => {
|
|
417
|
+
if (!plan) return [];
|
|
418
|
+
const room = Math.max(0, width - tile.title.length - 1);
|
|
419
|
+
const shown = truncateVisible(plan, room);
|
|
420
|
+
return shown ? [seg(shown, 'caption')] : [];
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const widest = (rows, read) => {
|
|
424
|
+
let width = 0;
|
|
425
|
+
for (const row of rows) {
|
|
426
|
+
const size = read(row).length;
|
|
427
|
+
if (size > width) width = size;
|
|
428
|
+
}
|
|
429
|
+
return width;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const taskBarWidth = (width, nameW, ratioW) => {
|
|
433
|
+
const room = width - nameW - ratioW - NAME_GAP - RATIO_GAP;
|
|
434
|
+
return room > 0 ? room : 0;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const taskBarSegments = (ratio, width) => {
|
|
438
|
+
if (width < 1) return [];
|
|
439
|
+
if (width < 3) return [seg(' '.repeat(width), 'muted')];
|
|
440
|
+
const inner = width - 2;
|
|
441
|
+
const clamped = clamp(Number.isFinite(ratio) ? ratio : 0, 0, 1);
|
|
442
|
+
const on = Math.round(clamped * inner);
|
|
443
|
+
const off = inner - on;
|
|
444
|
+
const parts = [seg(' ', 'muted')];
|
|
445
|
+
if (on) parts.push(seg(TRACE.repeat(on), 'add'));
|
|
446
|
+
if (off) parts.push(seg(' '.repeat(off), 'muted'));
|
|
447
|
+
parts.push(seg(' ', 'muted'));
|
|
448
|
+
return parts;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const taskLine = (row, barW) => {
|
|
452
|
+
const tone = row.bold ? 'text' : 'muted';
|
|
453
|
+
const titleTone = row.titleTone ?? 'text';
|
|
454
|
+
const titleBold = row.titleTone ? false : row.bold;
|
|
455
|
+
const line = [cell(row.title, titleTone, 'l', titleBold)];
|
|
456
|
+
if (barW > 0) {
|
|
457
|
+
const ratio = share(row.done, row.total);
|
|
458
|
+
line.push(cellSegs(taskBarSegments(ratio, barW)));
|
|
459
|
+
}
|
|
460
|
+
const count = cell(ratioLabel(row.done, row.total), tone, 'r', row.bold);
|
|
461
|
+
count.gap = RATIO_GAP;
|
|
462
|
+
line.push(count);
|
|
463
|
+
return line;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const tasksBlock = (model, width, height, ctx, tile) => {
|
|
467
|
+
const data = model.tasks;
|
|
468
|
+
if (!data.total) {
|
|
469
|
+
const aside = planAside(tile, data.plan, width);
|
|
470
|
+
return notice('no tasks', titleAside(tile, aside, width));
|
|
471
|
+
}
|
|
472
|
+
const specs = taskSpecs(data);
|
|
473
|
+
const nameW = widest(specs, (row) => row.title);
|
|
474
|
+
const ratioW = widest(specs, (row) => ratioLabel(row.done, row.total));
|
|
475
|
+
const barW = taskBarWidth(width, nameW, ratioW);
|
|
476
|
+
const visible = taskRows(specs, Math.max(0, height));
|
|
477
|
+
const rows = visible.map((row) => taskLine(row, barW));
|
|
478
|
+
const lines = tableLines(rows, width, NAME_GAP);
|
|
479
|
+
const aside = planAside(tile, data.plan, width);
|
|
480
|
+
return { titleLine: titleAside(tile, aside, width), lines };
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
module.exports = {
|
|
484
|
+
filesBlock,
|
|
485
|
+
diffsBlock,
|
|
486
|
+
npmBlock,
|
|
487
|
+
npmContent,
|
|
488
|
+
npmStatus,
|
|
489
|
+
tasksBlock,
|
|
490
|
+
};
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const tiles = require('./tiles.js');
|
|
3
|
+
const { paintBodyFill, paneResult } = require('../primitives.js');
|
|
4
|
+
const tiles = require('../tiles.js');
|
|
6
5
|
const { layoutTiles, paintTile, composeBands, tileHits, pageFill } = tiles;
|
|
7
|
-
const blocks = require('./
|
|
6
|
+
const blocks = require('./blocks.js');
|
|
8
7
|
const { filesBlock, diffsBlock, npmBlock, tasksBlock } = blocks;
|
|
9
|
-
const activity = require('./
|
|
8
|
+
const activity = require('./activity.js');
|
|
10
9
|
const { commitsBlock, branchesBlock, runsBlock } = activity;
|
|
10
|
+
const { agentsBlock } = require('../agents.js');
|
|
11
|
+
const { DASH_TILES } = require('../../input/actions.js');
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
const TILE_VIEW = {
|
|
14
|
+
files: { rank: 3, size: 'normal', build: filesBlock },
|
|
15
|
+
diffs: { rank: 0, size: 'normal', build: diffsBlock },
|
|
16
|
+
tasks: { rank: 4, size: 'normal', build: tasksBlock },
|
|
17
|
+
branches: { rank: 5, size: 'wide', build: branchesBlock },
|
|
18
|
+
commits: { rank: 2, size: 'wide', build: commitsBlock },
|
|
19
|
+
run: { rank: 1, size: 'normal', build: runsBlock },
|
|
20
|
+
npm: { rank: 6, size: 'normal', build: npmBlock },
|
|
21
|
+
agents: { rank: 7, size: 'normal', build: agentsBlock },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const TILES = DASH_TILES.map(({ id, key }) => {
|
|
25
|
+
const title = id;
|
|
26
|
+
const view = TILE_VIEW[id];
|
|
27
|
+
return { id, key, title, ...view };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const TILE_BY_ID = Object.fromEntries(TILES.map((tile) => [tile.id, tile]));
|
|
28
31
|
const MARGIN_MIN_H = 12;
|
|
29
32
|
|
|
30
33
|
const paintTiles = (model, bands, color) => {
|
|
@@ -32,7 +35,7 @@ const paintTiles = (model, bands, color) => {
|
|
|
32
35
|
const ctx = { now: model.now, frame: model.frame };
|
|
33
36
|
for (const band of bands) {
|
|
34
37
|
for (const place of band.tiles) {
|
|
35
|
-
const tile = TILE_BY_ID
|
|
38
|
+
const tile = TILE_BY_ID[place.id];
|
|
36
39
|
const inner = Math.max(0, place.w - 2);
|
|
37
40
|
const room = Math.max(0, band.h - 1);
|
|
38
41
|
const content = tile.build(model, inner, room, ctx, tile);
|