@platformatic/runtime 3.68.0 → 4.0.0-new-config.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/config.d.ts +5 -44
- package/index.d.ts +10 -14
- package/index.js +191 -6
- package/lib/config.js +122 -111
- package/lib/errors.js +5 -17
- package/lib/generator.js +19 -92
- package/lib/management-api.js +3 -5
- package/lib/management-handlers.js +3 -5
- package/lib/runtime.js +377 -187
- package/lib/schema.js +1 -1
- package/lib/upgrade.js +16 -1
- package/lib/versions/v4.0.0.js +31 -0
- package/lib/worker/controller.js +51 -64
- package/lib/worker/itc.js +5 -6
- package/lib/worker/main.js +4 -19
- package/lib/worker/management.js +0 -4
- package/lib/worker/symbols.js +2 -0
- package/lib/worker-scaler.js +8 -10
- package/package.json +17 -16
- package/schema.json +33 -159
package/lib/config.js
CHANGED
|
@@ -9,22 +9,24 @@ import {
|
|
|
9
9
|
omitProperties,
|
|
10
10
|
runtimeUnwrappablePropertiesList
|
|
11
11
|
} from '@platformatic/foundation'
|
|
12
|
-
import { realpathSync } from 'node:fs'
|
|
13
12
|
import { readdir, readFile } from 'node:fs/promises'
|
|
14
13
|
import { createRequire } from 'node:module'
|
|
14
|
+
import { loadAdditionalApplications } from '@platformatic/foundation/lib/v4/index.js'
|
|
15
15
|
import { isAbsolute, join, resolve as resolvePath } from 'node:path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
16
17
|
|
|
17
18
|
import {
|
|
18
|
-
ApplicationIdCollisionError,
|
|
19
19
|
InspectAndInspectBrkError,
|
|
20
20
|
InspectorHostError,
|
|
21
21
|
InspectorPortError,
|
|
22
|
-
InvalidArgumentError
|
|
23
|
-
InvalidEntrypointError
|
|
22
|
+
InvalidArgumentError
|
|
24
23
|
} from './errors.js'
|
|
25
24
|
import { schema } from './schema.js'
|
|
26
25
|
import { upgrade } from './upgrade.js'
|
|
27
26
|
|
|
27
|
+
// The runtime package's own entry point, which is where the bundled capability copies live.
|
|
28
|
+
const runtimeScopePath = fileURLToPath(new URL('../index.js', import.meta.url))
|
|
29
|
+
|
|
28
30
|
// Validate and coerce workers values early to avoid runtime hangs when invalid
|
|
29
31
|
function coercePositiveInteger (value) {
|
|
30
32
|
if (typeof value === 'number') {
|
|
@@ -143,17 +145,6 @@ export async function wrapInRuntimeConfig (config, context) {
|
|
|
143
145
|
// on purpose, the package.json might be missing
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
// Carry over only the server properties that the user actually set, so
|
|
147
|
-
// we do not end up with a `{ hostname: undefined, ... }` object that then
|
|
148
|
-
// gets populated by schema defaults or trips truthy checks downstream.
|
|
149
|
-
const server = {}
|
|
150
|
-
if (config.server) {
|
|
151
|
-
for (const key of ['hostname', 'port', 'http2', 'https']) {
|
|
152
|
-
if (config.server[key] !== undefined) {
|
|
153
|
-
server[key] = config.server[key]
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
148
|
const production = context?.isProduction ?? context?.production
|
|
158
149
|
|
|
159
150
|
const runtimeConfig = config.runtime ?? {}
|
|
@@ -162,10 +153,8 @@ export async function wrapInRuntimeConfig (config, context) {
|
|
|
162
153
|
/* c8 ignore next */
|
|
163
154
|
const wrapped = {
|
|
164
155
|
$schema: schema.$id,
|
|
165
|
-
...(Object.keys(server).length > 0 ? { server } : {}),
|
|
166
156
|
watch: !production,
|
|
167
157
|
...omitProperties(runtimeConfig, runtimeUnwrappablePropertiesList),
|
|
168
|
-
entrypoint: applicationId,
|
|
169
158
|
applications: [
|
|
170
159
|
{
|
|
171
160
|
id: applicationId,
|
|
@@ -228,6 +217,49 @@ export function parseInspectorOptions (config, inspect, inspectBreak) {
|
|
|
228
217
|
config.watch = false
|
|
229
218
|
}
|
|
230
219
|
|
|
220
|
+
/*
|
|
221
|
+
The one entry point for applications added while the runtime is running, shared by the HTTP route
|
|
222
|
+
and the management ITC handler so the two cannot drift.
|
|
223
|
+
|
|
224
|
+
Under v4 an added application is evaluated exactly as boot evaluates one -- its configuration
|
|
225
|
+
file is found in its own directory, its environment is resolved main-side, and it arrives with
|
|
226
|
+
resolvedConfig. Skipping that leaves the worker to look for a v3 file name that v4 never writes,
|
|
227
|
+
which fails as an unhelpful "unable to initialize the worker".
|
|
228
|
+
*/
|
|
229
|
+
export async function prepareAddedApplications (config, entries, existingIds = []) {
|
|
230
|
+
const metadata = config[kMetadata]
|
|
231
|
+
|
|
232
|
+
if (!metadata?.v4) {
|
|
233
|
+
const prepared = []
|
|
234
|
+
|
|
235
|
+
for (const entry of entries) {
|
|
236
|
+
prepared.push(await prepareApplication(config, entry, config.workers))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return prepared
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const { applications } = await loadAdditionalApplications({
|
|
243
|
+
configPath: metadata.path,
|
|
244
|
+
entries,
|
|
245
|
+
existingIds,
|
|
246
|
+
rootEnvBlock: config.env,
|
|
247
|
+
command: metadata.v4.command,
|
|
248
|
+
mode: metadata.v4.mode,
|
|
249
|
+
production: metadata.v4.production,
|
|
250
|
+
// The runtime is the fallback scope for capability resolution, the same as at boot.
|
|
251
|
+
runtimeScope: runtimeScopePath
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const prepared = []
|
|
255
|
+
|
|
256
|
+
for (const application of applications) {
|
|
257
|
+
prepared.push(await prepareV4Application(config, application, config.workers))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return prepared
|
|
261
|
+
}
|
|
262
|
+
|
|
231
263
|
export async function prepareApplication (config, application, defaultWorkers) {
|
|
232
264
|
// We need to have absolute paths here, ot the `loadConfig` will fail
|
|
233
265
|
// Make sure we don't resolve if env var was not replaced
|
|
@@ -249,10 +281,10 @@ export async function prepareApplication (config, application, defaultWorkers) {
|
|
|
249
281
|
let pkg
|
|
250
282
|
|
|
251
283
|
if (application.config) {
|
|
252
|
-
const
|
|
253
|
-
pkg = await loadConfigurationModule(application.path,
|
|
284
|
+
const applicationConfig = await loadConfiguration(application.config)
|
|
285
|
+
pkg = await loadConfigurationModule(application.path, applicationConfig)
|
|
254
286
|
|
|
255
|
-
application.type = extractModuleFromSchemaUrl(
|
|
287
|
+
application.type = extractModuleFromSchemaUrl(applicationConfig, true).module
|
|
256
288
|
application.skipTelemetryHooks = pkg.skipTelemetryHooks
|
|
257
289
|
} else {
|
|
258
290
|
const { moduleName, capability } = await importCapabilityAndConfig(application.path)
|
|
@@ -279,10 +311,16 @@ export async function prepareApplication (config, application, defaultWorkers) {
|
|
|
279
311
|
}
|
|
280
312
|
}
|
|
281
313
|
|
|
314
|
+
return finalizeApplication(config, application, defaultWorkers)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Everything an application needs regardless of how its capability was determined. v3 discovers
|
|
318
|
+
// that by loading the application's config file; v4 has it from the loader's envelope before any
|
|
319
|
+
// worker exists.
|
|
320
|
+
export function finalizeApplication (config, application, defaultWorkers) {
|
|
282
321
|
// Validate and coerce per-service workers
|
|
283
322
|
parseWorkers(application, `Service "${application.id}"`, defaultWorkers)
|
|
284
323
|
|
|
285
|
-
application.entrypoint = application.id === config.entrypoint
|
|
286
324
|
application.dependencies ??= []
|
|
287
325
|
application.localUrl = `http://${application.id}.plt.local`
|
|
288
326
|
|
|
@@ -297,38 +335,37 @@ export async function prepareApplication (config, application, defaultWorkers) {
|
|
|
297
335
|
return application
|
|
298
336
|
}
|
|
299
337
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
338
|
+
/*
|
|
339
|
+
The v4 preparation. The loader resolved the path, selected the capability and imported its schema
|
|
340
|
+
subpath — which carries skipTelemetryHooks and modulesToLoad — all main-side, before any worker
|
|
341
|
+
existed. So there is nothing to discover here, and in particular no application config file to
|
|
342
|
+
re-read: that is the whole point of evaluating configuration exactly once per load.
|
|
343
|
+
*/
|
|
344
|
+
export async function prepareV4Application (config, application, defaultWorkers) {
|
|
345
|
+
// resolvedConfig replaces v3's config file path in workerData: the worker receives the validated
|
|
346
|
+
// capability payload as data and never re-reads a file.
|
|
347
|
+
application.resolvedConfig = application.config ?? {}
|
|
348
|
+
application.config = undefined
|
|
349
|
+
application.type = application.module ?? 'unknown'
|
|
350
|
+
application.skipTelemetryHooks = application.capabilityMetadata?.skipTelemetryHooks ?? false
|
|
351
|
+
|
|
352
|
+
// This is needed to work around a Rust bug on dylibs, as in the v3 path.
|
|
353
|
+
const modulesToLoad = application.capabilityMetadata?.modulesToLoad ?? []
|
|
354
|
+
|
|
355
|
+
if (modulesToLoad.length > 0 && application.path) {
|
|
356
|
+
const _require = createRequire(application.path)
|
|
357
|
+
|
|
358
|
+
for (const m of modulesToLoad) {
|
|
359
|
+
try {
|
|
360
|
+
loadModule(_require, _require.resolve(m)).catch(() => {})
|
|
361
|
+
} catch {
|
|
362
|
+
// A module the capability names but the application cannot resolve is not fatal here; the
|
|
363
|
+
// worker reports it with its own context when it actually needs it.
|
|
364
|
+
}
|
|
318
365
|
}
|
|
319
|
-
|
|
320
|
-
const existingPath = isAbsolute(existing.path) ? existing.path : resolvePath(root, existing.path)
|
|
321
|
-
|
|
322
|
-
return canonicalPath(existingPath) === canonicalPath(entryPath) ? null : `the path "${existing.path}"`
|
|
323
366
|
}
|
|
324
367
|
|
|
325
|
-
|
|
326
|
-
const resolvedPath = join(resolvedApplicationsPath, existing.id)
|
|
327
|
-
|
|
328
|
-
return canonicalPath(resolvedPath) === canonicalPath(entryPath) ? null : `the URL "${existing.url}"`
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return null
|
|
368
|
+
return finalizeApplication(config, application, defaultWorkers)
|
|
332
369
|
}
|
|
333
370
|
|
|
334
371
|
function isApplicationEnabled (application, environment) {
|
|
@@ -393,7 +430,7 @@ export async function transform (config, _, context) {
|
|
|
393
430
|
}
|
|
394
431
|
|
|
395
432
|
const scaleConfig = config.verticalScaler.applications[appId]
|
|
396
|
-
const workersConfig = appConfig.workers
|
|
433
|
+
const workersConfig = appConfig.workers ??= {}
|
|
397
434
|
|
|
398
435
|
workersConfig.minimum ??= scaleConfig.minWorkers ?? config.workers.minimum
|
|
399
436
|
workersConfig.maximum ??= scaleConfig.maxWorkers ?? config.workers.maximum
|
|
@@ -414,11 +451,7 @@ export async function transform (config, _, context) {
|
|
|
414
451
|
const { exclude = [], mappings = {} } = config.autoload
|
|
415
452
|
let { path } = config.autoload
|
|
416
453
|
|
|
417
|
-
|
|
418
|
-
const root = config[kMetadata].root
|
|
419
|
-
const resolvedApplicationsPath = resolvePath(root, config.resolvedApplicationsBasePath ?? 'external')
|
|
420
|
-
|
|
421
|
-
path = resolvePath(root, path)
|
|
454
|
+
path = resolvePath(config[kMetadata].root, path)
|
|
422
455
|
const entries = await readdir(path, { withFileTypes: true })
|
|
423
456
|
|
|
424
457
|
for (let i = 0; i < entries.length; ++i) {
|
|
@@ -443,21 +476,7 @@ export async function transform (config, _, context) {
|
|
|
443
476
|
const existingApplicationId = applications.findIndex(application => application.id === id)
|
|
444
477
|
|
|
445
478
|
if (existingApplicationId !== -1) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
// Merging on the id alone can boot local code where the configuration named a repository, as
|
|
449
|
-
// the autoloaded path survives next to the configured url. Reject the duplicate instead: an id
|
|
450
|
-
// is the mesh hostname, the injected PLT_<ID>_URL name, the metrics label and the argument of
|
|
451
|
-
// "wattpm inject", so two different applications cannot share one.
|
|
452
|
-
if (isApplicationEnabled(existing, environment)) {
|
|
453
|
-
const source = conflictingApplicationSource(root, resolvedApplicationsPath, existing, entryPath)
|
|
454
|
-
|
|
455
|
-
if (source) {
|
|
456
|
-
throw new ApplicationIdCollisionError(id, entryPath, source)
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
applications[existingApplicationId] = { ...application, ...existing }
|
|
479
|
+
applications[existingApplicationId] = { ...application, ...applications[existingApplicationId] }
|
|
461
480
|
} else {
|
|
462
481
|
applications.push(application)
|
|
463
482
|
}
|
|
@@ -470,54 +489,22 @@ export async function transform (config, _, context) {
|
|
|
470
489
|
}
|
|
471
490
|
}
|
|
472
491
|
|
|
492
|
+
return finalizeConfiguration(config, applications, context, production, prepareApplication)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// The half of the transform the loader does not own: inspector options, worker counts, the
|
|
496
|
+
// per-application preparation, and the runtime-level normalizations. v4 reaches this directly,
|
|
497
|
+
// having already expanded autoload and resolved enabled in the eval worker.
|
|
498
|
+
export async function finalizeConfiguration (config, applications, context, production, prepare) {
|
|
473
499
|
config.inspectorOptions = undefined
|
|
474
500
|
parseInspectorOptions(config, context?.inspect, context?.inspectBreak)
|
|
475
501
|
|
|
476
|
-
let hasValidEntrypoint = false
|
|
477
|
-
|
|
478
502
|
// Root-level workers
|
|
479
503
|
parseWorkers(config, 'Runtime', { static: 1, dynamic: false })
|
|
480
504
|
const defaultWorkers = config.workers
|
|
481
505
|
|
|
482
506
|
for (let i = 0; i < applications.length; ++i) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (application.entrypoint) {
|
|
486
|
-
hasValidEntrypoint = true
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// If there is no entrypoint, autodetect one
|
|
491
|
-
if (!config.entrypoint) {
|
|
492
|
-
// If there is only one application, it becomes the entrypoint
|
|
493
|
-
if (applications.length === 1) {
|
|
494
|
-
applications[0].entrypoint = true
|
|
495
|
-
config.entrypoint = applications[0].id
|
|
496
|
-
hasValidEntrypoint = true
|
|
497
|
-
} else {
|
|
498
|
-
// Search if exactly application uses @platformatic/gateway
|
|
499
|
-
const gateways = []
|
|
500
|
-
|
|
501
|
-
for (const application of applications) {
|
|
502
|
-
if (!application.config) {
|
|
503
|
-
continue
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (application.type === '@platformatic/gateway') {
|
|
507
|
-
gateways.push(application.id)
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (gateways.length === 1) {
|
|
512
|
-
applications.find(s => s.id === gateways[0]).entrypoint = true
|
|
513
|
-
config.entrypoint = gateways[0]
|
|
514
|
-
hasValidEntrypoint = true
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (!hasValidEntrypoint && config.entrypoint && !context.allowMissingEntrypoint) {
|
|
520
|
-
throw new InvalidEntrypointError(config.entrypoint)
|
|
507
|
+
await prepare(config, applications[i], defaultWorkers)
|
|
521
508
|
}
|
|
522
509
|
|
|
523
510
|
if (typeof config.metrics === 'boolean') {
|
|
@@ -557,3 +544,27 @@ export async function transform (config, _, context) {
|
|
|
557
544
|
|
|
558
545
|
return config
|
|
559
546
|
}
|
|
547
|
+
|
|
548
|
+
/*
|
|
549
|
+
The v4 transform. What is absent is the point:
|
|
550
|
+
|
|
551
|
+
- no alias merging, because `services` and `web` do not exist in v4;
|
|
552
|
+
- no autoload expansion and no `enabled` filtering, because both ran in the root eval worker,
|
|
553
|
+
which is the only place either runs. Leaving them reachable here would let a second expansion
|
|
554
|
+
re-merge entries and read the filesystem after the authoritative snapshot already exists;
|
|
555
|
+
- no verticalScaler migration, which the schema audit removes.
|
|
556
|
+
|
|
557
|
+
The `autoload` declaration survives as data beside the expanded list — `GET /metadata` reports it
|
|
558
|
+
and `applications:add --save` needs it to decide whether a new application belongs in a mapping
|
|
559
|
+
or as an explicit entry. It is carried, never re-executed.
|
|
560
|
+
*/
|
|
561
|
+
export async function transformV4 (config, _, context) {
|
|
562
|
+
const production = context?.isProduction ?? context?.production
|
|
563
|
+
const applications = config.applications ?? []
|
|
564
|
+
|
|
565
|
+
if (typeof config.watch === 'undefined') {
|
|
566
|
+
config.watch = !production
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return finalizeConfiguration(config, applications, context, production, prepareV4Application)
|
|
570
|
+
}
|
package/lib/errors.js
CHANGED
|
@@ -13,7 +13,11 @@ export const ERROR_PREFIX = 'PLT_RUNTIME'
|
|
|
13
13
|
|
|
14
14
|
export const AddressInUseError = createError(
|
|
15
15
|
`${ERROR_PREFIX}_EADDR_IN_USE`,
|
|
16
|
-
'
|
|
16
|
+
'Port %d is already in use by applications "%s" and "%s"'
|
|
17
|
+
)
|
|
18
|
+
export const WorkerAddressInUseError = createError(
|
|
19
|
+
`${ERROR_PREFIX}_WORKER_EADDR_IN_USE`,
|
|
20
|
+
'Port %d is already in use by another worker of the application "%s". Multiple workers can share a port only when the reusePort feature is available in your OS, otherwise set "server.portAssignment" to "perWorkerIncrement" in the application configuration or use a single worker.'
|
|
17
21
|
)
|
|
18
22
|
export const RuntimeExitedError = createError(
|
|
19
23
|
`${ERROR_PREFIX}_RUNTIME_EXIT`,
|
|
@@ -98,18 +102,6 @@ export const NoConfigFileFoundError = createError(
|
|
|
98
102
|
`${ERROR_PREFIX}_NO_CONFIG_FILE_FOUND`,
|
|
99
103
|
"No config file found for application '%s'"
|
|
100
104
|
)
|
|
101
|
-
export const InvalidEntrypointError = createError(
|
|
102
|
-
`${ERROR_PREFIX}_INVALID_ENTRYPOINT`,
|
|
103
|
-
"Invalid entrypoint: '%s' does not exist"
|
|
104
|
-
)
|
|
105
|
-
export const ApplicationIdCollisionError = createError(
|
|
106
|
-
`${ERROR_PREFIX}_APPLICATION_ID_COLLISION`,
|
|
107
|
-
'The application id "%s" is used by the autoloaded directory "%s" and by a different application defined in the configuration file via %s. Application ids must be unique.'
|
|
108
|
-
)
|
|
109
|
-
export const MissingEntrypointError = createError(
|
|
110
|
-
`${ERROR_PREFIX}_MISSING_ENTRYPOINT`,
|
|
111
|
-
'Missing application entrypoint.'
|
|
112
|
-
)
|
|
113
105
|
export const MissingDependencyError = createError(`${ERROR_PREFIX}_MISSING_DEPENDENCY`, 'Missing dependency: "%s"')
|
|
114
106
|
export const InspectAndInspectBrkError = createError(
|
|
115
107
|
`${ERROR_PREFIX}_INSPECT_AND_INSPECT_BRK`,
|
|
@@ -232,10 +224,6 @@ export const HealthSignalTypeMustBeStringError = createError(
|
|
|
232
224
|
`${ERROR_PREFIX}_HEALTH_SIGNAL_TYPE_MUST_BE_STRING`,
|
|
233
225
|
'Health signal type must be a string, received "%s"'
|
|
234
226
|
)
|
|
235
|
-
export const CannotRemoveEntrypointError = createError(
|
|
236
|
-
`${ERROR_PREFIX}_CANNOT_REMOVE_ENTRYPOINT`,
|
|
237
|
-
'Cannot remove the entrypoint application.'
|
|
238
|
-
)
|
|
239
227
|
export const WorkerInterceptorNotReadyError = createError(
|
|
240
228
|
`${ERROR_PREFIX}_WORKER_INTERCEPTOR_NOT_READY`,
|
|
241
229
|
'The "%s" application worker interceptor is not ready'
|
package/lib/generator.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import createError from '@fastify/error'
|
|
2
1
|
import {
|
|
3
2
|
createEnvFileTool,
|
|
4
3
|
defaultPackageManager,
|
|
@@ -23,44 +22,20 @@ const engines = {
|
|
|
23
22
|
node: '>=22.19.0'
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const NoApplicationNamedError = createError(
|
|
29
|
-
`${ERROR_PREFIX}_NO_APPLICATION_FOUND`,
|
|
30
|
-
"No application named '%s' has been added to this runtime."
|
|
31
|
-
)
|
|
32
|
-
const NoEntryPointError = createError(`${ERROR_PREFIX}_NO_ENTRYPOINT`, 'No entrypoint had been defined.')
|
|
33
|
-
|
|
34
|
-
function getRuntimeWrappableProperties (skipServer) {
|
|
35
|
-
const wrappableProperties = {
|
|
25
|
+
function getRuntimeWrappableProperties () {
|
|
26
|
+
return {
|
|
36
27
|
logger: {
|
|
37
28
|
level: '{PLT_SERVER_LOGGER_LEVEL}'
|
|
38
29
|
},
|
|
39
30
|
managementApi: '{PLT_MANAGEMENT_API}'
|
|
40
31
|
}
|
|
41
|
-
|
|
42
|
-
if (!skipServer) {
|
|
43
|
-
wrappableProperties.server = {
|
|
44
|
-
hostname: '{PLT_SERVER_HOSTNAME}',
|
|
45
|
-
port: '{PORT}'
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return wrappableProperties
|
|
50
32
|
}
|
|
51
33
|
|
|
52
34
|
function getRuntimeBaseEnvVars (config) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
env.PLT_SERVER_HOSTNAME = '127.0.0.1'
|
|
57
|
-
env.PORT = config.port || 3042
|
|
35
|
+
return {
|
|
36
|
+
PLT_SERVER_LOGGER_LEVEL: config.logLevel || 'info',
|
|
37
|
+
PLT_MANAGEMENT_API: true
|
|
58
38
|
}
|
|
59
|
-
|
|
60
|
-
env.PLT_SERVER_LOGGER_LEVEL = config.logLevel || 'info'
|
|
61
|
-
env.PLT_MANAGEMENT_API = true
|
|
62
|
-
|
|
63
|
-
return env
|
|
64
39
|
}
|
|
65
40
|
|
|
66
41
|
export class RuntimeGenerator extends BaseGenerator {
|
|
@@ -73,7 +48,6 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
73
48
|
this.applicationsFolder = opts.applicationsFolder ?? 'applications'
|
|
74
49
|
this.applications = []
|
|
75
50
|
this.existingApplications = []
|
|
76
|
-
this.entryPoint = null
|
|
77
51
|
this.packageManager = opts.packageManager ?? defaultPackageManager
|
|
78
52
|
}
|
|
79
53
|
|
|
@@ -97,15 +71,6 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
97
71
|
application.setRuntime(this)
|
|
98
72
|
}
|
|
99
73
|
|
|
100
|
-
setEntryPoint (entryPoint) {
|
|
101
|
-
const application =
|
|
102
|
-
this.existingApplications.includes(entryPoint) || this.applications.find(svc => svc.name === entryPoint)
|
|
103
|
-
if (!application) {
|
|
104
|
-
throw new NoApplicationNamedError(entryPoint)
|
|
105
|
-
}
|
|
106
|
-
this.entryPoint = application
|
|
107
|
-
}
|
|
108
|
-
|
|
109
74
|
async generatePackageJson () {
|
|
110
75
|
const template = {
|
|
111
76
|
name: `${this.runtimeName}`,
|
|
@@ -165,8 +130,6 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
165
130
|
|
|
166
131
|
const { PLT_ROOT, ...existingEnvironment } = this.existingConfig[kMetadata].env
|
|
167
132
|
this.config.env = existingEnvironment
|
|
168
|
-
this.config.port = this.config.env.PORT
|
|
169
|
-
this.entryPoint = this.existingConfig.applications.find(svc => svc.entrypoint)
|
|
170
133
|
this.existingApplications = this.existingConfig.applications.map(s => s.id)
|
|
171
134
|
|
|
172
135
|
this.updateRuntimeConfig(this.existingConfigRaw)
|
|
@@ -190,37 +153,42 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
190
153
|
}
|
|
191
154
|
|
|
192
155
|
setApplicationsConfigValues () {
|
|
193
|
-
|
|
156
|
+
let newApplicationOrdinal = 0
|
|
157
|
+
this.applications.forEach(({ name, application }) => {
|
|
194
158
|
if (!application.config) {
|
|
195
159
|
// set default config
|
|
196
160
|
application.setConfig()
|
|
197
161
|
}
|
|
162
|
+
const existingOrdinal = this.existingApplications.indexOf(name)
|
|
163
|
+
const ordinal = existingOrdinal === -1 ? this.existingApplications.length + newApplicationOrdinal++ : existingOrdinal
|
|
164
|
+
this.setApplicationPort(application, ordinal)
|
|
198
165
|
})
|
|
199
166
|
}
|
|
200
167
|
|
|
168
|
+
setApplicationPort (application, ordinal) {
|
|
169
|
+
if (application.config.port === 3042) {
|
|
170
|
+
application.setConfig({ port: 3042 + ordinal })
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
201
174
|
async _getConfigFileContents () {
|
|
202
175
|
const config = {
|
|
203
176
|
$schema: `https://schemas.platformatic.dev/wattpm/${this.platformaticVersion}.json`,
|
|
204
|
-
entrypoint: this.entryPoint.name,
|
|
205
177
|
watch: true,
|
|
206
178
|
autoload: {
|
|
207
179
|
path: this.config.autoload || this.applicationsFolder,
|
|
208
180
|
exclude: ['docs']
|
|
209
181
|
},
|
|
210
|
-
...getRuntimeWrappableProperties(
|
|
182
|
+
...getRuntimeWrappableProperties()
|
|
211
183
|
}
|
|
212
184
|
|
|
213
185
|
return config
|
|
214
186
|
}
|
|
215
187
|
|
|
216
188
|
async _afterPrepare () {
|
|
217
|
-
if (!this.entryPoint) {
|
|
218
|
-
throw new NoEntryPointError()
|
|
219
|
-
}
|
|
220
189
|
const applicationsEnv = await this.prepareApplicationFiles()
|
|
221
190
|
this.addEnvVars({
|
|
222
191
|
...this.config.env,
|
|
223
|
-
...this.getRuntimeEnv(),
|
|
224
192
|
...applicationsEnv
|
|
225
193
|
})
|
|
226
194
|
|
|
@@ -258,21 +226,6 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
258
226
|
|
|
259
227
|
async prepareQuestions () {
|
|
260
228
|
await this.populateFromExistingConfig()
|
|
261
|
-
|
|
262
|
-
if (this.existingConfig) {
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (!this.config.skipServer) {
|
|
267
|
-
// port
|
|
268
|
-
this.questions.push({
|
|
269
|
-
type: 'input',
|
|
270
|
-
name: 'port',
|
|
271
|
-
default: '3042',
|
|
272
|
-
message: 'What port do you want to use?',
|
|
273
|
-
filter: Number
|
|
274
|
-
})
|
|
275
|
-
}
|
|
276
229
|
}
|
|
277
230
|
|
|
278
231
|
setApplicationsDirectory () {
|
|
@@ -322,12 +275,6 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
322
275
|
// do nothing, makes no sense
|
|
323
276
|
}
|
|
324
277
|
|
|
325
|
-
getRuntimeEnv () {
|
|
326
|
-
return {
|
|
327
|
-
PORT: this.config.port
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
278
|
async postInstallActions () {
|
|
332
279
|
for (const { application } of this.applications) {
|
|
333
280
|
await application.postInstallActions()
|
|
@@ -451,6 +398,7 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
451
398
|
})
|
|
452
399
|
}
|
|
453
400
|
applicationInstance.setConfig(baseConfig)
|
|
401
|
+
this.setApplicationPort(applicationInstance, allNewApplicationsNames.indexOf(newApplication.name))
|
|
454
402
|
applicationInstance.setConfigFields(newApplication.fields)
|
|
455
403
|
|
|
456
404
|
const applicationEnvPrefix = `PLT_${applicationInstance.config.envPrefix}`
|
|
@@ -494,18 +442,6 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
494
442
|
contents: JSON.stringify(currrentPackageJson, null, 2)
|
|
495
443
|
})
|
|
496
444
|
|
|
497
|
-
// set new entrypoint if specified
|
|
498
|
-
const newEntrypoint = newConfig.entrypoint
|
|
499
|
-
if (newEntrypoint) {
|
|
500
|
-
// load platformatic.json runtime config
|
|
501
|
-
const runtimePkgConfigFileData = JSON.parse(
|
|
502
|
-
await readFile(join(this.targetDirectory, this.runtimeConfig), 'utf-8')
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
this.setEntryPoint(newEntrypoint)
|
|
506
|
-
runtimePkgConfigFileData.entrypoint = newEntrypoint
|
|
507
|
-
this.updateRuntimeConfig(runtimePkgConfigFileData)
|
|
508
|
-
}
|
|
509
445
|
await this.writeFiles()
|
|
510
446
|
// save new env
|
|
511
447
|
await envTool.save()
|
|
@@ -548,15 +484,6 @@ export class RuntimeGenerator extends BaseGenerator {
|
|
|
548
484
|
tags: ['runtime-env']
|
|
549
485
|
})
|
|
550
486
|
}
|
|
551
|
-
|
|
552
|
-
updateConfigEntryPoint (entrypoint) {
|
|
553
|
-
// This can return null if the generator was not supposed to modify the config
|
|
554
|
-
const configObject = this.getRuntimeConfigFileObject()
|
|
555
|
-
const config = JSON.parse(configObject.contents)
|
|
556
|
-
config.entrypoint = entrypoint
|
|
557
|
-
|
|
558
|
-
this.updateRuntimeConfig(config)
|
|
559
|
-
}
|
|
560
487
|
}
|
|
561
488
|
|
|
562
489
|
export class WrappedGenerator extends BaseGenerator {
|
|
@@ -626,7 +553,7 @@ export class WrappedGenerator extends BaseGenerator {
|
|
|
626
553
|
async #createConfigFile () {
|
|
627
554
|
const config = {
|
|
628
555
|
$schema: `https://schemas.platformatic.dev/${this.module}/${this.platformaticVersion}.json`,
|
|
629
|
-
runtime: getRuntimeWrappableProperties(
|
|
556
|
+
runtime: getRuntimeWrappableProperties()
|
|
630
557
|
}
|
|
631
558
|
|
|
632
559
|
this.addFile({
|
package/lib/management-api.js
CHANGED
|
@@ -14,7 +14,7 @@ import { platform, tmpdir } from 'node:os'
|
|
|
14
14
|
import { dirname, join } from 'node:path'
|
|
15
15
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
16
16
|
import { createWebSocketStream } from 'ws'
|
|
17
|
-
import {
|
|
17
|
+
import { prepareAddedApplications } from './config.js'
|
|
18
18
|
|
|
19
19
|
const PLATFORMATIC_TMP_DIR = join(tmpdir(), 'platformatic', 'runtimes')
|
|
20
20
|
|
|
@@ -169,11 +169,9 @@ export async function managementApiPlugin (app, opts) {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
applications[i] = await prepareApplication(config, applications[i], config.workers)
|
|
174
|
-
}
|
|
172
|
+
const prepared = await prepareAddedApplications(config, applications, runtime.getApplicationsIds())
|
|
175
173
|
|
|
176
|
-
const created = await runtime.addApplications(
|
|
174
|
+
const created = await runtime.addApplications(prepared, request.query.start !== 'false')
|
|
177
175
|
reply.code(201)
|
|
178
176
|
return created
|
|
179
177
|
})
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
kMetadata,
|
|
4
4
|
validate
|
|
5
5
|
} from '@platformatic/foundation'
|
|
6
|
-
import {
|
|
6
|
+
import { prepareAddedApplications } from './config.js'
|
|
7
7
|
|
|
8
8
|
const allOperations = [
|
|
9
9
|
'getRuntimeStatus',
|
|
@@ -138,11 +138,9 @@ export function createManagementHandlers (runtime, allowedOperations) {
|
|
|
138
138
|
|
|
139
139
|
validate(applicationSchema, applications, {}, true, config[kMetadata].root)
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
applications[i] = await prepareApplication(config, applications[i], config.workers)
|
|
143
|
-
}
|
|
141
|
+
const prepared = await prepareAddedApplications(config, applications, runtime.getApplicationsIds())
|
|
144
142
|
|
|
145
|
-
return runtime.addApplications(
|
|
143
|
+
return runtime.addApplications(prepared, start)
|
|
146
144
|
})
|
|
147
145
|
|
|
148
146
|
register('removeApplications', async ({ ids }) => {
|