@platformatic/runtime 0.26.1 → 0.28.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/lib/app.js CHANGED
@@ -1,14 +1,11 @@
1
1
  'use strict'
2
2
  const { once } = require('node:events')
3
- const { dirname } = require('node:path')
4
- const {
5
- addLoggerToTheConfig
6
- } = require('@platformatic/service')
3
+ const { dirname, basename } = require('node:path')
4
+ const { FileWatcher } = require('@platformatic/utils')
7
5
  const {
8
6
  buildServer,
9
7
  loadConfig
10
- } = require('@platformatic/start')
11
- const { FileWatcher } = require('@platformatic/utils')
8
+ } = require('./unified-api')
12
9
 
13
10
  class PlatformaticApp {
14
11
  #hotReload
@@ -16,8 +13,9 @@ class PlatformaticApp {
16
13
  #restarting
17
14
  #started
18
15
  #originalWatch
16
+ #logger
19
17
 
20
- constructor (appConfig, loaderPort) {
18
+ constructor (appConfig, loaderPort, logger) {
21
19
  this.appConfig = appConfig
22
20
  this.config = null
23
21
  this.#hotReload = false
@@ -26,6 +24,15 @@ class PlatformaticApp {
26
24
  this.server = null
27
25
  this.#started = false
28
26
  this.#originalWatch = null
27
+ this.#logger = logger
28
+ }
29
+
30
+ getStatus () {
31
+ if (this.#started) {
32
+ return 'started'
33
+ } else {
34
+ return 'stopped'
35
+ }
29
36
  }
30
37
 
31
38
  async restart (force) {
@@ -55,12 +62,15 @@ class PlatformaticApp {
55
62
  throw new Error('application is already started')
56
63
  }
57
64
 
58
- await this.#initializeConfig()
65
+ if (!this.#restarting) {
66
+ await this.#initializeConfig()
67
+ }
59
68
  const { configManager } = this.config
60
69
  const config = configManager.current
61
70
 
62
71
  this.#originalWatch = config.watch
63
72
  config.watch = false
73
+ this.#setuplogger(configManager)
64
74
 
65
75
  try {
66
76
  // If this is a restart, have the fastify server restart itself. If this
@@ -77,9 +87,6 @@ class PlatformaticApp {
77
87
  this.#logAndExit(err)
78
88
  }
79
89
 
80
- this.server.platformatic.configManager = configManager
81
- this.server.platformatic.config = config
82
-
83
90
  if (config.plugins !== undefined && this.#originalWatch !== false) {
84
91
  this.#startFileWatching()
85
92
  }
@@ -110,26 +117,11 @@ class PlatformaticApp {
110
117
  this.#started = false
111
118
  }
112
119
 
113
- async handleProcessLevelEvent ({ msg, signal, err }) {
114
- if (msg === 'plt:start') {
115
- await this.start()
116
- return
117
- }
118
-
120
+ async handleProcessLevelEvent ({ signal, err }) {
119
121
  if (!this.server) {
120
122
  return false
121
123
  }
122
124
 
123
- if (msg === 'plt:restart') {
124
- await this.restart(true)
125
- return
126
- }
127
-
128
- if (msg === 'plt:stop') {
129
- await this.stop()
130
- return
131
- }
132
-
133
125
  if (signal === 'SIGUSR2') {
134
126
  this.server.log.info('reloading configuration')
135
127
  await this.restart()
@@ -161,53 +153,43 @@ class PlatformaticApp {
161
153
  return appConfig.localServiceEnvVars.get(key)
162
154
  }
163
155
  })
164
- const { args, configManager } = this.config
156
+ const { configManager } = this.config
165
157
 
166
- if (appConfig._configOverrides instanceof Map) {
167
- try {
168
- appConfig._configOverrides.forEach((value, key) => {
169
- if (typeof key !== 'string') {
170
- throw new Error('config path must be a string.')
171
- }
172
-
173
- const parts = key.split('.')
174
- let next = configManager.current
175
- let obj
176
-
177
- for (let i = 0; next !== undefined && i < parts.length; ++i) {
178
- obj = next
179
- next = obj[parts[i]]
180
- }
181
-
182
- if (next !== undefined) {
183
- obj[parts.at(-1)] = value
184
- }
185
- })
186
- } catch (err) {
187
- configManager.stopWatching()
188
- throw err
158
+ function applyOverrides () {
159
+ if (appConfig._configOverrides instanceof Map) {
160
+ try {
161
+ appConfig._configOverrides.forEach((value, key) => {
162
+ if (typeof key !== 'string') {
163
+ throw new Error('config path must be a string.')
164
+ }
165
+
166
+ const parts = key.split('.')
167
+ let next = configManager.current
168
+ let obj
169
+
170
+ for (let i = 0; next !== undefined && i < parts.length; ++i) {
171
+ obj = next
172
+ next = obj[parts[i]]
173
+ }
174
+
175
+ if (next !== undefined) {
176
+ obj[parts.at(-1)] = value
177
+ }
178
+ })
179
+ } catch (err) {
180
+ configManager.stopWatching()
181
+ throw err
182
+ }
189
183
  }
190
184
  }
191
185
 
192
- // Set the logger if not present (and the config supports it).
193
- if (configManager.current.server) {
194
- addLoggerToTheConfig(configManager.current)
195
- configManager.current.server.logger.name = this.appConfig.id
196
- }
197
-
198
- this.#hotReload = args.hotReload && this.appConfig.hotReload
186
+ applyOverrides()
199
187
 
200
- if (configManager.current.plugins) {
201
- if (this.#hotReload) {
202
- this.#hotReload = configManager.current.plugins.hotReload
203
- }
204
-
205
- configManager.current.plugins.hotReload = false
206
- }
188
+ this.#hotReload = this.appConfig.hotReload
207
189
 
208
190
  configManager.on('update', async (newConfig) => {
209
- /* c8 ignore next 4 */
210
191
  this.server.platformatic.config = newConfig
192
+ applyOverrides()
211
193
  this.server.log.debug('config changed')
212
194
  this.server.log.trace({ newConfig }, 'new config')
213
195
  await this.restart()
@@ -219,14 +201,26 @@ class PlatformaticApp {
219
201
  })
220
202
  }
221
203
 
204
+ #setuplogger (configManager) {
205
+ // Set the logger if not present (and the config supports it).
206
+ if (configManager.current.server) {
207
+ const childLogger = this.#logger.child({
208
+ name: this.appConfig.id
209
+ }, { level: configManager.current.server.logger?.level || 'info' })
210
+ configManager.current.server.logger = childLogger
211
+ }
212
+ }
213
+
222
214
  #startFileWatching () {
223
215
  const server = this.server
224
216
  const { configManager } = server.platformatic
217
+ // TODO FileWatcher and ConfigManager both watch the configuration file
218
+ // we should remove the watching from the ConfigManager
225
219
  const fileWatcher = new FileWatcher({
226
220
  path: dirname(configManager.fullPath),
227
221
  /* c8 ignore next 2 */
228
222
  allowToWatch: this.#originalWatch?.allow,
229
- watchIgnore: this.#originalWatch?.ignore
223
+ watchIgnore: [...(this.#originalWatch?.ignore || []), basename(configManager.fullPath)]
230
224
  })
231
225
 
232
226
  fileWatcher.on('update', async () => {
@@ -253,7 +247,7 @@ class PlatformaticApp {
253
247
 
254
248
  #logAndExit (err) {
255
249
  this.config?.configManager?.stopWatching()
256
- console.error(err)
250
+ this.#logger.error({ err })
257
251
  process.exit(1)
258
252
  }
259
253
  }
@@ -0,0 +1,28 @@
1
+ 'use strict'
2
+ const ConfigManager = require('@platformatic/config')
3
+ const { platformaticRuntime } = require('./config')
4
+ const { startWithConfig } = require('./start')
5
+
6
+ async function buildServer (options = {}) {
7
+ if (!options.configManager) {
8
+ // Instantiate a new config manager from the current options.
9
+ const cm = new ConfigManager({
10
+ ...platformaticRuntime.configManagerConfig,
11
+ source: options
12
+ })
13
+ await cm.parseAndValidate()
14
+
15
+ if (typeof options === 'string') {
16
+ options = { configManager: cm }
17
+ } else {
18
+ options.configManager = cm
19
+ }
20
+ }
21
+
22
+ // The transformConfig() function can't be sent between threads.
23
+ delete options.configManager._transformConfig
24
+
25
+ return startWithConfig(options.configManager)
26
+ }
27
+
28
+ module.exports = { buildServer }
package/lib/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
  const { readFile, readdir } = require('node:fs/promises')
3
- const { join, resolve: pathResolve } = require('node:path')
3
+ const { basename, join, resolve: pathResolve } = require('node:path')
4
4
  const Topo = require('@hapi/topo')
5
5
  const ConfigManager = require('@platformatic/config')
6
6
  const { schema } = require('./schema')
@@ -101,7 +101,7 @@ async function parseClientsAndComposer (configManager) {
101
101
  }
102
102
 
103
103
  if (dep.origin === `{${err.key}}`) {
104
- service.localServiceEnvVars.set(err.key, `${clientName}.plt.local`)
104
+ service.localServiceEnvVars.set(err.key, `http://${clientName}.plt.local`)
105
105
  }
106
106
  }
107
107
  }
@@ -118,6 +118,7 @@ async function parseClientsAndComposer (configManager) {
118
118
  const promises = parsed.clients.map((client) => {
119
119
  // eslint-disable-next-line no-async-promise-executor
120
120
  return new Promise(async (resolve, reject) => {
121
+ let clientName = client.serviceId ?? ''
121
122
  let clientUrl
122
123
  let missingKey
123
124
 
@@ -135,11 +136,15 @@ async function parseClientsAndComposer (configManager) {
135
136
  }
136
137
 
137
138
  const isLocal = missingKey && client.url === `{${missingKey}}`
138
- const clientAbsolutePath = pathResolve(service.path, client.path)
139
- const clientPackageJson = join(clientAbsolutePath, 'package.json')
140
- const clientMetadata = JSON.parse(await readFile(clientPackageJson, 'utf8'))
139
+
141
140
  /* c8 ignore next 20 - unclear why c8 is unhappy for nearly 20 lines here */
142
- const clientName = clientMetadata.name ?? ''
141
+ if (!clientName) {
142
+ const clientAbsolutePath = pathResolve(service.path, client.path)
143
+ const clientPackageJson = join(clientAbsolutePath, 'package.json')
144
+ const clientMetadata = JSON.parse(await readFile(clientPackageJson, 'utf8'))
145
+
146
+ clientName = clientMetadata.name ?? ''
147
+ }
143
148
 
144
149
  if (clientUrl === undefined) {
145
150
  // Combine the service name with the client name to avoid collisions
@@ -156,8 +161,8 @@ async function parseClientsAndComposer (configManager) {
156
161
 
157
162
  const dependency = configManager.current.serviceMap.get(clientName)
158
163
 
164
+ /* c8 ignore next 4 */
159
165
  if (dependency === undefined) {
160
- /* c8 ignore next 3 */
161
166
  reject(new Error(`service '${service.id}' has unknown dependency: '${clientName}'`))
162
167
  return
163
168
  }
@@ -206,4 +211,27 @@ platformaticRuntime.configManagerConfig = {
206
211
  }
207
212
  }
208
213
 
209
- module.exports = { platformaticRuntime }
214
+ async function wrapConfigInRuntimeConfig ({ configManager, args }) {
215
+ /* c8 ignore next */
216
+ const id = basename(configManager.dirname) || 'main'
217
+ const wrapperConfig = {
218
+ $schema: schema.$id,
219
+ entrypoint: id,
220
+ allowCycles: false,
221
+ hotReload: true,
222
+ services: [
223
+ {
224
+ id,
225
+ path: configManager.dirname,
226
+ config: configManager.fullPath
227
+ }
228
+ ]
229
+ }
230
+ const cm = new ConfigManager({ source: wrapperConfig, schema })
231
+
232
+ await _transformConfig(cm)
233
+ await cm.parseAndValidate()
234
+ return cm
235
+ }
236
+
237
+ module.exports = { platformaticRuntime, wrapConfigInRuntimeConfig }
package/lib/loader.mjs CHANGED
@@ -1,16 +1,17 @@
1
1
  import { createRequire, isBuiltin } from 'node:module'
2
2
  import { dirname, isAbsolute, resolve as pathResolve } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
3
+ import { fileURLToPath, pathToFileURL } from 'node:url'
4
4
  const require = createRequire(import.meta.url)
5
- const thisFile = fileURLToPath(import.meta.url)
6
5
  const isWindows = process.platform === 'win32'
7
6
  let timestamp = process.hrtime.bigint()
8
7
  let port
9
8
 
9
+ /* c8 ignore next 3 - c8 upgrade marked many existing things as uncovered */
10
10
  function bustEsmCache () {
11
11
  timestamp = process.hrtime.bigint()
12
12
  }
13
13
 
14
+ /* c8 ignore next 11 - c8 upgrade marked many existing things as uncovered */
14
15
  function clearCjsCache () {
15
16
  // This evicts all of the modules from the require() cache.
16
17
  // Note: This does not clean up children references to the deleted module.
@@ -25,6 +26,7 @@ function clearCjsCache () {
25
26
 
26
27
  function isRelativePath (p) {
27
28
  // This function is extracted from Node core, so it should work.
29
+ /* c8 ignore next - c8 upgrade marked many existing things as uncovered */
28
30
  return p.charAt(0) === '.' &&
29
31
  /* c8 ignore next 9 */
30
32
  (
@@ -38,15 +40,15 @@ function isRelativePath (p) {
38
40
  )
39
41
  }
40
42
 
41
- function specifierToPath (specifier, referencingModuleId) {
42
- // Convert the specifier into an absolute path if possible. If the specifier
43
- // cannot be converted to a path (for example for a core module), then return
44
- // null.
43
+ function specifierToFileUrl (specifier, referencingModuleId) {
44
+ // Convert the specifier into an absolute path URL if possible. If the
45
+ // specifier cannot be converted to a path (for example for a core module),
46
+ // then return null.
45
47
  try {
46
48
  const url = new URL(specifier)
47
49
 
48
50
  if (url.protocol === 'file:') {
49
- specifier = url.pathname
51
+ return url.href
50
52
  } else {
51
53
  return null
52
54
  }
@@ -54,12 +56,14 @@ function specifierToPath (specifier, referencingModuleId) {
54
56
  // Ignore error.
55
57
  }
56
58
 
59
+ /* c8 ignore next 3 - c8 upgrade marked many existing things as uncovered */
57
60
  if (isBuiltin(specifier)) {
58
61
  return null
59
62
  }
60
63
 
64
+ /* c8 ignore next 3 */
61
65
  if (isAbsolute(specifier)) {
62
- return specifier
66
+ return pathToFileURL(specifier).href
63
67
  }
64
68
 
65
69
  /* c8 ignore next 3 */
@@ -67,31 +71,35 @@ function specifierToPath (specifier, referencingModuleId) {
67
71
  throw new Error(`cannot map '${specifier}' to an absolute path`)
68
72
  }
69
73
 
74
+ /* c8 ignore next 5 - c8 upgrade marked many existing things as uncovered */
70
75
  if (isRelativePath(specifier)) {
71
- return pathResolve(dirname(fileURLToPath(referencingModuleId)), specifier)
76
+ return pathToFileURL(
77
+ pathResolve(dirname(fileURLToPath(referencingModuleId)), specifier)
78
+ ).href
72
79
  } else {
73
80
  // The specifier is something in node_modules/.
74
81
  const req = createRequire(referencingModuleId)
75
82
 
76
- return req.resolve(specifier)
83
+ return pathToFileURL(req.resolve(specifier)).href
77
84
  }
78
85
  }
79
86
 
80
87
  export async function resolve (specifier, context, nextResolve) {
81
- const path = specifierToPath(specifier, context.parentURL)
88
+ const url = specifierToFileUrl(specifier, context.parentURL)
82
89
 
83
90
  // If the specifier could not be mapped to a file, or the path is this file,
84
91
  // then don't do anything.
85
- if (typeof path !== 'string' || path === thisFile) {
92
+ if (typeof url !== 'string' || url === import.meta.url) {
86
93
  return nextResolve(specifier, context)
87
94
  }
88
95
 
89
- return nextResolve(`${path}?ts=${timestamp}`, context)
96
+ return nextResolve(`${url}?ts=${timestamp}`, context)
90
97
  }
91
98
 
92
99
  export function globalPreload (context) {
93
100
  port = context.port
94
101
  port.on('message', () => {
102
+ /* c8 ignore next 3 - c8 upgrade marked many existing things as uncovered */
95
103
  bustEsmCache()
96
104
  clearCjsCache()
97
105
  port.postMessage('plt:cache-cleared')
package/lib/start.js CHANGED
@@ -1,11 +1,13 @@
1
1
  'use strict'
2
2
  const { once } = require('node:events')
3
3
  const { join } = require('node:path')
4
+ const { pathToFileURL } = require('node:url')
4
5
  const { Worker } = require('node:worker_threads')
5
6
  const closeWithGrace = require('close-with-grace')
6
7
  const { loadConfig } = require('@platformatic/service')
7
8
  const { platformaticRuntime } = require('./config')
8
- const kLoaderFile = join(__dirname, 'loader.mjs')
9
+ const { RuntimeApiClient } = require('./api.js')
10
+ const kLoaderFile = pathToFileURL(join(__dirname, 'loader.mjs')).href
9
11
  const kWorkerFile = join(__dirname, 'worker.js')
10
12
  const kWorkerExecArgv = [
11
13
  '--no-warnings',
@@ -35,8 +37,8 @@ async function startWithConfig (configManager) {
35
37
  configManager.fileWatcher?.stopWatching()
36
38
  })
37
39
 
38
- worker.on('error', (err) => {
39
- console.error(err)
40
+ worker.on('error', () => {
41
+ // the error is logged in the worker
40
42
  process.exit(1)
41
43
  })
42
44
 
@@ -56,22 +58,8 @@ async function startWithConfig (configManager) {
56
58
 
57
59
  await once(worker, 'message') // plt:init
58
60
 
59
- return {
60
- async start () {
61
- worker.postMessage({ msg: 'plt:start' })
62
- const [msg] = await once(worker, 'message') // plt:started
63
-
64
- return msg.url
65
- },
66
- async close () {
67
- worker.postMessage({ msg: 'plt:stop' })
68
- await once(worker, 'exit')
69
- },
70
- async restart () {
71
- worker.postMessage({ msg: 'plt:restart' })
72
- await once(worker, 'message') // plt:restarted
73
- }
74
- }
61
+ const runtimeApiClient = new RuntimeApiClient(worker)
62
+ return runtimeApiClient
75
63
  }
76
64
 
77
65
  module.exports = { start, startWithConfig }
@@ -0,0 +1,198 @@
1
+ 'use strict'
2
+ const { resolve } = require('node:path')
3
+ const parseArgs = require('minimist')
4
+ const ConfigManager = require('@platformatic/config')
5
+ const {
6
+ platformaticService,
7
+ buildServer,
8
+ loadConfig,
9
+ start,
10
+ schema: serviceSchema
11
+ } = require('@platformatic/service')
12
+ const {
13
+ schema: dbSchema,
14
+ platformaticDB
15
+ } = require('@platformatic/db')
16
+ const {
17
+ schema: composerSchema,
18
+ platformaticComposer
19
+ } = require('@platformatic/composer')
20
+ const { buildServer: runtimeBuildServer } = require('./build-server')
21
+ const { platformaticRuntime, wrapConfigInRuntimeConfig } = require('./config')
22
+ const { schema: runtimeSchema } = require('./schema')
23
+ const {
24
+ start: runtimeStart,
25
+ startWithConfig: runtimeStartWithConfig
26
+ } = require('./start')
27
+
28
+ const kSupportedAppTypes = new Set(['service', 'db', 'composer', 'runtime'])
29
+
30
+ async function tryGetConfigTypeFromSchema (config) {
31
+ /* c8 ignore next 6 - c8 is not seeing this as covered for some reason. */
32
+ if (typeof config === 'string') {
33
+ // Handle config file paths.
34
+ const loadedConfig = await loadConfig({}, ['-c', config], platformaticService)
35
+
36
+ config = loadedConfig.configManager.current
37
+ }
38
+
39
+ const schema = config?.$schema
40
+
41
+ if (typeof schema !== 'string') {
42
+ throw new Error('configuration is missing a schema')
43
+ }
44
+
45
+ const configType = schema.split('/').pop()
46
+
47
+ if (!kSupportedAppTypes.has(configType)) {
48
+ throw new Error(`unknown configuration type: '${configType}'`)
49
+ }
50
+
51
+ return configType
52
+ }
53
+
54
+ async function getConfigType (args = [], directory) {
55
+ try {
56
+ // The config type was not specified, so we need to figure it out.
57
+ // Try to get the config file from the provided arguments.
58
+ let { config } = parseArgs(args, { alias: { c: 'config' } })
59
+
60
+ if (!config) {
61
+ // Couldn't get the config file from the arguments, so look in the
62
+ // provided directory (or current directory) for any recognized
63
+ // config files.
64
+ const searchDir = directory ?? process.cwd()
65
+ const configFile = await ConfigManager.findConfigFile(searchDir)
66
+
67
+ config = resolve(searchDir, configFile)
68
+ }
69
+
70
+ // At this point, we have the config file. However, several different
71
+ // file formats are supported, so use the config manager to parse the
72
+ // file (without worrying about the validity of the file). We can then
73
+ // use the $schema field to determine the config type.
74
+ const configManager = new ConfigManager({ source: config })
75
+ const configString = await configManager.load()
76
+ const parsedConfig = configManager._parser(configString)
77
+
78
+ return await tryGetConfigTypeFromSchema(parsedConfig)
79
+ } catch (err) {
80
+ const configFiles = ConfigManager.listConfigFiles()
81
+ const msg = `
82
+ Missing config file!
83
+ Be sure to have a config file with one of the following names:
84
+ ${configFiles.map((s) => ' * ' + s).join('\n')}
85
+ Alternatively run "npm create platformatic@latest" to generate a basic plt service config.
86
+ `
87
+
88
+ throw new Error(msg, { cause: err })
89
+ }
90
+ }
91
+
92
+ async function getCurrentSchema (configType) {
93
+ if (configType === 'service') {
94
+ return serviceSchema.schema
95
+ } else if (configType === 'db') {
96
+ return dbSchema
97
+ } else if (configType === 'composer') {
98
+ return composerSchema
99
+ } else if (configType === 'runtime') {
100
+ return runtimeSchema
101
+ }
102
+
103
+ throw new Error(`unknown configuration type: '${configType}'`)
104
+ }
105
+
106
+ /* c8 ignore next 10 - for some reason c8 is not seeing this as covered. */
107
+ async function _buildServer (options) {
108
+ const configType = await tryGetConfigTypeFromSchema(options)
109
+ const app = getApp(configType)
110
+
111
+ if (app === platformaticRuntime) {
112
+ return runtimeBuildServer(options)
113
+ }
114
+
115
+ return buildServer(options, app)
116
+ }
117
+
118
+ function getApp (configType) {
119
+ if (configType === 'service') {
120
+ return platformaticService
121
+ } else if (configType === 'db') {
122
+ return platformaticDB
123
+ } else if (configType === 'composer') {
124
+ return platformaticComposer
125
+ } else if (configType === 'runtime') {
126
+ return platformaticRuntime
127
+ }
128
+
129
+ throw new Error('unknown kind: ' + configType)
130
+ }
131
+
132
+ async function _loadConfig (minimistConfig, args, configType, overrides) {
133
+ // If the config type was specified, then use that. Otherwise, compute it.
134
+ if (typeof configType !== 'string') {
135
+ configType = await getConfigType(args)
136
+ }
137
+
138
+ return loadConfig(minimistConfig, args, getApp(configType), overrides)
139
+ }
140
+
141
+ async function _start (args) {
142
+ const configType = await getConfigType(args)
143
+
144
+ if (configType === 'runtime') {
145
+ return runtimeStart(args)
146
+ }
147
+
148
+ return start(getApp(configType), args)
149
+ }
150
+
151
+ async function startCommand (args) {
152
+ try {
153
+ await _start(args)
154
+ } catch (err) {
155
+ logErrorAndExit(err)
156
+ }
157
+ }
158
+
159
+ async function startCommandInRuntime (args) {
160
+ try {
161
+ const configType = await getConfigType(args)
162
+ const config = await _loadConfig({}, args, configType)
163
+ let runtime
164
+
165
+ if (configType === 'runtime') {
166
+ runtime = await runtimeStartWithConfig(config.configManager)
167
+ } else {
168
+ const wrappedConfig = await wrapConfigInRuntimeConfig(config)
169
+ runtime = await runtimeStartWithConfig(wrappedConfig)
170
+ }
171
+
172
+ return await runtime.start()
173
+ } catch (err) {
174
+ logErrorAndExit(err)
175
+ }
176
+ }
177
+
178
+ function logErrorAndExit (err) {
179
+ delete err?.stack
180
+ console.error(err?.message)
181
+
182
+ if (err?.cause) {
183
+ console.error(`${err.cause}`)
184
+ }
185
+
186
+ process.exit(1)
187
+ }
188
+
189
+ module.exports = {
190
+ buildServer: _buildServer,
191
+ getConfigType,
192
+ getCurrentSchema,
193
+ loadConfig: _loadConfig,
194
+ start: _start,
195
+ startCommand,
196
+ startCommandInRuntime,
197
+ getApp
198
+ }