@ms-cloudpack/app-server 0.19.4 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/lib/renderRoute/getErrorResponse.d.ts +20 -0
  2. package/lib/renderRoute/getErrorResponse.d.ts.map +1 -0
  3. package/lib/renderRoute/getErrorResponse.js +44 -0
  4. package/lib/renderRoute/getErrorResponse.js.map +1 -0
  5. package/lib/renderRoute/injectScripts.d.ts +8 -0
  6. package/lib/renderRoute/injectScripts.d.ts.map +1 -0
  7. package/lib/renderRoute/injectScripts.js +81 -0
  8. package/lib/renderRoute/injectScripts.js.map +1 -0
  9. package/lib/renderRoute/isHtmlAcceptable.d.ts +11 -0
  10. package/lib/renderRoute/isHtmlAcceptable.d.ts.map +1 -0
  11. package/lib/renderRoute/isHtmlAcceptable.js +26 -0
  12. package/lib/renderRoute/isHtmlAcceptable.js.map +1 -0
  13. package/lib/renderRoute/renderRoute.d.ts +1 -1
  14. package/lib/renderRoute/renderRoute.d.ts.map +1 -1
  15. package/lib/renderRoute/renderRoute.js +17 -175
  16. package/lib/renderRoute/renderRoute.js.map +1 -1
  17. package/lib/renderRoute/ssr/bundleServerEntry.d.ts +7 -0
  18. package/lib/renderRoute/ssr/bundleServerEntry.d.ts.map +1 -0
  19. package/lib/renderRoute/ssr/bundleServerEntry.js +54 -0
  20. package/lib/renderRoute/ssr/bundleServerEntry.js.map +1 -0
  21. package/lib/renderRoute/ssr/renderCustomScript.d.ts.map +1 -0
  22. package/lib/renderRoute/{renderCustomScript.js → ssr/renderCustomScript.js} +19 -10
  23. package/lib/renderRoute/ssr/renderCustomScript.js.map +1 -0
  24. package/lib/renderRoute/ssr/{importMapLoader.d.ts → worker/importMapLoader.d.ts} +2 -2
  25. package/lib/renderRoute/ssr/worker/importMapLoader.d.ts.map +1 -0
  26. package/lib/renderRoute/ssr/worker/importMapLoader.js.map +1 -0
  27. package/lib/renderRoute/ssr/worker/runServerEntry.d.ts +4 -0
  28. package/lib/renderRoute/ssr/worker/runServerEntry.d.ts.map +1 -0
  29. package/lib/renderRoute/ssr/worker/runServerEntry.js.map +1 -0
  30. package/lib/renderRoute/ssr/worker/workerEntry.js +1 -1
  31. package/lib/renderRoute/ssr/worker/workerEntry.js.map +1 -1
  32. package/package.json +9 -9
  33. package/lib/renderRoute/getHtmlErrorResponse.d.ts +0 -6
  34. package/lib/renderRoute/getHtmlErrorResponse.d.ts.map +0 -1
  35. package/lib/renderRoute/getHtmlErrorResponse.js +0 -22
  36. package/lib/renderRoute/getHtmlErrorResponse.js.map +0 -1
  37. package/lib/renderRoute/renderCustomScript.d.ts.map +0 -1
  38. package/lib/renderRoute/renderCustomScript.js.map +0 -1
  39. package/lib/renderRoute/ssr/importMapLoader.d.ts.map +0 -1
  40. package/lib/renderRoute/ssr/importMapLoader.js.map +0 -1
  41. package/lib/renderRoute/ssr/runServerEntry.d.ts +0 -4
  42. package/lib/renderRoute/ssr/runServerEntry.d.ts.map +0 -1
  43. package/lib/renderRoute/ssr/runServerEntry.js.map +0 -1
  44. /package/lib/renderRoute/{renderCustomScript.d.ts → ssr/renderCustomScript.d.ts} +0 -0
  45. /package/lib/renderRoute/ssr/{importMapLoader.js → worker/importMapLoader.js} +0 -0
  46. /package/lib/renderRoute/ssr/{runServerEntry.js → worker/runServerEntry.js} +0 -0
