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,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"label": "Project Tasks: Feature requests, Improvements, Bug reports, Refactoring, Research, Security",
|
|
3
|
+
"kinds": [
|
|
4
|
+
{
|
|
5
|
+
"id": "features",
|
|
6
|
+
"title": "Feature requests and Enhancements",
|
|
7
|
+
"short": "Features"
|
|
8
|
+
},
|
|
9
|
+
{ "id": "improvements", "title": "Improvements", "short": "Improvements" },
|
|
10
|
+
{ "id": "bugs", "title": "Bug Reports and Fixes", "short": "Bug Reports" },
|
|
11
|
+
{
|
|
12
|
+
"id": "debt",
|
|
13
|
+
"title": "Refactoring and Technical debt",
|
|
14
|
+
"short": "Refactoring"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "research",
|
|
18
|
+
"title": "Research and Experiments",
|
|
19
|
+
"short": "Research"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "security",
|
|
23
|
+
"title": "Security and Compliance",
|
|
24
|
+
"short": "Security"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -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,57 +1,33 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const { TASK_KINDS, taskKind } = files;
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { trimText } = require('../common/utilities.js');
|
|
5
|
+
const { ageSeconds } = require('../common/format.js');
|
|
7
6
|
|
|
7
|
+
const { folderOf, extOf } = require('./tree.js');
|
|
8
|
+
const { TASK_KINDS, taskKind } = require('../common/files.js');
|
|
9
|
+
|
|
10
|
+
const MINUTE_MS = 60_000;
|
|
8
11
|
const RUNS_SHOWN = 6;
|
|
9
12
|
const CHAIN_GAP_MS = 2000;
|
|
10
13
|
const NPM_NAMED = /^npm (?:run|exec)(?:\s+-s)?\s+(\S+)/;
|
|
11
14
|
const NPM_ALIAS = /^npm (test|start|stop|restart)(?:\s|$)/;
|
|
12
|
-
const AGE_TEXT = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
13
|
-
const AGE_SEC = {
|
|
14
|
-
second: 1,
|
|
15
|
-
minute: 60,
|
|
16
|
-
hour: 3600,
|
|
17
|
-
day: 86400,
|
|
18
|
-
week: 604800,
|
|
19
|
-
month: 2592000,
|
|
20
|
-
year: 31536000,
|
|
21
|
-
};
|
|
22
15
|
const STATUS_RANK = { running: 3, failed: 2, aborted: 1, passed: 0 };
|
|
23
16
|
|
|
24
17
|
const namedScript = (command) => {
|
|
25
|
-
const text =
|
|
18
|
+
const text = trimText(command);
|
|
26
19
|
const named = NPM_NAMED.exec(text);
|
|
27
20
|
if (named) return named[1];
|
|
28
21
|
const alias = NPM_ALIAS.exec(text);
|
|
29
22
|
return alias ? alias[1] : '';
|
|
30
23
|
};
|
|
31
24
|
|
|
32
|
-
const scriptName = (command) => {
|
|
33
|
-
const named = namedScript(command);
|
|
34
|
-
if (named) return named;
|
|
35
|
-
const text = `${command ?? ''}`.trim();
|
|
36
|
-
const token = text.split(/\s+/)[0];
|
|
37
|
-
const slash = Math.max(token.lastIndexOf('/'), token.lastIndexOf('\\'));
|
|
38
|
-
return token.slice(slash + 1) || text;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
25
|
const runName = (record) => {
|
|
42
|
-
const command =
|
|
43
|
-
const script =
|
|
26
|
+
const command = trimText(record.command);
|
|
27
|
+
const script = trimText(record.script);
|
|
44
28
|
return namedScript(command) || script || command;
|
|
45
29
|
};
|
|
46
30
|
|
|
47
|
-
const ageSeconds = (text) => {
|
|
48
|
-
const raw = `${text ?? ''}`.trim();
|
|
49
|
-
if (/^yesterday$/i.test(raw)) return AGE_SEC.day;
|
|
50
|
-
const match = AGE_TEXT.exec(raw);
|
|
51
|
-
if (!match) return Number.POSITIVE_INFINITY;
|
|
52
|
-
return Number(match[1]) * AGE_SEC[match[2].toLowerCase()];
|
|
53
|
-
};
|
|
54
|
-
|
|
55
31
|
const newerDate = (current, next) => {
|
|
56
32
|
if (!next) return current;
|
|
57
33
|
if (!current || ageSeconds(next) < ageSeconds(current)) return next;
|
|
@@ -129,17 +105,32 @@ const externalRun = (record) => {
|
|
|
129
105
|
};
|
|
130
106
|
};
|
|
131
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
|
+
|
|
132
125
|
const internalRun = (run) => ({
|
|
133
126
|
source: 'reslop',
|
|
134
|
-
name:
|
|
127
|
+
name: sessionName(run),
|
|
135
128
|
status: npmRunStatus(run),
|
|
136
129
|
exit: run.exit,
|
|
137
130
|
startedAt: run.startedAt,
|
|
138
131
|
endedAt: run.endedAt,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
expected: 0,
|
|
142
|
-
result: null,
|
|
132
|
+
...progressOf(run),
|
|
133
|
+
result: run.result ?? null,
|
|
143
134
|
});
|
|
144
135
|
|
|
145
136
|
const rankOf = (status) => STATUS_RANK[status] ?? 0;
|
|
@@ -174,6 +165,26 @@ const continuesRun = (prev, next) => {
|
|
|
174
165
|
return next.startedAt <= prev.endedAt + CHAIN_GAP_MS;
|
|
175
166
|
};
|
|
176
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
|
+
|
|
177
188
|
const absorbRun = (prev, next) => {
|
|
178
189
|
const open = isOpen(prev) || isOpen(next);
|
|
179
190
|
if (rankOf(next.status) >= rankOf(prev.status)) {
|
|
@@ -200,25 +211,55 @@ const collapseRuns = (list) => {
|
|
|
200
211
|
return groups.sort((left, right) => right.startedAt - left.startedAt);
|
|
201
212
|
};
|
|
202
213
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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);
|
|
207
246
|
};
|
|
208
247
|
|
|
209
248
|
const taskKindCounts = (store) => {
|
|
210
249
|
const kinds = TASK_KINDS.map((kind) => ({
|
|
211
250
|
id: kind.id,
|
|
212
|
-
title: kind.
|
|
251
|
+
title: kind.short,
|
|
213
252
|
done: 0,
|
|
214
253
|
total: 0,
|
|
215
254
|
}));
|
|
216
255
|
const byId = new Map(kinds.map((row) => [row.id, row]));
|
|
217
256
|
let done = 0;
|
|
218
257
|
let total = 0;
|
|
219
|
-
|
|
258
|
+
const file = store && store.reviewPath ? store.reviewPath : '';
|
|
259
|
+
const plan = file ? path.basename(file) : '';
|
|
260
|
+
if (!store) return { kinds, done, total, plan };
|
|
220
261
|
for (const task of store.tasks) {
|
|
221
|
-
if (
|
|
262
|
+
if (!trimText(task.text)) continue;
|
|
222
263
|
const row = byId.get(taskKind(task));
|
|
223
264
|
row.total += 1;
|
|
224
265
|
total += 1;
|
|
@@ -226,7 +267,7 @@ const taskKindCounts = (store) => {
|
|
|
226
267
|
row.done += 1;
|
|
227
268
|
done += 1;
|
|
228
269
|
}
|
|
229
|
-
return { kinds, done, total };
|
|
270
|
+
return { kinds, done, total, plan };
|
|
230
271
|
};
|
|
231
272
|
|
|
232
273
|
const versionFields = (pkg, outdatedMap) => {
|
|
@@ -237,12 +278,25 @@ const versionFields = (pkg, outdatedMap) => {
|
|
|
237
278
|
return { current, wanted: hit.wanted, latest: hit.latest };
|
|
238
279
|
};
|
|
239
280
|
|
|
281
|
+
const auditFields = (pkg, auditMap) => {
|
|
282
|
+
const finding = auditMap ? auditMap.get(pkg.name) : null;
|
|
283
|
+
return {
|
|
284
|
+
audit: Boolean(finding),
|
|
285
|
+
fix: finding && finding.fix ? finding.fix : '',
|
|
286
|
+
range: finding && finding.range ? finding.range : '',
|
|
287
|
+
severity: finding && finding.severity ? finding.severity : '',
|
|
288
|
+
title: finding && finding.title ? finding.title : '',
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
|
|
240
292
|
const modulesModel = (modules, auditMap, outdatedMap) => {
|
|
241
293
|
const packages = modules.packages.map((pkg) => ({
|
|
242
294
|
name: pkg.name,
|
|
243
295
|
bytes: pkg.bytes,
|
|
244
296
|
dev: pkg.dev,
|
|
245
|
-
|
|
297
|
+
transitive: Boolean(pkg.transitive),
|
|
298
|
+
chain: pkg.chain || '',
|
|
299
|
+
...auditFields(pkg, auditMap),
|
|
246
300
|
...versionFields(pkg, outdatedMap),
|
|
247
301
|
}));
|
|
248
302
|
return { ...modules, packages };
|
|
@@ -266,6 +320,8 @@ const npmModel = (input) => {
|
|
|
266
320
|
};
|
|
267
321
|
};
|
|
268
322
|
|
|
323
|
+
const minuteOf = (at) => Math.floor((at || 0) / MINUTE_MS);
|
|
324
|
+
|
|
269
325
|
const filesModel = (input) => {
|
|
270
326
|
const index = input.index;
|
|
271
327
|
const ready = Boolean(index && index.ready);
|
|
@@ -288,7 +344,7 @@ const diffsModel = (input) => {
|
|
|
288
344
|
dirs: groups.dirs,
|
|
289
345
|
exts: groups.exts,
|
|
290
346
|
files: input.entries.length,
|
|
291
|
-
|
|
347
|
+
activity: input.activity ?? { minutes: [], live: false },
|
|
292
348
|
delta: input.diffDelta,
|
|
293
349
|
};
|
|
294
350
|
};
|
|
@@ -325,8 +381,18 @@ const buildModel = (input) => ({
|
|
|
325
381
|
commits: commitsModel(input),
|
|
326
382
|
branches: branchesModel(input),
|
|
327
383
|
npm: npmModel(input),
|
|
328
|
-
runs: mergeRuns(input.runs, input.
|
|
384
|
+
runs: mergeRuns(input.runs, input.npmRuns),
|
|
329
385
|
tasks: tasksModel(input),
|
|
386
|
+
agents: input.agents ?? { ready: true, installed: 0, total: 0, items: [] },
|
|
330
387
|
});
|
|
331
388
|
|
|
332
|
-
module.exports = {
|
|
389
|
+
module.exports = {
|
|
390
|
+
MINUTE_MS,
|
|
391
|
+
minuteOf,
|
|
392
|
+
groupChanges,
|
|
393
|
+
foldSteps,
|
|
394
|
+
mergeRuns,
|
|
395
|
+
runName,
|
|
396
|
+
npmModel,
|
|
397
|
+
buildModel,
|
|
398
|
+
};
|
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,13 +54,36 @@ 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);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const DIRECT_SECTIONS = [
|
|
66
|
+
'dependencies',
|
|
67
|
+
'devDependencies',
|
|
68
|
+
'optionalDependencies',
|
|
69
|
+
'peerDependencies',
|
|
70
|
+
];
|
|
71
|
+
const CHILD_SECTIONS = [
|
|
72
|
+
'dependencies',
|
|
73
|
+
'optionalDependencies',
|
|
74
|
+
'peerDependencies',
|
|
75
|
+
];
|
|
76
|
+
const CHAIN_MARK = ' 🢒 ';
|
|
77
|
+
|
|
78
|
+
const directNames = (pkg) => {
|
|
79
|
+
const names = new Set();
|
|
80
|
+
for (const section of DIRECT_SECTIONS) {
|
|
81
|
+
for (const name of declaredNames(pkg, section)) names.add(name);
|
|
82
|
+
}
|
|
83
|
+
return names;
|
|
61
84
|
};
|
|
62
85
|
|
|
63
|
-
const
|
|
86
|
+
const devNames = (pkg) => {
|
|
64
87
|
const keep = new Set([
|
|
65
88
|
...declaredNames(pkg, 'dependencies'),
|
|
66
89
|
...declaredNames(pkg, 'optionalDependencies'),
|
|
@@ -69,16 +92,84 @@ const devOnlyNames = (pkg, lock) => {
|
|
|
69
92
|
for (const name of declaredNames(pkg, 'devDependencies')) {
|
|
70
93
|
if (!keep.has(name)) names.add(name);
|
|
71
94
|
}
|
|
95
|
+
return names;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const entryName = (key) => {
|
|
99
|
+
const at = key.lastIndexOf(MODULE_PREFIX);
|
|
100
|
+
if (at < 0) return '';
|
|
101
|
+
const rest = key.slice(at + MODULE_PREFIX.length);
|
|
102
|
+
if (rest.startsWith('@')) {
|
|
103
|
+
const parts = rest.split('/');
|
|
104
|
+
if (!parts[1]) return '';
|
|
105
|
+
return `${parts[0]}/${parts[1]}`;
|
|
106
|
+
}
|
|
107
|
+
return rest.split('/')[0] || '';
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const childNames = (entry) => {
|
|
111
|
+
const names = [];
|
|
112
|
+
if (!entry || typeof entry !== 'object') return names;
|
|
113
|
+
for (const section of CHILD_SECTIONS) {
|
|
114
|
+
const map = entry[section];
|
|
115
|
+
if (!map || typeof map !== 'object') continue;
|
|
116
|
+
for (const name of Object.keys(map)) {
|
|
117
|
+
if (!names.includes(name)) names.push(name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return names;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const dependencyChildren = (lock) => {
|
|
124
|
+
const children = new Map();
|
|
72
125
|
const packages = lockPackages(lock);
|
|
73
|
-
if (!packages) return
|
|
126
|
+
if (!packages) return children;
|
|
74
127
|
for (const key of Object.keys(packages)) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
128
|
+
if (!key) continue;
|
|
129
|
+
const name = entryName(key);
|
|
130
|
+
if (!name) continue;
|
|
131
|
+
const list = children.get(name) ?? [];
|
|
132
|
+
for (const child of childNames(packages[key])) {
|
|
133
|
+
if (!list.includes(child)) list.push(child);
|
|
134
|
+
}
|
|
135
|
+
if (list.length) children.set(name, list);
|
|
80
136
|
}
|
|
81
|
-
return
|
|
137
|
+
return children;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const parentChain = (name, declared, children) => {
|
|
141
|
+
if (declared.has(name)) return '';
|
|
142
|
+
const prev = new Map();
|
|
143
|
+
const queue = [];
|
|
144
|
+
const roots = [...declared].sort();
|
|
145
|
+
for (const root of roots) {
|
|
146
|
+
if (prev.has(root)) continue;
|
|
147
|
+
prev.set(root, '');
|
|
148
|
+
queue.push(root);
|
|
149
|
+
}
|
|
150
|
+
let head = 0;
|
|
151
|
+
while (head < queue.length) {
|
|
152
|
+
const at = queue[head];
|
|
153
|
+
head += 1;
|
|
154
|
+
const kids = children.get(at);
|
|
155
|
+
if (!kids) continue;
|
|
156
|
+
for (const kid of kids) {
|
|
157
|
+
if (prev.has(kid)) continue;
|
|
158
|
+
prev.set(kid, at);
|
|
159
|
+
if (kid !== name) {
|
|
160
|
+
queue.push(kid);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const path = [];
|
|
164
|
+
let cursor = at;
|
|
165
|
+
while (cursor) {
|
|
166
|
+
path.push(cursor);
|
|
167
|
+
cursor = prev.get(cursor) || '';
|
|
168
|
+
}
|
|
169
|
+
return path.join(CHAIN_MARK);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return '';
|
|
82
173
|
};
|
|
83
174
|
|
|
84
175
|
const lockVersion = (lock, name) => {
|
|
@@ -89,16 +180,24 @@ const lockVersion = (lock, name) => {
|
|
|
89
180
|
};
|
|
90
181
|
|
|
91
182
|
const topPackages = (ranked, pkg, lock) => {
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
183
|
+
const declared = directNames(pkg);
|
|
184
|
+
const devs = devNames(pkg);
|
|
185
|
+
const children = dependencyChildren(lock);
|
|
186
|
+
const rows = ranked.map((item) => {
|
|
187
|
+
const direct = declared.has(item.name);
|
|
188
|
+
return {
|
|
189
|
+
name: item.name,
|
|
190
|
+
bytes: item.bytes,
|
|
191
|
+
dev: direct && devs.has(item.name),
|
|
192
|
+
transitive: !direct,
|
|
193
|
+
chain: parentChain(item.name, declared, children),
|
|
194
|
+
version: lockVersion(lock, item.name),
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
const runtime = rows.filter((row) => !row.dev && !row.transitive);
|
|
198
|
+
const development = rows.filter((row) => row.dev);
|
|
199
|
+
const nested = rows.filter((row) => row.transitive);
|
|
200
|
+
return [...runtime, ...development, ...nested];
|
|
102
201
|
};
|
|
103
202
|
|
|
104
203
|
const measureModules = async (root) => {
|
|
@@ -108,12 +207,13 @@ const measureModules = async (root) => {
|
|
|
108
207
|
const worker = async () => {
|
|
109
208
|
while (queue.length) {
|
|
110
209
|
const dir = queue.pop();
|
|
111
|
-
let entries;
|
|
210
|
+
let entries = null;
|
|
112
211
|
try {
|
|
113
212
|
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
114
213
|
} catch {
|
|
115
|
-
|
|
214
|
+
// unreadable directory
|
|
116
215
|
}
|
|
216
|
+
if (!entries) continue;
|
|
117
217
|
const files = [];
|
|
118
218
|
for (const entry of entries) {
|
|
119
219
|
const full = path.join(dir, entry.name);
|
|
@@ -148,12 +248,13 @@ const installedCount = async (top, lock) => {
|
|
|
148
248
|
}
|
|
149
249
|
return count;
|
|
150
250
|
}
|
|
151
|
-
let entries;
|
|
251
|
+
let entries = null;
|
|
152
252
|
try {
|
|
153
253
|
entries = await fs.promises.readdir(path.join(top, MODULES));
|
|
154
254
|
} catch {
|
|
155
|
-
|
|
255
|
+
// missing modules
|
|
156
256
|
}
|
|
257
|
+
if (!entries) return 0;
|
|
157
258
|
let count = 0;
|
|
158
259
|
for (const name of entries) {
|
|
159
260
|
if (name.startsWith('.')) continue;
|
|
@@ -190,4 +291,4 @@ const readNpmSummary = async (top, previous) => {
|
|
|
190
291
|
};
|
|
191
292
|
};
|
|
192
293
|
|
|
193
|
-
module.exports = { readNpmSummary };
|
|
294
|
+
module.exports = { TOP_PACKAGES, readNpmSummary };
|