@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/dist/main.js CHANGED
@@ -12,10 +12,10 @@ import { createMagicSource, composeTwoSourcemaps, getOriginalPosition, sourcemap
12
12
  import { parseHtmlString, stringifyHtmlAst, visitHtmlNodes, getHtmlNodeAttribute, analyzeScriptNode, setHtmlNodeAttributes, parseSrcSet, getHtmlNodePosition, getHtmlNodeAttributePosition, applyPostCss, postCssPluginUrlVisitor, parseJsUrls, getHtmlNodeText, setHtmlNodeText, applyBabelPlugins, injectScriptNodeAsEarlyAsPossible, createHtmlNode, findHtmlNode, removeHtmlNode, removeHtmlNodeText, transpileWithParcel, injectJsImport, minifyWithParcel, analyzeLinkNode } from "@jsenv/ast";
13
13
  import { createRequire } from "node:module";
14
14
  import babelParser from "@babel/parser";
15
+ import net, { createServer, isIP } from "node:net";
15
16
  import http from "node:http";
16
17
  import cluster from "node:cluster";
17
18
  import { performance as performance$1 } from "node:perf_hooks";
18
- import net, { createServer } from "node:net";
19
19
  import { Readable, Stream, Writable } from "node:stream";
20
20
  import { Http2ServerResponse } from "node:http2";
21
21
  import { lookup } from "node:dns";
@@ -13742,7 +13742,6 @@ const jsenvPluginImportMetaScenarios = () => {
13742
13742
  });
13743
13743
  const {
13744
13744
  dev = [],
13745
- test = [],
13746
13745
  build = []
13747
13746
  } = metadata.importMetaScenarios;
13748
13747
  const replacements = [];
