@jsenv/core 31.1.4 → 32.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/dist/js/v8_coverage.js +36 -23
- package/dist/main.js +493 -546
- package/package.json +5 -5
- package/src/basic_fetch.js +42 -0
- package/src/build/build.js +81 -93
- package/src/build/start_build_server.js +32 -112
- package/src/dev/file_service.js +49 -66
- package/src/dev/start_dev_server.js +43 -94
- package/src/execute/execute.js +4 -1
- package/src/execute/runtimes/node/node_child_process.js +0 -1
- package/src/jsenv_internal_directory.js +18 -0
- package/src/kitchen/kitchen.js +2 -0
- package/src/lookup_package_directory.js +34 -0
- package/src/plugins/explorer/jsenv_plugin_explorer.js +5 -4
- package/src/plugins/plugins.js +4 -16
- package/src/plugins/url_analysis/webmanifest/webmanifest_urls.js +10 -0
- package/src/plugins/url_resolution/jsenv_plugin_url_resolution.js +2 -2
- package/src/test/coverage/v8_coverage_node_directory.js +4 -2
- package/src/test/execute_plan.js +6 -67
- package/src/test/execute_test_plan.js +199 -45
- package/src/watch_source_files.js +50 -0
package/src/test/execute_plan.js
CHANGED
|
@@ -5,14 +5,13 @@ import wrapAnsi from "wrap-ansi"
|
|
|
5
5
|
import stripAnsi from "strip-ansi"
|
|
6
6
|
|
|
7
7
|
import { urlToFileSystemPath } from "@jsenv/urls"
|
|
8
|
-
import {
|
|
8
|
+
import { createLog, startSpinner } from "@jsenv/log"
|
|
9
9
|
import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
|
|
10
10
|
import { ensureEmptyDirectory, writeFileSync } from "@jsenv/filesystem"
|
|
11
11
|
|
|
12
12
|
import { reportToCoverage } from "./coverage/report_to_coverage.js"
|
|
13
13
|
import { run } from "@jsenv/core/src/execute/run.js"
|
|
14
14
|
|
|
15
|
-
import { pingServer } from "../ping_server.js"
|
|
16
15
|
import { ensureGlobalGc } from "./gc.js"
|
|
17
16
|
import { generateExecutionSteps } from "./execution_steps.js"
|
|
18
17
|
import { createExecutionLog, createSummaryLog } from "./logs_file_execution.js"
|
|
@@ -48,7 +47,7 @@ export const executePlan = async (
|
|
|
48
47
|
coverageMethodForBrowsers,
|
|
49
48
|
coverageMethodForNodeJs,
|
|
50
49
|
coverageV8ConflictWarning,
|
|
51
|
-
|
|
50
|
+
coverageTempDirectoryUrl,
|
|
52
51
|
|
|
53
52
|
beforeExecutionCallback = () => {},
|
|
54
53
|
afterExecutionCallback = () => {},
|
|
@@ -59,30 +58,6 @@ export const executePlan = async (
|
|
|
59
58
|
const callbacks = []
|
|
60
59
|
const stopAfterAllSignal = { notify: () => {} }
|
|
61
60
|
|
|
62
|
-
let someNeedsServer = false
|
|
63
|
-
let someNodeRuntime = false
|
|
64
|
-
const runtimes = {}
|
|
65
|
-
Object.keys(plan).forEach((filePattern) => {
|
|
66
|
-
const filePlan = plan[filePattern]
|
|
67
|
-
Object.keys(filePlan).forEach((executionName) => {
|
|
68
|
-
const executionConfig = filePlan[executionName]
|
|
69
|
-
const { runtime } = executionConfig
|
|
70
|
-
if (runtime) {
|
|
71
|
-
runtimes[runtime.name] = runtime.version
|
|
72
|
-
if (runtime.type === "browser") {
|
|
73
|
-
someNeedsServer = true
|
|
74
|
-
}
|
|
75
|
-
if (runtime.type === "node") {
|
|
76
|
-
someNodeRuntime = true
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
logger.debug(
|
|
82
|
-
createDetailedMessage(`Prepare executing plan`, {
|
|
83
|
-
runtimes: JSON.stringify(runtimes, null, " "),
|
|
84
|
-
}),
|
|
85
|
-
)
|
|
86
61
|
const multipleExecutionsOperation = Abort.startOperation()
|
|
87
62
|
multipleExecutionsOperation.addAbortSignal(signal)
|
|
88
63
|
if (handleSIGINT) {
|
|
@@ -104,32 +79,6 @@ export const executePlan = async (
|
|
|
104
79
|
}
|
|
105
80
|
|
|
106
81
|
try {
|
|
107
|
-
const coverageTempDirectoryUrl = new URL(
|
|
108
|
-
coverageTempDirectoryRelativeUrl,
|
|
109
|
-
rootDirectoryUrl,
|
|
110
|
-
).href
|
|
111
|
-
if (
|
|
112
|
-
someNodeRuntime &&
|
|
113
|
-
coverageEnabled &&
|
|
114
|
-
coverageMethodForNodeJs === "NODE_V8_COVERAGE"
|
|
115
|
-
) {
|
|
116
|
-
if (process.env.NODE_V8_COVERAGE) {
|
|
117
|
-
// when runned multiple times, we don't want to keep previous files in this directory
|
|
118
|
-
await ensureEmptyDirectory(process.env.NODE_V8_COVERAGE)
|
|
119
|
-
} else {
|
|
120
|
-
coverageMethodForNodeJs = "Profiler"
|
|
121
|
-
logger.warn(
|
|
122
|
-
createDetailedMessage(
|
|
123
|
-
`process.env.NODE_V8_COVERAGE is required to generate coverage for Node.js subprocesses`,
|
|
124
|
-
{
|
|
125
|
-
"suggestion": `set process.env.NODE_V8_COVERAGE`,
|
|
126
|
-
"suggestion 2": `use coverageMethodForNodeJs: "Profiler". But it means coverage for child_process and worker_thread cannot be collected`,
|
|
127
|
-
},
|
|
128
|
-
),
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
82
|
if (gcBetweenExecutions) {
|
|
134
83
|
ensureGlobalGc()
|
|
135
84
|
}
|
|
@@ -177,19 +126,6 @@ export const executePlan = async (
|
|
|
177
126
|
coverageMethodForNodeJs,
|
|
178
127
|
stopAfterAllSignal,
|
|
179
128
|
}
|
|
180
|
-
if (someNeedsServer) {
|
|
181
|
-
if (!devServerOrigin) {
|
|
182
|
-
throw new TypeError(
|
|
183
|
-
`devServerOrigin is required when running tests on browser(s)`,
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
const devServerStarted = await pingServer(devServerOrigin)
|
|
187
|
-
if (!devServerStarted) {
|
|
188
|
-
throw new Error(
|
|
189
|
-
`dev server not started at ${devServerOrigin}. It is required to run tests`,
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
129
|
|
|
194
130
|
logger.debug(`Generate executions`)
|
|
195
131
|
const executionSteps = await getExecutionAsSteps({
|
|
@@ -292,7 +228,10 @@ export const executePlan = async (
|
|
|
292
228
|
executionResult = await run({
|
|
293
229
|
signal: multipleExecutionsOperation.signal,
|
|
294
230
|
logger,
|
|
295
|
-
allocatedMs:
|
|
231
|
+
allocatedMs:
|
|
232
|
+
typeof executionParams.allocatedMs === "function"
|
|
233
|
+
? executionParams.allocatedMs(beforeExecutionInfo)
|
|
234
|
+
: executionParams.allocatedMs,
|
|
296
235
|
keepRunning,
|
|
297
236
|
mirrorConsole: false, // file are executed in parallel, log would be a mess to read
|
|
298
237
|
collectConsole: executionParams.collectConsole,
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
1
2
|
import { URL_META } from "@jsenv/url-meta"
|
|
2
3
|
import {
|
|
3
4
|
urlToFileSystemPath,
|
|
4
|
-
resolveDirectoryUrl,
|
|
5
|
-
urlIsInsideOf,
|
|
6
5
|
urlToRelativeUrl,
|
|
6
|
+
urlIsInsideOf,
|
|
7
7
|
} from "@jsenv/urls"
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
ensureEmptyDirectory,
|
|
10
|
+
assertAndNormalizeDirectoryUrl,
|
|
11
|
+
assertAndNormalizeFileUrl,
|
|
12
|
+
} from "@jsenv/filesystem"
|
|
9
13
|
import { createLogger, createDetailedMessage } from "@jsenv/log"
|
|
10
14
|
|
|
15
|
+
import { lookupPackageDirectory } from "../lookup_package_directory.js"
|
|
16
|
+
import { pingServer } from "../ping_server.js"
|
|
17
|
+
import { basicFetch } from "../basic_fetch.js"
|
|
11
18
|
import { generateCoverageJsonFile } from "./coverage/coverage_reporter_json_file.js"
|
|
12
19
|
import { generateCoverageHtmlDirectory } from "./coverage/coverage_reporter_html_directory.js"
|
|
13
20
|
import { generateCoverageTextLog } from "./coverage/coverage_reporter_text_log.js"
|
|
@@ -16,8 +23,8 @@ import { executePlan } from "./execute_plan.js"
|
|
|
16
23
|
/**
|
|
17
24
|
* Execute a list of files and log how it goes.
|
|
18
25
|
* @param {Object} testPlanParameters
|
|
19
|
-
* @param {string|url} testPlanParameters.
|
|
20
|
-
* @param {string|url} [testPlanParameters.
|
|
26
|
+
* @param {string|url} testPlanParameters.testDirectoryUrl Directory containing test files
|
|
27
|
+
* @param {string|url} [testPlanParameters.devServerOrigin=undefined] Jsenv dev server origin; required when executing test on browsers
|
|
21
28
|
* @param {Object} testPlanParameters.testPlan Object associating patterns leading to files to runtimes where they should be executed
|
|
22
29
|
* @param {boolean} [testPlanParameters.completedExecutionLogAbbreviation=false] Abbreviate completed execution information to shorten terminal output
|
|
23
30
|
* @param {boolean} [testPlanParameters.completedExecutionLogMerging=false] Merge completed execution logs to shorten terminal output
|
|
@@ -43,7 +50,8 @@ export const executeTestPlan = async ({
|
|
|
43
50
|
logFileRelativeUrl = ".jsenv/test_plan_debug.txt",
|
|
44
51
|
completedExecutionLogAbbreviation = false,
|
|
45
52
|
completedExecutionLogMerging = false,
|
|
46
|
-
|
|
53
|
+
testDirectoryUrl,
|
|
54
|
+
devServerModuleUrl,
|
|
47
55
|
devServerOrigin,
|
|
48
56
|
|
|
49
57
|
testPlan,
|
|
@@ -61,9 +69,7 @@ export const executeTestPlan = async ({
|
|
|
61
69
|
gcBetweenExecutions = logMemoryHeapUsage,
|
|
62
70
|
|
|
63
71
|
coverageEnabled = process.argv.includes("--coverage"),
|
|
64
|
-
coverageConfig = {
|
|
65
|
-
"./src/": true,
|
|
66
|
-
},
|
|
72
|
+
coverageConfig = { "./**/*": true },
|
|
67
73
|
coverageIncludeMissing = true,
|
|
68
74
|
coverageAndExecutionAllowed = false,
|
|
69
75
|
coverageMethodForNodeJs = process.env.NODE_V8_COVERAGE
|
|
@@ -71,16 +77,23 @@ export const executeTestPlan = async ({
|
|
|
71
77
|
: "Profiler",
|
|
72
78
|
coverageMethodForBrowsers = "playwright_api", // "istanbul" also accepted
|
|
73
79
|
coverageV8ConflictWarning = true,
|
|
74
|
-
|
|
80
|
+
coverageTempDirectoryUrl,
|
|
81
|
+
coverageReportRootDirectoryUrl,
|
|
75
82
|
// skip empty means empty files won't appear in the coverage reports (json and html)
|
|
76
83
|
coverageReportSkipEmpty = false,
|
|
77
84
|
// skip full means file with 100% coverage won't appear in coverage reports (json and html)
|
|
78
85
|
coverageReportSkipFull = false,
|
|
79
86
|
coverageReportTextLog = true,
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
coverageReportJson = process.env.CI,
|
|
88
|
+
coverageReportJsonFileUrl,
|
|
89
|
+
coverageReportHtml = !process.env.CI,
|
|
90
|
+
coverageReportHtmlDirectoryUrl,
|
|
82
91
|
...rest
|
|
83
92
|
}) => {
|
|
93
|
+
let someNeedsServer = false
|
|
94
|
+
let someNodeRuntime = false
|
|
95
|
+
let stopDevServerNeeded = false
|
|
96
|
+
const runtimes = {}
|
|
84
97
|
// param validation
|
|
85
98
|
{
|
|
86
99
|
const unexpectedParamNames = Object.keys(rest)
|
|
@@ -89,16 +102,81 @@ export const executeTestPlan = async ({
|
|
|
89
102
|
`${unexpectedParamNames.join(",")}: there is no such param`,
|
|
90
103
|
)
|
|
91
104
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
testDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
106
|
+
testDirectoryUrl,
|
|
107
|
+
"testDirectoryUrl",
|
|
108
|
+
)
|
|
109
|
+
if (!existsSync(new URL(testDirectoryUrl))) {
|
|
110
|
+
throw new Error(`ENOENT on testDirectoryUrl at ${testDirectoryUrl}`)
|
|
97
111
|
}
|
|
98
|
-
rootDirectoryUrl = rootDirectoryUrlValidation.value
|
|
99
112
|
if (typeof testPlan !== "object") {
|
|
100
113
|
throw new Error(`testPlan must be an object, got ${testPlan}`)
|
|
101
114
|
}
|
|
115
|
+
|
|
116
|
+
Object.keys(testPlan).forEach((filePattern) => {
|
|
117
|
+
const filePlan = testPlan[filePattern]
|
|
118
|
+
if (!filePlan) return
|
|
119
|
+
Object.keys(filePlan).forEach((executionName) => {
|
|
120
|
+
const executionConfig = filePlan[executionName]
|
|
121
|
+
const { runtime } = executionConfig
|
|
122
|
+
if (runtime) {
|
|
123
|
+
runtimes[runtime.name] = runtime.version
|
|
124
|
+
if (runtime.type === "browser") {
|
|
125
|
+
someNeedsServer = true
|
|
126
|
+
}
|
|
127
|
+
if (runtime.type === "node") {
|
|
128
|
+
someNodeRuntime = true
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
if (someNeedsServer) {
|
|
135
|
+
if (!devServerOrigin) {
|
|
136
|
+
throw new TypeError(
|
|
137
|
+
`devServerOrigin is required when running tests on browser(s)`,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
let devServerStarted = await pingServer(devServerOrigin)
|
|
141
|
+
if (!devServerStarted) {
|
|
142
|
+
if (!devServerModuleUrl) {
|
|
143
|
+
throw new TypeError(
|
|
144
|
+
`devServerModuleUrl is required when dev server is not started in order to run tests on browser(s)`,
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
process.env.IMPORTED_BY_TEST_PLAN = "1"
|
|
149
|
+
await import(devServerModuleUrl)
|
|
150
|
+
delete process.env.IMPORTED_BY_TEST_PLAN
|
|
151
|
+
} catch (e) {
|
|
152
|
+
if (e.code === "MODULE_NOT_FOUND") {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Cannot find file responsible to start dev server at "${devServerModuleUrl}"`,
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
throw e
|
|
158
|
+
}
|
|
159
|
+
devServerStarted = await pingServer(devServerOrigin)
|
|
160
|
+
if (!devServerStarted) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`dev server not started after importing "${devServerModuleUrl}", ensure this module file is starting a server at "${devServerOrigin}"`,
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
stopDevServerNeeded = true
|
|
166
|
+
}
|
|
167
|
+
const { sourceDirectoryUrl } = await basicFetch(
|
|
168
|
+
`${devServerOrigin}/__server_params__.json`,
|
|
169
|
+
)
|
|
170
|
+
if (
|
|
171
|
+
testDirectoryUrl !== sourceDirectoryUrl &&
|
|
172
|
+
!urlIsInsideOf(testDirectoryUrl, sourceDirectoryUrl)
|
|
173
|
+
) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`testDirectoryUrl must be inside sourceDirectoryUrl when running tests on browser(s)`,
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
102
180
|
if (coverageEnabled) {
|
|
103
181
|
if (typeof coverageConfig !== "object") {
|
|
104
182
|
throw new TypeError(
|
|
@@ -135,16 +213,96 @@ export const executeTestPlan = async ({
|
|
|
135
213
|
)
|
|
136
214
|
}
|
|
137
215
|
}
|
|
216
|
+
if (coverageReportRootDirectoryUrl === undefined) {
|
|
217
|
+
coverageReportRootDirectoryUrl =
|
|
218
|
+
lookupPackageDirectory(testDirectoryUrl)
|
|
219
|
+
} else {
|
|
220
|
+
coverageReportRootDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
221
|
+
coverageReportRootDirectoryUrl,
|
|
222
|
+
"coverageReportRootDirectoryUrl",
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
if (coverageTempDirectoryUrl === undefined) {
|
|
226
|
+
coverageTempDirectoryUrl = new URL(
|
|
227
|
+
"./.coverage/tmp/",
|
|
228
|
+
coverageReportRootDirectoryUrl,
|
|
229
|
+
)
|
|
230
|
+
} else {
|
|
231
|
+
coverageTempDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
232
|
+
coverageTempDirectoryUrl,
|
|
233
|
+
"coverageTempDirectoryUrl",
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
if (coverageReportJson) {
|
|
237
|
+
if (coverageReportJsonFileUrl === undefined) {
|
|
238
|
+
coverageReportJsonFileUrl = new URL(
|
|
239
|
+
"./.coverage/coverage.json",
|
|
240
|
+
coverageReportRootDirectoryUrl,
|
|
241
|
+
)
|
|
242
|
+
} else {
|
|
243
|
+
coverageReportJsonFileUrl = assertAndNormalizeFileUrl(
|
|
244
|
+
coverageReportJsonFileUrl,
|
|
245
|
+
"coverageReportJsonFileUrl",
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (coverageReportHtml) {
|
|
250
|
+
if (coverageReportHtmlDirectoryUrl === undefined) {
|
|
251
|
+
coverageReportHtmlDirectoryUrl = new URL(
|
|
252
|
+
"./.coverage/",
|
|
253
|
+
coverageReportRootDirectoryUrl,
|
|
254
|
+
)
|
|
255
|
+
} else {
|
|
256
|
+
coverageReportHtmlDirectoryUrl = assertAndNormalizeDirectoryUrl(
|
|
257
|
+
coverageReportHtmlDirectoryUrl,
|
|
258
|
+
"coverageReportHtmlDirectoryUrl",
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
138
262
|
}
|
|
139
263
|
}
|
|
140
264
|
|
|
141
265
|
const logger = createLogger({ logLevel })
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
266
|
+
logger.debug(
|
|
267
|
+
createDetailedMessage(`Prepare executing plan`, {
|
|
268
|
+
runtimes: JSON.stringify(runtimes, null, " "),
|
|
269
|
+
}),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
// param normalization
|
|
273
|
+
{
|
|
274
|
+
if (coverageEnabled) {
|
|
275
|
+
if (Object.keys(coverageConfig).length === 0) {
|
|
276
|
+
logger.warn(
|
|
277
|
+
`coverageConfig is an empty object. Nothing will be instrumented for coverage so your coverage will be empty`,
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
if (
|
|
281
|
+
someNodeRuntime &&
|
|
282
|
+
coverageEnabled &&
|
|
283
|
+
coverageMethodForNodeJs === "NODE_V8_COVERAGE"
|
|
284
|
+
) {
|
|
285
|
+
if (process.env.NODE_V8_COVERAGE) {
|
|
286
|
+
// when runned multiple times, we don't want to keep previous files in this directory
|
|
287
|
+
await ensureEmptyDirectory(process.env.NODE_V8_COVERAGE)
|
|
288
|
+
} else {
|
|
289
|
+
coverageMethodForNodeJs = "Profiler"
|
|
290
|
+
logger.warn(
|
|
291
|
+
createDetailedMessage(
|
|
292
|
+
`process.env.NODE_V8_COVERAGE is required to generate coverage for Node.js subprocesses`,
|
|
293
|
+
{
|
|
294
|
+
"suggestion": `set process.env.NODE_V8_COVERAGE`,
|
|
295
|
+
"suggestion 2": `use coverageMethodForNodeJs: "Profiler". But it means coverage for child_process and worker_thread cannot be collected`,
|
|
296
|
+
},
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
146
302
|
}
|
|
147
303
|
|
|
304
|
+
testPlan = { ...testPlan, "**/.jsenv/": null }
|
|
305
|
+
|
|
148
306
|
const result = await executePlan(testPlan, {
|
|
149
307
|
signal,
|
|
150
308
|
handleSIGINT,
|
|
@@ -158,7 +316,7 @@ export const executeTestPlan = async ({
|
|
|
158
316
|
logFileRelativeUrl,
|
|
159
317
|
completedExecutionLogMerging,
|
|
160
318
|
completedExecutionLogAbbreviation,
|
|
161
|
-
rootDirectoryUrl,
|
|
319
|
+
rootDirectoryUrl: testDirectoryUrl,
|
|
162
320
|
devServerOrigin,
|
|
163
321
|
|
|
164
322
|
maxExecutionsInParallel,
|
|
@@ -174,8 +332,17 @@ export const executeTestPlan = async ({
|
|
|
174
332
|
coverageMethodForBrowsers,
|
|
175
333
|
coverageMethodForNodeJs,
|
|
176
334
|
coverageV8ConflictWarning,
|
|
177
|
-
|
|
335
|
+
coverageTempDirectoryUrl,
|
|
178
336
|
})
|
|
337
|
+
if (stopDevServerNeeded) {
|
|
338
|
+
// we are expecting ECONNRESET because server will be stopped by the request
|
|
339
|
+
basicFetch(`${devServerOrigin}/__stop__`).catch((e) => {
|
|
340
|
+
if (e.code === "ECONNRESET") {
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
throw e
|
|
344
|
+
})
|
|
345
|
+
}
|
|
179
346
|
if (
|
|
180
347
|
updateProcessExitCode &&
|
|
181
348
|
result.planSummary.counters.total !== result.planSummary.counters.completed
|
|
@@ -189,42 +356,29 @@ export const executeTestPlan = async ({
|
|
|
189
356
|
// keep this one first because it does ensureEmptyDirectory
|
|
190
357
|
// and in case coverage json file gets written in the same directory
|
|
191
358
|
// it must be done before
|
|
192
|
-
if (coverageEnabled &&
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
rootDirectoryUrl,
|
|
196
|
-
)
|
|
197
|
-
if (!urlIsInsideOf(coverageHtmlDirectoryUrl, rootDirectoryUrl)) {
|
|
198
|
-
throw new Error(
|
|
199
|
-
`coverageReportHtmlDirectory must be inside rootDirectoryUrl`,
|
|
200
|
-
)
|
|
201
|
-
}
|
|
202
|
-
await ensureEmptyDirectory(coverageHtmlDirectoryUrl)
|
|
203
|
-
const htmlCoverageDirectoryIndexFileUrl = `${coverageHtmlDirectoryUrl}index.html`
|
|
359
|
+
if (coverageEnabled && coverageReportHtml) {
|
|
360
|
+
await ensureEmptyDirectory(coverageReportHtmlDirectoryUrl)
|
|
361
|
+
const htmlCoverageDirectoryIndexFileUrl = `${coverageReportHtmlDirectoryUrl}index.html`
|
|
204
362
|
logger.info(
|
|
205
363
|
`-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`,
|
|
206
364
|
)
|
|
207
365
|
promises.push(
|
|
208
366
|
generateCoverageHtmlDirectory(planCoverage, {
|
|
209
|
-
rootDirectoryUrl,
|
|
367
|
+
rootDirectoryUrl: coverageReportRootDirectoryUrl,
|
|
210
368
|
coverageHtmlDirectoryRelativeUrl: urlToRelativeUrl(
|
|
211
|
-
|
|
212
|
-
|
|
369
|
+
coverageReportHtmlDirectoryUrl,
|
|
370
|
+
coverageReportRootDirectoryUrl,
|
|
213
371
|
),
|
|
214
372
|
coverageReportSkipEmpty,
|
|
215
373
|
coverageReportSkipFull,
|
|
216
374
|
}),
|
|
217
375
|
)
|
|
218
376
|
}
|
|
219
|
-
if (coverageEnabled &&
|
|
220
|
-
const coverageJsonFileUrl = new URL(
|
|
221
|
-
coverageReportJsonFile,
|
|
222
|
-
rootDirectoryUrl,
|
|
223
|
-
).href
|
|
377
|
+
if (coverageEnabled && coverageReportJson) {
|
|
224
378
|
promises.push(
|
|
225
379
|
generateCoverageJsonFile({
|
|
226
380
|
coverage: result.planCoverage,
|
|
227
|
-
coverageJsonFileUrl,
|
|
381
|
+
coverageJsonFileUrl: coverageReportJsonFileUrl,
|
|
228
382
|
logger,
|
|
229
383
|
}),
|
|
230
384
|
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { registerDirectoryLifecycle } from "@jsenv/filesystem"
|
|
2
|
+
|
|
3
|
+
export const watchSourceFiles = (
|
|
4
|
+
sourceDirectoryUrl,
|
|
5
|
+
callback,
|
|
6
|
+
{ sourceFileConfig = {}, keepProcessAlive, cooldownBetweenFileEvents },
|
|
7
|
+
) => {
|
|
8
|
+
// Project should use a dedicated directory (usually "src/")
|
|
9
|
+
// passed to the dev server via "sourceDirectoryUrl" param
|
|
10
|
+
// In that case all files inside the source directory should be watched
|
|
11
|
+
// But some project might want to use their root directory as source directory
|
|
12
|
+
// In that case source directory might contain files matching "node_modules/*" or ".git/*"
|
|
13
|
+
// And jsenv should not consider these as source files and watch them (to not hurt performances)
|
|
14
|
+
const watchPatterns = {
|
|
15
|
+
"**/*": true, // by default watch everything inside the source directory
|
|
16
|
+
"**/.*": false, // file starting with a dot -> do not watch
|
|
17
|
+
"**/.*/": false, // directory starting with a dot -> do not watch
|
|
18
|
+
"**/node_modules/": false, // node_modules directory -> do not watch
|
|
19
|
+
...sourceFileConfig,
|
|
20
|
+
}
|
|
21
|
+
const stopWatchingSourceFiles = registerDirectoryLifecycle(
|
|
22
|
+
sourceDirectoryUrl,
|
|
23
|
+
{
|
|
24
|
+
watchPatterns,
|
|
25
|
+
cooldownBetweenFileEvents,
|
|
26
|
+
keepProcessAlive,
|
|
27
|
+
recursive: true,
|
|
28
|
+
added: ({ relativeUrl }) => {
|
|
29
|
+
callback({
|
|
30
|
+
url: new URL(relativeUrl, sourceDirectoryUrl).href,
|
|
31
|
+
event: "added",
|
|
32
|
+
})
|
|
33
|
+
},
|
|
34
|
+
updated: ({ relativeUrl }) => {
|
|
35
|
+
callback({
|
|
36
|
+
url: new URL(relativeUrl, sourceDirectoryUrl).href,
|
|
37
|
+
event: "modified",
|
|
38
|
+
})
|
|
39
|
+
},
|
|
40
|
+
removed: ({ relativeUrl }) => {
|
|
41
|
+
callback({
|
|
42
|
+
url: new URL(relativeUrl, sourceDirectoryUrl).href,
|
|
43
|
+
event: "removed",
|
|
44
|
+
})
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
stopWatchingSourceFiles.watchPatterns = watchPatterns
|
|
49
|
+
return stopWatchingSourceFiles
|
|
50
|
+
}
|