reslop 0.2.7 → 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 +17 -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 +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} +123 -96
- 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 +111 -48
- package/lib/{editor.js → input/editor.js} +7 -8
- package/lib/{keys.js → input/keys.js} +15 -43
- 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/{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 +383 -0
- package/lib/render/frame.js +123 -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 +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} +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 +33 -39
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +91 -52
- package/lib/session/compose.js +181 -145
- package/lib/session/dashboard.js +23 -22
- package/lib/session/dispatch.js +85 -21
- package/lib/session/files-pane.js +47 -26
- package/lib/session/find.js +16 -29
- package/lib/session/import.js +82 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +75 -72
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +6 -3
- package/lib/session/npm/history.js +249 -0
- package/lib/session/npm/npm.js +815 -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} +156 -63
- package/lib/session/tasks.js +320 -65
- 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} +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 -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,27 @@ 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.rev) ui.rev = loaded.rev;
|
|
173
|
+
if (loaded.revShort) ui.revShort = loaded.revShort;
|
|
174
|
+
else if (ui.rev && !ui.revShort) ui.revShort = ui.rev.slice(0, 7);
|
|
175
|
+
if (loaded.sourceLabel) ui.sourceLabel = loaded.sourceLabel;
|
|
176
|
+
if (loaded.change) ui.change = loaded.change;
|
|
177
|
+
if (loaded.branch !== undefined) ui.branch = loaded.branch || '';
|
|
178
|
+
const repository = ui.change ? ui.change.repository : '';
|
|
179
|
+
if (!ui.repoName && repository) ui.repoName = repository;
|
|
180
|
+
}
|
|
181
|
+
|
|
164
182
|
applyLoaded(loaded) {
|
|
165
183
|
const ui = this.ui;
|
|
166
184
|
if (ui.nav.pane === 'repos') return false;
|
|
@@ -169,21 +187,20 @@ class Lifecycle {
|
|
|
169
187
|
if (!loader.isCurrent(loaded.generation, ui.done)) return false;
|
|
170
188
|
}
|
|
171
189
|
const wasTasks = nav.tasksOpen === true;
|
|
190
|
+
const firstLoad = !loader.didLoad;
|
|
172
191
|
const here = ui.current();
|
|
173
192
|
ui.top = loaded.top ?? ui.cwd;
|
|
193
|
+
this.applyPrefs();
|
|
174
194
|
collection.replace(loaded.items);
|
|
175
195
|
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;
|
|
196
|
+
this.applySource(loaded);
|
|
184
197
|
if (!ui.review.store) ui.initReview();
|
|
185
198
|
ui.review.applyImported(loaded.imported);
|
|
186
199
|
this.restoreAfterLoad(here, wasTasks);
|
|
200
|
+
const issue = loaded.change && loaded.change.source === 'issue';
|
|
201
|
+
if (firstLoad && issue && !collection.items.length) {
|
|
202
|
+
ui.composer.tasks.openTasksPage();
|
|
203
|
+
}
|
|
187
204
|
loader.didLoad = true;
|
|
188
205
|
ui.updater.maybePrompt();
|
|
189
206
|
loader.pendingExtras = loaded.pending === true;
|
|
@@ -193,10 +210,9 @@ class Lifecycle {
|
|
|
193
210
|
return true;
|
|
194
211
|
}
|
|
195
212
|
|
|
196
|
-
load() {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.applyLoaded(loaded);
|
|
213
|
+
async load() {
|
|
214
|
+
await this.openLoad();
|
|
215
|
+
if (this.ui.loadError) throw this.ui.loadError;
|
|
200
216
|
}
|
|
201
217
|
|
|
202
218
|
npmStamp() {
|
|
@@ -246,13 +262,18 @@ class Lifecycle {
|
|
|
246
262
|
paths: ui.paths,
|
|
247
263
|
signal: ui.loader.signal,
|
|
248
264
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
265
|
+
const takeExtras = async () => {
|
|
266
|
+
try {
|
|
267
|
+
const value = await fetchExtras(ui.repo, loaded, extra);
|
|
268
|
+
return { value };
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (ui.loader.isCurrent(gen)) this.failOpen(error);
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
const outcome = await takeExtras();
|
|
275
|
+
if (!outcome) return;
|
|
276
|
+
const next = outcome.value;
|
|
256
277
|
if (!this.isWanted(gen)) return;
|
|
257
278
|
if (next) {
|
|
258
279
|
this.applyLoaded(tagLoaded(next, gen));
|
|
@@ -284,7 +305,7 @@ class Lifecycle {
|
|
|
284
305
|
|
|
285
306
|
afterSnapshot(kind, extra = {}) {
|
|
286
307
|
const nav = this.ui.nav;
|
|
287
|
-
if (nav.pane === 'commits') this.ui.commits.
|
|
308
|
+
if (nav.pane === 'commits') this.ui.commits.reload();
|
|
288
309
|
if (kind !== 'open' && kind !== 'watch') {
|
|
289
310
|
nav.scroll = 0;
|
|
290
311
|
nav.clearSelection();
|
|
@@ -299,15 +320,18 @@ class Lifecycle {
|
|
|
299
320
|
ui.ops.setBusy('loading');
|
|
300
321
|
ui.paint();
|
|
301
322
|
}
|
|
302
|
-
let loaded;
|
|
323
|
+
let loaded = null;
|
|
324
|
+
const side = ui.reads.read();
|
|
303
325
|
try {
|
|
304
326
|
const opts = this.snapshotOpts({ signal: ui.loader.signal });
|
|
305
327
|
loaded = await fetchSnapshot(ui.repo, ui.cwd, ui.paths, opts);
|
|
306
328
|
} catch (error) {
|
|
307
329
|
if (ui.loader.isCurrent(gen)) this.failOpen(error);
|
|
308
|
-
return;
|
|
309
330
|
}
|
|
331
|
+
if (!loaded) return;
|
|
332
|
+
const found = await side;
|
|
310
333
|
if (!this.isWanted(gen)) return;
|
|
334
|
+
ui.reads.apply(found);
|
|
311
335
|
this.applyLoaded(tagLoaded(loaded, gen));
|
|
312
336
|
this.afterSnapshot(kind, extra);
|
|
313
337
|
if (loaded.pending === true || this.npmExtrasStale()) {
|
|
@@ -335,7 +359,8 @@ class Lifecycle {
|
|
|
335
359
|
if (loader.didLoad && !loader.pendingExtras) {
|
|
336
360
|
loader.promise = Promise.resolve();
|
|
337
361
|
} else {
|
|
338
|
-
|
|
362
|
+
const run = this.runSnapshot(loader.bump(), 'open');
|
|
363
|
+
loader.promise = this.ui.ops.track(run);
|
|
339
364
|
}
|
|
340
365
|
return loader.promise;
|
|
341
366
|
}
|
|
@@ -345,32 +370,15 @@ class Lifecycle {
|
|
|
345
370
|
this.ui.loader.didLoad = true;
|
|
346
371
|
return;
|
|
347
372
|
}
|
|
348
|
-
this.ui.ensureRepo();
|
|
349
|
-
await this.
|
|
350
|
-
if (this.ui.loadError) throw this.ui.loadError;
|
|
373
|
+
await this.ui.ensureRepo();
|
|
374
|
+
await this.load();
|
|
351
375
|
}
|
|
352
376
|
|
|
353
377
|
refreshFromRepo(extra = {}) {
|
|
354
378
|
const ui = this.ui;
|
|
355
379
|
ui.loader.promise = null;
|
|
356
380
|
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();
|
|
381
|
+
ui.ops.track(this.runSnapshot(gen, kindFromExtra(extra), extra));
|
|
374
382
|
}
|
|
375
383
|
|
|
376
384
|
reloadAfterChange(afterLoad) {
|
|
@@ -385,11 +393,7 @@ class Lifecycle {
|
|
|
385
393
|
ui.syncFileCursor();
|
|
386
394
|
if (afterLoad) afterLoad();
|
|
387
395
|
};
|
|
388
|
-
|
|
389
|
-
return void this.refreshFromRepo({ afterLoad: restore });
|
|
390
|
-
}
|
|
391
|
-
this.refreshFromRepo();
|
|
392
|
-
restore();
|
|
396
|
+
this.refreshFromRepo({ afterLoad: restore });
|
|
393
397
|
}
|
|
394
398
|
|
|
395
399
|
onReload() {
|
|
@@ -455,12 +459,13 @@ class Lifecycle {
|
|
|
455
459
|
return;
|
|
456
460
|
}
|
|
457
461
|
const store = this.ui.review.store;
|
|
458
|
-
let disk;
|
|
462
|
+
let disk = null;
|
|
459
463
|
try {
|
|
460
464
|
disk = loadReview(store.reviewPath, store.templates);
|
|
461
465
|
} catch {
|
|
462
|
-
|
|
466
|
+
// unreadable review
|
|
463
467
|
}
|
|
468
|
+
if (!disk) return;
|
|
464
469
|
mergeReview(store, disk);
|
|
465
470
|
this.reviewStamp = stamp;
|
|
466
471
|
this.ui.paint();
|
|
@@ -484,12 +489,10 @@ class Lifecycle {
|
|
|
484
489
|
const repos = info ? info.repos : [];
|
|
485
490
|
const paths = ['.'];
|
|
486
491
|
for (const repo of repos) paths.push(path.join(repo.name, '.git'));
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
onPaths: (rels) => ui.workspace.notePaths(rels),
|
|
492
|
-
});
|
|
492
|
+
const matchReview = (rel) => rel.includes(`/${REVIEW_DIR}/`);
|
|
493
|
+
const onPaths = (rels) => ui.workspace.notePaths(rels);
|
|
494
|
+
const options = { root, paths, matchReview, onPaths };
|
|
495
|
+
this.diskWatcher = new DiskWatcher(options);
|
|
493
496
|
return;
|
|
494
497
|
}
|
|
495
498
|
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,249 @@
|
|
|
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
|
+
command,
|
|
193
|
+
status: externalStatus(record),
|
|
194
|
+
exit: externalExit(record),
|
|
195
|
+
startedAt: Number(record.startedAt) || 0,
|
|
196
|
+
endedAt: Number(record.endedAt) || 0,
|
|
197
|
+
logName: logBase(record.log),
|
|
198
|
+
rawName: logBase(record.raw),
|
|
199
|
+
externalId: `${record.id ?? ''}`,
|
|
200
|
+
result: record.result ?? null,
|
|
201
|
+
progress: record.progress ?? null,
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const whenLabel = (ms) => {
|
|
206
|
+
if (!ms) return '';
|
|
207
|
+
const date = new Date(ms);
|
|
208
|
+
const hh = pad2(date.getHours());
|
|
209
|
+
const mm = pad2(date.getMinutes());
|
|
210
|
+
const time = `${hh}:${mm}`;
|
|
211
|
+
const today = new Date();
|
|
212
|
+
const sameDay =
|
|
213
|
+
date.getFullYear() === today.getFullYear() &&
|
|
214
|
+
date.getMonth() === today.getMonth() &&
|
|
215
|
+
date.getDate() === today.getDate();
|
|
216
|
+
if (sameDay) return time;
|
|
217
|
+
const month = pad2(date.getMonth() + 1);
|
|
218
|
+
const day = pad2(date.getDate());
|
|
219
|
+
return `${month}-${day} ${time}`;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const elapsedText = (run, now) => {
|
|
223
|
+
const end = run.endedAt || now;
|
|
224
|
+
const ms = Math.max(0, end - (run.startedAt || end));
|
|
225
|
+
const sec = Math.round(ms / 1000);
|
|
226
|
+
if (sec < 60) return `${sec}s`;
|
|
227
|
+
const min = Math.floor(sec / 60);
|
|
228
|
+
const rest = sec % 60;
|
|
229
|
+
return `${min}m${pad2(rest)}s`;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
filteredOutput,
|
|
234
|
+
runLabel,
|
|
235
|
+
historyMark,
|
|
236
|
+
historyCounts,
|
|
237
|
+
countText,
|
|
238
|
+
historyExit,
|
|
239
|
+
noteTests,
|
|
240
|
+
RUN_KEEP,
|
|
241
|
+
launchedReslop,
|
|
242
|
+
historyName,
|
|
243
|
+
sameWindow,
|
|
244
|
+
logBase,
|
|
245
|
+
applyExternal,
|
|
246
|
+
recordedItem,
|
|
247
|
+
whenLabel,
|
|
248
|
+
elapsedText,
|
|
249
|
+
};
|