@ms-cloudpack/app-server 0.3.13 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/lib/createRoutes.d.ts +2 -2
  2. package/lib/createRoutes.d.ts.map +1 -1
  3. package/lib/createRoutes.js +39 -40
  4. package/lib/createRoutes.js.map +1 -1
  5. package/lib/index.d.ts +4 -0
  6. package/lib/index.d.ts.map +1 -1
  7. package/lib/index.js.map +1 -1
  8. package/lib/renderRoute/getDefaultHtmlResponse.d.ts +6 -0
  9. package/lib/renderRoute/getDefaultHtmlResponse.d.ts.map +1 -0
  10. package/lib/renderRoute/getDefaultHtmlResponse.js +15 -0
  11. package/lib/renderRoute/getDefaultHtmlResponse.js.map +1 -0
  12. package/lib/renderRoute/getHtmlErrorResponse.d.ts +6 -0
  13. package/lib/renderRoute/getHtmlErrorResponse.d.ts.map +1 -0
  14. package/lib/renderRoute/getHtmlErrorResponse.js +22 -0
  15. package/lib/renderRoute/getHtmlErrorResponse.js.map +1 -0
  16. package/lib/renderRoute/renderRoute.d.ts +8 -0
  17. package/lib/renderRoute/renderRoute.d.ts.map +1 -0
  18. package/lib/renderRoute/renderRoute.js +161 -0
  19. package/lib/renderRoute/renderRoute.js.map +1 -0
  20. package/lib/startAppServer.d.ts +1 -1
  21. package/lib/startAppServer.d.ts.map +1 -1
  22. package/lib/startAppServer.js +6 -25
  23. package/lib/startAppServer.js.map +1 -1
  24. package/lib/types/RenderRouteFunction.d.ts +7 -0
  25. package/lib/types/RenderRouteFunction.d.ts.map +1 -0
  26. package/lib/types/RenderRouteFunction.js +2 -0
  27. package/lib/types/RenderRouteFunction.js.map +1 -0
  28. package/lib/types/RenderRouteOptions.d.ts +20 -0
  29. package/lib/types/RenderRouteOptions.d.ts.map +1 -0
  30. package/lib/types/RenderRouteOptions.js +2 -0
  31. package/lib/types/RenderRouteOptions.js.map +1 -0
  32. package/lib/types/RenderRouteResult.d.ts +19 -0
  33. package/lib/types/RenderRouteResult.d.ts.map +1 -0
  34. package/lib/types/RenderRouteResult.js +2 -0
  35. package/lib/types/RenderRouteResult.js.map +1 -0
  36. package/lib/types/RenderRouteScript.d.ts +8 -0
  37. package/lib/types/RenderRouteScript.d.ts.map +1 -0
  38. package/lib/types/RenderRouteScript.js +2 -0
  39. package/lib/types/RenderRouteScript.js.map +1 -0
  40. package/package.json +9 -9
  41. package/lib/getDefaultHtmlResponse.d.ts +0 -6
  42. package/lib/getDefaultHtmlResponse.d.ts.map +0 -1
  43. package/lib/getDefaultHtmlResponse.js +0 -18
  44. package/lib/getDefaultHtmlResponse.js.map +0 -1
  45. package/lib/getHtmlResponse.d.ts +0 -6
  46. package/lib/getHtmlResponse.d.ts.map +0 -1
  47. package/lib/getHtmlResponse.js +0 -117
  48. package/lib/getHtmlResponse.js.map +0 -1
@@ -5,9 +5,9 @@ import type { ApiServer, Session } from '@ms-cloudpack/api-server';
5
5
  import type { PackageImportPaths } from '@ms-cloudpack/import-map';
6
6
  import type { PackageHashes } from '@ms-cloudpack/package-hashes';
7
7
  /**
8
- * Creates the routes for the express app, considering how the config is defined.
8
+ * Creates the routes for the express app based on the config.
9
9
  */
