@nerest/nerest 0.1.0 → 1.5.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 (89) hide show
  1. package/LICENSE.md +178 -0
  2. package/README.md +129 -40
  3. package/bin/index.ts +3 -0
  4. package/bin/typegen.ts +35 -0
  5. package/bin/watch.ts +3 -2
  6. package/build/configs/development.ts +63 -0
  7. package/build/configs/production.ts +59 -0
  8. package/build/configs/shared.ts +51 -0
  9. package/build/configs/vite-logger.development.ts +30 -0
  10. package/build/index.ts +53 -76
  11. package/client/index.ts +14 -2
  12. package/dist/bin/index.js +4 -0
  13. package/dist/bin/typegen.d.ts +1 -0
  14. package/dist/bin/typegen.js +31 -0
  15. package/dist/bin/watch.js +1 -2
  16. package/dist/build/configs/development.d.ts +4 -0
  17. package/dist/build/configs/development.js +53 -0
  18. package/dist/build/configs/production.d.ts +4 -0
  19. package/dist/build/configs/production.js +50 -0
  20. package/dist/build/configs/shared.d.ts +10 -0
  21. package/dist/build/configs/shared.js +24 -0
  22. package/dist/build/configs/vite-logger.development.d.ts +2 -0
  23. package/dist/build/configs/vite-logger.development.js +25 -0
  24. package/dist/build/index.js +39 -69
  25. package/dist/client/index.js +9 -2
  26. package/dist/server/development.d.ts +1 -1
  27. package/dist/server/development.js +51 -134
  28. package/dist/server/hooks/logger.d.ts +1 -2
  29. package/dist/server/hooks/logger.js +3 -0
  30. package/dist/server/hooks/props.d.ts +2 -2
  31. package/dist/server/hooks/props.js +2 -2
  32. package/dist/server/hooks/runtime.js +3 -3
  33. package/dist/server/{parts → loaders}/apps.d.ts +2 -1
  34. package/dist/server/loaders/apps.js +36 -0
  35. package/dist/server/loaders/assets.js +25 -2
  36. package/dist/server/loaders/build.d.ts +2 -0
  37. package/dist/server/loaders/build.js +11 -0
  38. package/dist/server/loaders/examples.js +7 -13
  39. package/dist/server/loaders/manifest.d.ts +8 -1
  40. package/dist/server/loaders/manifest.js +12 -4
  41. package/dist/server/loaders/preview.d.ts +4 -0
  42. package/dist/server/loaders/preview.js +11 -0
  43. package/dist/server/loaders/project.d.ts +16 -0
  44. package/dist/server/loaders/project.js +11 -0
  45. package/dist/server/loaders/schema.d.ts +2 -1
  46. package/dist/server/loaders/schema.js +12 -8
  47. package/dist/server/parts/k8s-probes.js +10 -6
  48. package/dist/server/parts/preview.d.ts +2 -1
  49. package/dist/server/parts/preview.js +5 -4
  50. package/dist/server/parts/render.d.ts +5 -3
  51. package/dist/server/parts/render.js +8 -8
  52. package/dist/server/parts/swagger.d.ts +2 -1
  53. package/dist/server/parts/swagger.js +8 -19
  54. package/dist/server/parts/validator.d.ts +3 -1
  55. package/dist/server/parts/validator.js +13 -0
  56. package/dist/server/production.js +17 -83
  57. package/dist/server/shared.d.ts +14 -0
  58. package/dist/server/shared.js +94 -0
  59. package/dist/server/utils.d.ts +1 -0
  60. package/dist/server/utils.js +5 -0
  61. package/package.json +46 -43
  62. package/schemas/nerest-build.schema.d.ts +19 -1
  63. package/schemas/nerest-build.schema.json +21 -1
  64. package/server/development.ts +67 -172
  65. package/server/hooks/logger.ts +3 -0
  66. package/server/hooks/props.ts +5 -5
  67. package/server/hooks/runtime.ts +3 -3
  68. package/server/loaders/apps.ts +58 -0
  69. package/server/loaders/assets.ts +30 -2
  70. package/server/loaders/build.ts +14 -0
  71. package/server/loaders/examples.ts +7 -15
  72. package/server/loaders/manifest.ts +23 -4
  73. package/server/loaders/preview.ts +17 -0
  74. package/server/loaders/project.ts +33 -0
  75. package/server/loaders/schema.ts +12 -10
  76. package/server/parts/k8s-probes.ts +26 -13
  77. package/server/parts/preview.ts +11 -4
  78. package/server/parts/render.ts +13 -9
  79. package/server/parts/swagger.ts +10 -29
  80. package/server/parts/validator.ts +14 -0
  81. package/server/production.ts +22 -110
  82. package/server/shared.ts +150 -0
  83. package/server/utils.ts +6 -0
  84. package/dist/server/parts/apps.js +0 -37
  85. package/dist/server/parts/props-hook.d.ts +0 -1
  86. package/dist/server/parts/props-hook.js +0 -8
  87. package/dist/server/parts/runtime-hook.d.ts +0 -2
  88. package/dist/server/parts/runtime-hook.js +0 -28
  89. package/server/parts/apps.ts +0 -59
