@jsenv/core 33.0.1 → 34.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.
Files changed (29) hide show
  1. package/dist/js/autoreload.js +1 -4
  2. package/dist/js/supervisor.js +498 -290
  3. package/dist/jsenv.js +874 -347
  4. package/package.json +2 -3
  5. package/src/basic_fetch.js +23 -13
  6. package/src/build/start_build_server.js +3 -2
  7. package/src/dev/file_service.js +1 -1
  8. package/src/dev/start_dev_server.js +9 -6
  9. package/src/execute/execute.js +7 -18
  10. package/src/execute/runtimes/browsers/from_playwright.js +168 -32
  11. package/src/execute/runtimes/browsers/webkit.js +1 -1
  12. package/src/execute/web_server_param.js +68 -0
  13. package/src/kitchen/compat/features_compatibility.js +3 -0
  14. package/src/plugins/autoreload/client/reload.js +1 -4
  15. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +30 -18
  16. package/src/plugins/plugins.js +1 -1
  17. package/src/plugins/ribbon/jsenv_plugin_ribbon.js +3 -2
  18. package/src/plugins/supervisor/client/supervisor.js +467 -287
  19. package/src/plugins/supervisor/html_supervisor_injection.js +281 -0
  20. package/src/plugins/supervisor/js_supervisor_injection.js +281 -0
  21. package/src/plugins/supervisor/jsenv_plugin_supervisor.js +48 -233
  22. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +1 -1
  23. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +5 -0
  24. package/src/plugins/transpilation/jsenv_plugin_top_level_await.js +1 -1
  25. package/src/test/execute_steps.js +10 -18
  26. package/src/test/execute_test_plan.js +16 -61
  27. package/src/test/logs_file_execution.js +74 -28
  28. package/dist/js/script_type_module_supervisor.js +0 -109
  29. package/src/plugins/supervisor/client/script_type_module_supervisor.js +0 -98
package/dist/jsenv.js CHANGED
@@ -15,15 +15,15 @@ import { Readable, Stream, Writable } from "node:stream";
15
15
  import { Http2ServerResponse } from "node:http2";
16
16
  import { lookup } from "node:dns";
17
17
  import { SOURCEMAP, generateSourcemapFileUrl, composeTwoSourcemaps, generateSourcemapDataUrl, createMagicSource, getOriginalPosition } from "@jsenv/sourcemap";
