@jsenv/core 30.4.2 → 31.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "30.4.2",
3
+ "version": "31.0.1",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -69,16 +69,16 @@
69
69
  "@jsenv/abort": "4.2.4",
70
70
  "@jsenv/ast": "3.0.3",
71
71
  "@jsenv/babel-plugins": "1.1.5",
72
- "@jsenv/filesystem": "4.1.9",
72
+ "@jsenv/filesystem": "4.2.0",
73
73
  "@jsenv/importmap": "1.2.1",
74
74
  "@jsenv/integrity": "0.0.1",
75
75
  "@jsenv/log": "3.3.4",
76
76
  "@jsenv/node-esm-resolution": "1.0.1",
77
- "@jsenv/plugin-bundling": "1.2.2",
78
- "@jsenv/server": "14.1.16",
77
+ "@jsenv/plugin-bundling": "2.0.0",
78
+ "@jsenv/server": "15.0.0",
79
79
  "@jsenv/sourcemap": "1.0.9",
80
80
  "@jsenv/uneval": "1.6.0",
81
- "@jsenv/url-meta": "7.0.2",
81
+ "@jsenv/url-meta": "7.1.0",
82
82
  "@jsenv/urls": "1.2.8",
83
83
  "@jsenv/utils": "2.0.1",
84
84
  "@paralleldrive/cuid2": "2.2.0",
@@ -28,7 +28,7 @@ import {
28
28
  urlToRelativeUrl,
29
29
  } from "@jsenv/urls"
30
30
  import {
31
- assertAndNormalizeDirectoryUrl,
31
+ validateDirectoryUrl,
32
32
  ensureEmptyDirectory,
33
33
  writeFileSync,
34
34
  registerDirectoryLifecycle,
@@ -155,7 +155,32 @@ export const build = async ({
155
155
  writeGeneratedFiles = false,
156
156
  assetManifest = versioningMethod === "filename",
157
157
  assetManifestFileRelativeUrl = "asset-manifest.json",
158
+ ...rest
158
159
  }) => {
160
+ // param validation
161
+ {
162
+ const unexpectedParamNames = Object.keys(rest)
163
+ if (unexpectedParamNames.length > 0) {
164
+ throw new TypeError(
165
+ `${unexpectedParamNames.join(",")}: there is no such param`,
166
+ )
167
+ }
168
+ const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl)
169
+ if (!rootDirectoryUrlValidation.valid) {
170
+ throw new TypeError(
171
+ `rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`,
172
+ )
173
+ }
174
+ rootDirectoryUrl = rootDirectoryUrlValidation.value
175
+ const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl)
176
+ if (!buildDirectoryUrlValidation.valid) {
177
+ throw new TypeError(
178
+ `buildDirectoryUrl ${buildDirectoryUrlValidation.message}, got ${buildDirectoryUrlValidation}`,
179
+ )
180
+ }
181
+ buildDirectoryUrl = buildDirectoryUrlValidation.value
182
+ }
183
+
159
184
  const operation = Abort.startOperation()
160
185
  operation.addAbortSignal(signal)
161
186
  if (handleSIGINT) {
@@ -169,8 +194,6 @@ export const build = async ({
169
194
  })
170
195
  }
171
196
 
172
- rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
173
- buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl)
174
197
  assertEntryPoints({ entryPoints })
