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
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { split, jsonParse } = require('metautil');
|
|
6
|
+
|
|
7
|
+
const parseVersion = (text) => {
|
|
8
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(`${text ?? ''}`);
|
|
9
|
+
if (!match) return null;
|
|
10
|
+
const major = Number(match[1]);
|
|
11
|
+
const minor = Number(match[2]);
|
|
12
|
+
const patch = Number(match[3]);
|
|
13
|
+
return { major, minor, patch };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const cmpVersion = (left, right) => {
|
|
17
|
+
const major = left.major - right.major;
|
|
18
|
+
const minor = left.minor - right.minor;
|
|
19
|
+
const patch = left.patch - right.patch;
|
|
20
|
+
return major || minor || patch;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const toInt = (text) => parseInt(text, 10);
|
|
24
|
+
|
|
25
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
26
|
+
|
|
27
|
+
const resource = (dir, name) => fs.readFileSync(path.join(dir, name), 'utf8');
|
|
28
|
+
|
|
29
|
+
const readText = (file) => {
|
|
30
|
+
try {
|
|
31
|
+
return fs.readFileSync(file, 'utf8');
|
|
32
|
+
} catch {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const readJson = (file) => jsonParse(readText(file));
|
|
38
|
+
|
|
39
|
+
const writeJson = (file, data) => {
|
|
40
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
41
|
+
fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const listDir = (dir) => {
|
|
45
|
+
try {
|
|
46
|
+
return fs.readdirSync(dir);
|
|
47
|
+
} catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const errorMessage = (reason) => {
|
|
53
|
+
if (reason instanceof Error) return reason.message;
|
|
54
|
+
return `${reason ?? ''}`;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const trimText = (value) => `${value ?? ''}`.trim();
|
|
58
|
+
|
|
59
|
+
const oneLine = (text, fallback = '') => {
|
|
60
|
+
const body = `${text ?? ''}`.trim();
|
|
61
|
+
const first = split(body, '\n')[0];
|
|
62
|
+
return first || fallback;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const withNewline = (text) => {
|
|
66
|
+
const body = `${text ?? ''}`;
|
|
67
|
+
return !body || body.endsWith('\n') ? body : `${body}\n`;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const withNotice = (text, notice) => {
|
|
71
|
+
const body = `${text ?? ''}`;
|
|
72
|
+
if (body.endsWith(`${notice}\n`)) return body;
|
|
73
|
+
return `${withNewline(body)}${notice}\n`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const asText = (value) => (typeof value === 'string' ? value : '');
|
|
77
|
+
|
|
78
|
+
const unquote = (value) => {
|
|
79
|
+
const text = `${value ?? ''}`;
|
|
80
|
+
if (text.length < 2) return text;
|
|
81
|
+
const open = text[0];
|
|
82
|
+
const quoted = open === text.at(-1) && (open === '"' || open === '\x27');
|
|
83
|
+
return quoted ? text.slice(1, -1) : text;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const pad2 = (n) => `${n}`.padStart(2, '0');
|
|
87
|
+
|
|
88
|
+
const dateStamp = (date) => {
|
|
89
|
+
const month = pad2(date.getMonth() + 1);
|
|
90
|
+
return `${date.getFullYear()}-${month}-${pad2(date.getDate())}`;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
parseVersion,
|
|
95
|
+
cmpVersion,
|
|
96
|
+
toInt,
|
|
97
|
+
clamp,
|
|
98
|
+
resource,
|
|
99
|
+
readText,
|
|
100
|
+
readJson,
|
|
101
|
+
writeJson,
|
|
102
|
+
listDir,
|
|
103
|
+
errorMessage,
|
|
104
|
+
trimText,
|
|
105
|
+
oneLine,
|
|
106
|
+
asText,
|
|
107
|
+
withNewline,
|
|
108
|
+
withNotice,
|
|
109
|
+
unquote,
|
|
110
|
+
pad2,
|
|
111
|
+
dateStamp,
|
|
112
|
+
};
|
package/lib/dashboard/git.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
+
const { readText } = require('../common/utilities.js');
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
const { runGitAsync } = worktree;
|
|
7
|
+
const { runGit } = require('../git/worktree.js');
|
|
8
8
|
|
|
9
9
|
const UNIT = '\x1f';
|
|
10
10
|
const RECENT_COUNT = 8;
|
|
@@ -29,7 +29,7 @@ const TRACK_BEHIND = /behind (\d+)/;
|
|
|
29
29
|
|
|
30
30
|
const gitText = async (top, args) => {
|
|
31
31
|
try {
|
|
32
|
-
const result = await
|
|
32
|
+
const result = await runGit(args, top);
|
|
33
33
|
return result.status === 0 ? result.stdout.trim() : '';
|
|
34
34
|
} catch {
|
|
35
35
|
return '';
|
|
@@ -49,11 +49,10 @@ const parseTrack = (text) => {
|
|
|
49
49
|
const raw = `${text ?? ''}`;
|
|
50
50
|
const ahead = TRACK_AHEAD.exec(raw);
|
|
51
51
|
const behind = TRACK_BEHIND.exec(raw);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
52
|
+
const aheadCount = ahead ? Number(ahead[1]) : 0;
|
|
53
|
+
const behindCount = behind ? Number(behind[1]) : 0;
|
|
54
|
+
const gone = raw.includes('gone');
|
|
55
|
+
return { ahead: aheadCount, behind: behindCount, gone };
|
|
57
56
|
};
|
|
58
57
|
|
|
59
58
|
const parseBranch = (line) => {
|
|
@@ -82,13 +81,7 @@ const parseCommit = (line) => {
|
|
|
82
81
|
};
|
|
83
82
|
};
|
|
84
83
|
|
|
85
|
-
const readNumber = (file) =>
|
|
86
|
-
try {
|
|
87
|
-
return countOf(fs.readFileSync(file, 'utf8'));
|
|
88
|
-
} catch {
|
|
89
|
-
return 0;
|
|
90
|
-
}
|
|
91
|
-
};
|
|
84
|
+
const readNumber = (file) => countOf(readText(file));
|
|
92
85
|
|
|
93
86
|
const readStep = (dir, step, total) => ({
|
|
94
87
|
step: readNumber(path.join(dir, step)),
|
|
@@ -125,4 +118,4 @@ const readGitSummary = async (top) => {
|
|
|
125
118
|
};
|
|
126
119
|
};
|
|
127
120
|
|
|
128
|
-
module.exports = { readGitSummary, parseTrack };
|
|
121
|
+
module.exports = { gitText, countOf, readGitSummary, parseTrack };
|
package/lib/dashboard/model.js
CHANGED
|
@@ -1,58 +1,33 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { trimText } = require('../common/utilities.js');
|
|
5
|
+
const { ageSeconds } = require('../common/format.js');
|
|
6
|
+
|
|
7
|
+
const { folderOf, extOf } = require('./tree.js');
|
|
8
|
+
const { TASK_KINDS, taskKind } = require('../common/files.js');
|
|
7
9
|
|
|
8
10
|
const MINUTE_MS = 60_000;
|
|
9
11
|
const RUNS_SHOWN = 6;
|
|
10
12
|
const CHAIN_GAP_MS = 2000;
|
|
11
13
|
const NPM_NAMED = /^npm (?:run|exec)(?:\s+-s)?\s+(\S+)/;
|
|
12
14
|
const NPM_ALIAS = /^npm (test|start|stop|restart)(?:\s|$)/;
|
|
13
|
-
const AGE_TEXT = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
14
|
-
const AGE_SEC = {
|
|
15
|
-
second: 1,
|
|
16
|
-
minute: 60,
|
|
17
|
-
hour: 3600,
|
|
18
|
-
day: 86400,
|
|
19
|
-
week: 604800,
|
|
20
|
-
month: 2592000,
|
|
21
|
-
year: 31536000,
|
|
22
|
-
};
|
|
23
15
|
const STATUS_RANK = { running: 3, failed: 2, aborted: 1, passed: 0 };
|
|
24
16
|
|
|
25
17
|
const namedScript = (command) => {
|
|
26
|
-
const text =
|
|
18
|
+
const text = trimText(command);
|
|
27
19
|
const named = NPM_NAMED.exec(text);
|
|
28
20
|
if (named) return named[1];
|
|
29
21
|
const alias = NPM_ALIAS.exec(text);
|
|
30
22
|
return alias ? alias[1] : '';
|
|
31
23
|
};
|
|
32
24
|
|
|
33
|
-
const scriptName = (command) => {
|
|
34
|
-
const named = namedScript(command);
|
|
35
|
-
if (named) return named;
|
|
36
|
-
const text = `${command ?? ''}`.trim();
|
|
37
|
-
const token = text.split(/\s+/)[0];
|
|
38
|
-
const slash = Math.max(token.lastIndexOf('/'), token.lastIndexOf('\\'));
|
|
39
|
-
return token.slice(slash + 1) || text;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
25
|
const runName = (record) => {
|
|
43
|
-
const command =
|
|
44
|
-
const script =
|
|
26
|
+
const command = trimText(record.command);
|
|
27
|
+
const script = trimText(record.script);
|
|
45
28
|
return namedScript(command) || script || command;
|
|
46
29
|
};
|
|
47
30
|
|
|
48
|
-
const ageSeconds = (text) => {
|
|
49
|
-
const raw = `${text ?? ''}`.trim();
|
|
50
|
-
if (/^yesterday$/i.test(raw)) return AGE_SEC.day;
|
|
51
|
-
const match = AGE_TEXT.exec(raw);
|
|
52
|
-
if (!match) return Number.POSITIVE_INFINITY;
|
|
53
|
-
return Number(match[1]) * AGE_SEC[match[2].toLowerCase()];
|
|
54
|
-
};
|
|
55
|
-
|
|
56
31
|
const newerDate = (current, next) => {
|
|
57
32
|
if (!next) return current;
|
|
58
33
|
if (!current || ageSeconds(next) < ageSeconds(current)) return next;
|
|
@@ -130,17 +105,32 @@ const externalRun = (record) => {
|
|
|
130
105
|
};
|
|
131
106
|
};
|
|
132
107
|
|
|
108
|
+
const sessionName = (run) => {
|
|
109
|
+
const named = namedScript(run.label);
|
|
110
|
+
if (named) return named;
|
|
111
|
+
const script = trimText(run.name);
|
|
112
|
+
if (script) return script;
|
|
113
|
+
return trimText(run.command ?? run.label);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const progressOf = (run) => {
|
|
117
|
+
const progress = run.progress ?? {};
|
|
118
|
+
return {
|
|
119
|
+
done: progress.done ?? 0,
|
|
120
|
+
failed: progress.failed ?? 0,
|
|
121
|
+
expected: progress.expected ?? 0,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
133
125
|
const internalRun = (run) => ({
|
|
134
126
|
source: 'reslop',
|
|
135
|
-
name:
|
|
127
|
+
name: sessionName(run),
|
|
136
128
|
status: npmRunStatus(run),
|
|
137
129
|
exit: run.exit,
|
|
138
130
|
startedAt: run.startedAt,
|
|
139
131
|
endedAt: run.endedAt,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
expected: 0,
|
|
143
|
-
result: null,
|
|
132
|
+
...progressOf(run),
|
|
133
|
+
result: run.result ?? null,
|
|
144
134
|
});
|
|
145
135
|
|
|
146
136
|
const rankOf = (status) => STATUS_RANK[status] ?? 0;
|
|
@@ -175,6 +165,26 @@ const continuesRun = (prev, next) => {
|
|
|
175
165
|
return next.startedAt <= prev.endedAt + CHAIN_GAP_MS;
|
|
176
166
|
};
|
|
177
167
|
|
|
168
|
+
const chainedStep = (prev, next) => {
|
|
169
|
+
if (prev.name !== next.name || prev.command === next.command) return false;
|
|
170
|
+
const end = Number(prev.endedAt) || 0;
|
|
171
|
+
const at = Number(next.startedAt) || 0;
|
|
172
|
+
return end > 0 && at >= end && at <= end + CHAIN_GAP_MS;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const foldSteps = (runs) => {
|
|
176
|
+
const ordered = [...runs].sort(
|
|
177
|
+
(left, right) => (left.startedAt || 0) - (right.startedAt || 0),
|
|
178
|
+
);
|
|
179
|
+
const groups = [];
|
|
180
|
+
for (const run of ordered) {
|
|
181
|
+
const prev = groups.at(-1);
|
|
182
|
+
if (prev && chainedStep(prev, run)) groups[groups.length - 1] = run;
|
|
183
|
+
else groups.push(run);
|
|
184
|
+
}
|
|
185
|
+
return groups.reverse();
|
|
186
|
+
};
|
|
187
|
+
|
|
178
188
|
const absorbRun = (prev, next) => {
|
|
179
189
|
const open = isOpen(prev) || isOpen(next);
|
|
180
190
|
if (rankOf(next.status) >= rankOf(prev.status)) {
|
|
@@ -201,25 +211,55 @@ const collapseRuns = (list) => {
|
|
|
201
211
|
return groups.sort((left, right) => right.startedAt - left.startedAt);
|
|
202
212
|
};
|
|
203
213
|
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
214
|
+
const overlaps = (row, run) => {
|
|
215
|
+
if (row.name !== sessionName(run)) return false;
|
|
216
|
+
const start = Number(run.startedAt) || 0;
|
|
217
|
+
const end = Number(run.endedAt) || start;
|
|
218
|
+
const at = Number(row.startedAt) || 0;
|
|
219
|
+
const low = start - CHAIN_GAP_MS;
|
|
220
|
+
const high = Math.max(start, end) + CHAIN_GAP_MS;
|
|
221
|
+
return at >= low && at <= high;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const applySession = (row, run) => {
|
|
225
|
+
if (!run.running) return;
|
|
226
|
+
const next = internalRun(run);
|
|
227
|
+
row.status = next.status;
|
|
228
|
+
row.exit = next.exit;
|
|
229
|
+
row.done = next.done;
|
|
230
|
+
row.failed = next.failed;
|
|
231
|
+
row.expected = Math.max(row.expected, next.expected);
|
|
232
|
+
row.endedAt = 0;
|
|
233
|
+
if (next.result) row.result = next.result;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const mergeRuns = (records, npmRuns) => {
|
|
237
|
+
const sessions = Array.isArray(npmRuns) ? npmRuns : [];
|
|
238
|
+
const list = collapseRuns(records.map(externalRun));
|
|
239
|
+
for (const run of sessions) {
|
|
240
|
+
const row = list.find((item) => overlaps(item, run));
|
|
241
|
+
if (row) applySession(row, run);
|
|
242
|
+
else list.push(internalRun(run));
|
|
243
|
+
}
|
|
244
|
+
list.sort((left, right) => right.startedAt - left.startedAt);
|
|
245
|
+
return list.slice(0, RUNS_SHOWN);
|
|
208
246
|
};
|
|
209
247
|
|
|
210
248
|
const taskKindCounts = (store) => {
|
|
211
249
|
const kinds = TASK_KINDS.map((kind) => ({
|
|
212
250
|
id: kind.id,
|
|
213
|
-
title: kind.
|
|
251
|
+
title: kind.short,
|
|
214
252
|
done: 0,
|
|
215
253
|
total: 0,
|
|
216
254
|
}));
|
|
217
255
|
const byId = new Map(kinds.map((row) => [row.id, row]));
|
|
218
256
|
let done = 0;
|
|
219
257
|
let total = 0;
|
|
220
|
-
|
|
258
|
+
const file = store && store.reviewPath ? store.reviewPath : '';
|
|
259
|
+
const plan = file ? path.basename(file) : '';
|
|
260
|
+
if (!store) return { kinds, done, total, plan };
|
|
221
261
|
for (const task of store.tasks) {
|
|
222
|
-
if (
|
|
262
|
+
if (!trimText(task.text)) continue;
|
|
223
263
|
const row = byId.get(taskKind(task));
|
|
224
264
|
row.total += 1;
|
|
225
265
|
total += 1;
|
|
@@ -227,7 +267,7 @@ const taskKindCounts = (store) => {
|
|
|
227
267
|
row.done += 1;
|
|
228
268
|
done += 1;
|
|
229
269
|
}
|
|
230
|
-
return { kinds, done, total };
|
|
270
|
+
return { kinds, done, total, plan };
|
|
231
271
|
};
|
|
232
272
|
|
|
233
273
|
const versionFields = (pkg, outdatedMap) => {
|
|
@@ -341,16 +381,17 @@ const buildModel = (input) => ({
|
|
|
341
381
|
commits: commitsModel(input),
|
|
342
382
|
branches: branchesModel(input),
|
|
343
383
|
npm: npmModel(input),
|
|
344
|
-
runs: mergeRuns(input.runs, input.
|
|
384
|
+
runs: mergeRuns(input.runs, input.npmRuns),
|
|
345
385
|
tasks: tasksModel(input),
|
|
386
|
+
agents: input.agents ?? { ready: true, installed: 0, total: 0, items: [] },
|
|
346
387
|
});
|
|
347
388
|
|
|
348
389
|
module.exports = {
|
|
349
390
|
MINUTE_MS,
|
|
350
391
|
minuteOf,
|
|
351
392
|
groupChanges,
|
|
393
|
+
foldSteps,
|
|
352
394
|
mergeRuns,
|
|
353
|
-
scriptName,
|
|
354
395
|
runName,
|
|
355
396
|
npmModel,
|
|
356
397
|
buildModel,
|
package/lib/dashboard/npm.js
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
+
const { jsonParse } = require('metautil');
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
const {
|
|
8
|
-
const watch = require('../session/watch.js');
|
|
9
|
-
const { MODULES_STAMP } = watch;
|
|
7
|
+
const { MANIFEST } = require('../deps/deps.js');
|
|
8
|
+
const { MODULES_STAMP } = require('./watch.js');
|
|
10
9
|
|
|
11
10
|
const MODULES = 'node_modules';
|
|
12
11
|
const MODULE_PREFIX = `${MODULES}/`;
|
|
@@ -15,7 +14,8 @@ const TOP_PACKAGES = 8;
|
|
|
15
14
|
|
|
16
15
|
const readJson = async (file) => {
|
|
17
16
|
try {
|
|
18
|
-
|
|
17
|
+
const text = await fs.promises.readFile(file, 'utf8');
|
|
18
|
+
return jsonParse(text);
|
|
19
19
|
} catch {
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
@@ -54,10 +54,12 @@ const packageName = (root, file) => {
|
|
|
54
54
|
const rankedPackages = (totals) => {
|
|
55
55
|
const list = [];
|
|
56
56
|
for (const [name, bytes] of totals) list.push({ name, bytes });
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const bySize = (left, right) => {
|
|
58
|
+
const byBytes = right.bytes - left.bytes;
|
|
59
|
+
const byName = left.name.localeCompare(right.name, 'en');
|
|
60
|
+
return byBytes || byName;
|
|
61
|
+
};
|
|
62
|
+
return list.sort(bySize);
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
const DIRECT_SECTIONS = [
|
|
@@ -205,12 +207,13 @@ const measureModules = async (root) => {
|
|
|
205
207
|
const worker = async () => {
|
|
206
208
|
while (queue.length) {
|
|
207
209
|
const dir = queue.pop();
|
|
208
|
-
let entries;
|
|
210
|
+
let entries = null;
|
|
209
211
|
try {
|
|
210
212
|
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
211
213
|
} catch {
|
|
212
|
-
|
|
214
|
+
// unreadable directory
|
|
213
215
|
}
|
|
216
|
+
if (!entries) continue;
|
|
214
217
|
const files = [];
|
|
215
218
|
for (const entry of entries) {
|
|
216
219
|
const full = path.join(dir, entry.name);
|
|
@@ -245,12 +248,13 @@ const installedCount = async (top, lock) => {
|
|
|
245
248
|
}
|
|
246
249
|
return count;
|
|
247
250
|
}
|
|
248
|
-
let entries;
|
|
251
|
+
let entries = null;
|
|
249
252
|
try {
|
|
250
253
|
entries = await fs.promises.readdir(path.join(top, MODULES));
|
|
251
254
|
} catch {
|
|
252
|
-
|
|
255
|
+
// missing modules
|
|
253
256
|
}
|
|
257
|
+
if (!entries) return 0;
|
|
254
258
|
let count = 0;
|
|
255
259
|
for (const name of entries) {
|
|
256
260
|
if (name.startsWith('.')) continue;
|
package/lib/dashboard/tree.js
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
+
const { posixRel } = require('../common/files.js');
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
const {
|
|
8
|
-
const watch = require('../session/watch.js');
|
|
9
|
-
const { UNKNOWN_PATH } = watch;
|
|
7
|
+
const { runGit } = require('../git/worktree.js');
|
|
8
|
+
const { UNKNOWN_PATH } = require('./watch.js');
|
|
10
9
|
|
|
11
10
|
const ROOT_DIR = '.';
|
|
12
11
|
const NO_EXT = '(none)';
|
|
@@ -15,11 +14,9 @@ const BINARY_PROBE = 8000;
|
|
|
15
14
|
const CONCURRENCY = 32;
|
|
16
15
|
const HEAT_MS = 4000;
|
|
17
16
|
const RECENT_KEEP = 64;
|
|
18
|
-
const SKIP_WALK =
|
|
17
|
+
const SKIP_WALK = ['.git', 'node_modules'];
|
|
19
18
|
const SKIP_PREFIX = ['.git/', 'node_modules/'];
|
|
20
19
|
|
|
21
|
-
const slash = (rel) => `${rel}`.replaceAll('\\', '/');
|
|
22
|
-
|
|
23
20
|
const folderOf = (rel) => {
|
|
24
21
|
const at = rel.indexOf('/');
|
|
25
22
|
return at < 0 ? ROOT_DIR : rel.slice(0, at);
|
|
@@ -79,16 +76,17 @@ const readEntry = async (root, rel, prev, at) => {
|
|
|
79
76
|
|
|
80
77
|
const walkFiles = async (root, dir = '') => {
|
|
81
78
|
const found = [];
|
|
82
|
-
let entries;
|
|
79
|
+
let entries = null;
|
|
83
80
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
const dirPath = path.join(root, dir);
|
|
82
|
+
const types = { withFileTypes: true };
|
|
83
|
+
entries = await fs.promises.readdir(dirPath, types);
|
|
87
84
|
} catch {
|
|
88
|
-
|
|
85
|
+
// unreadable directory
|
|
89
86
|
}
|
|
87
|
+
if (!entries) return found;
|
|
90
88
|
for (const entry of entries) {
|
|
91
|
-
if (SKIP_WALK.
|
|
89
|
+
if (SKIP_WALK.includes(entry.name)) continue;
|
|
92
90
|
const rel = dir ? `${dir}/${entry.name}` : entry.name;
|
|
93
91
|
if (entry.isFile()) found.push(rel);
|
|
94
92
|
if (!entry.isDirectory()) continue;
|
|
@@ -101,9 +99,9 @@ const walkFiles = async (root, dir = '') => {
|
|
|
101
99
|
const listProject = async (root) => {
|
|
102
100
|
const args = ['ls-files', '-z', '--cached', '--others', '--exclude-standard'];
|
|
103
101
|
try {
|
|
104
|
-
const result = await
|
|
102
|
+
const result = await runGit(args, root);
|
|
105
103
|
if (result.status === 0) {
|
|
106
|
-
return result.stdout.split('\0').filter(Boolean).map(
|
|
104
|
+
return result.stdout.split('\0').filter(Boolean).map(posixRel);
|
|
107
105
|
}
|
|
108
106
|
} catch {
|
|
109
107
|
// not a git repository: walk the tree
|
|
@@ -115,9 +113,9 @@ const ignoredAmong = async (root, rels) => {
|
|
|
115
113
|
if (!rels.length) return new Set();
|
|
116
114
|
try {
|
|
117
115
|
const args = ['check-ignore', '-z', '--', ...rels];
|
|
118
|
-
const result = await
|
|
116
|
+
const result = await runGit(args, root);
|
|
119
117
|
if (result.status !== 0 && result.status !== 1) return new Set();
|
|
120
|
-
return new Set(result.stdout.split('\0').filter(Boolean).map(
|
|
118
|
+
return new Set(result.stdout.split('\0').filter(Boolean).map(posixRel));
|
|
121
119
|
} catch {
|
|
122
120
|
return new Set();
|
|
123
121
|
}
|
|
@@ -132,8 +130,11 @@ const groupOf = (map, key) => {
|
|
|
132
130
|
return group;
|
|
133
131
|
};
|
|
134
132
|
|
|
135
|
-
const bySize = (left, right) =>
|
|
136
|
-
right.bytes - left.bytes
|
|
133
|
+
const bySize = (left, right) => {
|
|
134
|
+
const byBytes = right.bytes - left.bytes;
|
|
135
|
+
const byKey = left.key.localeCompare(right.key, 'en');
|
|
136
|
+
return byBytes || byKey;
|
|
137
|
+
};
|
|
137
138
|
|
|
138
139
|
class FileIndex {
|
|
139
140
|
constructor(root) {
|
|
@@ -209,7 +210,7 @@ class FileIndex {
|
|
|
209
210
|
async touch(paths) {
|
|
210
211
|
const rels = [];
|
|
211
212
|
for (const item of paths) {
|
|
212
|
-
const rel =
|
|
213
|
+
const rel = posixRel(item);
|
|
213
214
|
if (rel === UNKNOWN_PATH) return this.rescan();
|
|
214
215
|
if (SKIP_PREFIX.some((prefix) => rel.startsWith(prefix))) continue;
|
|
215
216
|
rels.push(rel);
|
|
@@ -239,22 +240,18 @@ class FileIndex {
|
|
|
239
240
|
const dirs = new Map();
|
|
240
241
|
const exts = new Map();
|
|
241
242
|
for (const entry of this.entries.values()) {
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
groupOf(exts, entry.ext),
|
|
246
|
-
];
|
|
243
|
+
const byDir = groupOf(dirs, entry.dir);
|
|
244
|
+
const byExt = groupOf(exts, entry.ext);
|
|
245
|
+
const groups = [total, byDir, byExt];
|
|
247
246
|
for (const group of groups) {
|
|
248
247
|
group.files += 1;
|
|
249
248
|
group.bytes += entry.size;
|
|
250
249
|
group.lines += entry.lines;
|
|
251
250
|
}
|
|
252
251
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
exts: [...exts.values()].sort(bySize),
|
|
257
|
-
};
|
|
252
|
+
const dirList = [...dirs.values()].sort(bySize);
|
|
253
|
+
const extList = [...exts.values()].sort(bySize);
|
|
254
|
+
this.cached = { total, dirs: dirList, exts: extList };
|
|
258
255
|
return this.cached;
|
|
259
256
|
}
|
|
260
257
|
|