18
- import { parseHtmlString, stringifyHtmlAst, getHtmlNodeAttribute, visitHtmlNodes, analyzeScriptNode, setHtmlNodeAttributes, parseSrcSet, getHtmlNodePosition, getHtmlNodeAttributePosition, parseCssUrls, parseJsUrls, getHtmlNodeText, setHtmlNodeText, applyBabelPlugins, injectScriptNodeAsEarlyAsPossible, createHtmlNode, findHtmlNode, removeHtmlNode, removeHtmlNodeText, injectJsImport, analyzeLinkNode, injectHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
18
+ import { parseHtmlString, stringifyHtmlAst, getHtmlNodeAttribute, visitHtmlNodes, analyzeScriptNode, setHtmlNodeAttributes, parseSrcSet, getHtmlNodePosition, getHtmlNodeAttributePosition, parseCssUrls, parseJsUrls, getHtmlNodeText, setHtmlNodeText, removeHtmlNodeText, applyBabelPlugins, injectScriptNodeAsEarlyAsPossible, createHtmlNode, findHtmlNode, removeHtmlNode, injectJsImport, analyzeLinkNode, injectHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
19
19
  import { createRequire } from "node:module";
20
20
  import babelParser from "@babel/parser";
21
21
  import { bundleJsModules } from "@jsenv/plugin-bundling";
22
22
  import v8, { takeCoverage } from "node:v8";
23
- import wrapAnsi from "wrap-ansi";
24
23
  import stripAnsi from "strip-ansi";
25
24
  import { createId } from "@paralleldrive/cuid2";
26
25
  import { runInNewContext } from "node:vm";
26
+ import wrapAnsi from "wrap-ansi";
27
27
  import { fork } from "node:child_process";
28
28
  import { Worker } from "node:worker_threads";
29
29
 
@@ -8131,7 +8131,9 @@ const featuresCompatMap = {
8131
8131
  samsung: "9.2"
8132
8132
  },
8133
8133
  import_meta_resolve: {
8134
- chrome: "107"
8134
+ chrome: "107",
8135
+ edge: "105",
8136
+ firefox: "106"
8135
8137
  },
8136
8138
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility
8137
8139
  import_dynamic: {
@@ -8161,7 +8163,8 @@ const featuresCompatMap = {
8161
8163
  chrome: "89",
8162
8164
  opera: "76",
8163
8165
  samsung: "15",
8164
- firefox: "108"
8166
+ firefox: "108",
8167
+ safari: "16.4"
8165
8168
  },
8166
8169
  import_type_json: {
8167
8170
  chrome: "91",
@@ -10411,6 +10414,11 @@ const findOriginalDirectoryReference = (urlInfo, context) => {
10411
10414
  return ref;
10412
10415
  };
10413
10416
 
10417
+ /*
10418
+ * This plugin ensure content inlined inside HTML is cooked (inline <script> for instance)
10419
+ * For <script hot-accept> the script content will be moved to a virtual file
10420
+ * to enable hot reloading
10421
+ */
10414
10422
  const jsenvPluginHtmlInlineContent = ({
10415
10423
  analyzeConvertedScripts
10416
10424
  }) => {
@@ -10508,9 +10516,7 @@ ${e.traceMessage}`);
10508
10516
  if (!analyzeConvertedScripts && getHtmlNodeAttribute(scriptNode, "jsenv-injected-by") === "jsenv:as_js_classic_html") {
10509
10517
  return;
10510
10518
  }
10511
- if (getHtmlNodeAttribute(scriptNode, "jsenv-cooked-by") === "jsenv:supervisor" || getHtmlNodeAttribute(scriptNode, "jsenv-inlined-by") === "jsenv:supervisor" || getHtmlNodeAttribute(scriptNode, "jsenv-injected-by") === "jsenv:supervisor") {
10512
- return;
10513
- }
10519
+ const hotAccept = getHtmlNodeAttribute(scriptNode, "hot-accept") !== undefined;
10514
10520
  const {
10515
10521
  type,
10516
10522
  contentType,
@@ -10555,14 +10561,26 @@ ${e.traceMessage}`);
10555
10561
  inlineContentUrlInfo: inlineScriptUrlInfo,
10556
10562
  inlineContentReference: inlineScriptReference
10557
10563
  });
10558
- });
10559
- mutations.push(() => {
10560
- setHtmlNodeText(scriptNode, inlineScriptUrlInfo.content);
10561
- setHtmlNodeAttributes(scriptNode, {
10562
- "jsenv-cooked-by": "jsenv:html_inline_content",
10563
- ...(extension ? {
10564
- type: type === "js_module" ? "module" : undefined
10565
- } : {})
10564
+ mutations.push(() => {
10565
+ const attributes = {
10566
+ "jsenv-cooked-by": "jsenv:html_inline_content",
10567
+ // 1. <script type="jsx"> becomes <script>
10568
+ // 2. <script type="module/jsx"> becomes <script type="module">
10569
+ ...(extension ? {
10570
+ type: type === "js_module" ? "module" : undefined
10571
+ } : {})
10572
+ };
10573
+ if (hotAccept) {
10574
+ removeHtmlNodeText(scriptNode);
10575
+ setHtmlNodeAttributes(scriptNode, {
10576
+ ...attributes
10577
+ });
10578
+ } else {
10579
+ setHtmlNodeText(scriptNode, inlineScriptUrlInfo.content);
10580
+ setHtmlNodeAttributes(scriptNode, {
10581
+ ...attributes
10582
+ });
10583
+ }
10566
10584
  });
10567
10585
  });
10568
10586
  }
@@ -15062,7 +15080,7 @@ const jsenvPluginAsJsClassicHtml = ({
15062
15080
  break;
15063
15081
  }
15064
15082
  } catch (e) {
15065
- if (context.dev) {
15083
+ if (context.dev && e.code !== "PARSE_ERROR") {
15066
15084
  needsSystemJs = true;
15067
15085
  // ignore cooking error, the browser will trigger it again on fetch
15068
15086
  // + disable cache for this html file because when browser will reload
@@ -17484,49 +17502,501 @@ const jsenvPluginHttpUrls = () => {
17484
17502
  };
17485
17503
 
17486
17504
  /*
17487
- * Jsenv needs to wait for all js execution inside an HTML page before killing the browser.
17488
- * A naive approach would consider execution done when "load" event is dispatched on window but:
17505
+ * ```js
17506
+ * console.log(42)
17507
+ * ```
17508
+ * becomes
17509
+ * ```js
17510
+ * window.__supervisor__.jsClassicStart('main.html@L10-L13.js')
17511
+ * try {
17512
+ * console.log(42)
17513
+ * window.__supervisor__.jsClassicEnd('main.html@L10-L13.js')
17514
+ * } catch(e) {
17515
+ * window.__supervisor__.jsClassicError('main.html@L10-L13.js', e)
17516
+ * }
17517
+ * ```
17518
+ *
17519
+ * ```js
17520
+ * import value from "./file.js"
17521
+ * console.log(value)
17522
+ * ```
17523
+ * becomes
17524
+ * ```js
17525
+ * window.__supervisor__.jsModuleStart('main.html@L10-L13.js')
17526
+ * try {
17527
+ * const value = await import("./file.js")
17528
+ * console.log(value)
17529
+ * window.__supervisor__.jsModuleEnd('main.html@L10-L13.js')
17530
+ * } catch(e) {
17531
+ * window.__supervisor__.jsModuleError('main.html@L10-L13.js', e)
17532
+ * }
17533
+ * ```
17489
17534
  *
17535
+ * -> TO KEEP IN MIND:
17536
+ * Static import can throw errors like
17537
+ * The requested module '/js_module_export_not_found/foo.js' does not provide an export named 'answerr'
17538
+ * While dynamic import will work just fine
17539
+ * and create a variable named "undefined"
17540
+ */
17541
+ const injectSupervisorIntoJs = async ({
17542
+ content,
17543
+ url,
17544
+ type,
17545
+ inlineSrc
17546
+ }) => {
17547
+ const babelPluginJsSupervisor = type === "js_module" ? babelPluginJsModuleSupervisor : babelPluginJsClassicSupervisor;
17548
+ const result = await applyBabelPlugins({
17549
+ urlInfo: {
17550
+ content,
17551
+ originalUrl: url,
17552
+ type
17553
+ },
17554
+ babelPlugins: [[babelPluginJsSupervisor, {
17555
+ inlineSrc
17556
+ }]]
17557
+ });
17558
+ let code = result.code;
17559
+ let map = result.map;
17560
+ const sourcemapDataUrl = generateSourcemapDataUrl(map);
17561
+ code = SOURCEMAP.writeComment({
17562
+ contentType: "text/javascript",
17563
+ content: code,
17564
+ specifier: sourcemapDataUrl
17565
+ });
17566
+ code = `${code}
17567
+ //# sourceURL=${url}`;
17568
+ return code;
17569
+ };
17570
+ const babelPluginJsModuleSupervisor = babel => {
17571
+ const t = babel.types;
17572
+ return {
17573
+ name: "js-module-supervisor",
17574
+ visitor: {
17575
+ Program: (programPath, state) => {
17576
+ const {
17577
+ inlineSrc
17578
+ } = state.opts;
17579
+ if (state.file.metadata.jsExecutionInstrumented) return;
17580
+ state.file.metadata.jsExecutionInstrumented = true;
17581
+ const urlNode = t.stringLiteral(inlineSrc);
17582
+ const startCallNode = createSupervisionCall({
17583
+ t,
17584
+ urlNode,
17585
+ methodName: "jsModuleStart"
17586
+ });
17587
+ const endCallNode = createSupervisionCall({
17588
+ t,
17589
+ urlNode,
17590
+ methodName: "jsModuleEnd"
17591
+ });
17592
+ const errorCallNode = createSupervisionCall({
17593
+ t,
17594
+ urlNode,
17595
+ methodName: "jsModuleError",
17596
+ args: [t.identifier("e")]
17597
+ });
17598
+ const bodyPath = programPath.get("body");
17599
+ const importNodes = [];
17600
+ const topLevelNodes = [];
17601
+ for (const topLevelNodePath of bodyPath) {
17602
+ const topLevelNode = topLevelNodePath.node;
17603
+ if (t.isImportDeclaration(topLevelNode)) {
17604
+ importNodes.push(topLevelNode);
17605
+ } else {
17606
+ topLevelNodes.push(topLevelNode);
17607
+ }
17608
+ }
17609
+
17610
+ // replace all import nodes with dynamic imports
17611
+ const dynamicImports = [];
17612
+ importNodes.forEach(importNode => {
17613
+ const dynamicImportConversion = convertStaticImportIntoDynamicImport(importNode, t);
17614
+ if (Array.isArray(dynamicImportConversion)) {
17615
+ dynamicImports.push(...dynamicImportConversion);
17616
+ } else {
17617
+ dynamicImports.push(dynamicImportConversion);
17618
+ }
17619
+ });
17620
+ const tryCatchNode = t.tryStatement(t.blockStatement([...dynamicImports, ...topLevelNodes, endCallNode]), t.catchClause(t.identifier("e"), t.blockStatement([errorCallNode])));
17621
+ programPath.replaceWith(t.program([startCallNode, tryCatchNode]));
17622
+ }
17623
+ }
17624
+ };
17625
+ };
17626
+ const convertStaticImportIntoDynamicImport = (staticImportNode, t) => {
17627
+ const awaitExpression = t.awaitExpression(t.callExpression(t.import(), [t.stringLiteral(staticImportNode.source.value)]));
17628
+
17629
+ // import "./file.js" -> await import("./file.js")
17630
+ if (staticImportNode.specifiers.length === 0) {
17631
+ return t.expressionStatement(awaitExpression);
17632
+ }
17633
+ if (staticImportNode.specifiers.length === 1) {
17634
+ const [firstSpecifier] = staticImportNode.specifiers;
17635
+ if (firstSpecifier.type === "ImportNamespaceSpecifier") {
17636
+ return t.variableDeclaration("const", [t.variableDeclarator(t.identifier(firstSpecifier.local.name), awaitExpression)]);
17637
+ }
17638
+ }
17639
+ if (staticImportNode.specifiers.length === 2) {
17640
+ const [first, second] = staticImportNode.specifiers;
17641
+ if (first.type === "ImportDefaultSpecifier" && second.type === "ImportNamespaceSpecifier") {
17642
+ const namespaceDeclaration = t.variableDeclaration("const", [t.variableDeclarator(t.identifier(second.local.name), awaitExpression)]);
17643
+ const defaultDeclaration = t.variableDeclaration("const", [t.variableDeclarator(t.identifier(first.local.name), t.memberExpression(t.identifier(second.local.name), t.identifier("default")))]);
17644
+ return [namespaceDeclaration, defaultDeclaration];
17645
+ }
17646
+ }
17647
+
17648
+ // import { name } from "./file.js" -> const { name } = await import("./file.js")
17649
+ // import toto, { name } from "./file.js" -> const { name, default as toto } = await import("./file.js")
17650
+ const objectPattern = t.objectPattern(staticImportNode.specifiers.map(specifier => {
17651
+ if (specifier.type === "ImportDefaultSpecifier") {
17652
+ return t.objectProperty(t.identifier("default"), t.identifier(specifier.local.name), false,
17653
+ // computed
17654
+ false // shorthand
17655
+ );
17656
+ }
17657
+ // if (specifier.type === "ImportNamespaceSpecifier") {
17658
+ // return t.restElement(t.identifier(specifier.local.name))
17659
+ // }
17660
+ const isRenamed = specifier.imported.name !== specifier.local.name;
17661
+ if (isRenamed) {
17662
+ return t.objectProperty(t.identifier(specifier.imported.name), t.identifier(specifier.local.name), false,
17663
+ // computed
17664
+ false // shorthand
17665
+ );
17666
+ }
17667
+ // shorthand must be true
17668
+ return t.objectProperty(t.identifier(specifier.local.name), t.identifier(specifier.local.name), false,
17669
+ // computed
17670
+ true // shorthand
17671
+ );
17672
+ }));
17673
+
17674
+ const variableDeclarator = t.variableDeclarator(objectPattern, awaitExpression);
17675
+ const variableDeclaration = t.variableDeclaration("const", [variableDeclarator]);
17676
+ return variableDeclaration;
17677
+ };
17678
+ const babelPluginJsClassicSupervisor = babel => {
17679
+ const t = babel.types;
17680
+ return {
17681
+ name: "js-classic-supervisor",
17682
+ visitor: {
17683
+ Program: (programPath, state) => {
17684
+ const {
17685
+ inlineSrc
17686
+ } = state.opts;
17687
+ if (state.file.metadata.jsExecutionInstrumented) return;
17688
+ state.file.metadata.jsExecutionInstrumented = true;
17689
+ const urlNode = t.stringLiteral(inlineSrc);
17690
+ const startCallNode = createSupervisionCall({
17691
+ t,
17692
+ urlNode,
17693
+ methodName: "jsClassicStart"
17694
+ });
17695
+ const endCallNode = createSupervisionCall({
17696
+ t,
17697
+ urlNode,
17698
+ methodName: "jsClassicEnd"
17699
+ });
17700
+ const errorCallNode = createSupervisionCall({
17701
+ t,
17702
+ urlNode,
17703
+ methodName: "jsClassicError",
17704
+ args: [t.identifier("e")]
17705
+ });
17706
+ const topLevelNodes = programPath.node.body;
17707
+ const tryCatchNode = t.tryStatement(t.blockStatement([...topLevelNodes, endCallNode]), t.catchClause(t.identifier("e"), t.blockStatement([errorCallNode])));
17708
+ programPath.replaceWith(t.program([startCallNode, tryCatchNode]));
17709
+ }
17710
+ }
17711
+ };
17712
+ };
17713
+ const createSupervisionCall = ({
17714
+ t,
17715
+ methodName,
17716
+ urlNode,
17717
+ args = []
17718
+ }) => {
17719
+ return t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.identifier("window"), t.identifier("__supervisor__")), t.identifier(methodName)), [urlNode, ...args]), [], null);
17720
+ };
17721
+
17722
+ /*
17723
+ * Jsenv needs to track js execution in order to:
17724
+ * 1. report errors
17725
+ * 2. wait for all js execution inside an HTML page before killing the browser
17726
+ *
17727
+ * A naive approach would rely on "load" events on window but:
17490
17728
  * scenario | covered by window "load"
17491
17729
  * ------------------------------------------- | -------------------------
17492
17730
  * js referenced by <script src> | yes
17493
17731
  * js inlined into <script> | yes
17494
17732
  * js referenced by <script type="module" src> | partially (not for import and top level await)
17495
17733
  * js inlined into <script type="module"> | not at all
17496
- *
17497
- * This plugin provides a way for jsenv to know when js execution is done
17498
- * As a side effect this plugin enables ability to hot reload js inlined into <script hot-accept>
17734
+ * Same for "error" event on window who is not enough
17499
17735
  *
17500
17736
  * <script src="file.js">
17501
17737
  * becomes
17502
17738
  * <script>
17503
- * window.__supervisor__.superviseScript({ src: 'file.js' })
17739
+ * window.__supervisor__.superviseScript('file.js')
17504
17740
  * </script>
17505
17741
  *
17506
17742
  * <script>
17507
17743
  * console.log(42)
17508
17744
  * </script>
17509
17745
  * becomes
17510
- * <script>
17511
- * window.__supervisor__.superviseScript({ src: 'main.html@L10-L13.js' })
17746
+ * <script inlined-from-src="main.html@L10-C5.js">
17747
+ * window.__supervisor.__superviseScript("main.html@L10-C5.js")
17512
17748
  * </script>
17513
17749
  *
17514
17750
  * <script type="module" src="module.js"></script>
17515
17751
  * becomes
17516
17752
  * <script type="module">
17517
- * import { superviseScriptTypeModule } from 'supervisor'
17518
- * superviseScriptTypeModule({ src: "module.js" })
17753
+ * window.__supervisor__.superviseScriptTypeModule('module.js')
17519
17754
  * </script>
17520
17755
  *
17521
17756
  * <script type="module">
17522
17757
  * console.log(42)
17523
17758
  * </script>
17524
17759
  * becomes
17525
- * <script type="module">
17526
- * import { superviseScriptTypeModule } from 'supervisor'
17527
- * superviseScriptTypeModule({ src: 'main.html@L10-L13.js' })
17760
+ * <script type="module" inlined-from-src="main.html@L10-C5.js">
17761
+ * window.__supervisor__.superviseScriptTypeModule('main.html@L10-C5.js')
17528
17762
  * </script>
17763
+ *
17764
+ * Why Inline scripts are converted to files dynamically?
17765
+ * -> No changes required on js source code, it's only the HTML that is modified
17766
+ * - Also allow to catch syntax errors and export missing
17767
+ */
17768
+ const supervisorFileUrl$1 = new URL("./js/supervisor.js", import.meta.url).href;
17769
+ const injectSupervisorIntoHTML = async ({
17770
+ content,
17771
+ url
17772
+ }, {
17773
+ supervisorScriptSrc = supervisorFileUrl$1,
17774
+ supervisorOptions,
17775
+ webServer,
17776
+ onInlineScript = () => {},
17777
+ generateInlineScriptSrc = ({
17778
+ inlineScriptUrl
17779
+ }) => urlToRelativeUrl(inlineScriptUrl, webServer.rootDirectoryUrl),
17780
+ inlineAsRemote
17781
+ }) => {
17782
+ const htmlAst = parseHtmlString(content);
17783
+ const mutations = [];
17784
+ const actions = [];
17785
+ const scriptInfos = [];
17786
+ // 1. Find inline and remote scripts
17787
+ {
17788
+ const handleInlineScript = (scriptNode, {
17789
+ type,
17790
+ extension,
17791
+ textContent
17792
+ }) => {
17793
+ const {
17794
+ line,
17795
+ column,
17796
+ lineEnd,
17797
+ columnEnd,
17798
+ isOriginal
17799
+ } = getHtmlNodePosition(scriptNode, {
17800
+ preferOriginal: true
17801
+ });
17802
+ const inlineScriptUrl = generateInlineContentUrl({
17803
+ url,
17804
+ extension: extension || ".js",
17805
+ line,
17806
+ column,
17807
+ lineEnd,
17808
+ columnEnd
17809
+ });
17810
+ const inlineScriptSrc = generateInlineScriptSrc({
17811
+ type,
17812
+ textContent,
17813
+ inlineScriptUrl,
17814
+ isOriginal,
17815
+ line,
17816
+ column
17817
+ });
17818
+ onInlineScript({
17819
+ type,
17820
+ textContent,
17821
+ url: inlineScriptUrl,
17822
+ isOriginal,
17823
+ line,
17824
+ column,
17825
+ src: inlineScriptSrc
17826
+ });
17827
+ if (inlineAsRemote) {
17828
+ // prefere la version src
17829
+ scriptInfos.push({
17830
+ type,
17831
+ src: inlineScriptSrc
17832
+ });
17833
+ const remoteJsSupervised = generateCodeToSuperviseScriptWithSrc({
17834
+ type,
17835
+ src: inlineScriptSrc
17836
+ });
17837
+ mutations.push(() => {
17838
+ setHtmlNodeText(scriptNode, remoteJsSupervised);
17839
+ setHtmlNodeAttributes(scriptNode, {
17840
+ "jsenv-cooked-by": "jsenv:supervisor",
17841
+ "src": undefined,
17842
+ "inlined-from-src": inlineScriptSrc
17843
+ });
17844
+ });
17845
+ } else {
17846
+ scriptInfos.push({
17847
+ type,
17848
+ src: inlineScriptSrc,
17849
+ isInline: true
17850
+ });
17851
+ actions.push(async () => {
17852
+ try {
17853
+ const inlineJsSupervised = await injectSupervisorIntoJs({
17854
+ webServer,
17855
+ content: textContent,
17856
+ url: inlineScriptUrl,
17857
+ type,
17858
+ inlineSrc: inlineScriptSrc
17859
+ });
17860
+ mutations.push(() => {
17861
+ setHtmlNodeText(scriptNode, inlineJsSupervised);
17862
+ setHtmlNodeAttributes(scriptNode, {
17863
+ "jsenv-cooked-by": "jsenv:supervisor"
17864
+ });
17865
+ });
17866
+ } catch (e) {
17867
+ if (e.code === "PARSE_ERROR") {
17868
+ // mutations.push(() => {
17869
+ // setHtmlNodeAttributes(scriptNode, {
17870
+ // "jsenv-cooked-by": "jsenv:supervisor",
17871
+ // })
17872
+ // })
17873
+ // on touche a rien
17874
+ return;
17875
+ }
17876
+ throw e;
17877
+ }
17878
+ });
17879
+ }
17880
+ };
17881
+ const handleScriptWithSrc = (scriptNode, {
17882
+ type,
17883
+ src
17884
+ }) => {
17885
+ scriptInfos.push({
17886
+ type,
17887
+ src
17888
+ });
17889
+ const remoteJsSupervised = generateCodeToSuperviseScriptWithSrc({
17890
+ type,
17891
+ src
17892
+ });
17893
+ mutations.push(() => {
17894
+ setHtmlNodeText(scriptNode, remoteJsSupervised);
17895
+ setHtmlNodeAttributes(scriptNode, {
17896
+ "jsenv-cooked-by": "jsenv:supervisor",
17897
+ "src": undefined,
17898
+ "inlined-from-src": src
17899
+ });
17900
+ });
17901
+ };
17902
+ visitHtmlNodes(htmlAst, {
17903
+ script: scriptNode => {
17904
+ const {
17905
+ type,
17906
+ extension
17907
+ } = analyzeScriptNode(scriptNode);
17908
+ if (type !== "js_classic" && type !== "js_module") {
17909
+ return;
17910
+ }
17911
+ if (getHtmlNodeAttribute(scriptNode, "jsenv-injected-by")) {
17912
+ return;
17913
+ }
17914
+ const noSupervisor = getHtmlNodeAttribute(scriptNode, "no-supervisor");
17915
+ if (noSupervisor !== undefined) {
17916
+ return;
17917
+ }
17918
+ const scriptNodeText = getHtmlNodeText(scriptNode);
17919
+ if (scriptNodeText) {
17920
+ handleInlineScript(scriptNode, {
17921
+ type,
17922
+ extension,
17923
+ textContent: scriptNodeText
17924
+ });
17925
+ return;
17926
+ }
17927
+ const src = getHtmlNodeAttribute(scriptNode, "src");
17928
+ if (src) {
17929
+ handleScriptWithSrc(scriptNode, {
17930
+ type,
17931
+ src
17932
+ });
17933
+ return;
17934
+ }
17935
+ }
17936
+ });
17937
+ }
17938
+ // 2. Inject supervisor js file + setup call
17939
+ {
17940
+ const setupParamsSource = stringifyParams({
17941
+ ...supervisorOptions,
17942
+ serverIsJsenvDevServer: webServer.isJsenvDevServer,
17943
+ rootDirectoryUrl: webServer.rootDirectoryUrl,
17944
+ scriptInfos
17945
+ }, " ");
17946
+ injectScriptNodeAsEarlyAsPossible(htmlAst, createHtmlNode({
17947
+ tagName: "script",
17948
+ textContent: `
17949
+ window.__supervisor__.setup({
17950
+ ${setupParamsSource}
17951
+ })
17952
+ `
17953
+ }), "jsenv:supervisor");
17954
+ const supervisorScript = createHtmlNode({
17955
+ tagName: "script",
17956
+ src: supervisorScriptSrc
17957
+ });
17958
+ injectScriptNodeAsEarlyAsPossible(htmlAst, supervisorScript, "jsenv:supervisor");
17959
+ }
17960
+ // 3. Perform actions (transforming inline script content) and html mutations
17961
+ if (actions.length > 0) {
17962
+ await Promise.all(actions.map(action => action()));
17963
+ }
17964
+ mutations.forEach(mutation => mutation());
17965
+ const htmlModified = stringifyHtmlAst(htmlAst);
17966
+ return {
17967
+ content: htmlModified
17968
+ };
17969
+ };
17970
+ const stringifyParams = (params, prefix = "") => {
17971
+ const source = JSON.stringify(params, null, prefix);
17972
+ if (prefix.length) {
17973
+ // remove leading "{\n"
17974
+ // remove leading prefix
17975
+ // remove trailing "\n}"
17976
+ return source.slice(2 + prefix.length, -2);
17977
+ }
17978
+ // remove leading "{"
17979
+ // remove trailing "}"
17980
+ return source.slice(1, -1);
17981
+ };
17982
+ const generateCodeToSuperviseScriptWithSrc = ({
17983
+ type,
17984
+ src
17985
+ }) => {
17986
+ if (type === "js_module") {
17987
+ return `
17988
+ window.__supervisor__.superviseScriptTypeModule(${JSON.stringify(src)}, (url) => import(url));
17989
+ `;
17990
+ }
17991
+ return `
17992
+ window.__supervisor__.superviseScript(${JSON.stringify(src)});
17993
+ `;
17994
+ };
17995
+
17996
+ /*
17997
+ * This plugin provides a way for jsenv to know when js execution is done
17529
17998
  */
