@platformatic/runtime 2.74.3 → 3.0.0-alpha.2

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/config.js CHANGED
@@ -1,35 +1,124 @@
1
1
  'use strict'
2
2
 
3
+ const { join, resolve: resolvePath, isAbsolute } = require('node:path')
3
4
  const { readdir } = require('node:fs/promises')
4
5
  const { createRequire } = require('node:module')
5
- const { join, resolve: pathResolve, isAbsolute } = require('node:path')
6
+ const { importStackableAndConfig, validationOptions } = require('@platformatic/basic')
6
7
  const {
7
- loadModule,
8
+ kMetadata,
8
9
  omitProperties,
9
- schemaComponents: { runtimeUnwrappablePropertiesList }
10
- } = require('@platformatic/utils')
11
- const ConfigManager = require('@platformatic/config')
12
- const { Store } = require('@platformatic/config')
13
-
14
- const errors = require('./errors')
10
+ loadModule,
11
+ runtimeUnwrappablePropertiesList,
12
+ findConfigurationFile,
13
+ loadConfigurationModule,
14
+ loadConfiguration,
15
+ extractModuleFromSchemaUrl
16
+ } = require('@platformatic/foundation')
17
+ const {
18
+ InspectAndInspectBrkError,
19
+ InvalidEntrypointError,
20
+ InvalidServicesWithWebError,
21
+ MissingEntrypointError,
22
+ InspectorPortError,
23
+ InspectorHostError
24
+ } = require('./errors')
15
25
  const { schema } = require('./schema')
