headlamp 0.1.21 → 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.
@@ -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.21",
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 700ms -x \"npm run build:yalc\""
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 { build } from 'esbuild';
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 });