monocart-coverage-reports 2.13.1 → 3.0.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/README.md +267 -114
- package/README.zh-Hans.md +268 -115
- package/lib/assets.js +6 -6
- package/lib/cli.js +191 -95
- package/lib/client/cdp-client.js +74 -43
- package/lib/client/coverage-client.js +24 -8
- package/lib/client/ws-session.js +115 -37
- package/lib/converter/ast-visitor.js +5 -7
- package/lib/converter/ast.js +6 -6
- package/lib/converter/collect-source-maps.js +119 -92
- package/lib/converter/converter.js +49 -117
- package/lib/converter/fake-source-coverage.js +64 -0
- package/lib/converter/find-original-range.js +3 -3
- package/lib/converter/flatten-source-maps.js +2 -4
- package/lib/converter/ignore.js +2 -4
- package/lib/converter/info-branch.js +5 -3
- package/lib/converter/info-function.js +5 -3
- package/lib/converter/info-statement.js +5 -3
- package/lib/converter/range-index.js +78 -0
- package/lib/converter/source-range-merge.js +103 -0
- package/lib/converter/untested.js +27 -55
- package/lib/default/options.js +13 -1
- package/lib/generate.js +44 -26
- package/lib/index.d.ts +52 -4
- package/lib/index.js +35 -41
- package/lib/istanbul/istanbul-summary.js +2 -2
- package/lib/istanbul/istanbul.js +35 -13
- package/lib/packages/chunks/chunk-RVOSNMVU.js +2 -0
- package/lib/packages/monocart-coverage-assets.js +4 -3
- package/lib/packages/monocart-coverage-converter.js +27 -0
- package/lib/packages/monocart-coverage-vendor.js +2 -41
- package/lib/packages/monocart-coverage-vendor.js.LICENSE +3 -221
- package/lib/platform/concurrency.js +47 -43
- package/lib/platform/share.js +1 -1
- package/lib/register/hooks.js +19 -13
- package/lib/register/register.js +5 -3
- package/lib/reports/codacy.js +3 -5
- package/lib/reports/codecov.js +3 -5
- package/lib/reports/console-details.js +5 -5
- package/lib/reports/console-summary.js +4 -4
- package/lib/reports/custom.js +8 -24
- package/lib/reports/markdown-details.js +4 -6
- package/lib/reports/markdown-summary.js +4 -6
- package/lib/reports/raw.js +10 -12
- package/lib/reports/v8-json.js +3 -5
- package/lib/reports/v8.js +3 -5
- package/lib/utils/dedupe.js +1 -1
- package/lib/utils/gc.js +30 -14
- package/lib/utils/markdown.js +2 -2
- package/lib/utils/merge/merge.js +2 -4
- package/lib/utils/merge/range-tree.js +1 -1
- package/lib/utils/request.js +85 -73
- package/lib/utils/snapshot.js +4 -4
- package/lib/utils/source-path.js +5 -5
- package/lib/utils/util.js +61 -41
- package/lib/v8/v8-summary.js +2 -4
- package/lib/v8/v8.js +11 -11
- package/migrate-to-3.md +175 -0
- package/package.json +153 -149
- package/skills/monocart-coverage-reports/SKILL.md +106 -0
- package/skills/monocart-coverage-reports/references/cli.md +108 -0
- package/skills/monocart-coverage-reports/references/collection.md +152 -0
- package/skills/monocart-coverage-reports/references/filtering-and-sourcemaps.md +170 -0
- package/skills/monocart-coverage-reports/references/reports-and-ci.md +219 -0
- package/skills/monocart-coverage-reports/references/repository-map.md +55 -0
- package/lib/index.mjs +0 -9
- package/lib/register/hooks.mjs +0 -3
- package/lib/register/register.mjs +0 -3
package/lib/assets.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { deflateSync } from 'lz-utils';
|
|
4
|
+
import { Util } from './utils/util.js';
|
|
5
|
+
import { assetsMap } from './packages/monocart-coverage-assets.js';
|
|
6
6
|
|
|
7
7
|
const Assets = {
|
|
8
8
|
|
|
@@ -95,4 +95,4 @@ const Assets = {
|
|
|
95
95
|
}
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
export { Assets };
|
package/lib/cli.js
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { constants } from 'node:os';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import EC from 'eight-colors';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
import { program } from 'commander';
|
|
9
|
+
import { execa } from 'execa';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
import { MCR } from './index.js';
|
|
12
|
+
import { Util } from './utils/util.js';
|
|
13
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
12
14
|
|
|
13
|
-
const version =
|
|
15
|
+
const version = pkg.version;
|
|
14
16
|
|
|
15
17
|
// quote NODE_OPTIONS argument if it contains whitespace so child processes
|
|
16
18
|
// inheriting the env var can parse it correctly
|
|
17
|
-
const quoteNodeOption = (v) => ((
|
|
19
|
+
const quoteNodeOption = (v) => ((/[\s"\\]/).test(v) ? JSON.stringify(v) : v);
|
|
18
20
|
|
|
19
21
|
// absolute path so NODE_OPTIONS keeps resolving when child processes are
|
|
20
22
|
// spawned with a different cwd than the mcr parent process
|
|
21
|
-
const getRequirePath = () => {
|
|
22
|
-
return quoteNodeOption(path.resolve(__dirname, 'register', 'register.js'));
|
|
23
|
-
};
|
|
24
|
-
|
|
25
23
|
const getImportPath = () => {
|
|
26
|
-
return quoteNodeOption(pathToFileURL(path.resolve(
|
|
24
|
+
return quoteNodeOption(pathToFileURL(path.resolve(import.meta.dirname, 'register', 'register.js')).href);
|
|
27
25
|
};
|
|
28
26
|
|
|
29
27
|
const getInitNodeOptions = async (cliOptions) => {
|
|
@@ -32,79 +30,34 @@ const getInitNodeOptions = async (cliOptions) => {
|
|
|
32
30
|
nodeOptions.push(process.env.NODE_OPTIONS);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// for load mcr.config.ts
|
|
42
|
-
await import(cliOptions.require);
|
|
33
|
+
const name = cliOptions.import || cliOptions.require;
|
|
34
|
+
if (name) {
|
|
35
|
+
const specifier = path.isAbsolute(name) || name.startsWith('.') ? pathToFileURL(path.resolve(name)).href : import.meta.resolve(name);
|
|
36
|
+
nodeOptions.push(`--import ${quoteNodeOption(specifier)}`);
|
|
37
|
+
// Load before mcr.config.ts. CJS interoperability is handled by Node.
|
|
38
|
+
await import(specifier);
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
return nodeOptions;
|
|
46
42
|
};
|
|
47
43
|
|
|
48
|
-
const getPreloadType = (nodeOptions) => {
|
|
49
|
-
const hasImport = nodeOptions.find((it) => it.includes('--import'));
|
|
50
|
-
if (hasImport) {
|
|
51
|
-
return '--import';
|
|
52
|
-
}
|
|
53
|
-
return '--require';
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const checkRegisterFeature = () => {
|
|
57
|
-
const nv = process.versions.node;
|
|
58
|
-
|
|
59
|
-
// "module.register" added in Node.js: v20.6.0
|
|
60
|
-
// if (Util.cmpVersion(nv, '20.6.0') >= 0) {
|
|
61
|
-
// return true;
|
|
62
|
-
// }
|
|
63
|
-
// but also added in: v18.19.0
|
|
64
|
-
const requiredNV = '18.19.0';
|
|
65
|
-
if (Util.cmpVersion(nv, requiredNV) < 0) {
|
|
66
|
-
Util.logInfo(`The current Node.js version "${nv}" does NOT support "module.register", it requires "${requiredNV}" or higher.`);
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// could be < 20.6.0 but just ignore it, please using latest minor version
|
|
71
|
-
|
|
72
|
-
return true;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
44
|
const loadEnv = (cliOptions) => {
|
|
76
45
|
if (!cliOptions.env) {
|
|
77
46
|
return;
|
|
78
47
|
}
|
|
79
48
|
const envFile = cliOptions.env === true ? '.env' : cliOptions.env;
|
|
80
|
-
|
|
81
|
-
if (typeof loadEnvFile === 'function') {
|
|
82
|
-
loadEnvFile(envFile);
|
|
83
|
-
}
|
|
49
|
+
process.loadEnvFile(envFile);
|
|
84
50
|
};
|
|
85
51
|
|
|
86
52
|
const initNodeOptions = async (cliOptions) => {
|
|
87
53
|
|
|
88
54
|
loadEnv(cliOptions);
|
|
89
55
|
|
|
90
|
-
const supportRegister = checkRegisterFeature();
|
|
91
|
-
if (!supportRegister) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
56
|
const nodeOptions = await getInitNodeOptions(cliOptions);
|
|
96
57
|
// console.log(nodeOptions);
|
|
97
58
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// export source after
|
|
101
|
-
if (preloadType === '--import') {
|
|
102
|
-
const importPath = getImportPath();
|
|
103
|
-
nodeOptions.unshift(`--import ${importPath}`);
|
|
104
|
-
} else {
|
|
105
|
-
const requirePath = getRequirePath();
|
|
106
|
-
nodeOptions.unshift(`--require ${requirePath}`);
|
|
107
|
-
}
|
|
59
|
+
// Register source capture before other ESM preloads.
|
|
60
|
+
nodeOptions.unshift(`--import ${getImportPath()}`);
|
|
108
61
|
|
|
109
62
|
// console.log(nodeOptions);
|
|
110
63
|
const nodeOptionsStr = nodeOptions.join(' ');
|
|
@@ -132,9 +85,88 @@ const mergeCoverage = async (cliOptions) => {
|
|
|
132
85
|
await coverageReport.generate();
|
|
133
86
|
};
|
|
134
87
|
|
|
135
|
-
|
|
88
|
+
// Only check whether targets were supplied. Node owns all target parsing,
|
|
89
|
+
// discovery and validation; preserve each argument exactly as provided.
|
|
90
|
+
const getTestTargets = (test) => {
|
|
91
|
+
if (!test || (Array.isArray(test) && !test.length)) {
|
|
92
|
+
Util.logInfo('No test targets configured; skipping tests.');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return Array.isArray(test) ? test : [test];
|
|
97
|
+
};
|
|
136
98
|
|
|
137
|
-
|
|
99
|
+
// Preserve signals and failing commands even if a hook sets exitCode=0.
|
|
100
|
+
const getExitStatus = (code, signal) => signal || code || process.exitCode || 0;
|
|
101
|
+
|
|
102
|
+
const applyExitStatus = (status) => {
|
|
103
|
+
if (typeof status === 'number') {
|
|
104
|
+
process.exitCode = status;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
process.exitCode = 128 + (constants.signals[status] || 1);
|
|
108
|
+
if (process.platform !== 'win32') {
|
|
109
|
+
// Stay alive until the signal is delivered, after coverage has flushed.
|
|
110
|
+
const timer = setTimeout(() => {}, 2000);
|
|
111
|
+
try {
|
|
112
|
+
process.kill(process.pid, status);
|
|
113
|
+
} catch {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const proxySignals = (execution) => {
|
|
120
|
+
const state = {
|
|
121
|
+
finished: false, signal: null
|
|
122
|
+
};
|
|
123
|
+
const listeners = ['SIGINT', 'SIGTERM', 'SIGHUP'].map((signal) => {
|
|
124
|
+
const listener = () => {
|
|
125
|
+
state.signal ||= signal;
|
|
126
|
+
if (!state.finished) {
|
|
127
|
+
execution.kill(signal);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
process.on(signal, listener);
|
|
131
|
+
return [signal, listener];
|
|
132
|
+
});
|
|
133
|
+
state.dispose = () => {
|
|
134
|
+
for (const [signal, listener] of listeners) {
|
|
135
|
+
process.removeListener(signal, listener);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
return state;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const logSpawnFailure = (result) => {
|
|
142
|
+
if (result.failed && !Number.isInteger(result.exitCode) && !result.signal) {
|
|
143
|
+
Util.logError(result.shortMessage || result.message);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const executeSubprocess = async (command, args, onClose) => {
|
|
148
|
+
const execution = execa(command, args, {
|
|
149
|
+
stdio: 'inherit',
|
|
150
|
+
reject: false
|
|
151
|
+
});
|
|
152
|
+
const signals = proxySignals(execution);
|
|
153
|
+
let status;
|
|
154
|
+
try {
|
|
155
|
+
const result = await execution;
|
|
156
|
+
signals.finished = true;
|
|
157
|
+
logSpawnFailure(result);
|
|
158
|
+
// Preserve the native ChildProcess API exposed to onReady hooks.
|
|
159
|
+
status = await onClose(result.exitCode ?? (result.failed ? 1 : 0), result.signal || signals.signal, execution.nodeChildProcess);
|
|
160
|
+
status = signals.signal || status;
|
|
161
|
+
} finally {
|
|
162
|
+
signals.dispose();
|
|
163
|
+
}
|
|
164
|
+
applyExitStatus(status);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const executeCommand = async ([command, ... commandArgs], cliOptions, testArgs = []) => {
|
|
168
|
+
|
|
169
|
+
Util.logInfo(`Execute: ${EC.cyan([command, ... commandArgs].join(' '))}`);
|
|
138
170
|
|
|
139
171
|
if (command === 'merge') {
|
|
140
172
|
return mergeCoverage(cliOptions);
|
|
@@ -145,11 +177,24 @@ const executeCommand = async (command, cliOptions) => {
|
|
|
145
177
|
|
|
146
178
|
// console.log(options);
|
|
147
179
|
const coverageReport = MCR(cliOptions);
|
|
180
|
+
const isTest = command === 'test';
|
|
148
181
|
await coverageReport.loadConfig(cliOptions.config);
|
|
149
|
-
coverageReport.cleanCache();
|
|
150
182
|
|
|
151
183
|
const coverageOptions = coverageReport.options;
|
|
184
|
+
let childArgs;
|
|
185
|
+
if (isTest) {
|
|
186
|
+
const targets = getTestTargets(coverageOptions.test);
|
|
187
|
+
if (!targets) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
childArgs = ['--test', ... testArgs, ... targets];
|
|
191
|
+
}
|
|
192
|
+
coverageReport.cleanCache();
|
|
152
193
|
const nodeV8CoverageDir = initNodeV8CoverageDir(coverageOptions);
|
|
194
|
+
if (isTest) {
|
|
195
|
+
// Tests may spawn children with a different working directory.
|
|
196
|
+
process.env.NODE_V8_COVERAGE = path.resolve(nodeV8CoverageDir);
|
|
197
|
+
}
|
|
153
198
|
|
|
154
199
|
// =========================================
|
|
155
200
|
// onStart hook
|
|
@@ -159,31 +204,39 @@ const executeCommand = async (command, cliOptions) => {
|
|
|
159
204
|
}
|
|
160
205
|
// =========================================
|
|
161
206
|
|
|
162
|
-
const
|
|
163
|
-
shell: true
|
|
164
|
-
}, async (code, signal) => {
|
|
207
|
+
const onClose = async (code, signal, subprocess) => {
|
|
165
208
|
|
|
166
209
|
// generate coverage even it is failed. code != 0
|
|
167
210
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
211
|
+
try {
|
|
212
|
+
// onReady hook before adding coverage data. Some children need
|
|
213
|
+
// additional time to finish writing their coverage files.
|
|
214
|
+
const onReady = coverageOptions.onReady;
|
|
215
|
+
if (typeof onReady === 'function') {
|
|
216
|
+
await onReady(coverageReport, nodeV8CoverageDir, subprocess);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
await coverageReport.addFromDir(nodeV8CoverageDir);
|
|
220
|
+
const report = await coverageReport.generate();
|
|
221
|
+
if (isTest && !report) {
|
|
222
|
+
throw new Error('No coverage report was generated');
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
Util.logError(`Coverage report generation failed: ${error.message}`);
|
|
226
|
+
process.exitCode = 1;
|
|
227
|
+
} finally {
|
|
228
|
+
if (!Util.isDebug()) {
|
|
229
|
+
Util.rmSync(nodeV8CoverageDir);
|
|
230
|
+
}
|
|
174
231
|
}
|
|
175
|
-
// =========================================
|
|
176
|
-
|
|
177
|
-
await coverageReport.addFromDir(nodeV8CoverageDir);
|
|
178
|
-
await coverageReport.generate();
|
|
179
232
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
Util.rmSync(nodeV8CoverageDir);
|
|
183
|
-
}
|
|
233
|
+
return getExitStatus(code, signal);
|
|
234
|
+
};
|
|
184
235
|
|
|
185
|
-
|
|
186
|
-
|
|
236
|
+
if (isTest) {
|
|
237
|
+
return executeSubprocess(process.execPath, childArgs, onClose);
|
|
238
|
+
}
|
|
239
|
+
return executeSubprocess(command, commandArgs, onClose);
|
|
187
240
|
|
|
188
241
|
};
|
|
189
242
|
|
|
@@ -208,11 +261,35 @@ process.argv.forEach((it) => {
|
|
|
208
261
|
}
|
|
209
262
|
});
|
|
210
263
|
|
|
264
|
+
// Also recognize `mcr -c config -- test targets -- runner-options` without
|
|
265
|
+
// falling through to the shell's unrelated `test` command. Only MCR options
|
|
266
|
+
// before the first separator are parsed in that form.
|
|
267
|
+
const getTestCommand = (args, subArgs) => {
|
|
268
|
+
if (args[0] === 'test') {
|
|
269
|
+
return {
|
|
270
|
+
targets: args.slice(1), runnerArgs: subArgs.slice(1)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (args.length || subArgs[1] !== 'test') {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const remaining = subArgs.slice(2);
|
|
277
|
+
const index = remaining.indexOf('--');
|
|
278
|
+
if (index === -1) {
|
|
279
|
+
return {
|
|
280
|
+
targets: remaining, runnerArgs: []
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
targets: remaining.slice(0, index), runnerArgs: remaining.slice(index + 1)
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
|
|
211
288
|
program
|
|
212
289
|
.name('mcr')
|
|
213
290
|
.description('CLI to generate coverage reports')
|
|
214
291
|
.version(version, '-v, --version', 'output the current version')
|
|
215
|
-
.argument('[command]', 'command to execute')
|
|
292
|
+
.argument('[command]', 'command to execute, or built-in merge/test')
|
|
216
293
|
.allowUnknownOption()
|
|
217
294
|
.allowExcessArguments()
|
|
218
295
|
.option('-c, --config <path>', 'custom config file path')
|
|
@@ -229,6 +306,7 @@ program
|
|
|
229
306
|
|
|
230
307
|
.option('--entryFilter <pattern>', 'entry url filter')
|
|
231
308
|
.option('--sourceFilter <pattern>', 'source path filter')
|
|
309
|
+
.option('--outputFilter <pattern>', 'final source path filter')
|
|
232
310
|
.option('--filter <pattern>', 'the combined filter')
|
|
233
311
|
|
|
234
312
|
.option('--outputFile <path>', 'output file for v8 report')
|
|
@@ -238,22 +316,40 @@ program
|
|
|
238
316
|
.option('--lcov', 'generate lcov.info file')
|
|
239
317
|
|
|
240
318
|
.option('--import <module>', 'preload module at startup')
|
|
241
|
-
.option('--require <module>', '
|
|
319
|
+
.option('--require <module>', 'deprecated alias for --import (ESM resolution)')
|
|
242
320
|
|
|
243
321
|
.option('--env [path]', 'env file (default: ".env")')
|
|
244
322
|
|
|
245
323
|
.action((_command, cliOptions) => {
|
|
324
|
+
const testCommand = getTestCommand(program.args, subArgv);
|
|
325
|
+
if (testCommand) {
|
|
326
|
+
// Commander has already removed MCR options and their values.
|
|
327
|
+
// Positional targets override config; only args after -- are
|
|
328
|
+
// forwarded unchanged to the Node runner.
|
|
329
|
+
const { targets, runnerArgs } = testCommand;
|
|
330
|
+
if (targets.length) {
|
|
331
|
+
cliOptions.test = targets;
|
|
332
|
+
}
|
|
333
|
+
executeCommand(['test'], cliOptions, runnerArgs).catch((error) => {
|
|
334
|
+
Util.logError(error.message);
|
|
335
|
+
process.exitCode = 1;
|
|
336
|
+
});
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
246
340
|
const args = [].concat(program.args).concat(subArgv);
|
|
247
341
|
if (args[0] === '--') {
|
|
248
342
|
args.shift();
|
|
249
343
|
}
|
|
250
|
-
|
|
251
|
-
if (!command) {
|
|
344
|
+
if (!args.length || !args[0]) {
|
|
252
345
|
program.outputHelp();
|
|
253
346
|
return;
|
|
254
347
|
}
|
|
255
348
|
|
|
256
|
-
executeCommand(
|
|
349
|
+
executeCommand(args, cliOptions).catch((error) => {
|
|
350
|
+
Util.logError(error.message);
|
|
351
|
+
process.exitCode = 1;
|
|
352
|
+
});
|
|
257
353
|
});
|
|
258
354
|
|
|
259
355
|
program.parse(argv);
|
package/lib/client/cdp-client.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
2
|
+
import EC from 'eight-colors';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const Util = require('../utils/util.js');
|
|
4
|
+
import { Util } from '../utils/util.js';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { WSSession } from './ws-session.js';
|
|
7
|
+
import { CoverageClient } from './coverage-client.js';
|
|
9
8
|
|
|
10
9
|
const getDebuggerUrl = async (options) => {
|
|
11
10
|
|
|
12
11
|
const protocol = options.secure ? 'https' : 'http';
|
|
13
12
|
|
|
14
13
|
const url = `${protocol}://${options.host}:${options.port}/json/list`;
|
|
15
|
-
const [err, res] = await Util.request(url
|
|
14
|
+
const [err, res] = await Util.request(url, {
|
|
15
|
+
timeout: options.timeout
|
|
16
|
+
});
|
|
16
17
|
if (err) {
|
|
17
18
|
return [err];
|
|
18
19
|
}
|
|
@@ -46,49 +47,78 @@ const getCDPUrl = async (options) => {
|
|
|
46
47
|
|
|
47
48
|
};
|
|
48
49
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return [null, options.session];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// create session
|
|
56
|
-
const [err, url] = await getCDPUrl(options);
|
|
57
|
-
|
|
58
|
-
return new Promise((resolve) => {
|
|
59
|
-
|
|
60
|
-
if (err) {
|
|
61
|
-
resolve([err]);
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const timeoutId = setTimeout(() => {
|
|
66
|
-
resolve([new Error(`Timeout to connect: ${url}`)]);
|
|
67
|
-
}, options.timeout);
|
|
68
|
-
|
|
50
|
+
const connectWebSocket = (WebSocket, url, options) => new Promise((resolve) => {
|
|
51
|
+
let ws;
|
|
52
|
+
try {
|
|
69
53
|
Util.logDebug(`Connect to ${url}`);
|
|
70
|
-
|
|
54
|
+
ws = new WebSocket(url, [], {
|
|
71
55
|
maxPayload: 256 * 1024 * 1024,
|
|
72
56
|
perMessageDeflate: false,
|
|
73
57
|
followRedirects: true,
|
|
74
58
|
... options.ws
|
|
75
59
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
60
|
+
} catch (error) {
|
|
61
|
+
resolve([error]);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let settled = false;
|
|
65
|
+
let timer;
|
|
66
|
+
const cleanup = () => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
ws.off('open', onOpen);
|
|
69
|
+
ws.off('error', onError);
|
|
70
|
+
ws.off('close', onClose);
|
|
71
|
+
};
|
|
72
|
+
const fail = (error) => {
|
|
73
|
+
if (settled) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
settled = true;
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
ws.off('open', onOpen);
|
|
79
|
+
resolve([error]);
|
|
80
|
+
// Keep error/close handlers through termination of a pending handshake.
|
|
81
|
+
if (ws.readyState !== 3) {
|
|
82
|
+
ws.terminate();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const onError = (error) => fail(error);
|
|
86
|
+
const onClose = () => {
|
|
87
|
+
fail(new Error(`Connection closed before opening: ${url}`));
|
|
88
|
+
cleanup();
|
|
89
|
+
};
|
|
90
|
+
const onOpen = () => {
|
|
91
|
+
const session = new WSSession(ws, {
|
|
92
|
+
timeout: options.requestTimeout
|
|
87
93
|
});
|
|
94
|
+
settled = true;
|
|
95
|
+
cleanup();
|
|
96
|
+
Util.logDebug(`${EC.green('Connected')} ${url}`);
|
|
97
|
+
resolve([null, session]);
|
|
98
|
+
};
|
|
99
|
+
ws.on('error', onError);
|
|
100
|
+
ws.once('close', onClose);
|
|
101
|
+
ws.once('open', onOpen);
|
|
102
|
+
if (options.timeout > 0) {
|
|
103
|
+
timer = setTimeout(() => fail(new Error(`Timeout to connect: ${url}`)), options.timeout);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
88
106
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
const getCDPSession = async (options) => {
|
|
108
|
+
if (options.session) {
|
|
109
|
+
return [null, options.session];
|
|
110
|
+
}
|
|
111
|
+
for (const key of ['timeout', 'requestTimeout']) {
|
|
112
|
+
if (!Number.isFinite(options[key]) || options[key] < 0) {
|
|
113
|
+
return [new RangeError(`${key} must be a non-negative finite number`)];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const [err, url] = await getCDPUrl(options);
|
|
117
|
+
if (err) {
|
|
118
|
+
return [err];
|
|
119
|
+
}
|
|
120
|
+
const { WebSocket } = await import('ws');
|
|
121
|
+
return connectWebSocket(WebSocket, url, options);
|
|
92
122
|
};
|
|
93
123
|
|
|
94
124
|
|
|
@@ -109,7 +139,8 @@ const CDPClient = async (cdpOptions) => {
|
|
|
109
139
|
return targets.find((it) => it.webSocketDebuggerUrl);
|
|
110
140
|
},
|
|
111
141
|
ws: {},
|
|
112
|
-
timeout: 10 * 1000
|
|
142
|
+
timeout: 10 * 1000,
|
|
143
|
+
requestTimeout: 30 * 1000
|
|
113
144
|
};
|
|
114
145
|
|
|
115
146
|
if (typeof cdpOptions === 'string') {
|
|
@@ -140,4 +171,4 @@ const CDPClient = async (cdpOptions) => {
|
|
|
140
171
|
|
|
141
172
|
};
|
|
142
173
|
|
|
143
|
-
|
|
174
|
+
export { CDPClient };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { Util } from '../utils/util.js';
|
|
2
2
|
|
|
3
3
|
const bindEvents = (target, events) => {
|
|
4
4
|
Object.keys(events).forEach((eventType) => {
|
|
@@ -80,6 +80,17 @@ class CoverageClient {
|
|
|
80
80
|
this.session = session;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// Best-effort background commands return null when a request fails.
|
|
84
|
+
// Keep critical commands on session.send() so their errors still propagate.
|
|
85
|
+
async safeSend(method, params) {
|
|
86
|
+
try {
|
|
87
|
+
return await this.session.send(method, params);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
Util.logDebug(`${method}: ${error?.message || error}`);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
// =================================================================================================
|
|
84
95
|
async startJSCoverage() {
|
|
85
96
|
if (!this.session || this.enabledJS) {
|
|
@@ -90,14 +101,16 @@ class CoverageClient {
|
|
|
90
101
|
this.jsEvents = {
|
|
91
102
|
'Debugger.scriptParsed': (params) => {
|
|
92
103
|
const { scriptId } = params;
|
|
93
|
-
this.
|
|
104
|
+
this.safeSend('Debugger.getScriptSource', {
|
|
94
105
|
scriptId
|
|
95
106
|
}).then((res) => {
|
|
96
|
-
|
|
107
|
+
if (res) {
|
|
108
|
+
this.scriptSources.set(scriptId, res.scriptSource);
|
|
109
|
+
}
|
|
97
110
|
});
|
|
98
111
|
},
|
|
99
112
|
'Debugger.paused': () => {
|
|
100
|
-
this.
|
|
113
|
+
this.safeSend('Debugger.resume');
|
|
101
114
|
}
|
|
102
115
|
};
|
|
103
116
|
|
|
@@ -169,11 +182,14 @@ class CoverageClient {
|
|
|
169
182
|
|
|
170
183
|
// anonymous url
|
|
171
184
|
const url = sourceURL || '';
|
|
172
|
-
this.
|
|
185
|
+
this.safeSend('CSS.getStyleSheetText', {
|
|
173
186
|
styleSheetId
|
|
174
187
|
}).then((res) => {
|
|
188
|
+
if (!res) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
175
191
|
// add source
|
|
176
|
-
const text = res
|
|
192
|
+
const text = res.text;
|
|
177
193
|
if (!text) {
|
|
178
194
|
Util.logDebug(`Not found css source: ${url}`);
|
|
179
195
|
}
|
|
@@ -272,7 +288,7 @@ class CoverageClient {
|
|
|
272
288
|
// write the coverage started by NODE_V8_COVERAGE to disk on demand
|
|
273
289
|
const res = await this.session.send('Runtime.evaluate', {
|
|
274
290
|
expression: `new Promise((resolve) => {
|
|
275
|
-
|
|
291
|
+
process.getBuiltinModule("v8").takeCoverage();
|
|
276
292
|
resolve(process.env.NODE_V8_COVERAGE);
|
|
277
293
|
})`,
|
|
278
294
|
includeCommandLineAPI: true,
|
|
@@ -319,4 +335,4 @@ class CoverageClient {
|
|
|
319
335
|
}
|
|
320
336
|
}
|
|
321
337
|
|
|
322
|
-
|
|
338
|
+
export { CoverageClient };
|