@@ -0,0 +1,20 @@
1
+ import type { ExpandedRenderFunctionResult, RenderFunctionOptions } from '@ms-cloudpack/common-types';
2
+ /**
3
+ * Log the error to the console and return an error response (500 status code).
4
+ *
5
+ * If it appears that the given request accepts HTML, it returns an HTML page,
6
+ * or plain text otherwise.
7
+ */
8
+ export declare function getErrorResponse(params: Pick<RenderFunctionOptions, 'req'> & {
9
+ message: string;
10
+ }): ExpandedRenderFunctionResult;
11
+ /**
12
+ * Fill in details of the message, log to console.error, and return an error response.
13
+ * `Route ${route.match} with (renderScript|serverEntry) "${scriptPath}" ${message}`
14
+ */
15
+ export declare function getCustomRenderErrorResponse(params: Pick<RenderFunctionOptions, 'req' | 'route'> & {
16
+ /** Error details (NOTE the format in the main function comment) */
17
+ message: string;
18
+ scriptPath: string | undefined;
19
+ }): ExpandedRenderFunctionResult;
20
+ //# sourceMappingURL=getErrorResponse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getErrorResponse.d.ts","sourceRoot":"","sources":["../../src/renderRoute/getErrorResponse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAKtG;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,GAAG;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB,GACA,4BAA4B,CA0B9B;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IACrD,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,GACA,4BAA4B,CAO9B"}
@@ -0,0 +1,44 @@
1
+ import { isHtmlAcceptable } from './isHtmlAcceptable.js';
2
+ const statusCode = 500;
3
+ /**
4
+ * Log the error to the console and return an error response (500 status code).
5
+ *
6
+ * If it appears that the given request accepts HTML, it returns an HTML page,
7
+ * or plain text otherwise.
8
+ */
9
+ export function getErrorResponse(params) {
10
+ const { message, req } = params;
11
+ console.error(`App server: request ${req.url} - ${message}`);
12
+ if (!isHtmlAcceptable(req)) {
13
+ // If HTML is not acceptable, return a plain text response
14
+ return {
15
+ content: message,
16
+ statusCode,
17
+ contentType: 'text/plain',
18
+ };
19
+ }
20
+ return {
21
+ content: `<!DOCTYPE html>
22
+ <html lang="en">
23
+ <head><title>Error</title></head>
24
+ <body>
25
+ <h1>Error</h1>
26
+ <pre>${message}</pre>
27
+ </body>
28
+ </html>`,
29
+ statusCode,
30
+ contentType: 'text/html',
31
+ };
32
+ }
33
+ /**
34
+ * Fill in details of the message, log to console.error, and return an error response.
35
+ * `Route ${route.match} with (renderScript|serverEntry) "${scriptPath}" ${message}`
36
+ */
37
+ export function getCustomRenderErrorResponse(params) {
38
+ const { message, req, route, scriptPath } = params;
39
+ const scriptType = route.serverEntry ? 'serverEntry' : 'renderScript';
40
+ const renderer = scriptPath ? `${scriptType} "${scriptPath}"` : 'the default renderer';
41
+ const content = `Route ${JSON.stringify(route.match)} with ${renderer} ${message}`;
42
+ return getErrorResponse({ req, message: content });
43
+ }
44
+ //# sourceMappingURL=getErrorResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getErrorResponse.js","sourceRoot":"","sources":["../../src/renderRoute/getErrorResponse.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAEC;IAED,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;IAEhC,OAAO,CAAC,KAAK,CAAC,uBAAuB,GAAG,CAAC,GAAG,MAAM,OAAO,EAAE,CAAC,CAAC;IAE7D,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,0DAA0D;QAC1D,OAAO;YACL,OAAO,EAAE,OAAO;YAChB,UAAU;YACV,WAAW,EAAE,YAAY;SAC1B,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE;;;;;SAKJ,OAAO;;QAER;QACJ,UAAU;QACV,WAAW,EAAE,WAAW;KACzB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAIC;IAED,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IACnD,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,cAAc,CAAC;IACtE,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,KAAK,UAAU,GAAG,CAAC,CAAC,CAAC,sBAAsB,CAAC;IACvF,MAAM,OAAO,GAAG,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,QAAQ,IAAI,OAAO,EAAE,CAAC;IAEnF,OAAO,gBAAgB,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACrD,CAAC","sourcesContent":["import type { ExpandedRenderFunctionResult, RenderFunctionOptions } from '@ms-cloudpack/common-types';\nimport { isHtmlAcceptable } from './isHtmlAcceptable.js';\n\nconst statusCode = 500;\n\n/**\n * Log the error to the console and return an error response (500 status code).\n *\n * If it appears that the given request accepts HTML, it returns an HTML page,\n * or plain text otherwise.\n */\nexport function getErrorResponse(\n params: Pick<RenderFunctionOptions, 'req'> & {\n message: string;\n },\n): ExpandedRenderFunctionResult {\n const { message, req } = params;\n\n console.error(`App server: request ${req.url} - ${message}`);\n\n if (!isHtmlAcceptable(req)) {\n // If HTML is not acceptable, return a plain text response\n return {\n content: message,\n statusCode,\n contentType: 'text/plain',\n };\n }\n\n return {\n content: `<!DOCTYPE html>\n<html lang=\"en\">\n<head><title>Error</title></head>\n<body>\n <h1>Error</h1>\n <pre>${message}</pre>\n</body>\n</html>`,\n statusCode,\n contentType: 'text/html',\n };\n}\n\n/**\n * Fill in details of the message, log to console.error, and return an error response.\n * `Route ${route.match} with (renderScript|serverEntry) \"${scriptPath}\" ${message}`\n */\nexport function getCustomRenderErrorResponse(\n params: Pick<RenderFunctionOptions, 'req' | 'route'> & {\n /** Error details (NOTE the format in the main function comment) */\n message: string;\n scriptPath: string | undefined;\n },\n): ExpandedRenderFunctionResult {\n const { message, req, route, scriptPath } = params;\n const scriptType = route.serverEntry ? 'serverEntry' : 'renderScript';\n const renderer = scriptPath ? `${scriptType} \"${scriptPath}\"` : 'the default renderer';\n const content = `Route ${JSON.stringify(route.match)} with ${renderer} ${message}`;\n\n return getErrorResponse({ req, message: content });\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { RenderFunctionOptions, ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';
2
+ /**
3
+ * Modify the HTML response by injecting the import map, inline scripts, and entry/overlay scripts.
4
+ *
5
+ * Note that this just logs an error rather than returning an error response if something fails, for now.
6
+ */
7
+ export declare function injectScripts(options: RenderFunctionOptions, result: ExpandedRenderFunctionResult): Promise<void>;
8
+ //# sourceMappingURL=injectScripts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectScripts.d.ts","sourceRoot":"","sources":["../../src/renderRoute/injectScripts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAItG;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,IAAI,CAAC,CAsDf"}
@@ -0,0 +1,81 @@
1
+ import { Window } from 'happy-dom';
2
+ import HTMLSerializer from 'happy-dom/lib/html-serializer/HTMLSerializer.js';
3
+ /**
4
+ * Modify the HTML response by injecting the import map, inline scripts, and entry/overlay scripts.
5
+ *
6
+ * Note that this just logs an error rather than returning an error response if something fails, for now.
7
+ */
8
+ export async function injectScripts(options, result) {
9
+ const { route, overlayScript, entryScripts, inlineScripts, importMap, baseUrl } = options;
10
+ const window = new Window({
11
+ url: baseUrl,
12
+ settings: {
13
+ disableJavaScriptFileLoading: true,
14
+ disableJavaScriptEvaluation: true,
15
+ disableCSSFileLoading: true,
16
+ disableIframePageLoading: true,
17
+ },
18
+ });
19
+ try {
20
+ // TODO: happy-dom is overkill for what we're doing here--a full browser-like environment isn't
21
+ // needed to just parse and modify the HTML. If it becomes a perf concern (less likely since
22
+ // HTML page rendering is probably just done once per page load), we could switch to
23
+ // a pure parser like parse5 or htmlparser2.
24
+ window.document.write(result.content);
25
+ }
26
+ catch (e) {
27
+ // Trying to write a test for the above, it seemed very permissive, but catch just in case
28
+ console.error(`Error parsing html response for rendering route "${JSON.stringify(route.match)}":\n${e.stack || e}`);
29
+ return;
30
+ }
31
+ try {
32
+ const document = window.document;
33
+ if (entryScripts || overlayScript) {
34
+ // Inject the import map at the top of the head, in case other scripts use it
35
+ addScript({ document, type: 'importmap', prepend: true, content: JSON.stringify(importMap, null, 2) });
36
+ }
37
+ if (inlineScripts) {
38
+ for (const inlineScript of inlineScripts) {
39
+ addScript({ document, content: inlineScript });
40
+ }
41
+ }
42
+ // inject the overlay and entry scripts
43
+ addScript({ document, url: overlayScript });
44
+ if (entryScripts) {
45
+ for (const entryScript of entryScripts) {
46
+ addScript({ document, url: entryScript });
47
+ }
48
+ }
49
+ const serializer = new HTMLSerializer();
50
+ result.content = serializer.serializeToString(document);
51
+ await window.happyDOM.close();
52
+ }
53
+ catch (e) {
54
+ console.error(`Error injecting scripts for route "${JSON.stringify(route.match)}":\n${e.stack || e}`);
55
+ }
56
+ }
57
+ /**
58
+ * Helper function to add a script to the document.
59
+ * No-op if neither `url` nor `content` is provided.
60
+ */
61
+ function addScript(params) {
62
+ const { document, type = 'module', prepend, url, content } = params;
63
+ if (!url && !content) {
64
+ return;
65
+ }
66
+ const script = document.createElement('script');
67
+ script.type = type;
68
+ if (url) {
69
+ script.src = url;
70
+ }
71
+ else if (content) {
72
+ script.innerHTML = content;
73
+ }
74
+ if (prepend) {
75
+ document.head.prepend(script);
76
+ }
77
+ else {
78
+ document.head.appendChild(script);
79
+ }
80
+ }
81
+ //# sourceMappingURL=injectScripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectScripts.js","sourceRoot":"","sources":["../../src/renderRoute/injectScripts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAiB,MAAM,WAAW,CAAC;AAClD,OAAO,cAAc,MAAM,iDAAiD,CAAC;AAE7E;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAA8B,EAC9B,MAAoC;IAEpC,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC1F,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE;YACR,4BAA4B,EAAE,IAAI;YAClC,2BAA2B,EAAE,IAAI;YACjC,qBAAqB,EAAE,IAAI;YAC3B,wBAAwB,EAAE,IAAI;SAC/B;KACF,CAAC,CAAC;IACH,IAAI,CAAC;QACH,+FAA+F;QAC/F,4FAA4F;QAC5F,oFAAoF;QACpF,4CAA4C;QAC5C,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,0FAA0F;QAC1F,OAAO,CAAC,KAAK,CACX,oDAAoD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,OAAQ,CAAW,CAAC,KAAK,IAAI,CAAC,EAAE,CAChH,CAAC;QACF,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAEjC,IAAI,YAAY,IAAI,aAAa,EAAE,CAAC;YAClC,6EAA6E;YAC7E,SAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QACzG,CAAC;QAED,IAAI,aAAa,EAAE,CAAC;YAClB,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;gBACzC,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QAED,uCAAuC;QACvC,SAAS,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC;QAE5C,IAAI,YAAY,EAAE,CAAC;YACjB,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;gBACvC,SAAS,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,cAAc,EAAE,CAAC;QACxC,MAAM,CAAC,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACxD,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IAChC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,sCAAsC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,OAAQ,CAAW,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;IACnH,CAAC;AACH,CAAC;AACD;;;GAGG;AACH,SAAS,SAAS,CAAC,MAMlB;IACC,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IACpE,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC;IACnB,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,MAAM,CAAC,SAAS,GAAG,OAAO,CAAC;IAC7B,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;AACH,CAAC","sourcesContent":["import type { RenderFunctionOptions, ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';\nimport { Window, type Document } from 'happy-dom';\nimport HTMLSerializer from 'happy-dom/lib/html-serializer/HTMLSerializer.js';\n\n/**\n * Modify the HTML response by injecting the import map, inline scripts, and entry/overlay scripts.\n *\n * Note that this just logs an error rather than returning an error response if something fails, for now.\n */\nexport async function injectScripts(\n options: RenderFunctionOptions,\n result: ExpandedRenderFunctionResult,\n): Promise<void> {\n const { route, overlayScript, entryScripts, inlineScripts, importMap, baseUrl } = options;\n const window = new Window({\n url: baseUrl,\n settings: {\n disableJavaScriptFileLoading: true,\n disableJavaScriptEvaluation: true,\n disableCSSFileLoading: true,\n disableIframePageLoading: true,\n },\n });\n try {\n // TODO: happy-dom is overkill for what we're doing here--a full browser-like environment isn't\n // needed to just parse and modify the HTML. If it becomes a perf concern (less likely since\n // HTML page rendering is probably just done once per page load), we could switch to\n // a pure parser like parse5 or htmlparser2.\n window.document.write(result.content);\n } catch (e) {\n // Trying to write a test for the above, it seemed very permissive, but catch just in case\n console.error(\n `Error parsing html response for rendering route \"${JSON.stringify(route.match)}\":\\n${(e as Error).stack || e}`,\n );\n return;\n }\n\n try {\n const document = window.document;\n\n if (entryScripts || overlayScript) {\n // Inject the import map at the top of the head, in case other scripts use it\n addScript({ document, type: 'importmap', prepend: true, content: JSON.stringify(importMap, null, 2) });\n }\n\n if (inlineScripts) {\n for (const inlineScript of inlineScripts) {\n addScript({ document, content: inlineScript });\n }\n }\n\n // inject the overlay and entry scripts\n addScript({ document, url: overlayScript });\n\n if (entryScripts) {\n for (const entryScript of entryScripts) {\n addScript({ document, url: entryScript });\n }\n }\n\n const serializer = new HTMLSerializer();\n result.content = serializer.serializeToString(document);\n await window.happyDOM.close();\n } catch (e) {\n console.error(`Error injecting scripts for route \"${JSON.stringify(route.match)}\":\\n${(e as Error).stack || e}`);\n }\n}\n/**\n * Helper function to add a script to the document.\n * No-op if neither `url` nor `content` is provided.\n */\nfunction addScript(params: {\n document: Document;\n type?: string;\n prepend?: boolean;\n url?: string;\n content?: string;\n}): void {\n const { document, type = 'module', prepend, url, content } = params;\n if (!url && !content) {\n return;\n }\n\n const script = document.createElement('script');\n script.type = type;\n if (url) {\n script.src = url;\n } else if (content) {\n script.innerHTML = content;\n }\n\n if (prepend) {\n document.head.prepend(script);\n } else {\n document.head.appendChild(script);\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import type { Request } from '@ms-cloudpack/common-types';
2
+ /**
3
+ * Determine whether HTML is (probably) an acceptable response type based on the request extension
4
+ * and/or headers. (We can't rely on `req.accepts()` because not all requests set that header.)
5
+ */
6
+ export declare function isHtmlAcceptable(req: Request): boolean;
7
+ /**
8
+ * Determine whether the given `Accept` or `Content-Type` header allows HTML.
9
+ */
10
+ export declare function isHtmlType(acceptOrContentType: string): boolean;
11
+ //# sourceMappingURL=isHtmlAcceptable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isHtmlAcceptable.d.ts","sourceRoot":"","sources":["../../src/renderRoute/isHtmlAcceptable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAI1D;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAMtD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,mBAAmB,EAAE,MAAM,GAAG,OAAO,CAS/D"}
@@ -0,0 +1,26 @@
1
+ import { makeUrl } from '@ms-cloudpack/path-string-parsing';
2
+ import path from 'path';
3
+ /**
4
+ * Determine whether HTML is (probably) an acceptable response type based on the request extension
5
+ * and/or headers. (We can't rely on `req.accepts()` because not all requests set that header.)
6
+ */
7
+ export function isHtmlAcceptable(req) {
8
+ // Get the request path and extension (the URL.pathname conversion gets rid of any query or hash)
9
+ const requestPath = makeUrl(req.url, 'http://example.com').pathname;
10
+ const requestExt = path.extname(requestPath);
11
+ return (!requestExt || requestExt.startsWith('.htm')) && !req.xhr && isHtmlType(req.headers.accept || '*/*');
12
+ }
13
+ /**
14
+ * Determine whether the given `Accept` or `Content-Type` header allows HTML.
15
+ */
16
+ export function isHtmlType(acceptOrContentType) {
17
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
18
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values
19
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
20
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
21
+ return acceptOrContentType
22
+ .split(',')
23
+ .map((s) => s.split(';')[0].trim())
24
+ .some((mime) => mime === '*/*' || /xml|x?html/i.test(mime) || /^(text|application)\/\*$/.test(mime));
25
+ }
26
+ //# sourceMappingURL=isHtmlAcceptable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isHtmlAcceptable.js","sourceRoot":"","sources":["../../src/renderRoute/isHtmlAcceptable.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAC;AAC5D,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,iGAAiG;IACjG,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC,QAAQ,CAAC;IACpE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAE7C,OAAO,CAAC,CAAC,UAAU,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;AAC/G,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,mBAA2B;IACpD,mEAAmE;IACnE,sGAAsG;IACtG,yEAAyE;IACzE,2FAA2F;IAC3F,OAAO,mBAAmB;SACvB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SAClC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,KAAK,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACzG,CAAC","sourcesContent":["import type { Request } from '@ms-cloudpack/common-types';\nimport { makeUrl } from '@ms-cloudpack/path-string-parsing';\nimport path from 'path';\n\n/**\n * Determine whether HTML is (probably) an acceptable response type based on the request extension\n * and/or headers. (We can't rely on `req.accepts()` because not all requests set that header.)\n */\nexport function isHtmlAcceptable(req: Request): boolean {\n // Get the request path and extension (the URL.pathname conversion gets rid of any query or hash)\n const requestPath = makeUrl(req.url, 'http://example.com').pathname;\n const requestExt = path.extname(requestPath);\n\n return (!requestExt || requestExt.startsWith('.htm')) && !req.xhr && isHtmlType(req.headers.accept || '*/*');\n}\n\n/**\n * Determine whether the given `Accept` or `Content-Type` header allows HTML.\n */\nexport function isHtmlType(acceptOrContentType: string): boolean {\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\n return acceptOrContentType\n .split(',')\n .map((s) => s.split(';')[0].trim())\n .some((mime) => mime === '*/*' || /xml|x?html/i.test(mime) || /^(text|application)\\/\\*$/.test(mime));\n}\n"]}
@@ -1,5 +1,5 @@
1
- import type { RenderFunctionOptions, ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';
2
1
  import { type EnsurePackageBundledContext } from '@ms-cloudpack/api-server';
2
+ import type { ExpandedRenderFunctionResult, RenderFunctionOptions } from '@ms-cloudpack/common-types';
3
3
  /**
4
4
  * Get the response for the given route. If the route has a custom render script, use that.
5
5
  * Returns an error response if the file referenced by the script doesn't exist or throws an error.
@@ -1 +1 @@
1
- {"version":3,"file":"renderRoute.d.ts","sourceRoot":"","sources":["../../src/renderRoute/renderRoute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EAErB,4BAA4B,EAG7B,MAAM,4BAA4B,CAAC;AAcpC,OAAO,EAAwB,KAAK,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAKlG;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAAC,CAwG9C"}
1
+ {"version":3,"file":"renderRoute.d.ts","sourceRoot":"","sources":["../../src/renderRoute/renderRoute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EACV,4BAA4B,EAC5B,qBAAqB,EAEtB,MAAM,4BAA4B,CAAC;AAWpC;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAAC,CAwG9C"}
@@ -1,15 +1,12 @@
1
- import { makeUrl } from '@ms-cloudpack/path-string-parsing';
1
+ import { javascriptExtensions, sourceExtensions, typescriptExtensions } from '@ms-cloudpack/path-utilities';
2
2
  import fsPromises from 'fs/promises';
3
- import { Window } from 'happy-dom';
4
3
  import path from 'path';
5
4
  import { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';
6
- import { getHtmlErrorResponse } from './getHtmlErrorResponse.js';
7
- import HTMLSerializer from 'happy-dom/lib/html-serializer/HTMLSerializer.js';
8
- import { javascriptExtensions, sourceExtensions, sourceToIntermediatePath, typescriptExtensions, } from '@ms-cloudpack/path-utilities';
9
- import { ensurePackageBundled } from '@ms-cloudpack/api-server';
10
- import { bulletedList } from '@ms-cloudpack/task-reporter';
11
- import { environmentInfo } from '@ms-cloudpack/environment';
12
- import { renderCustomScript } from './renderCustomScript.js';
5
+ import { getCustomRenderErrorResponse, getErrorResponse } from './getErrorResponse.js';
6
+ import { injectScripts } from './injectScripts.js';
7
+ import { isHtmlAcceptable, isHtmlType } from './isHtmlAcceptable.js';
8
+ import { bundleServerEntry } from './ssr/bundleServerEntry.js';
9
+ import { renderCustomScript } from './ssr/renderCustomScript.js';
13
10
  /**
14
11
  * Get the response for the given route. If the route has a custom render script, use that.
15
12
  * Returns an error response if the file referenced by the script doesn't exist or throws an error.
@@ -44,7 +41,7 @@ export async function renderRoute(options, context) {
44
41
  const scriptExt = path.extname(renderScriptPath).toLowerCase();
45
42
  if (scriptExt === '.html' || scriptExt === '.htm') {
46
43
  // HTML file: just read it
47
- rawResult = await readStaticHtml(renderScriptPath);
44
+ rawResult = await readStaticHtml({ renderScriptPath, req: options.req });
48
45
  }
49
46
  else if (javascriptExtensions.includes(scriptExt)) {
50
47
  // JS file: run its function
@@ -64,7 +61,7 @@ export async function renderRoute(options, context) {
64
61
  'If you want to serve a static non-HTML file, use a route with `staticPath`, or a `serverEntry` ' +
65
62
  'which returns the file content for advanced scenarios.';
66
63
  }
67
- return makeErrorResponse({
64
+ return getCustomRenderErrorResponse({
68
65
  message,
69
66
  route,
70
67
  req: options.req,
@@ -80,7 +77,7 @@ export async function renderRoute(options, context) {
80
77
  return null;
81
78
  }
82
79
  if (rawResult === undefined) {
83
- return makeErrorResponse({
80
+ return getCustomRenderErrorResponse({
84
81
  message: `returned undefined. If the script fully handled the request (sent a response), ` +
85
82
  `return null instead. Otherwise ensure that the function returns the content to be rendered.`,
86
83
  route,
@@ -95,14 +92,14 @@ export async function renderRoute(options, context) {
95
92
  };
96
93
  // If it's HTML and a success statusCode, inject the import map and appropriate scripts.
97
94
  if (result.statusCode === 200 && isHtmlType(result.contentType)) {
98
- if (isHtmlAcceptable(options)) {
95
+ if (isHtmlAcceptable(options.req)) {
99
96
  // Inject the import map and scripts.
100
97
  await injectScripts(options, result);
101
98
  }
102
99
  else {
103
100
  // It appears we're accidentally returning HTML when a different file type was requested.
104
101
  // This could happen for overly broad route matches such as '*', or if no routes are configured.
105
- return makeErrorResponse({
102
+ return getCustomRenderErrorResponse({
106
103
  message: 'returned HTML, but this appears to be a non-HTML request. This is likely due to an ' +
107
104
  'overly broad match or other misconfiguration of server.routes in the cloudpack config.',
108
105
  route,
@@ -113,174 +110,19 @@ export async function renderRoute(options, context) {
113
110
  }
114
111
  return result;
115
112
  }
116
- async function bundleServerEntry(options, context) {
117
- const { definition, route, req, serverEntryPath } = options;
118
- const { name, version } = definition;
119
- // targetEnvironment: node currently bundles all node or agnostic entries, and implicitly does
120
- // production bundling. This seems like it could get expensive, but it's probably okay because
121
- // createPackageSettingsTransform overwrites the package's actual exports with the ones generated
122
- // from routes, and those will always have environment conditions.
123
- const response = await ensurePackageBundled({
124
- name,
125
- version,
126
- targetEnvironment: 'node',
127
- // TODO: This will only watch for changes in the given package, not dependencies.
128
- // Since we implicitly use production mode for node bundles, this means changes to
129
- // dependencies will be missed.
130
- shouldWatch: !environmentInfo.isJest,
131
- // Irrelevant with implicit production bundling
132
- enqueueDependencies: false,
133
- }, context);
134
- const { errors, outputPath = '', outputFiles = [] } = response.result;
135
- if (errors?.length) {
136
- return makeErrorResponse({
137
- message: `failed to bundle: ${JSON.stringify(response.result.errors, null, 2)}`,
138
- route,
139
- req,
140
- scriptPath: serverEntryPath,
141
- });
142
- }
143
- let serverEntryOutput = outputFiles.find((f) => f.entryPoint === serverEntryPath)?.outputPath;
144
- if (!serverEntryOutput) {
145
- // No literal match, so try the intermediate path without extension.
146
- const intermediateServerEntryPath = sourceToIntermediatePath(serverEntryPath).replace(/\.\w+$/, '');
147
- serverEntryOutput =
148
- outputFiles.find((f) => f.entryPoint === serverEntryPath)?.outputPath ||
149
- outputFiles.find((f) => f.outputPath.replace(/\.\w+$/, '') === intermediateServerEntryPath)?.outputPath;
150
- }
151
- if (!serverEntryOutput) {
152
- return makeErrorResponse({
153
- message: `did not produce a bundle output file corresponding to the serverEntry script (${serverEntryPath}). Produced files:\n${outputFiles.length
154
- ? bulletedList(outputFiles.map((f) => `${f.outputPath} (entry: ${f.entryPoint || 'unknown'})`))
155
- : 'none'}`,
156
- route,
157
- req: options.req,
158
- scriptPath: serverEntryPath,
159
- });
160
- }
161
- // Translate relative-to-output path into an absolute path.
162
- return path.resolve(outputPath, serverEntryOutput);
163
- }
164
- /**
165
- * Fill in details of the message, log to console.error, and return an error response.
166
- * `Route ${route.match} with (renderScript|serverEntry) "${scriptPath}" ${message}`
167
- */
168
- function makeErrorResponse(params) {
169
- const { message, route, scriptPath, req } = params;
170
- const scriptType = route.serverEntry ? 'serverEntry' : 'renderScript';
171
- const renderer = scriptPath ? `${scriptType} "${scriptPath}"` : 'the default renderer';
172
- const content = `Route ${JSON.stringify(route.match)} with ${renderer} ${message}`;
173
- console.error(`App server: request ${req.url} - ${content}`);
174
- return { statusCode: 500, contentType: 'text/plain', content };
175
- }
176
- function isHtmlAcceptable(options) {
177
- const { req, baseUrl } = options;
178
- // Get the request path and extension (the URL.pathname conversion gets rid of any query or hash)
179
- const requestPath = makeUrl(req.url, baseUrl).pathname;
180
- const requestExt = path.extname(requestPath);
181
- // Determine whether HTML is (probably) an acceptable response type (can be refined later if needed).
182
- // In theory we should be able to use req.accepts() but not all requests set the accept header.
183
- return (!requestExt || requestExt.startsWith('.htm')) && !req.xhr && isHtmlType(req.headers.accept || '*/*');
184
- }
185
- function isHtmlType(acceptOrContentType) {
186
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
187
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values
188
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
189
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
190
- return acceptOrContentType
191
- .split(',')
192
- .map((s) => s.split(';')[0].trim())
193
- .some((mime) => mime === '*/*' || /xml|x?html/i.test(mime) || /^(text|application)\/\*$/.test(mime));
194
- }
195
- /**
196
- * Modify the HTML response by injecting the import map, inline scripts, and entry/overlay scripts.
197
- *
198
- * Note that this just logs an error rather than returning an error response if something fails, for now.
199
- */
200
- async function injectScripts(options, result) {
201
- const { route, overlayScript, entryScripts, inlineScripts, importMap, baseUrl } = options;
202
- const window = new Window({
203
- url: baseUrl,
204
- settings: {
205
- disableJavaScriptFileLoading: true,
206
- disableJavaScriptEvaluation: true,
207
- disableCSSFileLoading: true,
208
- disableIframePageLoading: true,
209
- },
210
- });
211
- try {
212
- // TODO: happy-dom is overkill for what we're doing here--a full browser-like environment isn't
213
- // needed to just parse and modify the HTML. If it becomes a perf concern (less likely since
214
- // HTML page rendering is probably just done once per page load), we could switch to
215
- // a pure parser like parse5 or htmlparser2.
216
- window.document.write(result.content);
217
- }
218
- catch (e) {
219
- // Trying to write a test for the above, it seemed very permissive, but catch just in case
220
- console.error(`Error parsing html response for rendering route "${JSON.stringify(route.match)}":\n${e.stack || e}`);
221
- return;
222
- }
223
- try {
224
- const document = window.document;
225
- if (entryScripts || overlayScript) {
226
- // Inject the import map at the top of the head, in case other scripts use it
227
- addScript({ document, type: 'importmap', prepend: true, content: JSON.stringify(importMap, null, 2) });
228
- }
229
- if (inlineScripts) {
230
- for (const inlineScript of inlineScripts) {
231
- addScript({ document, content: inlineScript });
232
- }
233
- }
234
- // inject the overlay and entry scripts
235
- addScript({ document, url: overlayScript });
236
- if (entryScripts) {
237
- for (const entryScript of entryScripts) {
238
- addScript({ document, url: entryScript });
239
- }
240
- }
241
- const serializer = new HTMLSerializer();
242
- result.content = serializer.serializeToString(document);
243
- await window.happyDOM.close();
244
- }
245
- catch (e) {
246
- console.error(`Error injecting scripts for route "${JSON.stringify(route.match)}":\n${e.stack || e}`);
247
- }
248
- }
249
- /**
250
- * Helper function to add a script to the document.
251
- * No-op if neither `url` nor `content` is provided.
252
- */
253
- function addScript(params) {
254
- const { document, type = 'module', prepend, url, content } = params;
255
- if (!url && !content) {
256
- return;
257
- }
258
- const script = document.createElement('script');
259
- script.type = type;
260
- if (url) {
261
- script.src = url;
262
- }
263
- else if (content) {
264
- script.innerHTML = content;
265
- }
266
- if (prepend) {
267
- document.head.prepend(script);
268
- }
269
- else {
270
- document.head.appendChild(script);
271
- }
272
- }
273
113
  /**
274
114
  * Read a static HTML file, or return an error response if the file can't be read.
275
115
  */
276
- async function readStaticHtml(renderScriptPath) {
116
+ async function readStaticHtml(params) {
117
+ const { renderScriptPath, req } = params;
277
118
  try {
278
119
  return (await fsPromises.readFile(renderScriptPath, 'utf8')) || '';
279
120
  }
280
121
  catch (e) {
281
- const message = `Error reading HTML file at "${renderScriptPath}":\n${e.stack || e}`;
282
- console.error(message);
283
- return getHtmlErrorResponse(message);
122
+ return getErrorResponse({
123
+ req,
124
+ message: `Error reading HTML file at "${renderScriptPath}":\n${e.stack || e}`,
125
+ });
284
126
  }
285
127
  }
286
128
  //# sourceMappingURL=renderRoute.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderRoute.js","sourceRoot":"","sources":["../../src/renderRoute/renderRoute.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n RenderFunctionOptions,\n RenderFunctionResult,\n ExpandedRenderFunctionResult,\n RenderedRoute,\n Request,\n} from '@ms-cloudpack/common-types';\nimport { makeUrl } from '@ms-cloudpack/path-string-parsing';\nimport fsPromises from 'fs/promises';\nimport { Window, type Document } from 'happy-dom';\nimport path from 'path';\nimport { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';\nimport { getHtmlErrorResponse } from './getHtmlErrorResponse.js';\nimport HTMLSerializer from 'happy-dom/lib/html-serializer/HTMLSerializer.js';\nimport {\n javascriptExtensions,\n sourceExtensions,\n sourceToIntermediatePath,\n typescriptExtensions,\n} from '@ms-cloudpack/path-utilities';\nimport { ensurePackageBundled, type EnsurePackageBundledContext } from '@ms-cloudpack/api-server';\nimport { bulletedList } from '@ms-cloudpack/task-reporter';\nimport { environmentInfo } from '@ms-cloudpack/environment';\nimport { renderCustomScript } from './renderCustomScript.js';\n\n/**\n * Get the response for the given route. If the route has a custom render script, use that.\n * Returns an error response if the file referenced by the script doesn't exist or throws an error.\n */\nexport async function renderRoute(\n options: RenderFunctionOptions,\n context: EnsurePackageBundledContext,\n): Promise<ExpandedRenderFunctionResult | null> {\n const { route, session } = options;\n const { appPath } = session.config;\n\n // Get the script to render (if any). Prefer serverEntry if provided.\n // eslint-disable-next-line etc/no-deprecated\n let renderScriptPath = route.renderScript && path.resolve(appPath, route.renderScript);\n\n if (route.serverEntry) {\n const serverEntryExt = path.extname(route.serverEntry.sourcePath).toLowerCase();\n if (sourceExtensions.includes(serverEntryExt)) {\n // Server entry is a source file, so bundle it\n const serverEntryResult = await bundleServerEntry(\n { ...options, serverEntryPath: route.serverEntry.sourcePath },\n context,\n );\n if (typeof serverEntryResult === 'string') {\n renderScriptPath = serverEntryResult;\n } else {\n // This means there was an error\n return serverEntryResult;\n }\n } else {\n // HTML or something, so use it as-is\n renderScriptPath = path.resolve(appPath, route.serverEntry.sourcePath);\n }\n }\n\n // Get an initial result from either the renderScript/serverEntry or default handler.\n let rawResult: RenderFunctionResult;\n if (renderScriptPath) {\n const scriptExt = path.extname(renderScriptPath).toLowerCase();\n if (scriptExt === '.html' || scriptExt === '.htm') {\n // HTML file: just read it\n rawResult = await readStaticHtml(renderScriptPath);\n } else if (javascriptExtensions.includes(scriptExt)) {\n // JS file: run its function\n rawResult = await renderCustomScript({ ...options, renderScriptPath });\n } else {\n // Other file type: error. Technically we could just serve it as-is following the non-HTML\n // content type logic, but this probably indicates there's been a misunderstanding and we\n // should direct them to use a more appropriate type of route instead.\n let message = 'is not a supported file type. ';\n if (typescriptExtensions.includes(scriptExt)) {\n message += 'Use `serverEntry` for TS files.';\n } else {\n message +=\n `Supported file types are ${route.serverEntry ? 'TS, JS, or HTML' : 'JS or HTML (use `serverEntry` for TS)'}. ` +\n 'If you want to serve a static non-HTML file, use a route with `staticPath`, or a `serverEntry` ' +\n 'which returns the file content for advanced scenarios.';\n }\n return makeErrorResponse({\n message,\n route,\n req: options.req,\n scriptPath: renderScriptPath,\n });\n }\n } else {\n rawResult = await getDefaultHtmlResponse(options);\n }\n\n if (rawResult === null) {\n // This means the custom renderScript fully handled the request.\n return null;\n }\n\n if (rawResult === undefined) {\n return makeErrorResponse({\n message:\n `returned undefined. If the script fully handled the request (sent a response), ` +\n `return null instead. Otherwise ensure that the function returns the content to be rendered.`,\n route,\n req: options.req,\n scriptPath: renderScriptPath,\n });\n }\n\n const result: ExpandedRenderFunctionResult = {\n contentType: 'text/html',\n statusCode: 200,\n ...(typeof rawResult === 'string' ? { content: rawResult } : rawResult),\n };\n\n // If it's HTML and a success statusCode, inject the import map and appropriate scripts.\n if (result.statusCode === 200 && isHtmlType(result.contentType)) {\n if (isHtmlAcceptable(options)) {\n // Inject the import map and scripts.\n await injectScripts(options, result);\n } else {\n // It appears we're accidentally returning HTML when a different file type was requested.\n // This could happen for overly broad route matches such as '*', or if no routes are configured.\n return makeErrorResponse({\n message:\n 'returned HTML, but this appears to be a non-HTML request. This is likely due to an ' +\n 'overly broad match or other misconfiguration of server.routes in the cloudpack config.',\n route,\n scriptPath: renderScriptPath,\n req: options.req,\n });\n }\n }\n\n return result;\n}\n\nasync function bundleServerEntry(\n options: Pick<RenderFunctionOptions, 'definition' | 'route' | 'req'> & {\n /** Relative serverEntry source path from the route */\n serverEntryPath: string;\n },\n context: EnsurePackageBundledContext,\n): Promise<string | ExpandedRenderFunctionResult> {\n const { definition, route, req, serverEntryPath } = options;\n const { name, version } = definition;\n\n // targetEnvironment: node currently bundles all node or agnostic entries, and implicitly does\n // production bundling. This seems like it could get expensive, but it's probably okay because\n // createPackageSettingsTransform overwrites the package's actual exports with the ones generated\n // from routes, and those will always have environment conditions.\n const response = await ensurePackageBundled(\n {\n name,\n version,\n targetEnvironment: 'node',\n // TODO: This will only watch for changes in the given package, not dependencies.\n // Since we implicitly use production mode for node bundles, this means changes to\n // dependencies will be missed.\n shouldWatch: !environmentInfo.isJest,\n // Irrelevant with implicit production bundling\n enqueueDependencies: false,\n },\n context,\n );\n const { errors, outputPath = '', outputFiles = [] } = response.result;\n\n if (errors?.length) {\n return makeErrorResponse({\n message: `failed to bundle: ${JSON.stringify(response.result.errors, null, 2)}`,\n route,\n req,\n scriptPath: serverEntryPath,\n });\n }\n\n let serverEntryOutput = outputFiles.find((f) => f.entryPoint === serverEntryPath)?.outputPath;\n if (!serverEntryOutput) {\n // No literal match, so try the intermediate path without extension.\n const intermediateServerEntryPath = sourceToIntermediatePath(serverEntryPath).replace(/\\.\\w+$/, '');\n serverEntryOutput =\n outputFiles.find((f) => f.entryPoint === serverEntryPath)?.outputPath ||\n outputFiles.find((f) => f.outputPath.replace(/\\.\\w+$/, '') === intermediateServerEntryPath)?.outputPath;\n }\n\n if (!serverEntryOutput) {\n return makeErrorResponse({\n message: `did not produce a bundle output file corresponding to the serverEntry script (${serverEntryPath}). Produced files:\\n${\n outputFiles.length\n ? bulletedList(outputFiles.map((f) => `${f.outputPath} (entry: ${f.entryPoint || 'unknown'})`))\n : 'none'\n }`,\n route,\n req: options.req,\n scriptPath: serverEntryPath,\n });\n }\n\n // Translate relative-to-output path into an absolute path.\n return path.resolve(outputPath, serverEntryOutput);\n}\n\n/**\n * Fill in details of the message, log to console.error, and return an error response.\n * `Route ${route.match} with (renderScript|serverEntry) \"${scriptPath}\" ${message}`\n */\nfunction makeErrorResponse(params: {\n message: string;\n route: RenderedRoute;\n scriptPath: string | undefined;\n req: Request;\n}): ExpandedRenderFunctionResult {\n const { message, route, scriptPath, req } = params;\n const scriptType = route.serverEntry ? 'serverEntry' : 'renderScript';\n const renderer = scriptPath ? `${scriptType} \"${scriptPath}\"` : 'the default renderer';\n const content = `Route ${JSON.stringify(route.match)} with ${renderer} ${message}`;\n console.error(`App server: request ${req.url} - ${content}`);\n\n return { statusCode: 500, contentType: 'text/plain', content };\n}\n\nfunction isHtmlAcceptable(options: Pick<RenderFunctionOptions, 'req' | 'baseUrl'>): boolean {\n const { req, baseUrl } = options;\n // Get the request path and extension (the URL.pathname conversion gets rid of any query or hash)\n const requestPath = makeUrl(req.url, baseUrl).pathname;\n const requestExt = path.extname(requestPath);\n // Determine whether HTML is (probably) an acceptable response type (can be refined later if needed).\n // In theory we should be able to use req.accepts() but not all requests set the accept header.\n return (!requestExt || requestExt.startsWith('.htm')) && !req.xhr && isHtmlType(req.headers.accept || '*/*');\n}\n\nfunction isHtmlType(acceptOrContentType: string): boolean {\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\n return acceptOrContentType\n .split(',')\n .map((s) => s.split(';')[0].trim())\n .some((mime) => mime === '*/*' || /xml|x?html/i.test(mime) || /^(text|application)\\/\\*$/.test(mime));\n}\n\n/**\n * Modify the HTML response by injecting the import map, inline scripts, and entry/overlay scripts.\n *\n * Note that this just logs an error rather than returning an error response if something fails, for now.\n */\nasync function injectScripts(options: RenderFunctionOptions, result: ExpandedRenderFunctionResult): Promise<void> {\n const { route, overlayScript, entryScripts, inlineScripts, importMap, baseUrl } = options;\n const window = new Window({\n url: baseUrl,\n settings: {\n disableJavaScriptFileLoading: true,\n disableJavaScriptEvaluation: true,\n disableCSSFileLoading: true,\n disableIframePageLoading: true,\n },\n });\n try {\n // TODO: happy-dom is overkill for what we're doing here--a full browser-like environment isn't\n // needed to just parse and modify the HTML. If it becomes a perf concern (less likely since\n // HTML page rendering is probably just done once per page load), we could switch to\n // a pure parser like parse5 or htmlparser2.\n window.document.write(result.content);\n } catch (e) {\n // Trying to write a test for the above, it seemed very permissive, but catch just in case\n console.error(\n `Error parsing html response for rendering route \"${JSON.stringify(route.match)}\":\\n${(e as Error).stack || e}`,\n );\n return;\n }\n\n try {\n const document = window.document;\n\n if (entryScripts || overlayScript) {\n // Inject the import map at the top of the head, in case other scripts use it\n addScript({ document, type: 'importmap', prepend: true, content: JSON.stringify(importMap, null, 2) });\n }\n\n if (inlineScripts) {\n for (const inlineScript of inlineScripts) {\n addScript({ document, content: inlineScript });\n }\n }\n\n // inject the overlay and entry scripts\n addScript({ document, url: overlayScript });\n\n if (entryScripts) {\n for (const entryScript of entryScripts) {\n addScript({ document, url: entryScript });\n }\n }\n\n const serializer = new HTMLSerializer();\n result.content = serializer.serializeToString(document);\n await window.happyDOM.close();\n } catch (e) {\n console.error(`Error injecting scripts for route \"${JSON.stringify(route.match)}\":\\n${(e as Error).stack || e}`);\n }\n}\n\n/**\n * Helper function to add a script to the document.\n * No-op if neither `url` nor `content` is provided.\n */\nfunction addScript(params: {\n document: Document;\n type?: string;\n prepend?: boolean;\n url?: string;\n content?: string;\n}): void {\n const { document, type = 'module', prepend, url, content } = params;\n if (!url && !content) {\n return;\n }\n\n const script = document.createElement('script');\n script.type = type;\n if (url) {\n script.src = url;\n } else if (content) {\n script.innerHTML = content;\n }\n\n if (prepend) {\n document.head.prepend(script);\n } else {\n document.head.appendChild(script);\n }\n}\n\n/**\n * Read a static HTML file, or return an error response if the file can't be read.\n */\nasync function readStaticHtml(renderScriptPath: string): Promise<RenderFunctionResult> {\n try {\n return (await fsPromises.readFile(renderScriptPath, 'utf8')) || '';\n } catch (e) {\n const message = `Error reading HTML file at \"${renderScriptPath}\":\\n${(e as Error).stack || e}`;\n console.error(message);\n return getHtmlErrorResponse(message);\n }\n}\n"]}
1
+ {"version":3,"file":"renderRoute.js","sourceRoot":"","sources":["../../src/renderRoute/renderRoute.ts"],"names":[],"mappings":"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","sourcesContent":["import { type EnsurePackageBundledContext } from '@ms-cloudpack/api-server';\nimport type {\n ExpandedRenderFunctionResult,\n RenderFunctionOptions,\n RenderFunctionResult,\n} from '@ms-cloudpack/common-types';\nimport { javascriptExtensions, sourceExtensions, typescriptExtensions } from '@ms-cloudpack/path-utilities';\nimport fsPromises from 'fs/promises';\nimport path from 'path';\nimport { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';\nimport { getCustomRenderErrorResponse, getErrorResponse } from './getErrorResponse.js';\nimport { injectScripts } from './injectScripts.js';\nimport { isHtmlAcceptable, isHtmlType } from './isHtmlAcceptable.js';\nimport { bundleServerEntry } from './ssr/bundleServerEntry.js';\nimport { renderCustomScript } from './ssr/renderCustomScript.js';\n\n/**\n * Get the response for the given route. If the route has a custom render script, use that.\n * Returns an error response if the file referenced by the script doesn't exist or throws an error.\n */\nexport async function renderRoute(\n options: RenderFunctionOptions,\n context: EnsurePackageBundledContext,\n): Promise<ExpandedRenderFunctionResult | null> {\n const { route, session } = options;\n const { appPath } = session.config;\n\n // Get the script to render (if any). Prefer serverEntry if provided.\n // eslint-disable-next-line etc/no-deprecated\n let renderScriptPath = route.renderScript && path.resolve(appPath, route.renderScript);\n\n if (route.serverEntry) {\n const serverEntryExt = path.extname(route.serverEntry.sourcePath).toLowerCase();\n if (sourceExtensions.includes(serverEntryExt)) {\n // Server entry is a source file, so bundle it\n const serverEntryResult = await bundleServerEntry(\n { ...options, serverEntryPath: route.serverEntry.sourcePath },\n context,\n );\n if (typeof serverEntryResult === 'string') {\n renderScriptPath = serverEntryResult;\n } else {\n // This means there was an error\n return serverEntryResult;\n }\n } else {\n // HTML or something, so use it as-is\n renderScriptPath = path.resolve(appPath, route.serverEntry.sourcePath);\n }\n }\n\n // Get an initial result from either the renderScript/serverEntry or default handler.\n let rawResult: RenderFunctionResult;\n if (renderScriptPath) {\n const scriptExt = path.extname(renderScriptPath).toLowerCase();\n if (scriptExt === '.html' || scriptExt === '.htm') {\n // HTML file: just read it\n rawResult = await readStaticHtml({ renderScriptPath, req: options.req });\n } else if (javascriptExtensions.includes(scriptExt)) {\n // JS file: run its function\n rawResult = await renderCustomScript({ ...options, renderScriptPath });\n } else {\n // Other file type: error. Technically we could just serve it as-is following the non-HTML\n // content type logic, but this probably indicates there's been a misunderstanding and we\n // should direct them to use a more appropriate type of route instead.\n let message = 'is not a supported file type. ';\n if (typescriptExtensions.includes(scriptExt)) {\n message += 'Use `serverEntry` for TS files.';\n } else {\n message +=\n `Supported file types are ${route.serverEntry ? 'TS, JS, or HTML' : 'JS or HTML (use `serverEntry` for TS)'}. ` +\n 'If you want to serve a static non-HTML file, use a route with `staticPath`, or a `serverEntry` ' +\n 'which returns the file content for advanced scenarios.';\n }\n return getCustomRenderErrorResponse({\n message,\n route,\n req: options.req,\n scriptPath: renderScriptPath,\n });\n }\n } else {\n rawResult = await getDefaultHtmlResponse(options);\n }\n\n if (rawResult === null) {\n // This means the custom renderScript fully handled the request.\n return null;\n }\n\n if (rawResult === undefined) {\n return getCustomRenderErrorResponse({\n message:\n `returned undefined. If the script fully handled the request (sent a response), ` +\n `return null instead. Otherwise ensure that the function returns the content to be rendered.`,\n route,\n req: options.req,\n scriptPath: renderScriptPath,\n });\n }\n\n const result: ExpandedRenderFunctionResult = {\n contentType: 'text/html',\n statusCode: 200,\n ...(typeof rawResult === 'string' ? { content: rawResult } : rawResult),\n };\n\n // If it's HTML and a success statusCode, inject the import map and appropriate scripts.\n if (result.statusCode === 200 && isHtmlType(result.contentType)) {\n if (isHtmlAcceptable(options.req)) {\n // Inject the import map and scripts.\n await injectScripts(options, result);\n } else {\n // It appears we're accidentally returning HTML when a different file type was requested.\n // This could happen for overly broad route matches such as '*', or if no routes are configured.\n return getCustomRenderErrorResponse({\n message:\n 'returned HTML, but this appears to be a non-HTML request. This is likely due to an ' +\n 'overly broad match or other misconfiguration of server.routes in the cloudpack config.',\n route,\n scriptPath: renderScriptPath,\n req: options.req,\n });\n }\n }\n\n return result;\n}\n\n/**\n * Read a static HTML file, or return an error response if the file can't be read.\n */\nasync function readStaticHtml(\n params: Pick<RenderFunctionOptions, 'req'> & {\n renderScriptPath: string;\n },\n): Promise<RenderFunctionResult> {\n const { renderScriptPath, req } = params;\n try {\n return (await fsPromises.readFile(renderScriptPath, 'utf8')) || '';\n } catch (e) {\n return getErrorResponse({\n req,\n message: `Error reading HTML file at \"${renderScriptPath}\":\\n${(e as Error).stack || e}`,\n });\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { type EnsurePackageBundledContext } from '@ms-cloudpack/api-server';
2
+ import type { RenderFunctionOptions, ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';
3
+ export declare function bundleServerEntry(options: Pick<RenderFunctionOptions, 'definition' | 'route' | 'req' | 'baseUrl'> & {
4
+ /** Relative serverEntry source path from the route */
5
+ serverEntryPath: string;
6
+ }, context: EnsurePackageBundledContext): Promise<string | ExpandedRenderFunctionResult>;
7
+ //# sourceMappingURL=bundleServerEntry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundleServerEntry.d.ts","sourceRoot":"","sources":["../../../src/renderRoute/ssr/bundleServerEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,2BAA2B,EAAwB,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAOtG,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,YAAY,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC,GAAG;IACjF,sDAAsD;IACtD,eAAe,EAAE,MAAM,CAAC;CACzB,EACD,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,MAAM,GAAG,4BAA4B,CAAC,CAwDhD"}
@@ -0,0 +1,54 @@
1
+ import { ensurePackageBundled } from '@ms-cloudpack/api-server';
2
+ import { environmentInfo } from '@ms-cloudpack/environment';
3
+ import { sourceToIntermediatePath } from '@ms-cloudpack/path-utilities';
4
+ import { bulletedList } from '@ms-cloudpack/task-reporter';
5
+ import path from 'path';
6
+ import { getCustomRenderErrorResponse } from '../getErrorResponse.js';
7
+ export async function bundleServerEntry(options, context) {
8
+ const { definition, route, req, serverEntryPath } = options;
9
+ const { name, version } = definition;
10
+ // targetEnvironment: node currently bundles all node or agnostic entries, and implicitly does
11
+ // production bundling. This seems like it could get expensive, but it's probably okay because
12
+ // createPackageSettingsTransform overwrites the package's actual exports with the ones generated
13
+ // from routes, and those will always have environment conditions.
14
+ const response = await ensurePackageBundled({
15
+ name,
16
+ version,
17
+ targetEnvironment: 'node',
18
+ // TODO: This will only watch for changes in the given package, not dependencies.
19
+ // File watching is disabled in production mode for performance.
20
+ shouldWatch: !environmentInfo.isJest && context.session.config.mode !== 'production',
21
+ // Irrelevant with implicit production bundling
22
+ enqueueDependencies: false,
23
+ }, context);
24
+ const { errors, outputPath = '', outputFiles = [] } = response.result;
25
+ if (errors?.length) {
26
+ return getCustomRenderErrorResponse({
27
+ message: `failed to bundle: ${JSON.stringify(response.result.errors, null, 2)}`,
28
+ route,
29
+ req,
30
+ scriptPath: serverEntryPath,
31
+ });
32
+ }
33
+ let serverEntryOutput = outputFiles.find((f) => f.entryPoint === serverEntryPath)?.outputPath;
34
+ if (!serverEntryOutput) {
35
+ // No literal match, so try the intermediate path without extension.
36
+ const intermediateServerEntryPath = sourceToIntermediatePath(serverEntryPath).replace(/\.\w+$/, '');
37
+ serverEntryOutput =
38
+ outputFiles.find((f) => f.entryPoint === serverEntryPath)?.outputPath ||
39
+ outputFiles.find((f) => f.outputPath.replace(/\.\w+$/, '') === intermediateServerEntryPath)?.outputPath;
40
+ }
41
+ if (!serverEntryOutput) {
42
+ return getCustomRenderErrorResponse({
43
+ message: `did not produce a bundle output file corresponding to the serverEntry script (${serverEntryPath}). Produced files:\n${outputFiles.length
44
+ ? bulletedList(outputFiles.map((f) => `${f.outputPath} (entry: ${f.entryPoint || 'unknown'})`))
45
+ : 'none'}`,
46
+ route,
47
+ req,
48
+ scriptPath: serverEntryPath,
49
+ });
50
+ }
51
+ // Translate relative-to-output path into an absolute path.
52
+ return path.resolve(outputPath, serverEntryOutput);
53
+ }
54
+ //# sourceMappingURL=bundleServerEntry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundleServerEntry.js","sourceRoot":"","sources":["../../../src/renderRoute/ssr/bundleServerEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAElG,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAEtE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAGC,EACD,OAAoC;IAEpC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IAC5D,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IAErC,8FAA8F;IAC9F,8FAA8F;IAC9F,iGAAiG;IACjG,kEAAkE;IAClE,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CACzC;QACE,IAAI;QACJ,OAAO;QACP,iBAAiB,EAAE,MAAM;QACzB,iFAAiF;QACjF,gEAAgE;QAChE,WAAW,EAAE,CAAC,eAAe,CAAC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY;QACpF,+CAA+C;QAC/C,mBAAmB,EAAE,KAAK;KAC3B,EACD,OAAO,CACR,CAAC;IACF,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,EAAE,EAAE,WAAW,GAAG,EAAE,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC;IAEtE,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,4BAA4B,CAAC;YAClC,OAAO,EAAE,qBAAqB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;YAC/E,KAAK;YACL,GAAG;YACH,UAAU,EAAE,eAAe;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,IAAI,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,eAAe,CAAC,EAAE,UAAU,CAAC;IAC9F,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,oEAAoE;QACpE,MAAM,2BAA2B,GAAG,wBAAwB,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACpG,iBAAiB;YACf,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,eAAe,CAAC,EAAE,UAAU;gBACrE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,2BAA2B,CAAC,EAAE,UAAU,CAAC;IAC5G,CAAC;IAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,OAAO,4BAA4B,CAAC;YAClC,OAAO,EAAE,iFAAiF,eAAe,uBACvG,WAAW,CAAC,MAAM;gBAChB,CAAC,CAAC,YAAY,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,UAAU,YAAY,CAAC,CAAC,UAAU,IAAI,SAAS,GAAG,CAAC,CAAC;gBAC/F,CAAC,CAAC,MACN,EAAE;YACF,KAAK;YACL,GAAG;YACH,UAAU,EAAE,eAAe;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,2DAA2D;IAC3D,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;AACrD,CAAC","sourcesContent":["import { type EnsurePackageBundledContext, ensurePackageBundled } from '@ms-cloudpack/api-server';\nimport type { RenderFunctionOptions, ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';\nimport { environmentInfo } from '@ms-cloudpack/environment';\nimport { sourceToIntermediatePath } from '@ms-cloudpack/path-utilities';\nimport { bulletedList } from '@ms-cloudpack/task-reporter';\nimport path from 'path';\nimport { getCustomRenderErrorResponse } from '../getErrorResponse.js';\n\nexport async function bundleServerEntry(\n options: Pick<RenderFunctionOptions, 'definition' | 'route' | 'req' | 'baseUrl'> & {\n /** Relative serverEntry source path from the route */\n serverEntryPath: string;\n },\n context: EnsurePackageBundledContext,\n): Promise<string | ExpandedRenderFunctionResult> {\n const { definition, route, req, serverEntryPath } = options;\n const { name, version } = definition;\n\n // targetEnvironment: node currently bundles all node or agnostic entries, and implicitly does\n // production bundling. This seems like it could get expensive, but it's probably okay because\n // createPackageSettingsTransform overwrites the package's actual exports with the ones generated\n // from routes, and those will always have environment conditions.\n const response = await ensurePackageBundled(\n {\n name,\n version,\n targetEnvironment: 'node',\n // TODO: This will only watch for changes in the given package, not dependencies.\n // File watching is disabled in production mode for performance.\n shouldWatch: !environmentInfo.isJest && context.session.config.mode !== 'production',\n // Irrelevant with implicit production bundling\n enqueueDependencies: false,\n },\n context,\n );\n const { errors, outputPath = '', outputFiles = [] } = response.result;\n\n if (errors?.length) {\n return getCustomRenderErrorResponse({\n message: `failed to bundle: ${JSON.stringify(response.result.errors, null, 2)}`,\n route,\n req,\n scriptPath: serverEntryPath,\n });\n }\n\n let serverEntryOutput = outputFiles.find((f) => f.entryPoint === serverEntryPath)?.outputPath;\n if (!serverEntryOutput) {\n // No literal match, so try the intermediate path without extension.\n const intermediateServerEntryPath = sourceToIntermediatePath(serverEntryPath).replace(/\\.\\w+$/, '');\n serverEntryOutput =\n outputFiles.find((f) => f.entryPoint === serverEntryPath)?.outputPath ||\n outputFiles.find((f) => f.outputPath.replace(/\\.\\w+$/, '') === intermediateServerEntryPath)?.outputPath;\n }\n\n if (!serverEntryOutput) {\n return getCustomRenderErrorResponse({\n message: `did not produce a bundle output file corresponding to the serverEntry script (${serverEntryPath}). Produced files:\\n${\n outputFiles.length\n ? bulletedList(outputFiles.map((f) => `${f.outputPath} (entry: ${f.entryPoint || 'unknown'})`))\n : 'none'\n }`,\n route,\n req,\n scriptPath: serverEntryPath,\n });\n }\n\n // Translate relative-to-output path into an absolute path.\n return path.resolve(outputPath, serverEntryOutput);\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderCustomScript.d.ts","sourceRoot":"","sources":["../../../src/renderRoute/ssr/renderCustomScript.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,qBAAqB,EACrB,oBAAoB,EAErB,MAAM,4BAA4B,CAAC;AAOpC;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,qBAAqB,GAAG;IAAE,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAC3D,OAAO,CAAC,oBAAoB,CAAC,CAyD/B"}
@@ -1,8 +1,8 @@
1
1
  import { fileURLToPath, pathToFileURL } from 'url';
2
- import { getHtmlErrorResponse } from './getHtmlErrorResponse.js';
3
2
  import fsPromises from 'fs/promises';
4
3
  import path from 'path';
5
- import { runServerEntryInWorker } from './ssr/runServerEntryInWorker.js';
4
+ import { runServerEntryInWorker } from './runServerEntryInWorker.js';
5
+ import { getCustomRenderErrorResponse, getErrorResponse } from '../getErrorResponse.js';
6
6
  /**
7
7
  * Load the default export from a JS file passed as the `renderScript` in a route,
8
8
  * and return either the result of running the function, or an error page if something fails.
@@ -26,7 +26,13 @@ export async function renderCustomScript(params) {
26
26
  const renderScriptUrlWithCacheBreaker = renderScriptUrl + cacheBreakerQueryParam;
27
27
  const { config } = options.session;
28
28
  if (config.features?.enableSSR) {
29
- return executeRenderScriptInSsrMode(renderScriptPath, renderScriptUrlWithCacheBreaker, options.importMap);
29
+ return executeRenderScriptInSsrMode({
30
+ renderScriptPath,
31
+ renderScriptUrl: renderScriptUrlWithCacheBreaker,
32
+ // TODO this should be a Node-specific import map
33
+ importMap: options.importMap,
34
+ options,
35
+ });
30
36
  }
31
37
  // Try importing the script
32
38
  let createHtml;
@@ -55,14 +61,14 @@ export async function renderCustomScript(params) {
55
61
  // Return an error page. Doing this instead of returning a default or empty response ensures that
56
62
  // the user is aware of any configuration issues.
57
63
  errorMessage ??= `Unknown error loading render script at "${renderScriptPath}".`;
58
- console.error(errorMessage);
59
- return getHtmlErrorResponse(errorMessage);
64
+ return getErrorResponse({ req: options.req, message: errorMessage });
60
65
  }
61
- async function executeRenderScriptInSsrMode(renderScriptPath, renderScriptUrl, importMap) {
66
+ async function executeRenderScriptInSsrMode(params) {
67
+ const { renderScriptPath, renderScriptUrl, importMap, options } = params;
62
68
  const dirname = path.dirname(fileURLToPath(import.meta.url));
63
69
  // Tests run the worker from the compiled version, so we need to replace /src/ with /lib/ in the path.
64
70
  // This replace call has no effect in production, where the worker is always run from the compiled version.
65
- const loaderPath = path.join(dirname, 'ssr/importMapLoader.js').replace(/\/src\//, '/lib/');
71
+ const loaderPath = path.join(dirname, 'worker/importMapLoader.js').replace(/\/src\//, '/lib/');
66
72
  const serverEntryResult = await runServerEntryInWorker({
67
73
  loaderPath,
68
74
  renderScriptUrl,
@@ -71,9 +77,12 @@ async function executeRenderScriptInSsrMode(renderScriptPath, renderScriptUrl, i
71
77
  });
72
78
  const { errors, renderedContent } = serverEntryResult;
73
79
  if (errors?.length) {
74
- const errorMessage = errors.map((e) => e.text).join('\n');
75
- console.error(`Error running render script at "${renderScriptPath}":\n${errorMessage}`);
76
- return getHtmlErrorResponse(errorMessage);
80
+ return getCustomRenderErrorResponse({
81
+ req: options.req,
82
+ route: options.route,
83
+ message: `- error running script: ${errors.map((e) => e.text).join('\n')}`,
84
+ scriptPath: renderScriptPath,
85
+ });
77
86
  }
78
87
  return { content: renderedContent, contentType: 'text/html', statusCode: 200 };
79
88
  }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderCustomScript.js","sourceRoot":"","sources":["../../../src/renderRoute/ssr/renderCustomScript.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ExpandedRenderFunctionResult,\n ImportMap,\n RenderFunction,\n RenderFunctionOptions,\n RenderFunctionResult,\n RenderFunctionScript,\n} from '@ms-cloudpack/common-types';\nimport { fileURLToPath, pathToFileURL } from 'url';\nimport fsPromises from 'fs/promises';\nimport path from 'path';\nimport { runServerEntryInWorker } from './runServerEntryInWorker.js';\nimport { getCustomRenderErrorResponse, getErrorResponse } from '../getErrorResponse.js';\n\n/**\n * Load the default export from a JS file passed as the `renderScript` in a route,\n * and return either the result of running the function, or an error page if something fails.\n */\nexport async function renderCustomScript(\n params: RenderFunctionOptions & { renderScriptPath: string },\n): Promise<RenderFunctionResult> {\n const { renderScriptPath, ...options } = params;\n // Get the html factory function from a script default export.\n const renderScriptUrl = pathToFileURL(renderScriptPath).toString();\n\n // Note: because there isn't a way to purge the require cache, we need to add a cache breaker query param to the\n // script path to ensure we get the latest version of the script. This could be improved by using the git hash of\n // the file if it exists, or a hash of the content, or even the timestamp of the file.\n // TODO: this won't work to update anything the file imports, and doesn't seem to work for serverEntry at all...\n let cacheBreakerQueryParam = `?t=${Date.now()}`;\n try {\n const { mtime } = await fsPromises.stat(renderScriptPath);\n cacheBreakerQueryParam = `?t=${mtime.getTime()}`;\n } catch {\n /* no-op */\n }\n\n const renderScriptUrlWithCacheBreaker = renderScriptUrl + cacheBreakerQueryParam;\n\n const { config } = options.session;\n if (config.features?.enableSSR) {\n return executeRenderScriptInSsrMode({\n renderScriptPath,\n renderScriptUrl: renderScriptUrlWithCacheBreaker,\n // TODO this should be a Node-specific import map\n importMap: options.importMap,\n options,\n });\n }\n\n // Try importing the script\n let createHtml: RenderFunction | undefined;\n let errorMessage: string | undefined;\n try {\n const importResult = ((await import(renderScriptUrlWithCacheBreaker)) as RenderFunctionScript).default;\n if (typeof importResult === 'function') {\n createHtml = importResult;\n } else {\n errorMessage = `The render script at \"${renderScriptPath}\" does not export a default function.`;\n }\n } catch (e) {\n errorMessage = `Error importing render script at \"${renderScriptUrl}\":\\n${(e as Error).stack || e}`;\n }\n\n if (createHtml) {\n // Try running the script\n try {\n return await createHtml(options);\n } catch (e) {\n errorMessage = `Error running render script at \"${renderScriptPath}\":\\n${(e as Error).stack || e}`;\n }\n }\n\n // Return an error page. Doing this instead of returning a default or empty response ensures that\n // the user is aware of any configuration issues.\n errorMessage ??= `Unknown error loading render script at \"${renderScriptPath}\".`;\n return getErrorResponse({ req: options.req, message: errorMessage });\n}\n\nasync function executeRenderScriptInSsrMode(params: {\n renderScriptPath: string;\n renderScriptUrl: string;\n importMap: ImportMap;\n options: RenderFunctionOptions;\n}): Promise<ExpandedRenderFunctionResult> {\n const { renderScriptPath, renderScriptUrl, importMap, options } = params;\n\n const dirname = path.dirname(fileURLToPath(import.meta.url));\n // Tests run the worker from the compiled version, so we need to replace /src/ with /lib/ in the path.\n // This replace call has no effect in production, where the worker is always run from the compiled version.\n const loaderPath = path.join(dirname, 'worker/importMapLoader.js').replace(/\\/src\\//, '/lib/');\n\n const serverEntryResult = await runServerEntryInWorker({\n loaderPath,\n renderScriptUrl,\n importMap,\n renderParams: {},\n });\n\n const { errors, renderedContent } = serverEntryResult;\n if (errors?.length) {\n return getCustomRenderErrorResponse({\n req: options.req,\n route: options.route,\n message: `- error running script: ${errors.map((e) => e.text).join('\\n')}`,\n scriptPath: renderScriptPath,\n });\n }\n return { content: renderedContent, contentType: 'text/html', statusCode: 200 };\n}\n"]}
@@ -2,8 +2,8 @@
2
2
  * NOTE: This file is a custom loader for "bare" imports in nodejs
3
3
  * It should ONLY import types and not any runtime code.
4
4
  */
5
- import type { ResolveHookContext, LoadFnOutput, LoadHookContext, ResolveFnOutput } from 'module';
6
- import type { RunServerEntryOptions } from './types/RunServerEntryOptions.js';
5
+ import type { LoadFnOutput, LoadHookContext, ResolveFnOutput, ResolveHookContext } from 'module';
6
+ import type { RunServerEntryOptions } from '../types/RunServerEntryOptions.js';
7
7
  export declare function initialize(context: RunServerEntryOptions): void;
8
8
  type DefaultResolve = (specifier: string, context: ResolveHookContext) => ResolveFnOutput;
9
9
  export declare function resolve(specifier: string, context: ResolveHookContext, defaultResolve: DefaultResolve): ResolveFnOutput;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"importMapLoader.d.ts","sourceRoot":"","sources":["../../../../src/renderRoute/ssr/worker/importMapLoader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AACjG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAG/E,wBAAgB,UAAU,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAE/D;AAED,KAAK,cAAc,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,KAAK,eAAe,CAAC;AAE1F,wBAAgB,OAAO,CACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kBAAkB,EAC3B,cAAc,EAAE,cAAc,GAC7B,eAAe,CAYjB;AAED,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAEpF,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAmBjH"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"importMapLoader.js","sourceRoot":"","sources":["../../../../src/renderRoute/ssr/worker/importMapLoader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,IAAI,SAAS,GAAc,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC3C,MAAM,UAAU,UAAU,CAAC,OAA8B;IACvD,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AACnD,CAAC;AAID,MAAM,UAAU,OAAO,CACrB,SAAiB,EACjB,OAA2B,EAC3B,cAA8B;IAE9B,wBAAwB;IACxB,MAAM,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,wBAAwB,SAAS,OAAO,MAAM,EAAE,CAAC,CAAC;QAC9D,OAAO;YACL,GAAG,EAAE,MAAM;YACX,YAAY,EAAE,IAAI;SACnB,CAAC;IACJ,CAAC;IAED,OAAO,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC;AAID,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,GAAW,EAAE,OAAwB,EAAE,WAAwB;IACxF,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAC;QAE1C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,aAAa,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAEhC,OAAO;YACL,MAAM,EAAE,QAAQ;YAChB,MAAM;YACN,YAAY,EAAE,IAAI;SACnB,CAAC;IACJ,CAAC;IAED,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACnC,CAAC","sourcesContent":["/**\n * NOTE: This file is a custom loader for \"bare\" imports in nodejs\n * It should ONLY import types and not any runtime code.\n */\n\nimport type { ImportMap } from '@ms-cloudpack/common-types';\nimport type { LoadFnOutput, LoadHookContext, ResolveFnOutput, ResolveHookContext } from 'module';\nimport type { RunServerEntryOptions } from '../types/RunServerEntryOptions.js';\n\nlet importMap: ImportMap = { imports: {} };\nexport function initialize(context: RunServerEntryOptions): void {\n importMap = context.importMap || { imports: {} };\n}\n\ntype DefaultResolve = (specifier: string, context: ResolveHookContext) => ResolveFnOutput;\n\nexport function resolve(\n specifier: string,\n context: ResolveHookContext,\n defaultResolve: DefaultResolve,\n): ResolveFnOutput {\n // TODO: Deal with scope\n const target = importMap?.imports?.[specifier];\n if (target) {\n console.log(`Import map redirect: ${specifier} -> ${target}`);\n return {\n url: target,\n shortCircuit: true,\n };\n }\n\n return defaultResolve(specifier, context);\n}\n\ntype DefaultLoad = (url: string, context: LoadHookContext) => Promise<LoadFnOutput>;\n\nexport async function load(url: string, context: LoadHookContext, defaultLoad: DefaultLoad): Promise<LoadFnOutput> {\n if (url.startsWith('http')) {\n console.log(`Importing from URL: ${url}`);\n\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Failed to fetch ${url} (Status: ${res.status})`);\n }\n\n const source = await res.text();\n\n return {\n format: 'module',\n source,\n shortCircuit: true,\n };\n }\n\n return defaultLoad(url, context);\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { RunServerEntryOptions } from '../types/RunServerEntryOptions.js';
2
+ import type { RunServerEntryResult } from '../types/RunServerEntryResult.js';
3
+ export declare function runServerEntry(options: RunServerEntryOptions): Promise<RunServerEntryResult>;
4
+ //# sourceMappingURL=runServerEntry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runServerEntry.d.ts","sourceRoot":"","sources":["../../../../src/renderRoute/ssr/worker/runServerEntry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAO7E,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA4BlG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runServerEntry.js","sourceRoot":"","sources":["../../../../src/renderRoute/ssr/worker/runServerEntry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AASzC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAA8B;IACjE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IAEzE,0GAA0G;IAC1G,QAAQ,CAAC,UAAU,EAAE;QACnB,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;QACzC,IAAI,EAAE,EAAE,SAAS,EAAE;KACpB,CAAC,CAAC;IAEH,0EAA0E;IAC1E,qEAAqE;IACrE,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC;SACjD,IAAI,CAAC,CAAC,GAAiB,EAAE,EAAE;QAC1B,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACrC,OAAO,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAClC,CAAC;aAAM,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YAC7C,OAAO,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,uCAAuC,eAAe,EAAE,CAAC,CAAC;IAC5E,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACf,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACb,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEL,OAAO,EAAE,eAAe,EAAE,cAAc,IAAI,EAAE,EAAE,CAAC;AACnD,CAAC","sourcesContent":["import type { ServerRenderFunctionOptions } from '@ms-cloudpack/common-types';\nimport { register } from 'node:module';\nimport { pathToFileURL } from 'node:url';\nimport type { RunServerEntryOptions } from '../types/RunServerEntryOptions.js';\nimport type { RunServerEntryResult } from '../types/RunServerEntryResult.js';\n\ntype RenderModule = {\n render?: (params?: ServerRenderFunctionOptions) => Promise<string>;\n default?: (params?: ServerRenderFunctionOptions) => Promise<string>;\n};\n\nexport async function runServerEntry(options: RunServerEntryOptions): Promise<RunServerEntryResult> {\n const { importMap, renderScriptUrl, loaderPath, renderParams } = options;\n\n // Registers the customer loader, which is responsible for handling base-imports in a nodejs environment..\n register(loaderPath, {\n parentURL: pathToFileURL(import.meta.url),\n data: { importMap },\n });\n\n // With the custom loader registered, we can now import the render script.\n // We support both named and default exports for the render function.\n const renderedResult = await import(renderScriptUrl)\n .then((mod: RenderModule) => {\n if (typeof mod.render === 'function') {\n return mod.render(renderParams);\n } else if (typeof mod.default === 'function') {\n return mod.default(renderParams);\n }\n throw new Error(`No render function found in module: ${renderScriptUrl}`);\n })\n .then((output) => {\n return output;\n })\n .catch((err) => {\n console.log('Error:', err);\n });\n\n return { renderedContent: renderedResult || '' };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { initializeWorker } from '@ms-cloudpack/worker-pool';
2
- import { runServerEntry } from '../runServerEntry.js';
2
+ import { runServerEntry } from './runServerEntry.js';
3
3
  // eslint-disable-next-line no-restricted-properties -- exit handling for a worker
4
4
  const processExit = process.exit.bind(process);
5
5
  // Then initialize the worker with the appropriate listeners and api dictionary.
@@ -1 +1 @@
1
- {"version":3,"file":"workerEntry.js","sourceRoot":"","sources":["../../../../src/renderRoute/ssr/worker/workerEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,kFAAkF;AAClF,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAE/C,gFAAgF;AAChF,gBAAgB,CAAC;IACf,OAAO,EAAE;QACP,cAAc;KACf;IACD,UAAU,EAAE,GAAG,EAAE;QACf,0FAA0F;QAC1F,mFAAmF;QACnF,oDAAoD;QACpD,OAAO,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE;YACtB,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC,CAAC;IACJ,CAAC;IACD,SAAS,EAAE,GAAG,EAAE;QACd,oDAAoD;QACpD,OAAO,CAAC,IAAI,GAAG,WAAW,CAAC;IAC7B,CAAC;IACD,4FAA4F;IAC5F,+DAA+D;IAC/D,OAAO,EAAE,EAAE,GAAG,IAAI;CACnB,CAAC,CAAC","sourcesContent":["import { initializeWorker } from '@ms-cloudpack/worker-pool';\nimport { runServerEntry } from '../runServerEntry.js';\n\n// eslint-disable-next-line no-restricted-properties -- exit handling for a worker\nconst processExit = process.exit.bind(process);\n\n// Then initialize the worker with the appropriate listeners and api dictionary.\ninitializeWorker({\n methods: {\n runServerEntry,\n },\n beforeEach: () => {\n // Override process.exit calls to throw rather than exit the process, in case we load some\n // CJS code that calls process.exit. This gives an actual call stack for debugging.\n // eslint-disable-next-line no-restricted-properties\n process.exit = (code) => {\n throw new Error(`process.exit called with code ${code}`);\n };\n },\n afterEach: () => {\n // eslint-disable-next-line no-restricted-properties\n process.exit = processExit;\n },\n // Stop the worker after 30s. This is much longer than should usually be necessary, but it's\n // good to be conservative here in case of resource contention.\n timeout: 30 * 1000,\n});\n"]}
1
+ {"version":3,"file":"workerEntry.js","sourceRoot":"","sources":["../../../../src/renderRoute/ssr/worker/workerEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,kFAAkF;AAClF,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAE/C,gFAAgF;AAChF,gBAAgB,CAAC;IACf,OAAO,EAAE;QACP,cAAc;KACf;IACD,UAAU,EAAE,GAAG,EAAE;QACf,0FAA0F;QAC1F,mFAAmF;QACnF,oDAAoD;QACpD,OAAO,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE;YACtB,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC,CAAC;IACJ,CAAC;IACD,SAAS,EAAE,GAAG,EAAE;QACd,oDAAoD;QACpD,OAAO,CAAC,IAAI,GAAG,WAAW,CAAC;IAC7B,CAAC;IACD,4FAA4F;IAC5F,+DAA+D;IAC/D,OAAO,EAAE,EAAE,GAAG,IAAI;CACnB,CAAC,CAAC","sourcesContent":["import { initializeWorker } from '@ms-cloudpack/worker-pool';\nimport { runServerEntry } from './runServerEntry.js';\n\n// eslint-disable-next-line no-restricted-properties -- exit handling for a worker\nconst processExit = process.exit.bind(process);\n\n// Then initialize the worker with the appropriate listeners and api dictionary.\ninitializeWorker({\n methods: {\n runServerEntry,\n },\n beforeEach: () => {\n // Override process.exit calls to throw rather than exit the process, in case we load some\n // CJS code that calls process.exit. This gives an actual call stack for debugging.\n // eslint-disable-next-line no-restricted-properties\n process.exit = (code) => {\n throw new Error(`process.exit called with code ${code}`);\n };\n },\n afterEach: () => {\n // eslint-disable-next-line no-restricted-properties\n process.exit = processExit;\n },\n // Stop the worker after 30s. This is much longer than should usually be necessary, but it's\n // good to be conservative here in case of resource contention.\n timeout: 30 * 1000,\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ms-cloudpack/app-server",
3
- "version": "0.19.4",
3
+ "version": "0.20.1",
4
4
  "description": "An implementation of the App server for Cloudpack.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,15 +14,15 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@ms-cloudpack/api-server": "^0.64.7",
18
- "@ms-cloudpack/bundle-server": "^0.7.22",
19
- "@ms-cloudpack/common-types": "^0.26.3",
20
- "@ms-cloudpack/create-express-app": "^1.10.42",
17
+ "@ms-cloudpack/api-server": "^0.64.9",
18
+ "@ms-cloudpack/bundle-server": "^0.8.1",
19
+ "@ms-cloudpack/common-types": "^0.26.4",
20
+ "@ms-cloudpack/create-express-app": "^1.10.43",
21
21
  "@ms-cloudpack/environment": "^0.1.1",
22
- "@ms-cloudpack/import-map": "^0.10.36",
23
- "@ms-cloudpack/inline-scripts": "^0.2.21",
22
+ "@ms-cloudpack/import-map": "^0.10.37",
23
+ "@ms-cloudpack/inline-scripts": "^0.2.23",
24
24
  "@ms-cloudpack/path-string-parsing": "^1.2.7",
25
- "@ms-cloudpack/path-utilities": "^3.1.13",
25
+ "@ms-cloudpack/path-utilities": "^3.1.14",
26
26
  "@ms-cloudpack/task-reporter": "^0.17.2",
27
27
  "@ms-cloudpack/worker-pool": "^0.4.1",
28
28
  "happy-dom": "^18.0.0",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "devDependencies": {
32
32
  "@ms-cloudpack/common-types-browser": "^0.6.2",
33
- "@ms-cloudpack/config": "^0.37.1",
33
+ "@ms-cloudpack/config": "^0.37.2",
34
34
  "@ms-cloudpack/eslint-plugin-internal": "^0.0.1",
35
35
  "@ms-cloudpack/scripts": "^0.0.1",
36
36
  "@ms-cloudpack/test-utilities": "^0.5.0"
@@ -1,6 +0,0 @@
1
- import type { ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';
2
- /**
3
- * Get an error response: an HTML page with the given text, and a 500 status code.
4
- */
5
- export declare function getHtmlErrorResponse(errorText: string): ExpandedRenderFunctionResult;
6
- //# sourceMappingURL=getHtmlErrorResponse.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getHtmlErrorResponse.d.ts","sourceRoot":"","sources":["../../src/renderRoute/getHtmlErrorResponse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAE/E;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,4BAA4B,CAiBpF"}
@@ -1,22 +0,0 @@
1
- /**
2
- * Get an error response: an HTML page with the given text, and a 500 status code.
3
- */
4
- export function getHtmlErrorResponse(errorText) {
5
- return {
6
- content: [
7
- `<!DOCTYPE html>`,
8
- `<html lang="en">`,
9
- `<head>`,
10
- ` <title>Error</title>`,
11
- `</head>`,
12
- `<body>`,
13
- ` <h1>Error</h1>`,
14
- ` <pre>${errorText}</pre>`,
15
- `</body>`,
16
- `</html>`,
17
- ].join('\n'),
18
- statusCode: 500,
19
- contentType: 'text/html',
20
- };
21
- }
22
- //# sourceMappingURL=getHtmlErrorResponse.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getHtmlErrorResponse.js","sourceRoot":"","sources":["../../src/renderRoute/getHtmlErrorResponse.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,OAAO;QACL,OAAO,EAAE;YACP,iBAAiB;YACjB,kBAAkB;YAClB,QAAQ;YACR,wBAAwB;YACxB,SAAS;YACT,QAAQ;YACR,kBAAkB;YAClB,UAAU,SAAS,QAAQ;YAC3B,SAAS;YACT,SAAS;SACV,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,UAAU,EAAE,GAAG;QACf,WAAW,EAAE,WAAW;KACzB,CAAC;AACJ,CAAC","sourcesContent":["import type { ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';\n\n/**\n * Get an error response: an HTML page with the given text, and a 500 status code.\n */\nexport function getHtmlErrorResponse(errorText: string): ExpandedRenderFunctionResult {\n return {\n content: [\n `<!DOCTYPE html>`,\n `<html lang=\"en\">`,\n `<head>`,\n ` <title>Error</title>`,\n `</head>`,\n `<body>`,\n ` <h1>Error</h1>`,\n ` <pre>${errorText}</pre>`,\n `</body>`,\n `</html>`,\n ].join('\\n'),\n statusCode: 500,\n contentType: 'text/html',\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"renderCustomScript.d.ts","sourceRoot":"","sources":["../../src/renderRoute/renderCustomScript.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,qBAAqB,EACrB,oBAAoB,EAErB,MAAM,4BAA4B,CAAC;AAOpC;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,qBAAqB,GAAG;IAAE,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAC3D,OAAO,CAAC,oBAAoB,CAAC,CAoD/B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"renderCustomScript.js","sourceRoot":"","sources":["../../src/renderRoute/renderCustomScript.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAEzE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAA4D;IAE5D,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC;IAChD,8DAA8D;IAC9D,MAAM,eAAe,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEnE,gHAAgH;IAChH,iHAAiH;IACjH,sFAAsF;IACtF,gHAAgH;IAChH,IAAI,sBAAsB,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC1D,sBAAsB,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,WAAW;IACb,CAAC;IAED,MAAM,+BAA+B,GAAG,eAAe,GAAG,sBAAsB,CAAC;IAEjF,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IACnC,IAAI,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC;QAC/B,OAAO,4BAA4B,CAAC,gBAAgB,EAAE,+BAA+B,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC5G,CAAC;IAED,2BAA2B;IAC3B,IAAI,UAAsC,CAAC;IAC3C,IAAI,YAAgC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,YAAY,GAAI,CAAC,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAA0B,CAAC,OAAO,CAAC;QACvG,IAAI,OAAO,YAAY,KAAK,UAAU,EAAE,CAAC;YACvC,UAAU,GAAG,YAAY,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,YAAY,GAAG,yBAAyB,gBAAgB,uCAAuC,CAAC;QAClG,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,YAAY,GAAG,qCAAqC,eAAe,OAAQ,CAAW,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;IACtG,CAAC;IAED,IAAI,UAAU,EAAE,CAAC;QACf,yBAAyB;QACzB,IAAI,CAAC;YACH,OAAO,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,YAAY,GAAG,mCAAmC,gBAAgB,OAAQ,CAAW,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QACrG,CAAC;IACH,CAAC;IAED,iGAAiG;IACjG,iDAAiD;IACjD,YAAY,KAAK,2CAA2C,gBAAgB,IAAI,CAAC;IACjF,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC5B,OAAO,oBAAoB,CAAC,YAAY,CAAC,CAAC;AAC5C,CAAC;AAED,KAAK,UAAU,4BAA4B,CACzC,gBAAwB,EACxB,eAAuB,EACvB,SAAoB;IAEpB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,sGAAsG;IACtG,2GAA2G;IAC3G,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,wBAAwB,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAE5F,MAAM,iBAAiB,GAAG,MAAM,sBAAsB,CAAC;QACrD,UAAU;QACV,eAAe;QACf,SAAS;QACT,YAAY,EAAE,EAAE;KACjB,CAAC,CAAC;IAEH,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,iBAAiB,CAAC;IACtD,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;QACnB,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,OAAO,CAAC,KAAK,CAAC,mCAAmC,gBAAgB,OAAO,YAAY,EAAE,CAAC,CAAC;QACxF,OAAO,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;AACjF,CAAC","sourcesContent":["import type {\n ExpandedRenderFunctionResult,\n ImportMap,\n RenderFunction,\n RenderFunctionOptions,\n RenderFunctionResult,\n RenderFunctionScript,\n} from '@ms-cloudpack/common-types';\nimport { fileURLToPath, pathToFileURL } from 'url';\nimport { getHtmlErrorResponse } from './getHtmlErrorResponse.js';\nimport fsPromises from 'fs/promises';\nimport path from 'path';\nimport { runServerEntryInWorker } from './ssr/runServerEntryInWorker.js';\n\n/**\n * Load the default export from a JS file passed as the `renderScript` in a route,\n * and return either the result of running the function, or an error page if something fails.\n */\nexport async function renderCustomScript(\n params: RenderFunctionOptions & { renderScriptPath: string },\n): Promise<RenderFunctionResult> {\n const { renderScriptPath, ...options } = params;\n // Get the html factory function from a script default export.\n const renderScriptUrl = pathToFileURL(renderScriptPath).toString();\n\n // Note: because there isn't a way to purge the require cache, we need to add a cache breaker query param to the\n // script path to ensure we get the latest version of the script. This could be improved by using the git hash of\n // the file if it exists, or a hash of the content, or even the timestamp of the file.\n // TODO: this won't work to update anything the file imports, and doesn't seem to work for serverEntry at all...\n let cacheBreakerQueryParam = `?t=${Date.now()}`;\n try {\n const { mtime } = await fsPromises.stat(renderScriptPath);\n cacheBreakerQueryParam = `?t=${mtime.getTime()}`;\n } catch {\n /* no-op */\n }\n\n const renderScriptUrlWithCacheBreaker = renderScriptUrl + cacheBreakerQueryParam;\n\n const { config } = options.session;\n if (config.features?.enableSSR) {\n return executeRenderScriptInSsrMode(renderScriptPath, renderScriptUrlWithCacheBreaker, options.importMap);\n }\n\n // Try importing the script\n let createHtml: RenderFunction | undefined;\n let errorMessage: string | undefined;\n try {\n const importResult = ((await import(renderScriptUrlWithCacheBreaker)) as RenderFunctionScript).default;\n if (typeof importResult === 'function') {\n createHtml = importResult;\n } else {\n errorMessage = `The render script at \"${renderScriptPath}\" does not export a default function.`;\n }\n } catch (e) {\n errorMessage = `Error importing render script at \"${renderScriptUrl}\":\\n${(e as Error).stack || e}`;\n }\n\n if (createHtml) {\n // Try running the script\n try {\n return await createHtml(options);\n } catch (e) {\n errorMessage = `Error running render script at \"${renderScriptPath}\":\\n${(e as Error).stack || e}`;\n }\n }\n\n // Return an error page. Doing this instead of returning a default or empty response ensures that\n // the user is aware of any configuration issues.\n errorMessage ??= `Unknown error loading render script at \"${renderScriptPath}\".`;\n console.error(errorMessage);\n return getHtmlErrorResponse(errorMessage);\n}\n\nasync function executeRenderScriptInSsrMode(\n renderScriptPath: string,\n renderScriptUrl: string,\n importMap: ImportMap,\n): Promise<ExpandedRenderFunctionResult> {\n const dirname = path.dirname(fileURLToPath(import.meta.url));\n // Tests run the worker from the compiled version, so we need to replace /src/ with /lib/ in the path.\n // This replace call has no effect in production, where the worker is always run from the compiled version.\n const loaderPath = path.join(dirname, 'ssr/importMapLoader.js').replace(/\\/src\\//, '/lib/');\n\n const serverEntryResult = await runServerEntryInWorker({\n loaderPath,\n renderScriptUrl,\n importMap,\n renderParams: {},\n });\n\n const { errors, renderedContent } = serverEntryResult;\n if (errors?.length) {\n const errorMessage = errors.map((e) => e.text).join('\\n');\n console.error(`Error running render script at \"${renderScriptPath}\":\\n${errorMessage}`);\n return getHtmlErrorResponse(errorMessage);\n }\n return { content: renderedContent, contentType: 'text/html', statusCode: 200 };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"importMapLoader.d.ts","sourceRoot":"","sources":["../../../src/renderRoute/ssr/importMapLoader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAEjG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAG9E,wBAAgB,UAAU,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAE/D;AAED,KAAK,cAAc,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,KAAK,eAAe,CAAC;AAE1F,wBAAgB,OAAO,CACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kBAAkB,EAC3B,cAAc,EAAE,cAAc,GAC7B,eAAe,CAYjB;AAED,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAEpF,wBAAsB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAmBjH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"importMapLoader.js","sourceRoot":"","sources":["../../../src/renderRoute/ssr/importMapLoader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,IAAI,SAAS,GAAc,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC3C,MAAM,UAAU,UAAU,CAAC,OAA8B;IACvD,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AACnD,CAAC;AAID,MAAM,UAAU,OAAO,CACrB,SAAiB,EACjB,OAA2B,EAC3B,cAA8B;IAE9B,wBAAwB;IACxB,MAAM,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,wBAAwB,SAAS,OAAO,MAAM,EAAE,CAAC,CAAC;QAC9D,OAAO;YACL,GAAG,EAAE,MAAM;YACX,YAAY,EAAE,IAAI;SACnB,CAAC;IACJ,CAAC;IAED,OAAO,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC;AAID,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,GAAW,EAAE,OAAwB,EAAE,WAAwB;IACxF,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAC;QAE1C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,aAAa,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAEhC,OAAO;YACL,MAAM,EAAE,QAAQ;YAChB,MAAM;YACN,YAAY,EAAE,IAAI;SACnB,CAAC;IACJ,CAAC;IAED,OAAO,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACnC,CAAC","sourcesContent":["/**\n * NOTE: This file is a custom loader for \"bare\" imports in nodejs\n * It should ONLY import types and not any runtime code.\n */\n\nimport type { ResolveHookContext, LoadFnOutput, LoadHookContext, ResolveFnOutput } from 'module';\nimport type { ImportMap } from '@ms-cloudpack/common-types';\nimport type { RunServerEntryOptions } from './types/RunServerEntryOptions.js';\n\nlet importMap: ImportMap = { imports: {} };\nexport function initialize(context: RunServerEntryOptions): void {\n importMap = context.importMap || { imports: {} };\n}\n\ntype DefaultResolve = (specifier: string, context: ResolveHookContext) => ResolveFnOutput;\n\nexport function resolve(\n specifier: string,\n context: ResolveHookContext,\n defaultResolve: DefaultResolve,\n): ResolveFnOutput {\n // TODO: Deal with scope\n const target = importMap?.imports?.[specifier];\n if (target) {\n console.log(`Import map redirect: ${specifier} -> ${target}`);\n return {\n url: target,\n shortCircuit: true,\n };\n }\n\n return defaultResolve(specifier, context);\n}\n\ntype DefaultLoad = (url: string, context: LoadHookContext) => Promise<LoadFnOutput>;\n\nexport async function load(url: string, context: LoadHookContext, defaultLoad: DefaultLoad): Promise<LoadFnOutput> {\n if (url.startsWith('http')) {\n console.log(`Importing from URL: ${url}`);\n\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Failed to fetch ${url} (Status: ${res.status})`);\n }\n\n const source = await res.text();\n\n return {\n format: 'module',\n source,\n shortCircuit: true,\n };\n }\n\n return defaultLoad(url, context);\n}\n"]}
@@ -1,4 +0,0 @@
1
- import type { RunServerEntryOptions } from './types/RunServerEntryOptions.js';
2
- import type { RunServerEntryResult } from './types/RunServerEntryResult.js';
3
- export declare function runServerEntry(options: RunServerEntryOptions): Promise<RunServerEntryResult>;
4
- //# sourceMappingURL=runServerEntry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runServerEntry.d.ts","sourceRoot":"","sources":["../../../src/renderRoute/ssr/runServerEntry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAS5E,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA4BlG"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"runServerEntry.js","sourceRoot":"","sources":["../../../src/renderRoute/ssr/runServerEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAQzC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAA8B;IACjE,MAAM,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IAEzE,0GAA0G;IAC1G,QAAQ,CAAC,UAAU,EAAE;QACnB,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;QACzC,IAAI,EAAE,EAAE,SAAS,EAAE;KACpB,CAAC,CAAC;IAEH,0EAA0E;IAC1E,qEAAqE;IACrE,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC;SACjD,IAAI,CAAC,CAAC,GAAiB,EAAE,EAAE;QAC1B,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACrC,OAAO,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAClC,CAAC;aAAM,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YAC7C,OAAO,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,uCAAuC,eAAe,EAAE,CAAC,CAAC;IAC5E,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACf,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACb,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEL,OAAO,EAAE,eAAe,EAAE,cAAc,IAAI,EAAE,EAAE,CAAC;AACnD,CAAC","sourcesContent":["import { register } from 'node:module';\nimport type { RunServerEntryOptions } from './types/RunServerEntryOptions.js';\nimport type { RunServerEntryResult } from './types/RunServerEntryResult.js';\nimport { pathToFileURL } from 'node:url';\nimport type { ServerRenderFunctionOptions } from '@ms-cloudpack/common-types';\n\ntype RenderModule = {\n render?: (params?: ServerRenderFunctionOptions) => Promise<string>;\n default?: (params?: ServerRenderFunctionOptions) => Promise<string>;\n};\n\nexport async function runServerEntry(options: RunServerEntryOptions): Promise<RunServerEntryResult> {\n const { importMap, renderScriptUrl, loaderPath, renderParams } = options;\n\n // Registers the customer loader, which is responsible for handling base-imports in a nodejs environment..\n register(loaderPath, {\n parentURL: pathToFileURL(import.meta.url),\n data: { importMap },\n });\n\n // With the custom loader registered, we can now import the render script.\n // We support both named and default exports for the render function.\n const renderedResult = await import(renderScriptUrl)\n .then((mod: RenderModule) => {\n if (typeof mod.render === 'function') {\n return mod.render(renderParams);\n } else if (typeof mod.default === 'function') {\n return mod.default(renderParams);\n }\n throw new Error(`No render function found in module: ${renderScriptUrl}`);\n })\n .then((output) => {\n return output;\n })\n .catch((err) => {\n console.log('Error:', err);\n });\n\n return { renderedContent: renderedResult || '' };\n}\n"]}