@jsenv/core 27.3.2 → 27.4.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 (38) hide show
  1. package/README.md +16 -4
  2. package/dist/controllable_child_process.mjs +1 -0
  3. package/dist/controllable_worker_thread.mjs +1 -0
  4. package/dist/js/event_source_client.js +45 -24
  5. package/dist/js/execute_using_dynamic_import.js +5 -3
  6. package/dist/js/html_supervisor_installer.js +368 -139
  7. package/dist/main.js +668 -471
  8. package/package.json +5 -6
  9. package/src/build/build.js +6 -4
  10. package/src/build/graph_utils.js +14 -11
  11. package/src/execute/run.js +29 -28
  12. package/src/execute/runtimes/browsers/from_playwright.js +90 -92
  13. package/src/execute/runtimes/node/execute_using_dynamic_import.js +8 -2
  14. package/src/execute/runtimes/node/node_child_process.js +2 -0
  15. package/src/execute/runtimes/node/node_worker_thread.js +11 -6
  16. package/src/helpers/event_source/event_source.js +38 -17
  17. package/src/omega/errors.js +41 -9
  18. package/src/omega/kitchen.js +35 -19
  19. package/src/omega/omega_server.js +54 -1
  20. package/src/omega/server/file_service.js +30 -3
  21. package/src/omega/url_graph/url_graph_report.js +2 -4
  22. package/src/omega/url_graph.js +29 -16
  23. package/src/plugins/autoreload/dev_sse/client/event_source_client.js +8 -8
  24. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +160 -172
  25. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +0 -4
  26. package/src/plugins/bundling/js_module/bundle_js_module.js +0 -1
  27. package/src/plugins/html_supervisor/client/error_in_document.js +268 -121
  28. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +47 -5
  29. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +37 -12
  30. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +1 -2
  31. package/src/plugins/plugins.js +0 -2
  32. package/src/plugins/url_analysis/js/js_urls.js +0 -9
  33. package/src/plugins/url_analysis/jsenv_plugin_url_analysis.js +3 -1
  34. package/src/test/coverage/report_to_coverage.js +16 -11
  35. package/src/test/execute_plan.js +3 -2
  36. package/src/test/execute_test_plan.js +3 -1
  37. package/src/test/logs_file_execution.js +60 -27
  38. package/src/test/logs_file_execution.test.mjs +41 -0
@@ -10,109 +10,335 @@ const unevalException = value => {
10
10
  });
11
11
  };
12
12
 