@@ -1,10 +1,20 @@
1
+ import type { PreviewParts } from '../loaders/preview.js';
2
+
1
3
  // Renders the preview page available by convention at /api/{name}/examples/{example}
2
- export function renderPreviewPage(html: string, assets: string[]) {
4
+ export function renderPreviewPage(
5
+ html: string,
6
+ assets: string[],
7
+ parts: PreviewParts
8
+ ) {
3
9
  const { scripts, styles } = mapAssets(assets);
4
10
  return `
11
+ <!DOCTYPE html>
5
12
  <html>
6
13
  <head>
14
+ <meta charset="utf-8">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
16
  ${styles.join('\n')}
17
+ ${parts.head}
8
18
  </head>
9
19
  <body>
10
20
  ${html}
@@ -15,9 +25,6 @@ export function renderPreviewPage(html: string, assets: string[]) {
15
25
  }
16
26
 
17
27
  function mapAssets(assets: string[]) {
18
- // TODO: script type="module" is not supported by older browsers
19
- // but vite doesn't provide `nomodule` fallback by default
20
- // see @vitejs/plugin-legacy
21
28
  const scripts = assets
22
29
  .filter((src) => src.endsWith('.js'))
23
30
  .map((src) => `<script type="module" src="${src}"></script>`);
@@ -1,35 +1,39 @@
1
- import React from 'react';
1
+ import type { ComponentType } from 'react';
2
+ import { createElement } from 'react';
2
3
  import { renderToString } from 'react-dom/server';
3
- import { nanoid } from 'nanoid';
4
+ import type { Project } from '../loaders/project.js';
5
+ import { randomId } from '../utils.js';
4
6
 
5
7
  type RenderProps = {
6
8
  name: string;
7
9
  assets: string[];
8
- component: React.ComponentType;
10
+ component: ComponentType;
11
+ project: Project;
9
12
  };
10
13
 
11
14
  export function renderApp(
12
- { name, assets, component }: RenderProps,
15
+ { name, assets, component, project }: RenderProps,
13
16
  props: Record<string, unknown> = {}
14
17
  ) {
15
- const html = renderSsrComponent(name, component, props);
18
+ const html = renderSsrComponent(name, component, project, props);
16
19
  return { html, assets };
17
20
  }
18
21
 
19
22
  function renderSsrComponent(
20
23
  appName: string,
21
- appComponent: React.ComponentType,
24
+ appComponent: ComponentType,
25
+ project: Project,
22
26
  props: Record<string, unknown>
23
27
  ) {
24
- const html = renderToString(React.createElement(appComponent, props));
28
+ const html = renderToString(createElement(appComponent, props));
25
29
 
26
30
  // There may be multiple instances of the same app on the page,
27
31
  // so we will use a randomized id to avoid collisions
28
- const appId = nanoid();
32
+ const appId = randomId();
29
33
 
30
34
  // data-app-name and data-app-id are used by client entrypoint to hydrate
31
35
  // apps using correct serialized props
32
- const container = `<div data-app-name="${appName}" data-app-id="${appId}">${html}</div>`;
36
+ const container = `<div data-project-name="${project.name}" data-app-name="${appName}" data-app-id="${appId}">${html}</div>`;
33
37
  const script = `<script type="application/json" data-app-id="${appId}">${JSON.stringify(
34
38
  props
35
39
  )}</script>`;
@@ -1,43 +1,24 @@
1
- import path from 'path';
2
- import fs from 'fs';
3
1
  import type { FastifyInstance } from 'fastify';
4
2
  import fastifySwagger from '@fastify/swagger';
5
3
  import fastifySwaggerUi from '@fastify/swagger-ui';
6
4
 
5
+ import type { Project } from '../loaders/project.js';
6
+
7
7
  // Setup automatic OpenAPI specification compilation and enable
8
8
  // Swagger UI at the `/api` route
9
- export async function setupSwagger(app: FastifyInstance) {
10
- let appInfo: {
11
- name?: string;
12
- description?: string;
13
- version?: string;
14
- homepage?: string;
15
- repository?: string | { url?: string };
16
- } = {};
17
-
18
- try {
19
- const packageJson = fs.readFileSync(
20
- path.join(process.cwd(), 'package.json'),
21
- { encoding: 'utf-8' }
22
- );
23
- appInfo = JSON.parse(packageJson);
24
- } catch (e) {
25
- // We only use package.json info to setup Swagger info and links,
26
- // if we are unable to load them -- that's fine
27
- }
28
-
9
+ export async function setupSwagger(app: FastifyInstance, project: Project) {
29
10
  const homepage =
30
- appInfo.homepage ||
31
- (typeof appInfo.repository === 'string'
32
- ? appInfo.repository
33
- : appInfo.repository?.url);
11
+ project.homepage ||
12
+ (typeof project.repository === 'string'
13
+ ? project.repository
14
+ : project.repository?.url);
34
15
 
35
16
  await app.register(fastifySwagger, {
36
17
  openapi: {
37
18
  info: {
38
- title: appInfo.name ?? 'Nerest micro frontend',
39
- description: appInfo.description,
40
- version: appInfo.version ?? '',
19
+ title: project.name || 'Nerest micro frontend',
20
+ description: project.description,
21
+ version: project.version ?? '',
41
22
  contact: homepage
42
23
  ? {
43
24
  name: 'Homepage',
@@ -1,6 +1,7 @@
1
1
  import Ajv from 'ajv';
2
2
  import fastUri from 'fast-uri';
3
3
  import addFormats from 'ajv-formats';
4
+ import type { FastifyInstance } from 'fastify';
4
5
 
5
6
  // Ajv default export is broken, so we have to specify `.default`
6
7
  // manually: https://github.com/ajv-validator/ajv/issues/2132
@@ -20,3 +21,16 @@ export const validator = new Ajv.default({
20
21
  // `email`, `url`, etc. Used by default in fastify
21
22
  // https://www.npmjs.com/package/ajv-formats
22
23
  addFormats.default(validator);
24
+
25
+ // Setup schema validation. We have to use our own ajv instance that
26
+ // we can use both to validate request bodies and examples against
27
+ // app schemas
28
+ export function setupValidator(app: FastifyInstance) {
29
+ if (process.env.DISABLE_SCHEMA_VALIDATION) {
30
+ // If schema validation is disabled, return data as is without any checks
31
+ app.setValidatorCompiler(() => (data) => ({ value: data }));
32
+ } else {
33
+ // If schema validation is enabled, validate and coerce data via ajv
34
+ app.setValidatorCompiler(({ schema }) => validator.compile(schema));
35
+ }
36
+ }
@@ -1,130 +1,42 @@
1
1
  // This is the nerest production server entrypoint
2
- import path from 'path';
3
- import fs from 'fs/promises';
2
+ import type { ComponentType } from 'react';
3
+ import { createServer } from './shared.js';
4
+ import { loadNerestManifest } from './loaders/manifest.js';
4
5
 
5
- import fastify from 'fastify';
6
- import fastifyGracefulShutdown from 'fastify-graceful-shutdown';
7
- import type { RouteShorthandOptions } from 'fastify';
8
-
9
- import type { AppEntry } from './parts/apps.js';
10
- import { renderApp } from './parts/render.js';
11
- import { setupSwagger } from './parts/swagger.js';
12
- import { validator } from './parts/validator.js';
13
- import { renderPreviewPage } from './parts/preview.js';
14
- import { setupK8SProbes } from './parts/k8s-probes.js';
15
- import { runRuntimeHook } from './hooks/runtime.js';
16
- import { runPropsHook } from './hooks/props.js';
17
- import { runLoggerHook } from './hooks/logger.js';
18
-
19
- // TODO: refactor to merge the similar parts between production and development server?
20
- async function runProductionServer() {
6
+ // Important: this file is the server entrypoint that will be built by vite
7
+ // in `build/index.ts`. All of the import.meta.glob's will be resolved at build time
8
+ async function runProductionServer(port: number) {
21
9
  const root = process.cwd();
22
10
 
23
- // TODO: error handling for file reading
24
- const apps = JSON.parse(
25
- await fs.readFile(path.join(root, 'build/nerest-manifest.json'), {
26
- encoding: 'utf-8',
27
- })
28
- ) as Record<string, AppEntry>;
11
+ // Load project information from the manifest generated during production build
12
+ const { project, apps } = await loadNerestManifest(root);
29
13
 
30
14
  const components = import.meta.glob('/apps/*/index.tsx', {
31
15
  import: 'default',
32
16
  eager: true,
33
- }) as Record<string, React.ComponentType>;
17
+ }) as Record<string, ComponentType>;
34
18
 
35
19
  const propsHooks = import.meta.glob('/apps/*/props.ts', {
36
20
  eager: true,
37
21
  });
38
22
 
39
- const runtimeHook = import.meta.glob('/nerest-runtime.ts', { eager: true });
40
-
41
- const app = fastify({
42
- // Receive logger configuration from `nerest-runtime.logger()` or
43
- // use the default fastify one (`true`)
44
- logger:
45
- (await runLoggerHook(async () => runtimeHook['/nerest-runtime.ts'])) ??
46
- true,
23
+ const runtimeHook = import.meta.glob('/nerest/runtime.ts', { eager: true });
24
+
25
+ const app = await createServer({
26
+ root,
27
+ project,
28
+ apps,
29
+ loadComponent: async (entry: string) =>
30
+ components[`/apps/${entry}/index.tsx`],
31
+ loadPropsHook: async (entry: string) =>
32
+ propsHooks[`/apps/${entry}/props.ts`],
33
+ loadRuntimeHook: async () => runtimeHook['/nerest/runtime.ts'],
47
34
  });
48
35
 
49
- // Setup schema validation. We have to use our own ajv instance that
50
- // we can use both to validate request bodies and examples against
51
- // app schemas
52
- app.setValidatorCompiler(({ schema }) => validator.compile(schema));
53
-
54
- await setupSwagger(app);
55
-
56
- for (const appEntry of Object.values(apps)) {
57
- const { name, examples, schema, assets } = appEntry;
58
- const component = components[`/apps/${name}/index.tsx`];
59
- const propsHook = async () => propsHooks[`/apps/${name}/props.ts`];
60
-
61
- const routeOptions: RouteShorthandOptions = {};
62
-
63
- // TODO: report error if schema is missing, unless this app is client-only
64
- // TODO: disallow apps without schemas in production build
65
- if (schema) {
66
- routeOptions.schema = {
67
- // Use description as Swagger summary, since summary is visible
68
- // even when the route is collapsed in the UI
69
- summary: schema.description as string,
70
- // TODO: do we need to mix in examples like in the development server?
71
- body: schema,
72
- };
73
- }
74
-
75
- // POST /api/{name} -> render app with request.body as props
76
- app.post(`/api/${name}`, routeOptions, async (request) => {
77
- const props = await runPropsHook(request.body, request.log, propsHook);
78
- return renderApp({ name, assets, component }, props);
79
- });
80
-
81
- for (const [exampleName, example] of Object.entries(examples)) {
82
- // GET /api/{name}/examples/{example} -> render a preview page
83
- // with a predefined example body
84
- const exampleRoute = `/api/${name}/examples/${exampleName}`;
85
- app.get(
86
- exampleRoute,
87
- {
88
- schema: {
89
- // Add a clickable link to the example route in route's Swagger
90
- // description so it's easier to navigate to
91
- description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
92
- },
93
- },
94
- async (request, reply) => {
95
- const props = await runPropsHook(example, request.log, propsHook);
96
- const { html, assets: outAssets } = renderApp(
97
- {
98
- name,
99
- assets,
100
- component,
101
- },
102
- props
103
- );
104
-
105
- reply.type('text/html');
106
-
107
- return renderPreviewPage(html, outAssets);
108
- }
109
- );
110
- }
111
- }
112
-
113
- // Add graceful shutdown handler to prevent requests errors
114
- await app.register(fastifyGracefulShutdown);
115
-
116
- if (process.env.ENABLE_K8S_PROBES) {
117
- await setupK8SProbes(app);
118
- }
119
-
120
- // Execute runtime hook in nerest-runtime.ts if it exists
121
- await runRuntimeHook(app, async () => runtimeHook['/nerest-runtime.ts']);
122
-
123
- // TODO: remove hardcoded port
124
36
  await app.listen({
125
37
  host: '0.0.0.0',
126
- port: 3000,
38
+ port,
127
39
  });
128
40
  }
129
41
 
130
- runProductionServer();
42
+ runProductionServer(process.env.PORT ? Number(process.env.PORT) : 3000);
@@ -0,0 +1,150 @@
1
+ import fastify from 'fastify';
2
+ import type { FastifyInstance } from 'fastify';
3
+ import type { RouteShorthandOptions } from 'fastify';
4
+ import fastifyGracefulShutdown from 'fastify-graceful-shutdown';
5
+ import type { ComponentType } from 'react';
6
+ import { renderApp } from './parts/render.js';
7
+ import { setupSwagger } from './parts/swagger.js';
8
+ import { setupValidator, validator } from './parts/validator.js';
9
+ import { renderPreviewPage } from './parts/preview.js';
10
+ import { setupK8SProbes } from './parts/k8s-probes.js';
11
+ import { runRuntimeHook } from './hooks/runtime.js';
12
+ import { runPropsHook } from './hooks/props.js';
13
+ import { runLoggerHook } from './hooks/logger.js';
14
+ import type { Project } from './loaders/project.js';
15
+ import { loadPreviewParts } from './loaders/preview.js';
16
+ import type { PreviewParts } from './loaders/preview.js';
17
+ import type { AppEntry } from './loaders/apps.js';
18
+ import { randomId } from './utils.js';
19
+
20
+ type ServerOptions = {
21
+ root: string;
22
+ project: Project;
23
+ apps: Record<string, AppEntry>;
24
+ loadComponent: (entry: string) => Promise<ComponentType>;
25
+ loadPropsHook: (entry: string) => Promise<unknown>;
26
+ loadRuntimeHook: () => Promise<unknown>;
27
+ };
28
+
29
+ type ServerOptionsWithPreview = ServerOptions & {
30
+ previewParts: PreviewParts;
31
+ };
32
+
33
+ export async function createServer(options: ServerOptions) {
34
+ const { project, root, loadRuntimeHook } = options;
35
+
36
+ const app = fastify({
37
+ logger: (await runLoggerHook(loadRuntimeHook)) ?? true,
38
+ ignoreTrailingSlash: true,
39
+ useSemicolonDelimiter: false,
40
+ // JSON parsing can take a long time and blocks the event loop,
41
+ // so we need to limit the size of the body. 10MB is a good compromise
42
+ // baseline that was chosen by experimenting with real world usage
43
+ bodyLimit: 10 * 1024 * 1024,
44
+ genReqId(req): string {
45
+ return String(req.headers['x-request-id'] || randomId());
46
+ },
47
+ });
48
+
49
+ // Setup payload validation and Swagger based on apps' JSON Schema
50
+ setupValidator(app);
51
+ await setupSwagger(app, project);
52
+
53
+ // Load preview parts from `nerest/preview-{part}.html` files
54
+ const previewParts = await loadPreviewParts(root);
55
+
56
+ await setupRoutes(app, { ...options, previewParts });
57
+
58
+ // Add graceful shutdown handler to prevent requests errors
59
+ await app.register(fastifyGracefulShutdown);
60
+
61
+ if (process.env.ENABLE_K8S_PROBES) {
62
+ await setupK8SProbes(app);
63
+ }
64
+
65
+ // Execute runtime hook in nerest/runtime.ts if it exists
66
+ await runRuntimeHook(app, loadRuntimeHook);
67
+
68
+ return app;
69
+ }
70
+
71
+ async function setupRoutes(
72
+ app: FastifyInstance,
73
+ options: ServerOptionsWithPreview
74
+ ) {
75
+ const { project, apps, previewParts, loadComponent, loadPropsHook } = options;
76
+
77
+ for (const appEntry of Object.values(apps)) {
78
+ const { name, examples, schema, assets } = appEntry;
79
+
80
+ const routeOptions: RouteShorthandOptions = {};
81
+
82
+ // TODO: report error if schema is missing, making it mandatory
83
+ if (schema) {
84
+ routeOptions.schema = {
85
+ summary: schema.description as string,
86
+ // Tags are used to group routes in Swagger UI
87
+ tags: [name],
88
+ body: {
89
+ ...schema,
90
+ // Examples are also displayed in Swagger UI
91
+ examples: Object.values(examples),
92
+ },
93
+ };
94
+ }
95
+
96
+ // POST /api/{name} -> render app with request.body as props
97
+ app.post(`/api/${name}`, routeOptions, async (request) => {
98
+ const component = await loadComponent(name);
99
+ const props = await runPropsHook(
100
+ app,
101
+ () => loadPropsHook(name),
102
+ request.body
103
+ );
104
+ return renderApp({ name, assets, component, project }, props);
105
+ });
106
+
107
+ for (const [exampleName, example] of Object.entries(examples)) {
108
+ // Validate examples against schema
109
+ if (schema && !validator.validate(schema, example)) {
110
+ app.log.error(
111
+ `Example "${exampleName}" of app "${name}" does not satisfy schema: ${validator.errorsText()}`
112
+ );
113
+ }
114
+
115
+ // GET /api/{name}/examples/{example} -> render a preview page
116
+ const exampleRoute = `/api/${name}/examples/${exampleName}`;
117
+ app.get(
118
+ exampleRoute,
119
+ {
120
+ schema: {
121
+ // Add clickable link to go to the example in Swagger UI
122
+ description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
123
+ // Place examples under the same tag as the app
124
+ tags: [name],
125
+ },
126
+ },
127
+ async (request, reply) => {
128
+ const component = await loadComponent(name);
129
+ const props = await runPropsHook(
130
+ app,
131
+ () => loadPropsHook(name),
132
+ example
133
+ );
134
+ const { html, assets: outAssets } = renderApp(
135
+ {
136
+ name,
137
+ assets,
138
+ component,
139
+ project,
140
+ },
141
+ props
142
+ );
143
+
144
+ reply.type('text/html');
145
+ return renderPreviewPage(html, outAssets, previewParts);
146
+ }
147
+ );
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,6 @@
1
+ import crypto from 'crypto';
2
+
3
+ // Generate a random string ID 20 characters long
4
+ export function randomId() {
5
+ return crypto.randomBytes(10).toString('hex');
6
+ }
@@ -1,37 +0,0 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
3
- import { loadAppAssets } from '../loaders/assets.js';
4
- import { loadAppExamples } from '../loaders/examples.js';
5
- import { loadAppSchema } from '../loaders/schema.js';
6
- import { loadAppManifest } from '../loaders/manifest.js';
7
- // Build the record of the available apps by convention
8
- // apps -> /apps/{name}/index.tsx
9
- // examples -> /apps/{name}/examples/{example}.json
10
- export async function loadApps(root, deployedStaticPath) {
11
- const appsRoot = path.join(root, 'apps');
12
- const manifest = await loadAppManifest(root);
13
- const appsDirs = (await fs.readdir(appsRoot, { withFileTypes: true }))
14
- .filter((d) => d.isDirectory())
15
- .map((d) => d.name);
16
- const apps = [];
17
- for (const appDir of appsDirs) {
18
- apps.push(await loadApp(appsRoot, appDir, manifest, deployedStaticPath));
19
- }
20
- return Object.fromEntries(apps);
21
- }
22
- async function loadApp(appsRoot, name, manifest, deployedStaticPath) {
23
- // TODO: report problems with loading entries, assets and/or examples
24
- const appRoot = path.join(appsRoot, name);
25
- return [
26
- name,
27
- {
28
- name,
29
- root: appRoot,
30
- entry: path.join(appRoot, 'index.tsx'),
31
- propsHookEntry: path.join(appRoot, 'props.ts'),
32
- assets: loadAppAssets(name, manifest, deployedStaticPath),
33
- examples: await loadAppExamples(appRoot),
34
- schema: await loadAppSchema(appRoot),
35
- },
36
- ];
37
- }
@@ -1 +0,0 @@
1
- export declare function runPropsHook(props: unknown, loader: () => Promise<unknown>): Promise<Record<string, unknown>>;
@@ -1,8 +0,0 @@
1
- export async function runPropsHook(props, loader) {
2
- let module;
3
- try {
4
- module = (await loader());
5
- }
6
- catch { }
7
- return (typeof module?.default === 'function' ? module.default(props) : props);
8
- }
@@ -1,2 +0,0 @@
1
- import type { FastifyInstance } from 'fastify';
2
- export declare function runRuntimeHook(app: FastifyInstance, loader: () => Promise<unknown>): Promise<void>;
@@ -1,28 +0,0 @@
1
- // Load the runtime hook module and run it if it exists, passing down our
2
- // fastify instance. This hook can be used to modify fastify settings, add
3
- // plugins or routes on an individual app level.
4
- export async function runRuntimeHook(app, loader) {
5
- let module;
6
- try {
7
- module = (await loader());
8
- }
9
- catch { }
10
- if (typeof module?.default === 'function') {
11
- // If module exists and exports a default function, execute it and
12
- // pass down the fastify instance
13
- try {
14
- await module.default(app);
15
- }
16
- catch (e) {
17
- console.error('Failed to execute runtime hook', e);
18
- process.exit(1);
19
- }
20
- }
21
- else if (module) {
22
- console.error("Runtime hook found, but doesn't export default function!");
23
- process.exit(1);
24
- }
25
- else {
26
- console.log('Runtime hook not found, skipping...');
27
- }
28
- }
@@ -1,59 +0,0 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
3
- import type { Manifest } from 'vite';
4
-
5
- import { loadAppAssets } from '../loaders/assets.js';
6
- import { loadAppExamples } from '../loaders/examples.js';
7
- import { loadAppSchema } from '../loaders/schema.js';
8
- import { loadAppManifest } from '../loaders/manifest.js';
9
-
10
- export type AppEntry = {
11
- name: string;
12
- root: string;
13
- entry: string;
14
- propsHookEntry: string;
15
- assets: string[];
16
- examples: Record<string, unknown>;
17
- schema: Record<string, unknown> | null;
18
- };
19
-
20
- // Build the record of the available apps by convention
21
- // apps -> /apps/{name}/index.tsx
22
- // examples -> /apps/{name}/examples/{example}.json
23
- export async function loadApps(root: string, deployedStaticPath: string) {
24
- const appsRoot = path.join(root, 'apps');
25
- const manifest = await loadAppManifest(root);
26
-
27
- const appsDirs = (await fs.readdir(appsRoot, { withFileTypes: true }))
28
- .filter((d) => d.isDirectory())
29
- .map((d) => d.name);
30
-
31
- const apps: Array<[name: string, entry: AppEntry]> = [];
32
- for (const appDir of appsDirs) {
33
- apps.push(await loadApp(appsRoot, appDir, manifest, deployedStaticPath));
34
- }
35
-
36
- return Object.fromEntries(apps);
37
- }
38
-
39
- async function loadApp(
40
- appsRoot: string,
41
- name: string,
42
- manifest: Manifest,
43
- deployedStaticPath: string
44
- ): Promise<[name: string, entry: AppEntry]> {
45
- // TODO: report problems with loading entries, assets and/or examples
46
- const appRoot = path.join(appsRoot, name);
47
- return [
48
- name,
49
- {
50
- name,
51
- root: appRoot,
52
- entry: path.join(appRoot, 'index.tsx'),
53
- propsHookEntry: path.join(appRoot, 'props.ts'),
54
- assets: loadAppAssets(name, manifest, deployedStaticPath),
55
- examples: await loadAppExamples(appRoot),
56
- schema: await loadAppSchema(appRoot),
57
- },
58
- ];
59
- }