10
- export declare function createRoutes({ app, url, session, definition, bundleServer, apiServer, config, packages, packageImportPaths, packageHashes, }: {
10
+ export declare function createRoutes(params: {
11
11
  app: Express;
12
12
  url: string;
13
13
  session: Session;
@@ -1 +1 @@
1
- {"version":3,"file":"createRoutes.d.ts","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACxG,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,kCAAkC,CAAC;AAKnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAC3B,GAAG,EACH,GAAG,EACH,OAAO,EACP,UAAU,EACV,YAAY,EACZ,SAAS,EACT,MAAM,EACN,QAAQ,EACR,kBAAkB,EAClB,aAAa,GACd,EAAE;IACD,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,WAAW,CAAC;IACxB,YAAY,EAAE,YAAY,CAAC;IAC3B,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,aAAa,EAAE,aAAa,CAAC;CAC9B,QAyEA"}
1
+ {"version":3,"file":"createRoutes.d.ts","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACxG,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,kCAAkC,CAAC;AAKnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,WAAW,CAAC;IACxB,YAAY,EAAE,YAAY,CAAC;IAC3B,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,aAAa,EAAE,aAAa,CAAC;CAC9B,QA6BA"}
@@ -2,13 +2,14 @@ import { express } from '@ms-cloudpack/create-express-app';
2
2
  import { createImportMap } from '@ms-cloudpack/import-map';
3
3
  import { slash } from '@ms-cloudpack/path-string-parsing';
4
4
  import path from 'path';
5
- import { getHtmlResponse } from './getHtmlResponse.js';
5
+ import { renderRoute } from './renderRoute/renderRoute.js';
6
6
  import { getInlineScripts } from './inlineScripts/getInlineScripts.js';
7
7
  import { setHeaders } from './setHeaders.js';
8
8
  /**
9
- * Creates the routes for the express app, considering how the config is defined.
9
+ * Creates the routes for the express app based on the config.
10
10
  */
11
- export function createRoutes({ app, url, session, definition, bundleServer, apiServer, config, packages, packageImportPaths, packageHashes, }) {
11
+ export function createRoutes(params) {
12
+ const { app, session, config, packageHashes } = params;
12
13
  const routes = [...(config.devServer?.routes || [])];
13
14
  const hasDefaultRoute = routes.some((route) => route.match === '*' || route.match === '/');
14
15
  if (!hasDefaultRoute) {
@@ -27,43 +28,7 @@ export function createRoutes({ app, url, session, definition, bundleServer, apiS
27
28
  }
28
29
  else {
29
30
  app.get(route.match, (req, res) => {
30
- (async () => {
31
- // Build the import map if it hasn't been built yet for this session version.
32
- const importMap = session.getImportMap() ||
33
- session.setImportMap(await createImportMap({
34
- resolveMap: session.resolveMap,
35
- bundleServerUrl: bundleServer.url,
36
- sessionVersion: session.getSessionVersion(),
37
- targetVersions: session.targetVersions,
38
- }, { packages, packageImportPaths, getPackageHash, config: session.config }));
39
- // Parse the request path, extension, grab the overlay script path.
40
- const requestPath = slash(req.path.substring(1));
41
- const requestExt = path.extname(requestPath);
42
- const overlayScript = importMap.imports['@ms-cloudpack/overlay'];
43
- const entryScript =
44
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
45
- route.exportEntry && importMap.imports[slash(path.join(definition.name, route.exportEntry))];
46
- // Set the appropriate Cloudpack headers/cookies in the response.
47
- // TODO: setting the headers here prohibits cases where the page rendering is owned by existing server code
48
- // that can only accept changing the scripts. We should consider moving to a model where an initial script
49
- // fetch loads the import map and sets Cloudpack settings in local storage rather than headers/cookies.
50
- setHeaders({ res, session, apiServer, bundleServer });
51
- const inlineScripts = await getInlineScripts();
52
- const { content, statusCode, contentType } = await getHtmlResponse({
53
- route,
54
- baseUrl: url,
55
- importMap,
56
- overlayScript,
57
- entryScript,
58
- session,
59
- definition,
60
- inlineScripts,
61
- req,
62
- res,
63
- });
64
- res.type(contentType).status(statusCode).send(content).end();
65
- console.debug(`App server: Request: ${requestPath}, ext: ${requestExt}`);
66
- })().catch((err) => {
31
+ handleRequest({ ...params, route, req, res, getPackageHash }).catch((err) => {
67
32
  console.error(err?.stack || err);
68
33
  res.status(500).send(`Error loading app: ${err}`);
69
34
  });
@@ -71,4 +36,38 @@ export function createRoutes({ app, url, session, definition, bundleServer, apiS
71
36
  }
72
37
  }
73
38
  }
39
+ async function handleRequest(params) {
40
+ const { req, res, session, definition, route, bundleServer, apiServer, packages, getPackageHash, packageImportPaths, url, } = params;
41
+ // Build the import map if it hasn't been built yet for this session version.
42
+ const importMap = session.getImportMap() ||
43
+ session.setImportMap(await createImportMap({
44
+ resolveMap: session.resolveMap,
45
+ bundleServerUrl: bundleServer.url,
46
+ sessionVersion: session.getSessionVersion(),
47
+ targetVersions: session.targetVersions,
48
+ }, { packages, packageImportPaths, getPackageHash, config: session.config }));
49
+ // Parse the request path, extension, grab the overlay script path.
50
+ const requestPath = slash(req.path.substring(1));
51
+ const requestExt = path.extname(requestPath);
52
+ const overlayScript = importMap.imports['@ms-cloudpack/overlay'];
53
+ const entryScript =
54
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
55
+ route.exportEntry && importMap.imports[slash(path.join(definition.name, route.exportEntry))];
56
+ // Set the appropriate Cloudpack headers/cookies in the response.
57
+ // TODO: setting the headers here prohibits cases where the page rendering is owned by existing server code
58
+ // that can only accept changing the scripts. We should consider moving to a model where an initial script
59
+ // fetch loads the import map and sets Cloudpack settings in local storage rather than headers/cookies.
60
+ setHeaders({ res, session, apiServer, bundleServer });
61
+ const inlineScripts = await getInlineScripts();
62
+ const { content, statusCode, contentType } = await renderRoute({
63
+ ...params,
64
+ baseUrl: url,
65
+ importMap,
66
+ overlayScript,
67
+ entryScript,
68
+ inlineScripts,
69
+ });
70
+ res.type(contentType).status(statusCode).send(content).end();
71
+ console.debug(`App server: Request: ${requestPath}, ext: ${requestExt}`);
72
+ }
74
73
  //# 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 { CloudpackConfig, PackageDefinitionsCache, PackageJson } 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 type { BundleServer } from '@ms-cloudpack/bundle-server';\nimport type { ApiServer, Session } from '@ms-cloudpack/api-server';\nimport { getHtmlResponse } from './getHtmlResponse.js';\nimport { getInlineScripts } from './inlineScripts/getInlineScripts.js';\nimport { setHeaders } from './setHeaders.js';\nimport type { PackageImportPaths } from '@ms-cloudpack/import-map';\nimport type { PackageHashes } from '@ms-cloudpack/package-hashes';\n\n/**\n * Creates the routes for the express app, considering how the config is defined.\n */\nexport function createRoutes({\n app,\n url,\n session,\n definition,\n bundleServer,\n apiServer,\n config,\n packages,\n packageImportPaths,\n packageHashes,\n}: {\n app: Express;\n url: string;\n session: Session;\n definition: PackageJson;\n bundleServer: BundleServer;\n apiServer: ApiServer;\n config: CloudpackConfig;\n packages: PackageDefinitionsCache;\n packageImportPaths: PackageImportPaths;\n packageHashes: PackageHashes;\n}) {\n const routes = [...(config.devServer?.routes || [])];\n const hasDefaultRoute = routes.some((route) => route.match === '*' || route.match === '/');\n\n if (!hasDefaultRoute) {\n routes.push({\n match: '*',\n exportEntry: '.',\n });\n }\n\n // Hashes from external packages to be used in the import map.\n const getPackageHash = (packagePath: string) => {\n return packageHashes.get({ packagePath, isSourceHashingEnabled: false });\n };\n\n for (const route of routes) {\n if (route.staticPath) {\n app.use(route.match, express.static(path.resolve(session.appPath, route.staticPath)));\n } else {\n app.get(route.match, (req: Request, res: Response) => {\n (async () => {\n // Build the import map if it hasn't been built yet for this session version.\n const importMap =\n session.getImportMap() ||\n session.setImportMap(\n await createImportMap(\n {\n resolveMap: session.resolveMap,\n bundleServerUrl: bundleServer.url,\n sessionVersion: session.getSessionVersion(),\n targetVersions: session.targetVersions,\n },\n { packages, packageImportPaths, getPackageHash, config: session.config },\n ),\n );\n\n // Parse the request path, extension, grab the overlay script path.\n const requestPath = slash(req.path.substring(1));\n const requestExt = path.extname(requestPath);\n const overlayScript = importMap.imports['@ms-cloudpack/overlay'];\n const entryScript =\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n route.exportEntry && importMap.imports[slash(path.join(definition.name!, route.exportEntry))];\n // Set the appropriate Cloudpack headers/cookies in the response.\n // TODO: setting the headers here prohibits cases where the page rendering is owned by existing server code\n // that can only accept changing the scripts. We should consider moving to a model where an initial script\n // fetch loads the import map and sets Cloudpack settings in local storage rather than headers/cookies.\n setHeaders({ res, session, apiServer, bundleServer });\n const inlineScripts = await getInlineScripts();\n const { content, statusCode, contentType } = await getHtmlResponse({\n route,\n baseUrl: url,\n importMap,\n overlayScript,\n entryScript,\n session,\n definition,\n inlineScripts,\n req,\n res,\n });\n\n res.type(contentType).status(statusCode).send(content).end();\n\n console.debug(`App server: Request: ${requestPath}, ext: ${requestExt}`);\n })().catch((err) => {\n console.error((err as Error)?.stack || err);\n res.status(500).send(`Error loading app: ${err}`);\n });\n });\n }\n }\n}\n"]}
1
+ {"version":3,"file":"createRoutes.js","sourceRoot":"","sources":["../src/createRoutes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CloudpackConfig, PackageDefinitionsCache, PackageJson } 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 type { BundleServer } from '@ms-cloudpack/bundle-server';\nimport type { ApiServer, Session } from '@ms-cloudpack/api-server';\nimport { renderRoute } from './renderRoute/renderRoute.js';\nimport { getInlineScripts } from './inlineScripts/getInlineScripts.js';\nimport { setHeaders } from './setHeaders.js';\nimport type { PackageImportPaths } from '@ms-cloudpack/import-map';\nimport type { PackageHashes } from '@ms-cloudpack/package-hashes';\n\n/**\n * Creates the routes for the express app based on the config.\n */\nexport function createRoutes(params: {\n app: Express;\n url: string;\n session: Session;\n definition: PackageJson;\n bundleServer: BundleServer;\n apiServer: ApiServer;\n config: CloudpackConfig;\n packages: PackageDefinitionsCache;\n packageImportPaths: PackageImportPaths;\n packageHashes: PackageHashes;\n}) {\n const { app, session, config, packageHashes } = params;\n const routes = [...(config.devServer?.routes || [])];\n const hasDefaultRoute = routes.some((route) => route.match === '*' || route.match === '/');\n\n if (!hasDefaultRoute) {\n routes.push({\n match: '*',\n exportEntry: '.',\n });\n }\n\n // Hashes from external packages to be used in the import map.\n const getPackageHash = (packagePath: string) => {\n return packageHashes.get({ packagePath, isSourceHashingEnabled: false });\n };\n\n for (const route of routes) {\n if (route.staticPath) {\n app.use(route.match, express.static(path.resolve(session.appPath, route.staticPath)));\n } else {\n app.get(route.match, (req: Request, res: Response) => {\n handleRequest({ ...params, route, req, res, getPackageHash }).catch((err) => {\n console.error((err as Error)?.stack || err);\n res.status(500).send(`Error loading app: ${err}`);\n });\n });\n }\n }\n}\n\nasync function handleRequest(params: {\n req: Request;\n res: Response;\n session: Session;\n definition: PackageJson;\n route: { match: string; exportEntry?: string; staticPath?: string };\n bundleServer: BundleServer;\n apiServer: ApiServer;\n packages: PackageDefinitionsCache;\n packageImportPaths: PackageImportPaths;\n getPackageHash: (packagePath: string) => Promise<string>;\n url: string;\n}) {\n const {\n req,\n res,\n session,\n definition,\n route,\n bundleServer,\n apiServer,\n packages,\n getPackageHash,\n packageImportPaths,\n url,\n } = params;\n\n // Build the import map if it hasn't been built yet for this session version.\n const importMap =\n session.getImportMap() ||\n session.setImportMap(\n await createImportMap(\n {\n resolveMap: session.resolveMap,\n bundleServerUrl: bundleServer.url,\n sessionVersion: session.getSessionVersion(),\n targetVersions: session.targetVersions,\n },\n { packages, packageImportPaths, getPackageHash, config: session.config },\n ),\n );\n\n // Parse the request path, extension, grab the overlay script path.\n const requestPath = slash(req.path.substring(1));\n const requestExt = path.extname(requestPath);\n const overlayScript = importMap.imports['@ms-cloudpack/overlay'];\n const entryScript =\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n route.exportEntry && importMap.imports[slash(path.join(definition.name!, route.exportEntry))];\n\n // Set the appropriate Cloudpack headers/cookies in the response.\n // TODO: setting the headers here prohibits cases where the page rendering is owned by existing server code\n // that can only accept changing the scripts. We should consider moving to a model where an initial script\n // fetch loads the import map and sets Cloudpack settings in local storage rather than headers/cookies.\n setHeaders({ res, session, apiServer, bundleServer });\n\n const inlineScripts = await getInlineScripts();\n const { content, statusCode, contentType } = await renderRoute({\n ...params,\n baseUrl: url,\n importMap,\n overlayScript,\n entryScript,\n inlineScripts,\n });\n\n res.type(contentType).status(statusCode).send(content).end();\n\n console.debug(`App server: Request: ${requestPath}, ext: ${requestExt}`);\n}\n"]}
package/lib/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
1
  export { startAppServer } from './startAppServer.js';
2
2
  export type { AppServer } from './types/AppServer.js';
3
+ export type { RenderRouteFunction } from './types/RenderRouteFunction.js';
4
+ export type { RenderRouteOptions } from './types/RenderRouteOptions.js';
5
+ export type { RenderRouteResult } from './types/RenderRouteResult.js';
6
+ export type { RenderRouteScript } from './types/RenderRouteScript.js';
3
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,YAAY,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC"}
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export { startAppServer } from './startAppServer.js';\nexport type { AppServer } from './types/AppServer.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export { startAppServer } from './startAppServer.js';\nexport type { AppServer } from './types/AppServer.js';\nexport type { RenderRouteFunction } from './types/RenderRouteFunction.js';\nexport type { RenderRouteOptions } from './types/RenderRouteOptions.js';\nexport type { RenderRouteResult } from './types/RenderRouteResult.js';\nexport type { RenderRouteScript } from './types/RenderRouteScript.js';\n"]}
@@ -0,0 +1,6 @@
1
+ import type { RenderRouteFunction } from '../types/RenderRouteFunction.js';
2
+ /**
3
+ * Return the default HTML response.
4
+ */
5
+ export declare const getDefaultHtmlResponse: RenderRouteFunction;
6
+ //# sourceMappingURL=getDefaultHtmlResponse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getDefaultHtmlResponse.d.ts","sourceRoot":"","sources":["../../src/renderRoute/getDefaultHtmlResponse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAE3E;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,mBAUpC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Return the default HTML response.
3
+ */
4
+ export const getDefaultHtmlResponse = (options) => {
5
+ return `<!DOCTYPE html>
6
+ <html lang="en">
7
+ <head>
8
+ <title>${options.definition.name}</title>
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>`;
14
+ };
15
+ //# sourceMappingURL=getDefaultHtmlResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getDefaultHtmlResponse.js","sourceRoot":"","sources":["../../src/renderRoute/getDefaultHtmlResponse.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAwB,CAAC,OAAO,EAAE,EAAE;IACrE,OAAO;;;WAGE,OAAO,CAAC,UAAU,CAAC,IAAI;;;;;QAK1B,CAAC;AACT,CAAC,CAAC","sourcesContent":["import type { RenderRouteFunction } from '../types/RenderRouteFunction.js';\n\n/**\n * Return the default HTML response.\n */\nexport const getDefaultHtmlResponse: RenderRouteFunction = (options) => {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <title>${options.definition.name}</title>\n</head>\n<body>\n <div id=\"root\"></div>\n</body>\n</html>`;\n};\n"]}
@@ -0,0 +1,6 @@
1
+ import type { CompleteRenderRouteResult } from '../types/RenderRouteResult.js';
2
+ /**
3
+ * Get an error response: an HTML page with the given text, and a 500 status code.
4
+ */
5
+ export declare function getHtmlErrorResponse(errorText: string): CompleteRenderRouteResult;
6
+ //# sourceMappingURL=getHtmlErrorResponse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getHtmlErrorResponse.d.ts","sourceRoot":"","sources":["../../src/renderRoute/getHtmlErrorResponse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAE/E;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,yBAAyB,CAiBjF"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Get an error response: an HTML page with the given text, and a 500 status code.
3
+ */
4
+ export function getHtmlErrorResponse(errorText) {
5
+ return {
6
+ content: [
7
+ `<!DOCTYPE html>`,
8
+ `<html lang="en">`,
9
+ `<head>`,
10
+ ` <title>Error</title>`,
11
+ `</head>`,
12
+ `<body>`,
13
+ ` <h1>Error</h1>`,
14
+ ` <pre>${errorText}</pre>`,
15
+ `</body>`,
16
+ `</html>`,
17
+ ].join('\n'),
18
+ statusCode: 500,
19
+ contentType: 'text/html',
20
+ };
21
+ }
22
+ //# sourceMappingURL=getHtmlErrorResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getHtmlErrorResponse.js","sourceRoot":"","sources":["../../src/renderRoute/getHtmlErrorResponse.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,OAAO;QACL,OAAO,EAAE;YACP,iBAAiB;YACjB,kBAAkB;YAClB,QAAQ;YACR,wBAAwB;YACxB,SAAS;YACT,QAAQ;YACR,kBAAkB;YAClB,UAAU,SAAS,QAAQ;YAC3B,SAAS;YACT,SAAS;SACV,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,UAAU,EAAE,GAAG;QACf,WAAW,EAAE,WAAW;KACzB,CAAC;AACJ,CAAC","sourcesContent":["import type { CompleteRenderRouteResult } from '../types/RenderRouteResult.js';\n\n/**\n * Get an error response: an HTML page with the given text, and a 500 status code.\n */\nexport function getHtmlErrorResponse(errorText: string): CompleteRenderRouteResult {\n return {\n content: [\n `<!DOCTYPE html>`,\n `<html lang=\"en\">`,\n `<head>`,\n ` <title>Error</title>`,\n `</head>`,\n `<body>`,\n ` <h1>Error</h1>`,\n ` <pre>${errorText}</pre>`,\n `</body>`,\n `</html>`,\n ].join('\\n'),\n statusCode: 500,\n contentType: 'text/html',\n };\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { CompleteRenderRouteResult } from '../types/RenderRouteResult.js';
2
+ import type { RenderRouteOptions } from '../types/RenderRouteOptions.js';
3
+ /**
4
+ * Get the response for the given route. If the route has a custom render script, use that.
5
+ * Returns an error response if the file referenced by the script doesn't exist or throws an error.
6
+ */
7
+ export declare function renderRoute(options: RenderRouteOptions): Promise<CompleteRenderRouteResult>;
8
+ //# sourceMappingURL=renderRoute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderRoute.d.ts","sourceRoot":"","sources":["../../src/renderRoute/renderRoute.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,yBAAyB,EAAqB,MAAM,+BAA+B,CAAC;AAClG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAIzE;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAgCjG"}
@@ -0,0 +1,161 @@
1
+ import path from 'path';
2
+ import { pathToFileURL } from 'url';
3
+ import { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';
4
+ import { JSDOM } from 'jsdom';
5
+ import fsPromises from 'fs/promises';
6
+ import { getHtmlErrorResponse } from './getHtmlErrorResponse.js';
7
+ /**
8
+ * Get the response for the given route. If the route has a custom render script, use that.
9
+ * Returns an error response if the file referenced by the script doesn't exist or throws an error.
10
+ */
11
+ export async function renderRoute(options) {
12
+ const { route, session } = options;
13
+ // Get an initial result from either the render script or default handler.
14
+ let rawResult;
15
+ if (route.renderScript) {
16
+ const renderScriptPath = path.resolve(session.appPath, route.renderScript);
17
+ const scriptExt = path.extname(renderScriptPath).toLowerCase();
18
+ if (scriptExt === '.html' || scriptExt === '.htm') {
19
+ rawResult = await readStaticHtml(renderScriptPath);
20
+ }
21
+ else {
22
+ rawResult = await renderCustomScript({ ...options, renderScriptPath });
23
+ }
24
+ }
25
+ else {
26
+ rawResult = await getDefaultHtmlResponse(options);
27
+ }
28
+ const result = {
29
+ contentType: 'text/html',
30
+ statusCode: 200,
31
+ ...(typeof rawResult === 'string' ? { content: rawResult } : rawResult),
32
+ };
33
+ // If it's HTML and not an error, inject the import map and appropriate scripts.
34
+ // endsWith('html') covers: 'html', '.html', and 'text/html'
35
+ // (Might need to modify the status check in the future if there are other codes where injecting
36
+ // scripts is appropriate, but for now, only do this for 200.)
37
+ if (result.contentType.endsWith('html') && result.statusCode === 200) {
38
+ injectScripts(options, result);
39
+ }
40
+ return result;
41
+ }
42
+ /**
43
+ * Load the default export from a JS file passed as the `renderScript` in a route,
44
+ * and return either the result of running the function, or an error page if something fails.
45
+ */
46
+ async function renderCustomScript(params) {
47
+ const { renderScriptPath, ...options } = params;
48
+ // Get the html factory function from a script default export.
49
+ const renderScriptUrl = pathToFileURL(renderScriptPath).toString();
50
+ // Note: because there isn't a way to purge the require cache, we need to add a cache breaker query param to the
51
+ // script path to ensure we get the latest version of the script. This could be improved by using the git hash of
52
+ // the file if it exists, or a hash of the content, or even the timestamp of the file.
53
+ let cacheBreakerQueryParam = `?t=${Date.now()}`;
54
+ try {
55
+ const { mtime } = await fsPromises.stat(renderScriptPath);
56
+ cacheBreakerQueryParam = `?t=${mtime.getTime()}`;
57
+ }
58
+ catch {
59
+ /* no-op */
60
+ }
61
+ // Try importing the script
62
+ let createHtml;
63
+ let errorMessage;
64
+ try {
65
+ const importResult = (await import(renderScriptUrl + cacheBreakerQueryParam)).default;
66
+ if (typeof importResult === 'function') {
67
+ createHtml = importResult;
68
+ }
69
+ else {
70
+ errorMessage = `The render script at "${renderScriptPath}" does not export a default function.`;
71
+ }
72
+ }
73
+ catch (e) {
74
+ errorMessage = `Error importing render script at "${renderScriptUrl}":\n${e.stack || e}`;
75
+ }
76
+ if (createHtml) {
77
+ // Try running the script
78
+ try {
79
+ return await createHtml(options);
80
+ }
81
+ catch (e) {
82
+ errorMessage = `Error running render script at "${renderScriptPath}":\n${e.stack || e}`;
83
+ }
84
+ }
85
+ // Return an error page. Doing this instead of returning a default or empty response ensures that
86
+ // the user is aware of any configuration issues.
87
+ errorMessage ??= `Unknown error loading render script at "${renderScriptPath}".`;
88
+ console.error(errorMessage);
89
+ return getHtmlErrorResponse(errorMessage);
90
+ }
91
+ /**
92
+ * Modify the HTML response by injecting the import map, inline scripts, and entry/overlay scripts.
93
+ *
94
+ * Note that this just logs an error rather than returning an error response if something fails, for now.
95
+ */
96
+ function injectScripts(options, result) {
97
+ const { route, overlayScript, entryScript, inlineScripts, importMap } = options;
98
+ let jsdom;
99
+ try {
100
+ jsdom = new JSDOM(result.content);
101
+ }
102
+ catch (e) {
103
+ // Trying to write a test for the above, it seemed very permissive, but catch just in case
104
+ console.error(`Error parsing html response for rendering route "${route.match}":\n${e.stack || e}`);
105
+ return;
106
+ }
107
+ try {
108
+ const { document } = jsdom.window;
109
+ // inject the import map.
110
+ if (entryScript || overlayScript) {
111
+ addScript({ document, type: 'importmap', content: JSON.stringify(importMap, null, 2) });
112
+ }
113
+ for (const inlineScript of inlineScripts) {
114
+ addScript({ document, content: inlineScript });
115
+ }
116
+ // inject the overlay and entry scripts
117
+ addScript({ document, url: overlayScript });
118
+ addScript({ document, url: entryScript });
119
+ result.content = jsdom.serialize();
120
+ }
121
+ catch (e) {
122
+ console.error(`Error injecting scripts for route "${route.match}":\n${e.stack || e}`);
123
+ }
124
+ }
125
+ /**
126
+ * Helper function to add a script to the document.
127
+ */
128
+ function addScript(params) {
129
+ const { document, type = 'module', prepend, url, content } = params;
130
+ if (!url && !content) {
131
+ return;
132
+ }
133
+ const script = document.createElement('script');
134
+ script.type = type;
135
+ if (url) {
136
+ script.src = url;
137
+ }
138
+ else if (content) {
139
+ script.innerHTML = content;
140
+ }
141
+ if (prepend) {
142
+ document.head.prepend(script);
143
+ }
144
+ else {
145
+ document.head.appendChild(script);
146
+ }
147
+ }
148
+ /**
149
+ * Read a static HTML file, or return an error response if the file can't be read.
150
+ */
151
+ async function readStaticHtml(renderScriptPath) {
152
+ try {
153
+ return (await fsPromises.readFile(renderScriptPath, 'utf8')) || '';
154
+ }
155
+ catch (e) {
156
+ const message = `Error reading HTML file at "${renderScriptPath}":\n${e.stack || e}`;
157
+ console.error(message);
158
+ return getHtmlErrorResponse(message);
159
+ }
160
+ }
161
+ //# sourceMappingURL=renderRoute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderRoute.js","sourceRoot":"","sources":["../../src/renderRoute/renderRoute.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'path';\nimport { pathToFileURL } from 'url';\nimport { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';\nimport { JSDOM } from 'jsdom';\nimport fsPromises from 'fs/promises';\nimport { getHtmlErrorResponse } from './getHtmlErrorResponse.js';\nimport type { CompleteRenderRouteResult, RenderRouteResult } from '../types/RenderRouteResult.js';\nimport type { RenderRouteOptions } from '../types/RenderRouteOptions.js';\nimport type { RenderRouteFunction } from '../types/RenderRouteFunction.js';\nimport type { RenderRouteScript } from '../types/RenderRouteScript.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(options: RenderRouteOptions): Promise<CompleteRenderRouteResult> {\n const { route, session } = options;\n\n // Get an initial result from either the render script or default handler.\n let rawResult: RenderRouteResult;\n if (route.renderScript) {\n const renderScriptPath = path.resolve(session.appPath, route.renderScript);\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: CompleteRenderRouteResult = {\n contentType: 'text/html',\n statusCode: 200,\n ...(typeof rawResult === 'string' ? { content: rawResult } : rawResult),\n };\n\n // If it's HTML and not an error, inject the import map and appropriate scripts.\n // endsWith('html') covers: 'html', '.html', and 'text/html'\n // (Might need to modify the status check in the future if there are other codes where injecting\n // scripts is appropriate, but for now, only do this for 200.)\n if (result.contentType.endsWith('html') && result.statusCode === 200) {\n injectScripts(options, result);\n }\n\n return result;\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: RenderRouteOptions & { renderScriptPath: string },\n): Promise<RenderRouteResult> {\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: RenderRouteFunction | undefined;\n let errorMessage: string | undefined;\n try {\n const importResult = ((await import(renderScriptUrl + cacheBreakerQueryParam)) as RenderRouteScript).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: RenderRouteOptions, result: CompleteRenderRouteResult): void {\n const { route, overlayScript, entryScript, inlineScripts, importMap } = options;\n let jsdom: JSDOM;\n try {\n jsdom = new JSDOM(result.content);\n } catch (e) {\n // Trying to write a test for the above, it seemed very permissive, but catch just in case\n console.error(`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 // inject the import map.\n if (entryScript || overlayScript) {\n addScript({ document, type: 'importmap', content: JSON.stringify(importMap, null, 2) });\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<RenderRouteResult> {\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"]}
@@ -15,7 +15,7 @@ import type { AppServer } from './types/AppServer.js';
15
15
  * can support whichever routes the app needs, while the bundle server can provide package
16
16
  * assets in various forms using its own routing.
17
17
  */
18
- export declare function startAppServer({ session, bundleServer, apiServer, definition, config, packages, packageImportPaths, reporter, packageHashes, middlewareMode, server, }: {
18
+ export declare function startAppServer(params: {
19
19
  session: Session;
20
20
  definition: PackageJson;
21
21
  bundleServer: BundleServer;
@@ -1 +1 @@
1
- {"version":3,"file":"startAppServer.d.ts","sourceRoot":"","sources":["../src/startAppServer.ts"],"names":[],"mappings":";AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAGxG,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,EACnC,OAAO,EACP,YAAY,EACZ,SAAS,EACT,UAAU,EACV,MAA8B,EAC9B,QAAQ,EACR,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,cAAc,EACd,MAAM,GACP,EAAE;IACD,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,WAAW,CAAC;IACxB,YAAY,EAAE,YAAY,CAAC;IAC3B,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,QAAQ,EAAE,YAAY,CAAC;IACvB,aAAa,EAAE,aAAa,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,SAAS,CAAC,CAoDrB"}
1
+ {"version":3,"file":"startAppServer.d.ts","sourceRoot":"","sources":["../src/startAppServer.ts"],"names":[],"mappings":";AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAGxG,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,WAAW,CAAC;IACxB,YAAY,EAAE,YAAY,CAAC;IAC3B,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,QAAQ,EAAE,YAAY,CAAC;IACvB,aAAa,EAAE,aAAa,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,SAAS,CAAC,CAgCrB"}
@@ -10,14 +10,12 @@ import { cyan } from '@ms-cloudpack/task-reporter';
10
10
  * can support whichever routes the app needs, while the bundle server can provide package
11
11
  * assets in various forms using its own routing.
12
12
  */
13
- export async function startAppServer({ session, bundleServer, apiServer, definition, config = {}, packages, packageImportPaths, reporter, packageHashes, middlewareMode, server, }) {
14
- const { devServer = {} } = config || {};
13
+ export async function startAppServer(params) {
14
+ const { session, definition, config, reporter, middlewareMode, server } = params;
15
+ const { devServer = {} } = config;
15
16
  // Read the port from the config file or default to array of ports.
16
17
  const requireSpecifiedPort = devServer?.port !== undefined;
17
18
  const ports = devServer?.port ?? [5000, 5001, 5002, 5003];
18
- // Directory to serve as plain static assets. Defaults to "public".
19
- // Can be overridden by setting the 'publicDir' option in the cloudpack config.
20
- // const publicDir = path.resolve(devServer.publicPath ?? 'public');
21
19
  const task = reporter.addTask(`Starting app server for "${definition.name}"`);
22
20
  try {
23
21
  const { app, close, port, url } = await createExpressApp({
@@ -25,30 +23,13 @@ export async function startAppServer({ session, bundleServer, apiServer, definit
25
23
  requireSpecifiedPort,
26
24
  hostname: devServer?.domain,
27
25
  requestHeaders: devServer?.requestHeaders,
28
- sslOptions: config.devServer?.https,
29
- // publicDir,
26
+ sslOptions: devServer?.https,
30
27
  middlewareMode,
31
28
  server,
32
29
  });
33
- createRoutes({
34
- app,
35
- url,
36
- session,
37
- definition,
38
- bundleServer,
39
- apiServer,
40
- config,
41
- packages,
42
- packageImportPaths,
43
- packageHashes,
44
- });
30
+ createRoutes({ ...params, app, url });
45
31
  task.complete({ message: `Available @ ${cyan(url)}`, forceShow: true });
46
- return {
47
- app,
48
- close,
49
- port,
50
- url,
51
- };
32
+ return { app, close, port, url };
52
33
  }
53
34
  catch (err) {
54
35
  task.complete({ status: 'fail', message: 'Failed to start app server', forceShow: true });
@@ -1 +1 @@
1
- {"version":3,"file":"startAppServer.js","sourceRoot":"","sources":["../src/startAppServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAqB,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAOtE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,EACnC,OAAO,EACP,YAAY,EACZ,SAAS,EACT,UAAU,EACV,MAAM,GAAG,EAAqB,EAC9B,QAAQ,EACR,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,cAAc,EACd,MAAM,GAaP;IACC,MAAM,EAAE,SAAS,GAAG,EAAE,EAAE,GAAG,MAAM,IAAK,EAAsB,CAAC;IAE7D,mEAAmE;IACnE,MAAM,oBAAoB,GAAG,SAAS,EAAE,IAAI,KAAK,SAAS,CAAC;IAC3D,MAAM,KAAK,GAAG,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAE1D,mEAAmE;IACnE,+EAA+E;IAC/E,oEAAoE;IAEpE,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,4BAA4B,UAAU,CAAC,IAAI,GAAG,CAAC,CAAC;IAE9E,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,gBAAgB,CAAC;YACvD,SAAS,EAAE,KAAK;YAChB,oBAAoB;YACpB,QAAQ,EAAE,SAAS,EAAE,MAAM;YAC3B,cAAc,EAAE,SAAS,EAAE,cAAc;YACzC,UAAU,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK;YACnC,cAAc;YACd,cAAc;YACd,MAAM;SACP,CAAC,CAAC;QAEH,YAAY,CAAC;YACX,GAAG;YACH,GAAG;YACH,OAAO;YACP,UAAU;YACV,YAAY;YACZ,SAAS;YACT,MAAM;YACN,QAAQ;YACR,kBAAkB;YAClB,aAAa;SACd,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,eAAe,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAExE,OAAO;YACL,GAAG;YACH,KAAK;YACL,IAAI;YACJ,GAAG;SACJ,CAAC;IACJ,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,4BAA4B,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1F,0BAA0B,CAAC,GAAG,EAAE,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QAE5D,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC","sourcesContent":["import { createExpressApp } from '@ms-cloudpack/create-express-app';\nimport type { BundleServer } from '@ms-cloudpack/bundle-server';\nimport type { CloudpackConfig, PackageDefinitionsCache, PackageJson } from '@ms-cloudpack/common-types';\nimport { createRoutes } from './createRoutes.js';\nimport { handleErrorPortUnavailable } from './handleErrorPortUnavailable.js';\nimport { type TaskReporter, cyan } from '@ms-cloudpack/task-reporter';\nimport type { ApiServer, Session } from '@ms-cloudpack/api-server';\nimport type { PackageImportPaths } from '@ms-cloudpack/import-map';\nimport type { PackageHashes } from '@ms-cloudpack/package-hashes';\nimport type { Server } from 'http';\nimport type { AppServer } from './types/AppServer.js';\n\n/**\n * The app server hosts the appropriate routes for the web app, primarily returning html content\n * which loads resources from the bundle server.\n *\n * Separating the app server from the bundle service keeps the routes separate - the app server\n * can support whichever routes the app needs, while the bundle server can provide package\n * assets in various forms using its own routing.\n */\nexport async function startAppServer({\n session,\n bundleServer,\n apiServer,\n definition,\n config = {} as CloudpackConfig,\n packages,\n packageImportPaths,\n reporter,\n packageHashes,\n middlewareMode,\n server,\n}: {\n session: Session;\n definition: PackageJson;\n bundleServer: BundleServer;\n apiServer: ApiServer;\n config: CloudpackConfig;\n packages: PackageDefinitionsCache;\n packageImportPaths: PackageImportPaths;\n reporter: TaskReporter;\n packageHashes: PackageHashes;\n middlewareMode?: boolean;\n server?: Server;\n}): Promise<AppServer> {\n const { devServer = {} } = config || ({} as CloudpackConfig);\n\n // Read the port from the config file or default to array of ports.\n const requireSpecifiedPort = devServer?.port !== undefined;\n const ports = devServer?.port ?? [5000, 5001, 5002, 5003];\n\n // Directory to serve as plain static assets. Defaults to \"public\".\n // Can be overridden by setting the 'publicDir' option in the cloudpack config.\n // const publicDir = path.resolve(devServer.publicPath ?? 'public');\n\n const task = reporter.addTask(`Starting app server for \"${definition.name}\"`);\n\n try {\n const { app, close, port, url } = await createExpressApp({\n portRange: ports,\n requireSpecifiedPort,\n hostname: devServer?.domain,\n requestHeaders: devServer?.requestHeaders,\n sslOptions: config.devServer?.https,\n // publicDir,\n middlewareMode,\n server,\n });\n\n createRoutes({\n app,\n url,\n session,\n definition,\n bundleServer,\n apiServer,\n config,\n packages,\n packageImportPaths,\n packageHashes,\n });\n\n task.complete({ message: `Available @ ${cyan(url)}`, forceShow: true });\n\n return {\n app,\n close,\n port,\n url,\n };\n } catch (err: unknown) {\n task.complete({ status: 'fail', message: 'Failed to start app server', forceShow: true });\n handleErrorPortUnavailable(err, session.projectName, ports);\n\n throw err;\n }\n}\n"]}
1
+ {"version":3,"file":"startAppServer.js","sourceRoot":"","sources":["../src/startAppServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAqB,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAOtE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAYpC;IACC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IACjF,MAAM,EAAE,SAAS,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC;IAElC,mEAAmE;IACnE,MAAM,oBAAoB,GAAG,SAAS,EAAE,IAAI,KAAK,SAAS,CAAC;IAC3D,MAAM,KAAK,GAAG,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAE1D,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,4BAA4B,UAAU,CAAC,IAAI,GAAG,CAAC,CAAC;IAE9E,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,gBAAgB,CAAC;YACvD,SAAS,EAAE,KAAK;YAChB,oBAAoB;YACpB,QAAQ,EAAE,SAAS,EAAE,MAAM;YAC3B,cAAc,EAAE,SAAS,EAAE,cAAc;YACzC,UAAU,EAAE,SAAS,EAAE,KAAK;YAC5B,cAAc;YACd,MAAM;SACP,CAAC,CAAC;QAEH,YAAY,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtC,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,eAAe,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAExE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IACnC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,4BAA4B,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1F,0BAA0B,CAAC,GAAG,EAAE,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QAE5D,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC","sourcesContent":["import { createExpressApp } from '@ms-cloudpack/create-express-app';\nimport type { BundleServer } from '@ms-cloudpack/bundle-server';\nimport type { CloudpackConfig, PackageDefinitionsCache, PackageJson } from '@ms-cloudpack/common-types';\nimport { createRoutes } from './createRoutes.js';\nimport { handleErrorPortUnavailable } from './handleErrorPortUnavailable.js';\nimport { type TaskReporter, cyan } from '@ms-cloudpack/task-reporter';\nimport type { ApiServer, Session } from '@ms-cloudpack/api-server';\nimport type { PackageImportPaths } from '@ms-cloudpack/import-map';\nimport type { PackageHashes } from '@ms-cloudpack/package-hashes';\nimport type { Server } from 'http';\nimport type { AppServer } from './types/AppServer.js';\n\n/**\n * The app server hosts the appropriate routes for the web app, primarily returning html content\n * which loads resources from the bundle server.\n *\n * Separating the app server from the bundle service keeps the routes separate - the app server\n * can support whichever routes the app needs, while the bundle server can provide package\n * assets in various forms using its own routing.\n */\nexport async function startAppServer(params: {\n session: Session;\n definition: PackageJson;\n bundleServer: BundleServer;\n apiServer: ApiServer;\n config: CloudpackConfig;\n packages: PackageDefinitionsCache;\n packageImportPaths: PackageImportPaths;\n reporter: TaskReporter;\n packageHashes: PackageHashes;\n middlewareMode?: boolean;\n server?: Server;\n}): Promise<AppServer> {\n const { session, definition, config, reporter, middlewareMode, server } = params;\n const { devServer = {} } = config;\n\n // Read the port from the config file or default to array of ports.\n const requireSpecifiedPort = devServer?.port !== undefined;\n const ports = devServer?.port ?? [5000, 5001, 5002, 5003];\n\n const task = reporter.addTask(`Starting app server for \"${definition.name}\"`);\n\n try {\n const { app, close, port, url } = await createExpressApp({\n portRange: ports,\n requireSpecifiedPort,\n hostname: devServer?.domain,\n requestHeaders: devServer?.requestHeaders,\n sslOptions: devServer?.https,\n middlewareMode,\n server,\n });\n\n createRoutes({ ...params, app, url });\n\n task.complete({ message: `Available @ ${cyan(url)}`, forceShow: true });\n\n return { app, close, port, url };\n } catch (err: unknown) {\n task.complete({ status: 'fail', message: 'Failed to start app server', forceShow: true });\n handleErrorPortUnavailable(err, session.projectName, ports);\n\n throw err;\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { RenderRouteOptions } from './RenderRouteOptions.js';
2
+ import type { RenderRouteResult } from './RenderRouteResult.js';
3
+ /**
4
+ * A custom server render function that can be used to override the default HTML response.
5
+ */
6
+ export type RenderRouteFunction = (options: RenderRouteOptions) => Promise<RenderRouteResult> | RenderRouteResult;
7
+ //# sourceMappingURL=RenderRouteFunction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderRouteFunction.d.ts","sourceRoot":"","sources":["../../src/types/RenderRouteFunction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,iBAAiB,CAAC,GAAG,iBAAiB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=RenderRouteFunction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderRouteFunction.js","sourceRoot":"","sources":["../../src/types/RenderRouteFunction.ts"],"names":[],"mappings":"","sourcesContent":["import type { RenderRouteOptions } from './RenderRouteOptions.js';\nimport type { RenderRouteResult } from './RenderRouteResult.js';\n\n/**\n * A custom server render function that can be used to override the default HTML response.\n */\nexport type RenderRouteFunction = (options: RenderRouteOptions) => Promise<RenderRouteResult> | RenderRouteResult;\n"]}
@@ -0,0 +1,20 @@
1
+ import type { ImportMap } from '@ms-cloudpack/import-map';
2
+ import type { PackageJson, Route } from '@ms-cloudpack/common-types';
3
+ import type { Request, Response } from '@ms-cloudpack/create-express-app';
4
+ import type { Session } from '@ms-cloudpack/api-server';
5
+ /**
6
+ * The input options for a `renderScript` custom server render function.
7
+ */
8
+ export interface RenderRouteOptions {
9
+ session: Session;
10
+ route: Route;
11
+ baseUrl: string;
12
+ definition: PackageJson;
13
+ importMap: ImportMap;
14
+ overlayScript: string | undefined;
15
+ entryScript: string | undefined;
16
+ inlineScripts: string[];
17
+ req: Request;
18
+ res: Response;
19
+ }
20
+ //# sourceMappingURL=RenderRouteOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderRouteOptions.d.ts","sourceRoot":"","sources":["../../src/types/RenderRouteOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,WAAW,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,QAAQ,CAAC;CACf"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=RenderRouteOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderRouteOptions.js","sourceRoot":"","sources":["../../src/types/RenderRouteOptions.ts"],"names":[],"mappings":"","sourcesContent":["import type { ImportMap } from '@ms-cloudpack/import-map';\nimport type { PackageJson, Route } from '@ms-cloudpack/common-types';\nimport type { Request, Response } from '@ms-cloudpack/create-express-app';\nimport type { Session } from '@ms-cloudpack/api-server';\n\n/**\n * The input options for a `renderScript` custom server render function.\n */\nexport interface RenderRouteOptions {\n session: Session;\n route: Route;\n baseUrl: string;\n definition: PackageJson;\n importMap: ImportMap;\n overlayScript: string | undefined;\n entryScript: string | undefined;\n inlineScripts: string[];\n req: Request;\n res: Response;\n}\n"]}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The complete expected result of the `renderScript` custom server render function.
3
+ */
4
+ export interface CompleteRenderRouteResult {
5
+ content: string;
6
+ statusCode: number;
7
+ contentType: string;
8
+ }
9
+ /**
10
+ * The expected result of the `renderScript` custom server render function.
11
+ *
12
+ * If the result is a string, it's assumed to be the HTML content.
13
+ *
14
+ * If an object and the `content` is non-HTML, `contentType` must be set.
15
+ */
16
+ export type RenderRouteResult = string | ({
17
+ content: string;
18
+ } & Partial<CompleteRenderRouteResult>);
19
+ //# sourceMappingURL=RenderRouteResult.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderRouteResult.d.ts","sourceRoot":"","sources":["../../src/types/RenderRouteResult.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=RenderRouteResult.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderRouteResult.js","sourceRoot":"","sources":["../../src/types/RenderRouteResult.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The complete expected result of the `renderScript` custom server render function.\n */\nexport interface CompleteRenderRouteResult {\n content: string;\n statusCode: number;\n contentType: string;\n}\n\n/**\n * The expected result of the `renderScript` custom server render function.\n *\n * If the result is a string, it's assumed to be the HTML content.\n *\n * If an object and the `content` is non-HTML, `contentType` must be set.\n */\nexport type RenderRouteResult = string | ({ content: string } & Partial<CompleteRenderRouteResult>);\n"]}
@@ -0,0 +1,8 @@
1
+ import type { RenderRouteFunction } from './RenderRouteFunction.js';
2
+ /**
3
+ * The expected shape of the `renderScript` custom server render function script.
4
+ */
5
+ export type RenderRouteScript = {
6
+ default: RenderRouteFunction;
7
+ };
8
+ //# sourceMappingURL=RenderRouteScript.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderRouteScript.d.ts","sourceRoot":"","sources":["../../src/types/RenderRouteScript.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAAE,OAAO,EAAE,mBAAmB,CAAA;CAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=RenderRouteScript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderRouteScript.js","sourceRoot":"","sources":["../../src/types/RenderRouteScript.ts"],"names":[],"mappings":"","sourcesContent":["import type { RenderRouteFunction } from './RenderRouteFunction.js';\n\n/**\n * The expected shape of the `renderScript` custom server render function script.\n */\nexport type RenderRouteScript = { default: RenderRouteFunction };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ms-cloudpack/app-server",
3
- "version": "0.3.13",
3
+ "version": "0.4.0",
4
4
  "description": "An implementation of the App server for Cloudpack.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,15 +14,15 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@ms-cloudpack/api-server": "^0.37.5",
18
- "@ms-cloudpack/bundle-server": "^0.2.57",
19
- "@ms-cloudpack/common-types": "^0.2.5",
20
- "@ms-cloudpack/create-express-app": "^1.6.6",
21
- "@ms-cloudpack/import-map": "^0.2.8",
22
- "@ms-cloudpack/overlay": "^0.16.106",
23
- "@ms-cloudpack/package-hashes": "^0.5.8",
17
+ "@ms-cloudpack/api-server": "^0.38.0",
18
+ "@ms-cloudpack/bundle-server": "^0.2.58",
19
+ "@ms-cloudpack/common-types": "^0.3.0",
20
+ "@ms-cloudpack/create-express-app": "^1.6.7",
21
+ "@ms-cloudpack/import-map": "^0.2.9",
22
+ "@ms-cloudpack/overlay": "^0.16.107",
23
+ "@ms-cloudpack/package-hashes": "^0.5.9",
24
24
  "@ms-cloudpack/path-string-parsing": "^1.2.1",
25
- "@ms-cloudpack/path-utilities": "^2.7.3",
25
+ "@ms-cloudpack/path-utilities": "^2.7.4",
26
26
  "@ms-cloudpack/task-reporter": "^0.13.1",
27
27
  "jsdom": "^22.0.0"
28
28
  },
@@ -1,6 +0,0 @@
1
- import type { CreateHtmlResult, CreateHtmlOptions } from '@ms-cloudpack/api-server';
2
- /**
3
- * Return the default HTML response.
4
- */
5
- export declare function getDefaultHtmlResponse({ definition }: CreateHtmlOptions): CreateHtmlResult;
6
- //# sourceMappingURL=getDefaultHtmlResponse.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getDefaultHtmlResponse.d.ts","sourceRoot":"","sources":["../src/getDefaultHtmlResponse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAEpF;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,UAAU,EAAE,EAAE,iBAAiB,GAAG,gBAAgB,CAc1F"}
@@ -1,18 +0,0 @@
1
- /**
2
- * Return the default HTML response.
3
- */
4
- export function getDefaultHtmlResponse({ definition }) {
5
- const { name } = definition;
6
- return [
7
- `<!DOCTYPE html>`,
8
- `<html lang="en">`,
9
- `<head>`,
10
- ` <title>${name}</title>`,
11
- `</head>`,
12
- `<body>`,
13
- `<div id="root"></div>`,
14
- `</body>`,
15
- `</html>`,
16
- ].join('\n');
17
- }
18
- //# sourceMappingURL=getDefaultHtmlResponse.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getDefaultHtmlResponse.js","sourceRoot":"","sources":["../src/getDefaultHtmlResponse.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAE,UAAU,EAAqB;IACtE,MAAM,EAAE,IAAI,EAAE,GAAG,UAAU,CAAC;IAE5B,OAAO;QACL,iBAAiB;QACjB,kBAAkB;QAClB,QAAQ;QACR,YAAY,IAAI,UAAU;QAC1B,SAAS;QACT,QAAQ;QACR,uBAAuB;QACvB,SAAS;QACT,SAAS;KACV,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC","sourcesContent":["import type { CreateHtmlResult, CreateHtmlOptions } from '@ms-cloudpack/api-server';\n\n/**\n * Return the default HTML response.\n */\nexport function getDefaultHtmlResponse({ definition }: CreateHtmlOptions): CreateHtmlResult {\n const { name } = definition;\n\n return [\n `<!DOCTYPE html>`,\n `<html lang=\"en\">`,\n `<head>`,\n ` <title>${name}</title>`,\n `</head>`,\n `<body>`,\n `<div id=\"root\"></div>`,\n `</body>`,\n `</html>`,\n ].join('\\n');\n}\n"]}
@@ -1,6 +0,0 @@
1
- import type { CreateHtmlResult, CreateHtmlOptions } from '@ms-cloudpack/api-server';
2
- /**
3
- * Get the HTML response for the given route. If the route has a custom render script, use that.
4
- */
5
- export declare function getHtmlResponse(options: CreateHtmlOptions): Promise<Required<Exclude<CreateHtmlResult, string>>>;
6
- //# sourceMappingURL=getHtmlResponse.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getHtmlResponse.d.ts","sourceRoot":"","sources":["../src/getHtmlResponse.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAKlC;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC,CAwFtD"}
@@ -1,117 +0,0 @@
1
- import path from 'path';
2
- import { pathToFileURL } from 'url';
3
- import { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';
4
- import { JSDOM } from 'jsdom';
5
- import fsPromises from 'fs/promises';
6
- /**
7
- * Get the HTML response for the given route. If the route has a custom render script, use that.
8
- */
9
- export async function getHtmlResponse(options) {
10
- const { route, session, overlayScript, entryScript, inlineScripts } = options;
11
- let createHtml = getDefaultHtmlResponse;
12
- if (route.renderScript) {
13
- const renderScriptPath = path.resolve(session.appPath, route.renderScript);
14
- let cacheBreakerQueryParam = `?t=${Date.now()}`;
15
- try {
16
- const { mtime } = await fsPromises.stat(path.resolve(session.appPath, route.renderScript));
17
- cacheBreakerQueryParam = `?t=${mtime.getTime()}`;
18
- }
19
- catch {
20
- /* no-op */
21
- }
22
- if (isHtml(renderScriptPath)) {
23
- createHtml = readStaticHtml;
24
- }
25
- else {
26
- // Get the html factory function from a script default export.
27
- const renderScriptUrl = pathToFileURL(renderScriptPath).toString();
28
- // Note: because there isn't a way to purge the require cache, we need to add a cache breaker query param to the
29
- // script path to ensure we get the latest version of the script. This could be improved by using the git hash of
30
- // the file if it exists, or a hash of the content, or even the timestamp of the file.
31
- try {
32
- createHtml = (await import(renderScriptUrl + cacheBreakerQueryParam)).default;
33
- }
34
- catch (e) {
35
- console.error(`Error importing render script at "${renderScriptUrl}":`, e);
36
- }
37
- }
38
- if (!createHtml) {
39
- console.error(`The render script at "${renderScriptPath}" does not export a default function.`);
40
- }
41
- }
42
- let content;
43
- let contentType = 'html';
44
- let statusCode = 200;
45
- const result = await createHtml(options);
46
- if (typeof result === 'string') {
47
- content = result;
48
- }
49
- else {
50
- content = result.content;
51
- contentType = result.contentType ?? contentType;
52
- statusCode = result.statusCode ?? statusCode;
53
- }
54
- // endsWith('html') covers: 'html', '.html', and 'text/html'
55
- if (contentType.endsWith('html')) {
56
- try {
57
- const htmlDocument = new JSDOM(content);
58
- const { document } = htmlDocument.window;
59
- // inject the import map.
60
- if (entryScript || overlayScript) {
61
- const script = document.createElement('script');
62
- script.type = 'importmap';
63
- script.innerHTML = JSON.stringify(session.getImportMap(), null, 2);
64
- document.head.prepend(script);
65
- }
66
- for (const inlineScript of inlineScripts) {
67
- const script = document.createElement('script');
68
- script.type = 'module';
69
- script.innerHTML = inlineScript;
70
- document.head.appendChild(script);
71
- }
72
- // inject the overlay script.
73
- addScript(document.head, overlayScript);
74
- // inject the entry script.
75
- addScript(document.head, entryScript);
76
- content = htmlDocument.serialize();
77
- }
78
- catch (e) {
79
- console.error(`Error parsing html response for rendering route "${route.match}"`, e);
80
- }
81
- }
82
- return {
83
- content,
84
- statusCode,
85
- contentType,
86
- };
87
- }
88
- /**
89
- * Helper function to add a script to the document.
90
- */
91
- function addScript(parentElement, url) {
92
- if (url) {
93
- const script = parentElement.ownerDocument.createElement('script');
94
- script.type = 'module';
95
- script.src = url;
96
- parentElement.appendChild(script);
97
- }
98
- }
99
- function isHtml(scriptPath) {
100
- // path has an .htm or .html extension; case insensitive.
101
- return scriptPath.toLowerCase().match(/\.htm(l)?$/) !== null;
102
- }
103
- async function readStaticHtml(options) {
104
- const { route } = options;
105
- let html = '';
106
- if (route.renderScript) {
107
- const htmlPath = path.resolve(options.session.appPath, route.renderScript);
108
- try {
109
- html = (await fsPromises.readFile(htmlPath, 'utf8')) || '';
110
- }
111
- catch (e) {
112
- console.error(`Error reading HTML file at "${htmlPath}".`, e);
113
- }
114
- }
115
- return html;
116
- }
117
- //# sourceMappingURL=getHtmlResponse.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getHtmlResponse.js","sourceRoot":"","sources":["../src/getHtmlResponse.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'path';\nimport { pathToFileURL } from 'url';\nimport type {\n CreateHtmlScript,\n CreateHtmlFunction,\n CreateHtmlResult,\n CreateHtmlOptions,\n} from '@ms-cloudpack/api-server';\nimport { getDefaultHtmlResponse } from './getDefaultHtmlResponse.js';\nimport { JSDOM } from 'jsdom';\nimport fsPromises from 'fs/promises';\n\n/**\n * Get the HTML response for the given route. If the route has a custom render script, use that.\n */\nexport async function getHtmlResponse(\n options: CreateHtmlOptions,\n): Promise<Required<Exclude<CreateHtmlResult, string>>> {\n const { route, session, overlayScript, entryScript, inlineScripts } = options;\n let createHtml: CreateHtmlFunction = getDefaultHtmlResponse;\n\n if (route.renderScript) {\n const renderScriptPath = path.resolve(session.appPath, route.renderScript);\n let cacheBreakerQueryParam = `?t=${Date.now()}`;\n\n try {\n const { mtime } = await fsPromises.stat(path.resolve(session.appPath, route.renderScript));\n\n cacheBreakerQueryParam = `?t=${mtime.getTime()}`;\n } catch {\n /* no-op */\n }\n\n if (isHtml(renderScriptPath)) {\n createHtml = readStaticHtml;\n } else {\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 try {\n createHtml = ((await import(renderScriptUrl + cacheBreakerQueryParam)) as CreateHtmlScript).default;\n } catch (e) {\n console.error(`Error importing render script at \"${renderScriptUrl}\":`, e);\n }\n }\n\n if (!createHtml) {\n console.error(`The render script at \"${renderScriptPath}\" does not export a default function.`);\n }\n }\n\n let content;\n let contentType = 'html';\n let statusCode = 200;\n\n const result = await createHtml(options);\n if (typeof result === 'string') {\n content = result;\n } else {\n content = result.content;\n contentType = result.contentType ?? contentType;\n statusCode = result.statusCode ?? statusCode;\n }\n\n // endsWith('html') covers: 'html', '.html', and 'text/html'\n if (contentType.endsWith('html')) {\n try {\n const htmlDocument = new JSDOM(content);\n const { document } = htmlDocument.window;\n\n // inject the import map.\n if (entryScript || overlayScript) {\n const script = document.createElement('script');\n script.type = 'importmap';\n script.innerHTML = JSON.stringify(session.getImportMap(), null, 2);\n document.head.prepend(script);\n }\n\n for (const inlineScript of inlineScripts) {\n const script = document.createElement('script');\n script.type = 'module';\n script.innerHTML = inlineScript;\n document.head.appendChild(script);\n }\n\n // inject the overlay script.\n addScript(document.head, overlayScript);\n\n // inject the entry script.\n addScript(document.head, entryScript);\n\n content = htmlDocument.serialize();\n } catch (e) {\n console.error(`Error parsing html response for rendering route \"${route.match}\"`, e);\n }\n }\n\n return {\n content,\n statusCode,\n contentType,\n };\n}\n\n/**\n * Helper function to add a script to the document.\n */\nfunction addScript(parentElement: HTMLElement, url: string | undefined) {\n if (url) {\n const script = parentElement.ownerDocument.createElement('script');\n\n script.type = 'module';\n script.src = url;\n parentElement.appendChild(script);\n }\n}\n\nfunction isHtml(scriptPath: string): boolean {\n // path has an .htm or .html extension; case insensitive.\n return scriptPath.toLowerCase().match(/\\.htm(l)?$/) !== null;\n}\n\nasync function readStaticHtml(options: CreateHtmlOptions): Promise<string> {\n const { route } = options;\n let html = '';\n\n if (route.renderScript) {\n const htmlPath = path.resolve(options.session.appPath, route.renderScript);\n\n try {\n html = (await fsPromises.readFile(htmlPath, 'utf8')) || '';\n } catch (e) {\n console.error(`Error reading HTML file at \"${htmlPath}\".`, e);\n }\n }\n\n return html;\n}\n"]}