@jsenv/core 27.8.1 → 28.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "27.8.1",
3
+ "version": "28.0.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -73,7 +73,7 @@
73
73
  "@jsenv/integrity": "0.0.1",
74
74
  "@jsenv/log": "3.1.0",
75
75
  "@jsenv/node-esm-resolution": "0.1.0",
76
- "@jsenv/server": "13.1.0",
76
+ "@jsenv/server": "14.0.0",
77
77
  "@jsenv/sourcemap": "1.0.4",
78
78
  "@jsenv/uneval": "1.6.0",
79
79
  "@jsenv/url-meta": "7.0.0",
@@ -41,7 +41,7 @@ export const startBuildServer = async ({
41
41
  certificate,
42
42
  privateKey,
43
43
  acceptAnyIp,
44
- host,
44
+ hostname,
45
45
  port = 9779,
46
46
  services = [],
47
47
  keepProcessAlive = true,
@@ -163,7 +163,7 @@ export const startBuildServer = async ({
163
163
  certificate,
164
164
  privateKey,
165
165
  acceptAnyIp,
166
- host,
166
+ hostname,
167
167
  port,
168
168
  serverTiming: true,
169
169
  requestWaitingMs: 60_000,
@@ -22,7 +22,7 @@ export const startDevServer = async ({
22
22
  http2 = false,
23
23
  certificate,
24
24
  privateKey,
25
- host,
25
+ hostname,
26
26
  port = 3456,
27
27
  acceptAnyIp,
28
28
  keepProcessAlive = true,
@@ -150,7 +150,7 @@ export const startDevServer = async ({
150
150
  http2,
151
151
  certificate,
152
152
  privateKey,
153
- host,
153
+ hostname,
154
154
  port,
155
155
  services,
156
156
 
@@ -1,9 +1,8 @@
1
1
  import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
2
-
3
2
  import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
4
3
  import { createLogger } from "@jsenv/log"
5
4
 
6
- import { startOmegaServer } from "@jsenv/core/src/omega/omega_server.js"
5
+ import { pingServer } from "../ping_server.js"
7
6
  import { run } from "./run.js"
8
7
 
9
8
  export const execute = async ({
@@ -11,12 +10,13 @@ export const execute = async ({
11
10
  handleSIGINT = true,
12
11
  logLevel,
13
12
  rootDirectoryUrl,
13
+ devServerOrigin,
14
14
 
15
15
  fileRelativeUrl,
16
16
  allocatedMs,
17
17
  mirrorConsole = true,
18
18
  keepRunning = false,
19
- services,
19
+
20
20
  collectConsole,
21
21
  collectCoverage,
22
22
  coverageTempDirectoryUrl,
@@ -24,21 +24,6 @@ export const execute = async ({
24
24
  runtime,
25
25
  runtimeParams,
26
26
 
27
- scenarios = { dev: true },
28
- plugins = [],
29
- nodeEsmResolution,
30
- fileSystemMagicResolution,
31
- transpilation,
32
- htmlSupervisor = true,
33
- sourcemaps = "inline",
34
- writeGeneratedFiles = false,
35
-
36
- port,
37
- protocol,
38
- http2,
39
- certificate,
40
- privateKey,
41
-
42
27
  ignoreError = false,
43
28
  }) => {
44
29
  const logger = createLogger({ logLevel })
@@ -59,44 +44,21 @@ export const execute = async ({
59
44
  let resultTransformer = (result) => result
60
45
  runtimeParams = {
61
46
  rootDirectoryUrl,
47
+ devServerOrigin,
62
48
  fileRelativeUrl,
63
49
  ...runtimeParams,
64
50
  }
65
- if (runtime.needsServer) {
66
- const server = await startOmegaServer({
67
- signal: executeOperation.signal,
68
- logLevel: "warn",
69
- keepProcessAlive: false,
70
- services,
71
- port,
72
- protocol,
73
- http2,
74
- certificate,
75
- privateKey,
76
-
77
- rootDirectoryUrl,
78
- scenarios,
79
- runtimeCompat: { [runtime.name]: runtime.version },
80
-
81
- plugins,
82
-
83
- htmlSupervisor,
84
- nodeEsmResolution,
85
- fileSystemMagicResolution,
86
- transpilation,
87
- sourcemaps,
88
- writeGeneratedFiles,
89
- })
90
- executeOperation.addEndCallback(async () => {
91
- await server.stop("execution done")
92
- })
93
- runtimeParams = {
94
- ...runtimeParams,
95
- server,
51
+ if (runtime.type === "browser") {
52
+ if (!devServerOrigin) {
53
+ throw new TypeError(
54
+ `devServerOrigin is required when running tests on browser(s)`,
55
+ )
96
56
  }
97
- resultTransformer = (result) => {
98
- result.server = server
99
- return result
57
+ const devServerStarted = await pingServer(devServerOrigin)
58
+ if (!devServerStarted) {
59
+ throw new Error(
60
+ `dev server not started at ${devServerOrigin}. It is required to run tests`,
61
+ )
100
62
  }
101
63
  }
102
64
 
@@ -27,7 +27,6 @@ export const createRuntimeFromPlaywright = ({
27
27
  type: "browser",
28
28
  name: browserName,
29
29
  version: browserVersion,
30
- needsServer: true,
31
30
  }
32
31
  let browserAndContextPromise
33
32
  runtime.run = async ({
@@ -35,7 +34,7 @@ export const createRuntimeFromPlaywright = ({
35
34
  logger,
36
35
  rootDirectoryUrl,
37
36
  fileRelativeUrl,
38
- server,
37
+ devServerOrigin,
39
38
 
40
39
  // measurePerformance,
41
40
  collectPerformance,
@@ -98,7 +97,19 @@ export const createRuntimeFromPlaywright = ({
98
97
  }
99
98
  await disconnected
100
99
  }
101
- const page = await browserContext.newPage()
100
+ const coverageInHeaders =
101
+ coverageEnabled &&
102
+ (!coveragePlaywrightAPIAvailable ||
103
+ coverageMethodForBrowsers !== "playwright_api")
104
+ const page = await browserContext.newPage({
105
+ extraHTTPHeaders: {
106
+ ...(coverageInHeaders
107
+ ? {
108
+ "x-coverage-istanbul": JSON.stringify(coverageConfig),
109
+ }
110
+ : {}),
111
+ },
112
+ })
102
113
  const closePage = async () => {
103
114
  try {
104
115
  await page.close()
@@ -128,7 +139,7 @@ export const createRuntimeFromPlaywright = ({
128
139
  (v8CoveragesWithWebUrl) => {
129
140
  const fsUrl = moveUrl({
130
141
  url: v8CoveragesWithWebUrl.url,
131
- from: `${server.origin}/`,
142
+ from: `${devServerOrigin}/`,
132
143
  to: rootDirectoryUrl,
133
144
  preferAbsolute: true,
134
145
  })
@@ -202,7 +213,7 @@ export const createRuntimeFromPlaywright = ({
202
213
  })
203
214
  }
204
215
 
205
- const fileClientUrl = new URL(fileRelativeUrl, `${server.origin}/`).href
216
+ const fileClientUrl = new URL(fileRelativeUrl, `${devServerOrigin}/`).href
206
217
 
207
218
  // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-console
208
219
  const removeConsoleListener = registerEvent({
@@ -302,7 +313,7 @@ export const createRuntimeFromPlaywright = ({
302
313
  const { exceptionSource } = returnValue
303
314
  const error = evalException(exceptionSource, {
304
315
  rootDirectoryUrl,
305
- server,
316
+ devServerOrigin,
306
317
  transformErrorHook,
307
318
  })
308
319
  cb({
@@ -525,13 +536,13 @@ const registerEvent = ({ object, eventType, callback }) => {
525
536
 
526
537
  const evalException = (
527
538
  exceptionSource,
528
- { rootDirectoryUrl, server, transformErrorHook },
539
+ { rootDirectoryUrl, devServerOrigin, transformErrorHook },
529
540
  ) => {
530
541
  const script = new Script(exceptionSource, { filename: "" })
531
542
  const error = script.runInThisContext()
532
543
  if (error && error instanceof Error) {
533
544
  const remoteRootRegexp = new RegExp(
534
- escapeRegexpSpecialChars(`${server.origin}/`),
545
+ escapeRegexpSpecialChars(`${devServerOrigin}/`),
535
546
  "g",
536
547
  )
537
548
  error.stack = error.stack.replace(remoteRootRegexp, rootDirectoryUrl)
package/src/main.js CHANGED
@@ -21,6 +21,9 @@ export { nodeWorkerThread } from "./execute/runtimes/node/node_worker_thread.js"
21
21
  export { build } from "./build/build.js"
22
22
  export { startBuildServer } from "./build/start_build_server.js"
23
23
 
24
+ // helpers
25
+ export { pingServer } from "./ping_server.js"
26
+
24
27
  // advanced
25
28
  export { execute } from "./execute/execute.js"
26
29
  export { jsenvPluginInjectGlobals } from "./plugins/inject_globals/jsenv_plugin_inject_globals.js"
@@ -18,7 +18,7 @@ export const startOmegaServer = async ({
18
18
  privateKey,
19
19
  certificate,
20
20
  acceptAnyIp,
21
- host,
21
+ hostname,
22
22
  port = 0,
23
23
  keepProcessAlive = false,
24
24
  onStop = () => {},
@@ -62,7 +62,7 @@ export const startOmegaServer = async ({
62
62
  certificate,
63
63
  privateKey,
64
64
  acceptAnyIp,
65
- host,
65
+ hostname,
66
66
  port,
67
67
  requestWaitingMs: 60_1000,
68
68
  services: [
@@ -0,0 +1,30 @@
1
+ import { createServer } from "node:net"
2
+
3
+ export const pingServer = async (url) => {
4
+ const server = createServer()
5
+ const { hostname, port } = new URL(url)
6
+
7
+ try {
8
+ await new Promise((resolve, reject) => {
9
+ server.on("error", reject)
10
+ server.on("listening", () => {
11
+ resolve()
12
+ })
13
+ server.listen(port, hostname)
14
+ })
15
+ } catch (error) {
16
+ if (error && error.code === "EADDRINUSE") {
17
+ return true
18
+ }
19
+ if (error && error.code === "EACCES") {
20
+ return true
21
+ }
22
+ throw error
23
+ }
24
+ await new Promise((resolve, reject) => {
25
+ server.on("error", reject)
26
+ server.on("close", resolve)
27
+ server.close()
28
+ })
29
+ return false
30
+ }
@@ -28,7 +28,7 @@ export const jsenvPluginImportMetaScenarios = () => {
28
28
  babelPlugins: [babelPluginMetadataImportMetaScenarios],
29
29
  urlInfo,
30
30
  })
31
- const { dev = [], test = [], build = [] } = metadata.importMetaScenarios
31
+ const { dev = [], build = [] } = metadata.importMetaScenarios
32
32
  const replacements = []
33
33
  const replace = (path, value) => {
34
34
  replacements.push({ path, value })
@@ -38,9 +38,6 @@ export const jsenvPluginImportMetaScenarios = () => {
38
38
  dev.forEach((path) => {
39
39
  replace(path, "undefined")
40
40
  })
41
- test.forEach((path) => {
42
- replace(path, context.scenarios.test ? "true" : "undefined")
43
- })
44
41
  build.forEach((path) => {
45
42
  replace(path, "true")
46
43
  })
@@ -52,11 +49,6 @@ export const jsenvPluginImportMetaScenarios = () => {
52
49
  dev.forEach((path) => {
53
50
  replace(path, "true")
54
51
  })
55
- if (context.scenarios.test) {
56
- test.forEach((path) => {
57
- replace(path, "true")
58
- })
59
- }
60
52
  }
61
53
  const magicSource = createMagicSource(urlInfo.content)
62
54
  replacements.forEach(({ path, value }) => {
@@ -1,5 +1,6 @@
1
1
  import { applyBabelPlugins } from "@jsenv/ast"
2
2
 
3
+ import { babelPluginInstrument } from "@jsenv/core/src/test/coverage/babel_plugin_instrument.js"
3
4
  import { RUNTIME_COMPAT } from "@jsenv/core/src/omega/compat/runtime_compat.js"
4
5
  import { getBaseBabelPluginStructure } from "./helpers/babel_plugin_structure.js"
5
6
  import { babelPluginBabelHelpersAsJsenvImports } from "./helpers/babel_plugin_babel_helpers_as_jsenv_imports.js"
@@ -54,6 +55,18 @@ export const jsenvPluginBabel = ({
54
55
  isJsModule,
55
56
  getImportSpecifier,
56
57
  })
58
+ if (context.scenarios.dev) {
59
+ const requestHeaders = context.request.headers
60
+ if (requestHeaders["x-coverage-instanbul"]) {
61
+ babelPluginStructure["transform-instrument"] = [
62
+ babelPluginInstrument,
63
+ {
64
+ rootDirectoryUrl: context.rootDirectoryUrl,
65
+ coverageConfig: JSON.parse(requestHeaders["x-coverage-instanbul"]),
66
+ },
67
+ ]
68
+ }
69
+ }
57
70
  if (getCustomBabelPlugins) {
58
71
  Object.assign(babelPluginStructure, getCustomBabelPlugins(context))
59
72
  }
@@ -14,11 +14,10 @@ import {
14
14
  import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
15
15
  import { ensureEmptyDirectory, writeFileSync } from "@jsenv/filesystem"
16
16
 
17
- import { babelPluginInstrument } from "./coverage/babel_plugin_instrument.js"
18
17
  import { reportToCoverage } from "./coverage/report_to_coverage.js"
19
- import { startOmegaServer } from "@jsenv/core/src/omega/omega_server.js"
20
18
  import { run } from "@jsenv/core/src/execute/run.js"
21
19
 
20
+ import { pingServer } from "../ping_server.js"
22
21
  import { ensureGlobalGc } from "./gc.js"
23
22
  import { generateExecutionSteps } from "./execution_steps.js"
24
23
  import { createExecutionLog, createSummaryLog } from "./logs_file_execution.js"
@@ -37,10 +36,10 @@ export const executePlan = async (
37
36
  logFileRelativeUrl,
38
37
  completedExecutionLogMerging,
39
38
  completedExecutionLogAbbreviation,
40
-
41
39
  rootDirectoryUrl,
40
+ devServerOrigin,
41
+
42
42
  keepRunning,
43
- services,
44
43
  defaultMsAllocatedPerExecution,
45
44
  maxExecutionsInParallel,
46
45
  failFast,
@@ -55,20 +54,6 @@ export const executePlan = async (
55
54
  coverageV8ConflictWarning,
56
55
  coverageTempDirectoryRelativeUrl,
57
56
 
58
- scenarios,
59
- sourcemaps,
60
- plugins,
61
- nodeEsmResolution,
62
- fileSystemMagicResolution,
63
- transpilation,
64
- writeGeneratedFiles,
65
-
66
- protocol,
67
- privateKey,
68
- certificate,
69
- host,
70
- port,
71
-
72
57
  beforeExecutionCallback = () => {},
73
58
  afterExecutionCallback = () => {},
74
59
  } = {},
@@ -88,7 +73,7 @@ export const executePlan = async (
88
73
  const { runtime } = executionConfig
89
74
  if (runtime) {
90
75
  runtimes[runtime.name] = runtime.version
91
- if (runtime.needsServer) {
76
+ if (runtime.type === "browser") {
92
77
  someNeedsServer = true
93
78
  }
94
79
  if (runtime.type === "node") {
@@ -189,6 +174,7 @@ export const executePlan = async (
189
174
 
190
175
  let runtimeParams = {
191
176
  rootDirectoryUrl,
177
+ devServerOrigin,
192
178
  coverageEnabled,
193
179
  coverageConfig,
194
180
  coverageMethodForBrowsers,
@@ -196,55 +182,16 @@ export const executePlan = async (
196
182
  stopAfterAllSignal,
197
183
  }
198
184
  if (someNeedsServer) {
199
- const server = await startOmegaServer({
200
- signal: multipleExecutionsOperation.signal,
201
- logLevel: "warn",
202
- keepProcessAlive: false,
203
- port,
204
- host,
205
- protocol,
206
- certificate,
207
- privateKey,
208
- services,
209
-
210
- rootDirectoryUrl,
211
- scenarios,
212
- runtimeCompat: runtimes,
213
-
214
- plugins,
215
- htmlSupervisor: true,
216
- nodeEsmResolution,
217
- fileSystemMagicResolution,
218
- transpilation: {
219
- ...transpilation,
220
- getCustomBabelPlugins: ({ clientRuntimeCompat }) => {
221
- if (
222
- coverageEnabled &&
223
- (coverageMethodForBrowsers !== "playwright_api" ||
224
- Object.keys(clientRuntimeCompat)[0] !== "chrome")
225
- ) {
226
- return {
227
- "transform-instrument": [
228
- babelPluginInstrument,
229
- {
230
- rootDirectoryUrl,
231
- coverageConfig,
232
- },
233
- ],
234
- }
235
- }
236
- return {}
237
- },
238
- },
239
- sourcemaps,
240
- writeGeneratedFiles,
241
- })
242
- multipleExecutionsOperation.addEndCallback(async () => {
243
- await server.stop()
244
- })
245
- runtimeParams = {
246
- ...runtimeParams,
247
- server,
185
+ if (!devServerOrigin) {
186
+ throw new TypeError(
187
+ `devServerOrigin is required when running tests on browser(s)`,
188
+ )
189
+ }
190
+ const devServerStarted = await pingServer(devServerOrigin)
191
+ if (!devServerStarted) {
192
+ throw new Error(
193
+ `dev server not started at ${devServerOrigin}. It is required to run tests`,
194
+ )
248
195
  }
249
196
  }
250
197
 
@@ -17,9 +17,10 @@ import { generateCoverageTextLog } from "./coverage/coverage_reporter_text_log.j
17
17
  import { executePlan } from "./execute_plan.js"
18
18
 
19
19
  /**
20
- * Execute a list of files and log how it goes
20
+ * Execute a list of files and log how it goes.
21
21
  * @param {Object} testPlanParameters
22
22
  * @param {string|url} testPlanParameters.rootDirectoryUrl Root directory of the project
23
+ * @param {string|url} [testPlanParameters.serverOrigin=undefined] Jsenv dev server origin; required when executing test on browsers
23
24
  * @param {Object} testPlanParameters.testPlan Object associating patterns leading to files to runtimes where they should be executed
24
25
  * @param {boolean} [testPlanParameters.completedExecutionLogAbbreviation=false] Abbreviate completed execution information to shorten terminal output
25
26
  * @param {boolean} [testPlanParameters.completedExecutionLogMerging=false] Merge completed execution logs to shorten terminal output
@@ -45,6 +46,7 @@ export const executeTestPlan = async ({
45
46
  completedExecutionLogAbbreviation = false,
46
47
  completedExecutionLogMerging = false,
47
48
  rootDirectoryUrl,
49
+ devServerOrigin,
48
50
 
49
51
  testPlan,
50
52
  updateProcessExitCode = true,
@@ -79,18 +81,6 @@ export const executeTestPlan = async ({
79
81
  coverageReportTextLog = true,
80
82
  coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
81
83
  coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
82
-
83
- sourcemaps = "inline",
84
- plugins = [],
85
- nodeEsmResolution,
86
- fileSystemMagicResolution,
87
- writeGeneratedFiles = false,
88
-
89
- protocol,
90
- privateKey,
91
- certificate,
92
- host,
93
- port,
94
84
  }) => {
95
85
  const logger = createLogger({ logLevel })
96
86
  rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
@@ -150,6 +140,7 @@ export const executeTestPlan = async ({
150
140
  completedExecutionLogMerging,
151
141
  completedExecutionLogAbbreviation,
152
142
  rootDirectoryUrl,
143
+ devServerOrigin,
153
144
 
154
145
  maxExecutionsInParallel,
155
146
  defaultMsAllocatedPerExecution,
@@ -165,19 +156,6 @@ export const executeTestPlan = async ({
165
156
  coverageMethodForNodeJs,
166
157
  coverageV8ConflictWarning,
167
158
  coverageTempDirectoryRelativeUrl,
168
-
169
- scenarios: { dev: true, test: true },
170
- sourcemaps,
171
- plugins,
172
- nodeEsmResolution,
173
- fileSystemMagicResolution,
174
- writeGeneratedFiles,
175
-
176
- protocol,
177
- privateKey,
178
- certificate,
179
- host,
180
- port,
181
159
  })
182
160
  if (
183
161
  updateProcessExitCode &&