@nerest/nerest 0.0.9 → 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 +148 -39
  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 +52 -130
  28. package/dist/server/hooks/logger.d.ts +1 -0
  29. package/dist/server/hooks/logger.js +24 -0
  30. package/dist/server/hooks/props.d.ts +2 -1
  31. package/dist/server/hooks/props.js +6 -4
  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 -80
  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 -164
  65. package/server/hooks/logger.ts +31 -0
  66. package/server/hooks/props.ts +11 -6
  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 -106
  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,126 +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
-
18
- // TODO: refactor to merge the similar parts between production and development server?
19
- 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) {
20
9
  const root = process.cwd();
21
10
 
22
- // TODO: error handling for file reading
23
- const apps = JSON.parse(
24
- await fs.readFile(path.join(root, 'build/nerest-manifest.json'), {
25
- encoding: 'utf-8',
26
- })
27
- ) as Record<string, AppEntry>;
11
+ // Load project information from the manifest generated during production build
12
+ const { project, apps } = await loadNerestManifest(root);
28
13
 
29
14
  const components = import.meta.glob('/apps/*/index.tsx', {
30
15
  import: 'default',
31
16
  eager: true,
32
- }) as Record<string, React.ComponentType>;
17
+ }) as Record<string, ComponentType>;
33
18
 
34
19
  const propsHooks = import.meta.glob('/apps/*/props.ts', {
35
20
  eager: true,
36
21
  });
37
22
 
38
- const app = fastify();
39
-
40
- // Setup schema validation. We have to use our own ajv instance that
41
- // we can use both to validate request bodies and examples against
42
- // app schemas
43
- app.setValidatorCompiler(({ schema }) => validator.compile(schema));
44
-
45
- await setupSwagger(app);
46
-
47
- for (const appEntry of Object.values(apps)) {
48
- const { name, examples, schema, assets } = appEntry;
49
- const component = components[`/apps/${name}/index.tsx`];
50
- const propsHook = async () => propsHooks[`/apps/${name}/props.ts`];
51
-
52
- const routeOptions: RouteShorthandOptions = {};
53
-
54
- // TODO: report error if schema is missing, unless this app is client-only
55
- // TODO: disallow apps without schemas in production build
56
- if (schema) {
57
- routeOptions.schema = {
58
- // Use description as Swagger summary, since summary is visible
59
- // even when the route is collapsed in the UI
60
- summary: schema.description as string,
61
- // TODO: do we need to mix in examples like in the development server?
62
- body: schema,
63
- };
64
- }
65
-
66
- // POST /api/{name} -> render app with request.body as props
67
- app.post(`/api/${name}`, routeOptions, async (request) => {
68
- const props = await runPropsHook(request.body, propsHook);
69
- return renderApp({ name, assets, component }, props);
70
- });
71
-
72
- for (const [exampleName, example] of Object.entries(examples)) {
73
- // GET /api/{name}/examples/{example} -> render a preview page
74
- // with a predefined example body
75
- const exampleRoute = `/api/${name}/examples/${exampleName}`;
76
- app.get(
77
- exampleRoute,
78
- {
79
- schema: {
80
- // Add a clickable link to the example route in route's Swagger
81
- // description so it's easier to navigate to
82
- description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
83
- },
84
- },
85
- async (_, reply) => {
86
- const props = await runPropsHook(example, propsHook);
87
- const { html, assets: outAssets } = renderApp(
88
- {
89
- name,
90
- assets,
91
- component,
92
- },
93
- props
94
- );
95
-
96
- reply.type('text/html');
97
-
98
- return renderPreviewPage(html, outAssets);
99
- }
100
- );
101
- }
102
- }
103
-
104
- // Add graceful shutdown handler to prevent requests errors
105
- await app.register(fastifyGracefulShutdown);
106
-
107
- if (process.env.ENABLE_K8S_PROBES) {
108
- await setupK8SProbes(app);
109
- }
110
-
111
- // Execute runtime hook in nerest-runtime.ts if it exists
112
- await runRuntimeHook(app, async () => {
113
- const glob = import.meta.glob('/nerest-runtime.ts', { eager: true });
114
- return glob['/nerest-runtime.ts'];
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'],
115
34
  });
116
35
 
117
- // TODO: remove hardcoded port
118
36
  await app.listen({
119
37
  host: '0.0.0.0',
120
- port: 3000,
38
+ port,
121
39
  });
122
-
123
- console.log('Nerest is listening on 0.0.0.0:3000');
124
40
  }
125
41
 
126
- 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
- }