@kb-labs/qa-core 2.93.0 → 2.96.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/dist/index.d.ts +72 -6
- package/dist/index.js +562 -1553
- package/dist/index.js.map +1 -1
- package/dist/report/index.d.ts +14 -45
- package/dist/report/index.js +180 -410
- package/dist/report/index.js.map +1 -1
- package/package.json +5 -16
- package/dist/baseline/index.d.ts +0 -27
- package/dist/baseline/index.js +0 -838
- package/dist/baseline/index.js.map +0 -1
- package/dist/categories/index.d.ts +0 -13
- package/dist/categories/index.js +0 -39
- package/dist/categories/index.js.map +0 -1
- package/dist/history/index.d.ts +0 -58
- package/dist/history/index.js +0 -361
- package/dist/history/index.js.map +0 -1
- package/dist/last-run-store-C1AkskWI.d.ts +0 -81
- package/dist/runner/index.d.ts +0 -43
- package/dist/runner/index.js +0 -789
- package/dist/runner/index.js.map +0 -1
package/dist/report/index.js
CHANGED
|
@@ -1,452 +1,222 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function buildJsonReport(results, diff, checks) {
|
|
5
|
-
const severityMap = {};
|
|
6
|
-
if (checks) {
|
|
7
|
-
for (const c of checks) {
|
|
8
|
-
severityMap[c.id] = c.severity ?? "blocker";
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
const hasBlockerFailures = Object.entries(results).some(([ct, r]) => {
|
|
12
|
-
const sev = severityMap[ct] ?? "blocker";
|
|
13
|
-
return sev !== "info" && sev !== "warning" && r.failed.length > 0;
|
|
14
|
-
});
|
|
15
|
-
const summary = {};
|
|
16
|
-
const failures = {};
|
|
17
|
-
const errors = {};
|
|
18
|
-
const blockers = [];
|
|
19
|
-
const warnings = [];
|
|
20
|
-
for (const ct of Object.keys(results)) {
|
|
21
|
-
const r = results[ct];
|
|
22
|
-
const total = r.passed.length + r.failed.length + r.skipped.length;
|
|
23
|
-
summary[ct] = {
|
|
24
|
-
total,
|
|
25
|
-
passed: r.passed.length,
|
|
26
|
-
failed: r.failed.length,
|
|
27
|
-
skipped: r.skipped.length
|
|
28
|
-
};
|
|
29
|
-
failures[ct] = [...r.failed];
|
|
30
|
-
errors[ct] = { ...r.errors };
|
|
31
|
-
if (r.failed.length > 0) {
|
|
32
|
-
const sev = severityMap[ct] ?? "blocker";
|
|
33
|
-
const items = r.failed.flatMap((target) => {
|
|
34
|
-
const detailItems = r.details?.[target];
|
|
35
|
-
if (detailItems && detailItems.length > 0) {
|
|
36
|
-
return detailItems;
|
|
37
|
-
}
|
|
38
|
-
const msg = r.errors[target];
|
|
39
|
-
return msg ? [{ target, message: msg }] : [{ target, message: `check ${ct} failed` }];
|
|
40
|
-
});
|
|
41
|
-
if (sev === "blocker") {
|
|
42
|
-
blockers.push({ check: ct, items });
|
|
43
|
-
} else if (sev === "warning") {
|
|
44
|
-
warnings.push({ check: ct, items });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
status: hasBlockerFailures ? "failed" : "passed",
|
|
50
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
51
|
-
summary,
|
|
52
|
-
failures,
|
|
53
|
-
errors,
|
|
54
|
-
baseline: diff ?? null,
|
|
55
|
-
blockers,
|
|
56
|
-
warnings
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function buildDetailedJsonReport(results, grouped, diff, checks) {
|
|
60
|
-
const base = buildJsonReport(results, diff, checks);
|
|
61
|
-
return { ...base, grouped };
|
|
1
|
+
// src/report/text-reporter.ts
|
|
2
|
+
function formatTaskName(task) {
|
|
3
|
+
return task.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
62
4
|
}
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
function label(ct) {
|
|
67
|
-
return getCheckLabel(ct);
|
|
68
|
-
}
|
|
69
|
-
function buildBaselineDiffLines(diff) {
|
|
70
|
-
const lines = [];
|
|
71
|
-
for (const ct of Object.keys(diff)) {
|
|
72
|
-
const d = diff[ct];
|
|
73
|
-
if (d.newFailures.length > 0) {
|
|
74
|
-
lines.push(`${icon(ct)} ${label(ct)}: +${d.newFailures.length} new failures`);
|
|
75
|
-
for (const pkg of d.newFailures) {
|
|
76
|
-
lines.push(` - ${pkg}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (d.fixed.length > 0) {
|
|
80
|
-
lines.push(`${icon(ct)} ${label(ct)}: -${d.fixed.length} fixed`);
|
|
81
|
-
}
|
|
5
|
+
function buildRunReport(snap) {
|
|
6
|
+
if (!snap) {
|
|
7
|
+
return [{ header: "Run", lines: ["No run data available."] }];
|
|
82
8
|
}
|
|
83
|
-
return lines;
|
|
84
|
-
}
|
|
85
|
-
function buildRunReport(results, diff) {
|
|
86
9
|
const sections = [];
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
if (r.failed.length > 5) {
|
|
102
|
-
summaryLines.push(` ... and ${r.failed.length - 5} more`);
|
|
10
|
+
const tasks = [...new Set(snap.raw.results.map((r) => r.Task))];
|
|
11
|
+
for (const task of tasks) {
|
|
12
|
+
const results = snap.raw.results.filter((r) => r.Task === task);
|
|
13
|
+
const passed = results.filter((r) => r.OK && !r.Cached).length;
|
|
14
|
+
const failed = results.filter((r) => !r.OK && !r.Cached).length;
|
|
15
|
+
const cached = results.filter((r) => r.Cached).length;
|
|
16
|
+
const failedPkgs = results.filter((r) => !r.OK && !r.Cached).map((r) => r.Package);
|
|
17
|
+
const lines = [`pass ${passed} fail ${failed} cached ${cached}`];
|
|
18
|
+
if (failedPkgs.length > 0) {
|
|
19
|
+
const shown = failedPkgs.slice(0, 5);
|
|
20
|
+
lines.push(...shown.map((p2) => ` \u2717 ${p2}`));
|
|
21
|
+
if (failedPkgs.length > 5) {
|
|
22
|
+
lines.push(` ... ${failedPkgs.length - 5} more`);
|
|
103
23
|
}
|
|
104
24
|
}
|
|
105
|
-
|
|
106
|
-
totalFailed += r.failed.length;
|
|
107
|
-
totalSkipped += r.skipped.length;
|
|
108
|
-
}
|
|
109
|
-
sections.push({ header: "QA Summary Report", lines: summaryLines });
|
|
110
|
-
if (diff) {
|
|
111
|
-
const diffLines = buildBaselineDiffLines(diff);
|
|
112
|
-
if (diffLines.length > 0) {
|
|
113
|
-
sections.push({ header: "Baseline Comparison", lines: diffLines });
|
|
114
|
-
}
|
|
25
|
+
sections.push({ header: formatTaskName(task), lines });
|
|
115
26
|
}
|
|
27
|
+
const status = snap.raw.ok ? "passed" : "failed";
|
|
28
|
+
const { total, passed: p, failed: f, cached: c } = snap.raw.summary;
|
|
116
29
|
sections.push({
|
|
117
|
-
header: "
|
|
118
|
-
lines: [
|
|
30
|
+
header: "Summary",
|
|
31
|
+
lines: [`${status.toUpperCase()} total ${total} pass ${p} fail ${f} cached ${c} (${snap.raw.elapsed})`]
|
|
119
32
|
});
|
|
120
33
|
return sections;
|
|
121
34
|
}
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
for (const entry of entries) {
|
|
126
|
-
const date = new Date(entry.timestamp).toLocaleDateString();
|
|
127
|
-
const status = entry.status === "passed" ? "PASS" : "FAIL";
|
|
128
|
-
const summary = Object.keys(entry.summary).map((ct) => {
|
|
129
|
-
const s = entry.summary[ct];
|
|
130
|
-
return `${icon(ct)} ${s.failed}F`;
|
|
131
|
-
}).join(" ");
|
|
132
|
-
lines.push(`${date} ${entry.git.commit} ${status} ${summary} ${entry.git.message.slice(0, 40)}`);
|
|
35
|
+
function buildCheckReport(snap) {
|
|
36
|
+
if (!snap) {
|
|
37
|
+
return [{ header: "Check", lines: ["No check data available."] }];
|
|
133
38
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
lines
|
|
39
|
+
const sections = [];
|
|
40
|
+
for (const [pkg, pkgData] of Object.entries(snap.raw.packages)) {
|
|
41
|
+
const issues = pkgData.issues ?? [];
|
|
42
|
+
if (issues.length === 0) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const errors = issues.filter((i) => i.severity === "error").length;
|
|
46
|
+
const warnings = issues.filter((i) => i.severity === "warning").length;
|
|
47
|
+
const infos = issues.filter((i) => i.severity === "info").length;
|
|
48
|
+
const lines = [`errors ${errors} warnings ${warnings} info ${infos}`];
|
|
49
|
+
for (const issue of issues.slice(0, 5)) {
|
|
50
|
+
lines.push(` [${issue.severity}] ${issue.check}: ${issue.message}`);
|
|
51
|
+
}
|
|
52
|
+
if (issues.length > 5) {
|
|
53
|
+
lines.push(` ... ${issues.length - 5} more`);
|
|
54
|
+
}
|
|
55
|
+
sections.push({ header: pkg, lines });
|
|
144
56
|
}
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
const last = history[history.length - 1];
|
|
148
|
-
lines.push("");
|
|
149
|
-
lines.push(`Period: ${new Date(first.timestamp).toLocaleDateString()} \u2192 ${new Date(last.timestamp).toLocaleDateString()}`);
|
|
57
|
+
if (sections.length === 0) {
|
|
58
|
+
sections.push({ header: "Check", lines: ["All packages OK."] });
|
|
150
59
|
}
|
|
151
|
-
return
|
|
60
|
+
return sections;
|
|
152
61
|
}
|
|
153
|
-
function
|
|
154
|
-
if (
|
|
155
|
-
return [{ header: "
|
|
62
|
+
function buildStatsReport(snap) {
|
|
63
|
+
if (!snap) {
|
|
64
|
+
return [{ header: "Stats", lines: ["No stats data available."] }];
|
|
156
65
|
}
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
66
|
+
const { score, grade, summary, by_category, coverage } = snap.raw;
|
|
67
|
+
const sections = [];
|
|
68
|
+
sections.push({
|
|
69
|
+
header: "Score",
|
|
70
|
+
lines: [`${score}/100 Grade: ${grade} (${summary.healthy} healthy / ${summary.warning} warning / ${summary.error} error)`]
|
|
71
|
+
});
|
|
72
|
+
const catLines = Object.entries(by_category).map(([cat, data]) => {
|
|
73
|
+
const pct = data.total > 0 ? Math.round(data.healthy / data.total * 100) : 0;
|
|
74
|
+
return ` ${cat.padEnd(20)} ${data.grade} ${pct}% (${data.healthy}/${data.total})`;
|
|
75
|
+
});
|
|
76
|
+
sections.push({ header: "By Category", lines: catLines });
|
|
77
|
+
if (Object.keys(coverage).length > 0) {
|
|
78
|
+
const covLines = Object.entries(coverage).map(([k, v]) => ` ${k.padEnd(20)} ${v.pct}% (${v.pass}/${v.total})`);
|
|
79
|
+
sections.push({ header: "Coverage", lines: covLines });
|
|
172
80
|
}
|
|
173
|
-
|
|
174
|
-
lines.push("REGRESSIONS DETECTED!");
|
|
175
|
-
return [{ header: "Regression Detection", lines }];
|
|
81
|
+
return sections;
|
|
176
82
|
}
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
83
|
+
function buildHistoryTable(history, limit = 20) {
|
|
84
|
+
const rows = [...history].reverse().slice(0, limit);
|
|
85
|
+
if (rows.length === 0) {
|
|
86
|
+
return [{ header: "History", lines: ["No history available."] }];
|
|
87
|
+
}
|
|
88
|
+
const lines = rows.map((snap) => {
|
|
89
|
+
const status = snap.raw.ok ? "pass" : "fail";
|
|
90
|
+
const tasks = snap.tasks.join(", ");
|
|
91
|
+
const commit = snap.git?.commit ?? "-------";
|
|
92
|
+
const date = new Date(snap.timestamp).toLocaleString();
|
|
93
|
+
return ` ${date} ${commit} ${status} [${tasks}]`;
|
|
94
|
+
});
|
|
95
|
+
return [{ header: `History (last ${rows.length})`, lines }];
|
|
96
|
+
}
|
|
97
|
+
function buildTrendsReport(analysis) {
|
|
98
|
+
if (analysis.tasks.length === 0) {
|
|
99
|
+
return [{ header: "Trends", lines: ["Not enough history for trend analysis."] }];
|
|
180
100
|
}
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
for (const pkg of shown) {
|
|
192
|
-
lines.push(` - ${pkg}`);
|
|
101
|
+
const sections = [];
|
|
102
|
+
for (const trend of analysis.tasks) {
|
|
103
|
+
const arrow = trend.direction === "regression" ? "\u2191" : trend.direction === "improvement" ? "\u2193" : "\u2192";
|
|
104
|
+
const lines = [
|
|
105
|
+
`${arrow} ${trend.direction} prev ${trend.previous} curr ${trend.current} delta ${trend.delta > 0 ? "+" : ""}${trend.delta} velocity ${trend.velocity}`
|
|
106
|
+
];
|
|
107
|
+
if (trend.changelog.length > 0) {
|
|
108
|
+
const last = trend.changelog[trend.changelog.length - 1];
|
|
109
|
+
if (last.newFailures.length > 0) {
|
|
110
|
+
lines.push(` last regression: ${last.newFailures.slice(0, 3).join(", ")}`);
|
|
193
111
|
}
|
|
194
|
-
if (
|
|
195
|
-
lines.push(`
|
|
112
|
+
if (last.fixed.length > 0) {
|
|
113
|
+
lines.push(` last fixed: ${last.fixed.slice(0, 3).join(", ")}`);
|
|
196
114
|
}
|
|
197
115
|
}
|
|
116
|
+
sections.push({ header: formatTaskName(trend.task), lines });
|
|
198
117
|
}
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
function checkTag(status, ct) {
|
|
202
|
-
const short = ct === "typeCheck" ? "types" : ct;
|
|
203
|
-
if (status === "failed") {
|
|
204
|
-
return short.toUpperCase();
|
|
205
|
-
}
|
|
206
|
-
if (status === "skipped") {
|
|
207
|
-
return `-${short}-`;
|
|
208
|
-
}
|
|
209
|
-
return short;
|
|
210
|
-
}
|
|
211
|
-
function getErrorPreview(raw) {
|
|
212
|
-
const errLines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
213
|
-
for (const el of errLines) {
|
|
214
|
-
const cleaned = el.replace(/^Command failed: .*/, "").trim();
|
|
215
|
-
if (cleaned.length > 0) {
|
|
216
|
-
return cleaned.replace(/\/[^\s]*\/kb-labs\//g, "").slice(0, 100);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return "";
|
|
118
|
+
return sections;
|
|
220
119
|
}
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const tags = Object.keys(pkg.checks).map((ct) => checkTag(pkg.checks[ct], ct)).join(" ");
|
|
225
|
-
lines.push(` ${status} ${pkg.name.padEnd(40)} ${tags}`);
|
|
226
|
-
if (hasFail) {
|
|
227
|
-
for (const ct of Object.keys(pkg.checks)) {
|
|
228
|
-
if (pkg.checks[ct] === "failed") {
|
|
229
|
-
const preview = getErrorPreview((pkg.errors[ct] ?? "").trim());
|
|
230
|
-
lines.push(` ${ct}: ${preview || "failed"}`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
120
|
+
function buildRegressionsReport(detection) {
|
|
121
|
+
if (!detection.hasRegressions) {
|
|
122
|
+
return [{ header: "Regressions", lines: ["No regressions detected."] }];
|
|
233
123
|
}
|
|
124
|
+
const sections = [];
|
|
125
|
+
for (const reg of detection.regressions) {
|
|
126
|
+
const lines = [
|
|
127
|
+
`delta +${reg.delta}`,
|
|
128
|
+
...reg.newFailures.slice(0, 5).map((p) => ` \u2717 ${p}`)
|
|
129
|
+
];
|
|
130
|
+
if (reg.newFailures.length > 5) {
|
|
131
|
+
lines.push(` ... ${reg.newFailures.length - 5} more`);
|
|
132
|
+
}
|
|
133
|
+
sections.push({ header: formatTaskName(reg.task), lines });
|
|
134
|
+
}
|
|
135
|
+
const prev = new Date(detection.comparedAt.previous).toLocaleString();
|
|
136
|
+
const curr = new Date(detection.comparedAt.current).toLocaleString();
|
|
137
|
+
sections.push({ header: "Compared", lines: [`${prev} \u2192 ${curr}`] });
|
|
138
|
+
return sections;
|
|
234
139
|
}
|
|
235
|
-
function
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
for (const pkg of sorted) {
|
|
250
|
-
renderPackageLines(pkg, lines);
|
|
251
|
-
}
|
|
252
|
-
lines.push("");
|
|
253
|
-
}
|
|
254
|
-
return lines;
|
|
255
|
-
}
|
|
256
|
-
function buildDetailedRunReport(grouped, diff) {
|
|
140
|
+
function buildBaselineReport(baseline) {
|
|
141
|
+
if (!baseline) {
|
|
142
|
+
return [{ header: "Baseline", lines: ["No baseline set. Run `qa baseline update` to set one."] }];
|
|
143
|
+
}
|
|
144
|
+
const { score, grade } = baseline.stats;
|
|
145
|
+
const totalIssues = Object.values(baseline.check.packages).reduce((sum, pkg) => sum + (pkg.issues?.length ?? 0), 0);
|
|
146
|
+
const commit = baseline.git?.commit ?? "unknown";
|
|
147
|
+
const date = new Date(baseline.timestamp).toLocaleString();
|
|
148
|
+
return [{
|
|
149
|
+
header: "Baseline",
|
|
150
|
+
lines: [`score ${score}/100 grade ${grade} issues ${totalIssues} commit ${commit} set ${date}`]
|
|
151
|
+
}];
|
|
152
|
+
}
|
|
153
|
+
function buildBaselineDiffReport(diff) {
|
|
257
154
|
const sections = [];
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
if (b === "uncategorized") {
|
|
263
|
-
return -1;
|
|
264
|
-
}
|
|
265
|
-
return a.localeCompare(b);
|
|
155
|
+
const scoreDir = diff.scoreDelta > 0 ? "+" : "";
|
|
156
|
+
sections.push({
|
|
157
|
+
header: "Score Delta",
|
|
158
|
+
lines: [`${scoreDir}${diff.scoreDelta} ${diff.gradeDelta} new ${diff.newIssueCount} fixed ${diff.fixedIssueCount}`]
|
|
266
159
|
});
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
160
|
+
if (diff.newIssues.length > 0) {
|
|
161
|
+
const lines = diff.newIssues.slice(0, 10).map((i) => ` [${i.severity}] ${i.pkg} ${i.check}: ${i.message}`);
|
|
162
|
+
if (diff.newIssues.length > 10) {
|
|
163
|
+
lines.push(` ... ${diff.newIssues.length - 10} more`);
|
|
164
|
+
}
|
|
165
|
+
sections.push({ header: "New Issues", lines });
|
|
270
166
|
}
|
|
271
|
-
if (diff) {
|
|
272
|
-
const
|
|
273
|
-
if (
|
|
274
|
-
|
|
167
|
+
if (diff.fixedIssues.length > 0) {
|
|
168
|
+
const lines = diff.fixedIssues.slice(0, 10).map((i) => ` [${i.severity}] ${i.pkg} ${i.check}: ${i.message}`);
|
|
169
|
+
if (diff.fixedIssues.length > 10) {
|
|
170
|
+
lines.push(` ... ${diff.fixedIssues.length - 10} more`);
|
|
275
171
|
}
|
|
172
|
+
sections.push({ header: "Fixed Issues", lines });
|
|
276
173
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
for (const catKey of categoryKeys) {
|
|
280
|
-
totalPassed += grouped.categories[catKey].summary.passed;
|
|
281
|
-
totalFailed += grouped.categories[catKey].summary.failed;
|
|
174
|
+
if (diff.persistingIssues.length > 0) {
|
|
175
|
+
sections.push({ header: "Persisting Issues", lines: [`${diff.persistingIssues.length} issues unchanged`] });
|
|
282
176
|
}
|
|
283
|
-
sections.push({
|
|
284
|
-
header: "Totals",
|
|
285
|
-
lines: [`Total: ${totalPassed} passed, ${totalFailed} failed (${categoryKeys.length} categories)`]
|
|
286
|
-
});
|
|
287
177
|
return sections;
|
|
288
178
|
}
|
|
289
179
|
|
|
290
|
-
// src/report/
|
|
291
|
-
function
|
|
292
|
-
const checks = {};
|
|
293
|
-
for (const ct of checkTypes) {
|
|
294
|
-
checks[ct] = { passed: 0, failed: 0, skipped: 0 };
|
|
295
|
-
}
|
|
296
|
-
return { total: 0, passed: 0, failed: 0, checks };
|
|
297
|
-
}
|
|
298
|
-
function resolveCheckStatus(pkgName, ct, results) {
|
|
299
|
-
const r = results[ct];
|
|
300
|
-
if (!r) {
|
|
301
|
-
return "skipped";
|
|
302
|
-
}
|
|
303
|
-
if (r.failed.includes(pkgName)) {
|
|
304
|
-
return "failed";
|
|
305
|
-
}
|
|
306
|
-
if (r.passed.includes(pkgName)) {
|
|
307
|
-
return "passed";
|
|
308
|
-
}
|
|
309
|
-
return "skipped";
|
|
310
|
-
}
|
|
311
|
-
function buildPackageStatus(pkg, results, category) {
|
|
312
|
-
const checks = {};
|
|
313
|
-
const errors = {};
|
|
314
|
-
for (const ct of Object.keys(results)) {
|
|
315
|
-
checks[ct] = resolveCheckStatus(pkg.name, ct, results);
|
|
316
|
-
if (checks[ct] === "failed" && results[ct]?.errors[pkg.name]) {
|
|
317
|
-
errors[ct] = results[ct].errors[pkg.name];
|
|
318
|
-
}
|
|
319
|
-
}
|
|
180
|
+
// src/report/json-reporter.ts
|
|
181
|
+
function buildRunJsonReport(snap) {
|
|
320
182
|
return {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
183
|
+
id: snap.id,
|
|
184
|
+
timestamp: snap.timestamp,
|
|
185
|
+
git: snap.git,
|
|
186
|
+
durationMs: snap.durationMs,
|
|
187
|
+
tasks: snap.tasks,
|
|
188
|
+
ok: snap.raw.ok,
|
|
189
|
+
elapsed: snap.raw.elapsed,
|
|
190
|
+
summary: snap.raw.summary,
|
|
191
|
+
results: snap.raw.results
|
|
326
192
|
};
|
|
327
193
|
}
|
|
328
|
-
function
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const s = status.checks[ct];
|
|
338
|
-
if (!summary.checks[ct]) {
|
|
339
|
-
summary.checks[ct] = { passed: 0, failed: 0, skipped: 0 };
|
|
340
|
-
}
|
|
341
|
-
if (s === "passed") {
|
|
342
|
-
summary.checks[ct].passed++;
|
|
343
|
-
} else if (s === "failed") {
|
|
344
|
-
summary.checks[ct].failed++;
|
|
345
|
-
} else {
|
|
346
|
-
summary.checks[ct].skipped++;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
function groupResults(results, packages, categoryMap, config) {
|
|
351
|
-
const checkTypes = Object.keys(results);
|
|
352
|
-
const grouped = { categories: {} };
|
|
353
|
-
for (const pkg of packages) {
|
|
354
|
-
const categoryKeys = categoryMap.get(pkg.name) ?? ["uncategorized"];
|
|
355
|
-
for (const categoryKey of categoryKeys) {
|
|
356
|
-
const status = buildPackageStatus(pkg, results, categoryKey);
|
|
357
|
-
if (!grouped.categories[categoryKey]) {
|
|
358
|
-
const label2 = categoryKey === "uncategorized" ? "Uncategorized" : config?.categories?.[categoryKey]?.label ?? categoryKey;
|
|
359
|
-
grouped.categories[categoryKey] = {
|
|
360
|
-
label: label2,
|
|
361
|
-
repos: {},
|
|
362
|
-
summary: emptyGroupSummary(checkTypes)
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
const categoryGroup = grouped.categories[categoryKey];
|
|
366
|
-
if (!categoryGroup.repos[pkg.repo]) {
|
|
367
|
-
categoryGroup.repos[pkg.repo] = {
|
|
368
|
-
packages: [],
|
|
369
|
-
summary: emptyGroupSummary(checkTypes)
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
const repoGroup = categoryGroup.repos[pkg.repo];
|
|
373
|
-
repoGroup.packages.push(status);
|
|
374
|
-
addToSummary(repoGroup.summary, status);
|
|
375
|
-
addToSummary(categoryGroup.summary, status);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return grouped;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// src/report/error-grouping.ts
|
|
382
|
-
function extractPattern(errorText, checkType) {
|
|
383
|
-
if (checkType === "lint") {
|
|
384
|
-
const ruleMatch = errorText.match(/(\S+\/[\w-]+|no-[\w-]+)/);
|
|
385
|
-
if (ruleMatch?.[1]) {
|
|
386
|
-
return ruleMatch[1];
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
if (checkType === "typeCheck") {
|
|
390
|
-
const tsMatch = errorText.match(/TS(\d{4,5})/);
|
|
391
|
-
if (tsMatch) {
|
|
392
|
-
return `TS${tsMatch[1]}`;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
if (checkType === "test") {
|
|
396
|
-
const failMatch = errorText.match(/FAIL\s+(\S+)/);
|
|
397
|
-
if (failMatch) {
|
|
398
|
-
return `FAIL: ${failMatch[1]}`;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
if (checkType === "build") {
|
|
402
|
-
if (errorText.includes("Cannot find module")) {
|
|
403
|
-
return "Cannot find module";
|
|
404
|
-
}
|
|
405
|
-
if (errorText.includes("Module not found")) {
|
|
406
|
-
return "Module not found";
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
const firstLine = errorText.split("\n").find((l) => l.trim().length > 0)?.trim() ?? "";
|
|
410
|
-
return firstLine.slice(0, 100) || "Unknown error";
|
|
194
|
+
function buildCheckJsonReport(snap) {
|
|
195
|
+
return {
|
|
196
|
+
id: snap.id,
|
|
197
|
+
timestamp: snap.timestamp,
|
|
198
|
+
git: snap.git,
|
|
199
|
+
durationMs: snap.durationMs,
|
|
200
|
+
ok: snap.raw.ok,
|
|
201
|
+
packages: snap.raw.packages
|
|
202
|
+
};
|
|
411
203
|
}
|
|
412
|
-
function
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
existing.packages.push(pkgName);
|
|
427
|
-
} else {
|
|
428
|
-
groupMap.set(key, {
|
|
429
|
-
pattern,
|
|
430
|
-
count: 1,
|
|
431
|
-
packages: [pkgName],
|
|
432
|
-
checkType: ct,
|
|
433
|
-
example: errorText.slice(0, 200)
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
const groups = [];
|
|
439
|
-
for (const group of groupMap.values()) {
|
|
440
|
-
if (group.count === 1) {
|
|
441
|
-
ungrouped++;
|
|
442
|
-
} else {
|
|
443
|
-
groups.push(group);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
groups.sort((a, b) => b.count - a.count);
|
|
447
|
-
return { groups, ungrouped };
|
|
204
|
+
function buildStatsJsonReport(snap) {
|
|
205
|
+
return {
|
|
206
|
+
id: snap.id,
|
|
207
|
+
timestamp: snap.timestamp,
|
|
208
|
+
git: snap.git,
|
|
209
|
+
durationMs: snap.durationMs,
|
|
210
|
+
ok: snap.raw.ok,
|
|
211
|
+
score: snap.raw.score,
|
|
212
|
+
grade: snap.raw.grade,
|
|
213
|
+
summary: snap.raw.summary,
|
|
214
|
+
by_category: snap.raw.by_category,
|
|
215
|
+
issues_by_type: snap.raw.issues_by_type,
|
|
216
|
+
coverage: snap.raw.coverage
|
|
217
|
+
};
|
|
448
218
|
}
|
|
449
219
|
|
|
450
|
-
export { buildBaselineReport,
|
|
220
|
+
export { buildBaselineDiffReport, buildBaselineReport, buildCheckJsonReport, buildCheckReport, buildHistoryTable, buildRegressionsReport, buildRunJsonReport, buildRunReport, buildStatsJsonReport, buildStatsReport, buildTrendsReport, formatTaskName };
|
|
451
221
|
//# sourceMappingURL=index.js.map
|
|
452
222
|
//# sourceMappingURL=index.js.map
|