17999
+ const supervisorFileUrl = new URL("./js/supervisor.js", import.meta.url).href;
17530
18000
  const jsenvPluginSupervisor = ({
17531
18001
  logs = false,
17532
18002
  measurePerf = false,
@@ -17534,8 +18004,6 @@ const jsenvPluginSupervisor = ({
17534
18004
  openInEditor = true,
17535
18005
  errorBaseUrl
17536
18006
  }) => {
17537
- const supervisorFileUrl = new URL("./js/supervisor.js", import.meta.url).href;
17538
- const scriptTypeModuleSupervisorFileUrl = new URL("./js/script_type_module_supervisor.js", import.meta.url).href;
17539
18007
  return {
17540
18008
  name: "jsenv:supervisor",
17541
18009
  appliesDuring: "dev",
@@ -17677,170 +18145,50 @@ const jsenvPluginSupervisor = ({
17677
18145
  url,
17678
18146
  content
17679
18147
  }, context) => {
17680
- const htmlAst = parseHtmlString(content);
17681
- const scriptsToSupervise = [];
17682
- const handleInlineScript = (node, htmlNodeText) => {
17683
- const {
17684
- type,
17685
- extension
17686
- } = analyzeScriptNode(node);
17687
- const {
17688
- line,
17689
- column,
17690
- lineEnd,
17691
- columnEnd,
17692
- isOriginal
17693
- } = getHtmlNodePosition(node, {
17694
- preferOriginal: true
17695
- });
17696
- let inlineScriptUrl = generateInlineContentUrl({
17697
- url,
17698
- extension: extension || ".js",
17699
- line,
17700
- column,
17701
- lineEnd,
17702
- columnEnd
17703
- });
17704
- const [inlineScriptReference] = context.referenceUtils.foundInline({
17705
- type: "script",
17706
- subtype: "inline",
17707
- expectedType: type,
17708
- isOriginalPosition: isOriginal,
17709
- specifierLine: line - 1,
17710
- specifierColumn: column,
17711
- specifier: inlineScriptUrl,
17712
- contentType: "text/javascript",
17713
- content: htmlNodeText
17714
- });
17715
- removeHtmlNodeText(node);
17716
- if (extension) {
17717
- setHtmlNodeAttributes(node, {
17718
- type: type === "js_module" ? "module" : undefined
17719
- });
17720
- }
17721
- scriptsToSupervise.push({
17722
- node,
17723
- isInline: true,
17724
- type,
17725
- src: inlineScriptReference.generatedSpecifier
17726
- });
17727
- };
17728
- const handleScriptWithSrc = (node, src) => {
17729
- const {
17730
- type
17731
- } = analyzeScriptNode(node);
17732
- const integrity = getHtmlNodeAttribute(node, "integrity");
17733
- const crossorigin = getHtmlNodeAttribute(node, "crossorigin") !== undefined;
17734
- const defer = getHtmlNodeAttribute(node, "defer") !== undefined;
17735
- const async = getHtmlNodeAttribute(node, "async") !== undefined;
17736
- scriptsToSupervise.push({
17737
- node,
17738
- type,
17739
- src,
17740
- defer,
17741
- async,
17742
- integrity,
17743
- crossorigin
17744
- });
17745
- };
17746
- visitHtmlNodes(htmlAst, {
17747
- script: node => {
17748
- const {
17749
- type
17750
- } = analyzeScriptNode(node);
17751
- if (type !== "js_classic" && type !== "js_module") {
17752
- return;
17753
- }
17754
- if (getHtmlNodeAttribute(node, "jsenv-cooked-by") || getHtmlNodeAttribute(node, "jsenv-inlined-by") || getHtmlNodeAttribute(node, "jsenv-injected-by")) {
17755
- return;
17756
- }
17757
- const noSupervisor = getHtmlNodeAttribute(node, "no-supervisor");
17758
- if (noSupervisor !== undefined) {
17759
- return;
17760
- }
17761
- const htmlNodeText = getHtmlNodeText(node);
17762
- if (htmlNodeText) {
17763
- handleInlineScript(node, htmlNodeText);
17764
- return;
17765
- }
17766
- const src = getHtmlNodeAttribute(node, "src");
17767
- if (src) {
17768
- handleScriptWithSrc(node, src);
17769
- return;
17770
- }
17771
- }
17772
- });
17773
- const [scriptTypeModuleSupervisorFileReference] = context.referenceUtils.inject({
17774
- type: "js_import",
17775
- expectedType: "js_module",
17776
- specifier: scriptTypeModuleSupervisorFileUrl
17777
- });
17778
18148
  const [supervisorFileReference] = context.referenceUtils.inject({
17779
18149
  type: "script",
17780
18150
  expectedType: "js_classic",
17781
18151
  specifier: supervisorFileUrl
17782
18152
  });
17783
- injectScriptNodeAsEarlyAsPossible(htmlAst, createHtmlNode({
17784
- tagName: "script",
17785
- textContent: `
17786
- window.__supervisor__.setup(${JSON.stringify({
17787
- rootDirectoryUrl: context.rootDirectoryUrl,
18153
+ return injectSupervisorIntoHTML({
18154
+ content,
18155
+ url
18156
+ }, {
18157
+ supervisorScriptSrc: supervisorFileReference.generatedSpecifier,
18158
+ supervisorOptions: {
17788
18159
  errorBaseUrl,
17789
18160
  logs,
17790
18161
  measurePerf,
17791
18162
  errorOverlay,
17792
18163
  openInEditor
17793
- }, null, " ")})
17794
- `
17795
- }), "jsenv:supervisor");
17796
- injectScriptNodeAsEarlyAsPossible(htmlAst, createHtmlNode({
17797
- tagName: "script",
17798
- src: supervisorFileReference.generatedSpecifier
17799
- }), "jsenv:supervisor");
17800
- scriptsToSupervise.forEach(({
17801
- node,
17802
- isInline,
17803
- type,
17804
- src,
17805
- defer,
17806
- async,
17807
- integrity,
17808
- crossorigin
17809
- }) => {
17810
- const paramsAsJson = JSON.stringify({
17811
- src,
17812
- isInline,
17813
- defer,
17814
- async,
17815
- integrity,
17816
- crossorigin
17817
- });
17818
- if (type === "js_module") {
17819
- setHtmlNodeText(node, `
17820
- import { superviseScriptTypeModule } from ${scriptTypeModuleSupervisorFileReference.generatedSpecifier}
17821
- superviseScriptTypeModule(${paramsAsJson})
17822
- `);
17823
- } else {
17824
- setHtmlNodeText(node, `
17825
- window.__supervisor__.superviseScript(${paramsAsJson})
17826
- `);
17827
- }
17828
- if (src) {
17829
- setHtmlNodeAttributes(node, {
17830
- "jsenv-inlined-by": "jsenv:supervisor",
17831
- "src": undefined,
17832
- "inlined-from-src": src
17833
- });
17834
- } else {
17835
- setHtmlNodeAttributes(node, {
17836
- "jsenv-cooked-by": "jsenv:supervisor"
18164
+ },
18165
+ webServer: {
18166
+ rootDirectoryUrl: context.rootDirectoryUrl,
18167
+ isJsenvDevServer: true
18168
+ },
18169
+ inlineAsRemote: true,
18170
+ generateInlineScriptSrc: ({
18171
+ type,
18172
+ textContent,
18173
+ inlineScriptUrl,
18174
+ isOriginal,
18175
+ line,
18176
+ column
18177
+ }) => {
18178
+ const [inlineScriptReference] = context.referenceUtils.foundInline({
18179
+ type: "script",
18180
+ subtype: "inline",
18181
+ expectedType: type,
18182
+ isOriginalPosition: isOriginal,
18183
+ specifierLine: line - 1,
18184
+ specifierColumn: column,
18185
+ specifier: inlineScriptUrl,
18186
+ contentType: "text/javascript",
18187
+ content: textContent
17837
18188
  });
18189
+ return inlineScriptReference.generatedSpecifier;
17838
18190
  }
17839
18191
  });
17840
- const htmlModified = stringifyHtmlAst(htmlAst);
17841
- return {
17842
- content: htmlModified
17843
- };
17844
18192
  }
17845
18193
  }
17846
18194
  };
@@ -19451,7 +19799,12 @@ const jsenvPluginBabel = ({
19451
19799
  map
19452
19800
  } = await applyBabelPlugins({
19453
19801
  babelPlugins,
19454
- urlInfo
19802
+ urlInfo,
19803
+ options: {
19804
+ generatorOpts: {
19805
+ retainLines: context.dev
19806
+ }
19807
+ }
19455
19808
  });
19456
19809
  return {
19457
19810
  content: code,
@@ -19539,7 +19892,7 @@ const babelPluginMetadataUsesTopLevelAwait = () => {
19539
19892
  programPath.traverse({
19540
19893
  AwaitExpression: path => {
19541
19894
  const closestFunction = path.getFunctionParent();
19542
- if (!closestFunction) {
19895
+ if (!closestFunction || closestFunction.type === "Program") {
19543
19896
  usesTopLevelAwait = true;
19544
19897
  path.stop();
19545
19898
  }
@@ -20418,9 +20771,10 @@ const jsenvPluginRibbon = ({
20418
20771
  tagName: "script",
20419
20772
  type: "module",
20420
20773
  textContent: `
20421
- import { injectRibbon} from "${ribbonClientFileReference.generatedSpecifier}"
20774
+ import { injectRibbon } from "${ribbonClientFileReference.generatedSpecifier}"
20422
20775
 
20423
- injectRibbon(${paramsJson})`
20776
+ injectRibbon(${paramsJson})
20777
+ `
20424
20778
  });
20425
20779
  injectHtmlNode(htmlAst, scriptNode, "jsenv:ribbon");
20426
20780
  return stringifyHtmlAst(htmlAst);
@@ -20468,13 +20822,13 @@ const getCorePlugins = ({
20468
20822
  return [jsenvPluginUrlAnalysis({
20469
20823
  rootDirectoryUrl,
20470
20824
  ...urlAnalysis
20471
- }), jsenvPluginTranspilation(transpilation), ...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []),
20472
- // before inline as it turns inline <script> into <script src>
20473
- jsenvPluginImportmap(),
20825
+ }), jsenvPluginTranspilation(transpilation), jsenvPluginImportmap(),
20474
20826
  // before node esm to handle bare specifiers
20475
20827
  // + before node esm to handle importmap before inline content
20476
20828
  jsenvPluginInline(),
20477
20829
  // before "file urls" to resolve and load inline urls
20830
+ ...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []),
20831
+ // after inline as it needs inline script to be cooked
20478
20832
  jsenvPluginFileUrls({
20479
20833
  directoryReferenceAllowed,
20480
20834
  ...fileSystemMagicRedirection
@@ -22387,7 +22741,7 @@ const createFileService = ({
22387
22741
  const {
22388
22742
  runtimeName,
22389
22743
  runtimeVersion
22390
- } = parseUserAgentHeader(request.headers["user-agent"]);
22744
+ } = parseUserAgentHeader(request.headers["user-agent"] || "");
22391
22745
  const runtimeId = `${runtimeName}@${runtimeVersion}`;
22392
22746
  const existingContext = contextCache.get(runtimeId);
22393
22747
  if (existingContext) {
@@ -22846,7 +23200,7 @@ const startDevServer = async ({
22846
23200
  stopOnExit: false,
22847
23201
  stopOnSIGINT: handleSIGINT,
22848
23202
  stopOnInternalError: false,
22849
- keepProcessAlive,
23203
+ keepProcessAlive: process.env.IMPORTED_BY_TEST_PLAN ? false : keepProcessAlive,
22850
23204
  logLevel: serverLogLevel,
22851
23205
  startLog: false,
22852
23206
  https,
@@ -22855,7 +23209,13 @@ const startDevServer = async ({
22855
23209
  hostname,
22856
23210
  port,
22857
23211
  requestWaitingMs: 60_000,
22858
- services: [jsenvServiceCORS({
23212
+ services: [{
23213
+ injectResponseHeaders: () => {
23214
+ return {
23215
+ "x-server-name": "jsenv_dev_server"
23216
+ };
23217
+ }
23218
+ }, jsenvServiceCORS({
22859
23219
  accessControlAllowRequestOrigin: true,
22860
23220
  accessControlAllowRequestMethod: true,
22861
23221
  accessControlAllowRequestHeaders: true,
@@ -22864,7 +23224,7 @@ const startDevServer = async ({
22864
23224
  timingAllowOrigin: true
22865
23225
  }), {
22866
23226
  handleRequest: request => {
22867
- if (request.pathname === "/__server_params__.json") {
23227
+ if (request.pathname === "/__params__.json") {
22868
23228
  const json = JSON.stringify({
22869
23229
  sourceDirectoryUrl
22870
23230
  });
@@ -22877,12 +23237,6 @@ const startDevServer = async ({
22877
23237
  body: json
22878
23238
  };
22879
23239
  }
22880
- if (request.pathname === "/__stop__") {
22881
- server.stop();
22882
- return {
22883
- status: 200
22884
- };
22885
- }
22886
23240
  return null;
22887
23241
  }
22888
23242
  }, ...services, {
@@ -23041,18 +23395,28 @@ const basicFetch = async (url, {
23041
23395
  headers
23042
23396
  });
23043
23397
  req.on("response", response => {
23044
- req.setTimeout(0);
23045
- let responseBody = "";
23046
- response.setEncoding("utf8");
23047
- response.on("data", chunk => {
23048
- responseBody += chunk;
23049
- });
23050
- response.on("end", () => {
23051
- req.destroy();
23052
- if (response.headers["content-type"] === "application/json") {
23053
- resolve(JSON.parse(responseBody));
23054
- } else {
23055
- resolve(responseBody);
23398
+ resolve({
23399
+ status: response.statusCode,
23400
+ headers: response.headers,
23401
+ json: () => {
23402
+ req.setTimeout(0);
23403
+ req.destroy();
23404
+ return new Promise(resolve => {
23405
+ if (response.headers["content-type"] !== "application/json") {
23406
+ console.warn("not json");
23407
+ }
23408
+ let responseBody = "";
23409
+ response.setEncoding("utf8");
23410
+ response.on("data", chunk => {
23411
+ responseBody += chunk;
23412
+ });
23413
+ response.on("end", () => {
23414
+ resolve(JSON.parse(responseBody));
23415
+ });
23416
+ response.on("error", e => {
23417
+ reject(e);
23418
+ });
23419
+ });
23056
23420
  }
23057
23421
  });
23058
23422
  });
@@ -23061,6 +23425,57 @@ const basicFetch = async (url, {
23061
23425
  });
23062
23426
  };
23063
23427
 
23428
+ const assertAndNormalizeWebServer = async webServer => {
23429
+ if (!webServer) {
23430
+ throw new TypeError(`webServer is required when running tests on browser(s)`);
23431
+ }
23432
+ const unexpectedParamNames = Object.keys(webServer).filter(key => {
23433
+ return !["origin", "moduleUrl", "rootDirectoryUrl"].includes(key);
23434
+ });
23435
+ if (unexpectedParamNames.length > 0) {
23436
+ throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param to webServer`);
23437
+ }
23438
+ let aServerIsListening = await pingServer(webServer.origin);
23439
+ if (!aServerIsListening) {
23440
+ if (!webServer.moduleUrl) {
23441
+ throw new TypeError(`webServer.moduleUrl is required as there is no server listening "${webServer.origin}"`);
23442
+ }
23443
+ try {
23444
+ process.env.IMPORTED_BY_TEST_PLAN = "1";
23445
+ await import(webServer.moduleUrl);
23446
+ delete process.env.IMPORTED_BY_TEST_PLAN;
23447
+ } catch (e) {
23448
+ if (e.code === "ERR_MODULE_NOT_FOUND") {
23449
+ throw new Error(`webServer.moduleUrl does not lead to a file at "${webServer.moduleUrl}"`);
23450
+ }
23451
+ throw e;
23452
+ }
23453
+ aServerIsListening = await pingServer(webServer.origin);
23454
+ if (!aServerIsListening) {
23455
+ throw new Error(`webServer.moduleUrl did not start a server listening at "${webServer.origin}", check file at "${webServer.moduleUrl}"`);
23456
+ }
23457
+ }
23458
+ const {
23459
+ headers
23460
+ } = await basicFetch(webServer.origin);
23461
+ if (headers["x-server-name"] === "jsenv_dev_server") {
23462
+ webServer.isJsenvDevServer = true;
23463
+ const {
23464
+ json
23465
+ } = await basicFetch(`${webServer.origin}/__params__.json`, {
23466
+ rejectUnauthorized: false
23467
+ });
23468
+ if (webServer.rootDirectoryUrl === undefined) {
23469
+ const jsenvDevServerParams = await json();
23470
+ webServer.rootDirectoryUrl = jsenvDevServerParams.sourceDirectoryUrl;
23471
+ } else {
23472
+ webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(webServer.rootDirectoryUrl, "webServer.rootDirectoryUrl");
23473
+ }
23474
+ } else {
23475
+ webServer.rootDirectoryUrl = assertAndNormalizeDirectoryUrl(webServer.rootDirectoryUrl, "webServer.rootDirectoryUrl");
23476
+ }
23477
+ };
23478
+
23064
23479
  const generateCoverageJsonFile = async ({
23065
23480
  coverage,
23066
23481
  coverageJsonFileUrl,
@@ -23887,29 +24302,49 @@ const createExecutionLog = ({
23887
24302
  timeEllapsed,
23888
24303
  memoryHeap
23889
24304
  });
24305
+ let log;
23890
24306
  if (completedExecutionLogAbbreviation && status === "completed") {
23891
- return `${description}${summary}`;
24307
+ log = `${description}${summary}`;
24308
+ } else {
24309
+ const {
24310
+ consoleCalls = [],
24311
+ errors = []
24312
+ } = executionResult;
24313
+ const consoleOutput = formatConsoleCalls(consoleCalls);
24314
+ const errorsOutput = formatErrors(errors);
24315
+ log = formatExecution({
24316
+ label: `${description}${summary}`,
24317
+ details: {
24318
+ file: fileRelativeUrl,
24319
+ ...(logRuntime ? {
24320
+ runtime: `${runtimeName}/${runtimeVersion}`
24321
+ } : {}),
24322
+ ...(logEachDuration ? {
24323
+ duration: status === "executing" ? msAsEllapsedTime(Date.now() - startMs) : msAsDuration(endMs - startMs)
24324
+ } : {})
24325
+ },
24326
+ consoleOutput,
24327
+ errorsOutput
24328
+ });
23892
24329
  }
23893
24330
  const {
23894
- consoleCalls = [],
23895
- errors = []
23896
- } = executionResult;
23897
- const consoleOutput = formatConsoleCalls(consoleCalls);
23898
- const errorsOutput = formatErrors(errors);
23899
- return formatExecution({
23900
- label: `${description}${summary}`,
23901
- details: {
23902
- file: fileRelativeUrl,
23903
- ...(logRuntime ? {
23904
- runtime: `${runtimeName}/${runtimeVersion}`
23905
- } : {}),
23906
- ...(logEachDuration ? {
23907
- duration: status === "executing" ? msAsEllapsedTime(Date.now() - startMs) : msAsDuration(endMs - startMs)
23908
- } : {})
23909
- },
23910
- consoleOutput,
23911
- errorsOutput
24331
+ columns = 80
24332
+ } = process.stdout;
24333
+ log = wrapAnsi(log, columns, {
24334
+ trim: false,
24335
+ hard: true,
24336
+ wordWrap: false
23912
24337
  });
24338
+ if (endMs) {
24339
+ if (completedExecutionLogAbbreviation) {
24340
+ return `${log}\n`;
24341
+ }
24342
+ if (executionIndex === counters.total - 1) {
24343
+ return `${log}\n`;
24344
+ }
24345
+ return `${log}\n\n`;
24346
+ }
24347
+ return log;
23913
24348
  };
23914
24349
  const formatErrors = errors => {
23915
24350
  if (errors.length === 0) {
@@ -24022,39 +24457,51 @@ const descriptionFormatters = {
24022
24457
  index,
24023
24458
  total
24024
24459
  }) => {
24025
- return ANSI.color(`executing ${index + 1} of ${total}`, EXECUTION_COLORS.executing);
24460
+ return ANSI.color(`executing ${padNumber(index, total)} of ${total}`, EXECUTION_COLORS.executing);
24026
24461
  },
24027
24462
  aborted: ({
24028
24463
  index,
24029
24464
  total
24030
24465
  }) => {
24031
- return ANSI.color(`${UNICODE.FAILURE_RAW} execution ${index + 1} of ${total} aborted`, EXECUTION_COLORS.aborted);
24466
+ return ANSI.color(`${UNICODE.FAILURE_RAW} execution ${padNumber(index, total)} of ${total} aborted`, EXECUTION_COLORS.aborted);
24032
24467
  },
24033
24468
  timedout: ({
24034
24469
  index,
24035
24470
  total,
24036
24471
  executionParams
24037
24472
  }) => {
24038
- return ANSI.color(`${UNICODE.FAILURE_RAW} execution ${index + 1} of ${total} timeout after ${executionParams.allocatedMs}ms`, EXECUTION_COLORS.timedout);
24473
+ return ANSI.color(`${UNICODE.FAILURE_RAW} execution ${padNumber(index, total)} of ${total} timeout after ${executionParams.allocatedMs}ms`, EXECUTION_COLORS.timedout);
24039
24474
  },
24040
24475
  failed: ({
24041
24476
  index,
24042
24477
  total
24043
24478
  }) => {
24044
- return ANSI.color(`${UNICODE.FAILURE_RAW} execution ${index + 1} of ${total} failed`, EXECUTION_COLORS.failed);
24479
+ return ANSI.color(`${UNICODE.FAILURE_RAW} execution ${padNumber(index, total)} of ${total} failed`, EXECUTION_COLORS.failed);
24045
24480
  },
24046
24481
  completed: ({
24047
24482
  index,
24048
24483
  total
24049
24484
  }) => {
24050
- return ANSI.color(`${UNICODE.OK_RAW} execution ${index + 1} of ${total} completed`, EXECUTION_COLORS.completed);
24485
+ return ANSI.color(`${UNICODE.OK_RAW} execution ${padNumber(index, total)} of ${total} completed`, EXECUTION_COLORS.completed);
24051
24486
  },
24052
24487
  cancelled: ({
24053
24488
  index,
24054
24489
  total
24055
24490
  }) => {
24056
- return ANSI.color(`${UNICODE.FAILURE_RAW} execution ${index + 1} of ${total} cancelled`, EXECUTION_COLORS.cancelled);
24491
+ return ANSI.color(`${UNICODE.FAILURE_RAW} execution ${padNumber(index, total)} of ${total} cancelled`, EXECUTION_COLORS.cancelled);
24492
+ }
24493
+ };
24494
+ const padNumber = (index, total) => {
24495
+ const number = index + 1;
24496
+ const numberWidth = String(number).length;
24497
+ const totalWith = String(total).length;
24498
+ let missingWidth = totalWith - numberWidth;
24499
+ let padded = "";
24500
+ while (missingWidth--) {
24501
+ padded += "0";
24057
24502
  }
24503
+ padded += number;
24504
+ return padded;
24058
24505
  };
24059
24506
  const formatConsoleCalls = consoleCalls => {
24060
24507
  if (consoleCalls.length === 0) {
@@ -24200,8 +24647,7 @@ const executeSteps = async (executionSteps, {
24200
24647
  completedExecutionLogMerging,
24201
24648
  completedExecutionLogAbbreviation,
24202
24649
  rootDirectoryUrl,
24203
- devServerOrigin,
24204
- sourceDirectoryUrl,
24650
+ webServer,
24205
24651
  keepRunning,
24206
24652
  defaultMsAllocatedPerExecution,
24207
24653
  maxExecutionsInParallel,
@@ -24280,8 +24726,7 @@ const executeSteps = async (executionSteps, {
24280
24726
  }
24281
24727
  let runtimeParams = {
24282
24728
  rootDirectoryUrl,
24283
- devServerOrigin,
24284
- sourceDirectoryUrl,
24729
+ webServer,
24285
24730
  coverageEnabled,
24286
24731
  coverageConfig,
24287
24732
  coverageMethodForBrowsers,
@@ -24425,7 +24870,7 @@ const executeSteps = async (executionSteps, {
24425
24870
  global.gc();
24426
24871
  }
24427
24872
  if (executionLogsEnabled) {
24428
- let log = createExecutionLog(afterExecutionInfo, {
24873
+ const log = createExecutionLog(afterExecutionInfo, {
24429
24874
  completedExecutionLogAbbreviation,
24430
24875
  counters,
24431
24876
  logRuntime,
@@ -24437,18 +24882,6 @@ const executeSteps = async (executionSteps, {
24437
24882
  memoryHeap: memoryUsage().heapUsed
24438
24883
  } : {})
24439
24884
  });
24440
- log = `${log}
24441
-
24442
- `;
24443
- const {
24444
- columns = 80
24445
- } = process.stdout;
24446
- log = wrapAnsi(log, columns, {
24447
- trim: false,
24448
- hard: true,
24449
- wordWrap: false
24450
- });
24451
-
24452
24885
  // replace spinner with this execution result
24453
24886
  if (spinner) spinner.stop();
24454
24887
  executionLog.write(log);
@@ -24466,7 +24899,12 @@ const executeSteps = async (executionSteps, {
24466
24899
  });
24467
24900
  }
24468
24901
  }
24469
- if (failFast && executionResult.status !== "completed" && counters.done < counters.total) {
24902
+ const isLastExecutionLog = executionIndex === executionSteps.length - 1;
24903
+ const cancelRemaining = failFast && executionResult.status !== "completed" && counters.done < counters.total;
24904
+ if (isLastExecutionLog) {
24905
+ executionLog.write("\n");
24906
+ }
24907
+ if (cancelRemaining) {
24470
24908
  logger.info(`"failFast" enabled -> cancel remaining executions`);
24471
24909
  failFastAbortController.abort();
24472
24910
  }
@@ -24571,7 +25009,7 @@ const executeInParallel = async ({
24571
25009
  * Execute a list of files and log how it goes.
24572
25010
  * @param {Object} testPlanParameters
24573
25011
  * @param {string|url} testPlanParameters.rootDirectoryUrl Directory containing test files;
24574
- * @param {string|url} [testPlanParameters.devServerOrigin=undefined] Jsenv dev server origin; required when executing test on browsers
25012
+ * @param {Object} [testPlanParameters.webServer] Web server info; required when executing test on browsers
24575
25013
  * @param {Object} testPlanParameters.testPlan Object associating files with runtimes where they will be executed
24576
25014
  * @param {boolean} [testPlanParameters.completedExecutionLogAbbreviation=false] Abbreviate completed execution information to shorten terminal output
24577
25015
  * @param {boolean} [testPlanParameters.completedExecutionLogMerging=false] Merge completed execution logs to shorten terminal output
@@ -24598,8 +25036,7 @@ const executeTestPlan = async ({
24598
25036
  completedExecutionLogAbbreviation = false,
24599
25037
  completedExecutionLogMerging = false,
24600
25038
  rootDirectoryUrl,
24601
- devServerModuleUrl,
24602
- devServerOrigin,
25039
+ webServer,
24603
25040
  testPlan,
24604
25041
  updateProcessExitCode = true,
24605
25042
  maxExecutionsInParallel = 1,
@@ -24615,7 +25052,10 @@ const executeTestPlan = async ({
24615
25052
  gcBetweenExecutions = logMemoryHeapUsage,
24616
25053
  coverageEnabled = process.argv.includes("--coverage"),
24617
25054
  coverageConfig = {
24618
- "./**/src/**": true,
25055
+ "file:///**/.*": false,
25056
+ "file:///**/.*/": false,
25057
+ "file:///**/node_modules/": false,
25058
+ "./**/src/": true,
24619
25059
  "./**/tests/": false,
24620
25060
  "./**/*.test.html": false,
24621
25061
  "./**/*.test.js": false,
@@ -24641,8 +25081,6 @@ const executeTestPlan = async ({
24641
25081
  }) => {
24642
25082
  let someNeedsServer = false;
24643
25083
  let someNodeRuntime = false;
24644
- let stopDevServerNeeded = false;
24645
- let sourceDirectoryUrl;
24646
25084
  const runtimes = {};
24647
25085
  // param validation
24648
25086
  {
@@ -24677,34 +25115,7 @@ const executeTestPlan = async ({
24677
25115
  });
24678
25116
  });
24679
25117
  if (someNeedsServer) {
24680
- if (!devServerOrigin) {
24681
- throw new TypeError(`devServerOrigin is required when running tests on browser(s)`);
24682
- }
24683
- let devServerStarted = await pingServer(devServerOrigin);
24684
- if (!devServerStarted) {
24685
- if (!devServerModuleUrl) {
24686
- throw new TypeError(`devServerModuleUrl is required when dev server is not started in order to run tests on browser(s)`);
24687
- }
24688
- try {
24689
- process.env.IMPORTED_BY_TEST_PLAN = "1";
24690
- await import(devServerModuleUrl);
24691
- delete process.env.IMPORTED_BY_TEST_PLAN;
24692
- } catch (e) {
24693
- if (e.code === "ERR_MODULE_NOT_FOUND") {
24694
- throw new Error(`Cannot find file responsible to start dev server at "${devServerModuleUrl}"`);
24695
- }
24696
- throw e;
24697
- }
24698
- devServerStarted = await pingServer(devServerOrigin);
24699
- if (!devServerStarted) {
24700
- throw new Error(`dev server not started after importing "${devServerModuleUrl}", ensure this module file is starting a server at "${devServerOrigin}"`);
24701
- }
24702
- stopDevServerNeeded = true;
24703
- }
24704
- const devServerParams = await basicFetch(`${devServerOrigin}/__server_params__.json`, {
24705
- rejectUnauthorized: false
24706
- });
24707
- sourceDirectoryUrl = devServerParams.sourceDirectoryUrl;
25118
+ await assertAndNormalizeWebServer(webServer);
24708
25119
  }
24709
25120
  if (coverageEnabled) {
24710
25121
  if (typeof coverageConfig !== "object") {
@@ -24782,6 +25193,8 @@ const executeTestPlan = async ({
24782
25193
  }
24783
25194
  }
24784
25195
  testPlan = {
25196
+ "file:///**/node_modules/": null,
25197
+ "**/*./": null,
24785
25198
  ...testPlan,
24786
25199
  "**/.jsenv/": null
24787
25200
  };
@@ -24806,8 +25219,7 @@ const executeTestPlan = async ({
24806
25219
  completedExecutionLogMerging,
24807
25220
  completedExecutionLogAbbreviation,
24808
25221
  rootDirectoryUrl,
24809
- devServerOrigin,
24810
- sourceDirectoryUrl,
25222
+ webServer,
24811
25223
  maxExecutionsInParallel,
24812
25224
  defaultMsAllocatedPerExecution,
24813
25225
  failFast,
@@ -24822,17 +25234,6 @@ const executeTestPlan = async ({
24822
25234
  coverageV8ConflictWarning,
24823
25235
  coverageTempDirectoryUrl
24824
25236
  });
24825
- if (stopDevServerNeeded) {
24826
- // we are expecting ECONNRESET because server will be stopped by the request
24827
- basicFetch(`${devServerOrigin}/__stop__`, {
24828
- rejectUnauthorized: false
24829
- }).catch(e => {
24830
- if (e.code === "ECONNRESET") {
24831
- return;
24832
- }
24833
- throw e;
24834
- });
24835
- }
24836
25237
  if (updateProcessExitCode && result.planSummary.counters.total !== result.planSummary.counters.completed) {
24837
25238
  process.exitCode = 1;
24838
25239
  }
@@ -24881,7 +25282,7 @@ const createRuntimeFromPlaywright = ({
24881
25282
  browserName,
24882
25283
  browserVersion,
24883
25284
  coveragePlaywrightAPIAvailable = false,
24884
- ignoreErrorHook = () => false,
25285
+ shouldIgnoreError = () => false,
24885
25286
  transformErrorHook = error => error,
24886
25287
  isolatedTab = false
24887
25288
  }) => {
@@ -24895,9 +25296,8 @@ const createRuntimeFromPlaywright = ({
24895
25296
  signal = new AbortController().signal,
24896
25297
  logger,
24897
25298
  rootDirectoryUrl,
25299
+ webServer,
24898
25300
  fileRelativeUrl,
24899
- devServerOrigin,
24900
- sourceDirectoryUrl,
24901
25301
  // measurePerformance,
24902
25302
  collectPerformance,
24903
25303
  coverageEnabled = false,
@@ -24912,6 +25312,19 @@ const createRuntimeFromPlaywright = ({
24912
25312
  playwrightLaunchOptions = {},
24913
25313
  ignoreHTTPSErrors = true
24914
25314
  }) => {
25315
+ const fileUrl = new URL(fileRelativeUrl, rootDirectoryUrl).href;
25316
+ if (!urlIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
25317
+ throw new Error(`Cannot execute file that is outside web server root directory
25318
+ --- file ---
25319
+ ${fileUrl}
25320
+ --- web server root directory url ---
25321
+ ${webServer.rootDirectoryUrl}`);
25322
+ }
25323
+ const fileServerUrl = moveUrl({
25324
+ url: fileUrl,
25325
+ from: webServer.rootDirectoryUrl,
25326
+ to: `${webServer.origin}/`
25327
+ });
24915
25328
  const cleanupCallbackList = createCallbackListNotifiedOnce();
24916
25329
  const cleanup = memoize(async reason => {
24917
25330
  await cleanupCallbackList.notify({
@@ -24975,6 +25388,13 @@ const createRuntimeFromPlaywright = ({
24975
25388
  } : {})
24976
25389
  }
24977
25390
  });
25391
+ if (!webServer.isJsenvDevServer) {
25392
+ await initJsExecutionMiddleware(page, {
25393
+ webServer,
25394
+ fileUrl,
25395
+ fileServerUrl
25396
+ });
25397
+ }
24978
25398
  const closePage = async () => {
24979
25399
  try {
24980
25400
  await page.close();
@@ -25003,8 +25423,8 @@ const createRuntimeFromPlaywright = ({
25003
25423
  const v8CoveragesWithFsUrls = v8CoveragesWithWebUrls.map(v8CoveragesWithWebUrl => {
25004
25424
  const fsUrl = moveUrl({
25005
25425
  url: v8CoveragesWithWebUrl.url,
25006
- from: `${devServerOrigin}/`,
25007
- to: sourceDirectoryUrl
25426
+ from: `${webServer.origin}/`,
25427
+ to: webServer.rootDirectoryUrl
25008
25428
  });
25009
25429
  return {
25010
25430
  ...v8CoveragesWithWebUrl,
@@ -25066,19 +25486,7 @@ const createRuntimeFromPlaywright = ({
25066
25486
  result.performance = performance;
25067
25487
  });
25068
25488
  }
25069
- const fileUrl = new URL(fileRelativeUrl, rootDirectoryUrl).href;
25070
- if (!urlIsInsideOf(fileUrl, sourceDirectoryUrl)) {
25071
- throw new Error(`Cannot execute file that is outside source directory
25072
- --- file ---
25073
- ${fileUrl}
25074
- --- source directory ---
25075
- ${sourceDirectoryUrl}`);
25076
- }
25077
- const fileDevServerUrl = moveUrl({
25078
- url: fileUrl,
25079
- from: sourceDirectoryUrl,
25080
- to: `${devServerOrigin}/`
25081
- });
25489
+
25082
25490
  // https://github.com/GoogleChrome/puppeteer/blob/v1.4.0/docs/api.md#event-console
25083
25491
  const removeConsoleListener = registerEvent({
25084
25492
  object: page,
@@ -25106,7 +25514,7 @@ ${sourceDirectoryUrl}`);
25106
25514
  object: page,
25107
25515
  eventType: "error",
25108
25516
  callback: error => {
25109
- if (ignoreErrorHook(error)) {
25517
+ if (shouldIgnoreError(error, "error")) {
25110
25518
  return;
25111
25519
  }
25112
25520
  cb(transformErrorHook(error));
@@ -25119,7 +25527,10 @@ ${sourceDirectoryUrl}`);
25119
25527
  // object: page,
25120
25528
  // eventType: "pageerror",
25121
25529
  // callback: (error) => {
25122
- // if (ignoreErrorHook(error)) {
25530
+ // if (
25531
+ // webServer.isJsenvDevServer ||
25532
+ // shouldIgnoreError(error, "pageerror")
25533
+ // ) {
25123
25534
  // return
25124
25535
  // }
25125
25536
  // result.errors.push(transformErrorHook(error))
@@ -25161,16 +25572,28 @@ ${sourceDirectoryUrl}`);
25161
25572
  },
25162
25573
  response: async cb => {
25163
25574
  try {
25164
- await page.goto(fileDevServerUrl, {
25575
+ await page.goto(fileServerUrl, {
25165
25576
  timeout: 0
25166
25577
  });
25167
25578
  const returnValue = await page.evaluate( /* eslint-disable no-undef */
25168
25579
  /* istanbul ignore next */
25169
- () => {
25580
+ async () => {
25581
+ let startTime;
25582
+ try {
25583
+ startTime = window.performance.timing.navigationStart;
25584
+ } catch (e) {
25585
+ startTime = Date.now();
25586
+ }
25170
25587
  if (!window.__supervisor__) {
25171
- throw new Error(`window.__supervisor__ not found`);
25588
+ throw new Error("window.__supervisor__ is undefined");
25172
25589
  }
25173
- return window.__supervisor__.getDocumentExecutionResult();
25590
+ const executionResultFromJsenvSupervisor = await window.__supervisor__.getDocumentExecutionResult();
25591
+ return {
25592
+ type: "window_supervisor",
25593
+ startTime,
25594
+ endTime: Date.now(),
25595
+ executionResults: executionResultFromJsenvSupervisor.executionResults
25596
+ };
25174
25597
  }
25175
25598
  /* eslint-enable no-undef */);
25176
25599
 
@@ -25193,12 +25616,18 @@ ${sourceDirectoryUrl}`);
25193
25616
  result.errors.push(error);
25194
25617
  return;
25195
25618
  }
25619
+ if (winner.name === "pageerror") {
25620
+ let error = winner.data;
25621
+ result.status = "failed";
25622
+ result.errors.push(error);
25623
+ return;
25624
+ }
25196
25625
  if (winner.name === "closed") {
25197
25626
  result.status = "failed";
25198
25627
  result.errors.push(isBrowserDedicatedToExecution ? new Error(`browser disconnected during execution`) : new Error(`page closed during execution`));
25199
25628
  return;
25200
25629
  }
25201
- // winner.name = 'response'
25630
+ // winner.name === "response"
25202
25631
  const {
25203
25632
  executionResults
25204
25633
  } = winner.data;
@@ -25208,10 +25637,17 @@ ${sourceDirectoryUrl}`);
25208
25637
  const executionResult = executionResults[key];
25209
25638
  if (executionResult.status === "failed") {
25210
25639
  result.status = "failed";
25211
- result.errors.push({
25212
- ...executionResult.exception,
25213
- stack: executionResult.exception.text
25214
- });
25640
+ if (executionResult.exception) {
25641
+ result.errors.push({
25642
+ ...executionResult.exception,
25643
+ stack: executionResult.exception.text
25644
+ });
25645
+ } else {
25646
+ result.errors.push({
25647
+ ...executionResult.error,
25648
+ stack: executionResult.error.stack
25649
+ });
25650
+ }
25215
25651
  }
25216
25652
  });
25217
25653
  };
@@ -25240,7 +25676,7 @@ ${sourceDirectoryUrl}`);
25240
25676
  browserName,
25241
25677
  browserVersion,
25242
25678
  coveragePlaywrightAPIAvailable,
25243
- ignoreErrorHook,
25679
+ shouldIgnoreError,
25244
25680
  transformErrorHook,
25245
25681
  isolatedTab: true
25246
25682
  });
@@ -25358,6 +25794,106 @@ const extractTextFromConsoleMessage = consoleMessage => {
25358
25794
  // return text
25359
25795
  };
25360
25796
 
25797
+ const initJsExecutionMiddleware = async (page, {
25798
+ webServer,
25799
+ fileUrl,
25800
+ fileServerUrl
25801
+ }) => {
25802
+ const inlineScriptContents = new Map();
25803
+ const interceptHtmlToExecute = async ({
25804
+ route
25805
+ }) => {
25806
+ // Fetch original response.
25807
+ const response = await route.fetch();
25808
+ // Add a prefix to the title.
25809
+ const originalBody = await response.text();
25810
+ const injectionResult = await injectSupervisorIntoHTML({
25811
+ content: originalBody,
25812
+ url: fileUrl
25813
+ }, {
25814
+ supervisorScriptSrc: `/@fs/${supervisorFileUrl$1.slice("file:///".length)}`,
25815
+ supervisorOptions: {},
25816
+ inlineAsRemote: true,
25817
+ webServer,
25818
+ onInlineScript: ({
25819
+ src,
25820
+ textContent
25821
+ }) => {
25822
+ const inlineScriptWebUrl = new URL(src, `${webServer.origin}/`).href;
25823
+ inlineScriptContents.set(inlineScriptWebUrl, textContent);
25824
+ }
25825
+ });
25826
+ route.fulfill({
25827
+ response,
25828
+ body: injectionResult.content,
25829
+ headers: {
25830
+ ...response.headers(),
25831
+ "content-length": Buffer.byteLength(injectionResult.content)
25832
+ }
25833
+ });
25834
+ };
25835
+ const interceptInlineScript = ({
25836
+ url,
25837
+ route
25838
+ }) => {
25839
+ const inlineScriptContent = inlineScriptContents.get(url);
25840
+ route.fulfill({
25841
+ status: 200,
25842
+ body: inlineScriptContent,
25843
+ headers: {
25844
+ "content-type": "text/javascript",
25845
+ "content-length": Buffer.byteLength(inlineScriptContent)
25846
+ }
25847
+ });
25848
+ };
25849
+ const interceptFileSystemUrl = ({
25850
+ url,
25851
+ route
25852
+ }) => {
25853
+ const relativeUrl = url.slice(webServer.origin.length);
25854
+ const fsPath = relativeUrl.slice("/@fs/".length);
25855
+ const fsUrl = `file:///${fsPath}`;
25856
+ const fileContent = readFileSync$1(new URL(fsUrl), "utf8");
25857
+ route.fulfill({
25858
+ status: 200,
25859
+ body: fileContent,
25860
+ headers: {
25861
+ "content-type": "text/javascript",
25862
+ "content-length": Buffer.byteLength(fileContent)
25863
+ }
25864
+ });
25865
+ };
25866
+ await page.route("**", async route => {
25867
+ const request = route.request();
25868
+ const url = request.url();
25869
+ if (url === fileServerUrl && urlToExtension$1(url) === ".html") {
25870
+ interceptHtmlToExecute({
25871
+ url,
25872
+ request,
25873
+ route
25874
+ });
25875
+ return;
25876
+ }
25877
+ if (inlineScriptContents.has(url)) {
25878
+ interceptInlineScript({
25879
+ url,
25880
+ request,
25881
+ route
25882
+ });
25883
+ return;
25884
+ }
25885
+ const fsServerUrl = new URL("/@fs/", webServer.origin);
25886
+ if (url.startsWith(fsServerUrl)) {
25887
+ interceptFileSystemUrl({
25888
+ url,
25889
+ request,
25890
+ route
25891
+ });
25892
+ return;
25893
+ }
25894
+ route.fallback();
25895
+ });
25896
+ };
25361
25897
  const registerEvent = ({
25362
25898
  object,
25363
25899
  eventType,
@@ -25391,7 +25927,7 @@ const webkit = createRuntimeFromPlaywright({
25391
25927
  // browserVersion will be set by "browser._initializer.version"
25392
25928
  // see also https://github.com/microsoft/playwright/releases
25393
25929
  browserVersion: "unset",
25394
- ignoreErrorHook: error => {
25930
+ shouldIgnoreError: error => {
25395
25931
  // we catch error during execution but safari throw unhandled rejection
25396
25932
  // in a non-deterministic way.
25397
25933
  // I suppose it's due to some race condition to decide if the promise is catched or not
@@ -26318,8 +26854,7 @@ const startBuildServer = async ({
26318
26854
  stopOnExit: false,
26319
26855
  stopOnSIGINT: false,
26320
26856
  stopOnInternalError: false,
26321
- // the worker should be kept alive by the parent otherwise
26322
- keepProcessAlive,
26857
+ keepProcessAlive: process.env.IMPORTED_BY_TEST_PLAN ? false : keepProcessAlive,
26323
26858
  logLevel: serverLogLevel,
26324
26859
  startLog: false,
26325
26860
  https,
@@ -26403,8 +26938,7 @@ const execute = async ({
26403
26938
  handleSIGINT = true,
26404
26939
  logLevel,
26405
26940
  rootDirectoryUrl,
26406
- sourceDirectoryUrl = rootDirectoryUrl,
26407
- devServerOrigin,
26941
+ webServer,
26408
26942
  fileRelativeUrl,
26409
26943
  allocatedMs,
26410
26944
  mirrorConsole = true,
@@ -26430,23 +26964,16 @@ const execute = async ({
26430
26964
  }, abort);
26431
26965
  });
26432
26966
  }
26967
+ if (runtime.type === "browser") {
26968
+ await assertAndNormalizeWebServer(webServer);
26969
+ }
26433
26970
  let resultTransformer = result => result;
26434
26971
  runtimeParams = {
26435
26972
  rootDirectoryUrl,
26436
- sourceDirectoryUrl,
26437
- devServerOrigin,
26973
+ webServer,
26438
26974
  fileRelativeUrl,
26439
26975
  ...runtimeParams
26440
26976
  };
26441
- if (runtime.type === "browser") {
26442
- if (!devServerOrigin) {
26443
- throw new TypeError(`devServerOrigin is required to execute file on a browser`);
26444
- }
26445
- const devServerStarted = await pingServer(devServerOrigin);
26446
- if (!devServerStarted) {
26447
- throw new Error(`no server listening at ${devServerOrigin}. It is required to execute file`);
26448
- }
26449
- }
26450
26977
  let result = await run({
26451
26978
  signal: executeOperation.signal,
26452
26979
  logger,