@platformatic/runtime 4.0.0-new-config.2 → 4.0.0-new-config.6
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 +594 -596
- package/index.d.ts +11 -5
- package/index.js +161 -85
- package/lib/config.js +130 -237
- package/lib/errors.js +12 -0
- package/lib/generator.js +222 -35
- package/lib/logger.js +3 -3
- package/lib/management-api.js +4 -60
- package/lib/policies.js +27 -0
- package/lib/runtime.js +315 -239
- package/lib/schema.js +76 -0
- package/lib/worker/controller.js +54 -49
- package/lib/worker/interceptors.js +34 -34
- package/lib/worker/itc.js +49 -2
- package/lib/worker/main.js +28 -53
- package/lib/worker/symbols.js +1 -1
- package/package.json +18 -18
- package/schema.json +87 -44
package/lib/config.js
CHANGED
|
@@ -1,28 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
extractModuleFromSchemaUrl,
|
|
4
|
-
findConfigurationFile,
|
|
5
|
-
kMetadata,
|
|
6
|
-
loadConfiguration,
|
|
7
|
-
loadConfigurationModule,
|
|
8
|
-
loadModule,
|
|
9
|
-
omitProperties,
|
|
10
|
-
runtimeUnwrappablePropertiesList
|
|
11
|
-
} from '@platformatic/foundation'
|
|
12
|
-
import { readdir, readFile } from 'node:fs/promises'
|
|
13
|
-
import { createRequire } from 'node:module'
|
|
1
|
+
import { createDirectory, kMetadata, loadModule } from '@platformatic/foundation'
|
|
14
2
|
import { loadAdditionalApplications } from '@platformatic/foundation/lib/v4/index.js'
|
|
15
|
-
import {
|
|
3
|
+
import { createRequire, findPackageJSON } from 'node:module'
|
|
4
|
+
import { dirname, resolve as resolvePath } from 'node:path'
|
|
16
5
|
import { fileURLToPath } from 'node:url'
|
|
17
6
|
|
|
18
7
|
import {
|
|
8
|
+
ApplicationsPortsOverlapError,
|
|
19
9
|
InspectAndInspectBrkError,
|
|
20
10
|
InspectorHostError,
|
|
21
11
|
InspectorPortError,
|
|
22
|
-
InvalidArgumentError
|
|
12
|
+
InvalidArgumentError,
|
|
13
|
+
MissingDependencyError
|
|
23
14
|
} from './errors.js'
|
|
24
|
-
import { schema } from './schema.js'
|
|
25
|
-
import { upgrade } from './upgrade.js'
|
|
26
15
|
|
|
27
16
|
// The runtime package's own entry point, which is where the bundled capability copies live.
|
|
28
17
|
const runtimeScopePath = fileURLToPath(new URL('../index.js', import.meta.url))
|
|
@@ -83,6 +72,10 @@ function parseWorkers (config, prefix, defaultWorkers = { static: 1, dynamic: fa
|
|
|
83
72
|
config.workers = {}
|
|
84
73
|
}
|
|
85
74
|
|
|
75
|
+
// What this entry asked for, before the defaults are folded in.
|
|
76
|
+
const declaredMinimum = config.workers.minimum
|
|
77
|
+
const declaredStatic = config.workers.static
|
|
78
|
+
|
|
86
79
|
// Fill missing values from defaults
|
|
87
80
|
for (const key of ['minimum', 'maximum', 'static', 'dynamic']) {
|
|
88
81
|
if (typeof config.workers[key] === 'undefined' && typeof defaultWorkers[key] !== 'undefined') {
|
|
@@ -97,6 +90,16 @@ function parseWorkers (config, prefix, defaultWorkers = { static: 1, dynamic: fa
|
|
|
97
90
|
config.workers.maximum = t
|
|
98
91
|
}
|
|
99
92
|
|
|
93
|
+
/*
|
|
94
|
+
`static` is how many workers the application starts with, and an entry that declared its own
|
|
95
|
+
`minimum` has already said. The runtime-wide default -- which is 1 unless the project set one --
|
|
96
|
+
must not override it: `{ dynamic: true, minimum: 2 }` otherwise starts a single worker and
|
|
97
|
+
leaves the autoscaler to climb to the floor the entry asked for.
|
|
98
|
+
*/
|
|
99
|
+
if (typeof declaredStatic === 'undefined' && typeof declaredMinimum !== 'undefined') {
|
|
100
|
+
config.workers.static = declaredMinimum
|
|
101
|
+
}
|
|
102
|
+
|
|
100
103
|
if (typeof config.workers.static === 'undefined') {
|
|
101
104
|
config.workers.static = config.workers.minimum
|
|
102
105
|
}
|
|
@@ -132,49 +135,6 @@ export function autoDetectPprofCapture (config) {
|
|
|
132
135
|
return config
|
|
133
136
|
}
|
|
134
137
|
|
|
135
|
-
export async function wrapInRuntimeConfig (config, context) {
|
|
136
|
-
let applicationId = 'main'
|
|
137
|
-
try {
|
|
138
|
-
const packageJson = JSON.parse(await readFile(join(config[kMetadata].root, 'package.json'), 'utf-8'))
|
|
139
|
-
applicationId = packageJson?.name ?? 'main'
|
|
140
|
-
|
|
141
|
-
if (applicationId.startsWith('@')) {
|
|
142
|
-
applicationId = applicationId.split('/')[1]
|
|
143
|
-
}
|
|
144
|
-
} catch (err) {
|
|
145
|
-
// on purpose, the package.json might be missing
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const production = context?.isProduction ?? context?.production
|
|
149
|
-
|
|
150
|
-
const runtimeConfig = config.runtime ?? {}
|
|
151
|
-
|
|
152
|
-
// Important: do not change the order of the properties in this object
|
|
153
|
-
/* c8 ignore next */
|
|
154
|
-
const wrapped = {
|
|
155
|
-
$schema: schema.$id,
|
|
156
|
-
watch: !production,
|
|
157
|
-
...omitProperties(runtimeConfig, runtimeUnwrappablePropertiesList),
|
|
158
|
-
applications: [
|
|
159
|
-
{
|
|
160
|
-
id: applicationId,
|
|
161
|
-
path: config[kMetadata].root,
|
|
162
|
-
config: config[kMetadata].path,
|
|
163
|
-
...(runtimeConfig.application ?? {})
|
|
164
|
-
}
|
|
165
|
-
]
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return loadConfiguration(wrapped, context?.schema ?? schema, {
|
|
169
|
-
validationOptions,
|
|
170
|
-
transform,
|
|
171
|
-
upgrade,
|
|
172
|
-
replaceEnv: true,
|
|
173
|
-
root: config[kMetadata].root,
|
|
174
|
-
...context
|
|
175
|
-
})
|
|
176
|
-
}
|
|
177
|
-
|
|
178
138
|
export function parseInspectorOptions (config, inspect, inspectBreak) {
|
|
179
139
|
const hasInspect = inspect != null
|
|
180
140
|
const hasInspectBrk = inspectBreak != null
|
|
@@ -229,16 +189,6 @@ export function parseInspectorOptions (config, inspect, inspectBreak) {
|
|
|
229
189
|
export async function prepareAddedApplications (config, entries, existingIds = []) {
|
|
230
190
|
const metadata = config[kMetadata]
|
|
231
191
|
|
|
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
192
|
const { applications } = await loadAdditionalApplications({
|
|
243
193
|
configPath: metadata.path,
|
|
244
194
|
entries,
|
|
@@ -247,6 +197,8 @@ export async function prepareAddedApplications (config, entries, existingIds = [
|
|
|
247
197
|
command: metadata.v4.command,
|
|
248
198
|
mode: metadata.v4.mode,
|
|
249
199
|
production: metadata.v4.production,
|
|
200
|
+
realEnv: metadata.v4.realEnv,
|
|
201
|
+
customEnvFile: metadata.v4.customEnvFile,
|
|
250
202
|
// The runtime is the fallback scope for capability resolution, the same as at boot.
|
|
251
203
|
runtimeScope: runtimeScopePath
|
|
252
204
|
})
|
|
@@ -260,58 +212,98 @@ export async function prepareAddedApplications (config, entries, existingIds = [
|
|
|
260
212
|
return prepared
|
|
261
213
|
}
|
|
262
214
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
215
|
+
const hostnameWildcards = new Set(['0.0.0.0', '::', '[::]'])
|
|
216
|
+
|
|
217
|
+
// The listener an application declares in its own configuration, when it can be determined without
|
|
218
|
+
// starting it. A port coming from an environment variable which was not replaced, an ephemeral port
|
|
219
|
+
// and a missing server block all yield null, in which case only the start time check applies.
|
|
220
|
+
function declaredListener (applicationConfig) {
|
|
221
|
+
const server = applicationConfig?.server
|
|
222
|
+
|
|
223
|
+
if (!server) {
|
|
224
|
+
return null
|
|
268
225
|
}
|
|
269
226
|
|
|
270
|
-
|
|
271
|
-
|
|
227
|
+
const port = Number(server.port)
|
|
228
|
+
|
|
229
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
230
|
+
return null
|
|
272
231
|
}
|
|
273
232
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
// Attempting to detect capability here would cause slow glob operations on the cwd
|
|
277
|
-
if (application.url && !application.path) {
|
|
278
|
-
application.type = 'unknown'
|
|
279
|
-
} else {
|
|
280
|
-
try {
|
|
281
|
-
let pkg
|
|
233
|
+
return { port, hostname: server.hostname, perWorker: server.portAssignment === 'perWorkerIncrement' }
|
|
234
|
+
}
|
|
282
235
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
236
|
+
// Two declared listeners can only be compared when both hostnames are known. When a hostname is
|
|
237
|
+
// omitted the capability picks its own default, so unless the other side is a wildcard - which
|
|
238
|
+
// overlaps with anything - the comparison is left to the start time check.
|
|
239
|
+
function declaredListenersOverlap (first, second) {
|
|
240
|
+
const hostname = first.hostname?.toLowerCase()
|
|
241
|
+
const otherHostname = second.hostname?.toLowerCase()
|
|
286
242
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
pkg = capability
|
|
243
|
+
// A wildcard overlaps with any other hostname, known or not
|
|
244
|
+
if (hostnameWildcards.has(hostname) || hostnameWildcards.has(otherHostname)) {
|
|
245
|
+
return true
|
|
246
|
+
}
|
|
292
247
|
|
|
293
|
-
|
|
294
|
-
|
|
248
|
+
// Both applications rely on the same capability default, so they do collide
|
|
249
|
+
if (typeof hostname === 'undefined' && typeof otherHostname === 'undefined') {
|
|
250
|
+
return true
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Only one of the two defaults is unknown, so nothing can be concluded here
|
|
254
|
+
if (typeof hostname === 'undefined' || typeof otherHostname === 'undefined') {
|
|
255
|
+
return false
|
|
256
|
+
}
|
|
295
257
|
|
|
296
|
-
|
|
258
|
+
return hostname === otherHostname
|
|
259
|
+
}
|
|
297
260
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
261
|
+
function describeDeclaredPorts (listener) {
|
|
262
|
+
const { port, last } = listener
|
|
263
|
+
return port === last ? `port ${port}` : `ports ${port}-${last}, one per worker`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Rejects applications whose declared ports overlap before any of them is started, so that the
|
|
267
|
+
// failure does not depend on which worker happens to report its URL first. This is best effort:
|
|
268
|
+
// applications whose port cannot be determined here are checked when their workers start.
|
|
269
|
+
function verifyApplicationsPorts (applications) {
|
|
270
|
+
const listeners = []
|
|
271
|
+
|
|
272
|
+
for (const application of applications) {
|
|
273
|
+
// v4 keeps the validated capability configuration on the application (resolvedConfig), so the
|
|
274
|
+
// declared listener is read from it directly rather than being stashed on a symbol during a
|
|
275
|
+
// separate config-loading pass, as the v3 path did.
|
|
276
|
+
const listener = declaredListener(application.resolvedConfig)
|
|
277
|
+
|
|
278
|
+
// Dynamic scaling changes how many ports the application ends up using, so leave it to the start time check
|
|
279
|
+
if (!listener || application.workers?.dynamic) {
|
|
280
|
+
continue
|
|
311
281
|
}
|
|
282
|
+
|
|
283
|
+
const workers = listener.perWorker ? (application.workers?.static ?? 1) : 1
|
|
284
|
+
listeners.push({ ...listener, id: application.id, last: listener.port + workers - 1 })
|
|
312
285
|
}
|
|
313
286
|
|
|
314
|
-
|
|
287
|
+
for (let i = 0; i < listeners.length; i++) {
|
|
288
|
+
for (let j = i + 1; j < listeners.length; j++) {
|
|
289
|
+
const first = listeners[i]
|
|
290
|
+
const second = listeners[j]
|
|
291
|
+
|
|
292
|
+
const port = Math.max(first.port, second.port)
|
|
293
|
+
|
|
294
|
+
if (port > Math.min(first.last, second.last) || !declaredListenersOverlap(first, second)) {
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
throw new ApplicationsPortsOverlapError(
|
|
299
|
+
first.id,
|
|
300
|
+
describeDeclaredPorts(first),
|
|
301
|
+
second.id,
|
|
302
|
+
describeDeclaredPorts(second),
|
|
303
|
+
port
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
315
307
|
}
|
|
316
308
|
|
|
317
309
|
// Everything an application needs regardless of how its capability was determined. v3 discovers
|
|
@@ -337,7 +329,7 @@ export function finalizeApplication (config, application, defaultWorkers) {
|
|
|
337
329
|
|
|
338
330
|
/*
|
|
339
331
|
The v4 preparation. The loader resolved the path, selected the capability and imported its schema
|
|
340
|
-
subpath — which carries
|
|
332
|
+
subpath — which carries skipTracingHooks and modulesToLoad — all main-side, before any worker
|
|
341
333
|
existed. So there is nothing to discover here, and in particular no application config file to
|
|
342
334
|
re-read: that is the whole point of evaluating configuration exactly once per load.
|
|
343
335
|
*/
|
|
@@ -346,14 +338,37 @@ export async function prepareV4Application (config, application, defaultWorkers)
|
|
|
346
338
|
// capability payload as data and never re-reads a file.
|
|
347
339
|
application.resolvedConfig = application.config ?? {}
|
|
348
340
|
application.config = undefined
|
|
341
|
+
|
|
342
|
+
/*
|
|
343
|
+
A module application names an npm package as its capability instead of describing one in a file.
|
|
344
|
+
The package is resolved from the runtime root, and its own directory (sourcePath) is kept
|
|
345
|
+
separate from the application's writable root (path) — which the module treats as a working
|
|
346
|
+
directory and which need not exist yet. Keyed on moduleApplication, not module: a regular
|
|
347
|
+
application also carries a module (its detected capability), and must not be resolved this way.
|
|
348
|
+
*/
|
|
349
|
+
if (application.moduleApplication) {
|
|
350
|
+
if (!application.path) {
|
|
351
|
+
throw new InvalidArgumentError(`Application "${application.id}" must define path when module is set`)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
application.sourcePath = dirname(findPackageJSON(application.module, resolvePath(config[kMetadata].root, 'noop.js')))
|
|
356
|
+
} catch (error) {
|
|
357
|
+
throw new MissingDependencyError(application.module, { cause: error })
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
application.moduleRoot = config[kMetadata].root
|
|
361
|
+
await createDirectory(application.path)
|
|
362
|
+
}
|
|
363
|
+
|
|
349
364
|
application.type = application.module ?? 'unknown'
|
|
350
|
-
application.
|
|
365
|
+
application.skipTracingHooks = application.capabilityMetadata?.skipTracingHooks ?? false
|
|
351
366
|
|
|
352
367
|
// This is needed to work around a Rust bug on dylibs, as in the v3 path.
|
|
353
368
|
const modulesToLoad = application.capabilityMetadata?.modulesToLoad ?? []
|
|
354
369
|
|
|
355
370
|
if (modulesToLoad.length > 0 && application.path) {
|
|
356
|
-
const _require = createRequire(application.path)
|
|
371
|
+
const _require = createRequire(application.sourcePath ?? application.path)
|
|
357
372
|
|
|
358
373
|
for (const m of modulesToLoad) {
|
|
359
374
|
try {
|
|
@@ -368,130 +383,6 @@ export async function prepareV4Application (config, application, defaultWorkers)
|
|
|
368
383
|
return finalizeApplication(config, application, defaultWorkers)
|
|
369
384
|
}
|
|
370
385
|
|
|
371
|
-
function isApplicationEnabled (application, environment) {
|
|
372
|
-
const { enabled } = application
|
|
373
|
-
|
|
374
|
-
if (typeof enabled === 'undefined') {
|
|
375
|
-
return true
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (typeof enabled === 'string') {
|
|
379
|
-
return enabled !== 'false'
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (typeof enabled === 'object' && enabled !== null) {
|
|
383
|
-
return enabled[environment] ?? true
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return enabled
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export async function transform (config, _, context) {
|
|
390
|
-
const production = context?.isProduction ?? context?.production
|
|
391
|
-
const environment = production ? 'production' : 'development'
|
|
392
|
-
const applications = [...(config.applications ?? []), ...(config.services ?? []), ...(config.web ?? [])]
|
|
393
|
-
|
|
394
|
-
const watchType = typeof config.watch
|
|
395
|
-
if (watchType === 'string') {
|
|
396
|
-
config.watch = config.watch === 'true'
|
|
397
|
-
} else if (watchType === 'undefined') {
|
|
398
|
-
config.watch = !production
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Migrate the old verticalScaler property, only applied if the new settings are not set, otherwise workers takes precedence
|
|
402
|
-
// TODO: Remove in the next major version
|
|
403
|
-
if (config.verticalScaler) {
|
|
404
|
-
config.workers ??= {}
|
|
405
|
-
config.workers.total ??= config.verticalScaler.maxTotalWorkers
|
|
406
|
-
config.workers.dynamic ??= config.verticalScaler.enabled
|
|
407
|
-
config.workers.minimum ??= config.verticalScaler.minWorkers ?? 1
|
|
408
|
-
config.workers.maximum ??= config.verticalScaler.maxWorkers ?? config.verticalScaler.maxTotalWorkers ?? 1
|
|
409
|
-
config.workers.maxMemory ??= config.verticalScaler.maxTotalMemory
|
|
410
|
-
|
|
411
|
-
if (config.verticalScaler.cooldownSec) {
|
|
412
|
-
config.workers.cooldown ??= config.verticalScaler.cooldownSec * 1000
|
|
413
|
-
}
|
|
414
|
-
if (config.verticalScaler.gracePeriod) {
|
|
415
|
-
config.workers.gracePeriod ??= config.verticalScaler.gracePeriod
|
|
416
|
-
}
|
|
417
|
-
if (config.verticalScaler.scaleUpELU) {
|
|
418
|
-
config.workers.scaleUpELU ??= config.verticalScaler.scaleUpELU
|
|
419
|
-
}
|
|
420
|
-
if (config.verticalScaler.scaleDownELU) {
|
|
421
|
-
config.workers.scaleDownELU ??= config.verticalScaler.scaleDownELU
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (config.verticalScaler.applications) {
|
|
425
|
-
for (const appId in config.verticalScaler.applications) {
|
|
426
|
-
let appConfig = applications.find((app) => app.id === appId)
|
|
427
|
-
if (!appConfig) {
|
|
428
|
-
appConfig = { id: appId, workers: {} }
|
|
429
|
-
applications.push(appConfig)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const scaleConfig = config.verticalScaler.applications[appId]
|
|
433
|
-
const workersConfig = appConfig.workers ??= {}
|
|
434
|
-
|
|
435
|
-
workersConfig.minimum ??= scaleConfig.minWorkers ?? config.workers.minimum
|
|
436
|
-
workersConfig.maximum ??= scaleConfig.maxWorkers ?? config.workers.maximum
|
|
437
|
-
|
|
438
|
-
if (scaleConfig.scaleUpELU) {
|
|
439
|
-
workersConfig.scaleUpELU ??= scaleConfig.scaleUpELU
|
|
440
|
-
}
|
|
441
|
-
if (scaleConfig.scaleDownELU) {
|
|
442
|
-
workersConfig.scaleDownELU ??= scaleConfig.scaleDownELU
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
config.verticalScaler = undefined
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (config.autoload) {
|
|
451
|
-
const { exclude = [], mappings = {} } = config.autoload
|
|
452
|
-
let { path } = config.autoload
|
|
453
|
-
|
|
454
|
-
path = resolvePath(config[kMetadata].root, path)
|
|
455
|
-
const entries = await readdir(path, { withFileTypes: true })
|
|
456
|
-
|
|
457
|
-
for (let i = 0; i < entries.length; ++i) {
|
|
458
|
-
const entry = entries[i]
|
|
459
|
-
|
|
460
|
-
if (exclude.includes(entry.name) || !entry.isDirectory()) {
|
|
461
|
-
continue
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
const mapping = mappings[entry.name] ?? {}
|
|
465
|
-
const id = mapping.id ?? entry.name
|
|
466
|
-
const entryPath = join(path, entry.name)
|
|
467
|
-
|
|
468
|
-
let config
|
|
469
|
-
const configFilename = mapping.config ?? (await findConfigurationFile(entryPath))
|
|
470
|
-
|
|
471
|
-
if (typeof configFilename === 'string') {
|
|
472
|
-
config = join(entryPath, configFilename)
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const application = { id, config, path: entryPath, ...mapping }
|
|
476
|
-
const existingApplicationId = applications.findIndex(application => application.id === id)
|
|
477
|
-
|
|
478
|
-
if (existingApplicationId !== -1) {
|
|
479
|
-
applications[existingApplicationId] = { ...application, ...applications[existingApplicationId] }
|
|
480
|
-
} else {
|
|
481
|
-
applications.push(application)
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
for (let i = applications.length - 1; i >= 0; --i) {
|
|
487
|
-
if (!isApplicationEnabled(applications[i], environment)) {
|
|
488
|
-
applications.splice(i, 1)
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return finalizeConfiguration(config, applications, context, production, prepareApplication)
|
|
493
|
-
}
|
|
494
|
-
|
|
495
386
|
// The half of the transform the loader does not own: inspector options, worker counts, the
|
|
496
387
|
// per-application preparation, and the runtime-level normalizations. v4 reaches this directly,
|
|
497
388
|
// having already expanded autoload and resolved enabled in the eval worker.
|
|
@@ -507,6 +398,8 @@ export async function finalizeConfiguration (config, applications, context, prod
|
|
|
507
398
|
await prepare(config, applications[i], defaultWorkers)
|
|
508
399
|
}
|
|
509
400
|
|
|
401
|
+
verifyApplicationsPorts(applications)
|
|
402
|
+
|
|
510
403
|
if (typeof config.metrics === 'boolean') {
|
|
511
404
|
config.metrics = {
|
|
512
405
|
enabled: config.metrics,
|
package/lib/errors.js
CHANGED
|
@@ -15,6 +15,10 @@ 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
17
|
)
|
|
18
|
+
export const ApplicationsPortsOverlapError = createError(
|
|
19
|
+
`${ERROR_PREFIX}_APPLICATIONS_PORTS_OVERLAP`,
|
|
20
|
+
'The applications "%s" (%s) and "%s" (%s) are both configured to listen on port %d. Applications cannot share a port: change one of the ports or make them listen on different hostnames.'
|
|
21
|
+
)
|
|
18
22
|
export const WorkerAddressInUseError = createError(
|
|
19
23
|
`${ERROR_PREFIX}_WORKER_EADDR_IN_USE`,
|
|
20
24
|
'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.'
|
|
@@ -103,6 +107,10 @@ export const NoConfigFileFoundError = createError(
|
|
|
103
107
|
"No config file found for application '%s'"
|
|
104
108
|
)
|
|
105
109
|
export const MissingDependencyError = createError(`${ERROR_PREFIX}_MISSING_DEPENDENCY`, 'Missing dependency: "%s"')
|
|
110
|
+
export const InvalidApplicationModuleError = createError(
|
|
111
|
+
`${ERROR_PREFIX}_INVALID_APPLICATION_MODULE`,
|
|
112
|
+
'Application module "%s" must export a create function'
|
|
113
|
+
)
|
|
106
114
|
export const InspectAndInspectBrkError = createError(
|
|
107
115
|
`${ERROR_PREFIX}_INSPECT_AND_INSPECT_BRK`,
|
|
108
116
|
'--inspect and --inspect-brk cannot be used together'
|
|
@@ -232,3 +240,7 @@ export const WorkerInterceptorJoinTimeoutError = createError(
|
|
|
232
240
|
`${ERROR_PREFIX}_WORKER_INTERCEPTOR_JOIN_TIMEOUT`,
|
|
233
241
|
'The %s failed to join the mesh network in %dms.'
|
|
234
242
|
)
|
|
243
|
+
export const MixedServingStateError = createError(
|
|
244
|
+
`${ERROR_PREFIX}_MIXED_SERVING_STATE`,
|
|
245
|
+
'The workers of the application "%s" disagree about how it serves: %s. One application is one thing; workers that disagree about whether it serves HTTP would have the mesh route a share of requests to a worker that destroys them.'
|
|
246
|
+
)
|