@platformatic/node 2.72.0 → 3.0.0-alpha.1
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/config.d.ts +1 -1
- package/index.js +22 -425
- package/lib/schema.js +3 -3
- package/lib/stackable.js +396 -0
- package/lib/utils.js +5 -4
- package/package.json +8 -13
- package/schema.json +2 -2
- package/bin/create.js +0 -32
- package/bin/start.js +0 -36
package/config.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,435 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
ensureTrailingSlash,
|
|
6
|
-
getServerUrl,
|
|
7
|
-
importFile,
|
|
8
|
-
injectViaRequest,
|
|
9
|
-
schemaOptions,
|
|
10
|
-
transformConfig
|
|
11
|
-
} from '@platformatic/basic'
|
|
12
|
-
import { ConfigManager } from '@platformatic/config'
|
|
13
|
-
import { features } from '@platformatic/utils'
|
|
14
|
-
import inject from 'light-my-request'
|
|
15
|
-
import { existsSync } from 'node:fs'
|
|
16
|
-
import { readFile } from 'node:fs/promises'
|
|
17
|
-
import { Server } from 'node:http'
|
|
18
|
-
import { resolve as pathResolve, resolve } from 'node:path'
|
|
19
|
-
import { packageJson, schema } from './lib/schema.js'
|
|
20
|
-
import { getTsconfig, ignoreDirs, isServiceBuildable } from './lib/utils.js'
|
|
1
|
+
import { transform as basicTransform, resolve, validationOptions } from '@platformatic/basic'
|
|
2
|
+
import { kMetadata, loadConfiguration as utilsLoadConfiguration } from '@platformatic/utils'
|
|
3
|
+
import { schema } from './lib/schema.js'
|
|
4
|
+
import { NodeStackable } from './lib/stackable.js'
|
|
21
5
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'exports',
|
|
26
|
-
'exports#node',
|
|
27
|
-
'exports#import',
|
|
28
|
-
'exports#require',
|
|
29
|
-
'exports#default',
|
|
30
|
-
'exports#.#node',
|
|
31
|
-
'exports#.#import',
|
|
32
|
-
'exports#.#require',
|
|
33
|
-
'exports#.#default'
|
|
34
|
-
]
|
|
6
|
+
export async function transform (config, _schema, options) {
|
|
7
|
+
config = await basicTransform(config, schema, options)
|
|
8
|
+
config.telemetry = { ...options.telemetryConfig, ...config.telemetry }
|
|
35
9
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// Paolo: This is kinda hackish but there is no better way. I apologize.
|
|
39
|
-
function isFastify (app) {
|
|
40
|
-
return Object.getOwnPropertySymbols(app).some(s => s.description === 'fastify.state')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function isKoa (app) {
|
|
44
|
-
return typeof app.callback === 'function'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export class NodeStackable extends BaseStackable {
|
|
48
|
-
#module
|
|
49
|
-
#app
|
|
50
|
-
#server
|
|
51
|
-
#basePath
|
|
52
|
-
#dispatcher
|
|
53
|
-
#isFastify
|
|
54
|
-
#isKoa
|
|
55
|
-
#useHttpForDispatch
|
|
56
|
-
|
|
57
|
-
constructor (options, root, configManager) {
|
|
58
|
-
super('nodejs', packageJson.version, options, root, configManager)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async start ({ listen }) {
|
|
62
|
-
// Make this idempotent
|
|
63
|
-
if (this.url) {
|
|
64
|
-
return this.url
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Listen if entrypoint
|
|
68
|
-
if (this.#app && listen) {
|
|
69
|
-
await this._listen()
|
|
70
|
-
return this.url
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const config = this.configManager.current
|
|
74
|
-
|
|
75
|
-
if (!this.isProduction && (await isServiceBuildable(this.root, config))) {
|
|
76
|
-
this.logger.info(`Building service "${this.serviceId}" before starting in devevelopment mode ...`)
|
|
77
|
-
try {
|
|
78
|
-
await this.build()
|
|
79
|
-
this.childManager = null
|
|
80
|
-
} catch (e) {
|
|
81
|
-
this.logger.error(`Error while building service "${this.serviceId}": ${e.message}`)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const command = config.application.commands[this.isProduction ? 'production' : 'development']
|
|
86
|
-
|
|
87
|
-
if (command) {
|
|
88
|
-
return this.startWithCommand(command)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Resolve the entrypoint
|
|
92
|
-
// The priority is platformatic.application.json, then package.json and finally autodetect.
|
|
93
|
-
// Only when autodetecting we eventually search in the dist folder when in production mode
|
|
94
|
-
const finalEntrypoint = await this._findEntrypoint()
|
|
95
|
-
|
|
96
|
-
// Require the application
|
|
97
|
-
this.#basePath = config.application?.basePath
|
|
98
|
-
? ensureTrailingSlash(cleanBasePath(config.application?.basePath))
|
|
99
|
-
: undefined
|
|
100
|
-
|
|
101
|
-
this.registerGlobals({
|
|
102
|
-
basePath: this.#basePath
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
// The server promise must be created before requiring the entrypoint even if it's not going to be used
|
|
106
|
-
// at all. Otherwise there is chance we miss the listen event.
|
|
107
|
-
const serverOptions = this.serverConfig
|
|
108
|
-
const serverPromise = createServerListener(
|
|
109
|
-
(this.isEntrypoint ? serverOptions?.port : undefined) ?? true,
|
|
110
|
-
(this.isEntrypoint ? serverOptions?.hostname : undefined) ?? true
|
|
111
|
-
)
|
|
112
|
-
this.#module = await importFile(finalEntrypoint)
|
|
113
|
-
this.#module = this.#module.default || this.#module
|
|
114
|
-
|
|
115
|
-
// Deal with application
|
|
116
|
-
const factory = ['build', 'create'].find(f => typeof this.#module[f] === 'function')
|
|
117
|
-
|
|
118
|
-
if (this.#module.hasServer !== false) {
|
|
119
|
-
if (factory) {
|
|
120
|
-
// We have build function, this Stackable will not use HTTP unless it is the entrypoint
|
|
121
|
-
serverPromise.cancel()
|
|
122
|
-
|
|
123
|
-
this.#app = await this.#module[factory]()
|
|
124
|
-
this.#isFastify = isFastify(this.#app)
|
|
125
|
-
this.#isKoa = isKoa(this.#app)
|
|
126
|
-
|
|
127
|
-
if (this.#isFastify) {
|
|
128
|
-
await this.#app.ready()
|
|
129
|
-
} else if (this.#isKoa) {
|
|
130
|
-
this.#dispatcher = this.#app.callback()
|
|
131
|
-
} else if (this.#app instanceof Server) {
|
|
132
|
-
this.#server = this.#app
|
|
133
|
-
this.#dispatcher = this.#server.listeners('request')[0]
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (listen) {
|
|
137
|
-
await this._listen()
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
// User blackbox function, we wait for it to listen on a port
|
|
141
|
-
this.#server = await serverPromise
|
|
142
|
-
this.#dispatcher = this.#server.listeners('request')[0]
|
|
143
|
-
|
|
144
|
-
this.url = getServerUrl(this.#server)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
await this._collectMetrics()
|
|
149
|
-
return this.url
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async stop () {
|
|
153
|
-
if (this.childManager) {
|
|
154
|
-
return this.stopCommand()
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (this.#isFastify) {
|
|
158
|
-
return this.#app.close()
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/* c8 ignore next 3 */
|
|
162
|
-
if (!this.#server?.listening) {
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return new Promise((resolve, reject) => {
|
|
167
|
-
this.#server.close(error => {
|
|
168
|
-
/* c8 ignore next 3 */
|
|
169
|
-
if (error) {
|
|
170
|
-
return reject(error)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
resolve()
|
|
174
|
-
})
|
|
175
|
-
})
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async build () {
|
|
179
|
-
const config = this.configManager.current
|
|
180
|
-
const disableChildManager = config.node?.disablePlatformaticInBuild
|
|
181
|
-
const command = config.application?.commands?.build
|
|
182
|
-
|
|
183
|
-
if (command) {
|
|
184
|
-
return this.buildWithCommand(command, null, { disableChildManager })
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// If no command was specified, we try to see if there is a build script defined in package.json.
|
|
188
|
-
const hasBuildScript = await this.#hasBuildScript()
|
|
189
|
-
|
|
190
|
-
if (!hasBuildScript) {
|
|
191
|
-
this.logger.debug(
|
|
192
|
-
'No "application.commands.build" configuration value specified and no build script found in package.json. Skipping build ...'
|
|
193
|
-
)
|
|
194
|
-
return
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return this.buildWithCommand('npm run build', null, { disableChildManager })
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async inject (injectParams, onInject) {
|
|
201
|
-
let res
|
|
202
|
-
|
|
203
|
-
if (this.#useHttpForDispatch) {
|
|
204
|
-
this.logger.trace({ injectParams, url: this.url }, 'injecting via request')
|
|
205
|
-
res = await injectViaRequest(this.url, injectParams, onInject)
|
|
206
|
-
} else {
|
|
207
|
-
if (this.#isFastify) {
|
|
208
|
-
this.logger.trace({ injectParams }, 'injecting via fastify')
|
|
209
|
-
res = await this.#app.inject(injectParams, onInject)
|
|
210
|
-
} else {
|
|
211
|
-
this.logger.trace({ injectParams }, 'injecting via light-my-request')
|
|
212
|
-
res = await inject(this.#dispatcher ?? this.#app, injectParams, onInject)
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/* c8 ignore next 3 */
|
|
217
|
-
if (onInject) {
|
|
218
|
-
return
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Since inject might be called from the main thread directly via ITC, let's clean it up
|
|
222
|
-
const { statusCode, headers, body, payload, rawPayload } = res
|
|
223
|
-
|
|
224
|
-
return { statusCode, headers, body, payload, rawPayload }
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
_getWantsAbsoluteUrls () {
|
|
228
|
-
const config = this.configManager.current
|
|
229
|
-
return config.node.absoluteUrl
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
getMeta () {
|
|
233
|
-
return {
|
|
234
|
-
composer: {
|
|
235
|
-
tcp: typeof this.url !== 'undefined',
|
|
236
|
-
url: this.url,
|
|
237
|
-
prefix: this.basePath ?? this.#basePath,
|
|
238
|
-
wantsAbsoluteUrls: this._getWantsAbsoluteUrls(),
|
|
239
|
-
needsRootTrailingSlash: true
|
|
240
|
-
},
|
|
241
|
-
connectionStrings: this.connectionString ? [this.connectionString] : []
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async getDispatchTarget () {
|
|
246
|
-
this.#useHttpForDispatch =
|
|
247
|
-
this.childManager || (this.url && this.configManager.current.node?.dispatchViaHttp === true)
|
|
248
|
-
|
|
249
|
-
if (this.#useHttpForDispatch) {
|
|
250
|
-
return this.getUrl()
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return this.getDispatchFunc()
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async _listen () {
|
|
257
|
-
const serverOptions = this.serverConfig
|
|
258
|
-
const listenOptions = { host: serverOptions?.hostname || '127.0.0.1', port: serverOptions?.port || 0 }
|
|
259
|
-
|
|
260
|
-
if (this.isProduction && features.node.reusePort) {
|
|
261
|
-
listenOptions.reusePort = true
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (this.#isFastify) {
|
|
265
|
-
await this.#app.listen(listenOptions)
|
|
266
|
-
this.url = getServerUrl(this.#app.server)
|
|
267
|
-
} else {
|
|
268
|
-
// Express / Node / Koa
|
|
269
|
-
this.#server = await new Promise((resolve, reject) => {
|
|
270
|
-
return this.#app
|
|
271
|
-
.listen(listenOptions, function () {
|
|
272
|
-
resolve(this)
|
|
273
|
-
})
|
|
274
|
-
.on('error', reject)
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
this.url = getServerUrl(this.#server)
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return this.url
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
_getApplication () {
|
|
284
|
-
return this.#app
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async _findEntrypoint () {
|
|
288
|
-
const config = this.configManager.current
|
|
289
|
-
|
|
290
|
-
if (config.node.main) {
|
|
291
|
-
return pathResolve(this.root, config.node.main)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const { entrypoint, hadEntrypointField } = await getEntrypointInformation(this.root)
|
|
295
|
-
|
|
296
|
-
if (typeof this.workerId === 'undefined' || this.workerId === 0) {
|
|
297
|
-
if (!entrypoint) {
|
|
298
|
-
this.logger.error(
|
|
299
|
-
`The service "${this.serviceId}" had no valid entrypoint defined in the package.json file and no valid entrypoint file was found.`
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
process.exit(1)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (!hadEntrypointField) {
|
|
306
|
-
this.logger.warn(
|
|
307
|
-
`The service "${this.serviceId}" had no valid entrypoint defined in the package.json file. Falling back to the file "${entrypoint}".`
|
|
308
|
-
)
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return pathResolve(this.root, entrypoint)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async #hasBuildScript () {
|
|
316
|
-
// If no command was specified, we try to see if there is a build script defined in package.json.
|
|
317
|
-
let hasBuildScript
|
|
318
|
-
try {
|
|
319
|
-
const packageJson = JSON.parse(await readFile(resolve(this.root, 'package.json'), 'utf-8'))
|
|
320
|
-
hasBuildScript = typeof packageJson.scripts.build === 'string' && packageJson.scripts.build
|
|
321
|
-
} catch (e) {
|
|
322
|
-
// No-op
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return hasBuildScript
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async getWatchConfig () {
|
|
329
|
-
const config = this.configManager.current
|
|
330
|
-
|
|
331
|
-
const enabled = config.watch?.enabled !== false
|
|
332
|
-
|
|
333
|
-
if (!enabled) {
|
|
334
|
-
return { enabled, path: this.root }
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// ignore the outDir from tsconfig or service config if any
|
|
338
|
-
let ignore = config.watch?.ignore
|
|
339
|
-
if (!ignore) {
|
|
340
|
-
const tsConfig = await getTsconfig(this.root, config)
|
|
341
|
-
if (tsConfig) {
|
|
342
|
-
ignore = ignoreDirs(tsConfig?.compilerOptions?.outDir, tsConfig?.watchOptions?.excludeDirectories)
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
enabled,
|
|
348
|
-
path: this.root,
|
|
349
|
-
allow: config.watch?.allow,
|
|
350
|
-
ignore
|
|
351
|
-
}
|
|
352
|
-
}
|
|
10
|
+
return config
|
|
353
11
|
}
|
|
354
12
|
|
|
355
|
-
async function
|
|
356
|
-
|
|
357
|
-
let packageJson
|
|
358
|
-
let hadEntrypointField = false
|
|
359
|
-
|
|
360
|
-
try {
|
|
361
|
-
packageJson = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf-8'))
|
|
362
|
-
} catch {
|
|
363
|
-
// No package.json, we only load the index.js file
|
|
364
|
-
packageJson = {}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
for (const field of validFields) {
|
|
368
|
-
let current = packageJson
|
|
369
|
-
const sequence = field.split('#')
|
|
370
|
-
|
|
371
|
-
while (current && sequence.length && typeof current !== 'string') {
|
|
372
|
-
current = current[sequence.shift()]
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (typeof current === 'string') {
|
|
376
|
-
entrypoint = current
|
|
377
|
-
hadEntrypointField = true
|
|
378
|
-
break
|
|
379
|
-
}
|
|
380
|
-
}
|
|
13
|
+
export async function loadConfiguration (configOrRoot, sourceOrConfig, context) {
|
|
14
|
+
const { root, source } = await resolve(configOrRoot, sourceOrConfig, 'application')
|
|
381
15
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
entrypoint = file
|
|
389
|
-
break
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (entrypoint) {
|
|
394
|
-
break
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return { entrypoint, hadEntrypointField }
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export async function buildStackable (opts) {
|
|
403
|
-
const root = opts.context.directory
|
|
404
|
-
|
|
405
|
-
const configManager = new ConfigManager({
|
|
406
|
-
schema,
|
|
407
|
-
source: opts.config ?? {},
|
|
408
|
-
schemaOptions,
|
|
409
|
-
transformConfig,
|
|
410
|
-
dirname: root,
|
|
411
|
-
context: opts.context
|
|
16
|
+
return utilsLoadConfiguration(source, context?.schema ?? schema, {
|
|
17
|
+
validationOptions,
|
|
18
|
+
transform,
|
|
19
|
+
replaceEnv: true,
|
|
20
|
+
root,
|
|
21
|
+
...context
|
|
412
22
|
})
|
|
413
|
-
|
|
414
|
-
const config = configManager.current
|
|
415
|
-
// We need to update the config with the telemetry so the service name
|
|
416
|
-
// used in telemetry can be retreived using the management API
|
|
417
|
-
config.telemetry = opts.context.telemetryConfig
|
|
418
|
-
configManager.update(config)
|
|
23
|
+
}
|
|
419
24
|
|
|
420
|
-
|
|
25
|
+
export async function create (configOrRoot, sourceOrConfig, context) {
|
|
26
|
+
const config = await loadConfiguration(configOrRoot, sourceOrConfig, context)
|
|
27
|
+
return new NodeStackable(config[kMetadata].root, config, context)
|
|
421
28
|
}
|
|
422
29
|
|
|
423
30
|
export { Generator } from './lib/generator.js'
|
|
424
|
-
export { schema, schemaComponents } from './lib/schema.js'
|
|
425
|
-
|
|
426
|
-
export default {
|
|
427
|
-
configType: 'nodejs',
|
|
428
|
-
configManagerConfig: {
|
|
429
|
-
schemaOptions,
|
|
430
|
-
transformConfig
|
|
431
|
-
},
|
|
432
|
-
buildStackable,
|
|
433
|
-
schema,
|
|
434
|
-
version: packageJson.version
|
|
435
|
-
}
|
|
31
|
+
export { packageJson, schema, schemaComponents, version } from './lib/schema.js'
|
|
32
|
+
export * from './lib/stackable.js'
|
package/lib/schema.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { schemaComponents as basicSchemaComponents } from '@platformatic/basic'
|
|
2
2
|
import { schemaComponents as utilsSchemaComponents } from '@platformatic/utils'
|
|
3
3
|
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
4
5
|
|
|
5
|
-
export const packageJson = JSON.parse(readFileSync(
|
|
6
|
-
|
|
6
|
+
export const packageJson = JSON.parse(readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf8'))
|
|
7
7
|
export const version = packageJson.version
|
|
8
8
|
|
|
9
9
|
const node = {
|
|
@@ -35,7 +35,7 @@ export const schemaComponents = { node }
|
|
|
35
35
|
export const schema = {
|
|
36
36
|
$id: `https://schemas.platformatic.dev/@platformatic/node/${version}.json`,
|
|
37
37
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
38
|
-
title: 'Platformatic Node.js
|
|
38
|
+
title: 'Platformatic Node.js Config',
|
|
39
39
|
type: 'object',
|
|
40
40
|
properties: {
|
|
41
41
|
$schema: {
|
package/lib/stackable.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseStackable,
|
|
3
|
+
cleanBasePath,
|
|
4
|
+
createServerListener,
|
|
5
|
+
ensureTrailingSlash,
|
|
6
|
+
getServerUrl,
|
|
7
|
+
importFile,
|
|
8
|
+
injectViaRequest
|
|
9
|
+
} from '@platformatic/basic'
|
|
10
|
+
import { features } from '@platformatic/utils'
|
|
11
|
+
import inject from 'light-my-request'
|
|
12
|
+
import { existsSync } from 'node:fs'
|
|
13
|
+
import { readFile } from 'node:fs/promises'
|
|
14
|
+
import { Server } from 'node:http'
|
|
15
|
+
import { resolve as resolvePath } from 'node:path'
|
|
16
|
+
import { version } from './schema.js'
|
|
17
|
+
import { getTsconfig, ignoreDirs, isServiceBuildable } from './utils.js'
|
|
18
|
+
|
|
19
|
+
const validFields = [
|
|
20
|
+
'main',
|
|
21
|
+
'exports',
|
|
22
|
+
'exports',
|
|
23
|
+
'exports#node',
|
|
24
|
+
'exports#import',
|
|
25
|
+
'exports#require',
|
|
26
|
+
'exports#default',
|
|
27
|
+
'exports#.#node',
|
|
28
|
+
'exports#.#import',
|
|
29
|
+
'exports#.#require',
|
|
30
|
+
'exports#.#default'
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
const validFilesBasenames = ['index', 'main', 'app', 'application', 'server', 'start', 'bundle', 'run', 'entrypoint']
|
|
34
|
+
|
|
35
|
+
// Paolo: This is kinda hackish but there is no better way. I apologize.
|
|
36
|
+
function isFastify (app) {
|
|
37
|
+
return Object.getOwnPropertySymbols(app).some(s => s.description === 'fastify.state')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isKoa (app) {
|
|
41
|
+
return typeof app.callback === 'function'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function getEntrypointInformation (root) {
|
|
45
|
+
let entrypoint
|
|
46
|
+
let packageJson
|
|
47
|
+
let hadEntrypointField = false
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
packageJson = JSON.parse(await readFile(resolvePath(root, 'package.json'), 'utf-8'))
|
|
51
|
+
} catch {
|
|
52
|
+
// No package.json, we only load the index.js file
|
|
53
|
+
packageJson = {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const field of validFields) {
|
|
57
|
+
let current = packageJson
|
|
58
|
+
const sequence = field.split('#')
|
|
59
|
+
|
|
60
|
+
while (current && sequence.length && typeof current !== 'string') {
|
|
61
|
+
current = current[sequence.shift()]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof current === 'string') {
|
|
65
|
+
entrypoint = current
|
|
66
|
+
hadEntrypointField = true
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!entrypoint) {
|
|
72
|
+
for (const basename of validFilesBasenames) {
|
|
73
|
+
for (const ext of ['js', 'mjs', 'cjs']) {
|
|
74
|
+
const file = `${basename}.${ext}`
|
|
75
|
+
|
|
76
|
+
if (existsSync(resolvePath(root, file))) {
|
|
77
|
+
entrypoint = file
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (entrypoint) {
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { entrypoint, hadEntrypointField }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class NodeStackable extends BaseStackable {
|
|
92
|
+
#module
|
|
93
|
+
#app
|
|
94
|
+
#server
|
|
95
|
+
#basePath
|
|
96
|
+
#dispatcher
|
|
97
|
+
#isFastify
|
|
98
|
+
#isKoa
|
|
99
|
+
#useHttpForDispatch
|
|
100
|
+
|
|
101
|
+
constructor (root, config, context) {
|
|
102
|
+
super('nodejs', version, root, config, context)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async start ({ listen }) {
|
|
106
|
+
// Make this idempotent
|
|
107
|
+
if (this.url) {
|
|
108
|
+
return this.url
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Listen if entrypoint
|
|
112
|
+
if (this.#app && listen) {
|
|
113
|
+
await this._listen()
|
|
114
|
+
return this.url
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const config = this.config
|
|
118
|
+
|
|
119
|
+
if (!this.isProduction && (await isServiceBuildable(this.root, config))) {
|
|
120
|
+
this.logger.info(`Building service "${this.serviceId}" before starting in devevelopment mode ...`)
|
|
121
|
+
try {
|
|
122
|
+
await this.build()
|
|
123
|
+
this.childManager = null
|
|
124
|
+
} catch (e) {
|
|
125
|
+
this.logger.error(`Error while building service "${this.serviceId}": ${e.message}`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const command = config.application.commands[this.isProduction ? 'production' : 'development']
|
|
130
|
+
|
|
131
|
+
if (command) {
|
|
132
|
+
return this.startWithCommand(command)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Resolve the entrypoint
|
|
136
|
+
// The priority is platformatic.application.json, then package.json and finally autodetect.
|
|
137
|
+
// Only when autodetecting we eventually search in the dist folder when in production mode
|
|
138
|
+
const finalEntrypoint = await this._findEntrypoint()
|
|
139
|
+
|
|
140
|
+
// Require the application
|
|
141
|
+
this.#basePath = config.application?.basePath
|
|
142
|
+
? ensureTrailingSlash(cleanBasePath(config.application?.basePath))
|
|
143
|
+
: undefined
|
|
144
|
+
|
|
145
|
+
this.registerGlobals({
|
|
146
|
+
basePath: this.#basePath
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// The server promise must be created before requiring the entrypoint even if it's not going to be used
|
|
150
|
+
// at all. Otherwise there is chance we miss the listen event.
|
|
151
|
+
const serverOptions = this.serverConfig
|
|
152
|
+
const serverPromise = createServerListener(
|
|
153
|
+
(this.isEntrypoint ? serverOptions?.port : undefined) ?? true,
|
|
154
|
+
(this.isEntrypoint ? serverOptions?.hostname : undefined) ?? true
|
|
155
|
+
)
|
|
156
|
+
this.#module = await importFile(finalEntrypoint)
|
|
157
|
+
this.#module = this.#module.default || this.#module
|
|
158
|
+
|
|
159
|
+
// Deal with application
|
|
160
|
+
const factory = ['build', 'create'].find(f => typeof this.#module[f] === 'function')
|
|
161
|
+
|
|
162
|
+
if (this.#module.hasServer !== false) {
|
|
163
|
+
if (factory) {
|
|
164
|
+
// We have build function, this Stackable will not use HTTP unless it is the entrypoint
|
|
165
|
+
serverPromise.cancel()
|
|
166
|
+
|
|
167
|
+
this.#app = await this.#module[factory]()
|
|
168
|
+
this.#isFastify = isFastify(this.#app)
|
|
169
|
+
this.#isKoa = isKoa(this.#app)
|
|
170
|
+
|
|
171
|
+
if (this.#isFastify) {
|
|
172
|
+
await this.#app.ready()
|
|
173
|
+
} else if (this.#isKoa) {
|
|
174
|
+
this.#dispatcher = this.#app.callback()
|
|
175
|
+
} else if (this.#app instanceof Server) {
|
|
176
|
+
this.#server = this.#app
|
|
177
|
+
this.#dispatcher = this.#server.listeners('request')[0]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (listen) {
|
|
181
|
+
await this._listen()
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
// User blackbox function, we wait for it to listen on a port
|
|
185
|
+
this.#server = await serverPromise
|
|
186
|
+
this.#dispatcher = this.#server.listeners('request')[0]
|
|
187
|
+
|
|
188
|
+
this.url = getServerUrl(this.#server)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await this._collectMetrics()
|
|
193
|
+
return this.url
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async stop () {
|
|
197
|
+
if (this.childManager) {
|
|
198
|
+
return this.stopCommand()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (this.#isFastify) {
|
|
202
|
+
return this.#app.close()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* c8 ignore next 3 */
|
|
206
|
+
if (!this.#server?.listening) {
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return new Promise((resolve, reject) => {
|
|
211
|
+
this.#server.close(error => {
|
|
212
|
+
/* c8 ignore next 3 */
|
|
213
|
+
if (error) {
|
|
214
|
+
return reject(error)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
resolve()
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async build () {
|
|
223
|
+
const config = this.config
|
|
224
|
+
const disableChildManager = config.node?.disablePlatformaticInBuild
|
|
225
|
+
const command = config.application?.commands?.build
|
|
226
|
+
|
|
227
|
+
if (command) {
|
|
228
|
+
return this.buildWithCommand(command, null, { disableChildManager })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// If no command was specified, we try to see if there is a build script defined in package.json.
|
|
232
|
+
const hasBuildScript = await this.#hasBuildScript()
|
|
233
|
+
|
|
234
|
+
if (!hasBuildScript) {
|
|
235
|
+
this.logger.debug(
|
|
236
|
+
'No "application.commands.build" configuration value specified and no build script found in package.json. Skipping build ...'
|
|
237
|
+
)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return this.buildWithCommand('npm run build', null, { disableChildManager })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async inject (injectParams, onInject) {
|
|
245
|
+
let res
|
|
246
|
+
|
|
247
|
+
if (this.#useHttpForDispatch) {
|
|
248
|
+
this.logger.trace({ injectParams, url: this.url }, 'injecting via request')
|
|
249
|
+
res = await injectViaRequest(this.url, injectParams, onInject)
|
|
250
|
+
} else {
|
|
251
|
+
if (this.#isFastify) {
|
|
252
|
+
this.logger.trace({ injectParams }, 'injecting via fastify')
|
|
253
|
+
res = await this.#app.inject(injectParams, onInject)
|
|
254
|
+
} else {
|
|
255
|
+
this.logger.trace({ injectParams }, 'injecting via light-my-request')
|
|
256
|
+
res = await inject(this.#dispatcher ?? this.#app, injectParams, onInject)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* c8 ignore next 3 */
|
|
261
|
+
if (onInject) {
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Since inject might be called from the main thread directly via ITC, let's clean it up
|
|
266
|
+
const { statusCode, headers, body, payload, rawPayload } = res
|
|
267
|
+
|
|
268
|
+
return { statusCode, headers, body, payload, rawPayload }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_getWantsAbsoluteUrls () {
|
|
272
|
+
const config = this.config
|
|
273
|
+
return config.node.absoluteUrl
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
getMeta () {
|
|
277
|
+
return {
|
|
278
|
+
composer: {
|
|
279
|
+
tcp: typeof this.url !== 'undefined',
|
|
280
|
+
url: this.url,
|
|
281
|
+
prefix: this.basePath ?? this.#basePath,
|
|
282
|
+
wantsAbsoluteUrls: this._getWantsAbsoluteUrls(),
|
|
283
|
+
needsRootTrailingSlash: true
|
|
284
|
+
},
|
|
285
|
+
connectionStrings: this.connectionString ? [this.connectionString] : []
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async getDispatchTarget () {
|
|
290
|
+
this.#useHttpForDispatch = this.childManager || (this.url && this.config.node?.dispatchViaHttp === true)
|
|
291
|
+
|
|
292
|
+
if (this.#useHttpForDispatch) {
|
|
293
|
+
return this.getUrl()
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return this.getDispatchFunc()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async _listen () {
|
|
300
|
+
const serverOptions = this.serverConfig
|
|
301
|
+
const listenOptions = { host: serverOptions?.hostname || '127.0.0.1', port: serverOptions?.port || 0 }
|
|
302
|
+
|
|
303
|
+
if (this.isProduction && features.node.reusePort) {
|
|
304
|
+
listenOptions.reusePort = true
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (this.#isFastify) {
|
|
308
|
+
await this.#app.listen(listenOptions)
|
|
309
|
+
this.url = getServerUrl(this.#app.server)
|
|
310
|
+
} else {
|
|
311
|
+
// Express / Node / Koa
|
|
312
|
+
this.#server = await new Promise((resolve, reject) => {
|
|
313
|
+
return this.#app
|
|
314
|
+
.listen(listenOptions, function () {
|
|
315
|
+
resolve(this)
|
|
316
|
+
})
|
|
317
|
+
.on('error', reject)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
this.url = getServerUrl(this.#server)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return this.url
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
_getApplication () {
|
|
327
|
+
return this.#app
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async _findEntrypoint () {
|
|
331
|
+
const config = this.config
|
|
332
|
+
|
|
333
|
+
if (config.node.main) {
|
|
334
|
+
return resolvePath(this.root, config.node.main)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const { entrypoint, hadEntrypointField } = await getEntrypointInformation(this.root)
|
|
338
|
+
|
|
339
|
+
if (typeof this.workerId === 'undefined' || this.workerId === 0) {
|
|
340
|
+
if (!entrypoint) {
|
|
341
|
+
this.logger.error(
|
|
342
|
+
`The service "${this.serviceId}" had no valid entrypoint defined in the package.json file and no valid entrypoint file was found.`
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
process.exit(1)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!hadEntrypointField) {
|
|
349
|
+
this.logger.warn(
|
|
350
|
+
`The service "${this.serviceId}" had no valid entrypoint defined in the package.json file. Falling back to the file "${entrypoint}".`
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return resolvePath(this.root, entrypoint)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async #hasBuildScript () {
|
|
359
|
+
// If no command was specified, we try to see if there is a build script defined in package.json.
|
|
360
|
+
let hasBuildScript
|
|
361
|
+
try {
|
|
362
|
+
const packageJson = JSON.parse(await readFile(resolvePath(this.root, 'package.json'), 'utf-8'))
|
|
363
|
+
hasBuildScript = typeof packageJson.scripts.build === 'string' && packageJson.scripts.build
|
|
364
|
+
} catch (e) {
|
|
365
|
+
// No-op
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return hasBuildScript
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async getWatchConfig () {
|
|
372
|
+
const config = this.config
|
|
373
|
+
|
|
374
|
+
const enabled = config.watch?.enabled !== false
|
|
375
|
+
|
|
376
|
+
if (!enabled) {
|
|
377
|
+
return { enabled, path: this.root }
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ignore the outDir from tsconfig or service config if any
|
|
381
|
+
let ignore = config.watch?.ignore
|
|
382
|
+
if (!ignore) {
|
|
383
|
+
const tsConfig = await getTsconfig(this.root, config)
|
|
384
|
+
if (tsConfig) {
|
|
385
|
+
ignore = ignoreDirs(tsConfig?.compilerOptions?.outDir, tsConfig?.watchOptions?.excludeDirectories)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
enabled,
|
|
391
|
+
path: this.root,
|
|
392
|
+
allow: config.watch?.allow,
|
|
393
|
+
ignore
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import path, { join } from 'node:path'
|
|
2
|
-
import { readFile } from 'node:fs/promises'
|
|
3
1
|
import json5 from 'json5'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import path, { join } from 'node:path'
|
|
4
4
|
|
|
5
5
|
export async function isServiceBuildable (serviceRoot, config) {
|
|
6
6
|
// skip vite as stackable as it has its own build command
|
|
@@ -56,12 +56,13 @@ export function ignoreDirs (outDir, watchOptionsExcludeDirectories) {
|
|
|
56
56
|
if (outDir) {
|
|
57
57
|
ignore.add(outDir)
|
|
58
58
|
if (!outDir.endsWith('/**')) {
|
|
59
|
-
ignore.add(`${outDir}
|
|
59
|
+
ignore.add(`${outDir}/*`)
|
|
60
|
+
ignore.add(`${outDir}/**/*`)
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
if (ignore.size === 0) {
|
|
64
|
-
return ['dist', 'dist
|
|
65
|
+
return ['dist', 'dist/*', 'dist/**/*']
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
return Array.from(ignore)
|
package/package.json
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/node",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-alpha.1",
|
|
4
4
|
"description": "Platformatic Node.js Stackable",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"create-platformatic-node": "./bin/create.js",
|
|
9
|
-
"start-platformatic-node": "./bin/start.js"
|
|
10
|
-
},
|
|
11
7
|
"repository": {
|
|
12
8
|
"type": "git",
|
|
13
9
|
"url": "git+https://github.com/platformatic/platformatic.git"
|
|
@@ -21,10 +17,9 @@
|
|
|
21
17
|
"dependencies": {
|
|
22
18
|
"json5": "^2.2.3",
|
|
23
19
|
"light-my-request": "^6.0.0",
|
|
24
|
-
"@platformatic/
|
|
25
|
-
"@platformatic/
|
|
26
|
-
"@platformatic/
|
|
27
|
-
"@platformatic/utils": "2.72.0"
|
|
20
|
+
"@platformatic/basic": "3.0.0-alpha.1",
|
|
21
|
+
"@platformatic/generators": "3.0.0-alpha.1",
|
|
22
|
+
"@platformatic/utils": "3.0.0-alpha.1"
|
|
28
23
|
},
|
|
29
24
|
"devDependencies": {
|
|
30
25
|
"borp": "^0.20.0",
|
|
@@ -36,12 +31,12 @@
|
|
|
36
31
|
"neostandard": "^0.12.0",
|
|
37
32
|
"tsx": "^4.19.0",
|
|
38
33
|
"typescript": "^5.5.4",
|
|
39
|
-
"@platformatic/composer": "
|
|
40
|
-
"@platformatic/service": "
|
|
34
|
+
"@platformatic/composer": "3.0.0-alpha.1",
|
|
35
|
+
"@platformatic/service": "3.0.0-alpha.1"
|
|
41
36
|
},
|
|
42
37
|
"scripts": {
|
|
43
|
-
"test": "pnpm run lint && borp --concurrency=1 --
|
|
44
|
-
"coverage": "pnpm run lint && borp -C -X test -X test/fixtures --concurrency=1 --
|
|
38
|
+
"test": "pnpm run lint && borp --concurrency=1 --timeout 1200000",
|
|
39
|
+
"coverage": "pnpm run lint && borp -C -X test -X test/fixtures --concurrency=1 --timeout 1200000",
|
|
45
40
|
"gen-schema": "node lib/schema.js > schema.json",
|
|
46
41
|
"gen-types": "json2ts > config.d.ts < schema.json",
|
|
47
42
|
"build": "pnpm run gen-schema && pnpm run gen-types",
|
package/schema.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/node/
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/node/3.0.0-alpha.1.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
|
-
"title": "Platformatic Node.js
|
|
4
|
+
"title": "Platformatic Node.js Config",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
7
|
"$schema": {
|
package/bin/create.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { basename, join } from 'node:path'
|
|
4
|
-
import { parseArgs } from 'node:util'
|
|
5
|
-
import { Generator } from '../lib/generator.js'
|
|
6
|
-
|
|
7
|
-
async function execute () {
|
|
8
|
-
const args = parseArgs({
|
|
9
|
-
args: process.argv.slice(2),
|
|
10
|
-
options: {
|
|
11
|
-
dir: {
|
|
12
|
-
type: 'string',
|
|
13
|
-
default: join(process.cwd(), 'plt-node')
|
|
14
|
-
},
|
|
15
|
-
main: { type: 'string', default: 'index.js' }
|
|
16
|
-
}
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
const generator = new Generator()
|
|
20
|
-
|
|
21
|
-
generator.setConfig({
|
|
22
|
-
targetDirectory: args.values.dir,
|
|
23
|
-
serviceName: basename(args.values.dir),
|
|
24
|
-
main: args.values.main
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
await generator.run()
|
|
28
|
-
|
|
29
|
-
console.log('Application created successfully! Run `npm run start` to start an application.')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
execute()
|
package/bin/start.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { findConfigurationFile, loadConfigurationFile } from '@platformatic/config'
|
|
4
|
-
import { ensureLoggableError } from '@platformatic/utils'
|
|
5
|
-
import { buildStackable } from '../index.js'
|
|
6
|
-
|
|
7
|
-
async function execute () {
|
|
8
|
-
const root = process.cwd()
|
|
9
|
-
const configurationFile = await findConfigurationFile(root)
|
|
10
|
-
|
|
11
|
-
const stackable = await buildStackable({
|
|
12
|
-
config: await loadConfigurationFile(configurationFile),
|
|
13
|
-
context: {
|
|
14
|
-
directory: process.cwd(),
|
|
15
|
-
serverConfig: configurationFile?.runtime?.server ?? {},
|
|
16
|
-
isEntrypoint: true,
|
|
17
|
-
isProduction: true,
|
|
18
|
-
runtimeConfig: {
|
|
19
|
-
logger: {
|
|
20
|
-
level: 'info',
|
|
21
|
-
pretty: true
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
// Set the location of the config
|
|
29
|
-
const url = await stackable.start({ listen: true })
|
|
30
|
-
stackable.logger.info('Server listening on %s', url)
|
|
31
|
-
} catch (error) {
|
|
32
|
-
stackable.logger.error({ error: ensureLoggableError(error) }, 'Error starting the application.')
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
execute()
|