@@ -13759,9 +13758,6 @@ const jsenvPluginImportMetaScenarios = () => {
13759
13758
  dev.forEach(path => {
13760
13759
  replace(path, "undefined");
13761
13760
  });
13762
- test.forEach(path => {
13763
- replace(path, context.scenarios.test ? "true" : "undefined");
13764
- });
13765
13761
  build.forEach(path => {
13766
13762
  replace(path, "true");
13767
13763
  });
@@ -13773,12 +13769,6 @@ const jsenvPluginImportMetaScenarios = () => {
13773
13769
  dev.forEach(path => {
13774
13770
  replace(path, "true");
13775
13771
  });
13776
-
13777
- if (context.scenarios.test) {
13778
- test.forEach(path => {
13779
- replace(path, "true");
13780
- });
13781
- }
13782
13772
  }
13783
13773
 
13784
13774
  const magicSource = createMagicSource(urlInfo.content);
@@ -14062,6 +14052,87 @@ export default inlineContent.text`,
14062
14052
  return [asJsonModule, asCssModule, asTextModule];
14063
14053
  };
14064
14054
 
14055
+ const babelPluginInstrument = (api, {
14056
+ rootDirectoryUrl,
14057
+ useInlineSourceMaps = false,
14058
+ coverageConfig = {
14059
+ "./**/*": true
14060
+ }
14061
+ }) => {
14062
+ const {
14063
+ programVisitor
14064
+ } = requireFromJsenv("istanbul-lib-instrument");
14065
+ const {
14066
+ types
14067
+ } = api;
14068
+ const associations = URL_META.resolveAssociations({
14069
+ cover: coverageConfig
14070
+ }, rootDirectoryUrl);
14071
+
14072
+ const shouldInstrument = url => {
14073
+ return URL_META.applyAssociations({
14074
+ url,
14075
+ associations
14076
+ }).cover;
14077
+ };
14078
+
14079
+ return {
14080
+ name: "transform-instrument",
14081
+ visitor: {
14082
+ Program: {
14083
+ enter(path) {
14084
+ const {
14085
+ file
14086
+ } = this;
14087
+ const {
14088
+ opts
14089
+ } = file;
14090
+
14091
+ if (!opts.sourceFileName) {
14092
+ console.warn(`cannot instrument file when "sourceFileName" option is not set`);
14093
+ return;
14094
+ }
14095
+
14096
+ const fileUrl = fileSystemPathToUrl$1(opts.sourceFileName);
14097
+
14098
+ if (!shouldInstrument(fileUrl)) {
14099
+ return;
14100
+ }
14101
+
14102
+ this.__dv__ = null;
14103
+ let inputSourceMap;
14104
+
14105
+ if (useInlineSourceMaps) {
14106
+ // https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
14107
+ inputSourceMap = opts.inputSourceMap || file.inputMap ? file.inputMap.sourcemap : null;
14108
+ } else {
14109
+ inputSourceMap = opts.inputSourceMap;
14110
+ }
14111
+
14112
+ this.__dv__ = programVisitor(types, opts.filenameRelative || opts.filename, {
14113
+ coverageVariable: "__coverage__",
14114
+ inputSourceMap
14115
+ });
14116
+
14117
+ this.__dv__.enter(path);
14118
+ },
14119
+
14120
+ exit(path) {
14121
+ if (!this.__dv__) {
14122
+ return;
14123
+ }
14124
+
14125
+ const object = this.__dv__.exit(path); // object got two properties: fileCoverage and sourceMappingURL
14126
+
14127
+
14128
+ this.file.metadata.coverage = object.fileCoverage;
14129
+ }
14130
+
14131
+ }
14132
+ }
14133
+ };
14134
+ };
14135
+
14065
14136
  const versionFromValue = value => {
14066
14137
  if (typeof value === "number") {
14067
14138
  return numberToVersion(value);
@@ -15243,6 +15314,17 @@ const jsenvPluginBabel = ({
15243
15314
  getImportSpecifier
15244
15315
  });
15245
15316
 
15317
+ if (context.scenarios.dev) {
15318
+ const requestHeaders = context.request.headers;
15319
+
15320
+ if (requestHeaders["x-coverage-instanbul"]) {
15321
+ babelPluginStructure["transform-instrument"] = [babelPluginInstrument, {
15322
+ rootDirectoryUrl: context.rootDirectoryUrl,
15323
+ coverageConfig: JSON.parse(requestHeaders["x-coverage-instanbul"])
15324
+ }];
15325
+ }
15326
+ }
15327
+
15246
15328
  if (getCustomBabelPlugins) {
15247
15329
  Object.assign(babelPluginStructure, getCustomBabelPlugins(context));
15248
15330
  }
@@ -18367,101 +18449,12 @@ const statusIsClientError = status => status >= 400 && status < 500;
18367
18449
 
18368
18450
  const statusIsServerError = status => status >= 500 && status < 600;
18369
18451
 
18370
- const applyDnsResolution = async (hostname, {
18371
- verbatim = false
18372
- } = {}) => {
18373
- const dnsResolution = await new Promise((resolve, reject) => {
18374
- lookup(hostname, {
18375
- verbatim
18376
- }, (error, address, family) => {
18377
- if (error) {
18378
- reject(error);
18379
- } else {
18380
- resolve({
18381
- address,
18382
- family
18383
- });
18384
- }
18385
- });
18386
- });
18387
- return dnsResolution;
18388
- };
18389
-
18390
- const getServerOrigins = async ({
18391
- protocol,
18392
- host,
18393
- port
18394
- }) => {
18395
- const isLocal = LOOPBACK_HOSTNAMES.includes(host);
18396
- const localhostDnsResolution = await applyDnsResolution("localhost");
18397
- const localOrigin = createServerOrigin({
18398
- protocol,
18399
- hostname: localhostDnsResolution.address === "127.0.0.1" ? "localhost" : "127.0.0.1",
18400
- port
18401
- });
18402
-
18403
- if (isLocal) {
18404
- return {
18405
- local: localOrigin
18406
- };
18407
- }
18408
-
18409
- const isAnyIp = WILDCARD_HOSTNAMES.includes(host);
18410
- const networkOrigin = createServerOrigin({
18411
- protocol,
18412
- hostname: isAnyIp ? getExternalIp() : host,
18413
- port
18414
- });
18415
- return {
18416
- local: localOrigin,
18417
- network: networkOrigin
18418
- };
18419
- };
18420
- const LOOPBACK_HOSTNAMES = ["localhost", "127.0.0.1", "::1", "0000:0000:0000:0000:0000:0000:0000:0001"];
18421
- const WILDCARD_HOSTNAMES = [undefined, "0.0.0.0", "::", "0000:0000:0000:0000:0000:0000:0000:0000"];
18422
-
18423
- const createServerOrigin = ({
18424
- protocol,
18425
- hostname,
18426
- port
18427
- }) => {
18428
- const url = new URL("https://127.0.0.1:80");
18429
- url.protocol = protocol;
18430
- url.hostname = hostname;
18431
- url.port = port;
18432
- return url.origin;
18433
- };
18434
-
18435
- const getExternalIp = () => {
18436
- const networkInterfaceMap = networkInterfaces();
18437
- let internalIPV4NetworkAddress;
18438
- Object.keys(networkInterfaceMap).find(key => {
18439
- const networkAddressArray = networkInterfaceMap[key];
18440
- return networkAddressArray.find(networkAddress => {
18441
- if (networkAddress.internal) return false;
18442
- if (!isIpV4(networkAddress)) return false;
18443
- internalIPV4NetworkAddress = networkAddress;
18444
- return true;
18445
- });
18446
- });
18447
- return internalIPV4NetworkAddress ? internalIPV4NetworkAddress.address : null;
18448
- };
18449
-
18450
- const isIpV4 = networkAddress => {
18451
- // node 18+
18452
- if (typeof networkAddress.family === "number") {
18453
- return networkAddress.family === 4;
18454
- }
18455
-
18456
- return networkAddress.family === "IPv4";
18457
- };
18458
-
18459
18452
  const listen = async ({
18460
18453
  signal = new AbortController().signal,
18461
18454
  server,
18462
18455
  port,
18463
18456
  portHint,
18464
- host
18457
+ hostname
18465
18458
  }) => {
18466
18459
  const listeningOperation = Abort.startOperation();
18467
18460
 
@@ -18472,7 +18465,7 @@ const listen = async ({
18472
18465
  listeningOperation.throwIfAborted();
18473
18466
  port = await findFreePort(portHint, {
18474
18467
  signal: listeningOperation.signal,
18475
- host
18468
+ hostname
18476
18469
  });
18477
18470
  }
18478
18471
 
@@ -18480,7 +18473,7 @@ const listen = async ({
18480
18473
  port = await startListening({
18481
18474
  server,
18482
18475
  port,
18483
- host
18476
+ hostname
18484
18477
  });
18485
18478
  listeningOperation.addAbortCallback(() => stopListening(server));
18486
18479
  listeningOperation.throwIfAborted();
@@ -18492,7 +18485,7 @@ const listen = async ({
18492
18485
 
18493
18486
  const findFreePort = async (initialPort = 1, {
18494
18487
  signal = new AbortController().signal,
18495
- host = "127.0.0.1",
18488
+ hostname = "127.0.0.1",
18496
18489
  min = 1,
18497
18490
  max = 65534,
18498
18491
  next = port => port + 1
@@ -18514,27 +18507,27 @@ const findFreePort = async (initialPort = 1, {
18514
18507
  const nextPort = next(port);
18515
18508
 
18516
18509
  if (nextPort > max) {
18517
- throw new Error(`${host} has no available port between ${min} and ${max}`);
18510
+ throw new Error(`${hostname} has no available port between ${min} and ${max}`);
18518
18511
  }
18519
18512
 
18520
- return testUntil(nextPort, host);
18513
+ return testUntil(nextPort, hostname);
18521
18514
  };
18522
18515
 
18523
- const freePort = await testUntil(initialPort, host);
18516
+ const freePort = await testUntil(initialPort, hostname);
18524
18517
  return freePort;
18525
18518
  } finally {
18526
18519
  await findFreePortOperation.end();
18527
18520
  }
18528
18521
  };
18529
18522
 
18530
- const portIsFree = async (port, host) => {
18523
+ const portIsFree = async (port, hostname) => {
18531
18524
  const server = createServer();
18532
18525
 
18533
18526
  try {
18534
18527
  await startListening({
18535
18528
  server,
18536
18529
  port,
18537
- host
18530
+ hostname
18538
18531
  });
18539
18532
  } catch (error) {
18540
18533
  if (error && error.code === "EADDRINUSE") {
@@ -18555,7 +18548,7 @@ const portIsFree = async (port, host) => {
18555
18548
  const startListening = ({
18556
18549
  server,
18557
18550
  port,
18558
- host
18551
+ hostname
18559
18552
  }) => {
18560
18553
  return new Promise((resolve, reject) => {
18561
18554
  server.on("error", reject);
@@ -18564,7 +18557,7 @@ const startListening = ({
18564
18557
  // https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback
18565
18558
  resolve(server.address().port);
18566
18559
  });
18567
- server.listen(port, host);
18560
+ server.listen(port, hostname);
18568
18561
  });
18569
18562
  };
18570
18563
 
@@ -18803,6 +18796,134 @@ const STOP_REASON_PROCESS_BEFORE_EXIT = createReason("process before exit");
18803
18796
  const STOP_REASON_PROCESS_EXIT = createReason("process exit");
18804
18797
  const STOP_REASON_NOT_SPECIFIED = createReason("not specified");
18805
18798
 
18799
+ const createIpGetters = () => {
18800
+ const networkAddresses = [];
18801
+ const networkInterfaceMap = networkInterfaces();
18802
+
18803
+ for (const key of Object.keys(networkInterfaceMap)) {
18804
+ for (const networkAddress of networkInterfaceMap[key]) {
18805
+ networkAddresses.push(networkAddress);
18806
+ }
18807
+ }
18808
+
18809
+ return {
18810
+ getFirstInternalIp: ({
18811
+ preferIpv6
18812
+ }) => {
18813
+ const isPref = preferIpv6 ? isIpV6 : isIpV4;
18814
+ let firstInternalIp;
18815
+
18816
+ for (const networkAddress of networkAddresses) {
18817
+ if (networkAddress.internal) {
18818
+ firstInternalIp = networkAddress.address;
18819
+
18820
+ if (isPref(networkAddress)) {
18821
+ break;
18822
+ }
18823
+ }
18824
+ }
18825
+
18826
+ return firstInternalIp;
18827
+ },
18828
+ getFirstExternalIp: ({
18829
+ preferIpv6
18830
+ }) => {
18831
+ const isPref = preferIpv6 ? isIpV6 : isIpV4;
18832
+ let firstExternalIp;
18833
+
18834
+ for (const networkAddress of networkAddresses) {
18835
+ if (!networkAddress.internal) {
18836
+ firstExternalIp = networkAddress.address;
18837
+
18838
+ if (isPref(networkAddress)) {
18839
+ break;
18840
+ }
18841
+ }
18842
+ }
18843
+
18844
+ return firstExternalIp;
18845
+ }
18846
+ };
18847
+ };
18848
+
18849
+ const isIpV4 = networkAddress => {
18850
+ // node 18.5
18851
+ if (typeof networkAddress.family === "number") {
18852
+ return networkAddress.family === 4;
18853
+ }
18854
+
18855
+ return networkAddress.family === "IPv4";
18856
+ };
18857
+
18858
+ const isIpV6 = networkAddress => !isIpV4(networkAddress);
18859
+
18860
+ const parseHostname = hostname => {
18861
+ if (hostname === "0.0.0.0") {
18862
+ return {
18863
+ type: "ip",
18864
+ label: "unspecified",
18865
+ version: 4
18866
+ };
18867
+ }
18868
+
18869
+ if (hostname === "::" || hostname === "0000:0000:0000:0000:0000:0000:0000:0000") {
18870
+ return {
18871
+ type: "ip",
18872
+ label: "unspecified",
18873
+ version: 6
18874
+ };
18875
+ }
18876
+
18877
+ if (hostname === "127.0.0.1") {
18878
+ return {
18879
+ type: "ip",
18880
+ label: "loopback",
18881
+ version: 4
18882
+ };
18883
+ }
18884
+
18885
+ if (hostname === "::1" || hostname === "0000:0000:0000:0000:0000:0000:0000:0001") {
18886
+ return {
18887
+ type: "ip",
18888
+ label: "loopback",
18889
+ version: 6
18890
+ };
18891
+ }
18892
+
18893
+ const ipVersion = isIP(hostname);
18894
+
18895
+ if (ipVersion === 0) {
18896
+ return {
18897
+ type: "hostname"
18898
+ };
18899
+ }
18900
+
18901
+ return {
18902
+ type: "ip",
18903
+ version: ipVersion
18904
+ };
18905
+ };
18906
+
18907
+ const applyDnsResolution = async (hostname, {
18908
+ verbatim = false
18909
+ } = {}) => {
18910
+ const dnsResolution = await new Promise((resolve, reject) => {
18911
+ lookup(hostname, {
18912
+ verbatim
18913
+ }, (error, address, family) => {
18914
+ if (error) {
18915
+ reject(error);
18916
+ } else {
18917
+ resolve({
18918
+ address,
18919
+ family
18920
+ });
18921
+ }
18922
+ });
18923
+ });
18924
+ return dnsResolution;
18925
+ };
18926
+
18806
18927
  const startServer = async ({
18807
18928
  signal = new AbortController().signal,
18808
18929
  logLevel,
@@ -18814,7 +18935,8 @@ const startServer = async ({
18814
18935
  redirectHttpToHttps,
18815
18936
  allowHttpRequestOnHttps = false,
18816
18937
  acceptAnyIp = false,
18817
- host = acceptAnyIp ? undefined : "localhost",
18938
+ preferIpv6,
18939
+ hostname = "localhost",
18818
18940
  port = 0,
18819
18941
  // assign a random available port
18820
18942
  portHint,
@@ -18844,6 +18966,10 @@ const startServer = async ({
18844
18966
  }));
18845
18967
  }
18846
18968
  } = {}) => {
18969
+ const logger = createLogger({
18970
+ logLevel
18971
+ });
18972
+
18847
18973
  if (protocol !== "http" && protocol !== "https") {
18848
18974
  throw new Error(`protocol must be http or https, got ${protocol}`);
18849
18975
  }
@@ -18862,10 +18988,6 @@ const startServer = async ({
18862
18988
  throw new Error(`http2 needs "https" but protocol is "${protocol}"`);
18863
18989
  }
18864
18990
 
18865
- const logger = createLogger({
18866
- logLevel
18867
- });
18868
-
18869
18991
  if (redirectHttpToHttps === undefined && protocol === "https" && !allowHttpRequestOnHttps) {
18870
18992
  redirectHttpToHttps = true;
18871
18993
  }
@@ -18898,6 +19020,10 @@ const startServer = async ({
18898
19020
  let nodeServer;
18899
19021
  const startServerOperation = Abort.startOperation();
18900
19022
  const stopCallbackList = createCallbackListNotifiedOnce();
19023
+ const serverOrigins = {
19024
+ local: "" // favors hostname when possible
19025
+
19026
+ };
18901
19027
 
18902
19028
  try {
18903
19029
  startServerOperation.addAbortSignal(signal);
@@ -18925,12 +19051,85 @@ const startServer = async ({
18925
19051
  nodeServer.unref();
18926
19052
  }
18927
19053
 
19054
+ const createOrigin = hostname => {
19055
+ if (isIP(hostname) === 6) {
19056
+ return `${protocol}://[${hostname}]`;
19057
+ }
19058
+
19059
+ return `${protocol}://${hostname}`;
19060
+ };
19061
+
19062
+ const ipGetters = createIpGetters();
19063
+ let hostnameToListen;
19064
+
19065
+ if (acceptAnyIp) {
19066
+ const firstInternalIp = ipGetters.getFirstInternalIp({
19067
+ preferIpv6
19068
+ });
19069
+ serverOrigins.local = createOrigin(firstInternalIp);
19070
+ serverOrigins.localip = createOrigin(firstInternalIp);
19071
+ const firstExternalIp = ipGetters.getFirstExternalIp({
19072
+ preferIpv6
19073
+ });
19074
+ serverOrigins.externalip = createOrigin(firstExternalIp);
19075
+ hostnameToListen = preferIpv6 ? "::" : "0.0.0.0";
19076
+ } else {
19077
+ hostnameToListen = hostname;
19078
+ }
19079
+
19080
+ const hostnameInfo = parseHostname(hostname);
19081
+
19082
+ if (hostnameInfo.type === "ip") {
19083
+ if (acceptAnyIp) {
19084
+ throw new Error(`hostname cannot be an ip when acceptAnyIp is enabled, got ${hostname}`);
19085
+ }
19086
+
19087
+ preferIpv6 = hostnameInfo.version === 6;
19088
+ const firstInternalIp = ipGetters.getFirstInternalIp({
19089
+ preferIpv6
19090
+ });
19091
+ serverOrigins.local = createOrigin(firstInternalIp);
19092
+ serverOrigins.localip = createOrigin(firstInternalIp);
19093
+
19094
+ if (hostnameInfo.label === "unspecified") {
19095
+ const firstExternalIp = ipGetters.getFirstExternalIp({
19096
+ preferIpv6
19097
+ });
19098
+ serverOrigins.externalip = createOrigin(firstExternalIp);
19099
+ } else if (hostnameInfo.label === "loopback") {} else {
19100
+ serverOrigins.local = createOrigin(hostname);
19101
+ }
19102
+ } else {
19103
+ const hostnameDnsResolution = await applyDnsResolution(hostname, {
19104
+ verbatim: true
19105
+ });
19106
+
19107
+ if (hostnameDnsResolution) {
19108
+ const hostnameIp = hostnameDnsResolution.address;
19109
+ serverOrigins.localip = createOrigin(hostnameIp);
19110
+ serverOrigins.local = createOrigin(hostname);
19111
+ } else {
19112
+ const firstInternalIp = ipGetters.getFirstInternalIp({
19113
+ preferIpv6
19114
+ }); // fallback to internal ip because there is no ip
19115
+ // associated to this hostname on operating system (in hosts file)
19116
+
19117
+ hostname = firstInternalIp;
19118
+ hostnameToListen = firstInternalIp;
19119
+ serverOrigins.local = createOrigin(firstInternalIp);
19120
+ }
19121
+ }
19122
+
18928
19123
  port = await listen({
18929
19124
  signal: startServerOperation.signal,
18930
19125
  server: nodeServer,
18931
19126
  port,
18932
19127
  portHint,
18933
- host
19128
+ hostname: hostnameToListen
19129
+ }); // normalize origins (remove :80 when port is 80 for instance)
19130
+
19131
+ Object.keys(serverOrigins).forEach(key => {
19132
+ serverOrigins[key] = new URL(`${serverOrigins[key]}:${port}`).origin;
18934
19133
  });
