reslop 0.2.5 → 0.2.6
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 +12 -1
- package/LICENSE +1 -1
- package/README.md +11 -11
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -8
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +39 -47
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +332 -0
- package/lib/dashboard/npm.js +193 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +197 -402
- package/lib/deps.js +113 -240
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +76 -144
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +79 -178
- package/lib/editor.js +52 -69
- package/lib/files.js +71 -89
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +147 -272
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +141 -212
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +155 -141
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +14 -14
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +19 -25
- package/lib/highlight/txt.js +7 -10
- package/lib/highlight.js +17 -46
- package/lib/keys.js +49 -82
- package/lib/manifest.js +67 -126
- package/lib/npm-commands.js +120 -169
- package/lib/remote.js +140 -190
- package/lib/render/branches.js +63 -61
- package/lib/render/chrome.js +220 -333
- package/lib/render/commits.js +160 -361
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +245 -0
- package/lib/render/dash-table.js +290 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -258
- package/lib/render/files.js +40 -85
- package/lib/render/frame.js +78 -117
- package/lib/render/notes.js +139 -210
- package/lib/render/npm.js +213 -177
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +72 -174
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/tiles.js +260 -0
- package/lib/render/unit.js +15 -31
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -37
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -77
- package/lib/report-test.js +221 -99
- package/lib/report-tools.js +96 -154
- package/lib/review-merge.js +33 -41
- package/lib/review.js +68 -129
- package/lib/runs.js +225 -0
- package/lib/select.js +53 -91
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +61 -43
- package/lib/session/branches.js +166 -222
- package/lib/session/change-actions.js +49 -119
- package/lib/session/commit.js +274 -355
- package/lib/session/compose.js +556 -714
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +162 -170
- package/lib/session/files-pane.js +76 -44
- package/lib/session/items.js +6 -17
- package/lib/session/lifecycle.js +393 -466
- package/lib/session/load.js +1 -14
- package/lib/session/navigation.js +2 -2
- package/lib/session/npm.js +403 -420
- package/lib/session/ops.js +5 -13
- package/lib/session/pointer.js +16 -14
- package/lib/session/progress.js +39 -30
- package/lib/session/review.js +1 -3
- package/lib/session/tasks.js +276 -235
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -132
- package/lib/session/view.js +47 -81
- package/lib/session/watch.js +196 -212
- package/lib/session.js +81 -161
- package/lib/update.js +44 -111
- package/lib/utilities.js +46 -88
- package/lib/wrap.js +29 -55
- package/package.json +1 -1
package/lib/render/unit.js
CHANGED
|
@@ -1,47 +1,31 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const detect = require('../detect.js');
|
|
4
|
-
const
|
|
4
|
+
const diffLib = require('../diff/diff.js');
|
|
5
5
|
const primitives = require('./primitives.js');
|
|
6
6
|
const diff = require('./diff.js');
|
|
7
7
|
|
|
8
|
-
const {
|
|
9
|
-
const { paintDiffLine, codeCursorInRows, codeEditHits } = diff;
|
|
10
|
-
const { expandSoftRows } = diff;
|
|
11
|
-
const diffLib = require('../diff/diff.js');
|
|
8
|
+
const { detectLang } = detect;
|
|
12
9
|
const { sameHunk } = diffLib;
|
|
10
|
+
const { paneResult } = primitives;
|
|
11
|
+
const { paintDiffLine, expandSoftRows, codeHits } = diff;
|
|
13
12
|
|
|
14
13
|
const paintBodyUnit = (view, width, color) => {
|
|
15
|
-
const
|
|
16
|
-
const item = view.item;
|
|
14
|
+
const { item, codeOverlay: overlay } = view;
|
|
17
15
|
const lang = detectLang(view.reviewPath ?? '');
|
|
18
|
-
const overlay = view.codeOverlay;
|
|
19
16
|
const editing =
|
|
20
|
-
view.mode === 'compose' &&
|
|
21
|
-
const
|
|
22
|
-
const
|
|
17
|
+
view.mode === 'compose' && typeof overlay?.cursor === 'number';
|
|
18
|
+
const scrollCol = (editing ? overlay.scrollCol : 0) ?? 0;
|
|
19
|
+
const lines = view.unitLines ?? [];
|
|
23
20
|
const source = editing ? lines : expandSoftRows(lines, width, false, true);
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const row = { ...line };
|
|
30
|
-
if (editing) {
|
|
31
|
-
row.mark = ' ';
|
|
32
|
-
} else if ((row.type === 'add' || row.type === 'del') && !isBlock) {
|
|
33
|
-
row.mark = ' ';
|
|
34
|
-
}
|
|
35
|
-
body[i] = paintDiffLine(row, width, color, row.origin, lang, scrollCol);
|
|
36
|
-
}
|
|
37
|
-
const edit = overlay ? overlay.cursor : null;
|
|
38
|
-
const cursor = codeCursorInRows(source, false, width, edit, scrollCol);
|
|
39
|
-
const editHits = editing ? codeEditHits(source, false, width, scrollCol) : [];
|
|
40
|
-
return paneResult({
|
|
41
|
-
body,
|
|
42
|
-
cursor,
|
|
43
|
-
editHits,
|
|
21
|
+
const body = source.map((line) => {
|
|
22
|
+
const changed = line.type === 'add' || line.type === 'del';
|
|
23
|
+
const outside = changed && !(item && sameHunk(line.item, item));
|
|
24
|
+
const row = editing || outside ? { ...line, mark: ' ' } : line;
|
|
25
|
+
return paintDiffLine(row, width, color, row.origin, lang, scrollCol);
|
|
44
26
|
});
|
|
27
|
+
const hits = codeHits(source, false, width, overlay, editing, scrollCol);
|
|
28
|
+
return paneResult({ body, ...hits });
|
|
45
29
|
};
|
|
46
30
|
|
|
47
31
|
module.exports = { paintBodyUnit };
|
package/lib/report-model.js
CHANGED
|
@@ -4,7 +4,7 @@ const ansi = require('./ansi.js');
|
|
|
4
4
|
const { stripAnsi } = ansi;
|
|
5
5
|
const commands = require('./npm-commands.js');
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const { traceLine, relativize } = commands;
|
|
8
8
|
|
|
9
9
|
const summary = () => ({
|
|
10
10
|
tests: null,
|
|
@@ -23,8 +23,10 @@ const problem = (fields = {}) => {
|
|
|
23
23
|
severity: fields.severity || 'error',
|
|
24
24
|
name: fields.name || '',
|
|
25
25
|
message: fields.message || '',
|
|
26
|
+
key: fields.key || '',
|
|
26
27
|
trace,
|
|
27
28
|
traces,
|
|
29
|
+
related: fields.related || [],
|
|
28
30
|
duration: fields.duration || '',
|
|
29
31
|
code: fields.code || '',
|
|
30
32
|
operator: fields.operator || '',
|
|
@@ -44,15 +46,16 @@ const report = (tool, fields = {}) => ({
|
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
const userTrace = (text, root) => {
|
|
47
|
-
const lines = stripAnsi(`${text ?? ''}`).split(/\r?\n/);
|
|
48
49
|
const kept = [];
|
|
49
|
-
for (const line of
|
|
50
|
-
const next =
|
|
51
|
-
if (next
|
|
52
|
-
kept.push(relativize(indentStack(next), root).trimEnd());
|
|
50
|
+
for (const line of stripAnsi(`${text ?? ''}`).split(/\r?\n/)) {
|
|
51
|
+
const next = traceLine(line, root);
|
|
52
|
+
if (next !== null) kept.push(next.trimEnd());
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
let start = 0;
|
|
55
|
+
while (start < kept.length && kept[start].trim() === '') start += 1;
|
|
56
|
+
let end = kept.length;
|
|
57
|
+
while (end > start && kept[end - 1] === '') end -= 1;
|
|
58
|
+
return kept.slice(start, end).join('\n');
|
|
56
59
|
};
|
|
57
60
|
|
|
58
61
|
const normalizeMessage = (message) => {
|
|
@@ -60,33 +63,106 @@ const normalizeMessage = (message) => {
|
|
|
60
63
|
return text.replace(/\s+\S+:\d+:\d+$/, '');
|
|
61
64
|
};
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
// Build identity from raw diagnostics, before userTrace removes Node frames.
|
|
67
|
+
const errorKey = (fields, stack, root) => {
|
|
68
|
+
const text = stripAnsi(fields.message || '');
|
|
69
|
+
const header = /^([\w.]*Error)(?: \[[^\]]+\])?:\s*/.exec(text);
|
|
70
|
+
const type = fields.type || (header ? header[1] : '');
|
|
71
|
+
const message = header ? text.slice(header[0].length) : text;
|
|
72
|
+
let origin = '';
|
|
73
|
+
for (const row of stripAnsi(stack).split(/\r?\n/)) {
|
|
74
|
+
const line = row.trim().replace(/^at\s+/, '');
|
|
75
|
+
if (/^[\w.]*Error(?: \[[^\]]+\])?:/.test(line)) continue;
|
|
76
|
+
if (line.includes('node:') || !/:\d+:\d+\)?$/.test(line)) continue;
|
|
77
|
+
const location = line.replace(/^.*\((.*:\d+:\d+)\)$/, '$1');
|
|
78
|
+
origin = relativize(location.replace(/file:\/\//g, ''), root).replace(
|
|
79
|
+
/\\/g,
|
|
80
|
+
'/',
|
|
81
|
+
);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
return JSON.stringify([
|
|
85
|
+
fields.severity || 'error',
|
|
86
|
+
type,
|
|
87
|
+
fields.code || '',
|
|
88
|
+
fields.operator || '',
|
|
89
|
+
message.replace(/\r\n/g, '\n').trim(),
|
|
90
|
+
fields.expected || '',
|
|
91
|
+
fields.actual || '',
|
|
92
|
+
origin,
|
|
93
|
+
]);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const normalizeTrace = (trace) => {
|
|
97
|
+
if (!trace) return '';
|
|
98
|
+
return `${trace}`
|
|
99
|
+
.trim()
|
|
100
|
+
.split(/\r?\n/)
|
|
101
|
+
.map((line) => line.trim())
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.map((line) => line.replace(/\\/g, '/').replace(/:\d+(?::\d+)?/g, ':X:Y'))
|
|
104
|
+
.join('\n');
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const groupKey = (item) => {
|
|
108
|
+
if (item.key) return item.key;
|
|
109
|
+
const trace = normalizeTrace(item.trace);
|
|
110
|
+
return [
|
|
65
111
|
item.severity,
|
|
66
112
|
item.code,
|
|
67
113
|
item.operator,
|
|
68
|
-
item.
|
|
69
|
-
item.actual,
|
|
70
|
-
normalizeMessage(item.message),
|
|
114
|
+
trace || normalizeMessage(item.message),
|
|
71
115
|
].join('\0');
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const relatedProblem = (item, group) => {
|
|
119
|
+
const frames = item.trace.split(/\r?\n/).map((line) => line.trim());
|
|
120
|
+
const sample = (group.traces[0] || '')
|
|
121
|
+
.split(/\r?\n/)
|
|
122
|
+
.map((line) => line.trim());
|
|
123
|
+
let index = 0;
|
|
124
|
+
while (index < frames.length && frames[index] === sample[index]) index++;
|
|
125
|
+
if (index === frames.length && item.name === group.name) return '';
|
|
126
|
+
const rows = [item.name];
|
|
127
|
+
const branch = frames[index];
|
|
128
|
+
const last = frames.at(-1) || '';
|
|
129
|
+
let location = '';
|
|
130
|
+
if (/:\d+:\d+\)?$/.test(last)) {
|
|
131
|
+
const inner = last.replace(/^.*\((.*:\d+:\d+)\)$/, '$1');
|
|
132
|
+
location = inner.replace(/^at\s+/, '');
|
|
133
|
+
}
|
|
134
|
+
if (branch) {
|
|
135
|
+
let frame = branch.replace(/^at\s+/, '');
|
|
136
|
+
if (index === frames.length - 1 && location) frame = location;
|
|
137
|
+
rows.push(frame);
|
|
138
|
+
}
|
|
139
|
+
if (index < frames.length - 1 && location) rows.push(location);
|
|
140
|
+
return rows.filter(Boolean).join(' | ');
|
|
141
|
+
};
|
|
72
142
|
|
|
73
143
|
const groupProblems = (problems) => {
|
|
74
144
|
const groups = new Map();
|
|
75
|
-
const order = [];
|
|
76
145
|
for (const item of problems) {
|
|
77
146
|
const key = groupKey(item);
|
|
78
147
|
let group = groups.get(key);
|
|
79
148
|
if (!group) {
|
|
80
|
-
group = problem({ ...item, count: 0, traces: [],
|
|
149
|
+
group = problem({ ...item, count: 0, traces: [], related: [] });
|
|
81
150
|
groups.set(key, group);
|
|
82
|
-
order.push(group);
|
|
83
151
|
}
|
|
84
152
|
group.count += 1;
|
|
85
|
-
if (item.trace && group.traces.length
|
|
153
|
+
if (item.trace && !group.traces.length) {
|
|
154
|
+
group.traces.push(item.trace);
|
|
155
|
+
} else if (group.count > 1 && group.related.length < 2) {
|
|
156
|
+
const related = relatedProblem(item, group);
|
|
157
|
+
if (related && !group.related.includes(related)) {
|
|
158
|
+
group.related.push(related);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
86
161
|
}
|
|
162
|
+
const order = [...groups.values()];
|
|
87
163
|
for (const group of order) {
|
|
88
164
|
group.trace = group.traces[0] || '';
|
|
89
|
-
group.omitted = Math.max(0, group.count - group.
|
|
165
|
+
group.omitted = Math.max(0, group.count - 1 - group.related.length);
|
|
90
166
|
}
|
|
91
167
|
return order;
|
|
92
168
|
};
|
|
@@ -98,23 +174,25 @@ const groupReport = (item) =>
|
|
|
98
174
|
problems: groupProblems(item.problems),
|
|
99
175
|
});
|
|
100
176
|
|
|
101
|
-
const
|
|
177
|
+
const toolFailed = (item) => {
|
|
178
|
+
const stats = item.summary;
|
|
179
|
+
if (stats.failed || stats.errors) return true;
|
|
180
|
+
return item.problems.some((entry) => entry.severity !== 'warning');
|
|
181
|
+
};
|
|
102
182
|
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
return
|
|
183
|
+
const keepReport = (item) => {
|
|
184
|
+
const stats = item.summary;
|
|
185
|
+
if (stats.tests || stats.passed || stats.failed || stats.skipped) return true;
|
|
186
|
+
return !!(stats.errors || stats.warnings) || item.problems.length > 0;
|
|
107
187
|
};
|
|
108
188
|
|
|
109
189
|
const envelope = (reports, exit, stderr) => {
|
|
110
|
-
const status = exit
|
|
111
|
-
const
|
|
112
|
-
const items =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
return { ok, exit: status, reports: items, stderr: stderr || '' };
|
|
190
|
+
const status = exit ?? 1;
|
|
191
|
+
const kept = reports.filter(keepReport);
|
|
192
|
+
const items = kept.map((item) =>
|
|
193
|
+
report(item.tool, { ...item, ok: !toolFailed(item) }),
|
|
194
|
+
);
|
|
195
|
+
return { ok: status === 0, exit: status, reports: items, stderr };
|
|
118
196
|
};
|
|
119
197
|
|
|
120
198
|
module.exports = {
|
|
@@ -122,8 +200,7 @@ module.exports = {
|
|
|
122
200
|
problem,
|
|
123
201
|
report,
|
|
124
202
|
userTrace,
|
|
203
|
+
errorKey,
|
|
125
204
|
groupReport,
|
|
126
|
-
reduceStderr,
|
|
127
|
-
shortTail,
|
|
128
205
|
envelope,
|
|
129
206
|
};
|
package/lib/report-parse.js
CHANGED
|
@@ -6,31 +6,26 @@ const model = require('./report-model.js');
|
|
|
6
6
|
const testParse = require('./report-test.js');
|
|
7
7
|
const toolParse = require('./report-tools.js');
|
|
8
8
|
|
|
9
|
-
const { problem, report, groupReport } = model;
|
|
10
|
-
const { reduceStderr, shortTail, envelope } = model;
|
|
9
|
+
const { problem, report, userTrace, groupReport, envelope } = model;
|
|
11
10
|
const { parseTap, parseNodeJsonl, parseTestJson, parseSpec } = testParse;
|
|
12
|
-
const { parseToolJson, parseToolText } = toolParse;
|
|
11
|
+
const { parseToolJson, parseToolText, stderrRest } = toolParse;
|
|
13
12
|
|
|
14
|
-
const
|
|
13
|
+
const COUNTS = ['tests', 'passed', 'failed', 'skipped', 'errors', 'warnings'];
|
|
15
14
|
|
|
16
15
|
const toolHint = (program, args) => {
|
|
17
|
-
const name =
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
if (name === 'eslint') return 'eslint';
|
|
21
|
-
if (name === 'tsc') return 'tsc';
|
|
22
|
-
if (name === 'prettier') return 'prettier';
|
|
16
|
+
const name = program.split(/[/\\]/).pop();
|
|
17
|
+
const joined = [name, ...args].join(' ');
|
|
18
|
+
if (name === 'eslint' || name === 'tsc' || name === 'prettier') return name;
|
|
23
19
|
if (/\baudit\b/.test(joined)) return 'audit';
|
|
24
20
|
if (/\boutdated\b/.test(joined)) return 'outdated';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (name === 'node' && (args || []).includes('--test')) return 'test';
|
|
21
|
+
const isNpm = name === 'npm' || name === 'pnpm' || name === 'npm.cmd';
|
|
22
|
+
if (isNpm && /\b(ls|list)\b/.test(joined)) return 'ls';
|
|
23
|
+
if (name === 'node' && args.includes('--test')) return 'test';
|
|
29
24
|
return '';
|
|
30
25
|
};
|
|
31
26
|
|
|
32
27
|
const parseJson = (text) => {
|
|
33
|
-
const trimmed =
|
|
28
|
+
const trimmed = text.trim();
|
|
34
29
|
if (!trimmed || (trimmed[0] !== '{' && trimmed[0] !== '[')) return null;
|
|
35
30
|
try {
|
|
36
31
|
return JSON.parse(trimmed);
|
|
@@ -46,40 +41,64 @@ const parseJsonReports = (text, root) => {
|
|
|
46
41
|
if (!data) return null;
|
|
47
42
|
const test = parseTestJson(data, root);
|
|
48
43
|
if (test) return [test];
|
|
49
|
-
|
|
50
|
-
if (tools) return tools;
|
|
51
|
-
return [];
|
|
44
|
+
return parseToolJson(data, root) || [];
|
|
52
45
|
};
|
|
53
46
|
|
|
54
47
|
const parseReports = (text, root) => {
|
|
55
48
|
const json = parseJsonReports(text, root);
|
|
56
49
|
if (json) return json;
|
|
57
|
-
const reports = [];
|
|
58
50
|
const tap = parseTap(text, root);
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
if (!tap) {
|
|
63
|
-
const spec = parseSpec(text, root);
|
|
64
|
-
if (spec) reports.push(spec);
|
|
65
|
-
}
|
|
66
|
-
return reports;
|
|
51
|
+
const spec = tap ? null : parseSpec(text, root);
|
|
52
|
+
const reports = [tap, ...parseToolText(text, root), spec];
|
|
53
|
+
return reports.filter(Boolean);
|
|
67
54
|
};
|
|
68
55
|
|
|
56
|
+
const shortTail = (text) => text.trim().split(/\r?\n/).slice(-20).join('\n');
|
|
57
|
+
|
|
69
58
|
const withHint = (reports, text, exit, hint) => {
|
|
70
|
-
if (reports.length ||
|
|
71
|
-
const
|
|
72
|
-
return [report(
|
|
59
|
+
if (reports.length || !text.trim() || exit === 0) return reports;
|
|
60
|
+
const fallback = problem({ message: shortTail(text) });
|
|
61
|
+
return [report(hint || 'test', { problems: [fallback] })];
|
|
73
62
|
};
|
|
74
63
|
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
64
|
+
const mergeSummary = (left, right) => {
|
|
65
|
+
const stats = { ...left };
|
|
66
|
+
for (const key of COUNTS) {
|
|
67
|
+
if (right[key] === null) continue;
|
|
68
|
+
stats[key] = (stats[key] ?? 0) + right[key];
|
|
69
|
+
}
|
|
70
|
+
if (!stats.duration && right.duration) stats.duration = right.duration;
|
|
71
|
+
return stats;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const mergeReports = (left, right) => {
|
|
75
|
+
const merged = left.slice();
|
|
76
|
+
for (const item of right) {
|
|
77
|
+
const at = merged.findIndex((entry) => entry.tool === item.tool);
|
|
78
|
+
if (at < 0) {
|
|
79
|
+
merged.push(item);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const prev = merged[at];
|
|
83
|
+
merged[at] = report(item.tool, {
|
|
84
|
+
summary: mergeSummary(prev.summary, item.summary),
|
|
85
|
+
problems: prev.problems.concat(item.problems),
|
|
86
|
+
});
|
|
81
87
|
}
|
|
82
|
-
return
|
|
88
|
+
return merged;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const buildDocument = (stdout, stderr, root, exit, hint) => {
|
|
92
|
+
const out = stripAnsi(stdout);
|
|
93
|
+
const err = stripAnsi(stderr);
|
|
94
|
+
const reports = mergeReports(
|
|
95
|
+
parseReports(out, root),
|
|
96
|
+
parseReports(err, root),
|
|
97
|
+
);
|
|
98
|
+
const text = out.trim() ? out : err;
|
|
99
|
+
const grouped = withHint(reports, text, exit, hint).map(groupReport);
|
|
100
|
+
const rest = stderrRest(err, grouped);
|
|
101
|
+
return envelope(grouped, exit, userTrace(rest, root));
|
|
83
102
|
};
|
|
84
103
|
|
|
85
104
|
module.exports = {
|
package/lib/report-render.js
CHANGED
|
@@ -1,78 +1,111 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const utilities = require('./utilities.js');
|
|
4
|
+
const { unquote } = utilities;
|
|
5
|
+
|
|
3
6
|
const NOTICE_HEAD =
|
|
4
7
|
'reslop: reduced output; passing results are omitted ' +
|
|
5
8
|
'and similar problems are grouped';
|
|
6
9
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const notice = (rawFile) => {
|
|
11
|
+
const target = rawFile
|
|
12
|
+
? `the raw log \`.log/${rawFile}\``
|
|
13
|
+
: 'the matching raw log under `.log/`';
|
|
14
|
+
const hint = `Agent: if this report hides a needed detail, read ${target}.`;
|
|
15
|
+
return `${NOTICE_HEAD}\n${hint}`;
|
|
16
|
+
};
|
|
12
17
|
|
|
13
|
-
const
|
|
14
|
-
|
|
18
|
+
const addRow = (rows, key, value) => {
|
|
19
|
+
if (value === null || value === undefined || value === '') return;
|
|
20
|
+
rows.push([key, `${value}`]);
|
|
21
|
+
};
|
|
15
22
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return index + 1;
|
|
23
|
+
const isComparison = (line) => {
|
|
24
|
+
const match = /^(.*) (?:!==|===|!=|==) (\S.*)$/.exec(line.trim());
|
|
25
|
+
return !!match && match[1].trim() !== '';
|
|
20
26
|
};
|
|
21
27
|
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
index = writeRow(rows, index, 'passed', stats.passed);
|
|
26
|
-
index = writeRow(rows, index, 'failed', stats.failed);
|
|
27
|
-
index = writeRow(rows, index, 'skipped', stats.skipped);
|
|
28
|
-
index = writeRow(rows, index, 'errors', stats.errors);
|
|
29
|
-
index = writeRow(rows, index, 'warnings', stats.warnings);
|
|
30
|
-
index = writeRow(rows, index, 'duration', stats.duration);
|
|
31
|
-
rows.length = index;
|
|
32
|
-
return rows;
|
|
28
|
+
const isDiffHeader = (line) => {
|
|
29
|
+
const text = line.trim().replace(/^[+-]\s*/, '');
|
|
30
|
+
return /^actual\s+[+-]?\s*expected$/.test(text);
|
|
33
31
|
};
|
|
34
32
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
33
|
+
const isMarkedDiff = (line) => /^[+-](?:\s|$)/.test(line.trim());
|
|
34
|
+
|
|
35
|
+
const sameValue = (line, value) => {
|
|
36
|
+
if (!value) return false;
|
|
37
|
+
const text = line.trim();
|
|
38
|
+
return text === value || unquote(text) === value;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const startsDiff = (line, item) =>
|
|
42
|
+
isComparison(line) ||
|
|
43
|
+
isDiffHeader(line) ||
|
|
44
|
+
isMarkedDiff(line) ||
|
|
45
|
+
sameValue(line, item.expected) ||
|
|
46
|
+
sameValue(line, item.actual);
|
|
47
|
+
|
|
48
|
+
const displayMessage = (item) => {
|
|
49
|
+
const values = item.expected !== '' || item.actual !== '';
|
|
50
|
+
const lines = [];
|
|
51
|
+
for (const line of `${item.message}`.split('\n')) {
|
|
52
|
+
if (line.trim() === '') continue;
|
|
53
|
+
if (values && startsDiff(line, item)) break;
|
|
54
|
+
lines.push(line);
|
|
52
55
|
}
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
index = writeRow(rows, index, 'omitted', item.omitted);
|
|
56
|
+
if (lines.length && lines[0].trimEnd().endsWith(':')) {
|
|
57
|
+
lines[0] = lines[0].trimEnd().slice(0, -1);
|
|
56
58
|
}
|
|
57
|
-
|
|
59
|
+
if (lines.length && lines[0].trim() === '') lines.shift();
|
|
60
|
+
return lines.join('\n');
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const summaryRows = (stats) => {
|
|
64
|
+
const rows = [];
|
|
65
|
+
addRow(rows, 'passed', stats.passed);
|
|
66
|
+
addRow(rows, 'failed', stats.failed);
|
|
67
|
+
addRow(rows, 'skipped', stats.skipped);
|
|
68
|
+
addRow(rows, 'errors', stats.errors);
|
|
69
|
+
addRow(rows, 'warnings', stats.warnings);
|
|
70
|
+
addRow(rows, 'duration', stats.duration);
|
|
58
71
|
return rows;
|
|
59
72
|
};
|
|
60
73
|
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
|
|
74
|
+
const problemRows = (item) => {
|
|
75
|
+
const rows = [];
|
|
76
|
+
if (item.severity !== 'error') addRow(rows, 'severity', item.severity);
|
|
77
|
+
addRow(rows, 'duration', item.duration);
|
|
78
|
+
addRow(rows, 'code', item.code);
|
|
79
|
+
addRow(rows, 'operator', item.operator);
|
|
80
|
+
addRow(rows, 'expected', item.expected);
|
|
81
|
+
addRow(rows, 'actual', item.actual);
|
|
82
|
+
for (const pair of item.extra) addRow(rows, pair[0], pair[1]);
|
|
83
|
+
if (item.count > 1) addRow(rows, 'count', item.count);
|
|
84
|
+
if (item.omitted > 0) addRow(rows, 'omitted', item.omitted);
|
|
85
|
+
return rows;
|
|
65
86
|
};
|
|
66
87
|
|
|
88
|
+
const prefixLines = (prefix, text) =>
|
|
89
|
+
`${text}`
|
|
90
|
+
.replace(/\n+$/, '')
|
|
91
|
+
.split('\n')
|
|
92
|
+
.map((line) => `${prefix}${line}`);
|
|
93
|
+
|
|
94
|
+
const quoteLines = (text) => prefixLines('│ ', text);
|
|
95
|
+
|
|
67
96
|
const markdownProblem = (item) => {
|
|
68
97
|
const title = item.name || item.message || item.code || 'problem';
|
|
69
98
|
const lines = ['', `## ${title}`, ''];
|
|
70
99
|
for (const row of problemRows(item)) {
|
|
71
|
-
|
|
100
|
+
const value = row[1].includes('\n') ? `\n${row[1]}` : row[1];
|
|
101
|
+
lines.push(`- ${row[0]}: ${value}`);
|
|
72
102
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
const message = displayMessage(item);
|
|
104
|
+
if (message) lines.push('', ...prefixLines('> ', message), '');
|
|
105
|
+
if (item.trace) lines.push(`- trace:\n${item.trace}`);
|
|
106
|
+
if (item.related.length) {
|
|
107
|
+
lines.push('- related:');
|
|
108
|
+
for (const row of item.related) lines.push(` - ${row}`);
|
|
76
109
|
}
|
|
77
110
|
return lines;
|
|
78
111
|
};
|
|
@@ -82,23 +115,33 @@ const markdownReport = (item) => {
|
|
|
82
115
|
for (const row of summaryRows(item.summary)) {
|
|
83
116
|
lines.push(`${row[0]}: ${row[1]}`);
|
|
84
117
|
}
|
|
85
|
-
for (const
|
|
86
|
-
lines.push(...markdownProblem(problem));
|
|
87
|
-
}
|
|
118
|
+
for (const entry of item.problems) lines.push(...markdownProblem(entry));
|
|
88
119
|
return lines;
|
|
89
120
|
};
|
|
90
121
|
|
|
91
|
-
const
|
|
92
|
-
const lines = [
|
|
122
|
+
const renderDocument = (doc, rawFile) => {
|
|
123
|
+
const lines = [notice(rawFile)];
|
|
93
124
|
for (const item of doc.reports) lines.push(...markdownReport(item));
|
|
94
125
|
if (doc.stderr) lines.push('', '# stderr', '', doc.stderr);
|
|
126
|
+
lines.push('', `exit: ${doc.exit}`);
|
|
95
127
|
return `${lines.join('\n').replace(/\n+$/, '')}\n`;
|
|
96
128
|
};
|
|
97
129
|
|
|
98
|
-
const
|
|
130
|
+
const loggedText = (text, rawFile) => {
|
|
131
|
+
const body = `${text ?? ''}`;
|
|
132
|
+
if (body.startsWith(NOTICE_HEAD)) return body;
|
|
133
|
+
const head = notice(rawFile);
|
|
134
|
+
if (!body.trim()) return `${head}\n`;
|
|
135
|
+
return `${head}\n\n${body}`;
|
|
136
|
+
};
|
|
99
137
|
|
|
100
138
|
module.exports = {
|
|
101
|
-
|
|
102
|
-
|
|
139
|
+
NOTICE_HEAD,
|
|
140
|
+
notice,
|
|
141
|
+
summaryRows,
|
|
142
|
+
problemRows,
|
|
143
|
+
displayMessage,
|
|
144
|
+
quoteLines,
|
|
145
|
+
loggedText,
|
|
103
146
|
renderDocument,
|
|
104
147
|
};
|