@ms-cloudpack/app-server 0.13.3 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"createRoutes.d.ts","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAML,KAAK,WAAW,EAGjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,kCAAkC,CAAC;AASnF,UAAU,mBAAmB;IAC3B,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,WAAW,CAAC;CACzB;AAED,KAAK,mBAAmB,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,oBAAoB,GAAG,eAAe,GAAG,SAAS,CAAC,CAAC;AAE1G;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,mBAAmB,QAyCtF"}
1
+ {"version":3,"file":"createRoutes.d.ts","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAML,KAAK,WAAW,EAGjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,kCAAkC,CAAC;AASnF,UAAU,mBAAmB;IAC3B,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,WAAW,CAAC;CACzB;AAED,KAAK,mBAAmB,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,oBAAoB,GAAG,eAAe,GAAG,SAAS,CAAC,CAAC;AAE1G;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,mBAAmB,QA2CtF"}
@@ -23,8 +23,10 @@ export function createRoutes(options, context) {
23
23
  // being requested, or a JS file somehow being loaded from the app server unexpectedly.
24
24
  if (!routes.length) {
25
25
  const fallbackRoute = {
26
- match: '*',
27
- exportEntry: '.',
26
+ match: '/',
27
+ // We need importPath to include the import map's default path, sourcePath and
28
+ // requiredPath are required but unused in this case.
29
+ entry: [{ importPath: '.', sourcePath: '', requestPath: '' }],
28
30
  isDefaultFallback: true, // this internal property is read by renderRoute
29
31
  };
30
32
  routes.push(fallbackRoute);
@@ -64,8 +66,10 @@ async function handleRequest(options, context) {
64
66
  const importMap = (session.importMap ??= await createImportMap({ ...context, ...session, urls: { bundleServer } }));
65
67
  // For production mode, no overlay script.
66
68
  const overlayScript = session.config.mode !== 'production' ? importMap.imports['@ms-cloudpack/overlay'] : undefined;
69
+ // Get all entry scripts for the route.
70
+ const entryScripts = route.entry?.map(
67
71
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
68
- const entryScript = route.exportEntry && importMap.imports[slash(path.join(definition.name, route.exportEntry))];
72
+ (entry) => importMap.imports[slash(path.join(definition.name, entry.importPath))]);
69
73
  const inlineScripts = await getInlineScripts();
70
74
  // Set the appropriate Cloudpack headers in the response.
71
75
  res.header('Cache-Control', 'no-cache');
@@ -78,7 +82,7 @@ async function handleRequest(options, context) {
78
82
  importMap,
79
83
  overlayScript,
80
84
  inlineScripts,
81
- entryScript,
85
+ entryScripts,
82
86
  });
83
87
  }
84
88
  else {
@@ -89,8 +93,8 @@ async function handleRequest(options, context) {
89
93
  baseUrl: url,
90
94
  importMap,
91
95
  overlayScript,
92
- entryScript,
93
96
  inlineScripts,
97
+ entryScripts,
94
98
  });
95
99
  }
96
100
  res.type(rendered.contentType).status(rendered.statusCode).send(rendered.content).end();