18935
19134
  serviceController.callHooks("serverListening", {
18936
19135
  port
@@ -18941,12 +19140,23 @@ const startServer = async ({
18941
19140
  startServerOperation.throwIfAborted();
18942
19141
  } finally {
18943
19142
  await startServerOperation.end();
18944
- } // now the server is started (listening) it cannot be aborted anymore
19143
+ } // the main server origin
19144
+ // - when protocol is http
19145
+ // node-fetch do not apply local dns resolution to map localhost back to 127.0.0.1
19146
+ // despites localhost being mapped so we prefer to use the internal ip
19147
+ // (127.0.0.1)
19148
+ // - when protocol is https
19149
+ // using the hostname becomes important because the certificate is generated
19150
+ // for hostnames, not for ips
19151
+ // so we prefer https://locahost or https://local_hostname
19152
+ // over the ip
19153
+
19154
+
19155
+ const serverOrigin = serverOrigins.local; // now the server is started (listening) it cannot be aborted anymore
18945
19156
  // (otherwise an AbortError is thrown to the code calling "startServer")
18946
19157
  // we can proceed to create a stop function to stop it gacefully
18947
19158
  // and add a request handler
18948
19159
 
18949
-
18950
19160
  stopCallbackList.add(({
18951
19161
  reason
18952
19162
  }) => {
@@ -18984,12 +19194,6 @@ const startServer = async ({
18984
19194
  };
18985
19195
 
18986
19196
  status = "opened";
18987
- const serverOrigins = await getServerOrigins({
18988
- protocol,
18989
- host,
18990
- port
18991
- });
18992
- const serverOrigin = serverOrigins.local;
18993
19197
  const removeConnectionErrorListener = listenServerConnectionError(nodeServer, onError);
18994
19198
  stopCallbackList.add(removeConnectionErrorListener);
18995
19199
  const connectionsTracker = trackServerPendingConnections(nodeServer, {
@@ -19609,7 +19813,7 @@ const startServer = async ({
19609
19813
  let websocketServer = new WebSocketServer({
19610
19814
  noServer: true
19611
19815
  });
19612
- const websocketOrigin = protocol === "https" ? `wss://${host}:${port}` : `ws://${host}:${port}`;
19816
+ const websocketOrigin = protocol === "https" ? `wss://${hostname}:${port}` : `ws://${hostname}:${port}`;
19613
19817
  server.websocketOrigin = websocketOrigin;
19614
19818
 
19615
19819
  const upgradeCallback = (nodeRequest, socket, head) => {
@@ -19650,6 +19854,7 @@ const startServer = async ({
19650
19854
  Object.assign(server, {
19651
19855
  getStatus: () => status,
19652
19856
  port,
19857
+ hostname,
19653
19858
  origin: serverOrigin,
19654
19859
  origins: serverOrigins,
19655
19860
  nodeServer,
@@ -25261,7 +25466,7 @@ const startOmegaServer = async ({
25261
25466
  privateKey,
25262
25467
  certificate,
25263
25468
  acceptAnyIp,
25264
- host,
25469
+ hostname,
25265
25470
  port = 0,
25266
25471
  keepProcessAlive = false,
25267
25472
  onStop = () => {},
@@ -25302,7 +25507,7 @@ const startOmegaServer = async ({
25302
25507
  certificate,
25303
25508
  privateKey,
25304
25509
  acceptAnyIp,
25305
- host,
25510
+ hostname,
25306
25511
  port,
25307
25512
  requestWaitingMs: 60_1000,
25308
25513
  services: [jsenvServiceCORS({
@@ -25408,7 +25613,7 @@ const startDevServer = async ({
25408
25613
  http2 = false,
25409
25614
  certificate,
25410
25615
  privateKey,
25411
- host,
25616
+ hostname,
25412
25617
  port = 3456,
25413
25618
  acceptAnyIp,
25414
25619
  keepProcessAlive = true,
@@ -25548,7 +25753,7 @@ const startDevServer = async ({
25548
25753
  http2,
25549
25754
  certificate,
25550
25755
  privateKey,
25551
- host,
25756
+ hostname,
25552
25757
  port,
25553
25758
  services,
25554
25759
  rootDirectoryUrl,
@@ -25651,87 +25856,6 @@ const generateCoverageTextLog = (coverage, {
25651
25856
  report.execute(context);
25652
25857
  };
25653
25858
 
25654
- const babelPluginInstrument = (api, {
25655
- rootDirectoryUrl,
25656
- useInlineSourceMaps = false,
25657
- coverageConfig = {
25658
- "./**/*": true
25659
- }
25660
- }) => {
25661
- const {
25662
- programVisitor
25663
- } = requireFromJsenv("istanbul-lib-instrument");
25664
- const {
25665
- types
25666
- } = api;
25667
- const associations = URL_META.resolveAssociations({
25668
- cover: coverageConfig
25669
- }, rootDirectoryUrl);
25670
-
25671
- const shouldInstrument = url => {
25672
- return URL_META.applyAssociations({
25673
- url,
25674
- associations
25675
- }).cover;
25676
- };
25677
-
25678
- return {
25679
- name: "transform-instrument",
25680
- visitor: {
25681
- Program: {
25682
- enter(path) {
25683
- const {
25684
- file
25685
- } = this;
25686
- const {
25687
- opts
25688
- } = file;
25689
-
25690
- if (!opts.sourceFileName) {
25691
- console.warn(`cannot instrument file when "sourceFileName" option is not set`);
25692
- return;
25693
- }
25694
-
25695
- const fileUrl = fileSystemPathToUrl$1(opts.sourceFileName);
25696
-
25697
- if (!shouldInstrument(fileUrl)) {
25698
- return;
25699
- }
25700
-
25701
- this.__dv__ = null;
25702
- let inputSourceMap;
25703
-
25704
- if (useInlineSourceMaps) {
25705
- // https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
25706
- inputSourceMap = opts.inputSourceMap || file.inputMap ? file.inputMap.sourcemap : null;
25707
- } else {
25708
- inputSourceMap = opts.inputSourceMap;
25709
- }
25710
-
25711
- this.__dv__ = programVisitor(types, opts.filenameRelative || opts.filename, {
25712
- coverageVariable: "__coverage__",
25713
- inputSourceMap
25714
- });
25715
-
25716
- this.__dv__.enter(path);
25717
- },
25718
-
25719
- exit(path) {
25720
- if (!this.__dv__) {
25721
- return;
25722
- }
25723
-
25724
- const object = this.__dv__.exit(path); // object got two properties: fileCoverage and sourceMappingURL
25725
-
25726
-
25727
- this.file.metadata.coverage = object.fileCoverage;
25728
- }
25729
-
25730
- }
25731
- }
25732
- };
25733
- };
25734
-
25735
25859
  const readNodeV8CoverageDirectory = async ({
25736
25860
  logger,
25737
25861
  signal,
@@ -26424,6 +26548,41 @@ const run = async ({
26424
26548
  }
26425
26549
  };
26426
26550
 
26551
+ const pingServer = async url => {
26552
+ const server = createServer();
26553
+ const {
26554
+ hostname,
26555
+ port
26556
+ } = new URL(url);
26557
+
26558
+ try {
26559
+ await new Promise((resolve, reject) => {
26560
+ server.on("error", reject);
26561
+ server.on("listening", () => {
26562
+ resolve();
26563
+ });
26564
+ server.listen(port, hostname);
26565
+ });
26566
+ } catch (error) {
26567
+ if (error && error.code === "EADDRINUSE") {
26568
+ return true;
26569
+ }
26570
+
26571
+ if (error && error.code === "EACCES") {
26572
+ return true;
26573
+ }
26574
+
26575
+ throw error;
26576
+ }
26577
+
26578
+ await new Promise((resolve, reject) => {
26579
+ server.on("error", reject);
26580
+ server.on("close", resolve);
26581
+ server.close();
26582
+ });
26583
+ return false;
26584
+ };
26585
+
26427
26586
  const ensureGlobalGc = () => {
26428
26587
  if (!global.gc) {
26429
26588
  v8.setFlagsFromString("--expose_gc");
@@ -26859,8 +27018,8 @@ const executePlan = async (plan, {
26859
27018
  completedExecutionLogMerging,
26860
27019
  completedExecutionLogAbbreviation,
26861
27020
  rootDirectoryUrl,
27021
+ devServerOrigin,
26862
27022
  keepRunning,
26863
- services,
26864
27023
  defaultMsAllocatedPerExecution,
26865
27024
  maxExecutionsInParallel,
26866
27025
  failFast,
@@ -26873,18 +27032,6 @@ const executePlan = async (plan, {
26873
27032
  coverageMethodForNodeJs,
26874
27033
  coverageV8ConflictWarning,
26875
27034
  coverageTempDirectoryRelativeUrl,
26876
- scenarios,
26877
- sourcemaps,
26878
- plugins,
26879
- nodeEsmResolution,
26880
- fileSystemMagicResolution,
26881
- transpilation,
26882
- writeGeneratedFiles,
26883
- protocol,
26884
- privateKey,
26885
- certificate,
26886
- host,
26887
- port,
26888
27035
  beforeExecutionCallback = () => {},
26889
27036
  afterExecutionCallback = () => {}
26890
27037
  } = {}) => {
@@ -26908,7 +27055,7 @@ const executePlan = async (plan, {
26908
27055
  if (runtime) {
26909
27056
  runtimes[runtime.name] = runtime.version;
26910
27057
 
26911
- if (runtime.needsServer) {
27058
+ if (runtime.type === "browser") {
26912
27059
  someNeedsServer = true;
26913
27060
  }
26914
27061
 
@@ -26999,6 +27146,7 @@ const executePlan = async (plan, {
26999
27146
 
27000
27147
  let runtimeParams = {
27001
27148
  rootDirectoryUrl,
27149
+ devServerOrigin,
27002
27150
  coverageEnabled,
27003
27151
  coverageConfig,
27004
27152
  coverageMethodForBrowsers,
@@ -27007,48 +27155,15 @@ const executePlan = async (plan, {
27007
27155
  };
27008
27156
 
27009
27157
  if (someNeedsServer) {
27010
- const server = await startOmegaServer({
27011
- signal: multipleExecutionsOperation.signal,
27012
- logLevel: "warn",
27013
- keepProcessAlive: false,
27014
- port,
27015
- host,
27016
- protocol,
27017
- certificate,
27018
- privateKey,
27019
- services,
27020
- rootDirectoryUrl,
27021
- scenarios,
27022
- runtimeCompat: runtimes,
27023
- plugins,
27024
- htmlSupervisor: true,
27025
- nodeEsmResolution,
27026
- fileSystemMagicResolution,
27027
- transpilation: { ...transpilation,
27028
- getCustomBabelPlugins: ({
27029
- clientRuntimeCompat
27030
- }) => {
27031
- if (coverageEnabled && (coverageMethodForBrowsers !== "playwright_api" || Object.keys(clientRuntimeCompat)[0] !== "chrome")) {
27032
- return {
27033
- "transform-instrument": [babelPluginInstrument, {
27034
- rootDirectoryUrl,
27035
- coverageConfig
27036
- }]
27037
- };
27038
- }
27158
+ if (!devServerOrigin) {
27159
+ throw new TypeError(`devServerOrigin is required when running tests on browser(s)`);
27160
+ }
27039
27161
 
27040
- return {};
27041
- }
27042
- },
27043
- sourcemaps,
27044
- writeGeneratedFiles
27045
- });
27046
- multipleExecutionsOperation.addEndCallback(async () => {
27047
- await server.stop();
27048
- });
27049
- runtimeParams = { ...runtimeParams,
27050
- server
27051
- };
27162
+ const devServerStarted = await pingServer(devServerOrigin);
27163
+
27164
+ if (!devServerStarted) {
27165
+ throw new Error(`dev server not started at ${devServerOrigin}. It is required to run tests`);
27166
+ }
27052
27167
  }
27053
27168
 
27054
27169
  logger.debug(`Generate executions`);
@@ -27391,9 +27506,10 @@ const executeInParallel = async ({
27391
27506
  };
27392
27507
 
27393
27508
  /**
27394
- * Execute a list of files and log how it goes
27509
+ * Execute a list of files and log how it goes.
27395
27510
  * @param {Object} testPlanParameters
27396
27511
  * @param {string|url} testPlanParameters.rootDirectoryUrl Root directory of the project
27512
+ * @param {string|url} [testPlanParameters.serverOrigin=undefined] Jsenv dev server origin; required when executing test on browsers
27397
27513
  * @param {Object} testPlanParameters.testPlan Object associating patterns leading to files to runtimes where they should be executed
27398
27514
  * @param {boolean} [testPlanParameters.completedExecutionLogAbbreviation=false] Abbreviate completed execution information to shorten terminal output
27399
27515
  * @param {boolean} [testPlanParameters.completedExecutionLogMerging=false] Merge completed execution logs to shorten terminal output
@@ -27420,6 +27536,7 @@ const executeTestPlan = async ({
27420
27536
  completedExecutionLogAbbreviation = false,
27421
27537
  completedExecutionLogMerging = false,
27422
27538
  rootDirectoryUrl,
27539
+ devServerOrigin,
27423
27540
  testPlan,
27424
27541
  updateProcessExitCode = true,
27425
27542
  maxExecutionsInParallel = 1,
@@ -27450,17 +27567,7 @@ const executeTestPlan = async ({
27450
27567
  coverageReportSkipFull = false,
27451
27568
  coverageReportTextLog = true,
27452
27569
  coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
27453
- coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
27454
- sourcemaps = "inline",
27455
- plugins = [],
27456
- nodeEsmResolution,
27457
- fileSystemMagicResolution,
27458
- writeGeneratedFiles = false,
27459
- protocol,
27460
- privateKey,
27461
- certificate,
27462
- host,
27463
- port
27570
+ coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null
27464
27571
  }) => {
27465
27572
  const logger = createLogger({
27466
27573
  logLevel
@@ -27520,6 +27627,7 @@ const executeTestPlan = async ({
27520
27627
  completedExecutionLogMerging,
27521
27628
  completedExecutionLogAbbreviation,
27522
27629
  rootDirectoryUrl,
27630
+ devServerOrigin,
27523
27631
  maxExecutionsInParallel,
27524
27632
  defaultMsAllocatedPerExecution,
27525
27633
  failFast,
@@ -27532,21 +27640,7 @@ const executeTestPlan = async ({
27532
27640
  coverageMethodForBrowsers,
27533
27641
  coverageMethodForNodeJs,
27534
27642
  coverageV8ConflictWarning,
27535
- coverageTempDirectoryRelativeUrl,
27536
- scenarios: {
27537
- dev: true,
27538
- test: true
27539
- },
27540
- sourcemaps,
27541
- plugins,
27542
- nodeEsmResolution,
27543
- fileSystemMagicResolution,
27544
- writeGeneratedFiles,
27545
- protocol,
27546
- privateKey,
27547
- certificate,
27548
- host,
27549
- port
27643
+ coverageTempDirectoryRelativeUrl
27550
27644
  });
27551
27645
 
27552
27646
  if (updateProcessExitCode && result.planSummary.counters.total !== result.planSummary.counters.completed) {
@@ -27664,8 +27758,7 @@ const createRuntimeFromPlaywright = ({
27664
27758
  const runtime = {
27665
27759
  type: "browser",
27666
27760
  name: browserName,
27667
- version: browserVersion,
27668
- needsServer: true
27761
+ version: browserVersion
27669
27762
  };
27670
27763
  let browserAndContextPromise;
27671
27764
 
@@ -27674,7 +27767,7 @@ const createRuntimeFromPlaywright = ({
27674
27767
  logger,
27675
27768
  rootDirectoryUrl,
27676
27769
  fileRelativeUrl,
27677
- server,
27770
+ devServerOrigin,
27678
27771
  // measurePerformance,
27679
27772
  collectPerformance,
27680
27773
  coverageEnabled = false,
@@ -27749,7 +27842,13 @@ const createRuntimeFromPlaywright = ({
27749
27842
  await disconnected;
27750
27843
  };
27751
27844
 
27752
- const page = await browserContext.newPage();
27845
+ const coverageInHeaders = coverageEnabled && (!coveragePlaywrightAPIAvailable || coverageMethodForBrowsers !== "playwright_api");
27846
+ const page = await browserContext.newPage({
27847
+ extraHTTPHeaders: { ...(coverageInHeaders ? {
27848
+ "x-coverage-istanbul": JSON.stringify(coverageConfig)
27849
+ } : {})
27850
+ }
27851
+ });
27753
27852
 
27754
27853
  const closePage = async () => {
27755
27854
  try {
@@ -27777,7 +27876,7 @@ const createRuntimeFromPlaywright = ({
27777
27876
  const v8CoveragesWithFsUrls = v8CoveragesWithWebUrls.map(v8CoveragesWithWebUrl => {
27778
27877
  const fsUrl = moveUrl({
27779
27878
  url: v8CoveragesWithWebUrl.url,
27780
- from: `${server.origin}/`,
27879
+ from: `${devServerOrigin}/`,
27781
27880
  to: rootDirectoryUrl,
27782
27881
  preferAbsolute: true
27783
27882
  });
@@ -27848,7 +27947,7 @@ const createRuntimeFromPlaywright = ({
27848
27947
  });
27849
27948
  }
27850
27949
 
27851
- const fileClientUrl = new URL(fileRelativeUrl, `${server.origin}/`).href; // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-console
27950
+ const fileClientUrl = new URL(fileRelativeUrl, `${devServerOrigin}/`).href; // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-console
27852
27951
 
27853
27952
  const removeConsoleListener = registerEvent({
27854
27953
  object: page,
@@ -27962,7 +28061,7 @@ const createRuntimeFromPlaywright = ({
27962
28061
  } = returnValue;
27963
28062
  const error = evalException(exceptionSource, {
27964
28063
  rootDirectoryUrl,
27965
- server,
28064
+ devServerOrigin,
27966
28065
  transformErrorHook
27967
28066
  });
27968
28067
  cb({
@@ -28198,7 +28297,7 @@ const registerEvent = ({
28198
28297
 
28199
28298
  const evalException = (exceptionSource, {
28200
28299
  rootDirectoryUrl,
28201
- server,
28300
+ devServerOrigin,
28202
28301
  transformErrorHook
28203
28302
  }) => {
28204
28303
  const script = new Script(exceptionSource, {
@@ -28207,7 +28306,7 @@ const evalException = (exceptionSource, {
28207
28306
  const error = script.runInThisContext();
28208
28307
 
28209
28308
  if (error && error instanceof Error) {
28210
- const remoteRootRegexp = new RegExp(escapeRegexpSpecialChars(`${server.origin}/`), "g");
28309
+ const remoteRootRegexp = new RegExp(escapeRegexpSpecialChars(`${devServerOrigin}/`), "g");
28211
28310
  error.stack = error.stack.replace(remoteRootRegexp, rootDirectoryUrl);
28212
28311
  error.message = error.message.replace(remoteRootRegexp, rootDirectoryUrl);
28213
28312
  }
@@ -29205,7 +29304,7 @@ const startBuildServer = async ({
29205
29304
  certificate,
29206
29305
  privateKey,
29207
29306
  acceptAnyIp,
29208
- host,
29307
+ hostname,
29209
29308
  port = 9779,
29210
29309
  services = [],
29211
29310
  keepProcessAlive = true,
@@ -29342,7 +29441,7 @@ const startBuildServer = async ({
29342
29441
  certificate,
29343
29442
  privateKey,
29344
29443
  acceptAnyIp,
29345
- host,
29444
+ hostname,
29346
29445
  port,
29347
29446
  serverTiming: true,
29348
29447
  requestWaitingMs: 60_000,
@@ -29413,32 +29512,17 @@ const execute = async ({
29413
29512
  handleSIGINT = true,
29414
29513
  logLevel,
29415
29514
  rootDirectoryUrl,
29515
+ devServerOrigin,
29416
29516
  fileRelativeUrl,
29417
29517
  allocatedMs,
29418
29518
  mirrorConsole = true,
29419
29519
  keepRunning = false,
29420
- services,
29421
29520
  collectConsole,
29422
29521
  collectCoverage,
29423
29522
  coverageTempDirectoryUrl,
29424
29523
  collectPerformance = false,
29425
29524
  runtime,
29426
29525
  runtimeParams,
29427
- scenarios = {
29428
- dev: true
29429
- },
29430
- plugins = [],
29431
- nodeEsmResolution,
29432
- fileSystemMagicResolution,
29433
- transpilation,
29434
- htmlSupervisor = true,
29435
- sourcemaps = "inline",
29436
- writeGeneratedFiles = false,
29437
- port,
29438
- protocol,
29439
- http2,
29440
- certificate,
29441
- privateKey,
29442
29526
  ignoreError = false
29443
29527
  }) => {
29444
29528
  const logger = createLogger({
@@ -29460,45 +29544,21 @@ const execute = async ({
29460
29544
 
29461
29545
  runtimeParams = {
29462
29546
  rootDirectoryUrl,
29547
+ devServerOrigin,
29463
29548
  fileRelativeUrl,
29464
29549
  ...runtimeParams
29465
29550
  };
29466
29551
 
29467
- if (runtime.needsServer) {
29468
- const server = await startOmegaServer({
29469
- signal: executeOperation.signal,
29470
- logLevel: "warn",
29471
- keepProcessAlive: false,
29472
- services,
29473
- port,
29474
- protocol,
29475
- http2,
29476
- certificate,
29477
- privateKey,
29478
- rootDirectoryUrl,
29479
- scenarios,
29480
- runtimeCompat: {
29481
- [runtime.name]: runtime.version
29482
- },
29483
- plugins,
29484
- htmlSupervisor,
29485
- nodeEsmResolution,
29486
- fileSystemMagicResolution,
29487
- transpilation,
29488
- sourcemaps,
29489
- writeGeneratedFiles
29490
- });
29491
- executeOperation.addEndCallback(async () => {
29492
- await server.stop("execution done");
29493
- });
29494
- runtimeParams = { ...runtimeParams,
29495
- server
29496
- };
29552
+ if (runtime.type === "browser") {
29553
+ if (!devServerOrigin) {
29554
+ throw new TypeError(`devServerOrigin is required when running tests on browser(s)`);
29555
+ }
29497
29556
 
29498
- resultTransformer = result => {
29499
- result.server = server;
29500
- return result;
29501
- };
29557
+ const devServerStarted = await pingServer(devServerOrigin);
29558
+
29559
+ if (!devServerStarted) {
29560
+ throw new Error(`dev server not started at ${devServerOrigin}. It is required to run tests`);
29561
+ }
29502
29562
  }
29503
29563
 
29504
29564
  let result = await run({
@@ -29620,4 +29680,4 @@ const jsenvPluginInjectGlobals = urlAssociations => {
29620
29680
  };
29621
29681
  };
29622
29682
 
29623
- export { build, chromium, chromiumIsolatedTab, execute, executeTestPlan, firefox, firefoxIsolatedTab, jsenvPluginInjectGlobals, nodeChildProcess, nodeWorkerThread, startBuildServer, startDevServer, webkit, webkitIsolatedTab };
29683
+ export { build, chromium, chromiumIsolatedTab, execute, executeTestPlan, firefox, firefoxIsolatedTab, jsenvPluginInjectGlobals, nodeChildProcess, nodeWorkerThread, pingServer, startBuildServer, startDevServer, webkit, webkitIsolatedTab };