@nerest/nerest 0.0.9 → 0.1.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.
- package/README.md +23 -3
- package/dist/server/development.js +10 -5
- package/dist/server/hooks/logger.d.ts +2 -0
- package/dist/server/hooks/logger.js +21 -0
- package/dist/server/hooks/props.d.ts +2 -1
- package/dist/server/hooks/props.js +6 -4
- package/dist/server/production.js +12 -9
- package/package.json +1 -1
- package/server/development.ts +14 -6
- package/server/hooks/logger.ts +28 -0
- package/server/hooks/props.ts +9 -4
- package/server/production.ts +14 -10
package/README.md
CHANGED
|
@@ -39,10 +39,14 @@ OpenAPI specification is compiled automatically based on the provided schemas an
|
|
|
39
39
|
|
|
40
40
|
### Props Hook (`/props.ts`)
|
|
41
41
|
|
|
42
|
-
The app directory may contain a `props.ts` module that exports a default function. If it exists, this function will be executed on the server for every incoming request, receiving
|
|
42
|
+
The app directory may contain a `props.ts` module that exports a default function. If it exists, this function will be executed on the server for every incoming request, receiving the body of the request and a logger as parameters. You can use this hook to modify and return a new object, which will be passed down to the `index.tsx` entrypoint component instead. For example:
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
|
-
|
|
45
|
+
import type { FastifyBaseLogger } from 'fastify';
|
|
46
|
+
|
|
47
|
+
export default function (props: Props, logger: FastifyBaseLogger) {
|
|
48
|
+
logger.info('Hello from props.ts!');
|
|
49
|
+
|
|
46
50
|
return {
|
|
47
51
|
...props,
|
|
48
52
|
greeting: `${props.greeting} (modified in props.ts)`,
|
|
@@ -99,7 +103,7 @@ Excludes modules from the client build and maps them to globally available const
|
|
|
99
103
|
|
|
100
104
|
Here `react` and `react-dom` imports will be replaced with accessing the respective `window` constants.
|
|
101
105
|
|
|
102
|
-
### Runtime
|
|
106
|
+
### Runtime Hook
|
|
103
107
|
|
|
104
108
|
If the module `nerest-runtime.ts` exists in the root of the micro frontend and exports a default function, this function will be executed when the server starts, and the fastify app instance will be passed to it as its only argument. Example of `nerest-runtime.ts`:
|
|
105
109
|
|
|
@@ -113,6 +117,22 @@ export default function (app: FastifyInstance) {
|
|
|
113
117
|
|
|
114
118
|
This runtime hook can be used to adjust fastify settings, register additional plugins or add custom routes.
|
|
115
119
|
|
|
120
|
+
#### Logger Configuration
|
|
121
|
+
|
|
122
|
+
Nerest uses the default server-side [fastify logger](https://fastify.dev/docs/latest/Reference/Logging/#logging), enabled by default. To configure or disable it, export a `logger` function from the `nerest-runtime.ts` module. It will be called on server start, and the return value will be passed to fastify as logger configuration.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import type { FastifyServerOptions } from 'fastify';
|
|
126
|
+
|
|
127
|
+
export function logger(): FastifyServerOptions['logger'] {
|
|
128
|
+
return { prettyPrint: true };
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
To disable the logger, return `false` from the function.
|
|
133
|
+
|
|
134
|
+
You can also supply your own logger instance. Instead of passing configuration options, pass the instance. The logger you supply must conform to the [Pino](https://github.com/pinojs/pino) interface; that is, it must have the following methods: `info`, `error`, `debug`, `fatal`, `warn`, `trace`, `silent`, `child` and a string property `level`.
|
|
135
|
+
|
|
116
136
|
## Development
|
|
117
137
|
|
|
118
138
|
Run the build script to build the framework.
|
|
@@ -12,6 +12,7 @@ import { setupSwagger } from './parts/swagger.js';
|
|
|
12
12
|
import { setupK8SProbes } from './parts/k8s-probes.js';
|
|
13
13
|
import { runRuntimeHook } from './hooks/runtime.js';
|
|
14
14
|
import { runPropsHook } from './hooks/props.js';
|
|
15
|
+
import { runLoggerHook } from './hooks/logger.js';
|
|
15
16
|
// eslint-disable-next-line max-statements
|
|
16
17
|
export async function runDevelopmentServer() {
|
|
17
18
|
const root = process.cwd();
|
|
@@ -53,7 +54,12 @@ export async function runDevelopmentServer() {
|
|
|
53
54
|
const apps = await loadApps(root, 'http://0.0.0.0:3000/');
|
|
54
55
|
// Start vite server that will be rendering SSR components
|
|
55
56
|
const viteSsr = await createServer(config);
|
|
56
|
-
|
|
57
|
+
// Start fastify server that will be processing HTTP requests
|
|
58
|
+
const app = fastify({
|
|
59
|
+
// Receive logger configuration from `nerest-runtime.logger()` or
|
|
60
|
+
// use the default fastify one (`true`)
|
|
61
|
+
logger: (await runLoggerHook(() => viteSsr.ssrLoadModule('/nerest-runtime.ts'))) ?? true,
|
|
62
|
+
});
|
|
57
63
|
// Setup schema validation. We have to use our own ajv instance that
|
|
58
64
|
// we can use both to validate request bodies and examples against
|
|
59
65
|
// app schemas
|
|
@@ -83,7 +89,7 @@ export async function runDevelopmentServer() {
|
|
|
83
89
|
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
84
90
|
fixStacktrace: true,
|
|
85
91
|
});
|
|
86
|
-
const props = await runPropsHook(request.body, () => viteSsr.ssrLoadModule(propsHookEntry));
|
|
92
|
+
const props = await runPropsHook(request.body, request.log, () => viteSsr.ssrLoadModule(propsHookEntry));
|
|
87
93
|
return renderApp({
|
|
88
94
|
name,
|
|
89
95
|
assets: appEntry.assets,
|
|
@@ -105,11 +111,11 @@ export async function runDevelopmentServer() {
|
|
|
105
111
|
// description so it's easier to navigate to
|
|
106
112
|
description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
|
|
107
113
|
},
|
|
108
|
-
}, async (
|
|
114
|
+
}, async (request, reply) => {
|
|
109
115
|
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
110
116
|
fixStacktrace: true,
|
|
111
117
|
});
|
|
112
|
-
const props = await runPropsHook(example, () => viteSsr.ssrLoadModule(propsHookEntry));
|
|
118
|
+
const props = await runPropsHook(example, request.log, () => viteSsr.ssrLoadModule(propsHookEntry));
|
|
113
119
|
const { html, assets } = renderApp({
|
|
114
120
|
name,
|
|
115
121
|
assets: appEntry.assets,
|
|
@@ -142,7 +148,6 @@ export async function runDevelopmentServer() {
|
|
|
142
148
|
host: '0.0.0.0',
|
|
143
149
|
port: 3000,
|
|
144
150
|
});
|
|
145
|
-
console.log('Nerest is listening on 0.0.0.0:3000');
|
|
146
151
|
}
|
|
147
152
|
// TODO: this should probably be moved from here
|
|
148
153
|
async function startClientBuildWatcher(config) {
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
export declare function runLoggerHook(loader: () => Promise<unknown>): Promise<boolean | import("fastify").FastifyBaseLogger | (import("fastify").FastifyLoggerOptions<import("fastify").RawServerDefault, import("fastify").FastifyRequest<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown, import("fastify").FastifyBaseLogger, import("fastify/types/type-provider.js").ResolveFastifyRequestType<import("fastify").FastifyTypeProviderDefault, import("fastify").FastifySchema, import("fastify").RouteGenericInterface>>, import("fastify").FastifyReply<import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, import("fastify").RouteGenericInterface, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>> & import("pino").default.LoggerOptions) | null | undefined>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Load the runtime hook module and run it if it exists, to receive
|
|
2
|
+
// logger configuration. If the `logger` function does not exist
|
|
3
|
+
// in the module, ignore it and use default configuration.
|
|
4
|
+
export async function runLoggerHook(loader) {
|
|
5
|
+
let module;
|
|
6
|
+
try {
|
|
7
|
+
module = (await loader());
|
|
8
|
+
}
|
|
9
|
+
catch { }
|
|
10
|
+
if (typeof module?.logger === 'function') {
|
|
11
|
+
// If module exists and exports a logger function, execute it
|
|
12
|
+
try {
|
|
13
|
+
return module.logger();
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
console.error('Failed to load logger configuration', e);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { FastifyBaseLogger } from 'fastify';
|
|
2
|
+
export declare function runPropsHook(props: unknown, logger: FastifyBaseLogger, loader: () => Promise<unknown>): Promise<Record<string, unknown>>;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
// Load the props hook module and run it if it exists, passing down app's
|
|
2
|
-
// props object. This hook can be used to modify props before
|
|
3
|
-
// to the root app component.
|
|
4
|
-
export async function runPropsHook(props, loader) {
|
|
2
|
+
// props object and logger. This hook can be used to modify props before
|
|
3
|
+
// they are passed to the root app component.
|
|
4
|
+
export async function runPropsHook(props, logger, loader) {
|
|
5
5
|
let module;
|
|
6
6
|
try {
|
|
7
7
|
module = (await loader());
|
|
8
8
|
}
|
|
9
9
|
catch { }
|
|
10
10
|
// If module exists and exports a default function, run it to modify props
|
|
11
|
-
return (typeof module?.default === 'function'
|
|
11
|
+
return (typeof module?.default === 'function'
|
|
12
|
+
? module.default(props, logger)
|
|
13
|
+
: props);
|
|
12
14
|
}
|
|
@@ -10,6 +10,7 @@ import { renderPreviewPage } from './parts/preview.js';
|
|
|
10
10
|
import { setupK8SProbes } from './parts/k8s-probes.js';
|
|
11
11
|
import { runRuntimeHook } from './hooks/runtime.js';
|
|
12
12
|
import { runPropsHook } from './hooks/props.js';
|
|
13
|
+
import { runLoggerHook } from './hooks/logger.js';
|
|
13
14
|
// TODO: refactor to merge the similar parts between production and development server?
|
|
14
15
|
async function runProductionServer() {
|
|
15
16
|
const root = process.cwd();
|
|
@@ -24,7 +25,13 @@ async function runProductionServer() {
|
|
|
24
25
|
const propsHooks = import.meta.glob('/apps/*/props.ts', {
|
|
25
26
|
eager: true,
|
|
26
27
|
});
|
|
27
|
-
const
|
|
28
|
+
const runtimeHook = import.meta.glob('/nerest-runtime.ts', { eager: true });
|
|
29
|
+
const app = fastify({
|
|
30
|
+
// Receive logger configuration from `nerest-runtime.logger()` or
|
|
31
|
+
// use the default fastify one (`true`)
|
|
32
|
+
logger: (await runLoggerHook(async () => runtimeHook['/nerest-runtime.ts'])) ??
|
|
33
|
+
true,
|
|
34
|
+
});
|
|
28
35
|
// Setup schema validation. We have to use our own ajv instance that
|
|
29
36
|
// we can use both to validate request bodies and examples against
|
|
30
37
|
// app schemas
|
|
@@ -48,7 +55,7 @@ async function runProductionServer() {
|
|
|
48
55
|
}
|
|
49
56
|
// POST /api/{name} -> render app with request.body as props
|
|
50
57
|
app.post(`/api/${name}`, routeOptions, async (request) => {
|
|
51
|
-
const props = await runPropsHook(request.body, propsHook);
|
|
58
|
+
const props = await runPropsHook(request.body, request.log, propsHook);
|
|
52
59
|
return renderApp({ name, assets, component }, props);
|
|
53
60
|
});
|
|
54
61
|
for (const [exampleName, example] of Object.entries(examples)) {
|
|
@@ -61,8 +68,8 @@ async function runProductionServer() {
|
|
|
61
68
|
// description so it's easier to navigate to
|
|
62
69
|
description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
|
|
63
70
|
},
|
|
64
|
-
}, async (
|
|
65
|
-
const props = await runPropsHook(example, propsHook);
|
|
71
|
+
}, async (request, reply) => {
|
|
72
|
+
const props = await runPropsHook(example, request.log, propsHook);
|
|
66
73
|
const { html, assets: outAssets } = renderApp({
|
|
67
74
|
name,
|
|
68
75
|
assets,
|
|
@@ -79,15 +86,11 @@ async function runProductionServer() {
|
|
|
79
86
|
await setupK8SProbes(app);
|
|
80
87
|
}
|
|
81
88
|
// Execute runtime hook in nerest-runtime.ts if it exists
|
|
82
|
-
await runRuntimeHook(app, async () =>
|
|
83
|
-
const glob = import.meta.glob('/nerest-runtime.ts', { eager: true });
|
|
84
|
-
return glob['/nerest-runtime.ts'];
|
|
85
|
-
});
|
|
89
|
+
await runRuntimeHook(app, async () => runtimeHook['/nerest-runtime.ts']);
|
|
86
90
|
// TODO: remove hardcoded port
|
|
87
91
|
await app.listen({
|
|
88
92
|
host: '0.0.0.0',
|
|
89
93
|
port: 3000,
|
|
90
94
|
});
|
|
91
|
-
console.log('Nerest is listening on 0.0.0.0:3000');
|
|
92
95
|
}
|
|
93
96
|
runProductionServer();
|
package/package.json
CHANGED
package/server/development.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { setupSwagger } from './parts/swagger.js';
|
|
|
19
19
|
import { setupK8SProbes } from './parts/k8s-probes.js';
|
|
20
20
|
import { runRuntimeHook } from './hooks/runtime.js';
|
|
21
21
|
import { runPropsHook } from './hooks/props.js';
|
|
22
|
+
import { runLoggerHook } from './hooks/logger.js';
|
|
22
23
|
|
|
23
24
|
// eslint-disable-next-line max-statements
|
|
24
25
|
export async function runDevelopmentServer() {
|
|
@@ -65,7 +66,16 @@ export async function runDevelopmentServer() {
|
|
|
65
66
|
|
|
66
67
|
// Start vite server that will be rendering SSR components
|
|
67
68
|
const viteSsr = await createServer(config);
|
|
68
|
-
|
|
69
|
+
|
|
70
|
+
// Start fastify server that will be processing HTTP requests
|
|
71
|
+
const app = fastify({
|
|
72
|
+
// Receive logger configuration from `nerest-runtime.logger()` or
|
|
73
|
+
// use the default fastify one (`true`)
|
|
74
|
+
logger:
|
|
75
|
+
(await runLoggerHook(() =>
|
|
76
|
+
viteSsr.ssrLoadModule('/nerest-runtime.ts')
|
|
77
|
+
)) ?? true,
|
|
78
|
+
});
|
|
69
79
|
|
|
70
80
|
// Setup schema validation. We have to use our own ajv instance that
|
|
71
81
|
// we can use both to validate request bodies and examples against
|
|
@@ -101,7 +111,7 @@ export async function runDevelopmentServer() {
|
|
|
101
111
|
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
102
112
|
fixStacktrace: true,
|
|
103
113
|
});
|
|
104
|
-
const props = await runPropsHook(request.body, () =>
|
|
114
|
+
const props = await runPropsHook(request.body, request.log, () =>
|
|
105
115
|
viteSsr.ssrLoadModule(propsHookEntry)
|
|
106
116
|
);
|
|
107
117
|
return renderApp(
|
|
@@ -135,11 +145,11 @@ export async function runDevelopmentServer() {
|
|
|
135
145
|
description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
|
|
136
146
|
},
|
|
137
147
|
},
|
|
138
|
-
async (
|
|
148
|
+
async (request, reply) => {
|
|
139
149
|
const ssrComponent = await viteSsr.ssrLoadModule(entry, {
|
|
140
150
|
fixStacktrace: true,
|
|
141
151
|
});
|
|
142
|
-
const props = await runPropsHook(example, () =>
|
|
152
|
+
const props = await runPropsHook(example, request.log, () =>
|
|
143
153
|
viteSsr.ssrLoadModule(propsHookEntry)
|
|
144
154
|
);
|
|
145
155
|
const { html, assets } = renderApp(
|
|
@@ -188,8 +198,6 @@ export async function runDevelopmentServer() {
|
|
|
188
198
|
host: '0.0.0.0',
|
|
189
199
|
port: 3000,
|
|
190
200
|
});
|
|
191
|
-
|
|
192
|
-
console.log('Nerest is listening on 0.0.0.0:3000');
|
|
193
201
|
}
|
|
194
202
|
|
|
195
203
|
// TODO: this should probably be moved from here
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FastifyServerOptions } from 'fastify';
|
|
2
|
+
|
|
3
|
+
type LoggerHookModule = {
|
|
4
|
+
logger?: () => FastifyServerOptions['logger'];
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// Load the runtime hook module and run it if it exists, to receive
|
|
8
|
+
// logger configuration. If the `logger` function does not exist
|
|
9
|
+
// in the module, ignore it and use default configuration.
|
|
10
|
+
export async function runLoggerHook(loader: () => Promise<unknown>) {
|
|
11
|
+
let module: LoggerHookModule | undefined;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
module = (await loader()) as LoggerHookModule;
|
|
15
|
+
} catch {}
|
|
16
|
+
|
|
17
|
+
if (typeof module?.logger === 'function') {
|
|
18
|
+
// If module exists and exports a logger function, execute it
|
|
19
|
+
try {
|
|
20
|
+
return module.logger();
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.error('Failed to load logger configuration', e);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
}
|
package/server/hooks/props.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import type { FastifyBaseLogger } from 'fastify';
|
|
2
|
+
|
|
1
3
|
type PropsHookModule = {
|
|
2
|
-
default: (props: unknown) => unknown;
|
|
4
|
+
default: (props: unknown, logger: FastifyBaseLogger) => unknown;
|
|
3
5
|
};
|
|
4
6
|
|
|
5
7
|
// Load the props hook module and run it if it exists, passing down app's
|
|
6
|
-
// props object. This hook can be used to modify props before
|
|
7
|
-
// to the root app component.
|
|
8
|
+
// props object and logger. This hook can be used to modify props before
|
|
9
|
+
// they are passed to the root app component.
|
|
8
10
|
export async function runPropsHook(
|
|
9
11
|
props: unknown,
|
|
12
|
+
logger: FastifyBaseLogger,
|
|
10
13
|
loader: () => Promise<unknown>
|
|
11
14
|
) {
|
|
12
15
|
let module: PropsHookModule | undefined;
|
|
@@ -17,6 +20,8 @@ export async function runPropsHook(
|
|
|
17
20
|
|
|
18
21
|
// If module exists and exports a default function, run it to modify props
|
|
19
22
|
return (
|
|
20
|
-
typeof module?.default === 'function'
|
|
23
|
+
typeof module?.default === 'function'
|
|
24
|
+
? module.default(props, logger)
|
|
25
|
+
: props
|
|
21
26
|
) as Record<string, unknown>;
|
|
22
27
|
}
|
package/server/production.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { renderPreviewPage } from './parts/preview.js';
|
|
|
14
14
|
import { setupK8SProbes } from './parts/k8s-probes.js';
|
|
15
15
|
import { runRuntimeHook } from './hooks/runtime.js';
|
|
16
16
|
import { runPropsHook } from './hooks/props.js';
|
|
17
|
+
import { runLoggerHook } from './hooks/logger.js';
|
|
17
18
|
|
|
18
19
|
// TODO: refactor to merge the similar parts between production and development server?
|
|
19
20
|
async function runProductionServer() {
|
|
@@ -35,7 +36,15 @@ async function runProductionServer() {
|
|
|
35
36
|
eager: true,
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
const
|
|
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,
|
|
47
|
+
});
|
|
39
48
|
|
|
40
49
|
// Setup schema validation. We have to use our own ajv instance that
|
|
41
50
|
// we can use both to validate request bodies and examples against
|
|
@@ -65,7 +74,7 @@ async function runProductionServer() {
|
|
|
65
74
|
|
|
66
75
|
// POST /api/{name} -> render app with request.body as props
|
|
67
76
|
app.post(`/api/${name}`, routeOptions, async (request) => {
|
|
68
|
-
const props = await runPropsHook(request.body, propsHook);
|
|
77
|
+
const props = await runPropsHook(request.body, request.log, propsHook);
|
|
69
78
|
return renderApp({ name, assets, component }, props);
|
|
70
79
|
});
|
|
71
80
|
|
|
@@ -82,8 +91,8 @@ async function runProductionServer() {
|
|
|
82
91
|
description: `Open sandbox: [${exampleRoute}](${exampleRoute})`,
|
|
83
92
|
},
|
|
84
93
|
},
|
|
85
|
-
async (
|
|
86
|
-
const props = await runPropsHook(example, propsHook);
|
|
94
|
+
async (request, reply) => {
|
|
95
|
+
const props = await runPropsHook(example, request.log, propsHook);
|
|
87
96
|
const { html, assets: outAssets } = renderApp(
|
|
88
97
|
{
|
|
89
98
|
name,
|
|
@@ -109,18 +118,13 @@ async function runProductionServer() {
|
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
// 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'];
|
|
115
|
-
});
|
|
121
|
+
await runRuntimeHook(app, async () => runtimeHook['/nerest-runtime.ts']);
|
|
116
122
|
|
|
117
123
|
// TODO: remove hardcoded port
|
|
118
124
|
await app.listen({
|
|
119
125
|
host: '0.0.0.0',
|
|
120
126
|
port: 3000,
|
|
121
127
|
});
|
|
122
|
-
|
|
123
|
-
console.log('Nerest is listening on 0.0.0.0:3000');
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
runProductionServer();
|