@@ -1 +1 @@
1
- {"version":3,"file":"createRoutes.js","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from '@ms-cloudpack/api-server';\nimport {\n isBootstrapRoute,\n isRedirectRoute,\n isRenderedRoute,\n isStaticRoute,\n type BootstrapRoute,\n type PackageJson,\n type RenderedRoute,\n type ExpandedRenderFunctionResult,\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 './inlineScripts/getInlineScripts.js';\nimport { renderRoute, type RouteInternal } from './renderRoute/renderRoute.js';\nimport { renderBootstrapRoute } from './renderBootstrapRoute.js';\n\ninterface CreateRoutesOptions {\n app: Express;\n url: string;\n definition: PackageJson;\n}\n\ntype CreateRoutesContext = Pick<Context, 'packages' | 'packageImportPaths' | 'packageHashes' | 'session'>;\n\n/**\n * Creates the routes for the express app based on the config.\n */\nexport function createRoutes(options: CreateRoutesOptions, context: CreateRoutesContext) {\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 // Only as a basic fallback if no routes are specified, add a default catch-all route.\n // Adding this automatically has the risk of hiding errors, like if an unexpected path\n // being requested, or a JS file somehow being loaded from the app server unexpectedly.\n if (!routes.length) {\n const fallbackRoute: RouteInternal = {\n match: '*',\n exportEntry: '.',\n isDefaultFallback: true, // this internal property is read by renderRoute\n };\n routes.push(fallbackRoute);\n }\n\n for (const route of routes) {\n if (isStaticRoute(route)) {\n app.use(route.match, express.static(path.resolve(appPath, route.staticPath)));\n } else if (isRedirectRoute(route)) {\n app.get(route.match, (req, res) => {\n res.redirect(route.redirectTo);\n });\n } else if (isRenderedRoute(route) || isBootstrapRoute(route)) {\n app.get(route.match, (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 {\n throw new Error(`Unknown route: ${JSON.stringify(route)}`);\n }\n }\n}\n\nasync function handleRequest(\n options: Omit<CreateRoutesOptions, 'app'> & {\n req: Request;\n res: Response;\n route: RenderedRoute | BootstrapRoute;\n },\n context: CreateRoutesContext,\n) {\n const { req, res, definition, route, url } = options;\n const { session } = context;\n const { bundleServer } = session.urls;\n if (!bundleServer) {\n // Sanity check...\n throw new Error('The bundle server URL is not yet set in the session (this is a Cloudpack bug).');\n }\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({ ...context, ...session, urls: { bundleServer } }));\n\n // For production mode, no overlay script.\n const overlayScript = session.config.mode !== 'production' ? importMap.imports['@ms-cloudpack/overlay'] : undefined;\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const entryScript = route.exportEntry && importMap.imports[slash(path.join(definition.name!, route.exportEntry))];\n const inlineScripts = 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;\n\n if (isBootstrapRoute(route)) {\n // Render the bootstrap script\n rendered = await renderBootstrapRoute({\n session,\n importMap,\n overlayScript,\n inlineScripts,\n entryScript,\n });\n } else {\n // HTML or custom script rendering\n rendered = await renderRoute({\n ...options,\n session,\n baseUrl: url,\n importMap,\n overlayScript,\n entryScript,\n inlineScripts,\n });\n }\n\n res.type(rendered.contentType).status(rendered.statusCode).send(rendered.content).end();\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 { Context } from '@ms-cloudpack/api-server';\nimport {\n isBootstrapRoute,\n isRedirectRoute,\n isRenderedRoute,\n isStaticRoute,\n type BootstrapRoute,\n type PackageJson,\n type RenderedRoute,\n type ExpandedRenderFunctionResult,\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 './inlineScripts/getInlineScripts.js';\nimport { renderRoute, type RouteInternal } from './renderRoute/renderRoute.js';\nimport { renderBootstrapRoute } from './renderBootstrapRoute.js';\n\ninterface CreateRoutesOptions {\n app: Express;\n url: string;\n definition: PackageJson;\n}\n\ntype CreateRoutesContext = Pick<Context, 'packages' | 'packageImportPaths' | 'packageHashes' | 'session'>;\n\n/**\n * Creates the routes for the express app based on the config.\n */\nexport function createRoutes(options: CreateRoutesOptions, context: CreateRoutesContext) {\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 // Only as a basic fallback if no routes are specified, add a default catch-all route.\n // Adding this automatically has the risk of hiding errors, like if an unexpected path\n // being requested, or a JS file somehow being loaded from the app server unexpectedly.\n if (!routes.length) {\n const fallbackRoute: RouteInternal = {\n match: '/',\n // We need importPath to include the import map's default path, sourcePath and\n // requiredPath are required but unused in this case.\n entry: [{ importPath: '.', sourcePath: '', requestPath: '' }],\n isDefaultFallback: true, // this internal property is read by renderRoute\n };\n routes.push(fallbackRoute);\n }\n\n for (const route of routes) {\n if (isStaticRoute(route)) {\n app.use(route.match, express.static(path.resolve(appPath, route.staticPath)));\n } else if (isRedirectRoute(route)) {\n app.get(route.match, (req, res) => {\n res.redirect(route.redirectTo);\n });\n } else if (isRenderedRoute(route) || isBootstrapRoute(route)) {\n app.get(route.match, (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 {\n throw new Error(`Unknown route: ${JSON.stringify(route)}`);\n }\n }\n}\n\nasync function handleRequest(\n options: Omit<CreateRoutesOptions, 'app'> & {\n req: Request;\n res: Response;\n route: RenderedRoute | BootstrapRoute;\n },\n context: CreateRoutesContext,\n) {\n const { req, res, definition, route, url } = options;\n const { session } = context;\n const { bundleServer } = session.urls;\n\n if (!bundleServer) {\n // Sanity check...\n throw new Error('The bundle server URL is not yet set in the session (this is a Cloudpack bug).');\n }\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({ ...context, ...session, urls: { bundleServer } }));\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 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n (entry) => importMap.imports[slash(path.join(definition.name!, entry.importPath))],\n );\n\n const inlineScripts = 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;\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 });\n } else {\n // HTML or custom script rendering\n rendered = await renderRoute({\n ...options,\n session,\n baseUrl: url,\n importMap,\n overlayScript,\n inlineScripts,\n entryScripts,\n });\n }\n\n res.type(rendered.contentType).status(rendered.statusCode).send(rendered.content).end();\n\n console.debug(`App server: Request: ${req.path}`);\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  window.__cloudpack ??= {};
2
- window.__cloudpack.bootstrap = ({ inlineScripts, overlayScript, importMap, pageSessionContext, entryScript }) => {
2
+ window.__cloudpack.bootstrap = ({ inlineScripts, overlayScript, importMap, pageSessionContext, entryScripts }) => {
3
3
  // Inject the import map
4
4
  const importMapScriptTag = document.createElement('script');
5
5
  importMapScriptTag.type = 'importmap';
@@ -22,11 +22,13 @@ window.__cloudpack.bootstrap = ({ inlineScripts, overlayScript, importMap, pageS
22
22
  document.head.appendChild(overlayScriptTag);
23
23
  }
24
24
  // Inject the main script
25
- if (entryScript) {
26
- const entryScriptTag = document.createElement('script');
27
- entryScriptTag.type = 'module';
28
- entryScriptTag.src = entryScript;
29
- document.head.appendChild(entryScriptTag);
25
+ if (entryScripts) {
26
+ for (const entryScript of entryScripts) {
27
+ const entryScriptTag = document.createElement('script');
28
+ entryScriptTag.type = 'module';
29
+ entryScriptTag.src = entryScript;
30
+ document.head.appendChild(entryScriptTag);
31
+ }
30
32
  }
31
33
  };
32
34
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap.inline.js","sourceRoot":"","sources":["../../src/inlineScripts/bootstrap.inline.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,WAAW,KAAK,EAAE,CAAC;AAC1B,MAAM,CAAC,WAAW,CAAC,SAAS,GAAG,CAAC,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,kBAAkB,EAAE,WAAW,EAAE,EAAE,EAAE;IAC9G,wBAAwB;IACxB,MAAM,kBAAkB,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5D,kBAAkB,CAAC,IAAI,GAAG,WAAW,CAAC;IACtC,kBAAkB,CAAC,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAE9C,MAAM,CAAC,WAAW,KAAK,EAAE,CAAC;IAC1B,MAAM,CAAC,WAAW,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;IAE3D,4BAA4B;IAC5B,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;QACzC,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACnD,SAAS,CAAC,IAAI,GAAG,QAAQ,CAAC;QAC1B,SAAS,CAAC,WAAW,GAAG,YAAY,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,4BAA4B;IAC5B,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,gBAAgB,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC1D,gBAAgB,CAAC,IAAI,GAAG,QAAQ,CAAC;QACjC,gBAAgB,CAAC,GAAG,GAAG,aAAa,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAC9C,CAAC;IAED,yBAAyB;IACzB,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACxD,cAAc,CAAC,IAAI,GAAG,QAAQ,CAAC;QAC/B,cAAc,CAAC,GAAG,GAAG,WAAW,CAAC;QACjC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC,CAAC","sourcesContent":["window.__cloudpack ??= {};\nwindow.__cloudpack.bootstrap = ({ inlineScripts, overlayScript, importMap, pageSessionContext, entryScript }) => {\n // Inject the import map\n const importMapScriptTag = document.createElement('script');\n importMapScriptTag.type = 'importmap';\n importMapScriptTag.textContent = JSON.stringify(importMap);\n document.head.appendChild(importMapScriptTag);\n\n window.__cloudpack ??= {};\n window.__cloudpack.pageSessionContext = pageSessionContext;\n\n // Inject the inline scripts\n for (const inlineScript of inlineScripts) {\n const inlineTag = document.createElement('script');\n inlineTag.type = 'module';\n inlineTag.textContent = inlineScript;\n document.head.appendChild(inlineTag);\n }\n\n // Inject the overlay script\n if (overlayScript) {\n const overlayScriptTag = document.createElement('script');\n overlayScriptTag.type = 'module';\n overlayScriptTag.src = overlayScript;\n document.head.appendChild(overlayScriptTag);\n }\n\n // Inject the main script\n if (entryScript) {\n const entryScriptTag = document.createElement('script');\n entryScriptTag.type = 'module';\n entryScriptTag.src = entryScript;\n document.head.appendChild(entryScriptTag);\n }\n};\n"]}
1
+ {"version":3,"file":"bootstrap.inline.js","sourceRoot":"","sources":["../../src/inlineScripts/bootstrap.inline.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,WAAW,KAAK,EAAE,CAAC;AAC1B,MAAM,CAAC,WAAW,CAAC,SAAS,GAAG,CAAC,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,kBAAkB,EAAE,YAAY,EAAE,EAAE,EAAE;IAC/G,wBAAwB;IACxB,MAAM,kBAAkB,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5D,kBAAkB,CAAC,IAAI,GAAG,WAAW,CAAC;IACtC,kBAAkB,CAAC,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAE9C,MAAM,CAAC,WAAW,KAAK,EAAE,CAAC;IAC1B,MAAM,CAAC,WAAW,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;IAE3D,4BAA4B;IAC5B,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;QACzC,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACnD,SAAS,CAAC,IAAI,GAAG,QAAQ,CAAC;QAC1B,SAAS,CAAC,WAAW,GAAG,YAAY,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,4BAA4B;IAC5B,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,gBAAgB,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC1D,gBAAgB,CAAC,IAAI,GAAG,QAAQ,CAAC;QACjC,gBAAgB,CAAC,GAAG,GAAG,aAAa,CAAC;QACrC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAC9C,CAAC;IAED,yBAAyB;IACzB,IAAI,YAAY,EAAE,CAAC;QACjB,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;YACvC,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxD,cAAc,CAAC,IAAI,GAAG,QAAQ,CAAC;YAC/B,cAAc,CAAC,GAAG,GAAG,WAAW,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;AACH,CAAC,CAAC","sourcesContent":["window.__cloudpack ??= {};\nwindow.__cloudpack.bootstrap = ({ inlineScripts, overlayScript, importMap, pageSessionContext, entryScripts }) => {\n // Inject the import map\n const importMapScriptTag = document.createElement('script');\n importMapScriptTag.type = 'importmap';\n importMapScriptTag.textContent = JSON.stringify(importMap);\n document.head.appendChild(importMapScriptTag);\n\n window.__cloudpack ??= {};\n window.__cloudpack.pageSessionContext = pageSessionContext;\n\n // Inject the inline scripts\n for (const inlineScript of inlineScripts) {\n const inlineTag = document.createElement('script');\n inlineTag.type = 'module';\n inlineTag.textContent = inlineScript;\n document.head.appendChild(inlineTag);\n }\n\n // Inject the overlay script\n if (overlayScript) {\n const overlayScriptTag = document.createElement('script');\n overlayScriptTag.type = 'module';\n overlayScriptTag.src = overlayScript;\n document.head.appendChild(overlayScriptTag);\n }\n\n // Inject the main script\n if (entryScripts) {\n for (const entryScript of entryScripts) {\n const entryScriptTag = document.createElement('script');\n entryScriptTag.type = 'module';\n entryScriptTag.src = entryScript;\n document.head.appendChild(entryScriptTag);\n }\n }\n};\n"]}
@@ -125,7 +125,7 @@ async function renderCustomScript(params) {
125
125
  * Note that this just logs an error rather than returning an error response if something fails, for now.
126
126
  */
127
127
  function injectScripts(options, result) {
128
- const { route, overlayScript, entryScript, inlineScripts, importMap } = options;
128
+ const { route, overlayScript, entryScripts, inlineScripts, importMap } = options;
129
129
  let jsdom;
130
130
  try {
131
131
  jsdom = new JSDOM(result.content, { contentType: result.contentType });
@@ -137,11 +137,11 @@ function injectScripts(options, result) {
137
137
  }
138
138
  try {
139
139
  const { document } = jsdom.window;
140
- if (entryScript || overlayScript) {
140
+ if (entryScripts || overlayScript) {
141
141
  // Inject the import map at the top of the head, in case other scripts use it
142
142
  addScript({ document, type: 'importmap', prepend: true, content: JSON.stringify(importMap, null, 2) });
143
143
  }
144
- if (overlayScript || inlineScripts.length > 0) {
144
+ if (overlayScript || inlineScripts?.length) {
145
145
  addScript({
146
146
  document,
147
147
  content: `
@@ -149,12 +149,18 @@ function injectScripts(options, result) {
149
149
  window.__cloudpack.pageSessionContext = ${JSON.stringify(createPageSessionContext(options.session))};`,
150
150
  });
151
151
  }
152
- for (const inlineScript of inlineScripts) {
153
- addScript({ document, content: inlineScript });
152
+ if (inlineScripts) {
153
+ for (const inlineScript of inlineScripts) {
154
+ addScript({ document, content: inlineScript });
155
+ }
154
156
  }
155
157
  // inject the overlay and entry scripts
156
158
  addScript({ document, url: overlayScript });
157
- addScript({ document, url: entryScript });
159
+ if (entryScripts) {
160
+ for (const entryScript of entryScripts) {
161
+ addScript({ document, url: entryScript });
162
+ }
163
+ }
158
164
  result.content = jsdom.serialize();
159
165
  }
160
166
  catch (e) {
@@ -1 +1 @@
1
- {"version":3,"file":"renderRoute.js","sourceRoot":"","sources":["../../src/renderRoute/renderRoute.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n RenderedRoute,\n RenderFunction,\n RenderFunctionScript,\n RenderFunctionOptions,\n RenderFunctionResult,\n ExpandedRenderFunctionResult,\n} from '@ms-cloudpack/common-types';\nimport { makeUrl } from '@ms-cloudpack/path-string-parsing';\nimport fsPromises from 'fs/promises';\nimport { JSDOM } from 'jsdom';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\nimport { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';\nimport { getHtmlErrorResponse } from './getHtmlErrorResponse.js';\nimport { createPageSessionContext } from '../createPageSessionContext.js';\n\nexport type RouteInternal = RenderedRoute & { isDefaultFallback?: boolean };\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(options: RenderFunctionOptions): Promise<ExpandedRenderFunctionResult> {\n const { route, session, req } = options;\n const { config } = session;\n const { appPath } = config;\n\n // Get the request path and extension (the URL.pathname conversion gets rid of any query or hash)\n const requestPath = makeUrl(req.url, options.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 const isHtmlAcceptable =\n (!requestExt || requestExt.startsWith('.htm')) && !req.xhr && isHtmlType(req.headers.accept || '*/*');\n\n // Get an initial result from either the render script or default handler.\n let rawResult: RenderFunctionResult;\n const renderScriptPath = route.renderScript && path.resolve(appPath, route.renderScript);\n if (renderScriptPath) {\n const scriptExt = path.extname(renderScriptPath).toLowerCase();\n if (scriptExt === '.html' || scriptExt === '.htm') {\n rawResult = await readStaticHtml(renderScriptPath);\n } else {\n rawResult = await renderCustomScript({ ...options, renderScriptPath });\n }\n } else {\n rawResult = await getDefaultHtmlResponse(options);\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 // First verify that we're not accidentally returning HTML if a different file type is requested.\n // This could happen for overly broad route matches such as '*', or if no routes are configured.\n if (!isHtmlAcceptable) {\n const renderer = route.renderScript ? `renderScript \"${renderScriptPath}\"` : 'the default renderer';\n const content = (route as RouteInternal).isDefaultFallback\n ? // no routes configured\n `Cloudpack's default route can only return HTML, but this appears to be a non-HTML request. ` +\n `Please add a route under devServer.routes in the cloudpack config.`\n : // route with or without custom renderer\n `Route \"${route.match}\" with ${renderer} returned HTML, but this appears to be a non-HTML request. ` +\n `This is likely a misconfiguration of devServer.routes in the cloudpack config.`;\n return { statusCode: 500, contentType: 'text/plain', content };\n }\n\n // Inject the import map and scripts.\n injectScripts(options, result);\n }\n\n return result;\n}\n\nfunction isHtmlType(acceptOrContentType: string) {\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 * 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 */\nasync 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 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 // Try importing the script\n let createHtml: RenderFunction | undefined;\n let errorMessage: string | undefined;\n try {\n const importResult = ((await import(renderScriptUrl + cacheBreakerQueryParam)) 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\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 */\nfunction injectScripts(options: RenderFunctionOptions, result: ExpandedRenderFunctionResult): void {\n const { route, overlayScript, entryScript, inlineScripts, importMap } = options;\n let jsdom: JSDOM;\n try {\n jsdom = new JSDOM(result.content, { contentType: result.contentType } as ConstructorParameters<typeof JSDOM>[1]);\n } catch (e) {\n // Trying to write a test for the above, it seemed very permissive, but catch just in case\n console.error(`Error parsing html response for rendering route \"${route.match}\":\\n${(e as Error).stack || e}`);\n return;\n }\n\n try {\n const { document } = jsdom.window;\n\n if (entryScript || 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 (overlayScript || inlineScripts.length > 0) {\n addScript({\n document,\n content: `\n window.__cloudpack ??= {};\n window.__cloudpack.pageSessionContext = ${JSON.stringify(createPageSessionContext(options.session))};`,\n });\n }\n\n for (const inlineScript of inlineScripts) {\n addScript({ document, content: inlineScript });\n }\n\n // inject the overlay and entry scripts\n addScript({ document, url: overlayScript });\n addScript({ document, url: entryScript });\n\n result.content = jsdom.serialize();\n } catch (e) {\n console.error(`Error injecting scripts for route \"${route.match}\":\\n${(e as Error).stack || e}`);\n }\n}\n\n/**\n * Helper function to add a script to the document.\n */\nfunction addScript(params: { document: Document; type?: string; prepend?: boolean; url?: string; content?: string }) {\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 {\n RenderedRoute,\n RenderFunction,\n RenderFunctionScript,\n RenderFunctionOptions,\n RenderFunctionResult,\n ExpandedRenderFunctionResult,\n} from '@ms-cloudpack/common-types';\nimport { makeUrl } from '@ms-cloudpack/path-string-parsing';\nimport fsPromises from 'fs/promises';\nimport { JSDOM } from 'jsdom';\nimport path from 'path';\nimport { pathToFileURL } from 'url';\nimport { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';\nimport { getHtmlErrorResponse } from './getHtmlErrorResponse.js';\nimport { createPageSessionContext } from '../createPageSessionContext.js';\n\nexport type RouteInternal = RenderedRoute & { isDefaultFallback?: boolean };\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(options: RenderFunctionOptions): Promise<ExpandedRenderFunctionResult> {\n const { route, session, req } = options;\n const { config } = session;\n const { appPath } = config;\n\n // Get the request path and extension (the URL.pathname conversion gets rid of any query or hash)\n const requestPath = makeUrl(req.url, options.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 const isHtmlAcceptable =\n (!requestExt || requestExt.startsWith('.htm')) && !req.xhr && isHtmlType(req.headers.accept || '*/*');\n\n // Get an initial result from either the render script or default handler.\n let rawResult: RenderFunctionResult;\n const renderScriptPath = route.renderScript && path.resolve(appPath, route.renderScript);\n if (renderScriptPath) {\n const scriptExt = path.extname(renderScriptPath).toLowerCase();\n if (scriptExt === '.html' || scriptExt === '.htm') {\n rawResult = await readStaticHtml(renderScriptPath);\n } else {\n rawResult = await renderCustomScript({ ...options, renderScriptPath });\n }\n } else {\n rawResult = await getDefaultHtmlResponse(options);\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 // First verify that we're not accidentally returning HTML if a different file type is requested.\n // This could happen for overly broad route matches such as '*', or if no routes are configured.\n if (!isHtmlAcceptable) {\n const renderer = route.renderScript ? `renderScript \"${renderScriptPath}\"` : 'the default renderer';\n const content = (route as RouteInternal).isDefaultFallback\n ? // no routes configured\n `Cloudpack's default route can only return HTML, but this appears to be a non-HTML request. ` +\n `Please add a route under devServer.routes in the cloudpack config.`\n : // route with or without custom renderer\n `Route \"${route.match}\" with ${renderer} returned HTML, but this appears to be a non-HTML request. ` +\n `This is likely a misconfiguration of devServer.routes in the cloudpack config.`;\n return { statusCode: 500, contentType: 'text/plain', content };\n }\n\n // Inject the import map and scripts.\n injectScripts(options, result);\n }\n\n return result;\n}\n\nfunction isHtmlType(acceptOrContentType: string) {\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 * 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 */\nasync 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 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 // Try importing the script\n let createHtml: RenderFunction | undefined;\n let errorMessage: string | undefined;\n try {\n const importResult = ((await import(renderScriptUrl + cacheBreakerQueryParam)) 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\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 */\nfunction injectScripts(options: RenderFunctionOptions, result: ExpandedRenderFunctionResult): void {\n const { route, overlayScript, entryScripts, inlineScripts, importMap } = options;\n let jsdom: JSDOM;\n try {\n jsdom = new JSDOM(result.content, { contentType: result.contentType } as ConstructorParameters<typeof JSDOM>[1]);\n } catch (e) {\n // Trying to write a test for the above, it seemed very permissive, but catch just in case\n console.error(`Error parsing html response for rendering route \"${route.match}\":\\n${(e as Error).stack || e}`);\n return;\n }\n\n try {\n const { document } = jsdom.window;\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 (overlayScript || inlineScripts?.length) {\n addScript({\n document,\n content: `\n window.__cloudpack ??= {};\n window.__cloudpack.pageSessionContext = ${JSON.stringify(createPageSessionContext(options.session))};`,\n });\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 result.content = jsdom.serialize();\n } catch (e) {\n console.error(`Error injecting scripts for route \"${route.match}\":\\n${(e as Error).stack || e}`);\n }\n}\n\n/**\n * Helper function to add a script to the document.\n */\nfunction addScript(params: { document: Document; type?: string; prepend?: boolean; url?: string; content?: string }) {\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ms-cloudpack/app-server",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "description": "An implementation of the App server for Cloudpack.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,19 +14,19 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@ms-cloudpack/api-server": "^0.48.3",
18
- "@ms-cloudpack/bundle-server": "^0.4.47",
19
- "@ms-cloudpack/common-types": "^0.14.0",
20
- "@ms-cloudpack/create-express-app": "^1.7.2",
21
- "@ms-cloudpack/import-map": "^0.6.0",
22
- "@ms-cloudpack/overlay": "^0.17.46",
17
+ "@ms-cloudpack/api-server": "^0.48.5",
18
+ "@ms-cloudpack/bundle-server": "^0.4.49",
19
+ "@ms-cloudpack/common-types": "^0.15.0",
20
+ "@ms-cloudpack/create-express-app": "^1.7.3",
21
+ "@ms-cloudpack/import-map": "^0.6.1",
22
+ "@ms-cloudpack/overlay": "^0.17.48",
23
23
  "@ms-cloudpack/path-string-parsing": "^1.2.4",
24
- "@ms-cloudpack/path-utilities": "^2.7.31",
24
+ "@ms-cloudpack/path-utilities": "^2.7.32",
25
25
  "@ms-cloudpack/task-reporter": "^0.14.4",
26
26
  "jsdom": "^24.0.0"
27
27
  },
28
28
  "devDependencies": {
29
- "@ms-cloudpack/common-types-browser": "^0.4.0",
29
+ "@ms-cloudpack/common-types-browser": "^0.5.0",
30
30
  "@ms-cloudpack/eslint-plugin-internal": "^0.0.1",
31
31
  "@ms-cloudpack/scripts": "^0.0.1",
32
32
  "@ms-cloudpack/test-utilities": "^0.5.0",