reslop 0.2.7 → 0.3.1
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 +30 -1
- package/README.md +16 -14
- package/bin/reslop.js +8 -5
- package/lib/agents/agents.js +573 -0
- package/lib/agents/allow.js +71 -0
- package/lib/agents/allow.json +17 -0
- package/lib/agents/models.js +467 -0
- package/lib/api.js +15 -12
- package/lib/cli.js +43 -68
- package/lib/{files.js → common/files.js} +48 -55
- package/lib/{find.js → common/find.js} +1 -1
- 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 +90 -49
- package/lib/dashboard/npm.js +17 -13
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +36 -31
- package/lib/{workspace.js → dashboard/workspace.js} +23 -38
- 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} +151 -95
- package/lib/git/git.js +71 -0
- package/lib/git/worktree.js +504 -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 +114 -48
- package/lib/{editor.js → input/editor.js} +16 -8
- package/lib/{keys.js → input/keys.js} +24 -45
- package/lib/input/line-edit.js +78 -0
- package/lib/input/menu-pick.js +70 -0
- package/lib/render/agents.js +642 -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/{dash-blocks.js → dashboard/blocks.js} +131 -137
- 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 +9 -14
- package/lib/render/footer.js +385 -0
- package/lib/render/frame.js +123 -25
- package/lib/render/header.js +176 -0
- package/lib/render/log.js +320 -0
- package/lib/render/menu.js +60 -0
- package/lib/render/notes.js +16 -11
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +9 -13
- package/lib/render/primitives.js +101 -15
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +13 -25
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +13 -60
- package/lib/render/tiles.js +109 -48
- 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} +48 -19
- 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 +14 -0
- package/lib/review/format.js +420 -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 +976 -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 +33 -40
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +91 -52
- package/lib/session/compose.js +189 -147
- package/lib/session/dashboard.js +23 -22
- package/lib/session/dispatch.js +86 -21
- package/lib/session/files-pane.js +47 -26
- package/lib/session/find.js +16 -29
- package/lib/session/import.js +93 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +78 -72
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +6 -3
- package/lib/session/npm/history.js +250 -0
- package/lib/session/npm/npm.js +836 -0
- package/lib/session/{packages.js → npm/packages.js} +21 -14
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +202 -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} +158 -63
- 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 +65 -65
- package/lib/session/workspace.js +39 -78
- 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} +4 -3
- 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 -545
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -455
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -806
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/render/search.js +0 -25
- package/lib/review.js +0 -745
- 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
package/lib/session/lifecycle.js
CHANGED
|
@@ -3,20 +3,16 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const {
|
|
8
|
-
const
|
|
9
|
-
const {
|
|
10
|
-
const
|
|
11
|
-
const {
|
|
12
|
-
const
|
|
13
|
-
const {
|
|
14
|
-
const
|
|
15
|
-
const {
|
|
16
|
-
const review = require('../review.js');
|
|
17
|
-
const { hasNotes, loadReview } = review;
|
|
18
|
-
const reviewMerge = require('../review-merge.js');
|
|
19
|
-
const { mergeReview } = reviewMerge;
|
|
6
|
+
const { isAbort } = require('../common/process.js');
|
|
7
|
+
const { tagLoaded, fetchSnapshot, fetchExtras } = require('./load.js');
|
|
8
|
+
const { DiskWatcher, DEBOUNCE_MS } = require('../dashboard/watch.js');
|
|
9
|
+
const { itemPath, REVIEW_DIR } = require('../common/files.js');
|
|
10
|
+
const { relativeAge } = require('../common/format.js');
|
|
11
|
+
const { MANIFEST, LOCKFILE, depFileMeta } = require('../deps/deps.js');
|
|
12
|
+
const { hasNotes, loadReview } = require('../review/review.js');
|
|
13
|
+
const { setTheme } = require('../term/ansi.js');
|
|
14
|
+
const { readUiPrefs } = require('./prefs.js');
|
|
15
|
+
const { mergeReview } = require('../review/merge.js');
|
|
20
16
|
|
|
21
17
|
const WATCH_IGNORE_MS = 500;
|
|
22
18
|
|
|
@@ -147,8 +143,9 @@ class Lifecycle {
|
|
|
147
143
|
syncUnitEditor() {
|
|
148
144
|
const ui = this.ui;
|
|
149
145
|
const rel = ui.nav.reviewPath;
|
|
150
|
-
if (ui.nav.pane !== 'unit' || !rel
|
|
151
|
-
ui.
|
|
146
|
+
if (ui.nav.pane !== 'unit' || !rel) return;
|
|
147
|
+
const text = ui.reads.cached(rel);
|
|
148
|
+
if (text !== null) ui.composer.applyDiskText(text);
|
|
152
149
|
}
|
|
153
150
|
|
|
154
151
|
syncItemWatches() {
|
|
@@ -161,6 +158,30 @@ class Lifecycle {
|
|
|
161
158
|
this.diskWatcher.watchFiles(rels);
|
|
162
159
|
}
|
|
163
160
|
|
|
161
|
+
applyPrefs() {
|
|
162
|
+
const ui = this.ui;
|
|
163
|
+
const saved = readUiPrefs(ui.top);
|
|
164
|
+
ui.lineNumbers = saved.lineNumbers;
|
|
165
|
+
ui.layout = saved.layout;
|
|
166
|
+
ui.commits.commitView = saved.commitView;
|
|
167
|
+
setTheme(saved.theme);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
applySource(loaded) {
|
|
171
|
+
const ui = this.ui;
|
|
172
|
+
if (loaded.repositoryUrl !== undefined) {
|
|
173
|
+
ui.repositoryUrl = loaded.repositoryUrl || '';
|
|
174
|
+
}
|
|
175
|
+
if (loaded.rev) ui.rev = loaded.rev;
|
|
176
|
+
if (loaded.revShort) ui.revShort = loaded.revShort;
|
|
177
|
+
else if (ui.rev && !ui.revShort) ui.revShort = ui.rev.slice(0, 7);
|
|
178
|
+
if (loaded.sourceLabel) ui.sourceLabel = loaded.sourceLabel;
|
|
179
|
+
if (loaded.change) ui.change = loaded.change;
|
|
180
|
+
if (loaded.branch !== undefined) ui.branch = loaded.branch || '';
|
|
181
|
+
const repository = ui.change ? ui.change.repository : '';
|
|
182
|
+
if (!ui.repoName && repository) ui.repoName = repository;
|
|
183
|
+
}
|
|
184
|
+
|
|
164
185
|
applyLoaded(loaded) {
|
|
165
186
|
const ui = this.ui;
|
|
166
187
|
if (ui.nav.pane === 'repos') return false;
|
|
@@ -169,21 +190,20 @@ class Lifecycle {
|
|
|
169
190
|
if (!loader.isCurrent(loaded.generation, ui.done)) return false;
|
|
170
191
|
}
|
|
171
192
|
const wasTasks = nav.tasksOpen === true;
|
|
193
|
+
const firstLoad = !loader.didLoad;
|
|
172
194
|
const here = ui.current();
|
|
173
195
|
ui.top = loaded.top ?? ui.cwd;
|
|
196
|
+
this.applyPrefs();
|
|
174
197
|
collection.replace(loaded.items);
|
|
175
198
|
stampItemDates(ui.top, collection.items);
|
|
176
|
-
|
|
177
|
-
if (loaded.revShort) ui.revShort = loaded.revShort;
|
|
178
|
-
else if (ui.rev && !ui.revShort) ui.revShort = ui.rev.slice(0, 7);
|
|
179
|
-
if (loaded.sourceLabel) ui.sourceLabel = loaded.sourceLabel;
|
|
180
|
-
if (loaded.change) ui.change = loaded.change;
|
|
181
|
-
if (loaded.branch !== undefined) ui.branch = loaded.branch || '';
|
|
182
|
-
const repository = ui.change ? ui.change.repository : '';
|
|
183
|
-
if (!ui.repoName && repository) ui.repoName = repository;
|
|
199
|
+
this.applySource(loaded);
|
|
184
200
|
if (!ui.review.store) ui.initReview();
|
|
185
201
|
ui.review.applyImported(loaded.imported);
|
|
186
202
|
this.restoreAfterLoad(here, wasTasks);
|
|
203
|
+
const issue = loaded.change && loaded.change.source === 'issue';
|
|
204
|
+
if (firstLoad && issue && !collection.items.length) {
|
|
205
|
+
ui.composer.tasks.openTasksPage();
|
|
206
|
+
}
|
|
187
207
|
loader.didLoad = true;
|
|
188
208
|
ui.updater.maybePrompt();
|
|
189
209
|
loader.pendingExtras = loaded.pending === true;
|
|
@@ -193,10 +213,9 @@ class Lifecycle {
|
|
|
193
213
|
return true;
|
|
194
214
|
}
|
|
195
215
|
|
|
196
|
-
load() {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.applyLoaded(loaded);
|
|
216
|
+
async load() {
|
|
217
|
+
await this.openLoad();
|
|
218
|
+
if (this.ui.loadError) throw this.ui.loadError;
|
|
200
219
|
}
|
|
201
220
|
|
|
202
221
|
npmStamp() {
|
|
@@ -246,13 +265,18 @@ class Lifecycle {
|
|
|
246
265
|
paths: ui.paths,
|
|
247
266
|
signal: ui.loader.signal,
|
|
248
267
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
268
|
+
const takeExtras = async () => {
|
|
269
|
+
try {
|
|
270
|
+
const value = await fetchExtras(ui.repo, loaded, extra);
|
|
271
|
+
return { value };
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (ui.loader.isCurrent(gen)) this.failOpen(error);
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
const outcome = await takeExtras();
|
|
278
|
+
if (!outcome) return;
|
|
279
|
+
const next = outcome.value;
|
|
256
280
|
if (!this.isWanted(gen)) return;
|
|
257
281
|
if (next) {
|
|
258
282
|
this.applyLoaded(tagLoaded(next, gen));
|
|
@@ -284,7 +308,7 @@ class Lifecycle {
|
|
|
284
308
|
|
|
285
309
|
afterSnapshot(kind, extra = {}) {
|
|
286
310
|
const nav = this.ui.nav;
|
|
287
|
-
if (nav.pane === 'commits') this.ui.commits.
|
|
311
|
+
if (nav.pane === 'commits') this.ui.commits.reload();
|
|
288
312
|
if (kind !== 'open' && kind !== 'watch') {
|
|
289
313
|
nav.scroll = 0;
|
|
290
314
|
nav.clearSelection();
|
|
@@ -299,15 +323,18 @@ class Lifecycle {
|
|
|
299
323
|
ui.ops.setBusy('loading');
|
|
300
324
|
ui.paint();
|
|
301
325
|
}
|
|
302
|
-
let loaded;
|
|
326
|
+
let loaded = null;
|
|
327
|
+
const side = ui.reads.read();
|
|
303
328
|
try {
|
|
304
329
|
const opts = this.snapshotOpts({ signal: ui.loader.signal });
|
|
305
330
|
loaded = await fetchSnapshot(ui.repo, ui.cwd, ui.paths, opts);
|
|
306
331
|
} catch (error) {
|
|
307
332
|
if (ui.loader.isCurrent(gen)) this.failOpen(error);
|
|
308
|
-
return;
|
|
309
333
|
}
|
|
334
|
+
if (!loaded) return;
|
|
335
|
+
const found = await side;
|
|
310
336
|
if (!this.isWanted(gen)) return;
|
|
337
|
+
ui.reads.apply(found);
|
|
311
338
|
this.applyLoaded(tagLoaded(loaded, gen));
|
|
312
339
|
this.afterSnapshot(kind, extra);
|
|
313
340
|
if (loaded.pending === true || this.npmExtrasStale()) {
|
|
@@ -335,7 +362,8 @@ class Lifecycle {
|
|
|
335
362
|
if (loader.didLoad && !loader.pendingExtras) {
|
|
336
363
|
loader.promise = Promise.resolve();
|
|
337
364
|
} else {
|
|
338
|
-
|
|
365
|
+
const run = this.runSnapshot(loader.bump(), 'open');
|
|
366
|
+
loader.promise = this.ui.ops.track(run);
|
|
339
367
|
}
|
|
340
368
|
return loader.promise;
|
|
341
369
|
}
|
|
@@ -345,32 +373,15 @@ class Lifecycle {
|
|
|
345
373
|
this.ui.loader.didLoad = true;
|
|
346
374
|
return;
|
|
347
375
|
}
|
|
348
|
-
this.ui.ensureRepo();
|
|
349
|
-
await this.
|
|
350
|
-
if (this.ui.loadError) throw this.ui.loadError;
|
|
376
|
+
await this.ui.ensureRepo();
|
|
377
|
+
await this.load();
|
|
351
378
|
}
|
|
352
379
|
|
|
353
380
|
refreshFromRepo(extra = {}) {
|
|
354
381
|
const ui = this.ui;
|
|
355
382
|
ui.loader.promise = null;
|
|
356
383
|
const gen = ui.loader.bump();
|
|
357
|
-
|
|
358
|
-
if (ui.uiOpen && ui.repo.loadAsync) {
|
|
359
|
-
return void this.runSnapshot(gen, kind, extra);
|
|
360
|
-
}
|
|
361
|
-
const loaded = ui.repo.load(ui.cwd, ui.paths, this.snapshotOpts(extra));
|
|
362
|
-
this.applyLoaded(tagLoaded(loaded, gen));
|
|
363
|
-
this.afterSnapshot(kind, extra);
|
|
364
|
-
if (loaded.pending === true || this.npmExtrasStale()) {
|
|
365
|
-
ui.ops.setBusy('checking npm');
|
|
366
|
-
return void this.followExtras(loaded, gen, kind);
|
|
367
|
-
}
|
|
368
|
-
if (kind === 'keep') return;
|
|
369
|
-
this.finishIfEmpty(kind);
|
|
370
|
-
if (extra.doneStatus && !ui.done && !ui.loader.pendingExtras) {
|
|
371
|
-
ui.status = extra.doneStatus;
|
|
372
|
-
}
|
|
373
|
-
if (kind === 'watch') ui.paint();
|
|
384
|
+
ui.ops.track(this.runSnapshot(gen, kindFromExtra(extra), extra));
|
|
374
385
|
}
|
|
375
386
|
|
|
376
387
|
reloadAfterChange(afterLoad) {
|
|
@@ -385,11 +396,7 @@ class Lifecycle {
|
|
|
385
396
|
ui.syncFileCursor();
|
|
386
397
|
if (afterLoad) afterLoad();
|
|
387
398
|
};
|
|
388
|
-
|
|
389
|
-
return void this.refreshFromRepo({ afterLoad: restore });
|
|
390
|
-
}
|
|
391
|
-
this.refreshFromRepo();
|
|
392
|
-
restore();
|
|
399
|
+
this.refreshFromRepo({ afterLoad: restore });
|
|
393
400
|
}
|
|
394
401
|
|
|
395
402
|
onReload() {
|
|
@@ -455,12 +462,13 @@ class Lifecycle {
|
|
|
455
462
|
return;
|
|
456
463
|
}
|
|
457
464
|
const store = this.ui.review.store;
|
|
458
|
-
let disk;
|
|
465
|
+
let disk = null;
|
|
459
466
|
try {
|
|
460
467
|
disk = loadReview(store.reviewPath, store.templates);
|
|
461
468
|
} catch {
|
|
462
|
-
|
|
469
|
+
// unreadable review
|
|
463
470
|
}
|
|
471
|
+
if (!disk) return;
|
|
464
472
|
mergeReview(store, disk);
|
|
465
473
|
this.reviewStamp = stamp;
|
|
466
474
|
this.ui.paint();
|
|
@@ -484,12 +492,10 @@ class Lifecycle {
|
|
|
484
492
|
const repos = info ? info.repos : [];
|
|
485
493
|
const paths = ['.'];
|
|
486
494
|
for (const repo of repos) paths.push(path.join(repo.name, '.git'));
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
onPaths: (rels) => ui.workspace.notePaths(rels),
|
|
492
|
-
});
|
|
495
|
+
const matchReview = (rel) => rel.includes(`/${REVIEW_DIR}/`);
|
|
496
|
+
const onPaths = (rels) => ui.workspace.notePaths(rels);
|
|
497
|
+
const options = { root, paths, matchReview, onPaths };
|
|
498
|
+
this.diskWatcher = new DiskWatcher(options);
|
|
493
499
|
return;
|
|
494
500
|
}
|
|
495
501
|
const root = this.root;
|
package/lib/session/load.js
CHANGED
|
@@ -40,10 +40,8 @@ class LoadCoordinator {
|
|
|
40
40
|
|
|
41
41
|
const tagLoaded = (loaded, gen) => ({ ...loaded, generation: gen });
|
|
42
42
|
|
|
43
|
-
const fetchSnapshot = async (repo, cwd, paths, options) =>
|
|
44
|
-
|
|
45
|
-
return repo.load(cwd, paths, options);
|
|
46
|
-
};
|
|
43
|
+
const fetchSnapshot = async (repo, cwd, paths, options) =>
|
|
44
|
+
repo.load(cwd, paths, options);
|
|
47
45
|
|
|
48
46
|
const fetchExtras = async (repo, loaded, extra) => {
|
|
49
47
|
if (!repo.loadExtras) return null;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const files = require('../files.js');
|
|
3
|
+
const files = require('../common/files.js');
|
|
4
4
|
const { listPath, isTaskItem, isTasksEntry, TASKS_FILE } = files;
|
|
5
|
-
const
|
|
6
|
-
const { LIST_PANES } = actions;
|
|
5
|
+
const { LIST_PANES } = require('../input/actions.js');
|
|
7
6
|
|
|
8
7
|
const freshListScroll = () =>
|
|
9
8
|
Object.fromEntries(LIST_PANES.map((pane) => [pane, 0]));
|
|
@@ -21,6 +20,7 @@ class Navigation {
|
|
|
21
20
|
this.commitCursor = 0;
|
|
22
21
|
this.npmCursor = 0;
|
|
23
22
|
this.packagesCursor = 0;
|
|
23
|
+
this.agentCursor = 0;
|
|
24
24
|
this.scroll = 0;
|
|
25
25
|
this.listScrollByPane = freshListScroll();
|
|
26
26
|
this.reviewPath = null;
|
|
@@ -29,10 +29,12 @@ class Navigation {
|
|
|
29
29
|
this.selection = null;
|
|
30
30
|
this.mouseAnchor = null;
|
|
31
31
|
this.pendingClick = null;
|
|
32
|
+
this.scrollDrag = null;
|
|
32
33
|
this.lastClick = null;
|
|
33
34
|
this.tasksOpen = false;
|
|
34
35
|
this.tasksFocus = 0;
|
|
35
36
|
this.find = null;
|
|
37
|
+
this.import = null;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
clampIndex(length) {
|
|
@@ -98,6 +100,7 @@ class Navigation {
|
|
|
98
100
|
this.selection = null;
|
|
99
101
|
this.mouseAnchor = null;
|
|
100
102
|
this.pendingClick = null;
|
|
103
|
+
this.scrollDrag = null;
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
get listScroll() {
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { stripAnsi } = require('../../term/ansi.js');
|
|
4
|
+
const { buildDocument } = require('../../report/parse.js');
|
|
5
|
+
const report = require('../../report/render.js');
|
|
6
|
+
const { renderDocument, renderReport } = report;
|
|
7
|
+
const runStore = require('../../runs/runs.js');
|
|
8
|
+
const { Tracker, summarizeDocument } = runStore;
|
|
9
|
+
const { runName } = require('../../dashboard/model.js');
|
|
10
|
+
const { reduceOutput } = require('../../runs/output.js');
|
|
11
|
+
const { trimText, pad2 } = require('../../common/utilities.js');
|
|
12
|
+
|
|
13
|
+
const TESTS_RE = /^(?:TAP version \d+|\s*(?:not )?ok\b|[ℹi]\s+tests\s+\d+)/m;
|
|
14
|
+
|
|
15
|
+
const filteredOutput = (raw, root, status, rawFile) => {
|
|
16
|
+
const text = stripAnsi(`${raw ?? ''}`);
|
|
17
|
+
if (status === '' || !TESTS_RE.test(text)) {
|
|
18
|
+
const output = reduceOutput(raw, root, status);
|
|
19
|
+
return { output, log: output, result: null };
|
|
20
|
+
}
|
|
21
|
+
const doc = buildDocument(raw, '', root, status, 'test');
|
|
22
|
+
const result = summarizeDocument(doc);
|
|
23
|
+
const output = renderReport(doc);
|
|
24
|
+
const log = renderDocument(doc, rawFile);
|
|
25
|
+
return { output, log, result };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const runLabel = (entry) =>
|
|
29
|
+
entry.kind === 'bin' ? `npm exec ${entry.name}` : `npm run ${entry.name}`;
|
|
30
|
+
|
|
31
|
+
const historyMark = (run) => {
|
|
32
|
+
if (run.status === 'running') return 'run';
|
|
33
|
+
if (run.status === 'stopped') return 'stop';
|
|
34
|
+
if (run.exit === 0 || run.exit === '0') return 'pass';
|
|
35
|
+
return 'fail';
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const historyCounts = (run) => {
|
|
39
|
+
const result = run.result;
|
|
40
|
+
if (result && typeof result.tests === 'number') {
|
|
41
|
+
const fail = result.failed ?? 0;
|
|
42
|
+
const ok = result.passed ?? Math.max(0, result.tests - fail);
|
|
43
|
+
return { done: result.tests, ok, fail, total: result.tests };
|
|
44
|
+
}
|
|
45
|
+
const progress = run.progress;
|
|
46
|
+
if (!progress) return null;
|
|
47
|
+
const ok = progress.done ?? 0;
|
|
48
|
+
const fail = progress.failed ?? 0;
|
|
49
|
+
if (!ok && !fail && !(progress.expected > 0)) return null;
|
|
50
|
+
const total = progress.expected > 0 ? progress.expected : null;
|
|
51
|
+
return { done: ok + fail, ok, fail, total };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const countText = (value) => (typeof value === 'number' ? `${value}` : '');
|
|
55
|
+
|
|
56
|
+
const historyExit = (run, counts) => {
|
|
57
|
+
if (counts || run.status !== 'exited') return '';
|
|
58
|
+
if (run.exit === '' || run.exit === null || run.exit === undefined) return '';
|
|
59
|
+
return `exit ${run.exit}`;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const earlierTotal = (list, run) => {
|
|
63
|
+
let best = 0;
|
|
64
|
+
let at = -1;
|
|
65
|
+
for (const item of list) {
|
|
66
|
+
if (item === run || item.name !== run.name) continue;
|
|
67
|
+
if (item.status === 'running') continue;
|
|
68
|
+
const counts = historyCounts(item);
|
|
69
|
+
if (!counts || typeof counts.total !== 'number') continue;
|
|
70
|
+
const started = item.startedAt || 0;
|
|
71
|
+
if (started < at) continue;
|
|
72
|
+
at = started;
|
|
73
|
+
best = counts.total;
|
|
74
|
+
}
|
|
75
|
+
return best;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const noteTests = (run, list, text) => {
|
|
79
|
+
const plain = stripAnsi(`${text ?? ''}`);
|
|
80
|
+
if (!TESTS_RE.test(plain)) return;
|
|
81
|
+
const tracker = new Tracker();
|
|
82
|
+
tracker.feed(plain);
|
|
83
|
+
if (!Object.hasOwn(run, 'expected')) run.expected = earlierTotal(list, run);
|
|
84
|
+
run.progress = {
|
|
85
|
+
done: tracker.progress.done,
|
|
86
|
+
failed: tracker.progress.failed,
|
|
87
|
+
expected: run.expected,
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const RUN_KEEP = 50;
|
|
92
|
+
|
|
93
|
+
const LOG_GAP_MS = 2000;
|
|
94
|
+
|
|
95
|
+
const RESLOP_RUN = /^reslop(?:\.js)?\s+t\s+--\s+/;
|
|
96
|
+
|
|
97
|
+
const RESLOP_T = /reslop(?:\.js)?\s+t\s+--/;
|
|
98
|
+
|
|
99
|
+
const launchedReslop = (run) => {
|
|
100
|
+
if (run.externalId) return false;
|
|
101
|
+
const command = `${run.command ?? ''}`;
|
|
102
|
+
return RESLOP_T.test(command);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const historyName = (run) => {
|
|
106
|
+
const script = trimText(run.name);
|
|
107
|
+
if (script) return script;
|
|
108
|
+
return trimText(run.command).replace(RESLOP_RUN, '');
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const sameWindow = (left, right) => {
|
|
112
|
+
const start = Number(left.startedAt) || 0;
|
|
113
|
+
const end = Number(left.endedAt) || start;
|
|
114
|
+
const at = Number(right.startedAt) || 0;
|
|
115
|
+
const done = Number(right.endedAt) || at;
|
|
116
|
+
return done >= start - LOG_GAP_MS && at <= end + LOG_GAP_MS;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const logBase = (name) => {
|
|
120
|
+
const file = `${name ?? ''}`;
|
|
121
|
+
if (!file || file.includes('/') || file.includes('\\')) return '';
|
|
122
|
+
return file;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const externalStatus = (record) => {
|
|
126
|
+
if (record.status === 'running') return 'running';
|
|
127
|
+
if (record.status === 'aborted') return 'stopped';
|
|
128
|
+
return 'exited';
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const externalExit = (record) => {
|
|
132
|
+
if (record.status === 'running' || record.status === 'aborted') return '';
|
|
133
|
+
if (record.exit === null || record.exit === undefined) return '';
|
|
134
|
+
return record.exit;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const progressCount = (progress, key) => {
|
|
138
|
+
if (!progress || typeof progress[key] !== 'number') return 0;
|
|
139
|
+
return progress[key];
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const progressSame = (left, right) => {
|
|
143
|
+
const done = progressCount(left, 'done') === progressCount(right, 'done');
|
|
144
|
+
const leftFailed = progressCount(left, 'failed');
|
|
145
|
+
const rightFailed = progressCount(right, 'failed');
|
|
146
|
+
const failed = leftFailed === rightFailed;
|
|
147
|
+
const leftExpected = progressCount(left, 'expected');
|
|
148
|
+
const rightExpected = progressCount(right, 'expected');
|
|
149
|
+
const expected = leftExpected === rightExpected;
|
|
150
|
+
return done && failed && expected;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const resultSame = (left, right) => {
|
|
154
|
+
if (!left && !right) return true;
|
|
155
|
+
if (!left || !right) return false;
|
|
156
|
+
return (
|
|
157
|
+
left.tests === right.tests &&
|
|
158
|
+
left.passed === right.passed &&
|
|
159
|
+
left.failed === right.failed &&
|
|
160
|
+
left.errors === right.errors
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const applyExternal = (run, record) => {
|
|
165
|
+
const status = externalStatus(record);
|
|
166
|
+
const exit = externalExit(record);
|
|
167
|
+
const endedAt = Number(record.endedAt) || 0;
|
|
168
|
+
const progress = record.progress ?? null;
|
|
169
|
+
const result = record.result ?? null;
|
|
170
|
+
const same =
|
|
171
|
+
run.status === status &&
|
|
172
|
+
run.exit === exit &&
|
|
173
|
+
(run.endedAt || 0) === endedAt &&
|
|
174
|
+
progressSame(run.progress, progress) &&
|
|
175
|
+
resultSame(run.result, result);
|
|
176
|
+
if (same) return false;
|
|
177
|
+
run.status = status;
|
|
178
|
+
run.exit = exit;
|
|
179
|
+
run.endedAt = endedAt;
|
|
180
|
+
run.progress = progress;
|
|
181
|
+
run.result = result;
|
|
182
|
+
return true;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const recordedItem = (record) => {
|
|
186
|
+
const script = trimText(record.script);
|
|
187
|
+
const command = trimText(record.command);
|
|
188
|
+
const name = runName({ command, script }).replace(RESLOP_RUN, '');
|
|
189
|
+
return {
|
|
190
|
+
name,
|
|
191
|
+
kind: script ? 'script' : 'command',
|
|
192
|
+
script,
|
|
193
|
+
command,
|
|
194
|
+
status: externalStatus(record),
|
|
195
|
+
exit: externalExit(record),
|
|
196
|
+
startedAt: Number(record.startedAt) || 0,
|
|
197
|
+
endedAt: Number(record.endedAt) || 0,
|
|
198
|
+
logName: logBase(record.log),
|
|
199
|
+
rawName: logBase(record.raw),
|
|
200
|
+
externalId: `${record.id ?? ''}`,
|
|
201
|
+
result: record.result ?? null,
|
|
202
|
+
progress: record.progress ?? null,
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const whenLabel = (ms) => {
|
|
207
|
+
if (!ms) return '';
|
|
208
|
+
const date = new Date(ms);
|
|
209
|
+
const hh = pad2(date.getHours());
|
|
210
|
+
const mm = pad2(date.getMinutes());
|
|
211
|
+
const time = `${hh}:${mm}`;
|
|
212
|
+
const today = new Date();
|
|
213
|
+
const sameDay =
|
|
214
|
+
date.getFullYear() === today.getFullYear() &&
|
|
215
|
+
date.getMonth() === today.getMonth() &&
|
|
216
|
+
date.getDate() === today.getDate();
|
|
217
|
+
if (sameDay) return time;
|
|
218
|
+
const month = pad2(date.getMonth() + 1);
|
|
219
|
+
const day = pad2(date.getDate());
|
|
220
|
+
return `${month}-${day} ${time}`;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const elapsedText = (run, now) => {
|
|
224
|
+
const end = run.endedAt || now;
|
|
225
|
+
const ms = Math.max(0, end - (run.startedAt || end));
|
|
226
|
+
const sec = Math.round(ms / 1000);
|
|
227
|
+
if (sec < 60) return `${sec}s`;
|
|
228
|
+
const min = Math.floor(sec / 60);
|
|
229
|
+
const rest = sec % 60;
|
|
230
|
+
return `${min}m${pad2(rest)}s`;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
filteredOutput,
|
|
235
|
+
runLabel,
|
|
236
|
+
historyMark,
|
|
237
|
+
historyCounts,
|
|
238
|
+
countText,
|
|
239
|
+
historyExit,
|
|
240
|
+
noteTests,
|
|
241
|
+
RUN_KEEP,
|
|
242
|
+
launchedReslop,
|
|
243
|
+
historyName,
|
|
244
|
+
sameWindow,
|
|
245
|
+
logBase,
|
|
246
|
+
applyExternal,
|
|
247
|
+
recordedItem,
|
|
248
|
+
whenLabel,
|
|
249
|
+
elapsedText,
|
|
250
|
+
};
|