@jsenv/core 27.0.0-alpha.82 → 27.0.0-alpha.83
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/js/event_source_client.js +205 -1
- package/dist/main.js +845 -60
- package/package.json +2 -2
- package/src/build/start_build_server.js +29 -26
- package/src/dev/start_dev_server.js +34 -30
- package/src/execute/runtimes/browsers/from_playwright.js +3 -2
- package/src/helpers/event_source/event_source.js +197 -0
- package/src/helpers/event_source/sse_service.js +53 -0
- package/src/helpers/worker_reload.js +56 -0
- package/src/plugins/autoreload/dev_sse/client/event_source_client.js +1 -1
- package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +1 -1
- package/src/test/coverage/babel_plugin_instrument.js +82 -0
- package/src/test/coverage/coverage_reporter_html_directory.js +36 -0
- package/src/test/coverage/coverage_reporter_json_file.js +22 -0
- package/src/test/coverage/coverage_reporter_text_log.js +19 -0
- package/src/test/coverage/empty_coverage_factory.js +52 -0
- package/src/test/coverage/file_by_file_coverage.js +26 -0
- package/src/test/coverage/istanbul_coverage_composition.js +28 -0
- package/src/test/coverage/istanbul_coverage_map_from_coverage.js +16 -0
- package/src/test/coverage/list_files_not_covered.js +15 -0
- package/src/test/coverage/missing_coverage.js +41 -0
- package/src/test/coverage/report_to_coverage.js +196 -0
- package/src/test/coverage/v8_and_istanbul.js +37 -0
- package/src/test/coverage/v8_coverage_composition.js +24 -0
- package/src/test/coverage/v8_coverage_from_directory.js +87 -0
- package/src/test/coverage/v8_coverage_to_istanbul.js +99 -0
- package/src/test/execute_plan.js +2 -2
- package/src/test/execute_test_plan.js +3 -3
package/dist/main.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { registerFileLifecycle, readFileSync as readFileSync$1, bufferToEtag, writeFileSync, ensureWindowsDriveLetter, collectFiles, assertAndNormalizeDirectoryUrl, registerDirectoryLifecycle, writeFile, ensureEmptyDirectory, writeDirectory } from "@jsenv/filesystem";
|
|
3
|
-
import {
|
|
1
|
+
import { createSSERoom, timeStart, fetchFileSystem, composeTwoResponses, serveDirectory, startServer, pluginCORS, jsenvAccessControlAllowedHeaders, pluginServerTiming, pluginRequestWaitingCheck, composeServices, findFreePort } from "@jsenv/server";
|
|
2
|
+
import { registerFileLifecycle, readFileSync as readFileSync$1, bufferToEtag, writeFileSync, ensureWindowsDriveLetter, collectFiles, assertAndNormalizeDirectoryUrl, registerDirectoryLifecycle, writeFile, readFile, readDirectory, ensureEmptyDirectory, writeDirectory } from "@jsenv/filesystem";
|
|
3
|
+
import { createCallbackListNotifiedOnce, createCallbackList, Abort, raceProcessTeardownEvents, raceCallbacks } from "@jsenv/abort";
|
|
4
|
+
import { createDetailedMessage, createLogger, createTaskLog, loggerToLevels, byteAsFileSize, ANSI, msAsDuration, msAsEllapsedTime, byteAsMemoryUsage, UNICODE, createLog, startSpinner, distributePercentages } from "@jsenv/log";
|
|
4
5
|
import { urlToRelativeUrl, generateInlineContentUrl, ensurePathnameTrailingSlash, urlIsInsideOf, urlToFilename, DATA_URL, injectQueryParams, injectQueryParamsIntoSpecifier, fileSystemPathToUrl, urlToFileSystemPath, isFileSystemPath, normalizeUrl, stringifyUrlSite, setUrlFilename, moveUrl, getCallerPosition, resolveUrl, resolveDirectoryUrl, asUrlWithoutSearch, asUrlUntilPathname, urlToBasename, urlToExtension } from "@jsenv/urls";
|
|
5
|
-
import {
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { workerData, Worker } from "node:worker_threads";
|
|
6
8
|
import { URL_META } from "@jsenv/url-meta";
|
|
7
9
|
import { parseHtmlString, stringifyHtmlAst, visitHtmlAst, getHtmlNodeAttributeByName, htmlNodePosition, findNode, getHtmlNodeTextNode, removeHtmlNode, setHtmlNodeGeneratedText, removeHtmlNodeAttributeByName, parseScriptNode, injectScriptAsEarlyAsPossible, createHtmlNode, removeHtmlNodeText, assignHtmlNodeAttributes, parseLinkNode } from "@jsenv/utils/html_ast/html_ast.js";
|
|
8
10
|
import { htmlAttributeSrcSet } from "@jsenv/utils/html_ast/html_attribute_src_set.js";
|
|
@@ -13,7 +15,6 @@ import { parseJsUrls } from "@jsenv/utils/js_ast/parse_js_urls.js";
|
|
|
13
15
|
import { resolveImport, normalizeImportMap, composeTwoImportMaps } from "@jsenv/importmap";
|
|
14
16
|
import { applyNodeEsmResolution, defaultLookupPackageScope, defaultReadPackageJson, readCustomConditionsFromProcessArgs, applyFileSystemMagicResolution, getExtensionsToTry } from "@jsenv/node-esm-resolution";
|
|
15
17
|
import { statSync, realpathSync, readdirSync, readFileSync, existsSync } from "node:fs";
|
|
16
|
-
import { pathToFileURL } from "node:url";
|
|
17
18
|
import { CONTENT_TYPE } from "@jsenv/utils/content_type/content_type.js";
|
|
18
19
|
import { JS_QUOTES } from "@jsenv/utils/string/js_quotes.js";
|
|
19
20
|
import { applyBabelPlugins } from "@jsenv/utils/js_ast/apply_babel_plugins.js";
|
|
@@ -26,32 +27,75 @@ import { injectImport } from "@jsenv/utils/js_ast/babel_utils.js";
|
|
|
26
27
|
import { sortByDependencies } from "@jsenv/utils/graph/sort_by_dependencies.js";
|
|
27
28
|
import { applyRollupPlugins } from "@jsenv/utils/js_ast/apply_rollup_plugins.js";
|
|
28
29
|
import { sourcemapConverter } from "@jsenv/utils/sourcemap/sourcemap_converter.js";
|
|
29
|
-
import { createCallbackList, createCallbackListNotifiedOnce, Abort, raceCallbacks, raceProcessTeardownEvents } from "@jsenv/abort";
|
|
30
|
-
import { createSSEService } from "@jsenv/utils/event_source/sse_service.js";
|
|
31
|
-
import { timeStart, fetchFileSystem, composeTwoResponses, serveDirectory, startServer, pluginCORS, jsenvAccessControlAllowedHeaders, pluginServerTiming, pluginRequestWaitingCheck, composeServices, findFreePort } from "@jsenv/server";
|
|
32
30
|
import { SOURCEMAP, generateSourcemapUrl, sourcemapToBase64Url } from "@jsenv/utils/sourcemap/sourcemap_utils.js";
|
|
33
31
|
import { validateResponseIntegrity } from "@jsenv/integrity";
|
|
34
32
|
import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js";
|
|
35
33
|
import { memoizeByFirstArgument } from "@jsenv/utils/memoize/memoize_by_first_argument.js";
|
|
36
|
-
import { generateCoverageJsonFile } from "@jsenv/utils/coverage/coverage_reporter_json_file.js";
|
|
37
|
-
import { generateCoverageHtmlDirectory } from "@jsenv/utils/coverage/coverage_reporter_html_directory.js";
|
|
38
|
-
import { generateCoverageTextLog } from "@jsenv/utils/coverage/coverage_reporter_text_log.js";
|
|
39
34
|
import { memoryUsage } from "node:process";
|
|
40
35
|
import wrapAnsi from "wrap-ansi";
|
|
41
36
|
import stripAnsi from "strip-ansi";
|
|
42
37
|
import cuid from "cuid";
|
|
43
|
-
import { babelPluginInstrument } from "@jsenv/utils/coverage/babel_plugin_instrument.js";
|
|
44
|
-
import { reportToCoverage } from "@jsenv/utils/coverage/report_to_coverage.js";
|
|
45
38
|
import v8 from "node:v8";
|
|
46
39
|
import { runInNewContext, Script } from "node:vm";
|
|
47
40
|
import { memoize } from "@jsenv/utils/memoize/memoize.js";
|
|
48
|
-
import { filterV8Coverage } from "@jsenv/utils/coverage/v8_coverage_from_directory.js";
|
|
49
|
-
import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/utils/coverage/istanbul_coverage_composition.js";
|
|
50
41
|
import { escapeRegexpSpecialChars } from "@jsenv/utils/string/escape_regexp_special_chars.js";
|
|
51
42
|
import { fork } from "node:child_process";
|
|
52
43
|
import { uneval } from "@jsenv/uneval";
|
|
53
44
|
import { createVersionGenerator } from "@jsenv/utils/versioning/version_generator.js";
|
|
54
45
|
|
|
46
|
+
const createReloadableWorker = (workerFileUrl, options = {}) => {
|
|
47
|
+
const workerFilePath = fileURLToPath(workerFileUrl);
|
|
48
|
+
const isPrimary = !workerData || workerData.workerFilePath !== workerFilePath;
|
|
49
|
+
let worker;
|
|
50
|
+
|
|
51
|
+
const terminate = async () => {
|
|
52
|
+
if (worker) {
|
|
53
|
+
let _worker = worker;
|
|
54
|
+
worker = null;
|
|
55
|
+
const exitPromise = new Promise(resolve => {
|
|
56
|
+
_worker.once("exit", resolve);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
_worker.terminate();
|
|
60
|
+
|
|
61
|
+
await exitPromise;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const load = async () => {
|
|
66
|
+
if (!isPrimary) {
|
|
67
|
+
throw new Error(`worker can be loaded from primary file only`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
worker = new Worker(workerFilePath, { ...options,
|
|
71
|
+
workerData: { ...options.workerData,
|
|
72
|
+
workerFilePath
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
worker.once("error", error => {
|
|
76
|
+
console.error(error);
|
|
77
|
+
});
|
|
78
|
+
worker.once("exit", () => {
|
|
79
|
+
worker = null;
|
|
80
|
+
});
|
|
81
|
+
await new Promise(resolve => {
|
|
82
|
+
worker.once("online", resolve);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const reload = async () => {
|
|
87
|
+
await terminate();
|
|
88
|
+
await load();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
isPrimary,
|
|
93
|
+
load,
|
|
94
|
+
reload,
|
|
95
|
+
terminate
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
55
99
|
const parseAndTransformHtmlUrls = async (urlInfo, context) => {
|
|
56
100
|
const url = urlInfo.originalUrl;
|
|
57
101
|
const content = urlInfo.content;
|
|
@@ -5637,6 +5681,60 @@ const jsenvPluginDevSSEClient = () => {
|
|
|
5637
5681
|
};
|
|
5638
5682
|
};
|
|
5639
5683
|
|
|
5684
|
+
const createSSEService = ({
|
|
5685
|
+
serverEventCallbackList
|
|
5686
|
+
}) => {
|
|
5687
|
+
const destroyCallbackList = createCallbackListNotifiedOnce();
|
|
5688
|
+
const cache = [];
|
|
5689
|
+
const sseRoomLimit = 100;
|
|
5690
|
+
|
|
5691
|
+
const getOrCreateSSERoom = request => {
|
|
5692
|
+
const htmlFileRelativeUrl = request.ressource.slice(1);
|
|
5693
|
+
const cacheEntry = cache.find(cacheEntryCandidate => cacheEntryCandidate.htmlFileRelativeUrl === htmlFileRelativeUrl);
|
|
5694
|
+
|
|
5695
|
+
if (cacheEntry) {
|
|
5696
|
+
return cacheEntry.sseRoom;
|
|
5697
|
+
}
|
|
5698
|
+
|
|
5699
|
+
const sseRoom = createSSERoom({
|
|
5700
|
+
retryDuration: 2000,
|
|
5701
|
+
historyLength: 100,
|
|
5702
|
+
welcomeEventEnabled: true,
|
|
5703
|
+
effect: () => {
|
|
5704
|
+
return serverEventCallbackList.add(event => {
|
|
5705
|
+
sseRoom.sendEvent(event);
|
|
5706
|
+
});
|
|
5707
|
+
}
|
|
5708
|
+
});
|
|
5709
|
+
const removeSSECleanupCallback = destroyCallbackList.add(() => {
|
|
5710
|
+
removeSSECleanupCallback();
|
|
5711
|
+
sseRoom.close();
|
|
5712
|
+
});
|
|
5713
|
+
cache.push({
|
|
5714
|
+
htmlFileRelativeUrl,
|
|
5715
|
+
sseRoom,
|
|
5716
|
+
cleanup: () => {
|
|
5717
|
+
removeSSECleanupCallback();
|
|
5718
|
+
sseRoom.close();
|
|
5719
|
+
}
|
|
5720
|
+
});
|
|
5721
|
+
|
|
5722
|
+
if (cache.length >= sseRoomLimit) {
|
|
5723
|
+
const firstCacheEntry = cache.shift();
|
|
5724
|
+
firstCacheEntry.cleanup();
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5727
|
+
return sseRoom;
|
|
5728
|
+
};
|
|
5729
|
+
|
|
5730
|
+
return {
|
|
5731
|
+
getOrCreateSSERoom,
|
|
5732
|
+
destroy: () => {
|
|
5733
|
+
destroyCallbackList.notify();
|
|
5734
|
+
}
|
|
5735
|
+
};
|
|
5736
|
+
};
|
|
5737
|
+
|
|
5640
5738
|
const jsenvPluginDevSSEServer = ({
|
|
5641
5739
|
rootDirectoryUrl,
|
|
5642
5740
|
urlGraph,
|
|
@@ -8295,7 +8393,7 @@ const jsenvPluginExplorer = ({
|
|
|
8295
8393
|
|
|
8296
8394
|
const startDevServer = async ({
|
|
8297
8395
|
signal = new AbortController().signal,
|
|
8298
|
-
handleSIGINT,
|
|
8396
|
+
handleSIGINT = true,
|
|
8299
8397
|
logLevel = "info",
|
|
8300
8398
|
omegaServerLogLevel = "warn",
|
|
8301
8399
|
port = 3456,
|
|
@@ -8315,9 +8413,9 @@ const startDevServer = async ({
|
|
|
8315
8413
|
devServerMainFile = getCallerPosition().url,
|
|
8316
8414
|
// force disable server autoreload when this code is executed:
|
|
8317
8415
|
// - inside a forked child process
|
|
8318
|
-
// -
|
|
8319
|
-
//
|
|
8320
|
-
devServerAutoreload = typeof process.send !== "function" && !
|
|
8416
|
+
// - debugged by vscode
|
|
8417
|
+
// otherwise we get net:ERR_CONNECTION_REFUSED
|
|
8418
|
+
devServerAutoreload = typeof process.send !== "function" && !process.env.VSCODE_INSPECTOR_OPTIONS,
|
|
8321
8419
|
clientFiles = {
|
|
8322
8420
|
"./src/": true,
|
|
8323
8421
|
"./test/": true
|
|
@@ -8357,32 +8455,36 @@ const startDevServer = async ({
|
|
|
8357
8455
|
logLevel
|
|
8358
8456
|
});
|
|
8359
8457
|
rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl);
|
|
8360
|
-
const
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
});
|
|
8458
|
+
const operation = Abort.startOperation();
|
|
8459
|
+
operation.addAbortSignal(signal);
|
|
8460
|
+
|
|
8461
|
+
if (handleSIGINT) {
|
|
8462
|
+
operation.addAbortSource(abort => {
|
|
8463
|
+
return raceProcessTeardownEvents({
|
|
8464
|
+
SIGINT: true
|
|
8465
|
+
}, abort);
|
|
8466
|
+
});
|
|
8467
|
+
}
|
|
8371
8468
|
|
|
8372
|
-
if (
|
|
8469
|
+
if (port === 0) {
|
|
8470
|
+
port = await findFreePort(port, {
|
|
8471
|
+
signal: operation.signal
|
|
8472
|
+
});
|
|
8473
|
+
}
|
|
8474
|
+
|
|
8475
|
+
const reloadableWorker = createReloadableWorker(devServerMainFile);
|
|
8476
|
+
|
|
8477
|
+
if (devServerAutoreload && reloadableWorker.isPrimary) {
|
|
8373
8478
|
const devServerFileChangeCallback = ({
|
|
8374
8479
|
relativeUrl,
|
|
8375
8480
|
event
|
|
8376
8481
|
}) => {
|
|
8377
8482
|
const url = new URL(relativeUrl, rootDirectoryUrl).href;
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
logger.info(`file ${event} ${url} -> restarting server...`);
|
|
8381
|
-
reloadableProcess.reload();
|
|
8382
|
-
}
|
|
8483
|
+
logger.info(`file ${event} ${url} -> restarting server...`);
|
|
8484
|
+
reloadableWorker.reload();
|
|
8383
8485
|
};
|
|
8384
8486
|
|
|
8385
|
-
const
|
|
8487
|
+
const stopWatchingDevServerFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
|
|
8386
8488
|
watchPatterns: {
|
|
8387
8489
|
[devServerMainFile]: true,
|
|
8388
8490
|
...devServerFiles
|
|
@@ -8415,14 +8517,16 @@ const startDevServer = async ({
|
|
|
8415
8517
|
});
|
|
8416
8518
|
}
|
|
8417
8519
|
});
|
|
8418
|
-
|
|
8419
|
-
|
|
8520
|
+
operation.addAbortCallback(() => {
|
|
8521
|
+
stopWatchingDevServerFiles();
|
|
8522
|
+
reloadableWorker.terminate();
|
|
8420
8523
|
});
|
|
8524
|
+
await reloadableWorker.load();
|
|
8421
8525
|
return {
|
|
8422
8526
|
origin: `${protocol}://127.0.0.1:${port}`,
|
|
8423
8527
|
stop: () => {
|
|
8424
|
-
|
|
8425
|
-
|
|
8528
|
+
stopWatchingDevServerFiles();
|
|
8529
|
+
reloadableWorker.terminate();
|
|
8426
8530
|
}
|
|
8427
8531
|
};
|
|
8428
8532
|
}
|
|
@@ -8541,6 +8645,679 @@ const startDevServer = async ({
|
|
|
8541
8645
|
};
|
|
8542
8646
|
};
|
|
8543
8647
|
|
|
8648
|
+
const generateCoverageJsonFile = async ({
|
|
8649
|
+
coverage,
|
|
8650
|
+
coverageJsonFileUrl,
|
|
8651
|
+
coverageJsonFileLog,
|
|
8652
|
+
logger
|
|
8653
|
+
}) => {
|
|
8654
|
+
const coverageAsText = JSON.stringify(coverage, null, " ");
|
|
8655
|
+
|
|
8656
|
+
if (coverageJsonFileLog) {
|
|
8657
|
+
logger.info(`-> ${urlToFileSystemPath(coverageJsonFileUrl)} (${byteAsFileSize(Buffer.byteLength(coverageAsText))})`);
|
|
8658
|
+
}
|
|
8659
|
+
|
|
8660
|
+
await writeFile(coverageJsonFileUrl, coverageAsText);
|
|
8661
|
+
};
|
|
8662
|
+
|
|
8663
|
+
const istanbulCoverageMapFromCoverage = coverage => {
|
|
8664
|
+
const {
|
|
8665
|
+
createCoverageMap
|
|
8666
|
+
} = requireFromJsenv("istanbul-lib-coverage");
|
|
8667
|
+
const coverageAdjusted = {};
|
|
8668
|
+
Object.keys(coverage).forEach(key => {
|
|
8669
|
+
coverageAdjusted[key.slice(2)] = { ...coverage[key],
|
|
8670
|
+
path: key.slice(2)
|
|
8671
|
+
};
|
|
8672
|
+
});
|
|
8673
|
+
const coverageMap = createCoverageMap(coverageAdjusted);
|
|
8674
|
+
return coverageMap;
|
|
8675
|
+
};
|
|
8676
|
+
|
|
8677
|
+
const generateCoverageHtmlDirectory = async (coverage, {
|
|
8678
|
+
rootDirectoryUrl,
|
|
8679
|
+
coverageHtmlDirectoryRelativeUrl,
|
|
8680
|
+
coverageSkipEmpty,
|
|
8681
|
+
coverageSkipFull
|
|
8682
|
+
}) => {
|
|
8683
|
+
const libReport = requireFromJsenv("istanbul-lib-report");
|
|
8684
|
+
const reports = requireFromJsenv("istanbul-reports");
|
|
8685
|
+
const context = libReport.createContext({
|
|
8686
|
+
dir: urlToFileSystemPath(rootDirectoryUrl),
|
|
8687
|
+
coverageMap: istanbulCoverageMapFromCoverage(coverage),
|
|
8688
|
+
sourceFinder: path => {
|
|
8689
|
+
return readFileSync(urlToFileSystemPath(resolveUrl(path, rootDirectoryUrl)), "utf8");
|
|
8690
|
+
}
|
|
8691
|
+
});
|
|
8692
|
+
const report = reports.create("html", {
|
|
8693
|
+
skipEmpty: coverageSkipEmpty,
|
|
8694
|
+
skipFull: coverageSkipFull,
|
|
8695
|
+
subdir: coverageHtmlDirectoryRelativeUrl
|
|
8696
|
+
});
|
|
8697
|
+
report.execute(context);
|
|
8698
|
+
};
|
|
8699
|
+
|
|
8700
|
+
const generateCoverageTextLog = (coverage, {
|
|
8701
|
+
coverageSkipEmpty,
|
|
8702
|
+
coverageSkipFull
|
|
8703
|
+
}) => {
|
|
8704
|
+
const libReport = requireFromJsenv("istanbul-lib-report");
|
|
8705
|
+
const reports = requireFromJsenv("istanbul-reports");
|
|
8706
|
+
const context = libReport.createContext({
|
|
8707
|
+
coverageMap: istanbulCoverageMapFromCoverage(coverage)
|
|
8708
|
+
});
|
|
8709
|
+
const report = reports.create("text", {
|
|
8710
|
+
skipEmpty: coverageSkipEmpty,
|
|
8711
|
+
skipFull: coverageSkipFull
|
|
8712
|
+
});
|
|
8713
|
+
report.execute(context);
|
|
8714
|
+
};
|
|
8715
|
+
|
|
8716
|
+
const babelPluginInstrument = (api, {
|
|
8717
|
+
rootDirectoryUrl,
|
|
8718
|
+
useInlineSourceMaps = false,
|
|
8719
|
+
coverageConfig = {
|
|
8720
|
+
"./**/*": true
|
|
8721
|
+
}
|
|
8722
|
+
}) => {
|
|
8723
|
+
const {
|
|
8724
|
+
programVisitor
|
|
8725
|
+
} = requireFromJsenv("istanbul-lib-instrument");
|
|
8726
|
+
const {
|
|
8727
|
+
types
|
|
8728
|
+
} = api;
|
|
8729
|
+
const associations = URL_META.resolveAssociations({
|
|
8730
|
+
cover: coverageConfig
|
|
8731
|
+
}, rootDirectoryUrl);
|
|
8732
|
+
|
|
8733
|
+
const shouldInstrument = url => {
|
|
8734
|
+
return URL_META.applyAssociations({
|
|
8735
|
+
url,
|
|
8736
|
+
associations
|
|
8737
|
+
}).cover;
|
|
8738
|
+
};
|
|
8739
|
+
|
|
8740
|
+
return {
|
|
8741
|
+
name: "transform-instrument",
|
|
8742
|
+
visitor: {
|
|
8743
|
+
Program: {
|
|
8744
|
+
enter(path) {
|
|
8745
|
+
const {
|
|
8746
|
+
file
|
|
8747
|
+
} = this;
|
|
8748
|
+
const {
|
|
8749
|
+
opts
|
|
8750
|
+
} = file;
|
|
8751
|
+
|
|
8752
|
+
if (!opts.sourceFileName) {
|
|
8753
|
+
console.warn(`cannot instrument file when "sourceFileName" option is not set`);
|
|
8754
|
+
return;
|
|
8755
|
+
}
|
|
8756
|
+
|
|
8757
|
+
const fileUrl = fileSystemPathToUrl(opts.sourceFileName);
|
|
8758
|
+
|
|
8759
|
+
if (!shouldInstrument(fileUrl)) {
|
|
8760
|
+
return;
|
|
8761
|
+
}
|
|
8762
|
+
|
|
8763
|
+
this.__dv__ = null;
|
|
8764
|
+
let inputSourceMap;
|
|
8765
|
+
|
|
8766
|
+
if (useInlineSourceMaps) {
|
|
8767
|
+
// https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
|
|
8768
|
+
inputSourceMap = opts.inputSourceMap || file.inputMap ? file.inputMap.sourcemap : null;
|
|
8769
|
+
} else {
|
|
8770
|
+
inputSourceMap = opts.inputSourceMap;
|
|
8771
|
+
}
|
|
8772
|
+
|
|
8773
|
+
this.__dv__ = programVisitor(types, opts.filenameRelative || opts.filename, {
|
|
8774
|
+
coverageVariable: "__coverage__",
|
|
8775
|
+
inputSourceMap
|
|
8776
|
+
});
|
|
8777
|
+
|
|
8778
|
+
this.__dv__.enter(path);
|
|
8779
|
+
},
|
|
8780
|
+
|
|
8781
|
+
exit(path) {
|
|
8782
|
+
if (!this.__dv__) {
|
|
8783
|
+
return;
|
|
8784
|
+
}
|
|
8785
|
+
|
|
8786
|
+
const object = this.__dv__.exit(path); // object got two properties: fileCoverage and sourceMappingURL
|
|
8787
|
+
|
|
8788
|
+
|
|
8789
|
+
this.file.metadata.coverage = object.fileCoverage;
|
|
8790
|
+
}
|
|
8791
|
+
|
|
8792
|
+
}
|
|
8793
|
+
}
|
|
8794
|
+
};
|
|
8795
|
+
};
|
|
8796
|
+
|
|
8797
|
+
const visitNodeV8Directory = async ({
|
|
8798
|
+
logger,
|
|
8799
|
+
signal,
|
|
8800
|
+
NODE_V8_COVERAGE,
|
|
8801
|
+
onV8Coverage,
|
|
8802
|
+
maxMsWaitingForNodeToWriteCoverageFile = 2000
|
|
8803
|
+
}) => {
|
|
8804
|
+
const operation = Abort.startOperation();
|
|
8805
|
+
operation.addAbortSignal(signal);
|
|
8806
|
+
|
|
8807
|
+
const tryReadDirectory = async () => {
|
|
8808
|
+
const dirContent = await readDirectory(NODE_V8_COVERAGE);
|
|
8809
|
+
|
|
8810
|
+
if (dirContent.length > 0) {
|
|
8811
|
+
return dirContent;
|
|
8812
|
+
}
|
|
8813
|
+
|
|
8814
|
+
logger.warn(`v8 coverage directory is empty at ${NODE_V8_COVERAGE}`);
|
|
8815
|
+
return dirContent;
|
|
8816
|
+
};
|
|
8817
|
+
|
|
8818
|
+
try {
|
|
8819
|
+
operation.throwIfAborted();
|
|
8820
|
+
const dirContent = await tryReadDirectory();
|
|
8821
|
+
const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE);
|
|
8822
|
+
await dirContent.reduce(async (previous, dirEntry) => {
|
|
8823
|
+
operation.throwIfAborted();
|
|
8824
|
+
await previous;
|
|
8825
|
+
const dirEntryUrl = resolveUrl(dirEntry, coverageDirectoryUrl);
|
|
8826
|
+
|
|
8827
|
+
const tryReadJsonFile = async (timeSpentTrying = 0) => {
|
|
8828
|
+
const fileContent = await readFile(dirEntryUrl, {
|
|
8829
|
+
as: "string"
|
|
8830
|
+
});
|
|
8831
|
+
|
|
8832
|
+
if (fileContent === "") {
|
|
8833
|
+
if (timeSpentTrying < 400) {
|
|
8834
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
8835
|
+
return tryReadJsonFile(timeSpentTrying + 200);
|
|
8836
|
+
}
|
|
8837
|
+
|
|
8838
|
+
console.warn(`Coverage JSON file is empty at ${dirEntryUrl}`);
|
|
8839
|
+
return null;
|
|
8840
|
+
}
|
|
8841
|
+
|
|
8842
|
+
try {
|
|
8843
|
+
const fileAsJson = JSON.parse(fileContent);
|
|
8844
|
+
return fileAsJson;
|
|
8845
|
+
} catch (e) {
|
|
8846
|
+
if (timeSpentTrying < maxMsWaitingForNodeToWriteCoverageFile) {
|
|
8847
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
8848
|
+
return tryReadJsonFile(timeSpentTrying + 200);
|
|
8849
|
+
}
|
|
8850
|
+
|
|
8851
|
+
console.warn(createDetailedMessage(`Error while reading coverage file`, {
|
|
8852
|
+
"error stack": e.stack,
|
|
8853
|
+
"file": dirEntryUrl
|
|
8854
|
+
}));
|
|
8855
|
+
return null;
|
|
8856
|
+
}
|
|
8857
|
+
};
|
|
8858
|
+
|
|
8859
|
+
const fileContent = await tryReadJsonFile();
|
|
8860
|
+
|
|
8861
|
+
if (fileContent) {
|
|
8862
|
+
onV8Coverage(fileContent);
|
|
8863
|
+
}
|
|
8864
|
+
}, Promise.resolve());
|
|
8865
|
+
} finally {
|
|
8866
|
+
await operation.end();
|
|
8867
|
+
}
|
|
8868
|
+
};
|
|
8869
|
+
const filterV8Coverage = (v8Coverage, {
|
|
8870
|
+
urlShouldBeCovered
|
|
8871
|
+
}) => {
|
|
8872
|
+
const v8CoverageFiltered = { ...v8Coverage,
|
|
8873
|
+
result: v8Coverage.result.filter(fileReport => urlShouldBeCovered(fileReport.url))
|
|
8874
|
+
};
|
|
8875
|
+
return v8CoverageFiltered;
|
|
8876
|
+
};
|
|
8877
|
+
|
|
8878
|
+
const composeTwoV8Coverages = (firstV8Coverage, secondV8Coverage) => {
|
|
8879
|
+
if (secondV8Coverage.result.length === 0) {
|
|
8880
|
+
return firstV8Coverage;
|
|
8881
|
+
} // eslint-disable-next-line import/no-unresolved
|
|
8882
|
+
|
|
8883
|
+
|
|
8884
|
+
const {
|
|
8885
|
+
mergeProcessCovs
|
|
8886
|
+
} = requireFromJsenv("@c88/v8-coverage"); // "mergeProcessCovs" do not preserves source-map-cache during the merge
|
|
8887
|
+
// so we store sourcemap cache now
|
|
8888
|
+
|
|
8889
|
+
const sourceMapCache = {};
|
|
8890
|
+
|
|
8891
|
+
const visit = coverageReport => {
|
|
8892
|
+
if (coverageReport["source-map-cache"]) {
|
|
8893
|
+
Object.assign(sourceMapCache, coverageReport["source-map-cache"]);
|
|
8894
|
+
}
|
|
8895
|
+
};
|
|
8896
|
+
|
|
8897
|
+
visit(firstV8Coverage);
|
|
8898
|
+
visit(secondV8Coverage);
|
|
8899
|
+
const v8Coverage = mergeProcessCovs([firstV8Coverage, secondV8Coverage]);
|
|
8900
|
+
v8Coverage["source-map-cache"] = sourceMapCache;
|
|
8901
|
+
return v8Coverage;
|
|
8902
|
+
};
|
|
8903
|
+
|
|
8904
|
+
const composeTwoFileByFileIstanbulCoverages = (firstFileByFileIstanbulCoverage, secondFileByFileIstanbulCoverage) => {
|
|
8905
|
+
const fileByFileIstanbulCoverage = {};
|
|
8906
|
+
Object.keys(firstFileByFileIstanbulCoverage).forEach(key => {
|
|
8907
|
+
fileByFileIstanbulCoverage[key] = firstFileByFileIstanbulCoverage[key];
|
|
8908
|
+
});
|
|
8909
|
+
Object.keys(secondFileByFileIstanbulCoverage).forEach(key => {
|
|
8910
|
+
const firstCoverage = firstFileByFileIstanbulCoverage[key];
|
|
8911
|
+
const secondCoverage = secondFileByFileIstanbulCoverage[key];
|
|
8912
|
+
fileByFileIstanbulCoverage[key] = firstCoverage ? merge(firstCoverage, secondCoverage) : secondCoverage;
|
|
8913
|
+
});
|
|
8914
|
+
return fileByFileIstanbulCoverage;
|
|
8915
|
+
};
|
|
8916
|
+
|
|
8917
|
+
const merge = (firstIstanbulCoverage, secondIstanbulCoverage) => {
|
|
8918
|
+
const {
|
|
8919
|
+
createFileCoverage
|
|
8920
|
+
} = requireFromJsenv("istanbul-lib-coverage");
|
|
8921
|
+
const istanbulFileCoverageObject = createFileCoverage(firstIstanbulCoverage);
|
|
8922
|
+
istanbulFileCoverageObject.merge(secondIstanbulCoverage);
|
|
8923
|
+
const istanbulCoverage = istanbulFileCoverageObject.toJSON();
|
|
8924
|
+
return istanbulCoverage;
|
|
8925
|
+
};
|
|
8926
|
+
|
|
8927
|
+
const v8CoverageToIstanbul = async (v8Coverage, {
|
|
8928
|
+
signal
|
|
8929
|
+
}) => {
|
|
8930
|
+
const operation = Abort.startOperation();
|
|
8931
|
+
operation.addAbortSignal(signal);
|
|
8932
|
+
|
|
8933
|
+
try {
|
|
8934
|
+
const v8ToIstanbul = requireFromJsenv("v8-to-istanbul");
|
|
8935
|
+
const sourcemapCache = v8Coverage["source-map-cache"];
|
|
8936
|
+
let istanbulCoverageComposed = null;
|
|
8937
|
+
await v8Coverage.result.reduce(async (previous, fileV8Coverage) => {
|
|
8938
|
+
operation.throwIfAborted();
|
|
8939
|
+
await previous;
|
|
8940
|
+
const {
|
|
8941
|
+
source
|
|
8942
|
+
} = fileV8Coverage;
|
|
8943
|
+
let sources; // when v8 coverage comes from playwright (chromium) v8Coverage.source is set
|
|
8944
|
+
|
|
8945
|
+
if (typeof source === "string") {
|
|
8946
|
+
sources = {
|
|
8947
|
+
source
|
|
8948
|
+
};
|
|
8949
|
+
} // when v8 coverage comes from Node.js, the source can be read from sourcemapCache
|
|
8950
|
+
else if (sourcemapCache) {
|
|
8951
|
+
sources = sourcesFromSourceMapCache(fileV8Coverage.url, sourcemapCache);
|
|
8952
|
+
}
|
|
8953
|
+
|
|
8954
|
+
const path = urlToFileSystemPath(fileV8Coverage.url);
|
|
8955
|
+
const converter = v8ToIstanbul(path, // wrapperLength is undefined we don't need it
|
|
8956
|
+
// https://github.com/istanbuljs/v8-to-istanbul/blob/2b54bc97c5edf8a37b39a171ec29134ba9bfd532/lib/v8-to-istanbul.js#L27
|
|
8957
|
+
undefined, sources);
|
|
8958
|
+
await converter.load();
|
|
8959
|
+
converter.applyCoverage(fileV8Coverage.functions);
|
|
8960
|
+
const istanbulCoverage = converter.toIstanbul();
|
|
8961
|
+
istanbulCoverageComposed = istanbulCoverageComposed ? composeTwoFileByFileIstanbulCoverages(istanbulCoverageComposed, istanbulCoverage) : istanbulCoverage;
|
|
8962
|
+
}, Promise.resolve());
|
|
8963
|
+
|
|
8964
|
+
if (!istanbulCoverageComposed) {
|
|
8965
|
+
return {};
|
|
8966
|
+
}
|
|
8967
|
+
|
|
8968
|
+
istanbulCoverageComposed = markAsConvertedFromV8(istanbulCoverageComposed);
|
|
8969
|
+
return istanbulCoverageComposed;
|
|
8970
|
+
} finally {
|
|
8971
|
+
await operation.end();
|
|
8972
|
+
}
|
|
8973
|
+
};
|
|
8974
|
+
|
|
8975
|
+
const markAsConvertedFromV8 = fileByFileCoverage => {
|
|
8976
|
+
const fileByFileMarked = {};
|
|
8977
|
+
Object.keys(fileByFileCoverage).forEach(key => {
|
|
8978
|
+
const fileCoverage = fileByFileCoverage[key];
|
|
8979
|
+
fileByFileMarked[key] = { ...fileCoverage,
|
|
8980
|
+
fromV8: true
|
|
8981
|
+
};
|
|
8982
|
+
});
|
|
8983
|
+
return fileByFileMarked;
|
|
8984
|
+
};
|
|
8985
|
+
|
|
8986
|
+
const sourcesFromSourceMapCache = (url, sourceMapCache) => {
|
|
8987
|
+
const sourceMapAndLineLengths = sourceMapCache[url];
|
|
8988
|
+
|
|
8989
|
+
if (!sourceMapAndLineLengths) {
|
|
8990
|
+
return {};
|
|
8991
|
+
}
|
|
8992
|
+
|
|
8993
|
+
const {
|
|
8994
|
+
data,
|
|
8995
|
+
lineLengths
|
|
8996
|
+
} = sourceMapAndLineLengths; // See: https://github.com/nodejs/node/pull/34305
|
|
8997
|
+
|
|
8998
|
+
if (!data) {
|
|
8999
|
+
return undefined;
|
|
9000
|
+
}
|
|
9001
|
+
|
|
9002
|
+
const sources = {
|
|
9003
|
+
sourcemap: data,
|
|
9004
|
+
...(lineLengths ? {
|
|
9005
|
+
source: sourcesFromLineLengths(lineLengths)
|
|
9006
|
+
} : {})
|
|
9007
|
+
};
|
|
9008
|
+
return sources;
|
|
9009
|
+
};
|
|
9010
|
+
|
|
9011
|
+
const sourcesFromLineLengths = lineLengths => {
|
|
9012
|
+
let source = "";
|
|
9013
|
+
lineLengths.forEach(length => {
|
|
9014
|
+
source += `${"".padEnd(length, ".")}\n`;
|
|
9015
|
+
});
|
|
9016
|
+
return source;
|
|
9017
|
+
};
|
|
9018
|
+
|
|
9019
|
+
const composeV8AndIstanbul = (v8FileByFileCoverage, istanbulFileByFileCoverage, {
|
|
9020
|
+
coverageV8ConflictWarning
|
|
9021
|
+
}) => {
|
|
9022
|
+
const fileByFileCoverage = {};
|
|
9023
|
+
const v8Files = Object.keys(v8FileByFileCoverage);
|
|
9024
|
+
const istanbulFiles = Object.keys(istanbulFileByFileCoverage);
|
|
9025
|
+
v8Files.forEach(key => {
|
|
9026
|
+
fileByFileCoverage[key] = v8FileByFileCoverage[key];
|
|
9027
|
+
});
|
|
9028
|
+
istanbulFiles.forEach(key => {
|
|
9029
|
+
const v8Coverage = v8FileByFileCoverage[key];
|
|
9030
|
+
|
|
9031
|
+
if (v8Coverage) {
|
|
9032
|
+
if (coverageV8ConflictWarning) {
|
|
9033
|
+
console.warn(createDetailedMessage(`Coverage conflict on "${key}", found two coverage that cannot be merged together: v8 and istanbul. The istanbul coverage will be ignored.`, {
|
|
9034
|
+
details: `This happens when a file is executed on a runtime using v8 coverage (node or chromium) and on runtime using istanbul coverage (firefox or webkit)`,
|
|
9035
|
+
suggestion: "You can disable this warning with coverageV8ConflictWarning: false"
|
|
9036
|
+
}));
|
|
9037
|
+
}
|
|
9038
|
+
|
|
9039
|
+
fileByFileCoverage[key] = v8Coverage;
|
|
9040
|
+
} else {
|
|
9041
|
+
fileByFileCoverage[key] = istanbulFileByFileCoverage[key];
|
|
9042
|
+
}
|
|
9043
|
+
});
|
|
9044
|
+
return fileByFileCoverage;
|
|
9045
|
+
};
|
|
9046
|
+
|
|
9047
|
+
const normalizeFileByFileCoveragePaths = (fileByFileCoverage, rootDirectoryUrl) => {
|
|
9048
|
+
const fileByFileNormalized = {};
|
|
9049
|
+
Object.keys(fileByFileCoverage).forEach(key => {
|
|
9050
|
+
const fileCoverage = fileByFileCoverage[key];
|
|
9051
|
+
const {
|
|
9052
|
+
path
|
|
9053
|
+
} = fileCoverage;
|
|
9054
|
+
const url = isFileSystemPath(path) ? fileSystemPathToUrl(path) : resolveUrl(path, rootDirectoryUrl);
|
|
9055
|
+
const relativeUrl = urlToRelativeUrl(url, rootDirectoryUrl);
|
|
9056
|
+
fileByFileNormalized[`./${relativeUrl}`] = { ...fileCoverage,
|
|
9057
|
+
path: `./${relativeUrl}`
|
|
9058
|
+
};
|
|
9059
|
+
});
|
|
9060
|
+
return fileByFileNormalized;
|
|
9061
|
+
};
|
|
9062
|
+
|
|
9063
|
+
const listRelativeFileUrlToCover = async ({
|
|
9064
|
+
signal,
|
|
9065
|
+
rootDirectoryUrl,
|
|
9066
|
+
coverageConfig
|
|
9067
|
+
}) => {
|
|
9068
|
+
const matchingFileResultArray = await collectFiles({
|
|
9069
|
+
signal,
|
|
9070
|
+
directoryUrl: rootDirectoryUrl,
|
|
9071
|
+
associations: {
|
|
9072
|
+
cover: coverageConfig
|
|
9073
|
+
},
|
|
9074
|
+
predicate: ({
|
|
9075
|
+
cover
|
|
9076
|
+
}) => cover
|
|
9077
|
+
});
|
|
9078
|
+
return matchingFileResultArray.map(({
|
|
9079
|
+
relativeUrl
|
|
9080
|
+
}) => relativeUrl);
|
|
9081
|
+
};
|
|
9082
|
+
|
|
9083
|
+
const relativeUrlToEmptyCoverage = async (relativeUrl, {
|
|
9084
|
+
signal,
|
|
9085
|
+
rootDirectoryUrl
|
|
9086
|
+
}) => {
|
|
9087
|
+
const operation = Abort.startOperation();
|
|
9088
|
+
operation.addAbortSignal(signal);
|
|
9089
|
+
|
|
9090
|
+
try {
|
|
9091
|
+
const fileUrl = resolveUrl(relativeUrl, rootDirectoryUrl);
|
|
9092
|
+
const content = await readFile(fileUrl, {
|
|
9093
|
+
as: "string"
|
|
9094
|
+
});
|
|
9095
|
+
operation.throwIfAborted();
|
|
9096
|
+
const {
|
|
9097
|
+
metadata
|
|
9098
|
+
} = await applyBabelPlugins({
|
|
9099
|
+
babelPlugins: [[babelPluginInstrument, {
|
|
9100
|
+
rootDirectoryUrl
|
|
9101
|
+
}]],
|
|
9102
|
+
urlInfo: {
|
|
9103
|
+
originalUrl: fileUrl,
|
|
9104
|
+
content
|
|
9105
|
+
}
|
|
9106
|
+
});
|
|
9107
|
+
const {
|
|
9108
|
+
coverage
|
|
9109
|
+
} = metadata;
|
|
9110
|
+
|
|
9111
|
+
if (!coverage) {
|
|
9112
|
+
throw new Error(`missing coverage for file`);
|
|
9113
|
+
} // https://github.com/gotwarlost/istanbul/blob/bc84c315271a5dd4d39bcefc5925cfb61a3d174a/lib/command/common/run-with-cover.js#L229
|
|
9114
|
+
|
|
9115
|
+
|
|
9116
|
+
Object.keys(coverage.s).forEach(function (key) {
|
|
9117
|
+
coverage.s[key] = 0;
|
|
9118
|
+
});
|
|
9119
|
+
return coverage;
|
|
9120
|
+
} catch (e) {
|
|
9121
|
+
if (e && e.code === "PARSE_ERROR") {
|
|
9122
|
+
// return an empty coverage for that file when
|
|
9123
|
+
// it contains a syntax error
|
|
9124
|
+
return createEmptyCoverage(relativeUrl);
|
|
9125
|
+
}
|
|
9126
|
+
|
|
9127
|
+
throw e;
|
|
9128
|
+
} finally {
|
|
9129
|
+
await operation.end();
|
|
9130
|
+
}
|
|
9131
|
+
};
|
|
9132
|
+
|
|
9133
|
+
const createEmptyCoverage = relativeUrl => {
|
|
9134
|
+
const {
|
|
9135
|
+
createFileCoverage
|
|
9136
|
+
} = requireFromJsenv("istanbul-lib-coverage");
|
|
9137
|
+
return createFileCoverage(relativeUrl).toJSON();
|
|
9138
|
+
};
|
|
9139
|
+
|
|
9140
|
+
const getMissingFileByFileCoverage = async ({
|
|
9141
|
+
signal,
|
|
9142
|
+
rootDirectoryUrl,
|
|
9143
|
+
coverageConfig,
|
|
9144
|
+
fileByFileCoverage
|
|
9145
|
+
}) => {
|
|
9146
|
+
const relativeUrlsToCover = await listRelativeFileUrlToCover({
|
|
9147
|
+
signal,
|
|
9148
|
+
rootDirectoryUrl,
|
|
9149
|
+
coverageConfig
|
|
9150
|
+
});
|
|
9151
|
+
const relativeUrlsMissing = relativeUrlsToCover.filter(relativeUrlToCover => Object.keys(fileByFileCoverage).every(key => {
|
|
9152
|
+
return key !== `./${relativeUrlToCover}`;
|
|
9153
|
+
}));
|
|
9154
|
+
const operation = Abort.startOperation();
|
|
9155
|
+
operation.addAbortSignal(signal);
|
|
9156
|
+
const missingFileByFileCoverage = {};
|
|
9157
|
+
await relativeUrlsMissing.reduce(async (previous, relativeUrlMissing) => {
|
|
9158
|
+
operation.throwIfAborted();
|
|
9159
|
+
await previous;
|
|
9160
|
+
await operation.withSignal(async signal => {
|
|
9161
|
+
const emptyCoverage = await relativeUrlToEmptyCoverage(relativeUrlMissing, {
|
|
9162
|
+
signal,
|
|
9163
|
+
rootDirectoryUrl
|
|
9164
|
+
});
|
|
9165
|
+
missingFileByFileCoverage[`./${relativeUrlMissing}`] = emptyCoverage;
|
|
9166
|
+
});
|
|
9167
|
+
}, Promise.resolve());
|
|
9168
|
+
return missingFileByFileCoverage;
|
|
9169
|
+
};
|
|
9170
|
+
|
|
9171
|
+
const reportToCoverage = async (report, {
|
|
9172
|
+
signal,
|
|
9173
|
+
logger,
|
|
9174
|
+
rootDirectoryUrl,
|
|
9175
|
+
coverageConfig,
|
|
9176
|
+
coverageIncludeMissing,
|
|
9177
|
+
urlShouldBeCovered,
|
|
9178
|
+
coverageForceIstanbul,
|
|
9179
|
+
coverageV8ConflictWarning
|
|
9180
|
+
}) => {
|
|
9181
|
+
// collect v8 and istanbul coverage from executions
|
|
9182
|
+
let {
|
|
9183
|
+
v8Coverage,
|
|
9184
|
+
fileByFileIstanbulCoverage
|
|
9185
|
+
} = await getCoverageFromReport({
|
|
9186
|
+
signal,
|
|
9187
|
+
report,
|
|
9188
|
+
onMissing: ({
|
|
9189
|
+
file,
|
|
9190
|
+
executionResult,
|
|
9191
|
+
executionName
|
|
9192
|
+
}) => {
|
|
9193
|
+
// several reasons not to have coverage here:
|
|
9194
|
+
// 1. the file we executed did not import an instrumented file.
|
|
9195
|
+
// - a test file without import
|
|
9196
|
+
// - a test file importing only file excluded from coverage
|
|
9197
|
+
// - a coverDescription badly configured so that we don't realize
|
|
9198
|
+
// a file should be covered
|
|
9199
|
+
// 2. the file we wanted to executed timedout
|
|
9200
|
+
// - infinite loop
|
|
9201
|
+
// - too extensive operation
|
|
9202
|
+
// - a badly configured or too low allocatedMs for that execution.
|
|
9203
|
+
// 3. the file we wanted to execute contains syntax-error
|
|
9204
|
+
// in any scenario we are fine because
|
|
9205
|
+
// coverDescription will generate empty coverage for files
|
|
9206
|
+
// that were suppose to be coverage but were not.
|
|
9207
|
+
if (executionResult.status === "completed" && executionResult.runtimeName !== "node" && !process.env.NODE_V8_COVERAGE) {
|
|
9208
|
+
logger.warn(`No execution.coverageFileUrl from execution named "${executionName}" of ${file}`);
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
});
|
|
9212
|
+
|
|
9213
|
+
if (!coverageForceIstanbul && process.env.NODE_V8_COVERAGE) {
|
|
9214
|
+
await visitNodeV8Directory({
|
|
9215
|
+
logger,
|
|
9216
|
+
signal,
|
|
9217
|
+
NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE,
|
|
9218
|
+
onV8Coverage: nodeV8Coverage => {
|
|
9219
|
+
const nodeV8CoverageLight = filterV8Coverage(nodeV8Coverage, {
|
|
9220
|
+
urlShouldBeCovered
|
|
9221
|
+
});
|
|
9222
|
+
v8Coverage = v8Coverage ? composeTwoV8Coverages(v8Coverage, nodeV8CoverageLight) : nodeV8CoverageLight;
|
|
9223
|
+
}
|
|
9224
|
+
});
|
|
9225
|
+
} // try to merge v8 with istanbul, if any
|
|
9226
|
+
|
|
9227
|
+
|
|
9228
|
+
let fileByFileCoverage;
|
|
9229
|
+
|
|
9230
|
+
if (v8Coverage) {
|
|
9231
|
+
let v8FileByFileCoverage = await v8CoverageToIstanbul(v8Coverage, {
|
|
9232
|
+
signal
|
|
9233
|
+
});
|
|
9234
|
+
v8FileByFileCoverage = normalizeFileByFileCoveragePaths(v8FileByFileCoverage, rootDirectoryUrl);
|
|
9235
|
+
|
|
9236
|
+
if (fileByFileIstanbulCoverage) {
|
|
9237
|
+
fileByFileIstanbulCoverage = normalizeFileByFileCoveragePaths(fileByFileIstanbulCoverage, rootDirectoryUrl);
|
|
9238
|
+
fileByFileCoverage = composeV8AndIstanbul(v8FileByFileCoverage, fileByFileIstanbulCoverage, {
|
|
9239
|
+
coverageV8ConflictWarning
|
|
9240
|
+
});
|
|
9241
|
+
} else {
|
|
9242
|
+
fileByFileCoverage = v8FileByFileCoverage;
|
|
9243
|
+
}
|
|
9244
|
+
} // get istanbul only
|
|
9245
|
+
else if (fileByFileIstanbulCoverage) {
|
|
9246
|
+
fileByFileCoverage = normalizeFileByFileCoveragePaths(fileByFileIstanbulCoverage, rootDirectoryUrl);
|
|
9247
|
+
} // no coverage found in execution (or zero file where executed)
|
|
9248
|
+
else {
|
|
9249
|
+
fileByFileCoverage = {};
|
|
9250
|
+
} // now add coverage for file not covered
|
|
9251
|
+
|
|
9252
|
+
|
|
9253
|
+
if (coverageIncludeMissing) {
|
|
9254
|
+
const missingFileByFileCoverage = await getMissingFileByFileCoverage({
|
|
9255
|
+
signal,
|
|
9256
|
+
rootDirectoryUrl,
|
|
9257
|
+
coverageConfig,
|
|
9258
|
+
fileByFileCoverage
|
|
9259
|
+
});
|
|
9260
|
+
Object.assign(fileByFileCoverage, normalizeFileByFileCoveragePaths(missingFileByFileCoverage, rootDirectoryUrl));
|
|
9261
|
+
}
|
|
9262
|
+
|
|
9263
|
+
return fileByFileCoverage;
|
|
9264
|
+
};
|
|
9265
|
+
|
|
9266
|
+
const getCoverageFromReport = async ({
|
|
9267
|
+
signal,
|
|
9268
|
+
report,
|
|
9269
|
+
onMissing
|
|
9270
|
+
}) => {
|
|
9271
|
+
const operation = Abort.startOperation();
|
|
9272
|
+
operation.addAbortSignal(signal);
|
|
9273
|
+
|
|
9274
|
+
try {
|
|
9275
|
+
let v8Coverage;
|
|
9276
|
+
let fileByFileIstanbulCoverage; // collect v8 and istanbul coverage from executions
|
|
9277
|
+
|
|
9278
|
+
await Object.keys(report).reduce(async (previous, file) => {
|
|
9279
|
+
operation.throwIfAborted();
|
|
9280
|
+
await previous;
|
|
9281
|
+
const executionResultForFile = report[file];
|
|
9282
|
+
await Object.keys(executionResultForFile).reduce(async (previous, executionName) => {
|
|
9283
|
+
operation.throwIfAborted();
|
|
9284
|
+
await previous;
|
|
9285
|
+
const executionResultForFileOnRuntime = executionResultForFile[executionName];
|
|
9286
|
+
const {
|
|
9287
|
+
coverageFileUrl
|
|
9288
|
+
} = executionResultForFileOnRuntime;
|
|
9289
|
+
|
|
9290
|
+
if (!coverageFileUrl) {
|
|
9291
|
+
onMissing({
|
|
9292
|
+
executionName,
|
|
9293
|
+
file,
|
|
9294
|
+
executionResult: executionResultForFileOnRuntime
|
|
9295
|
+
});
|
|
9296
|
+
return;
|
|
9297
|
+
}
|
|
9298
|
+
|
|
9299
|
+
const executionCoverage = await readFile(coverageFileUrl, {
|
|
9300
|
+
as: "json"
|
|
9301
|
+
});
|
|
9302
|
+
|
|
9303
|
+
if (isV8Coverage(executionCoverage)) {
|
|
9304
|
+
v8Coverage = v8Coverage ? composeTwoV8Coverages(v8Coverage, executionCoverage) : executionCoverage;
|
|
9305
|
+
} else {
|
|
9306
|
+
fileByFileIstanbulCoverage = fileByFileIstanbulCoverage ? composeTwoFileByFileIstanbulCoverages(fileByFileIstanbulCoverage, executionCoverage) : executionCoverage;
|
|
9307
|
+
}
|
|
9308
|
+
}, Promise.resolve());
|
|
9309
|
+
}, Promise.resolve());
|
|
9310
|
+
return {
|
|
9311
|
+
v8Coverage,
|
|
9312
|
+
fileByFileIstanbulCoverage
|
|
9313
|
+
};
|
|
9314
|
+
} finally {
|
|
9315
|
+
await operation.end();
|
|
9316
|
+
}
|
|
9317
|
+
};
|
|
9318
|
+
|
|
9319
|
+
const isV8Coverage = coverage => Boolean(coverage.result);
|
|
9320
|
+
|
|
8544
9321
|
const run = async ({
|
|
8545
9322
|
signal = new AbortController().signal,
|
|
8546
9323
|
logger,
|
|
@@ -13036,9 +13813,9 @@ const startBuildServer = async ({
|
|
|
13036
13813
|
buildServerMainFile = getCallerPosition().url,
|
|
13037
13814
|
// force disable server autoreload when this code is executed:
|
|
13038
13815
|
// - inside a forked child process
|
|
13039
|
-
// -
|
|
13040
|
-
//
|
|
13041
|
-
buildServerAutoreload = typeof process.send !== "function" && !
|
|
13816
|
+
// - debugged by vscode
|
|
13817
|
+
// otherwise we get net:ERR_CONNECTION_REFUSED
|
|
13818
|
+
buildServerAutoreload = typeof process.send !== "function" && !process.env.VSCODE_INSPECTOR_OPTIONS,
|
|
13042
13819
|
cooldownBetweenFileEvents
|
|
13043
13820
|
}) => {
|
|
13044
13821
|
const logger = createLogger({
|
|
@@ -13046,19 +13823,26 @@ const startBuildServer = async ({
|
|
|
13046
13823
|
});
|
|
13047
13824
|
rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl);
|
|
13048
13825
|
buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl);
|
|
13049
|
-
const
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13826
|
+
const operation = Abort.startOperation();
|
|
13827
|
+
operation.addAbortSignal(signal);
|
|
13828
|
+
|
|
13829
|
+
if (handleSIGINT) {
|
|
13830
|
+
operation.addAbortSource(abort => {
|
|
13831
|
+
return raceProcessTeardownEvents({
|
|
13832
|
+
SIGINT: true
|
|
13833
|
+
}, abort);
|
|
13834
|
+
});
|
|
13835
|
+
}
|
|
13836
|
+
|
|
13837
|
+
if (port === 0) {
|
|
13838
|
+
port = await findFreePort(port, {
|
|
13839
|
+
signal: operation.signal
|
|
13840
|
+
});
|
|
13841
|
+
}
|
|
13842
|
+
|
|
13843
|
+
const reloadableWorker = createReloadableWorker(buildServerMainFile);
|
|
13060
13844
|
|
|
13061
|
-
if (
|
|
13845
|
+
if (buildServerAutoreload && reloadableWorker.isPrimary) {
|
|
13062
13846
|
const buildServerFileChangeCallback = ({
|
|
13063
13847
|
relativeUrl,
|
|
13064
13848
|
event
|
|
@@ -13067,7 +13851,7 @@ const startBuildServer = async ({
|
|
|
13067
13851
|
|
|
13068
13852
|
if (buildServerAutoreload) {
|
|
13069
13853
|
logger.info(`file ${event} ${url} -> restarting server...`);
|
|
13070
|
-
|
|
13854
|
+
reloadableWorker.reload();
|
|
13071
13855
|
}
|
|
13072
13856
|
};
|
|
13073
13857
|
|
|
@@ -13104,19 +13888,20 @@ const startBuildServer = async ({
|
|
|
13104
13888
|
});
|
|
13105
13889
|
}
|
|
13106
13890
|
});
|
|
13107
|
-
|
|
13891
|
+
operation.addAbortCallback(() => {
|
|
13108
13892
|
stopWatchingBuildServerFiles();
|
|
13893
|
+
reloadableWorker.terminate();
|
|
13109
13894
|
});
|
|
13895
|
+
await reloadableWorker.load();
|
|
13110
13896
|
return {
|
|
13111
13897
|
origin: `${protocol}://127.0.0.1:${port}`,
|
|
13112
13898
|
stop: () => {
|
|
13113
13899
|
stopWatchingBuildServerFiles();
|
|
13114
|
-
|
|
13900
|
+
reloadableWorker.terminate();
|
|
13115
13901
|
}
|
|
13116
13902
|
};
|
|
13117
13903
|
}
|
|
13118
13904
|
|
|
13119
|
-
signal = reloadableProcess.signal;
|
|
13120
13905
|
const startBuildServerTask = createTaskLog("start build server", {
|
|
13121
13906
|
disabled: !loggerToLevels(logger).info
|
|
13122
13907
|
});
|