13
- const displayErrorInDocument = error => {
14
- const title = "An error occured";
15
- let theme = error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark";
16
- let message = errorToHTML(error);
17
- const css = `
18
- .jsenv-console {
19
- background: rgba(0, 0, 0, 0.95);
20
- position: absolute;
21
- top: 0;
22
- left: 0;
23
- width: 100%;
24
- height: 100%;
25
- display: flex;
26
- flex-direction: column;
27
- align-items: center;
28
- z-index: 1000;
29
- box-sizing: border-box;
30
- padding: 1em;
31
- }
13
+ const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay";
14
+ const displayErrorInDocument = (error, {
15
+ rootDirectoryUrl,
16
+ url,
17
+ line,
18
+ column
19
+ }) => {
20
+ document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
21
+ node.parentNode.removeChild(node);
22
+ });
23
+ const {
24
+ theme,
25
+ title,
26
+ message,
27
+ stack
28
+ } = errorToHTML(error, {
29
+ url,
30
+ line,
31
+ column
32
+ });
33
+ const jsenvErrorOverlay = new JsenvErrorOverlay({
34
+ theme,
35
+ title,
36
+ stack: stack ? `${replaceLinks(message, {
37
+ rootDirectoryUrl
38
+ })}\n${replaceLinks(stack, {
39
+ rootDirectoryUrl
40
+ })}` : replaceLinks(message, {
41
+ rootDirectoryUrl
42
+ })
43
+ });
44
+ document.body.appendChild(jsenvErrorOverlay);
45
+ };
32
46
 
33
- .jsenv-console h1 {
34
- color: red;
35
- display: flex;
36
- align-items: center;
37
- }
47
+ class JsenvErrorOverlay extends HTMLElement {
48
+ constructor({
49
+ title,
50
+ stack,
51
+ theme = "dark"
52
+ }) {
53
+ super();
54
+ this.root = this.attachShadow({
55
+ mode: "open"
56
+ });
57
+ this.root.innerHTML = overlayHtml;
58
+ this.root.querySelector(".overlay").setAttribute("data-theme", theme);
59
+ this.root.querySelector(".title").innerHTML = title;
60
+ this.root.querySelector(".stack").innerHTML = stack;
61
+
62
+ this.root.querySelector(".backdrop").onclick = () => {
63
+ if (!this.parentNode) {
64
+ // not in document anymore
65
+ return;
66
+ }
38
67
 
39
- #button-close-jsenv-console {
40
- margin-left: 10px;
41
- }
68
+ this.root.querySelector(".backdrop").onclick = null;
69
+ this.parentNode.removeChild(this);
70
+ };
71
+ }
42
72
 
43
- .jsenv-console pre {
44
- overflow: auto;
45
- max-width: 70em;
46
- /* avoid scrollbar to hide the text behind it */
47
- padding: 20px;
48
- }
73
+ }
74
+
75
+ if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
76
+ customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
77
+ }
78
+
79
+ const overlayHtml = `
80
+ <style>
81
+ :host {
82
+ position: fixed;
83
+ z-index: 99999;
84
+ top: 0;
85
+ left: 0;
86
+ width: 100%;
87
+ height: 100%;
88
+ overflow-y: scroll;
89
+ margin: 0;
90
+ background: rgba(0, 0, 0, 0.66);
91
+ }
92
+
93
+ .backdrop {
94
+ position: absolute;
95
+ left: 0;
96
+ right: 0;
97
+ top: 0;
98
+ bottom: 0;
99
+ }
100
+
101
+ .overlay {
102
+ position: relative;
103
+ background: rgba(0, 0, 0, 0.95);
104
+ width: 800px;
105
+ margin: 30px auto;
106
+ padding: 25px 40px;
107
+ padding-top: 0;
108
+ overflow: hidden; /* for h1 margins */
109
+ border-radius: 4px 8px;
110
+ box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
111
+ box-sizing: border-box;
112
+ font-family: monospace;
113
+ direction: ltr;
114
+ }
115
+
116
+ h1 {
117
+ color: red;
118
+ text-align: center;
119
+ }
120
+
121
+ pre {
122
+ overflow: auto;
123
+ max-width: 100%;
124
+ /* padding is nice + prevents scrollbar from hiding the text behind it */
125
+ /* does not work nicely on firefox though https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */
126
+ padding: 20px;
127
+ }
128
+
129
+ .tip {
130
+ border-top: 1px solid #999;
131
+ padding-top: 12px;
132
+ }
133
+
134
+ [data-theme="dark"] {
135
+ color: #999;
136
+ }
137
+ [data-theme="dark"] pre {
138
+ background: #111;
139
+ border: 1px solid #333;
140
+ color: #eee;
141
+ }
142
+
143
+ [data-theme="light"] {
144
+ color: #EEEEEE;
145
+ }
146
+ [data-theme="light"] pre {
147
+ background: #1E1E1E;
148
+ border: 1px solid white;
149
+ color: #EEEEEE;
150
+ }
151
+
152
+ pre a {
153
+ color: inherit;
154
+ }
155
+ </style>
156
+ <div class="backdrop"></div>
157
+ <div class="overlay">
158
+ <h1 class="title"></h1>
159
+ <pre class="stack"></pre>
160
+ <div class="tip">Click outside to close.</div>
161
+ </div>
162
+ `;
163
+
164
+ const parseErrorInfo = error => {
165
+ if (error === undefined) {
166
+ return {
167
+ message: "undefined"
168
+ };
169
+ }
49
170
 
50
- .jsenv-console pre[data-theme="dark"] {
51
- background: #111;
52
- border: 1px solid #333;
53
- color: #eee;
54
- }
171
+ if (error === null) {
172
+ return {
173
+ message: "null"
174
+ };
175
+ }
55
176
 
56
- .jsenv-console pre[data-theme="light"] {
57
- background: #1E1E1E;
58
- border: 1px solid white;
59
- color: #EEEEEE;
177
+ if (typeof error === "string") {
178
+ return {
179
+ message: error
180
+ };
181
+ }
182
+
183
+ if (error instanceof Error) {
184
+ if (error.name === "SyntaxError") {
185
+ return {
186
+ message: error.message
187
+ };
60
188
  }
61
189
 
62
- .jsenv-console pre a {
63
- color: inherit;
190
+ if (error.cause && error.cause.code === "PARSE_ERROR") {
191
+ if (error.messageHTML) {
192
+ return {
193
+ message: error.messageHTML
194
+ };
195
+ }
196
+
197
+ return {
198
+ message: error.message
199
+ };
200
+ } // stackTrace formatted by V8
201
+
202
+
203
+ if (Error.captureStackTrace) {
204
+ return {
205
+ message: error.message,
206
+ stack: getErrorStackWithoutErrorMessage(error)
207
+ };
64
208
  }
65
- `;
66
- const html = `
67
- <style type="text/css">${css}></style>
68
- <div class="jsenv-console">
69
- <h1>${title} <button id="button-close-jsenv-console">X</button></h1>
70
- <pre data-theme="${theme}">${message}</pre>
71
- </div>
72
- `;
73
- const removeJsenvConsole = appendHMTLInside(html, document.body);
74
-
75
- document.querySelector("#button-close-jsenv-console").onclick = () => {
76
- removeJsenvConsole();
209
+
210
+ return {
211
+ message: error.message,
212
+ stack: error.stack ? ` ${error.stack}` : null
213
+ };
214
+ }
215
+
216
+ if (typeof error === "object") {
217
+ return error;
218
+ }
219
+
220
+ return {
221
+ message: JSON.stringify(error)
77
222
  };
78
223
  };
