headlamp 0.1.22 → 0.1.23
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/cli.cjs +1756 -1707
- package/dist/cli.cjs.map +4 -4
- package/dist/index.js +1747 -1698
- package/dist/index.js.map +4 -4
- package/dist/jest/environment.cjs +579 -0
- package/dist/jest/reporter.cjs +173 -0
- package/package.json +3 -3
- package/scripts/build.mjs +8 -2
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/* eslint-disable global-require */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
/* eslint-disable import/no-dynamic-require */
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const print = (payload) => {
|
|
9
|
+
try {
|
|
10
|
+
const line = `[JEST-BRIDGE-EVENT] ${JSON.stringify(payload)}`;
|
|
11
|
+
(process.stderr || process.stdout).write(`${line}\n`);
|
|
12
|
+
} catch {}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const isObject = (v) => typeof v === 'object' && v !== null;
|
|
16
|
+
const sanitizeError = (err) => {
|
|
17
|
+
if (!isObject(err)) return err;
|
|
18
|
+
const out = {};
|
|
19
|
+
const name = err.name || (err.constructor && err.constructor.name) || undefined;
|
|
20
|
+
if (name) out.name = String(name);
|
|
21
|
+
if (typeof err.message === 'string') out.message = err.message;
|
|
22
|
+
if (typeof err.stack === 'string') out.stack = err.stack;
|
|
23
|
+
if (err.code !== undefined) out.code = err.code;
|
|
24
|
+
if (err.expected !== undefined) out.expected = err.expected;
|
|
25
|
+
if (err.received !== undefined) out.received = err.received;
|
|
26
|
+
if (err.matcherResult && isObject(err.matcherResult)) {
|
|
27
|
+
const mr = err.matcherResult;
|
|
28
|
+
let messageText;
|
|
29
|
+
try {
|
|
30
|
+
messageText =
|
|
31
|
+
typeof mr.message === 'function'
|
|
32
|
+
? String(mr.message())
|
|
33
|
+
: typeof mr.message === 'string'
|
|
34
|
+
? mr.message
|
|
35
|
+
: undefined;
|
|
36
|
+
} catch {}
|
|
37
|
+
out.matcherResult = {
|
|
38
|
+
matcherName: typeof mr.matcherName === 'string' ? mr.matcherName : undefined,
|
|
39
|
+
message: messageText,
|
|
40
|
+
stack: typeof mr.stack === 'string' ? mr.stack : undefined,
|
|
41
|
+
expected: mr.expected,
|
|
42
|
+
received: mr.received,
|
|
43
|
+
actual: mr.actual,
|
|
44
|
+
pass: typeof mr.pass === 'boolean' ? mr.pass : undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (err.cause) {
|
|
48
|
+
out.cause = sanitizeError(err.cause);
|
|
49
|
+
}
|
|
50
|
+
// Copy own enumerable props to preserve custom data
|
|
51
|
+
try {
|
|
52
|
+
for (const key of Object.keys(err)) {
|
|
53
|
+
if (!(key in out)) out[key] = err[key];
|
|
54
|
+
}
|
|
55
|
+
} catch {}
|
|
56
|
+
return out;
|
|
57
|
+
};
|
|
58
|
+
const sanitizeDetail = (d) => {
|
|
59
|
+
if (typeof d === 'string') return d;
|
|
60
|
+
if (!isObject(d)) return d;
|
|
61
|
+
// Common Jest detail shapes
|
|
62
|
+
const out = {};
|
|
63
|
+
if (d.message) out.message = d.message;
|
|
64
|
+
if (d.stack) out.stack = d.stack;
|
|
65
|
+
if (d.error) out.error = sanitizeError(d.error);
|
|
66
|
+
if (d.matcherResult) {
|
|
67
|
+
out.matcherResult = sanitizeError({ matcherResult: d.matcherResult }).matcherResult;
|
|
68
|
+
}
|
|
69
|
+
if (d.expected !== undefined) out.expected = d.expected;
|
|
70
|
+
if (d.received !== undefined) out.received = d.received;
|
|
71
|
+
// Copy the rest
|
|
72
|
+
try {
|
|
73
|
+
for (const key of Object.keys(d)) {
|
|
74
|
+
if (!(key in out)) out[key] = d[key];
|
|
75
|
+
}
|
|
76
|
+
} catch {}
|
|
77
|
+
return out;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
class BridgeReporter {
|
|
81
|
+
constructor(globalConfig, options) {
|
|
82
|
+
this.out =
|
|
83
|
+
process.env.JEST_BRIDGE_OUT ||
|
|
84
|
+
(options && options.outFile) ||
|
|
85
|
+
path.join(process.cwd(), 'coverage', 'jest-run.json');
|
|
86
|
+
this.buf = { startTime: Date.now(), testResults: [], aggregated: null };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onRunStart() {
|
|
90
|
+
this.buf.startTime = Date.now();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onTestResult(_test, tr) {
|
|
94
|
+
const mapAssertion = (a) => ({
|
|
95
|
+
title: a.title,
|
|
96
|
+
fullName: a.fullName || [...(a.ancestorTitles || []), a.title].join(' '),
|
|
97
|
+
status: a.status,
|
|
98
|
+
timedOut: Boolean(
|
|
99
|
+
a.status === 'failed' &&
|
|
100
|
+
String(a.failureMessages || '')
|
|
101
|
+
.toLowerCase()
|
|
102
|
+
.includes('timed out'),
|
|
103
|
+
),
|
|
104
|
+
duration: a.duration || 0,
|
|
105
|
+
location: a.location || null,
|
|
106
|
+
failureMessages: (a.failureMessages || []).map(String),
|
|
107
|
+
failureDetails: (a.failureDetails || []).map(sanitizeDetail),
|
|
108
|
+
});
|
|
109
|
+
this.buf.testResults.push({
|
|
110
|
+
testFilePath: tr.testFilePath,
|
|
111
|
+
status: tr.numFailingTests ? 'failed' : 'passed',
|
|
112
|
+
timedOut: Boolean(
|
|
113
|
+
(tr.testExecError &&
|
|
114
|
+
/timed out/i.test(
|
|
115
|
+
String(tr.testExecError && (tr.testExecError.message || tr.testExecError)),
|
|
116
|
+
)) ||
|
|
117
|
+
/timed out/i.test(String(tr.failureMessage || '')),
|
|
118
|
+
),
|
|
119
|
+
failureMessage: tr.failureMessage || '',
|
|
120
|
+
failureDetails: (tr.failureDetails || []).map(sanitizeDetail),
|
|
121
|
+
testExecError: tr.testExecError ? sanitizeError(tr.testExecError) : null,
|
|
122
|
+
console: tr.console || null,
|
|
123
|
+
perfStats: tr.perfStats || {},
|
|
124
|
+
testResults: (tr.testResults || []).map(mapAssertion),
|
|
125
|
+
});
|
|
126
|
+
try {
|
|
127
|
+
print({
|
|
128
|
+
type: 'suiteComplete',
|
|
129
|
+
testPath: tr.testFilePath,
|
|
130
|
+
numPassingTests: tr.numPassingTests,
|
|
131
|
+
numFailingTests: tr.numFailingTests,
|
|
132
|
+
});
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
onRunComplete(_contexts, agg) {
|
|
137
|
+
// Compute timed out counts heuristically from test results & errors
|
|
138
|
+
const suiteTimedOut = (r) =>
|
|
139
|
+
Boolean(
|
|
140
|
+
(r.testExecError &&
|
|
141
|
+
/timed out/i.test(
|
|
142
|
+
String(r.testExecError && (r.testExecError.message || r.testExecError)),
|
|
143
|
+
)) ||
|
|
144
|
+
/timed out/i.test(String(r.failureMessage || '')),
|
|
145
|
+
);
|
|
146
|
+
const fileTimeouts = this.buf.testResults.filter(suiteTimedOut);
|
|
147
|
+
const testTimeouts = this.buf.testResults
|
|
148
|
+
.flatMap((r) => r.testResults || [])
|
|
149
|
+
.filter((a) => a && a.timedOut);
|
|
150
|
+
this.buf.aggregated = {
|
|
151
|
+
numTotalTestSuites: agg.numTotalTestSuites,
|
|
152
|
+
numPassedTestSuites: agg.numPassedTestSuites,
|
|
153
|
+
numFailedTestSuites: agg.numFailedTestSuites,
|
|
154
|
+
numTotalTests: agg.numTotalTests,
|
|
155
|
+
numPassedTests: agg.numPassedTests,
|
|
156
|
+
numFailedTests: agg.numFailedTests,
|
|
157
|
+
numPendingTests: agg.numPendingTests,
|
|
158
|
+
numTodoTests: agg.numTodoTests,
|
|
159
|
+
numTimedOutTests: testTimeouts.length,
|
|
160
|
+
numTimedOutTestSuites: fileTimeouts.length,
|
|
161
|
+
startTime: agg.startTime,
|
|
162
|
+
success: agg.success,
|
|
163
|
+
runTimeMs: agg.testResults.reduce(
|
|
164
|
+
(t, r) => t + Math.max(0, (r.perfStats?.end || 0) - (r.perfStats?.start || 0)),
|
|
165
|
+
0,
|
|
166
|
+
),
|
|
167
|
+
};
|
|
168
|
+
fs.mkdirSync(path.dirname(this.out), { recursive: true });
|
|
169
|
+
fs.writeFileSync(this.out, JSON.stringify(this.buf), 'utf8');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = BridgeReporter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "headlamp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "Coverage-first, runner-agnostic test UX CLI for Jest",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"format": "prettier --write .",
|
|
24
24
|
"format:check": "prettier --check .",
|
|
25
25
|
"release": "npm publish --access public",
|
|
26
|
-
"build:yalc": "npm run build && npx yalc publish && node ./scripts/print-publish-log.mjs",
|
|
27
|
-
"dev:yalc": "nodemon -w src -e ts,tsx,js,jsx -d
|
|
26
|
+
"build:yalc": "npm run build && npx yalc publish --push && node ./scripts/print-publish-log.mjs",
|
|
27
|
+
"dev:yalc": "nodemon -w src -w src/jest -e ts,tsx,js,jsx,cjs -d 300ms -x \"npm run build:yalc\""
|
|
28
28
|
},
|
|
29
29
|
"keywords": [
|
|
30
30
|
"testing",
|
package/scripts/build.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { rm, mkdir, writeFile, chmod } from 'node:fs/promises';
|
|
1
|
+
import { rm, mkdir, writeFile, chmod, cp } from 'node:fs/promises';
|
|
3
2
|
import { resolve } from 'node:path';
|
|
4
3
|
|
|
4
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
5
|
+
import { build } from 'esbuild';
|
|
6
|
+
|
|
5
7
|
const root = resolve(new URL('.', import.meta.url).pathname, '..');
|
|
6
8
|
const src = resolve(root, 'src');
|
|
7
9
|
const dist = resolve(root, 'dist');
|
|
@@ -115,3 +117,7 @@ const cliPath = resolve(dist, 'cli.cjs');
|
|
|
115
117
|
const cliContent = await (await import('node:fs/promises')).readFile(cliPath, 'utf8');
|
|
116
118
|
await writeFile(cliPath, `#!/usr/bin/env node\n${cliContent}`);
|
|
117
119
|
await chmod(cliPath, 0o755);
|
|
120
|
+
|
|
121
|
+
// Copy Jest runtime assets (env/reporter) to dist so Jest can require them by absolute path
|
|
122
|
+
await mkdir(resolve(dist, 'jest'), { recursive: true });
|
|
123
|
+
await cp(resolve(src, 'jest'), resolve(dist, 'jest'), { recursive: true });
|