16
- const upgrade = require('./upgrade')
17
- const { parseArgs } = require('node:util')
26
+ const { upgrade } = require('./upgrade')
27
+
28
+ async function wrapInRuntimeConfig (config, context) {
29
+ let serviceId = 'main'
30
+ try {
31
+ const packageJson = join(config[kMetadata].root, 'package.json')
32
+ serviceId = require(packageJson).name || 'main'
33
+
34
+ if (serviceId.startsWith('@')) {
35
+ serviceId = serviceId.split('/')[1]
36
+ }
37
+ } catch (err) {
38
+ // on purpose, the package.json might be missing
39
+ }
18
40
 
19
- async function _transformConfig (configManager, args) {
20
- const config = configManager.current
41
+ // If the service supports its (so far, only @platformatic/service and descendants)
42
+ const { hostname, port, http2, https } = config.server ?? {}
43
+ const server = { hostname, port, http2, https }
44
+ const production = context?.isProduction ?? context?.production
45
+
46
+ // Important: do not change the order of the properties in this object
47
+ /* c8 ignore next */
48
+ const wrapped = {
49
+ $schema: schema.$id,
50
+ server,
51
+ watch: !production,
52
+ ...omitProperties(config.runtime ?? {}, runtimeUnwrappablePropertiesList),
53
+ entrypoint: serviceId,
54
+ services: [
55
+ {
56
+ id: serviceId,
57
+ path: config[kMetadata].root,
58
+ config: config[kMetadata].path
59
+ }
60
+ ]
61
+ }
21
62
 
22
- const { values } = parseArgs({
23
- args,
24
- strict: false,
25
- options: { production: { type: 'boolean', short: 'p', default: false } }
63
+ return loadConfiguration(wrapped, context?.schema ?? schema, {
64
+ validationOptions,
65
+ transform,
66
+ upgrade,
67
+ replaceEnv: true,
68
+ root: config[kMetadata].root,
69
+ ...context
26
70
  })
27
- const production = values.production
71
+ }
72
+
73
+ function parseInspectorOptions (config, inspect, inspectBreak) {
74
+ const hasInspect = inspect != null
75
+ const hasInspectBrk = inspectBreak != null
76
+
77
+ if (hasInspect && hasInspectBrk) {
78
+ throw new InspectAndInspectBrkError()
79
+ }
80
+
81
+ const value = inspectBreak ?? inspect
82
+
83
+ if (!value) {
84
+ return
85
+ }
86
+
87
+ let host = '127.0.0.1'
88
+ let port = 9229
89
+
90
+ if (typeof value === 'string' && value.length > 0) {
91
+ const splitAt = value.lastIndexOf(':')
92
+
93
+ if (splitAt === -1) {
94
+ port = value
95
+ } else {
96
+ host = value.substring(0, splitAt)
97
+ port = value.substring(splitAt + 1)
98
+ }
99
+
100
+ port = Number.parseInt(port, 10)
101
+
102
+ if (!(port === 0 || (port >= 1024 && port <= 65535))) {
103
+ throw new InspectorPortError()
104
+ }
105
+
106
+ if (!host) {
107
+ throw new InspectorHostError()
108
+ }
109
+ }
110
+
111
+ config.inspectorOptions = { host, port, breakFirstLine: hasInspectBrk, watchDisabled: !!config.watch }
112
+ config.watch = false
113
+ }
114
+
115
+ async function transform (config, _, context) {
116
+ const production = context?.isProduction ?? context?.production
28
117
 
29
118
  let services
30
119
  if (config.web?.length) {
31
120
  if (config.services?.length) {
32
- throw new errors.InvalidServicesWithWebError()
121
+ throw new InvalidServicesWithWebError()
33
122
  }
34
123
 
35
124
  services = config.web
@@ -48,12 +137,7 @@ async function _transformConfig (configManager, args) {
48
137
  const { exclude = [], mappings = {} } = config.autoload
49
138
  let { path } = config.autoload
50
139
 
51
- // This is a hack, but it's the only way to not fix the paths for the autoloaded services
52
- // while we are upgrading the config
53
- if (configManager._fixPaths) {
54
- path = pathResolve(configManager.dirname, path)
55
- }
56
-
140
+ path = resolvePath(config[kMetadata].root, path)
57
141
  const entries = await readdir(path, { withFileTypes: true })
58
142
 
59
143
  for (let i = 0; i < entries.length; ++i) {
@@ -68,7 +152,7 @@ async function _transformConfig (configManager, args) {
68
152
  const entryPath = join(path, entry.name)
69
153
 
70
154
  let config
71
- const configFilename = mapping.config ?? (await ConfigManager.findConfigFile(entryPath))
155
+ const configFilename = mapping.config ?? (await findConfigurationFile(entryPath))
72
156
 
73
157
  if (typeof configFilename === 'string') {
74
158
  config = join(entryPath, configFilename)
@@ -85,8 +169,9 @@ async function _transformConfig (configManager, args) {
85
169
  }
86
170
  }
87
171
 
88
- configManager.current.serviceMap = new Map()
89
- configManager.current.inspectorOptions = undefined
172
+ config.serviceMap = new Map()
173
+ config.inspectorOptions = undefined
174
+ parseInspectorOptions(config, context?.inspect, context?.inspectBreak)
90
175
 
91
176
  let hasValidEntrypoint = false
92
177
 
@@ -96,64 +181,44 @@ async function _transformConfig (configManager, args) {
96
181
  // We need to have absolute paths here, ot the `loadConfig` will fail
97
182
  // Make sure we don't resolve if env var was not replaced
98
183
  if (service.path && !isAbsolute(service.path) && !service.path.match(/^\{.*\}$/)) {
99
- service.path = pathResolve(configManager.dirname, service.path)
184
+ service.path = resolvePath(config[kMetadata].root, service.path)
100
185
  }
101
186
 
102
- if (configManager._fixPaths && service.path && service.config) {
103
- service.config = pathResolve(service.path, service.config)
187
+ if (service.path && service.config) {
188
+ service.config = resolvePath(service.path, service.config)
104
189
  }
105
190
 
106
- if (service.config) {
107
- try {
108
- const store = new Store({ cwd: service.path })
109
- const serviceConfig = await store.loadConfig(service)
110
- service.isPLTService = !!serviceConfig.app.isPLTService
111
- service.type = serviceConfig.app.configType
112
- const _require = createRequire(service.path)
113
- // This is needed to work around Rust bug on dylibs:
114
- // https://github.com/rust-lang/rust/issues/91979
115
- // https://github.com/rollup/rollup/issues/5761
116
- // TODO(mcollina): we should expose this inside every stackable configuration.
117
- serviceConfig.app.modulesToLoad?.forEach(m => {
118
- const toLoad = _require.resolve(m)
119
- loadModule(_require, toLoad).catch(() => {})
120
- })
121
- } catch (err) {
122
- // Fallback if for any reason a dependency is not found
123
- try {
124
- const manager = new ConfigManager({ source: pathResolve(service.path, service.config) })
125
- await manager.parse()
126
- const config = manager.current
127
- const type = config.$schema ? ConfigManager.matchKnownSchema(config.$schema) : undefined
128
- service.type = type
129
- service.isPLTService = !!config.isPLTService
130
- } catch (err) {
131
- // This should not happen, it happens on running some unit tests if we prepare the runtime
132
- // when not all the services configs are available. Given that we are running this only
133
- // to ddetermine the type of the service, it's safe to ignore this error and default to unknown
134
- service.type = 'unknown'
135
- service.isPLTService = false
136
- }
191
+ try {
192
+ let pkg
193
+
194
+ if (service.config) {
195
+ const config = await loadConfiguration(service.config)
196
+ pkg = await loadConfigurationModule(service.path, config)
197
+
198
+ service.type = extractModuleFromSchemaUrl(config, true).module
199
+ service.skipTelemetryHooks = pkg.skipTelemetryHooks
200
+ } else {
201
+ const { moduleName, stackable } = await importStackableAndConfig(service.path)
202
+ pkg = stackable
203
+
204
+ service.type = moduleName
137
205
  }
138
- } else {
139
- // We need to identify the service type
140
- const basic = await import('@platformatic/basic')
141
- service.isPLTService = false
142
- try {
143
- const { stackable } = await basic.importStackableAndConfig(service.path)
144
- service.type = stackable.default.configType
145
- const _require = createRequire(service.path)
146
- // This is needed to work around Rust bug on dylibs:
147
- // https://github.com/rust-lang/rust/issues/91979
148
- // https://github.com/rollup/rollup/issues/5761
149
- // TODO(mcollina): we should expose this inside every stackable configuration.
150
- stackable.default.modulesToLoad?.forEach(m => {
151
- const toLoad = _require.resolve(m)
152
- loadModule(_require, toLoad).catch(() => {})
153
- })
154
- } catch {
155
- // Nothing to do here
206
+
207
+ service.skipTelemetryHooks = pkg.skipTelemetryHooks
208
+
209
+ // This is needed to work around Rust bug on dylibs:
210
+ // https://github.com/rust-lang/rust/issues/91979
211
+ // https://github.com/rollup/rollup/issues/5761
212
+ const _require = createRequire(service.path)
213
+ for (const m of pkg.modulesToLoad ?? []) {
214
+ const toLoad = _require.resolve(m)
215
+ loadModule(_require, toLoad).catch(() => {})
156
216
  }
217
+ } catch (err) {
218
+ // This should not happen, it happens on running some unit tests if we prepare the runtime
219
+ // when not all the services configs are available. Given that we are running this only
220
+ // to ddetermine the type of the service, it's safe to ignore this error and default to unknown
221
+ service.type = 'unknown'
157
222
  }
158
223
 
159
224
  service.entrypoint = service.id === config.entrypoint
@@ -169,7 +234,7 @@ async function _transformConfig (configManager, args) {
169
234
  hasValidEntrypoint = true
170
235
  }
171
236
 
172
- configManager.current.serviceMap.set(service.id, service)
237
+ config.serviceMap.set(service.id, service)
173
238
  }
174
239
 
175
240
  // If there is no entrypoint, autodetect one
@@ -188,7 +253,7 @@ async function _transformConfig (configManager, args) {
188
253
  continue
189
254
  }
190
255
 
191
- if (service.type === 'composer') {
256
+ if (service.type === '@platformatic/composer') {
192
257
  composers.push(service.id)
193
258
  }
194
259
  }
@@ -201,162 +266,38 @@ async function _transformConfig (configManager, args) {
201
266
  }
202
267
  }
203
268
 
204
- if (!hasValidEntrypoint) {
269
+ if (!hasValidEntrypoint && !context.allowMissingEntrypoint) {
205
270
  if (config.entrypoint) {
206
- throw new errors.InvalidEntrypointError(config.entrypoint)
271
+ throw new InvalidEntrypointError(config.entrypoint)
207
272
  } else if (services.length >= 1) {
208
- throw new errors.MissingEntrypointError()
273
+ throw new MissingEntrypointError()
209
274
  }
210
275
  // If there are no services, and no entrypoint it's an empty app.
211
276
  // It won't start, but we should be able to parse and operate on it,
212
277
  // like adding other services.
213
278
  }
214
279
 
215
- configManager.current.services = services
216
- configManager.current.web = undefined
280
+ config.services = services
281
+ config.web = undefined
282
+ config.logger ??= {}
217
283
 
218
284
  if (production) {
219
285
  // Any value below 10 is considered as "immediate restart" and won't be processed via setTimeout or similar
220
286
  // Important: do not use 2 otherwise ajv will convert to boolean `true`
221
- configManager.current.restartOnError = 2
287
+ config.restartOnError = 2
222
288
  } else {
223
- if (configManager.current.restartOnError === true) {
224
- configManager.current.restartOnError = 5000
225
- } else if (configManager.current.restartOnError < 0) {
226
- configManager.current.restartOnError = 0
289
+ if (config.restartOnError === true) {
290
+ config.restartOnError = 5000
291
+ } else if (config.restartOnError < 0) {
292
+ config.restartOnError = 0
227
293
  }
228
294
  }
229
- }
230
-
231
- async function platformaticRuntime () {
232
- // No-op. Here for consistency with other app types.
233
- }
234
-
235
- platformaticRuntime[Symbol.for('skip-override')] = true
236
- platformaticRuntime.schema = schema
237
- platformaticRuntime.configType = 'runtime'
238
- platformaticRuntime.configManagerConfig = {
239
- version: require('../package.json').version,
240
- schema,
241
- allowToWatch: ['.env'],
242
- schemaOptions: {
243
- useDefaults: true,
244
- coerceTypes: true,
245
- allErrors: true,
246
- strict: false
247
- },
248
- async transformConfig (args) {
249
- await _transformConfig(this, args)
250
- },
251
- upgrade
252
- }
253
295
 
254
- async function wrapConfigInRuntimeConfig ({ configManager, args, opts }) {
255
- let serviceId = 'main'
256
- try {
257
- const packageJson = join(configManager.dirname, 'package.json')
258
- serviceId = require(packageJson).name || 'main'
259
- if (serviceId.startsWith('@')) {
260
- serviceId = serviceId.split('/')[1]
261
- }
262
- } catch (err) {
263
- // on purpose, the package.json might be missing
264
- }
265
-
266
- // If the service supports its (so far, only @platformatic/service and descendants)
267
- const { hostname, port, http2, https } = configManager.current.server ?? {}
268
- const server = { hostname, port, http2, https }
269
-
270
- // Important: do not change the order of the properties in this object
271
- /* c8 ignore next */
272
- const wrapperConfig = {
273
- $schema: schema.$id,
274
- server,
275
- watch: !args?.production,
276
- ...omitProperties(configManager.current.runtime ?? {}, runtimeUnwrappablePropertiesList),
277
- entrypoint: serviceId,
278
- services: [
279
- {
280
- id: serviceId,
281
- path: configManager.dirname,
282
- config: configManager.fullPath
283
- }
284
- ]
285
- }
286
-
287
- const cm = new ConfigManager({
288
- source: wrapperConfig,
289
- schema,
290
- schemaOptions: {
291
- useDefaults: true,
292
- coerceTypes: true,
293
- allErrors: true,
294
- strict: false
295
- },
296
- transformConfig (args) {
297
- return _transformConfig(this, args)
298
- }
299
- })
300
-
301
- await cm.parseAndValidate(true, [], opts)
302
-
303
- return cm
304
- }
305
-
306
- function parseInspectorOptions (configManager) {
307
- const { current, args } = configManager
308
- const hasInspect = 'inspect' in args
309
- const hasInspectBrk = 'inspect-brk' in args
310
- let inspectFlag
311
-
312
- if (hasInspect) {
313
- inspectFlag = args.inspect
314
-
315
- if (hasInspectBrk) {
316
- throw new errors.InspectAndInspectBrkError()
317
- }
318
- } else if (hasInspectBrk) {
319
- inspectFlag = args['inspect-brk']
320
- }
321
-
322
- if (inspectFlag !== undefined) {
323
- let host = '127.0.0.1'
324
- let port = 9229
325
-
326
- if (typeof inspectFlag === 'string' && inspectFlag.length > 0) {
327
- const splitAt = inspectFlag.lastIndexOf(':')
328
-
329
- if (splitAt === -1) {
330
- port = inspectFlag
331
- } else {
332
- host = inspectFlag.substring(0, splitAt)
333
- port = inspectFlag.substring(splitAt + 1)
334
- }
335
-
336
- port = Number.parseInt(port, 10)
337
-
338
- if (!(port === 0 || (port >= 1024 && port <= 65535))) {
339
- throw new errors.InspectorPortError()
340
- }
341
-
342
- if (!host) {
343
- throw new errors.InspectorHostError()
344
- }
345
- }
346
-
347
- current.inspectorOptions = {
348
- host,
349
- port,
350
- breakFirstLine: hasInspectBrk,
351
- watchDisabled: !!current.watch
352
- }
353
-
354
- current.watch = false
355
- }
296
+ return config
356
297
  }
357
298
 
358
299
  module.exports = {
300
+ wrapInRuntimeConfig,
359
301
  parseInspectorOptions,
360
- platformaticRuntime,
361
- wrapConfigInRuntimeConfig
302
+ transform
362
303
  }