@ms-cloudpack/app-server 0.20.5 → 0.20.7

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 (44) hide show
  1. package/lib/createRoutes.d.ts.map +1 -1
  2. package/lib/createRoutes.js +5 -2
  3. package/lib/createRoutes.js.map +1 -1
  4. package/lib/renderRoute/getCustomRenderErrorHandler.d.ts +37 -0
  5. package/lib/renderRoute/getCustomRenderErrorHandler.d.ts.map +1 -0
  6. package/lib/renderRoute/getCustomRenderErrorHandler.js +51 -0
  7. package/lib/renderRoute/getCustomRenderErrorHandler.js.map +1 -0
  8. package/lib/renderRoute/renderRoute.js +30 -45
  9. package/lib/renderRoute/renderRoute.js.map +1 -1
  10. package/lib/renderRoute/ssr/bundleServerEntry.d.ts +8 -2
  11. package/lib/renderRoute/ssr/bundleServerEntry.d.ts.map +1 -1
  12. package/lib/renderRoute/ssr/bundleServerEntry.js +28 -21
  13. package/lib/renderRoute/ssr/bundleServerEntry.js.map +1 -1
  14. package/lib/renderRoute/ssr/renderCustomScript.d.ts +2 -0
  15. package/lib/renderRoute/ssr/renderCustomScript.d.ts.map +1 -1
  16. package/lib/renderRoute/ssr/renderCustomScript.js +15 -69
  17. package/lib/renderRoute/ssr/renderCustomScript.js.map +1 -1
  18. package/lib/renderRoute/ssr/runServerEntryInWorker.d.ts +5 -4
  19. package/lib/renderRoute/ssr/runServerEntryInWorker.d.ts.map +1 -1
  20. package/lib/renderRoute/ssr/runServerEntryInWorker.js +17 -6
  21. package/lib/renderRoute/ssr/runServerEntryInWorker.js.map +1 -1
  22. package/lib/renderRoute/ssr/types/RunScriptResult.d.ts +12 -0
  23. package/lib/renderRoute/ssr/types/RunScriptResult.d.ts.map +1 -0
  24. package/lib/renderRoute/ssr/types/RunScriptResult.js +2 -0
  25. package/lib/renderRoute/ssr/types/RunScriptResult.js.map +1 -0
  26. package/lib/renderRoute/ssr/types/RunServerEntryOptions.d.ts +2 -2
  27. package/lib/renderRoute/ssr/types/RunServerEntryOptions.d.ts.map +1 -1
  28. package/lib/renderRoute/ssr/types/RunServerEntryOptions.js.map +1 -1
  29. package/lib/renderRoute/ssr/worker/importAndRunScript.d.ts +16 -0
  30. package/lib/renderRoute/ssr/worker/importAndRunScript.d.ts.map +1 -0
  31. package/lib/renderRoute/ssr/worker/importAndRunScript.js +70 -0
  32. package/lib/renderRoute/ssr/worker/importAndRunScript.js.map +1 -0
  33. package/lib/renderRoute/ssr/worker/runServerEntry.d.ts +7 -4
  34. package/lib/renderRoute/ssr/worker/runServerEntry.d.ts.map +1 -1
  35. package/lib/renderRoute/ssr/worker/runServerEntry.js +68 -30
  36. package/lib/renderRoute/ssr/worker/runServerEntry.js.map +1 -1
  37. package/lib/startServers.d.ts.map +1 -1
  38. package/lib/startServers.js +1 -0
  39. package/lib/startServers.js.map +1 -1
  40. package/package.json +9 -9
  41. package/lib/renderRoute/getErrorResponse.d.ts +0 -21
  42. package/lib/renderRoute/getErrorResponse.d.ts.map +0 -1
  43. package/lib/renderRoute/getErrorResponse.js +0 -44
  44. package/lib/renderRoute/getErrorResponse.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"createRoutes.d.ts","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAML,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,aAAa,EAGnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAYnF,UAAU,mBAAmB;IAC3B,GAAG,EAAE,OAAO,CAAC;IACb,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,+BAA+B;IAC/B,UAAU,EAAE,WAAW,CAAC;CACzB;AAcD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,2BAA2B,GAAG,IAAI,CA0DrG;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,KAAK,CAAC,GAAG;IAC1C,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;IACd,KAAK,EAAE,aAAa,GAAG,cAAc,CAAC;CACvC,EACD,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAiDf"}
1
+ {"version":3,"file":"createRoutes.d.ts","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAML,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,aAAa,EAGnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAYnF,UAAU,mBAAmB;IAC3B,GAAG,EAAE,OAAO,CAAC;IACb,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,+BAA+B;IAC/B,UAAU,EAAE,WAAW,CAAC;CACzB;AAcD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,2BAA2B,GAAG,IAAI,CA2DrG;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,KAAK,CAAC,GAAG;IAC1C,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;IACd,KAAK,EAAE,aAAa,GAAG,cAAc,CAAC;CACvC,EACD,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAmDf"}
@@ -53,6 +53,7 @@ export function createRoutes(options, context) {
53
53
  }
54
54
  else if (isRedirectRoute(route)) {
55
55
  app.get(pathMatch, matchDomain(domainMatch), (req, res) => {
56
+ console.debug(`App server: redirect ${req.url} to ${route.redirectTo}`);
56
57
  res.redirect(route.redirectTo);
57
58
  });
58
59
  }