79
224
 
80
- const escapeHtml = string => {
81
- return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
82
- };
225
+ const getErrorStackWithoutErrorMessage = error => {
226
+ let stack = error.stack;
227
+ const messageInStack = `${error.name}: ${error.message}`;
228
+
229
+ if (stack.startsWith(messageInStack)) {
230
+ stack = stack.slice(messageInStack.length);
231
+ }
83
232
 
84
- const errorToHTML = error => {
85
- let html;
233
+ const nextLineIndex = stack.indexOf("\n");
86
234
 
87
- if (error && error instanceof Error) {
88
- if (error.cause && error.cause.code === "PARSE_ERROR") {
89
- html = error.messageHTML || escapeHtml(error.message);
90
- } // stackTrace formatted by V8
91
- else if (Error.captureStackTrace) {
92
- html = escapeHtml(error.stack);
93
- } else {
94
- // other stack trace such as firefox do not contain error.message
95
- html = escapeHtml(`${error.message}
96
- ${error.stack}`);
235
+ if (nextLineIndex > -1) {
236
+ stack = stack.slice(nextLineIndex + 1);
237
+ }
238
+
239
+ return stack;
240
+ };
241
+
242
+ const errorToHTML = (error, {
243
+ url,
244
+ line,
245
+ column
246
+ }) => {
247
+ let {
248
+ message,
249
+ stack
250
+ } = parseErrorInfo(error);
251
+
252
+ if (url) {
253
+ if (!stack || error && error.name === "SyntaxError") {
254
+ stack = ` at ${appendLineAndColumn(url, {
255
+ line,
256
+ column
257
+ })}`;
97
258
  }
98
- } else if (typeof error === "string") {
99
- html = error;
100
- } else if (error === undefined) {
101
- html = "undefined";
102
- } else {
103
- html = JSON.stringify(error);
104
259
  }
105
260
 
106
- const htmlWithCorrectLineBreaks = html.replace(/\n/g, "\n");
107
- const htmlWithLinks = stringToStringWithLink(htmlWithCorrectLineBreaks, {
108
- transform: url => {
109
- return {
110
- href: url,
111
- text: url
261
+ return {
262
+ theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
263
+ title: "An error occured",
264
+ message,
265
+ stack
266
+ };
267
+ };
268
+
269
+ const replaceLinks = (string, {
270
+ rootDirectoryUrl
271
+ }) => {
272
+ // normalize line breaks
273
+ string = string.replace(/\n/g, "\n");
274
+ string = escapeHtml(string); // render links
275
+
276
+ string = stringToStringWithLink(string, {
277
+ transform: (url, {
278
+ line,
279
+ column
280
+ }) => {
281
+ const urlObject = new URL(url);
282
+
283
+ const onFileUrl = fileUrlObject => {
284
+ const atFsIndex = fileUrlObject.pathname.indexOf("/@fs/");
285
+ let fileUrl;
286
+
287
+ if (atFsIndex > -1) {
288
+ const afterAtFs = fileUrlObject.pathname.slice(atFsIndex + "/@fs/".length);
289
+ fileUrl = new URL(afterAtFs, "file:///").href;
290
+ } else {
291
+ fileUrl = fileUrlObject.href;
292
+ }
293
+
294
+ fileUrl = appendLineAndColumn(fileUrl, {
295
+ line,
296
+ column
297
+ });
298
+ return link({
299
+ href: `javascript:window.fetch('/__open_in_editor__/${fileUrl}')`,
300
+ text: fileUrl
301
+ });
112
302
  };
303
+
304
+ if (urlObject.origin === window.origin) {
305
+ const fileUrlObject = new URL(`${urlObject.pathname.slice(1)}${urlObject.search}`, rootDirectoryUrl);
306
+ return onFileUrl(fileUrlObject);
307
+ }
308
+
309
+ if (urlObject.href.startsWith("file:")) {
310
+ return onFileUrl(urlObject);
311
+ }
312
+
313
+ return link({
314
+ href: url,
315
+ text: appendLineAndColumn(url, {
316
+ line,
317
+ column
318
+ })
319
+ });
113
320
  }
114
321
  });
115
- return htmlWithLinks;
322
+ return string;
323
+ };
324
+
325
+ const escapeHtml = string => {
326
+ return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
327
+ };
328
+
329
+ const appendLineAndColumn = (url, {
330
+ line,
331
+ column
332
+ }) => {
333
+ if (line !== undefined && column !== undefined) {
334
+ return `${url}:${line}:${column}`;
335
+ }
336
+
337
+ if (line !== undefined) {
338
+ return `${url}:${line}`;
339
+ }
340
+
341
+ return url;
116
342
  }; // `Error: yo
117
343
  // at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
118
344
  // at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
@@ -147,14 +373,9 @@ const stringToStringWithLink = (source, {
147
373
  const lineAndColumnString = lineAndColumMatch[0];
148
374
  const lineNumber = lineAndColumMatch[1];
149
375
  const columnNumber = lineAndColumMatch[2];
150
- const url = match.slice(0, -lineAndColumnString.length);
151
- const {
152
- href,
153
- text
154
- } = transform(url);
155
- linkHTML = link({
156
- href,
157
- text: `${text}:${lineNumber}:${columnNumber}`
376
+ linkHTML = transform(match.slice(0, -lineAndColumnString.length), {
377
+ line: lineNumber,
378
+ column: columnNumber
158
379
  });
159
380
  } else {
160
381
  const linePattern = /:([0-9]+)$/;
@@ -163,25 +384,11 @@ const stringToStringWithLink = (source, {
163
384
  if (lineMatch) {
164
385
  const lineString = lineMatch[0];
165
386
  const lineNumber = lineMatch[1];
166
- const url = match.slice(0, -lineString.length);
167
- const {
168
- href,
169
- text
170
- } = transform(url);
171
- linkHTML = link({
172
- href,
173
- text: `${text}:${lineNumber}`
387
+ linkHTML = transform(match.slice(0, -lineString.length), {
388
+ line: lineNumber
174
389
  });
175
390
  } else {
176
- const url = match;
177
- const {
178
- href,
179
- text
180
- } = transform(url);
181
- linkHTML = link({
182
- href,
183
- text
184
- });
391
+ linkHTML = transform(match, {});
185
392
  }
186
393
  }
187
394
 
@@ -198,31 +405,6 @@ const link = ({
198
405
  text = href
199
406
  }) => `<a href="${href}">${text}</a>`;
200
407
 
201
- const appendHMTLInside = (html, parentNode) => {
202
- const temoraryParent = document.createElement("div");
203
- temoraryParent.innerHTML = html;
204
- return transferChildren(temoraryParent, parentNode);
205
- };
206
-
207
- const transferChildren = (fromNode, toNode) => {
208
- const childNodes = [].slice.call(fromNode.childNodes, 0);
209
- let i = 0;
210
-
211
- while (i < childNodes.length) {
212
- toNode.appendChild(childNodes[i]);
213
- i++;
214
- }
215
-
216
- return () => {
217
- let c = 0;
218
-
219
- while (c < childNodes.length) {
220
- fromNode.appendChild(childNodes[c]);
221
- c++;
222
- }
223
- };
224
- };
225
-
226
408
  const {
227
409
  Notification
228
410
  } = window;
@@ -252,7 +434,8 @@ const {
252
434
  } = window;
253
435
  const installHtmlSupervisor = ({
254
436
  logs,
255
- measurePerf
437
+ measurePerf,
438
+ rootDirectoryUrl
256
439
  }) => {
257
440
 
258
441
  const scriptExecutionResults = {};
@@ -289,14 +472,14 @@ const installHtmlSupervisor = ({
289
472
  const onExecutionError = (executionResult, {
290
473
  currentScript,
291
474
  errorExposureInNotification = false,
292
- errorExposureInDocument = true
475
+ errorExposureInDocument = false
293
476
  }) => {
294
477
  const error = executionResult.error;
295
478
 
296
479
  if (error && error.code === "NETWORK_FAILURE") {
297
480
  if (currentScript) {
298
- const errorEvent = new Event("error");
299
- currentScript.dispatchEvent(errorEvent);
481
+ const currentScriptErrorEvent = new Event("error");
482
+ currentScript.dispatchEvent(currentScriptErrorEvent);
300
483
  }
301
484
  } else if (typeof error === "object") {
302
485
  const globalErrorEvent = new Event("error");
@@ -312,7 +495,9 @@ const installHtmlSupervisor = ({
312
495
  }
313
496
 
314
497
  if (errorExposureInDocument) {
315
- displayErrorInDocument(error);
498
+ displayErrorInDocument(error, {
499
+ rootDirectoryUrl
500
+ });
316
501
  }
317
502
 
318
503
  executionResult.exceptionSource = unevalException(error);
@@ -467,6 +652,50 @@ const installHtmlSupervisor = ({
467
652
  copy.forEach(scriptToExecute => {
468
653
  __html_supervisor__.addScriptToExecute(scriptToExecute);
469
654
  });
655
+ window.addEventListener("error", errorEvent => {
656
+ if (!errorEvent.isTrusted) {
657
+ // ignore custom error event (not sent by browser)
658
+ return;
659
+ }
660
+
661
+ const {
662
+ error
663
+ } = errorEvent;
664
+ displayErrorInDocument(error, {
665
+ rootDirectoryUrl,
666
+ url: errorEvent.filename,
667
+ line: errorEvent.lineno,
668
+ column: errorEvent.colno
669
+ });
670
+ });
671
+
672
+ if (window.__jsenv_event_source_client__) {
673
+ const onServerErrorEvent = serverErrorEvent => {
674
+ const {
675
+ reason,
676
+ stack,
677
+ url,
678
+ line,
679
+ column,
680
+ contentFrame
681
+ } = JSON.parse(serverErrorEvent.data);
682
+ displayErrorInDocument({
683
+ message: reason,
684
+ stack: stack ? `${stack}\n\n${contentFrame}` : contentFrame
685
+ }, {
686
+ rootDirectoryUrl,
687
+ url,
688
+ line,
689
+ column
690
+ });
691
+ };
692
+
693
+ window.__jsenv_event_source_client__.addEventCallbacks({
694
+ file_not_found: onServerErrorEvent,
695
+ parse_error: onServerErrorEvent,
696
+ unexpected_error: onServerErrorEvent
697
+ });
698
+ }
470
699
  };
471
700
  const superviseScriptTypeModule = ({
472
701
  src,