175
198
  if (!["filename", "search_param"].includes(versioningMethod)) {
176
199
  throw new Error(
@@ -23,7 +23,7 @@ import {
23
23
  jsenvServiceErrorHandler,
24
24
  } from "@jsenv/server"
25
25
  import {
26
- assertAndNormalizeDirectoryUrl,
26
+ validateDirectoryUrl,
27
27
  registerDirectoryLifecycle,
28
28
  } from "@jsenv/filesystem"
29
29
  import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
@@ -44,10 +44,8 @@ export const startBuildServer = async ({
44
44
  handleSIGINT = true,
45
45
  logLevel,
46
46
  serverLogLevel = "warn",
47
- protocol = "http",
47
+ https,
48
48
  http2,
49
- certificate,
50
- privateKey,
51
49
  acceptAnyIp,
52
50
  hostname,
53
51
  port = 9779,
@@ -64,32 +62,55 @@ export const startBuildServer = async ({
64
62
  buildServerAutoreload = false,
65
63
  buildServerMainFile = getCallerPosition().url,
66
64
  cooldownBetweenFileEvents,
65
+ ...rest
67
66
  }) => {
68
- const logger = createLogger({ logLevel })
69
- rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
70
- buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl)
71
- if (buildIndexPath) {
72
- if (typeof buildIndexPath !== "string") {
67
+ // params validation
68
+ {
69
+ const unexpectedParamNames = Object.keys(rest)
70
+ if (unexpectedParamNames.length > 0) {
71
+ throw new TypeError(
72
+ `${unexpectedParamNames.join(",")}: there is no such param`,
73
+ )
74
+ }
75
+ const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl)
76
+ if (!rootDirectoryUrlValidation.valid) {
77
+ throw new TypeError(
78
+ `rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`,
79
+ )
80
+ }
81
+ rootDirectoryUrl = rootDirectoryUrlValidation.value
82
+ const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl)
83
+ if (!buildDirectoryUrlValidation.valid) {
73
84
  throw new TypeError(
74
- `buildIndexPath must be a string, got ${buildIndexPath}`,
85
+ `buildDirectoryUrl ${buildDirectoryUrlValidation.message}, got ${buildDirectoryUrlValidation}`,
75
86
  )
76
87
  }
77
- if (buildIndexPath[0] === "/") {
78
- buildIndexPath = buildIndexPath.slice(1)
79
- } else {
80
- const buildIndexUrl = new URL(buildIndexPath, buildDirectoryUrl).href
81
- if (!buildIndexUrl.startsWith(buildDirectoryUrl)) {
82
- throw new Error(
83
- `buildIndexPath must be relative, got ${buildIndexPath}`,
88
+ buildDirectoryUrl = buildDirectoryUrlValidation.value
89
+
90
+ if (buildIndexPath) {
91
+ if (typeof buildIndexPath !== "string") {
92
+ throw new TypeError(
93
+ `buildIndexPath must be a string, got ${buildIndexPath}`,
84
94
  )
85
95
  }
86
- buildIndexPath = buildIndexUrl.slice(buildDirectoryUrl.length)
87
- }
88
- if (!existsSync(new URL(buildIndexPath, buildDirectoryUrl))) {
89
- buildIndexPath = null
96
+ if (buildIndexPath[0] === "/") {
97
+ buildIndexPath = buildIndexPath.slice(1)
98
+ } else {
99
+ const buildIndexUrl = new URL(buildIndexPath, buildDirectoryUrl).href
100
+ if (!buildIndexUrl.startsWith(buildDirectoryUrl)) {
101
+ throw new Error(
102
+ `buildIndexPath must be relative, got ${buildIndexPath}`,
103
+ )
104
+ }
105
+ buildIndexPath = buildIndexUrl.slice(buildDirectoryUrl.length)
106
+ }
107
+ if (!existsSync(new URL(buildIndexPath, buildDirectoryUrl))) {
108
+ buildIndexPath = null
109
+ }
90
110
  }
91
111
  }
92
112
 
113
+ const logger = createLogger({ logLevel })
93
114
  const operation = Abort.startOperation()
94
115
  operation.addAbortSignal(signal)
95
116
  if (handleSIGINT) {
@@ -169,10 +190,8 @@ export const startBuildServer = async ({
169
190
  logLevel: serverLogLevel,
170
191
  startLog: false,
171
192
 
172
- protocol,
193
+ https,
173
194
  http2,
174
- certificate,
175
- privateKey,
176
195
  acceptAnyIp,
177
196
  hostname,
178
197
  port,
@@ -20,6 +20,7 @@ export const createFileService = ({
20
20
  logLevel,
21
21
  serverStopCallbacks,
22
22
  serverEventsDispatcher,
23
+ contextCache,
23
24
 
24
25
  rootDirectoryUrl,
25
26
  runtimeCompat,
@@ -82,7 +83,6 @@ export const createFileService = ({
82
83
  })
83
84
  serverStopCallbacks.push(stopWatchingClientFiles)
84
85
 
85
- const contextCache = new Map()
86
86
  const getOrCreateContext = (request) => {
87
87
  const { runtimeName, runtimeVersion } = parseUserAgentHeader(
88
88
  request.headers["user-agent"],
@@ -150,6 +150,7 @@ export const createFileService = ({
150
150
  ribbon,
151
151
  }),
152
152
  ],
153
+ supervisor,
153
154
  minification: false,
154
155
  sourcemaps,
155
156
  sourcemapsSourcesProtocol,
@@ -1,6 +1,6 @@
1
1
  import { parentPort } from "node:worker_threads"
2
2
  import {
3
- assertAndNormalizeDirectoryUrl,
3
+ validateDirectoryUrl,
4
4
  registerDirectoryLifecycle,
5
5
  } from "@jsenv/filesystem"
6
6
  import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
@@ -32,12 +32,10 @@ export const startDevServer = async ({
32
32
  handleSIGINT = true,
33
33
  logLevel = "info",
34
34
  serverLogLevel = "warn",
35
- protocol = "http",
35
+ https,
36
36
  // it's better to use http1 by default because it allows to get statusText in devtools
37
37
  // which gives valuable information when there is errors
38
38
  http2 = false,
39
- certificate,
40
- privateKey,
41
39
  hostname,
42
40
  port = 3456,
43
41
  acceptAnyIp,
@@ -82,9 +80,26 @@ export const startDevServer = async ({
82
80
  // no real need to write files during github workflow
83
81
  // and mitigates https://github.com/actions/runner-images/issues/3885
84
82
  writeGeneratedFiles = !process.env.CI,
83
+ ...rest
85
84
  }) => {
85
+ // params type checking
86
+ {
87
+ const unexpectedParamNames = Object.keys(rest)
88
+ if (unexpectedParamNames.length > 0) {
89
+ throw new TypeError(
90
+ `${unexpectedParamNames.join(",")}: there is no such param`,
91
+ )
92
+ }
93
+ const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl)
94
+ if (!rootDirectoryUrlValidation.valid) {
95
+ throw new TypeError(
96
+ `rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`,
97
+ )
98
+ }
99
+ rootDirectoryUrl = rootDirectoryUrlValidation.value
100
+ }
101
+
86
102
  const logger = createLogger({ logLevel })
87
- rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
88
103
  const operation = Abort.startOperation()
89
104
  operation.addAbortSignal(signal)
90
105
  if (handleSIGINT) {
@@ -158,6 +173,7 @@ export const startDevServer = async ({
158
173
  serverStopCallbacks.push(() => {
159
174
  serverEventsDispatcher.destroy()
160
175
  })
176
+ const contextCache = new Map()
161
177
  const server = await startServer({
162
178
  signal,
163
179
  stopOnExit: false,
@@ -167,10 +183,8 @@ export const startDevServer = async ({
167
183
  logLevel: serverLogLevel,
168
184
  startLog: false,
169
185
 
170
- protocol,
186
+ https,
171
187
  http2,
172
- certificate,
173
- privateKey,
174
188
  acceptAnyIp,
175
189
  hostname,
176
190
  port,
@@ -195,6 +209,7 @@ export const startDevServer = async ({
195
209
  logLevel,
196
210
  serverStopCallbacks,
197
211
  serverEventsDispatcher,
212
+ contextCache,
198
213
 
199
214
  rootDirectoryUrl,
200
215
  runtimeCompat,
@@ -265,13 +280,13 @@ export const startDevServer = async ({
265
280
  sendErrorDetails: true,
266
281
  }),
267
282
  ],
268
- onStop: (reason) => {
269
- onStop()
270
- serverStopCallbacks.forEach((serverStopCallback) => {
271
- serverStopCallback(reason)
272
- })
273
- serverStopCallbacks.length = 0
274
- },
283
+ })
284
+ server.stoppedPromise.then((reason) => {
285
+ onStop()
286
+ serverStopCallbacks.forEach((serverStopCallback) => {
287
+ serverStopCallback(reason)
288
+ })
289
+ serverStopCallbacks.length = 0
275
290
  })
276
291
  startDevServerTask.done()
277
292
  if (hostname) {
@@ -291,5 +306,6 @@ export const startDevServer = async ({
291
306
  stop: () => {
292
307
  server.stop()
293
308
  },
309
+ contextCache,
294
310
  }
295
311
  }
@@ -90,7 +90,7 @@ export const execute = async ({
90
90
  result = resultTransformer(result)
91
91
 
92
92
  try {
93
- if (result.status === "errored") {
93
+ if (result.status === "failed") {
94
94
  if (ignoreError) {
95
95
  return result
96
96
  }
@@ -119,7 +119,7 @@ export const run = async ({
119
119
  cb(runResult)
120
120
  } catch (e) {
121
121
  cb({
122
- status: "errored",
122
+ status: "failed",
123
123
  errors: [e],
124
124
  })
125
125
  }
@@ -147,7 +147,7 @@ export const run = async ({
147
147
  result.status = "aborted"
148
148
  }
149
149
  } else {
150
- result.status = "errored"
150
+ result.status = "failed"
151
151
  result.errors.push(e)
152
152
  }
153
153
  } finally {
@@ -331,12 +331,12 @@ export const createRuntimeFromPlaywright = ({
331
331
  }
332
332
  if (winner.name === "error") {
333
333
  let error = winner.data
334
- result.status = "errored"
334
+ result.status = "failed"
335
335
  result.errors.push(error)
336
336
  return
337
337
  }
338
338
  if (winner.name === "closed") {
339
- result.status = "errored"
339
+ result.status = "failed"
340
340
  result.errors.push(
341
341
  isBrowserDedicatedToExecution
342
342
  ? new Error(`browser disconnected during execution`)
@@ -350,8 +350,8 @@ export const createRuntimeFromPlaywright = ({
350
350
  result.namespace = executionResults
351
351
  Object.keys(executionResults).forEach((key) => {
352
352
  const executionResult = executionResults[key]
353
- if (executionResult.status === "errored") {
354
- result.status = "errored"
353
+ if (executionResult.status === "failed") {
354
+ result.status = "failed"
355
355
  result.errors.push({
356
356
  ...executionResult.exception,
357
357
  stack: executionResult.exception.text,
@@ -370,7 +370,7 @@ export const createRuntimeFromPlaywright = ({
370
370
  await callback()
371
371
  }, Promise.resolve())
372
372
  } catch (e) {
373
- result.status = "errored"
373
+ result.status = "failed"
374
374
  result.errors = [e]
375
375
  }
376
376
  if (keepRunning) {
@@ -219,7 +219,7 @@ nodeChildProcess.run = async ({
219
219
  if (winner.name === "error") {
220
220
  const error = winner.data
221
221
  removeOutputListener()
222
- result.status = "errored"
222
+ result.status = "failed"
223
223
  result.errors.push(error)
224
224
  return
225
225
  }
@@ -227,7 +227,7 @@ nodeChildProcess.run = async ({
227
227
  const { code } = winner.data
228
228
  await cleanup("process exit")
229
229
  if (code === 12) {
230
- result.status = "errored"
230
+ result.status = "failed"
231
231
  result.errors.push(
232
232
  new Error(
233
233
  `node process exited with 12 (the forked child process wanted to use a non-available port for debug)`,
@@ -242,13 +242,13 @@ nodeChildProcess.run = async ({
242
242
  code === EXIT_CODES.SIGTERM ||
243
243
  code === EXIT_CODES.SIGABORT
244
244
  ) {
245
- result.status = "errored"
245
+ result.status = "failed"
246
246
  result.errors.push(new Error(`node process exited during execution`))
247
247
  return
248
248
  }
249
249
  // process.exit(1) in child process or process.exitCode = 1 + process.exit()
250
250
  // means there was an error even if we don't know exactly what.
251
- result.status = "errored"
251
+ result.status = "failed"
252
252
  result.errors.push(
253
253
  new Error(`node process exited with code ${code} during execution`),
254
254
  )
@@ -256,7 +256,7 @@ nodeChildProcess.run = async ({
256
256
  }
257
257
  const { status, value } = winner.data
258
258
  if (status === "action-failed") {
259
- result.status = "errored"
259
+ result.status = "failed"
260
260
  result.errors.push(value)
261
261
  return
262
262
  }
@@ -270,7 +270,7 @@ nodeChildProcess.run = async ({
270
270
  try {
271
271
  await writeResult()
272
272
  } catch (e) {
273
- result.status = "errored"
273
+ result.status = "failed"
274
274
  result.errors.push(e)
275
275
  }
276
276
  if (keepRunning) {
@@ -171,7 +171,7 @@ nodeWorkerThread.run = async ({
171
171
  if (winner.name === "error") {
172
172
  const error = winner.data
173
173
  removeOutputListener()
174
- result.status = "errored"
174
+ result.status = "failed"
175
175
  result.errors.push(error)
176
176
  return
177
177
  }
@@ -179,7 +179,7 @@ nodeWorkerThread.run = async ({
179
179
  const { code } = winner.data
180
180
  await cleanup("process exit")
181
181
  if (code === 12) {
182
- result.status = "errored"
182
+ result.status = "failed"
183
183
  result.errors.push(
184
184
  new Error(
185
185
  `node process exited with 12 (the forked child process wanted to use a non-available port for debug)`,
@@ -194,7 +194,7 @@ nodeWorkerThread.run = async ({
194
194
  code === EXIT_CODES.SIGTERM ||
195
195
  code === EXIT_CODES.SIGABORT
196
196
  ) {
197
- result.status = "errored"
197
+ result.status = "failed"
198
198
  result.errors.push(
199
199
  new Error(`node worker thread exited during execution`),
200
200
  )
@@ -202,7 +202,7 @@ nodeWorkerThread.run = async ({
202
202
  }
203
203
  // process.exit(1) in child process or process.exitCode = 1 + process.exit()
204
204
  // means there was an error even if we don't know exactly what.
205
- result.status = "errored"
205
+ result.status = "failed"
206
206
  result.errors.push(
207
207
  new Error(
208
208
  `node worker thread exited with code ${code} during execution`,
@@ -211,7 +211,7 @@ nodeWorkerThread.run = async ({
211
211
  }
212
212
  const { status, value } = winner.data
213
213
  if (status === "action-failed") {
214
- result.status = "errored"
214
+ result.status = "failed"
215
215
  result.errors.push(value)
216
216
  return
217
217
  }
@@ -225,7 +225,7 @@ nodeWorkerThread.run = async ({
225
225
  try {
226
226
  await writeResult()
227
227
  } catch (e) {
228
- result.status = "errored"
228
+ result.status = "failed"
229
229
  result.errors.push(e)
230
230
  }
231
231
 
@@ -673,7 +673,17 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
673
673
  // (error hapenning before urlInfo.content can be set, or 404 for instance)
674
674
  // in that case we can't write anything
675
675
  } else {
676
- writeFileSync(new URL(generatedUrl), urlInfo.content)
676
+ let contentIsInlined = urlInfo.isInline
677
+ if (
678
+ contentIsInlined &&
679
+ context.supervisor &&
680
+ urlGraph.getUrlInfo(urlInfo.inlineUrlSite.url).type === "html"
681
+ ) {
682
+ contentIsInlined = false
683
+ }
684
+ if (!contentIsInlined) {
685
+ writeFileSync(new URL(generatedUrl), urlInfo.content)
686
+ }
677
687
  const { sourcemapGeneratedUrl, sourcemap } = urlInfo
678
688
  if (sourcemapGeneratedUrl && sourcemap) {
679
689
  writeFileSync(
@@ -814,7 +814,7 @@ window.__supervisor__ = (() => {
814
814
  }
815
815
 
816
816
  const onError = (e) => {
817
- executionResult.status = "errored"
817
+ executionResult.status = "failed"
818
818
  const exception = supervisor.createException({ reason: e })
819
819
  if (exception.needsReport) {
820
820
  supervisor.reportException(exception)
@@ -51,10 +51,7 @@ export const jsenvPluginAsJsClassicLibrary = ({
51
51
  ...context,
52
52
  buildDirectoryUrl: context.outDirectoryUrl,
53
53
  },
54
- options: {
55
- babelHelpersChunk: false,
56
- preserveDynamicImport: true,
57
- },
54
+ preserveDynamicImport: true,
58
55
  })
59
56
  const jsModuleBundledUrlInfo = bundleUrlInfos[jsModuleUrlInfo.url]
60
57
  if (context.dev) {
@@ -225,7 +225,7 @@ export const executePlan = async (
225
225
  total: executionSteps.length,
226
226
  aborted: 0,
227
227
  timedout: 0,
228
- errored: 0,
228
+ failed: 0,
229
229
  completed: 0,
230
230
  done: 0,
231
231
  }
@@ -306,7 +306,7 @@ export const executePlan = async (
306
306
  })
307
307
  } else {
308
308
  executionResult = {
309
- status: "errored",
309
+ status: "failed",
310
310
  errors: [
311
311
  new Error(
312
312
  `No file at ${fileRelativeUrl} for execution "${executionName}"`,
@@ -336,8 +336,8 @@ export const executePlan = async (
336
336
  counters.aborted++
337
337
  } else if (executionResult.status === "timedout") {
338
338
  counters.timedout++
339
- } else if (executionResult.status === "errored") {
340
- counters.errored++
339
+ } else if (executionResult.status === "failed") {
340
+ counters.failed++
341
341
  } else if (executionResult.status === "completed") {
342
342
  counters.completed++
343
343
  }
@@ -5,10 +5,7 @@ import {
5
5
  urlIsInsideOf,
6
6
  urlToRelativeUrl,
7
7
  } from "@jsenv/urls"
8
- import {
9
- ensureEmptyDirectory,
10
- assertAndNormalizeDirectoryUrl,
11
- } from "@jsenv/filesystem"
8
+ import { ensureEmptyDirectory, validateDirectoryUrl } from "@jsenv/filesystem"
12
9
  import { createLogger, createDetailedMessage } from "@jsenv/log"
13
10
 
14
11
  import { generateCoverageJsonFile } from "./coverage/coverage_reporter_json_file.js"
@@ -82,51 +79,72 @@ export const executeTestPlan = async ({
82
79
  coverageReportTextLog = true,
83
80
  coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
84
81
  coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
82
+ ...rest
85
83
  }) => {
86
- const logger = createLogger({ logLevel })
87
- rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
88
- if (typeof testPlan !== "object") {
89
- throw new Error(`testPlan must be an object, got ${testPlan}`)
90
- }
91
- if (coverageEnabled) {
92
- if (typeof coverageConfig !== "object") {
84
+ // param validation
85
+ {
86
+ const unexpectedParamNames = Object.keys(rest)
87
+ if (unexpectedParamNames.length > 0) {
93
88
  throw new TypeError(
94
- `coverageConfig must be an object, got ${coverageConfig}`,
89
+ `${unexpectedParamNames.join(",")}: there is no such param`,
95
90
  )
96
91
  }
97
- if (Object.keys(coverageConfig).length === 0) {
98
- logger.warn(
99
- `coverageConfig is an empty object. Nothing will be instrumented for coverage so your coverage will be empty`,
92
+ const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl)
93
+ if (!rootDirectoryUrlValidation.valid) {
94
+ throw new TypeError(
95
+ `rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`,
100
96
  )
101
97
  }
102
- if (!coverageAndExecutionAllowed) {
103
- const associationsForExecute = URL_META.resolveAssociations(
104
- { execute: testPlan },
105
- "file:///",
106
- )
107
- const associationsForCover = URL_META.resolveAssociations(
108
- { cover: coverageConfig },
109
- "file:///",
110
- )
111
- const patternsMatchingCoverAndExecute = Object.keys(
112
- associationsForExecute.execute,
113
- ).filter((testPlanPattern) => {
114
- const { cover } = URL_META.applyAssociations({
115
- url: testPlanPattern,
116
- associations: associationsForCover,
117
- })
118
- return cover
119
- })
120
- if (patternsMatchingCoverAndExecute.length) {
121
- // It would be strange, for a given file to be both covered and executed
122
- throw new Error(
123
- createDetailedMessage(`some file will be both covered and executed`, {
124
- patterns: patternsMatchingCoverAndExecute,
125
- }),
98
+ rootDirectoryUrl = rootDirectoryUrlValidation.value
99
+ if (typeof testPlan !== "object") {
100
+ throw new Error(`testPlan must be an object, got ${testPlan}`)
101
+ }
102
+ if (coverageEnabled) {
103
+ if (typeof coverageConfig !== "object") {
104
+ throw new TypeError(
105
+ `coverageConfig must be an object, got ${coverageConfig}`,
126
106
  )
127
107
  }
108
+ if (!coverageAndExecutionAllowed) {
109
+ const associationsForExecute = URL_META.resolveAssociations(
110
+ { execute: testPlan },
111
+ "file:///",
112
+ )
113
+ const associationsForCover = URL_META.resolveAssociations(
114
+ { cover: coverageConfig },
115
+ "file:///",
116
+ )
117
+ const patternsMatchingCoverAndExecute = Object.keys(
118
+ associationsForExecute.execute,
119
+ ).filter((testPlanPattern) => {
120
+ const { cover } = URL_META.applyAssociations({
121
+ url: testPlanPattern,
122
+ associations: associationsForCover,
123
+ })
124
+ return cover
125
+ })
126
+ if (patternsMatchingCoverAndExecute.length) {
127
+ // It would be strange, for a given file to be both covered and executed
128
+ throw new Error(
129
+ createDetailedMessage(
130
+ `some file will be both covered and executed`,
131
+ {
132
+ patterns: patternsMatchingCoverAndExecute,
133
+ },
134
+ ),
135
+ )
136
+ }
137
+ }
128
138
  }
129
139
  }
140
+
141
+ const logger = createLogger({ logLevel })
142
+ if (Object.keys(coverageConfig).length === 0) {
143
+ logger.warn(
144
+ `coverageConfig is an empty object. Nothing will be instrumented for coverage so your coverage will be empty`,
145
+ )
146
+ }
147
+
130
148
  const result = await executePlan(testPlan, {
131
149
  signal,
132
150
  handleSIGINT,