@@ -76,6 +77,8 @@ export function createRoutes(options, context) {
76
77
  export async function _handleRequest(options, context) {
77
78
  const { req, res, definition, route, url: baseUrl } = options;
78
79
  const { session } = context;
80
+ const isBootstrap = isBootstrapRoute(route);
81
+ console.debug(`App server: ${isBootstrap ? 'bootstrap' : 'render'} request ${req.path}`);
79
82
  // Build the import map if it hasn't been built yet for this session version.
80
83
  // (TS can't infer that urls.bundleServer is set on session from a check above.)
81
84
  const importMap = (session.importMap ??= await createImportMap({ targetEnvironment: 'browser' }, context));
@@ -91,7 +94,7 @@ export async function _handleRequest(options, context) {
91
94
  res.header('Cache-Control', 'no-cache');
92
95
  res.header('Access-Control-Allow-Origin', '*');
93
96
  let rendered;
94
- if (isBootstrapRoute(route)) {
97
+ if (isBootstrap) {
95
98
  // Render the bootstrap script
96
99
  rendered = await renderBootstrapRoute({
97
100
  session,
@@ -109,6 +112,6 @@ export async function _handleRequest(options, context) {
109
112
  if (rendered) {
110
113
  res.type(rendered.contentType).status(rendered.statusCode).send(rendered.content).end();
111
114
  }
112
- console.debug(`App server: Request: ${req.path}`);
115
+ console.debug(`App server: response ${req.path}`);
113
116
  }
114
117
  //# sourceMappingURL=createRoutes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createRoutes.js","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EnsurePackageBundledContext } from '@ms-cloudpack/api-server';\nimport {\n isBootstrapRoute,\n isProxyRoute,\n isRedirectRoute,\n isRenderedRoute,\n isStaticRoute,\n type BootstrapRoute,\n type PackageJson,\n type RenderedRoute,\n type ExpandedRenderFunctionResult,\n type RequestHandler,\n} from '@ms-cloudpack/common-types';\nimport type { Express, Request, Response } from '@ms-cloudpack/create-express-app';\nimport { express } from '@ms-cloudpack/create-express-app';\nimport { createImportMap } from '@ms-cloudpack/import-map';\nimport { slash } from '@ms-cloudpack/path-string-parsing';\nimport path from 'path';\nimport { getInlineScripts } from '@ms-cloudpack/inline-scripts';\nimport { renderRoute } from './renderRoute/renderRoute.js';\nimport { renderBootstrapRoute } from './renderBootstrapRoute.js';\nimport { createPageSessionContext } from './createPageSessionContext.js';\nimport { parseRouteMatch } from './parseRouteMatch.js';\nimport { createProxyMiddleware } from 'http-proxy-middleware';\n\ninterface CreateRoutesOptions {\n app: Express;\n /** Base app server URL, including the port */\n url: string;\n /** package.json for the app */\n definition: PackageJson;\n}\n\n/** Express middleware that will match the domain to continue the route execution. */\nfunction matchDomain(domain?: RegExp): RequestHandler {\n return (req, _, next) => {\n if (!domain || domain.test(req.hostname)) {\n return next();\n }\n\n // Jump to next route.\n return next('route');\n };\n}\n\n/**\n * Creates the routes for the express app based on the config.\n */\nexport function createRoutes(options: CreateRoutesOptions, context: EnsurePackageBundledContext): void {\n const { app, ...otherOptions } = options;\n const { session } = context;\n const { config } = session;\n const { appPath } = config;\n const routes = [...(session.config.routes || [])];\n\n if (!options.definition.name) {\n throw new Error('Name field is required in the package.json file.');\n }\n\n if (!routes.length) {\n // If no routes have been defined, register a default (no path) route only.\n // This will render default HTML which includes the app's default path ('.') from the import map.\n routes.push({\n match: '/',\n // sourcePath and requestPath are required by types but unused in this case.\n entry: [{ importPath: '.', sourcePath: '' }],\n });\n }\n\n for (const route of routes) {\n const matches = Array.isArray(route.match) ? route.match : [route.match];\n\n for (const match of matches) {\n const { pathMatch, domainMatch } = parseRouteMatch(match);\n\n if (isStaticRoute(route)) {\n const resolvedPath = path.resolve(appPath, route.staticPath);\n app.use(\n pathMatch,\n matchDomain(domainMatch),\n // Express v5 default behavior with dotfiles is to ignore them (404), even if the original\n // path had a directory with a leading dot...\n express.static(resolvedPath, { dotfiles: slash(resolvedPath).includes('/.') ? 'allow' : 'deny' }),\n );\n } else if (isRedirectRoute(route)) {\n app.get(pathMatch, matchDomain(domainMatch), (req, res) => {\n res.redirect(route.redirectTo);\n });\n } else if (isRenderedRoute(route) || isBootstrapRoute(route)) {\n app.get(pathMatch, matchDomain(domainMatch), (req, res) => {\n _handleRequest({ ...otherOptions, route, req, res }, context).catch((err) => {\n console.error((err as Error)?.stack || err);\n res.status(500).send(`Error loading app: ${err}`);\n });\n });\n } else if (isProxyRoute(route)) {\n app.use(\n pathMatch,\n matchDomain(domainMatch),\n createProxyMiddleware({ target: route.proxyTo, ...route }) as RequestHandler,\n );\n } else {\n throw new Error(`Unknown route: ${JSON.stringify(route)}`);\n }\n }\n }\n}\n\nexport async function _handleRequest(\n options: Omit<CreateRoutesOptions, 'app'> & {\n req: Request;\n res: Response;\n route: RenderedRoute | BootstrapRoute;\n },\n context: EnsurePackageBundledContext,\n): Promise<void> {\n const { req, res, definition, route, url: baseUrl } = options;\n const { session } = context;\n\n // Build the import map if it hasn't been built yet for this session version.\n // (TS can't infer that urls.bundleServer is set on session from a check above.)\n const importMap = (session.importMap ??= await createImportMap({ targetEnvironment: 'browser' }, context));\n\n // For production mode, no overlay script.\n const overlayScript = session.config.mode !== 'production' ? importMap.imports['@ms-cloudpack/overlay'] : undefined;\n\n // Get all entry scripts for the route.\n const entryScripts = route.entry?.map(\n (entry) => importMap.imports[slash(path.join(definition.name, entry.importPath))],\n );\n\n const pageSessionContextScript =\n `window.__cloudpack ??= {};\\n` +\n `window.__cloudpack.pageSessionContext = ${JSON.stringify(createPageSessionContext(session, req.path))};`;\n\n // Add the pageSessionContext to the top of the inlineScripts\n const inlineScripts = [pageSessionContextScript, ...(await getInlineScripts())];\n\n // Set the appropriate Cloudpack headers in the response.\n res.header('Cache-Control', 'no-cache');\n res.header('Access-Control-Allow-Origin', '*');\n\n let rendered: ExpandedRenderFunctionResult | null;\n\n if (isBootstrapRoute(route)) {\n // Render the bootstrap script\n rendered = await renderBootstrapRoute({\n session,\n importMap,\n overlayScript,\n inlineScripts,\n entryScripts,\n requestPath: req.path,\n });\n } else {\n // HTML or custom script rendering\n rendered = await renderRoute({ ...options, baseUrl, overlayScript, inlineScripts, entryScripts }, context);\n }\n\n if (rendered) {\n res.type(rendered.contentType).status(rendered.statusCode).send(rendered.content).end();\n }\n\n console.debug(`App server: Request: ${req.path}`);\n}\n"]}
1
+ {"version":3,"file":"createRoutes.js","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EnsurePackageBundledContext } from '@ms-cloudpack/api-server';\nimport {\n isBootstrapRoute,\n isProxyRoute,\n isRedirectRoute,\n isRenderedRoute,\n isStaticRoute,\n type BootstrapRoute,\n type PackageJson,\n type RenderedRoute,\n type ExpandedRenderFunctionResult,\n type RequestHandler,\n} from '@ms-cloudpack/common-types';\nimport type { Express, Request, Response } from '@ms-cloudpack/create-express-app';\nimport { express } from '@ms-cloudpack/create-express-app';\nimport { createImportMap } from '@ms-cloudpack/import-map';\nimport { slash } from '@ms-cloudpack/path-string-parsing';\nimport path from 'path';\nimport { getInlineScripts } from '@ms-cloudpack/inline-scripts';\nimport { renderRoute } from './renderRoute/renderRoute.js';\nimport { renderBootstrapRoute } from './renderBootstrapRoute.js';\nimport { createPageSessionContext } from './createPageSessionContext.js';\nimport { parseRouteMatch } from './parseRouteMatch.js';\nimport { createProxyMiddleware } from 'http-proxy-middleware';\n\ninterface CreateRoutesOptions {\n app: Express;\n /** Base app server URL, including the port */\n url: string;\n /** package.json for the app */\n definition: PackageJson;\n}\n\n/** Express middleware that will match the domain to continue the route execution. */\nfunction matchDomain(domain?: RegExp): RequestHandler {\n return (req, _, next) => {\n if (!domain || domain.test(req.hostname)) {\n return next();\n }\n\n // Jump to next route.\n return next('route');\n };\n}\n\n/**\n * Creates the routes for the express app based on the config.\n */\nexport function createRoutes(options: CreateRoutesOptions, context: EnsurePackageBundledContext): void {\n const { app, ...otherOptions } = options;\n const { session } = context;\n const { config } = session;\n const { appPath } = config;\n const routes = [...(session.config.routes || [])];\n\n if (!options.definition.name) {\n throw new Error('Name field is required in the package.json file.');\n }\n\n if (!routes.length) {\n // If no routes have been defined, register a default (no path) route only.\n // This will render default HTML which includes the app's default path ('.') from the import map.\n routes.push({\n match: '/',\n // sourcePath and requestPath are required by types but unused in this case.\n entry: [{ importPath: '.', sourcePath: '' }],\n });\n }\n\n for (const route of routes) {\n const matches = Array.isArray(route.match) ? route.match : [route.match];\n\n for (const match of matches) {\n const { pathMatch, domainMatch } = parseRouteMatch(match);\n\n if (isStaticRoute(route)) {\n const resolvedPath = path.resolve(appPath, route.staticPath);\n app.use(\n pathMatch,\n matchDomain(domainMatch),\n // Express v5 default behavior with dotfiles is to ignore them (404), even if the original\n // path had a directory with a leading dot...\n express.static(resolvedPath, { dotfiles: slash(resolvedPath).includes('/.') ? 'allow' : 'deny' }),\n );\n } else if (isRedirectRoute(route)) {\n app.get(pathMatch, matchDomain(domainMatch), (req, res) => {\n console.debug(`App server: redirect ${req.url} to ${route.redirectTo}`);\n res.redirect(route.redirectTo);\n });\n } else if (isRenderedRoute(route) || isBootstrapRoute(route)) {\n app.get(pathMatch, matchDomain(domainMatch), (req, res) => {\n _handleRequest({ ...otherOptions, route, req, res }, context).catch((err) => {\n console.error((err as Error)?.stack || err);\n res.status(500).send(`Error loading app: ${err}`);\n });\n });\n } else if (isProxyRoute(route)) {\n app.use(\n pathMatch,\n matchDomain(domainMatch),\n createProxyMiddleware({ target: route.proxyTo, ...route }) as RequestHandler,\n );\n } else {\n throw new Error(`Unknown route: ${JSON.stringify(route)}`);\n }\n }\n }\n}\n\nexport async function _handleRequest(\n options: Omit<CreateRoutesOptions, 'app'> & {\n req: Request;\n res: Response;\n route: RenderedRoute | BootstrapRoute;\n },\n context: EnsurePackageBundledContext,\n): Promise<void> {\n const { req, res, definition, route, url: baseUrl } = options;\n const { session } = context;\n const isBootstrap = isBootstrapRoute(route);\n console.debug(`App server: ${isBootstrap ? 'bootstrap' : 'render'} request ${req.path}`);\n\n // Build the import map if it hasn't been built yet for this session version.\n // (TS can't infer that urls.bundleServer is set on session from a check above.)\n const importMap = (session.importMap ??= await createImportMap({ targetEnvironment: 'browser' }, context));\n\n // For production mode, no overlay script.\n const overlayScript = session.config.mode !== 'production' ? importMap.imports['@ms-cloudpack/overlay'] : undefined;\n\n // Get all entry scripts for the route.\n const entryScripts = route.entry?.map(\n (entry) => importMap.imports[slash(path.join(definition.name, entry.importPath))],\n );\n\n const pageSessionContextScript =\n `window.__cloudpack ??= {};\\n` +\n `window.__cloudpack.pageSessionContext = ${JSON.stringify(createPageSessionContext(session, req.path))};`;\n\n // Add the pageSessionContext to the top of the inlineScripts\n const inlineScripts = [pageSessionContextScript, ...(await getInlineScripts())];\n\n // Set the appropriate Cloudpack headers in the response.\n res.header('Cache-Control', 'no-cache');\n res.header('Access-Control-Allow-Origin', '*');\n\n let rendered: ExpandedRenderFunctionResult | null;\n\n if (isBootstrap) {\n // Render the bootstrap script\n rendered = await renderBootstrapRoute({\n session,\n importMap,\n overlayScript,\n inlineScripts,\n entryScripts,\n requestPath: req.path,\n });\n } else {\n // HTML or custom script rendering\n rendered = await renderRoute({ ...options, baseUrl, overlayScript, inlineScripts, entryScripts }, context);\n }\n\n if (rendered) {\n res.type(rendered.contentType).status(rendered.statusCode).send(rendered.content).end();\n }\n\n console.debug(`App server: response ${req.path}`);\n}\n"]}
@@ -0,0 +1,37 @@
1
+ import type { ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';
2
+ import type { RenderRouteOptions } from '../types/RenderRouteOptions.js';
3
+ /**
4
+ * Custom render error handler function. See `message` for the format details.
5
+ *
6
+ * The result will be HTML if that's acceptable for the request, or plain text otherwise.
7
+ */
8
+ export type CustomRenderErrorHandler = (params: {
9
+ /**
10
+ * Error details. Note the context/format in the final string:
11
+ *
12
+ * - Default renderer: `Route ${route.match} with the default renderer ${message}`
13
+ * - `renderScript` route: `Route ${route.match} with renderScript "${route.renderScript}" ${message}`
14
+ * - `serverEntry` route:
15
+ * - Bundled:
16
+ * `Route ${route.match} with serverEntry "${route.serverEntry}" (bundled: "${bundledPath}") ${message}`
17
+ * - Not bundled, or the fact that it's bundled is irrelevant for this issue:
18
+ * `Route ${route.match} with serverEntry "${route.serverEntry}" ${message}`
19
+ */
20
+ message: string;
21
+ /** Extra message for the console only. */
22
+ extraLog?: string;
23
+ /**
24
+ * If the error is about the bundled `serverEntry`, this is its absolute path.
25
+ * Only pass this if the issue is specifically about the bundled output.
26
+ * (If the route uses `renderScript`, it's safe to pass that path for convenience, but it's ignored.)
27
+ */
28
+ bundledPath?: string;
29
+ }) => ExpandedRenderFunctionResult;
30
+ /**
31
+ * Get an error handler callback for the current request.
32
+ * This reduces the number of params that need to be passed to the handler, improving readability.
33
+ */
34
+ export declare function getCustomRenderErrorHandler(params: Pick<RenderRouteOptions, 'req' | 'route'> & {
35
+ appPath: string;
36
+ }): CustomRenderErrorHandler;
37
+ //# sourceMappingURL=getCustomRenderErrorHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getCustomRenderErrorHandler.d.ts","sourceRoot":"","sources":["../../src/renderRoute/getCustomRenderErrorHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAIzE;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,MAAM,EAAE;IAC9C;;;;;;;;;;OAUG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,KAAK,4BAA4B,CAAC;AAEnC;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,OAAO,CAAC,GAAG;IAClD,OAAO,EAAE,MAAM,CAAC;CACjB,GACA,wBAAwB,CA+C1B"}
@@ -0,0 +1,51 @@
1
+ import { isHtmlAcceptable } from './isHtmlAcceptable.js';
2
+ import path from 'path';
3
+ /**
4
+ * Get an error handler callback for the current request.
5
+ * This reduces the number of params that need to be passed to the handler, improving readability.
6
+ */
7
+ export function getCustomRenderErrorHandler(params) {
8
+ const { req, route, appPath } = params;
9
+ return (handlerParams) => {
10
+ const { message: extraMessage, extraLog, bundledPath } = handlerParams;
11
+ let renderer;
12
+ if (route.serverEntry) {
13
+ renderer = `serverEntry "${path.resolve(appPath, route.serverEntry.sourcePath)}"`;
14
+ if (bundledPath) {
15
+ renderer += ` (bundled: "${bundledPath}")`;
16
+ }
17
+ // eslint-disable-next-line etc/no-deprecated
18
+ }
19
+ else if (route.renderScript) {
20
+ // eslint-disable-next-line etc/no-deprecated
21
+ renderer = `renderScript "${path.resolve(appPath, route.renderScript)}"`;
22
+ }
23
+ else {
24
+ renderer = 'the default renderer';
25
+ }
26
+ const space = /^[:\n]/.test(extraMessage) ? '' : ' ';
27
+ const finalMessage = `Route ${JSON.stringify(route.match)} with ${renderer}${space}${extraMessage}`;
28
+ console.error(`App server: request ${req.url} - ${finalMessage}${extraLog ? `\n${extraLog}` : ''}`);
29
+ if (!isHtmlAcceptable(req)) {
30
+ // If HTML is not acceptable, return a plain text response
31
+ return {
32
+ content: finalMessage,
33
+ statusCode: 500,
34
+ contentType: 'text/plain',
35
+ };
36
+ }
37
+ return {
38
+ content: `<!DOCTYPE html>
39
+ <html lang="en">
40
+ <head><title>Error</title></head>
41
+ <body>
42
+ <h1>Error</h1>
43
+ <pre style="white-space: pre-wrap">${finalMessage}</pre>
44
+ </body>
45
+ </html>`,
46
+ statusCode: 500,
47
+ contentType: 'text/html',
48
+ };
49
+ };
50
+ }
51
+ //# sourceMappingURL=getCustomRenderErrorHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getCustomRenderErrorHandler.js","sourceRoot":"","sources":["../../src/renderRoute/getCustomRenderErrorHandler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,IAAI,MAAM,MAAM,CAAC;AA8BxB;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAEC;IAED,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAEvC,OAAO,CAAC,aAAa,EAAE,EAAE;QACvB,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,aAAa,CAAC;QAEvE,IAAI,QAAgB,CAAC;QACrB,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,QAAQ,GAAG,gBAAgB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC;YAClF,IAAI,WAAW,EAAE,CAAC;gBAChB,QAAQ,IAAI,eAAe,WAAW,IAAI,CAAC;YAC7C,CAAC;YACD,6CAA6C;QAC/C,CAAC;aAAM,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YAC9B,6CAA6C;YAC7C,QAAQ,GAAG,iBAAiB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC;QAC3E,CAAC;aAAM,CAAC;YACN,QAAQ,GAAG,sBAAsB,CAAC;QACpC,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACrD,MAAM,YAAY,GAAG,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,QAAQ,GAAG,KAAK,GAAG,YAAY,EAAE,CAAC;QAEpG,OAAO,CAAC,KAAK,CAAC,uBAAuB,GAAG,CAAC,GAAG,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAEpG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,0DAA0D;YAC1D,OAAO;gBACL,OAAO,EAAE,YAAY;gBACrB,UAAU,EAAE,GAAG;gBACf,WAAW,EAAE,YAAY;aAC1B,CAAC;QACJ,CAAC;QAED,OAAO;YACL,OAAO,EAAE;;;;;uCAKwB,YAAY;;QAE3C;YACF,UAAU,EAAE,GAAG;YACf,WAAW,EAAE,WAAW;SACzB,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';\nimport type { RenderRouteOptions } from '../types/RenderRouteOptions.js';\nimport { isHtmlAcceptable } from './isHtmlAcceptable.js';\nimport path from 'path';\n\n/**\n * Custom render error handler function. See `message` for the format details.\n *\n * The result will be HTML if that's acceptable for the request, or plain text otherwise.\n */\nexport type CustomRenderErrorHandler = (params: {\n /**\n * Error details. Note the context/format in the final string:\n *\n * - Default renderer: `Route ${route.match} with the default renderer ${message}`\n * - `renderScript` route: `Route ${route.match} with renderScript \"${route.renderScript}\" ${message}`\n * - `serverEntry` route:\n * - Bundled:\n * `Route ${route.match} with serverEntry \"${route.serverEntry}\" (bundled: \"${bundledPath}\") ${message}`\n * - Not bundled, or the fact that it's bundled is irrelevant for this issue:\n * `Route ${route.match} with serverEntry \"${route.serverEntry}\" ${message}`\n */\n message: string;\n /** Extra message for the console only. */\n extraLog?: string;\n /**\n * If the error is about the bundled `serverEntry`, this is its absolute path.\n * Only pass this if the issue is specifically about the bundled output.\n * (If the route uses `renderScript`, it's safe to pass that path for convenience, but it's ignored.)\n */\n bundledPath?: string;\n}) => ExpandedRenderFunctionResult;\n\n/**\n * Get an error handler callback for the current request.\n * This reduces the number of params that need to be passed to the handler, improving readability.\n */\nexport function getCustomRenderErrorHandler(\n params: Pick<RenderRouteOptions, 'req' | 'route'> & {\n appPath: string;\n },\n): CustomRenderErrorHandler {\n const { req, route, appPath } = params;\n\n return (handlerParams) => {\n const { message: extraMessage, extraLog, bundledPath } = handlerParams;\n\n let renderer: string;\n if (route.serverEntry) {\n renderer = `serverEntry \"${path.resolve(appPath, route.serverEntry.sourcePath)}\"`;\n if (bundledPath) {\n renderer += ` (bundled: \"${bundledPath}\")`;\n }\n // eslint-disable-next-line etc/no-deprecated\n } else if (route.renderScript) {\n // eslint-disable-next-line etc/no-deprecated\n renderer = `renderScript \"${path.resolve(appPath, route.renderScript)}\"`;\n } else {\n renderer = 'the default renderer';\n }\n\n const space = /^[:\\n]/.test(extraMessage) ? '' : ' ';\n const finalMessage = `Route ${JSON.stringify(route.match)} with ${renderer}${space}${extraMessage}`;\n\n console.error(`App server: request ${req.url} - ${finalMessage}${extraLog ? `\\n${extraLog}` : ''}`);\n\n if (!isHtmlAcceptable(req)) {\n // If HTML is not acceptable, return a plain text response\n return {\n content: finalMessage,\n statusCode: 500,\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 style=\"white-space: pre-wrap\">${finalMessage}</pre>\n</body>\n</html>`,\n statusCode: 500,\n contentType: 'text/html',\n };\n };\n}\n"]}
@@ -2,7 +2,7 @@ import { javascriptExtensions, sourceExtensions, typescriptExtensions } from '@m
2
2
  import fsPromises from 'fs/promises';
3
3
  import path from 'path';
4
4
  import { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';
5
- import { getCustomRenderErrorResponse, getErrorResponse } from './getErrorResponse.js';
5
+ import { getCustomRenderErrorHandler } from './getCustomRenderErrorHandler.js';
6
6
  import { injectScripts } from './injectScripts.js';
7
7
  import { isHtmlAcceptable, isHtmlType } from './isHtmlAcceptable.js';
8
8
  import { bundleServerEntry } from './ssr/bundleServerEntry.js';
@@ -14,14 +14,15 @@ import { renderCustomScript } from './ssr/renderCustomScript.js';
14
14
  export async function renderRoute(options, context) {
15
15
  const { route } = options;
16
16
  const { appPath, features } = context.session.config;
17
- // Get the script to render (if any). Prefer serverEntry if provided.
18
- // eslint-disable-next-line etc/no-deprecated
19
- let renderScriptPath = route.renderScript && path.resolve(appPath, route.renderScript);
17
+ const handleCustomRenderError = getCustomRenderErrorHandler({ req: options.req, route, appPath });
18
+ /** Absolute custom render script path (if any). For `serverEntry`, this is the bundled path. */
19
+ let renderScriptPath;
20
+ // Prefer serverEntry if given
20
21
  if (route.serverEntry) {
21
- const serverEntryExt = path.extname(route.serverEntry.sourcePath).toLowerCase();
22
- if (sourceExtensions.includes(serverEntryExt)) {
22
+ const serverEntryPath = path.resolve(appPath, route.serverEntry.sourcePath);
23
+ if (sourceExtensions.includes(path.extname(serverEntryPath).toLowerCase())) {
23
24
  // Server entry is a source file, so bundle it
24
- const serverEntryResult = await bundleServerEntry({ ...options, serverEntryPath: route.serverEntry.sourcePath }, context);
25
+ const serverEntryResult = await bundleServerEntry({ definition: options.definition, handleCustomRenderError, serverEntryPath }, context);
25
26
  if (typeof serverEntryResult === 'string') {
26
27
  renderScriptPath = serverEntryResult;
27
28
  }
@@ -32,20 +33,32 @@ export async function renderRoute(options, context) {
32
33
  }
33
34
  else {
34
35
  // HTML or something, so use it as-is
35
- renderScriptPath = path.resolve(appPath, route.serverEntry.sourcePath);
36
+ renderScriptPath = path.resolve(appPath, serverEntryPath);
36
37
  }
37
38
  }
39
+ else {
40
+ // Fall back to renderScript or the default renderer
41
+ // eslint-disable-next-line etc/no-deprecated
42
+ renderScriptPath = route.renderScript && path.resolve(appPath, route.renderScript);
43
+ }
38
44
  // Get an initial result from either the renderScript/serverEntry or default handler.
39
45
  let rawResult;
40
46
  if (renderScriptPath) {
41
47
  const scriptExt = path.extname(renderScriptPath).toLowerCase();
42
48
  if (scriptExt === '.html' || scriptExt === '.htm') {
43
49
  // HTML file: just read it
44
- rawResult = await readStaticHtml({ renderScriptPath, req: options.req });
50
+ try {
51
+ rawResult = (await fsPromises.readFile(renderScriptPath, 'utf8')) || '';
52
+ }
53
+ catch (e) {
54
+ return handleCustomRenderError({
55
+ message: `could not be read:\n${e.message || e}`,
56
+ });
57
+ }
45
58
  }
46
59
  else if (javascriptExtensions.includes(scriptExt)) {
47
- // JS file: run its function
48
- rawResult = await renderCustomScript({ ...options, renderScriptPath }, context);
60
+ // JS file (potentially was bundled): run its function
61
+ rawResult = await renderCustomScript({ ...options, renderScriptPath, handleCustomRenderError }, context);
49
62
  }
50
63
  else {
51
64
  // Other file type: error. Technically we could just serve it as-is following the non-HTML
@@ -57,16 +70,11 @@ export async function renderRoute(options, context) {
57
70
  }
58
71
  else {
59
72
  message +=
60
- `Supported file types are ${route.serverEntry ? 'TS, JS, or HTML' : 'JS or HTML (use `serverEntry` for TS)'}. ` +
73
+ `\n\nSupported file types are ${route.serverEntry ? 'TS, JS, or HTML' : 'JS or HTML (use `serverEntry` for TS)'}. ` +
61
74
  'If you want to serve a static non-HTML file, use a route with `staticPath`, or a `serverEntry` ' +
62
75
  'which returns the file content for advanced scenarios.';
63
76
  }
64
- return getCustomRenderErrorResponse({
65
- message,
66
- route,
67
- req: options.req,
68
- scriptPath: renderScriptPath,
69
- });
77
+ return handleCustomRenderError({ message });
70
78
  }
71
79
  }
72
80
  else {
@@ -75,22 +83,17 @@ export async function renderRoute(options, context) {
75
83
  if (rawResult === null) {
76
84
  if (features?.enableSSR) {
77
85
  // null is not valid in new SSR mode
78
- return getCustomRenderErrorResponse({
86
+ // (technically this error came from the bundled script, but that's irrelevant to the issue)
87
+ return handleCustomRenderError({
79
88
  message: `returned null, which is not supported with the "enableSSR" feature.`,
80
- route,
81
- req: options.req,
82
- scriptPath: renderScriptPath,
83
89
  });
84
90
  }
85
91
  // This means the custom renderScript fully handled the request.
86
92
  return null;
87
93
  }
88
94
  if (rawResult === undefined) {
89
- return getCustomRenderErrorResponse({
95
+ return handleCustomRenderError({
90
96
  message: `returned undefined. Ensure that the function returns the content to be rendered.`,
91
- route,
92
- req: options.req,
93
- scriptPath: renderScriptPath,
94
97
  });
95
98
  }
96
99
  const result = {
@@ -116,30 +119,12 @@ export async function renderRoute(options, context) {
116
119
  else {
117
120
  // It appears we're accidentally returning HTML when a different file type was requested.
118
121
  // This could happen for overly broad route matches such as '*', or if no routes are configured.
119
- return getCustomRenderErrorResponse({
122
+ return handleCustomRenderError({
120
123
  message: 'returned HTML, but this appears to be a non-HTML request. This is likely due to an ' +
121
124
  'overly broad match or other misconfiguration of server.routes in the cloudpack config.',
122
- route,
123
- scriptPath: renderScriptPath,
124
- req: options.req,
125
125
  });
126
126
  }
127
127
  }
128
128
  return result;
129
129
  }
130
- /**
131
- * Read a static HTML file, or return an error response if the file can't be read.
132
- */
133
- async function readStaticHtml(params) {
134
- const { renderScriptPath, req } = params;
135
- try {
136
- return (await fsPromises.readFile(renderScriptPath, 'utf8')) || '';
137
- }
138
- catch (e) {
139
- return getErrorResponse({
140
- req,
141
- message: `Error reading HTML file at "${renderScriptPath}":\n${e.stack || e}`,
142
- });
143
- }
144
- }
145
130
  //# 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 EnsurePackageBundledContext } from '@ms-cloudpack/api-server';\nimport type { ExpandedRenderFunctionResult, RenderFunctionResult } 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';\nimport type { RenderRouteOptions } from '../types/RenderRouteOptions.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: RenderRouteOptions,\n context: EnsurePackageBundledContext,\n): Promise<ExpandedRenderFunctionResult | null> {\n const { route } = options;\n const { appPath, features } = context.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 }, context);\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 = getDefaultHtmlResponse(options);\n }\n\n if (rawResult === null) {\n if (features?.enableSSR) {\n // null is not valid in new SSR mode\n return getCustomRenderErrorResponse({\n message: `returned null, which is not supported with the \"enableSSR\" feature.`,\n route,\n req: options.req,\n scriptPath: renderScriptPath,\n });\n }\n // This means the custom renderScript fully handled the request.\n return null;\n }\n\n if (rawResult === undefined) {\n return getCustomRenderErrorResponse({\n message: `returned undefined. 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 // Add any custom headers (for success or error)\n if (result.headers) {\n for (const [key, value] of Object.entries(result.headers)) {\n options.res.header(key, value);\n }\n }\n\n // If it's HTML and a success statusCode, inject the import map and appropriate scripts.\n if (\n result.injectScripts !== false &&\n result.statusCode === 200 &&\n typeof result.content === 'string' &&\n isHtmlType(result.contentType)\n ) {\n if (isHtmlAcceptable(options.req)) {\n // Inject the import map and scripts.\n await injectScripts(options, result, context);\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<RenderRouteOptions, '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"]}
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 { ExpandedRenderFunctionResult, RenderFunctionResult } 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 { getCustomRenderErrorHandler } from './getCustomRenderErrorHandler.js';\nimport { injectScripts } from './injectScripts.js';\nimport { isHtmlAcceptable, isHtmlType } from './isHtmlAcceptable.js';\nimport { bundleServerEntry } from './ssr/bundleServerEntry.js';\nimport { renderCustomScript } from './ssr/renderCustomScript.js';\nimport type { RenderRouteOptions } from '../types/RenderRouteOptions.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: RenderRouteOptions,\n context: EnsurePackageBundledContext,\n): Promise<ExpandedRenderFunctionResult | null> {\n const { route } = options;\n const { appPath, features } = context.session.config;\n\n const handleCustomRenderError = getCustomRenderErrorHandler({ req: options.req, route, appPath });\n\n /** Absolute custom render script path (if any). For `serverEntry`, this is the bundled path. */\n let renderScriptPath: string | undefined;\n\n // Prefer serverEntry if given\n if (route.serverEntry) {\n const serverEntryPath = path.resolve(appPath, route.serverEntry.sourcePath);\n\n if (sourceExtensions.includes(path.extname(serverEntryPath).toLowerCase())) {\n // Server entry is a source file, so bundle it\n const serverEntryResult = await bundleServerEntry(\n { definition: options.definition, handleCustomRenderError, serverEntryPath },\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, serverEntryPath);\n }\n } else {\n // Fall back to renderScript or the default renderer\n // eslint-disable-next-line etc/no-deprecated\n renderScriptPath = route.renderScript && path.resolve(appPath, route.renderScript);\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 try {\n rawResult = (await fsPromises.readFile(renderScriptPath, 'utf8')) || '';\n } catch (e) {\n return handleCustomRenderError({\n message: `could not be read:\\n${(e as Error).message || e}`,\n });\n }\n } else if (javascriptExtensions.includes(scriptExt)) {\n // JS file (potentially was bundled): run its function\n rawResult = await renderCustomScript({ ...options, renderScriptPath, handleCustomRenderError }, context);\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 `\\n\\nSupported 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 handleCustomRenderError({ message });\n }\n } else {\n rawResult = getDefaultHtmlResponse(options);\n }\n\n if (rawResult === null) {\n if (features?.enableSSR) {\n // null is not valid in new SSR mode\n // (technically this error came from the bundled script, but that's irrelevant to the issue)\n return handleCustomRenderError({\n message: `returned null, which is not supported with the \"enableSSR\" feature.`,\n });\n }\n // This means the custom renderScript fully handled the request.\n return null;\n }\n\n if (rawResult === undefined) {\n return handleCustomRenderError({\n message: `returned undefined. Ensure that the function returns the content to be rendered.`,\n });\n }\n\n const result: ExpandedRenderFunctionResult = {\n contentType: 'text/html',\n statusCode: 200,\n ...(typeof rawResult === 'string' ? { content: rawResult } : rawResult),\n };\n\n // Add any custom headers (for success or error)\n if (result.headers) {\n for (const [key, value] of Object.entries(result.headers)) {\n options.res.header(key, value);\n }\n }\n\n // If it's HTML and a success statusCode, inject the import map and appropriate scripts.\n if (\n result.injectScripts !== false &&\n result.statusCode === 200 &&\n typeof result.content === 'string' &&\n isHtmlType(result.contentType)\n ) {\n if (isHtmlAcceptable(options.req)) {\n // Inject the import map and scripts.\n await injectScripts(options, result, context);\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 handleCustomRenderError({\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 });\n }\n }\n\n return result;\n}\n"]}
@@ -1,8 +1,14 @@
1
1
  import { type EnsurePackageBundledContext } from '@ms-cloudpack/api-server';
2
2
  import type { ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';
3
3
  import type { RenderRouteOptions } from '../../types/RenderRouteOptions.js';
4
- export declare function bundleServerEntry(options: Pick<RenderRouteOptions, 'definition' | 'route' | 'req'> & {
5
- /** Relative serverEntry source path from the route */
4
+ import type { CustomRenderErrorHandler } from '../getCustomRenderErrorHandler.js';
5
+ /**
6
+ * Bundle the `serverEntry` script.
7
+ * @returns Absolute path to the bundled file, or an error response if bundling failed.
8
+ */
9
+ export declare function bundleServerEntry(options: Pick<RenderRouteOptions, 'definition'> & {
10
+ /** Absolute path to the original serverEntry file from the route */
6
11
  serverEntryPath: string;
12
+ handleCustomRenderError: CustomRenderErrorHandler;
7
13
  }, context: EnsurePackageBundledContext): Promise<string | ExpandedRenderFunctionResult>;
8
14
  //# sourceMappingURL=bundleServerEntry.d.ts.map
@@ -1 +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,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAM/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAE5E,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,GAAG,OAAO,GAAG,KAAK,CAAC,GAAG;IAClE,sDAAsD;IACtD,eAAe,EAAE,MAAM,CAAC;CACzB,EACD,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,MAAM,GAAG,4BAA4B,CAAC,CAwDhD"}
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,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAM/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAElF;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,CAAC,GAAG;IAChD,oEAAoE;IACpE,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,EAAE,wBAAwB,CAAC;CACnD,EACD,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,MAAM,GAAG,4BAA4B,CAAC,CAiEhD"}
@@ -1,51 +1,58 @@
1
1
  import { ensurePackageBundled } from '@ms-cloudpack/api-server';
2
2
  import { environmentInfo } from '@ms-cloudpack/environment';
3
+ import { slash } from '@ms-cloudpack/path-string-parsing';
3
4
  import { sourceToIntermediatePath } from '@ms-cloudpack/path-utilities';
4
5
  import { bulletedList } from '@ms-cloudpack/task-reporter';
5
6
  import path from 'path';
6
- import { getCustomRenderErrorResponse } from '../getErrorResponse.js';
7
+ /**
8
+ * Bundle the `serverEntry` script.
9
+ * @returns Absolute path to the bundled file, or an error response if bundling failed.
10
+ */
7
11
  export async function bundleServerEntry(options, context) {
8
- const { definition, route, req, serverEntryPath } = options;
12
+ const { definition, serverEntryPath, handleCustomRenderError } = options;
9
13
  const { name, version } = definition;
14
+ const { config } = context.session;
10
15
  // targetEnvironment: node currently bundles all node or agnostic entries, and implicitly does
11
16
  // production bundling. This seems like it could get expensive, but it's probably okay because
12
17
  // createPackageSettingsTransform overwrites the package's actual exports with the ones generated
13
18
  // from routes, and those will always have environment conditions.
19
+ const isEffectivelyProduction = config.mode === 'production' || !config.features?.enableSSR;
14
20
  const response = await ensurePackageBundled({
15
21
  name,
16
22
  version,
17
23
  targetEnvironment: 'node',
18
24
  // TODO: This will only watch for changes in the given package, not dependencies.
19
25
  // File watching is disabled in production mode for performance.
20
- shouldWatch: !environmentInfo.isJest && context.session.config.mode !== 'production',
21
- // Irrelevant with implicit production bundling
26
+ shouldWatch: !environmentInfo.isJest && !isEffectivelyProduction,
27
+ // In library mode, wait to bundle dependencies until they're requested, since the resolve map
28
+ // is very likely to include extra deps that are irrelevant for the serverEntry.
22
29
  enqueueDependencies: false,
23
30
  }, context);
24
31
  const { errors, outputPath = '', outputFiles = [] } = response.result;
25
32
  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,
33
+ // The bundle errors will have been logged to the console on bundle task failure,
34
+ // so don't log them again and skip trying to format them for the UI.
35
+ return handleCustomRenderError({
36
+ message: `failed to bundle (see the console for details)`,
31
37
  });
32
38
  }
33
- let serverEntryOutput = outputFiles.find((f) => f.entryPoint === serverEntryPath)?.outputPath;
39
+ // TODO: Issues have been reported with this. Needs investigation and a test...
40
+ let serverEntryOutput = outputFiles.find((f) => f.entryPoint && slash(serverEntryPath) === slash(path.resolve(config.appPath, f.entryPoint)))?.outputPath;
34
41
  if (!serverEntryOutput) {
35
42
  // 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;
43
+ const intermediateServerEntryPath = sourceToIntermediatePath(path.relative(config.appPath, serverEntryPath)).replace(/\.\w+$/, '');
44
+ serverEntryOutput = outputFiles.find((f) => intermediateServerEntryPath.endsWith(f.outputPath.replace(/\.\w+$/, '')))?.outputPath;
40
45
  }
41
46
  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,
47
+ return handleCustomRenderError({
48
+ message: `- tried to bundle the serverEntry script, but couldn't find a corresponding file ` +
49
+ `in the bundle output (see console for details).`,
50
+ extraLog: `Entries (output file to source file): ${JSON.stringify(response.result.entries || {})}\n` +
51
+ `Output path: ${outputPath}\n` +
52
+ 'Bundled files: ' +
53
+ (outputFiles.length
54
+ ? '\n' + bulletedList(outputFiles.map((f) => `${f.outputPath} (entry: ${f.entryPoint || 'unknown'})`))
55
+ : 'none'),
49
56
  });
50
57
  }
51
58
  // Translate relative-to-output path into an absolute path.
@@ -1 +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;AAGtE,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 { 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';\nimport type { RenderRouteOptions } from '../../types/RenderRouteOptions.js';\n\nexport async function bundleServerEntry(\n options: Pick<RenderRouteOptions, '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 // 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"]}
1
+ {"version":3,"file":"bundleServerEntry.js","sourceRoot":"","sources":["../../../src/renderRoute/ssr/bundleServerEntry.ts"],"names":[],"mappings":"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","sourcesContent":["import { type EnsurePackageBundledContext, ensurePackageBundled } from '@ms-cloudpack/api-server';\nimport type { ExpandedRenderFunctionResult } from '@ms-cloudpack/common-types';\nimport { environmentInfo } from '@ms-cloudpack/environment';\nimport { slash } from '@ms-cloudpack/path-string-parsing';\nimport { sourceToIntermediatePath } from '@ms-cloudpack/path-utilities';\nimport { bulletedList } from '@ms-cloudpack/task-reporter';\nimport path from 'path';\nimport type { RenderRouteOptions } from '../../types/RenderRouteOptions.js';\nimport type { CustomRenderErrorHandler } from '../getCustomRenderErrorHandler.js';\n\n/**\n * Bundle the `serverEntry` script.\n * @returns Absolute path to the bundled file, or an error response if bundling failed.\n */\nexport async function bundleServerEntry(\n options: Pick<RenderRouteOptions, 'definition'> & {\n /** Absolute path to the original serverEntry file from the route */\n serverEntryPath: string;\n handleCustomRenderError: CustomRenderErrorHandler;\n },\n context: EnsurePackageBundledContext,\n): Promise<string | ExpandedRenderFunctionResult> {\n const { definition, serverEntryPath, handleCustomRenderError } = options;\n const { name, version } = definition;\n const { config } = context.session;\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 isEffectivelyProduction = config.mode === 'production' || !config.features?.enableSSR;\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 && !isEffectivelyProduction,\n // In library mode, wait to bundle dependencies until they're requested, since the resolve map\n // is very likely to include extra deps that are irrelevant for the serverEntry.\n enqueueDependencies: false,\n },\n context,\n );\n const { errors, outputPath = '', outputFiles = [] } = response.result;\n\n if (errors?.length) {\n // The bundle errors will have been logged to the console on bundle task failure,\n // so don't log them again and skip trying to format them for the UI.\n return handleCustomRenderError({\n message: `failed to bundle (see the console for details)`,\n });\n }\n\n // TODO: Issues have been reported with this. Needs investigation and a test...\n let serverEntryOutput = outputFiles.find(\n (f) => f.entryPoint && slash(serverEntryPath) === slash(path.resolve(config.appPath, f.entryPoint)),\n )?.outputPath;\n if (!serverEntryOutput) {\n // No literal match, so try the intermediate path without extension.\n const intermediateServerEntryPath = sourceToIntermediatePath(\n path.relative(config.appPath, serverEntryPath),\n ).replace(/\\.\\w+$/, '');\n serverEntryOutput = outputFiles.find((f) =>\n intermediateServerEntryPath.endsWith(f.outputPath.replace(/\\.\\w+$/, '')),\n )?.outputPath;\n }\n\n if (!serverEntryOutput) {\n return handleCustomRenderError({\n message:\n `- tried to bundle the serverEntry script, but couldn't find a corresponding file ` +\n `in the bundle output (see console for details).`,\n extraLog:\n `Entries (output file to source file): ${JSON.stringify(response.result.entries || {})}\\n` +\n `Output path: ${outputPath}\\n` +\n 'Bundled files: ' +\n (outputFiles.length\n ? '\\n' + bulletedList(outputFiles.map((f) => `${f.outputPath} (entry: ${f.entryPoint || 'unknown'})`))\n : 'none'),\n });\n }\n\n // Translate relative-to-output path into an absolute path.\n return path.resolve(outputPath, serverEntryOutput);\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import type { Context } from '@ms-cloudpack/api-server';
2
2
  import type { RenderFunctionResult } from '@ms-cloudpack/common-types';
3
+ import type { CustomRenderErrorHandler } from '../getCustomRenderErrorHandler.js';
3
4
  import type { RenderRouteOptions } from '../../types/RenderRouteOptions.js';
4
5
  /**
5
6
  * Load the default export from a JS file passed as the `renderScript` in a route,
@@ -8,5 +9,6 @@ import type { RenderRouteOptions } from '../../types/RenderRouteOptions.js';
8
9
  export declare function renderCustomScript(params: RenderRouteOptions & {
9
10
  /** Full path to the render script (bundled version if relevant) */
10
11
  renderScriptPath: string;
12
+ handleCustomRenderError: CustomRenderErrorHandler;
11
13
  }, context: Pick<Context, 'session'>): Promise<RenderFunctionResult>;
12
14
  //# sourceMappingURL=renderCustomScript.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderCustomScript.d.ts","sourceRoot":"","sources":["../../../src/renderRoute/ssr/renderCustomScript.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAGV,oBAAoB,EAIrB,MAAM,4BAA4B,CAAC;AAIpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAI5E;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,kBAAkB,GAAG;IAC3B,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;CAC1B,EAGD,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAChC,OAAO,CAAC,oBAAoB,CAAC,CA4E/B"}
1
+ {"version":3,"file":"renderCustomScript.d.ts","sourceRoot":"","sources":["../../../src/renderRoute/ssr/renderCustomScript.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAkB,oBAAoB,EAA8B,MAAM,4BAA4B,CAAC;AAEnH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAElF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAI5E;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,kBAAkB,GAAG;IAC3B,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;IACzB,uBAAuB,EAAE,wBAAwB,CAAC;CACnD,EAGD,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,GAChC,OAAO,CAAC,oBAAoB,CAAC,CA0C/B"}