@platformatic/runtime 3.8.0 → 3.10.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 +32 -1
- package/lib/config.js +15 -9
- package/lib/management-api.js +2 -1
- package/lib/runtime.js +146 -26
- package/lib/scaling-algorithm.js +28 -3
- package/lib/worker/controller.js +49 -24
- package/lib/worker/itc.js +2 -2
- package/lib/worker/main.js +24 -57
- package/lib/worker/symbols.js +1 -0
- package/package.json +16 -15
- package/schema.json +180 -13
package/config.d.ts
CHANGED
|
@@ -31,10 +31,39 @@ export type PlatformaticRuntimeConfig = {
|
|
|
31
31
|
maxHeapTotal?: number | string;
|
|
32
32
|
maxYoungGeneration?: number | string;
|
|
33
33
|
};
|
|
34
|
-
preload?: string | string[];
|
|
35
34
|
dependencies?: string[];
|
|
36
35
|
arguments?: string[];
|
|
36
|
+
env?: {
|
|
37
|
+
[k: string]: string;
|
|
38
|
+
};
|
|
39
|
+
envfile?: string;
|
|
40
|
+
sourceMaps?: boolean;
|
|
41
|
+
packageManager?: "npm" | "pnpm" | "yarn";
|
|
42
|
+
preload?: string | string[];
|
|
37
43
|
nodeOptions?: string;
|
|
44
|
+
permissions?: {
|
|
45
|
+
fs?: {
|
|
46
|
+
read?: string[];
|
|
47
|
+
write?: string[];
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
telemetry?: {
|
|
51
|
+
/**
|
|
52
|
+
* An array of instrumentations loaded if telemetry is enabled
|
|
53
|
+
*/
|
|
54
|
+
instrumentations?: (
|
|
55
|
+
| string
|
|
56
|
+
| {
|
|
57
|
+
package: string;
|
|
58
|
+
exportName?: string;
|
|
59
|
+
options?: {
|
|
60
|
+
[k: string]: unknown;
|
|
61
|
+
};
|
|
62
|
+
[k: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
)[];
|
|
65
|
+
[k: string]: unknown;
|
|
66
|
+
};
|
|
38
67
|
};
|
|
39
68
|
};
|
|
40
69
|
};
|
|
@@ -255,6 +284,7 @@ export type PlatformaticRuntimeConfig = {
|
|
|
255
284
|
};
|
|
256
285
|
};
|
|
257
286
|
plugins?: string[];
|
|
287
|
+
timeout?: number | string;
|
|
258
288
|
};
|
|
259
289
|
telemetry?: {
|
|
260
290
|
enabled?: boolean | string;
|
|
@@ -343,6 +373,7 @@ export type PlatformaticRuntimeConfig = {
|
|
|
343
373
|
timeWindowSec?: number;
|
|
344
374
|
cooldownSec?: number;
|
|
345
375
|
scaleIntervalSec?: number;
|
|
376
|
+
gracePeriod?: number;
|
|
346
377
|
applications?: {
|
|
347
378
|
[k: string]: {
|
|
348
379
|
minWorkers?: number;
|
package/lib/config.js
CHANGED
|
@@ -45,7 +45,7 @@ function raiseInvalidWorkersError (location, received, hint) {
|
|
|
45
45
|
throw new InvalidArgumentError(`${location} workers must be a positive integer; received "${received}"${extra}`)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export function
|
|
48
|
+
export function pprofCapturePreloadPath () {
|
|
49
49
|
const require = createRequire(import.meta.url)
|
|
50
50
|
|
|
51
51
|
let pprofCapturePath
|
|
@@ -55,6 +55,12 @@ export function autoDetectPprofCapture (config) {
|
|
|
55
55
|
// No-op
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
return pprofCapturePath
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function autoDetectPprofCapture (config) {
|
|
62
|
+
const pprofCapturePath = pprofCapturePreloadPath()
|
|
63
|
+
|
|
58
64
|
// Add to preload if not already present
|
|
59
65
|
if (!config.preload) {
|
|
60
66
|
config.preload = []
|
|
@@ -192,14 +198,7 @@ export async function transform (config, _, context) {
|
|
|
192
198
|
config = join(entryPath, configFilename)
|
|
193
199
|
}
|
|
194
200
|
|
|
195
|
-
const application = {
|
|
196
|
-
id,
|
|
197
|
-
config,
|
|
198
|
-
path: entryPath,
|
|
199
|
-
useHttp: !!mapping.useHttp,
|
|
200
|
-
health: mapping.health,
|
|
201
|
-
dependencies: mapping.dependencies
|
|
202
|
-
}
|
|
201
|
+
const application = { id, config, path: entryPath, ...mapping }
|
|
203
202
|
const existingApplicationId = applications.findIndex(application => application.id === id)
|
|
204
203
|
|
|
205
204
|
if (existingApplicationId !== -1) {
|
|
@@ -336,6 +335,13 @@ export async function transform (config, _, context) {
|
|
|
336
335
|
// like adding other applications.
|
|
337
336
|
}
|
|
338
337
|
|
|
338
|
+
if (config.metrics === true) {
|
|
339
|
+
config.metrics = {
|
|
340
|
+
enabled: true,
|
|
341
|
+
timeout: 1000
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
339
345
|
config.applications = applications
|
|
340
346
|
config.web = undefined
|
|
341
347
|
config.services = undefined
|
package/lib/management-api.js
CHANGED
|
@@ -126,7 +126,8 @@ export async function managementApiPlugin (app, opts) {
|
|
|
126
126
|
const { id } = request.params
|
|
127
127
|
app.log.debug('stop profiling', { id })
|
|
128
128
|
|
|
129
|
-
const
|
|
129
|
+
const options = request.body || {}
|
|
130
|
+
const profileData = await runtime.stopApplicationProfiling(id, options)
|
|
130
131
|
reply.type('application/octet-stream').code(200).send(profileData)
|
|
131
132
|
})
|
|
132
133
|
|
package/lib/runtime.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
kTimeout,
|
|
10
10
|
parseMemorySize
|
|
11
11
|
} from '@platformatic/foundation'
|
|
12
|
-
import os from 'node:os'
|
|
13
12
|
import { ITC } from '@platformatic/itc'
|
|
14
13
|
import fastify from 'fastify'
|
|
15
14
|
import { EventEmitter, once } from 'node:events'
|
|
@@ -17,13 +16,15 @@ import { existsSync } from 'node:fs'
|
|
|
17
16
|
import { readFile } from 'node:fs/promises'
|
|
18
17
|
import { STATUS_CODES } from 'node:http'
|
|
19
18
|
import { createRequire } from 'node:module'
|
|
20
|
-
import
|
|
19
|
+
import os from 'node:os'
|
|
20
|
+
import { dirname, isAbsolute, join } from 'node:path'
|
|
21
21
|
import { setImmediate as immediate, setTimeout as sleep } from 'node:timers/promises'
|
|
22
22
|
import { pathToFileURL } from 'node:url'
|
|
23
23
|
import { Worker } from 'node:worker_threads'
|
|
24
24
|
import SonicBoom from 'sonic-boom'
|
|
25
25
|
import { Agent, request, interceptors as undiciInterceptors } from 'undici'
|
|
26
26
|
import { createThreadInterceptor } from 'undici-thread-interceptor'
|
|
27
|
+
import { pprofCapturePreloadPath } from './config.js'
|
|
27
28
|
import {
|
|
28
29
|
ApplicationAlreadyStartedError,
|
|
29
30
|
ApplicationNotFoundError,
|
|
@@ -40,12 +41,12 @@ import {
|
|
|
40
41
|
import { abstractLogger, createLogger } from './logger.js'
|
|
41
42
|
import { startManagementApi } from './management-api.js'
|
|
42
43
|
import { startPrometheusServer } from './prom-server.js'
|
|
44
|
+
import ScalingAlgorithm from './scaling-algorithm.js'
|
|
43
45
|
import { startScheduler } from './scheduler.js'
|
|
44
46
|
import { createSharedStore } from './shared-http-cache.js'
|
|
45
47
|
import { version } from './version.js'
|
|
46
48
|
import { sendViaITC, waitEventFromITC } from './worker/itc.js'
|
|
47
49
|
import { RoundRobinMap } from './worker/round-robin-map.js'
|
|
48
|
-
import ScalingAlgorithm from './scaling-algorithm.js'
|
|
49
50
|
import {
|
|
50
51
|
kApplicationId,
|
|
51
52
|
kConfig,
|
|
@@ -57,7 +58,8 @@ import {
|
|
|
57
58
|
kStderrMarker,
|
|
58
59
|
kWorkerId,
|
|
59
60
|
kWorkersBroadcast,
|
|
60
|
-
kWorkerStatus
|
|
61
|
+
kWorkerStatus,
|
|
62
|
+
kWorkerStartTime
|
|
61
63
|
} from './worker/symbols.js'
|
|
62
64
|
|
|
63
65
|
const kWorkerFile = join(import.meta.dirname, 'worker/main.js')
|
|
@@ -277,7 +279,7 @@ export class Runtime extends EventEmitter {
|
|
|
277
279
|
}
|
|
278
280
|
|
|
279
281
|
if (this.#config.verticalScaler?.enabled) {
|
|
280
|
-
this.#setupVerticalScaler()
|
|
282
|
+
await this.#setupVerticalScaler()
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
this.#showUrl()
|
|
@@ -562,11 +564,11 @@ export class Runtime extends EventEmitter {
|
|
|
562
564
|
return sendViaITC(service, 'startProfiling', options)
|
|
563
565
|
}
|
|
564
566
|
|
|
565
|
-
async stopApplicationProfiling (id, ensureStarted = true) {
|
|
567
|
+
async stopApplicationProfiling (id, options = {}, ensureStarted = true) {
|
|
566
568
|
const service = await this.#getApplicationById(id, ensureStarted)
|
|
567
569
|
this.#validatePprofCapturePreload()
|
|
568
570
|
|
|
569
|
-
return sendViaITC(service, 'stopProfiling')
|
|
571
|
+
return sendViaITC(service, 'stopProfiling', options)
|
|
570
572
|
}
|
|
571
573
|
|
|
572
574
|
async updateUndiciInterceptors (undiciConfig) {
|
|
@@ -891,8 +893,12 @@ export class Runtime extends EventEmitter {
|
|
|
891
893
|
continue
|
|
892
894
|
}
|
|
893
895
|
|
|
894
|
-
const applicationMetrics = await
|
|
895
|
-
|
|
896
|
+
const applicationMetrics = await executeWithTimeout(
|
|
897
|
+
sendViaITC(worker, 'getMetrics', format),
|
|
898
|
+
this.#config.metrics?.timeout ?? 10000
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
if (applicationMetrics && applicationMetrics !== kTimeout) {
|
|
896
902
|
if (metrics === null) {
|
|
897
903
|
metrics = format === 'json' ? [] : ''
|
|
898
904
|
}
|
|
@@ -1315,6 +1321,17 @@ export class Runtime extends EventEmitter {
|
|
|
1315
1321
|
execArgv.push('--enable-source-maps')
|
|
1316
1322
|
}
|
|
1317
1323
|
|
|
1324
|
+
if (applicationConfig.permissions?.fs) {
|
|
1325
|
+
execArgv.push(...this.#setupPermissions(applicationConfig))
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
let preload = config.preload
|
|
1329
|
+
if (execArgv.includes('--permission')) {
|
|
1330
|
+
// Remove wattpm-pprof-capture from preload since it is not supported
|
|
1331
|
+
const pprofCapturePath = pprofCapturePreloadPath()
|
|
1332
|
+
preload = preload.filter(p => p !== pprofCapturePath)
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1318
1335
|
const workerEnv = structuredClone(this.#env)
|
|
1319
1336
|
|
|
1320
1337
|
if (applicationConfig.nodeOptions?.trim().length > 0) {
|
|
@@ -1337,7 +1354,10 @@ export class Runtime extends EventEmitter {
|
|
|
1337
1354
|
|
|
1338
1355
|
const worker = new Worker(kWorkerFile, {
|
|
1339
1356
|
workerData: {
|
|
1340
|
-
config
|
|
1357
|
+
config: {
|
|
1358
|
+
...config,
|
|
1359
|
+
preload
|
|
1360
|
+
},
|
|
1341
1361
|
applicationConfig: {
|
|
1342
1362
|
...applicationConfig,
|
|
1343
1363
|
isProduction: this.#isProduction,
|
|
@@ -1642,6 +1662,8 @@ export class Runtime extends EventEmitter {
|
|
|
1642
1662
|
}
|
|
1643
1663
|
|
|
1644
1664
|
worker[kWorkerStatus] = 'started'
|
|
1665
|
+
worker[kWorkerStartTime] = Date.now()
|
|
1666
|
+
|
|
1645
1667
|
this.emitAndNotify('application:worker:started', eventPayload)
|
|
1646
1668
|
this.#broadcastWorkers()
|
|
1647
1669
|
|
|
@@ -2441,9 +2463,14 @@ export class Runtime extends EventEmitter {
|
|
|
2441
2463
|
}
|
|
2442
2464
|
}
|
|
2443
2465
|
|
|
2444
|
-
#setupVerticalScaler () {
|
|
2445
|
-
const
|
|
2446
|
-
if (
|
|
2466
|
+
async #setupVerticalScaler () {
|
|
2467
|
+
const fixedWorkersCount = this.#config.workers
|
|
2468
|
+
if (fixedWorkersCount !== undefined) {
|
|
2469
|
+
this.logger.warn(
|
|
2470
|
+
`Vertical scaler disabled because the "workers" configuration is set to ${fixedWorkersCount}`
|
|
2471
|
+
)
|
|
2472
|
+
return
|
|
2473
|
+
}
|
|
2447
2474
|
|
|
2448
2475
|
const scalerConfig = this.#config.verticalScaler
|
|
2449
2476
|
|
|
@@ -2456,6 +2483,7 @@ export class Runtime extends EventEmitter {
|
|
|
2456
2483
|
scalerConfig.minELUDiff ??= 0.2
|
|
2457
2484
|
scalerConfig.scaleIntervalSec ??= 60
|
|
2458
2485
|
scalerConfig.timeWindowSec ??= 60
|
|
2486
|
+
scalerConfig.gracePeriod ??= 30 * 1000
|
|
2459
2487
|
scalerConfig.applications ??= {}
|
|
2460
2488
|
|
|
2461
2489
|
const maxTotalWorkers = scalerConfig.maxTotalWorkers
|
|
@@ -2468,9 +2496,18 @@ export class Runtime extends EventEmitter {
|
|
|
2468
2496
|
const scaleIntervalSec = scalerConfig.scaleIntervalSec
|
|
2469
2497
|
const timeWindowSec = scalerConfig.timeWindowSec
|
|
2470
2498
|
const applicationsConfigs = scalerConfig.applications
|
|
2499
|
+
const gracePeriod = scalerConfig.gracePeriod
|
|
2500
|
+
const healthCheckInterval = 1000
|
|
2501
|
+
|
|
2502
|
+
const initialResourcesUpdates = []
|
|
2471
2503
|
|
|
2472
2504
|
for (const application of this.#config.applications) {
|
|
2473
2505
|
if (application.entrypoint && !features.node.reusePort) {
|
|
2506
|
+
this.logger.warn(
|
|
2507
|
+
`The "${application.id}" application cannot be scaled because it is an entrypoint` +
|
|
2508
|
+
' and the "reusePort" feature is not available in your OS.'
|
|
2509
|
+
)
|
|
2510
|
+
|
|
2474
2511
|
applicationsConfigs[application.id] = {
|
|
2475
2512
|
minWorkers: 1,
|
|
2476
2513
|
maxWorkers: 1
|
|
@@ -2478,6 +2515,10 @@ export class Runtime extends EventEmitter {
|
|
|
2478
2515
|
continue
|
|
2479
2516
|
}
|
|
2480
2517
|
if (application.workers !== undefined) {
|
|
2518
|
+
this.logger.warn(
|
|
2519
|
+
`The "${application.id}" application cannot be scaled because` +
|
|
2520
|
+
` it has a fixed number of workers (${application.workers}).`
|
|
2521
|
+
)
|
|
2481
2522
|
applicationsConfigs[application.id] = {
|
|
2482
2523
|
minWorkers: application.workers,
|
|
2483
2524
|
maxWorkers: application.workers
|
|
@@ -2488,12 +2529,22 @@ export class Runtime extends EventEmitter {
|
|
|
2488
2529
|
applicationsConfigs[application.id] ??= {}
|
|
2489
2530
|
applicationsConfigs[application.id].minWorkers ??= minWorkers
|
|
2490
2531
|
applicationsConfigs[application.id].maxWorkers ??= maxWorkers
|
|
2532
|
+
|
|
2533
|
+
const appMinWorkers = applicationsConfigs[application.id].minWorkers
|
|
2534
|
+
if (appMinWorkers > 1) {
|
|
2535
|
+
initialResourcesUpdates.push({
|
|
2536
|
+
application: application.id,
|
|
2537
|
+
workers: minWorkers
|
|
2538
|
+
})
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
if (initialResourcesUpdates.length > 0) {
|
|
2543
|
+
await this.updateApplicationsResources(initialResourcesUpdates)
|
|
2491
2544
|
}
|
|
2492
2545
|
|
|
2493
2546
|
for (const applicationId in applicationsConfigs) {
|
|
2494
|
-
const application = this.#config.applications.find(
|
|
2495
|
-
app => app.id === applicationId
|
|
2496
|
-
)
|
|
2547
|
+
const application = this.#config.applications.find(app => app.id === applicationId)
|
|
2497
2548
|
if (!application) {
|
|
2498
2549
|
delete applicationsConfigs[applicationId]
|
|
2499
2550
|
|
|
@@ -2512,18 +2563,43 @@ export class Runtime extends EventEmitter {
|
|
|
2512
2563
|
applications: applicationsConfigs
|
|
2513
2564
|
})
|
|
2514
2565
|
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
}
|
|
2566
|
+
const healthCheckTimeout = setTimeout(async () => {
|
|
2567
|
+
let shouldCheckForScaling = false
|
|
2568
|
+
|
|
2569
|
+
const now = Date.now()
|
|
2520
2570
|
|
|
2521
|
-
|
|
2571
|
+
for (const worker of this.#workers.values()) {
|
|
2572
|
+
if (
|
|
2573
|
+
worker[kWorkerStatus] !== 'started' ||
|
|
2574
|
+
worker[kWorkerStartTime] + gracePeriod > now
|
|
2575
|
+
) {
|
|
2576
|
+
continue
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
try {
|
|
2580
|
+
const health = await this.#getHealth(worker)
|
|
2581
|
+
if (!health) continue
|
|
2582
|
+
|
|
2583
|
+
scalingAlgorithm.addWorkerHealthInfo({
|
|
2584
|
+
workerId: worker[kId],
|
|
2585
|
+
applicationId: worker[kApplicationId],
|
|
2586
|
+
elu: health.elu
|
|
2587
|
+
})
|
|
2522
2588
|
|
|
2523
|
-
|
|
2589
|
+
if (health.elu > scaleUpELU) {
|
|
2590
|
+
shouldCheckForScaling = true
|
|
2591
|
+
}
|
|
2592
|
+
} catch (err) {
|
|
2593
|
+
this.logger.error({ err }, 'Failed to get health for worker')
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
if (shouldCheckForScaling) {
|
|
2524
2598
|
await checkForScaling()
|
|
2525
2599
|
}
|
|
2526
|
-
|
|
2600
|
+
|
|
2601
|
+
healthCheckTimeout.refresh()
|
|
2602
|
+
}, healthCheckInterval).unref()
|
|
2527
2603
|
|
|
2528
2604
|
let isScaling = false
|
|
2529
2605
|
let lastScaling = 0
|
|
@@ -2548,16 +2624,16 @@ export class Runtime extends EventEmitter {
|
|
|
2548
2624
|
const recommendations = scalingAlgorithm.getRecommendations(appsWorkersInfo)
|
|
2549
2625
|
if (recommendations.length > 0) {
|
|
2550
2626
|
await applyRecommendations(recommendations)
|
|
2627
|
+
lastScaling = Date.now()
|
|
2551
2628
|
}
|
|
2552
2629
|
} catch (err) {
|
|
2553
2630
|
this.logger.error({ err }, 'Failed to scale applications')
|
|
2554
2631
|
} finally {
|
|
2555
2632
|
isScaling = false
|
|
2556
|
-
lastScaling = Date.now()
|
|
2557
2633
|
}
|
|
2558
2634
|
}
|
|
2559
2635
|
|
|
2560
|
-
const applyRecommendations = async
|
|
2636
|
+
const applyRecommendations = async recommendations => {
|
|
2561
2637
|
const resourcesUpdates = []
|
|
2562
2638
|
for (const recommendation of recommendations) {
|
|
2563
2639
|
const { applicationId, workersCount, direction } = recommendation
|
|
@@ -2574,4 +2650,48 @@ export class Runtime extends EventEmitter {
|
|
|
2574
2650
|
// Interval for periodic scaling checks
|
|
2575
2651
|
setInterval(checkForScaling, scaleIntervalSec * 1000).unref()
|
|
2576
2652
|
}
|
|
2653
|
+
|
|
2654
|
+
#setupPermissions (applicationConfig) {
|
|
2655
|
+
const argv = []
|
|
2656
|
+
const allows = new Set()
|
|
2657
|
+
const { read, write } = applicationConfig.permissions.fs
|
|
2658
|
+
|
|
2659
|
+
if (read?.length) {
|
|
2660
|
+
for (const p of read) {
|
|
2661
|
+
allows.add(`--allow-fs-read=${isAbsolute(p) ? p : join(applicationConfig.path, p)}`)
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
if (write?.length) {
|
|
2666
|
+
for (const p of write) {
|
|
2667
|
+
allows.add(`--allow-fs-write=${isAbsolute(p) ? p : join(applicationConfig.path, p)}`)
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
if (allows.size === 0) {
|
|
2672
|
+
return argv
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
// We need to allow read access to the node_modules folder both at the runtime level and at the application level
|
|
2676
|
+
allows.add(`--allow-fs-read=${join(this.#root, 'node_modules', '*')}`)
|
|
2677
|
+
allows.add(`--allow-fs-read=${join(applicationConfig.path, 'node_modules', '*')}`)
|
|
2678
|
+
|
|
2679
|
+
// Since we can't really predict how dependencies are installed (symlinks, pnpm store, and so forth), we also
|
|
2680
|
+
// add any node_modules folder found in the ancestors of the current file
|
|
2681
|
+
let lastPath = import.meta.dirname
|
|
2682
|
+
let currentPath = import.meta.dirname
|
|
2683
|
+
|
|
2684
|
+
do {
|
|
2685
|
+
lastPath = currentPath
|
|
2686
|
+
const nodeModules = join(currentPath, 'node_modules')
|
|
2687
|
+
if (existsSync(nodeModules)) {
|
|
2688
|
+
allows.add(`--allow-fs-read=${join(nodeModules, '*')}`)
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
currentPath = dirname(currentPath)
|
|
2692
|
+
} while (lastPath !== currentPath)
|
|
2693
|
+
|
|
2694
|
+
argv.push('--permission', ...allows)
|
|
2695
|
+
return argv
|
|
2696
|
+
}
|
|
2577
2697
|
}
|
package/lib/scaling-algorithm.js
CHANGED
|
@@ -19,9 +19,7 @@ class ScalingAlgorithm {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
addWorkerHealthInfo (healthInfo) {
|
|
22
|
-
const workerId = healthInfo
|
|
23
|
-
const applicationId = healthInfo.application
|
|
24
|
-
const elu = healthInfo.currentHealth.elu
|
|
22
|
+
const { workerId, applicationId, elu } = healthInfo
|
|
25
23
|
const timestamp = Date.now()
|
|
26
24
|
|
|
27
25
|
if (!this.#appsELUs[applicationId]) {
|
|
@@ -59,6 +57,27 @@ class ScalingAlgorithm {
|
|
|
59
57
|
|
|
60
58
|
for (const { applicationId, elu, workersCount } of appsInfo) {
|
|
61
59
|
const appMinWorkers = this.#appsConfigs[applicationId]?.minWorkers ?? 1
|
|
60
|
+
const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
|
|
61
|
+
|
|
62
|
+
if (workersCount < appMinWorkers) {
|
|
63
|
+
recommendations.push({
|
|
64
|
+
applicationId,
|
|
65
|
+
workersCount: appMinWorkers,
|
|
66
|
+
direction: 'up'
|
|
67
|
+
})
|
|
68
|
+
totalWorkersCount += appMinWorkers - workersCount
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (workersCount > appMaxWorkers) {
|
|
73
|
+
recommendations.push({
|
|
74
|
+
applicationId,
|
|
75
|
+
workersCount: appMaxWorkers,
|
|
76
|
+
direction: 'down'
|
|
77
|
+
})
|
|
78
|
+
totalWorkersCount -= workersCount - appMaxWorkers
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
62
81
|
|
|
63
82
|
if (elu < this.#scaleDownELU && workersCount > appMinWorkers) {
|
|
64
83
|
recommendations.push({
|
|
@@ -75,6 +94,12 @@ class ScalingAlgorithm {
|
|
|
75
94
|
|
|
76
95
|
const { applicationId, workersCount } = scaleUpCandidate
|
|
77
96
|
|
|
97
|
+
const isScaled = recommendations.some(
|
|
98
|
+
r => r.applicationId === applicationId &&
|
|
99
|
+
r.direction === 'up'
|
|
100
|
+
)
|
|
101
|
+
if (isScaled) continue
|
|
102
|
+
|
|
78
103
|
const appMaxWorkers = this.#appsConfigs[applicationId]?.maxWorkers ?? this.#maxTotalWorkers
|
|
79
104
|
if (workersCount >= appMaxWorkers) continue
|
|
80
105
|
|
package/lib/worker/controller.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureLoggableError,
|
|
3
|
+
executeWithTimeout,
|
|
3
4
|
FileWatcher,
|
|
4
5
|
kHandledError,
|
|
5
6
|
listRecognizedConfigurationFiles,
|
|
@@ -24,6 +25,21 @@ function fetchApplicationUrl (application, key) {
|
|
|
24
25
|
return getApplicationUrl(application.id)
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
function handleUnhandled (app, type, err) {
|
|
29
|
+
const label =
|
|
30
|
+
workerData.worker.count > 1
|
|
31
|
+
? `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
|
|
32
|
+
: `application "${workerData.applicationConfig.id}"`
|
|
33
|
+
|
|
34
|
+
globalThis.platformatic.logger.error({ err: ensureLoggableError(err) }, `The ${label} threw an ${type}.`)
|
|
35
|
+
|
|
36
|
+
executeWithTimeout(app?.stop(), 1000)
|
|
37
|
+
.catch()
|
|
38
|
+
.finally(() => {
|
|
39
|
+
process.exit(1)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
export class Controller extends EventEmitter {
|
|
28
44
|
#starting
|
|
29
45
|
#started
|
|
@@ -34,21 +50,13 @@ export class Controller extends EventEmitter {
|
|
|
34
50
|
#context
|
|
35
51
|
#lastELU
|
|
36
52
|
|
|
37
|
-
constructor (
|
|
38
|
-
appConfig,
|
|
39
|
-
workerId,
|
|
40
|
-
telemetryConfig,
|
|
41
|
-
loggerConfig,
|
|
42
|
-
serverConfig,
|
|
43
|
-
metricsConfig,
|
|
44
|
-
hasManagementApi,
|
|
45
|
-
watch
|
|
46
|
-
) {
|
|
53
|
+
constructor (runtimeConfig, applicationConfig, workerId, serverConfig, metricsConfig) {
|
|
47
54
|
super()
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
55
|
+
this.runtimeConfig = runtimeConfig
|
|
56
|
+
this.applicationConfig = applicationConfig
|
|
57
|
+
this.applicationId = this.applicationConfig.id
|
|
50
58
|
this.workerId = workerId
|
|
51
|
-
this.#watch = watch
|
|
59
|
+
this.#watch = !!runtimeConfig.watch
|
|
52
60
|
this.#starting = false
|
|
53
61
|
this.#started = false
|
|
54
62
|
this.#listening = false
|
|
@@ -60,17 +68,17 @@ export class Controller extends EventEmitter {
|
|
|
60
68
|
controller: this,
|
|
61
69
|
applicationId: this.applicationId,
|
|
62
70
|
workerId: this.workerId,
|
|
63
|
-
directory: this.
|
|
64
|
-
dependencies: this.
|
|
65
|
-
isEntrypoint: this.
|
|
66
|
-
isProduction: this.
|
|
67
|
-
telemetryConfig,
|
|
71
|
+
directory: this.applicationConfig.path,
|
|
72
|
+
dependencies: this.applicationConfig.dependencies,
|
|
73
|
+
isEntrypoint: this.applicationConfig.entrypoint,
|
|
74
|
+
isProduction: this.applicationConfig.isProduction,
|
|
75
|
+
telemetryConfig: this.applicationConfig.telemetry,
|
|
76
|
+
loggerConfig: runtimeConfig.logger,
|
|
68
77
|
metricsConfig,
|
|
69
|
-
loggerConfig,
|
|
70
78
|
serverConfig,
|
|
71
79
|
worker: workerData?.worker,
|
|
72
|
-
hasManagementApi: !!
|
|
73
|
-
fetchApplicationUrl: fetchApplicationUrl.bind(null,
|
|
80
|
+
hasManagementApi: !!runtimeConfig.managementApi,
|
|
81
|
+
fetchApplicationUrl: fetchApplicationUrl.bind(null, applicationConfig)
|
|
74
82
|
}
|
|
75
83
|
}
|
|
76
84
|
|
|
@@ -90,7 +98,7 @@ export class Controller extends EventEmitter {
|
|
|
90
98
|
// Note: capability's init() is executed within start
|
|
91
99
|
async init () {
|
|
92
100
|
try {
|
|
93
|
-
const appConfig = this.
|
|
101
|
+
const appConfig = this.applicationConfig
|
|
94
102
|
|
|
95
103
|
if (appConfig.isProduction && !process.env.NODE_ENV) {
|
|
96
104
|
process.env.NODE_ENV = 'production'
|
|
@@ -120,6 +128,10 @@ export class Controller extends EventEmitter {
|
|
|
120
128
|
}
|
|
121
129
|
|
|
122
130
|
this.#updateDispatcher()
|
|
131
|
+
|
|
132
|
+
if (this.capability.exitOnUnhandledErrors && this.runtimeConfig.exitOnUnhandledErrors) {
|
|
133
|
+
this.#setupHandlers()
|
|
134
|
+
}
|
|
123
135
|
} catch (err) {
|
|
124
136
|
if (err.validationErrors) {
|
|
125
137
|
globalThis.platformatic.logger.error(
|
|
@@ -168,7 +180,7 @@ export class Controller extends EventEmitter {
|
|
|
168
180
|
}
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
const listen = !!this.
|
|
183
|
+
const listen = !!this.applicationConfig.useHttp
|
|
172
184
|
|
|
173
185
|
try {
|
|
174
186
|
await this.capability.start({ listen })
|
|
@@ -203,7 +215,7 @@ export class Controller extends EventEmitter {
|
|
|
203
215
|
|
|
204
216
|
async listen () {
|
|
205
217
|
// This server is not an entrypoint or already listened in start. Behave as no-op.
|
|
206
|
-
if (!this.
|
|
218
|
+
if (!this.applicationConfig.entrypoint || this.applicationConfig.useHttp || this.#listening) {
|
|
207
219
|
return
|
|
208
220
|
}
|
|
209
221
|
|
|
@@ -299,4 +311,17 @@ export class Controller extends EventEmitter {
|
|
|
299
311
|
|
|
300
312
|
setGlobalDispatcher(dispatcher)
|
|
301
313
|
}
|
|
314
|
+
|
|
315
|
+
#setupHandlers () {
|
|
316
|
+
process.on('uncaughtException', handleUnhandled.bind(null, this, 'uncaught exception'))
|
|
317
|
+
process.on('unhandledRejection', handleUnhandled.bind(null, this, 'unhandled rejection'))
|
|
318
|
+
|
|
319
|
+
process.on('newListener', event => {
|
|
320
|
+
if (event === 'uncaughtException' || event === 'unhandledRejection') {
|
|
321
|
+
globalThis.platformatic.logger.warn(
|
|
322
|
+
`A listener has been added for the "process.${event}" event. This listener will be never triggered as Watt default behavior will kill the process before.\n To disable this behavior, set "exitOnUnhandledErrors" to false in the runtime config.`
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
})
|
|
326
|
+
}
|
|
302
327
|
}
|
package/lib/worker/itc.js
CHANGED
|
@@ -71,7 +71,7 @@ export async function waitEventFromITC (worker, event) {
|
|
|
71
71
|
|
|
72
72
|
export function setupITC (controller, application, dispatcher, sharedContext) {
|
|
73
73
|
const logger = globalThis.platformatic.logger
|
|
74
|
-
const messaging = new MessagingITC(controller.
|
|
74
|
+
const messaging = new MessagingITC(controller.applicationConfig.id, workerData.config, logger)
|
|
75
75
|
|
|
76
76
|
Object.assign(globalThis.platformatic ?? {}, {
|
|
77
77
|
messaging: {
|
|
@@ -82,7 +82,7 @@ export function setupITC (controller, application, dispatcher, sharedContext) {
|
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
const itc = new ITC({
|
|
85
|
-
name: controller.
|
|
85
|
+
name: controller.applicationConfig.id + '-worker',
|
|
86
86
|
port: parentPort,
|
|
87
87
|
handlers: {
|
|
88
88
|
async start () {
|
package/lib/worker/main.js
CHANGED
|
@@ -2,8 +2,6 @@ import {
|
|
|
2
2
|
buildPinoFormatters,
|
|
3
3
|
buildPinoTimestamp,
|
|
4
4
|
disablePinoDirectWrite,
|
|
5
|
-
ensureLoggableError,
|
|
6
|
-
executeWithTimeout,
|
|
7
5
|
getPrivateSymbol
|
|
8
6
|
} from '@platformatic/foundation'
|
|
9
7
|
import dotenv from 'dotenv'
|
|
@@ -30,21 +28,6 @@ class ForwardingEventEmitter extends EventEmitter {
|
|
|
30
28
|
}
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
function handleUnhandled (app, type, err) {
|
|
34
|
-
const label =
|
|
35
|
-
workerData.worker.count > 1
|
|
36
|
-
? `worker ${workerData.worker.index} of the application "${workerData.applicationConfig.id}"`
|
|
37
|
-
: `application "${workerData.applicationConfig.id}"`
|
|
38
|
-
|
|
39
|
-
globalThis.platformatic.logger.error({ err: ensureLoggableError(err) }, `The ${label} threw an ${type}.`)
|
|
40
|
-
|
|
41
|
-
executeWithTimeout(app?.stop(), 1000)
|
|
42
|
-
.catch()
|
|
43
|
-
.finally(() => {
|
|
44
|
-
process.exit(1)
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
31
|
function patchLogging () {
|
|
49
32
|
disablePinoDirectWrite()
|
|
50
33
|
|
|
@@ -113,16 +96,16 @@ async function main () {
|
|
|
113
96
|
events: new ForwardingEventEmitter()
|
|
114
97
|
})
|
|
115
98
|
|
|
116
|
-
const
|
|
99
|
+
const runtimeConfig = workerData.config
|
|
117
100
|
|
|
118
|
-
await performPreloading(
|
|
101
|
+
await performPreloading(runtimeConfig, workerData.applicationConfig)
|
|
119
102
|
|
|
120
|
-
const
|
|
103
|
+
const applicationConfig = workerData.applicationConfig
|
|
121
104
|
|
|
122
105
|
// Load env file and mixin env vars from application config
|
|
123
106
|
let envfile
|
|
124
|
-
if (
|
|
125
|
-
envfile = resolve(workerData.dirname,
|
|
107
|
+
if (applicationConfig.envfile) {
|
|
108
|
+
envfile = resolve(workerData.dirname, applicationConfig.envfile)
|
|
126
109
|
} else {
|
|
127
110
|
envfile = resolve(workerData.applicationConfig.path, '.env')
|
|
128
111
|
}
|
|
@@ -133,20 +116,20 @@ async function main () {
|
|
|
133
116
|
path: envfile
|
|
134
117
|
})
|
|
135
118
|
|
|
136
|
-
if (
|
|
137
|
-
Object.assign(process.env,
|
|
119
|
+
if (runtimeConfig.env) {
|
|
120
|
+
Object.assign(process.env, runtimeConfig.env)
|
|
138
121
|
}
|
|
139
|
-
if (
|
|
140
|
-
Object.assign(process.env,
|
|
122
|
+
if (applicationConfig.env) {
|
|
123
|
+
Object.assign(process.env, applicationConfig.env)
|
|
141
124
|
}
|
|
142
125
|
|
|
143
|
-
const { threadDispatcher } = await setDispatcher(
|
|
126
|
+
const { threadDispatcher } = await setDispatcher(runtimeConfig)
|
|
144
127
|
|
|
145
128
|
// If the application is an entrypoint and runtime server config is defined, use it.
|
|
146
129
|
let serverConfig = null
|
|
147
|
-
if (
|
|
148
|
-
serverConfig =
|
|
149
|
-
} else if (
|
|
130
|
+
if (runtimeConfig.server && applicationConfig.entrypoint) {
|
|
131
|
+
serverConfig = runtimeConfig.server
|
|
132
|
+
} else if (applicationConfig.useHttp) {
|
|
150
133
|
serverConfig = {
|
|
151
134
|
port: 0,
|
|
152
135
|
hostname: '127.0.0.1',
|
|
@@ -169,48 +152,32 @@ async function main () {
|
|
|
169
152
|
const res = await fetch(url)
|
|
170
153
|
const [{ devtoolsFrontendUrl }] = await res.json()
|
|
171
154
|
|
|
172
|
-
console.log(`For ${
|
|
155
|
+
console.log(`For ${applicationConfig.id} debugger open the following in chrome: "${devtoolsFrontendUrl}"`)
|
|
173
156
|
}
|
|
174
157
|
|
|
175
158
|
// Create the application
|
|
176
159
|
// Add idLabel to metrics config to determine which label name to use (defaults to applicationId)
|
|
177
|
-
const metricsConfig =
|
|
160
|
+
const metricsConfig = runtimeConfig.metrics
|
|
178
161
|
? {
|
|
179
|
-
...
|
|
180
|
-
idLabel:
|
|
162
|
+
...runtimeConfig.metrics,
|
|
163
|
+
idLabel: runtimeConfig.metrics.applicationLabel || 'applicationId'
|
|
181
164
|
}
|
|
182
|
-
:
|
|
165
|
+
: runtimeConfig.metrics
|
|
183
166
|
|
|
184
167
|
const controller = new Controller(
|
|
185
|
-
|
|
168
|
+
runtimeConfig,
|
|
169
|
+
applicationConfig,
|
|
186
170
|
workerData.worker.count > 1 ? workerData.worker.index : undefined,
|
|
187
|
-
application.telemetry,
|
|
188
|
-
config.logger,
|
|
189
171
|
serverConfig,
|
|
190
|
-
metricsConfig
|
|
191
|
-
!!config.managementApi,
|
|
192
|
-
!!config.watch
|
|
172
|
+
metricsConfig
|
|
193
173
|
)
|
|
194
174
|
|
|
195
|
-
if (config.exitOnUnhandledErrors) {
|
|
196
|
-
process.on('uncaughtException', handleUnhandled.bind(null, controller, 'uncaught exception'))
|
|
197
|
-
process.on('unhandledRejection', handleUnhandled.bind(null, controller, 'unhandled rejection'))
|
|
198
|
-
|
|
199
|
-
process.on('newListener', event => {
|
|
200
|
-
if (event === 'uncaughtException' || event === 'unhandledRejection') {
|
|
201
|
-
globalThis.platformatic.logger.warn(
|
|
202
|
-
`A listener has been added for the "process.${event}" event. This listener will be never triggered as Watt default behavior will kill the process before.\n To disable this behavior, set "exitOnUnhandledErrors" to false in the runtime config.`
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
|
|
208
175
|
await controller.init()
|
|
209
176
|
|
|
210
|
-
if (
|
|
177
|
+
if (applicationConfig.entrypoint && runtimeConfig.basePath) {
|
|
211
178
|
const meta = await controller.capability.getMeta()
|
|
212
179
|
if (!meta.gateway.wantsAbsoluteUrls) {
|
|
213
|
-
stripBasePath(
|
|
180
|
+
stripBasePath(runtimeConfig.basePath)
|
|
214
181
|
}
|
|
215
182
|
}
|
|
216
183
|
|
|
@@ -222,7 +189,7 @@ async function main () {
|
|
|
222
189
|
}
|
|
223
190
|
|
|
224
191
|
// Setup interaction with parent port
|
|
225
|
-
const itc = setupITC(controller,
|
|
192
|
+
const itc = setupITC(controller, applicationConfig, threadDispatcher, sharedContext)
|
|
226
193
|
globalThis[kITC] = itc
|
|
227
194
|
globalThis.platformatic.itc = itc
|
|
228
195
|
|
package/lib/worker/symbols.js
CHANGED
|
@@ -6,6 +6,7 @@ export const kWorkerId = Symbol.for('plt.runtime.worker.id')
|
|
|
6
6
|
export const kITC = Symbol.for('plt.runtime.itc')
|
|
7
7
|
export const kHealthCheckTimer = Symbol.for('plt.runtime.worker.healthCheckTimer')
|
|
8
8
|
export const kWorkerStatus = Symbol('plt.runtime.worker.status')
|
|
9
|
+
export const kWorkerStartTime = Symbol.for('plt.runtime.worker.startTime')
|
|
9
10
|
export const kInterceptors = Symbol.for('plt.runtime.worker.interceptors')
|
|
10
11
|
export const kLastELU = Symbol.for('plt.runtime.worker.lastELU')
|
|
11
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"@fastify/express": "^4.0.0",
|
|
20
20
|
"@fastify/formbody": "^8.0.0",
|
|
21
21
|
"autocannon": "^8.0.0",
|
|
22
|
+
"atomic-sleep": "^1.0.0",
|
|
22
23
|
"c8": "^10.0.0",
|
|
23
24
|
"cleaner-spec-reporter": "^0.5.0",
|
|
24
25
|
"eslint": "9",
|
|
@@ -34,14 +35,14 @@
|
|
|
34
35
|
"typescript": "^5.5.4",
|
|
35
36
|
"undici-oidc-interceptor": "^0.5.0",
|
|
36
37
|
"why-is-node-running": "^2.2.2",
|
|
37
|
-
"@platformatic/composer": "3.
|
|
38
|
-
"@platformatic/db": "3.
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/
|
|
42
|
-
"@platformatic/
|
|
43
|
-
"@platformatic/sql-mapper": "3.
|
|
44
|
-
"@platformatic/wattpm-pprof-capture": "3.
|
|
38
|
+
"@platformatic/composer": "3.10.0",
|
|
39
|
+
"@platformatic/db": "3.10.0",
|
|
40
|
+
"@platformatic/node": "3.10.0",
|
|
41
|
+
"@platformatic/gateway": "3.10.0",
|
|
42
|
+
"@platformatic/service": "3.10.0",
|
|
43
|
+
"@platformatic/sql-graphql": "3.10.0",
|
|
44
|
+
"@platformatic/sql-mapper": "3.10.0",
|
|
45
|
+
"@platformatic/wattpm-pprof-capture": "3.10.0"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
48
|
"@fastify/accepts": "^5.0.0",
|
|
@@ -71,12 +72,12 @@
|
|
|
71
72
|
"undici": "^7.0.0",
|
|
72
73
|
"undici-thread-interceptor": "^0.14.0",
|
|
73
74
|
"ws": "^8.16.0",
|
|
74
|
-
"@platformatic/basic": "3.
|
|
75
|
-
"@platformatic/foundation": "3.
|
|
76
|
-
"@platformatic/itc": "3.
|
|
77
|
-
"@platformatic/
|
|
78
|
-
"@platformatic/
|
|
79
|
-
"@platformatic/telemetry": "3.
|
|
75
|
+
"@platformatic/basic": "3.10.0",
|
|
76
|
+
"@platformatic/foundation": "3.10.0",
|
|
77
|
+
"@platformatic/itc": "3.10.0",
|
|
78
|
+
"@platformatic/generators": "3.10.0",
|
|
79
|
+
"@platformatic/metrics": "3.10.0",
|
|
80
|
+
"@platformatic/telemetry": "3.10.0"
|
|
80
81
|
},
|
|
81
82
|
"engines": {
|
|
82
83
|
"node": ">=22.19.0"
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.10.0.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Runtime Config",
|
|
5
5
|
"type": "object",
|
|
@@ -171,6 +171,40 @@
|
|
|
171
171
|
},
|
|
172
172
|
"additionalProperties": false
|
|
173
173
|
},
|
|
174
|
+
"dependencies": {
|
|
175
|
+
"type": "array",
|
|
176
|
+
"items": {
|
|
177
|
+
"type": "string"
|
|
178
|
+
},
|
|
179
|
+
"default": []
|
|
180
|
+
},
|
|
181
|
+
"arguments": {
|
|
182
|
+
"type": "array",
|
|
183
|
+
"items": {
|
|
184
|
+
"type": "string"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
"env": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"additionalProperties": {
|
|
190
|
+
"type": "string"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"envfile": {
|
|
194
|
+
"type": "string"
|
|
195
|
+
},
|
|
196
|
+
"sourceMaps": {
|
|
197
|
+
"type": "boolean",
|
|
198
|
+
"default": false
|
|
199
|
+
},
|
|
200
|
+
"packageManager": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"enum": [
|
|
203
|
+
"npm",
|
|
204
|
+
"pnpm",
|
|
205
|
+
"yarn"
|
|
206
|
+
]
|
|
207
|
+
},
|
|
174
208
|
"preload": {
|
|
175
209
|
"anyOf": [
|
|
176
210
|
{
|
|
@@ -186,20 +220,66 @@
|
|
|
186
220
|
}
|
|
187
221
|
]
|
|
188
222
|
},
|
|
189
|
-
"dependencies": {
|
|
190
|
-
"type": "array",
|
|
191
|
-
"items": {
|
|
192
|
-
"type": "string"
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
"arguments": {
|
|
196
|
-
"type": "array",
|
|
197
|
-
"items": {
|
|
198
|
-
"type": "string"
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
223
|
"nodeOptions": {
|
|
202
224
|
"type": "string"
|
|
225
|
+
},
|
|
226
|
+
"permissions": {
|
|
227
|
+
"type": "object",
|
|
228
|
+
"properties": {
|
|
229
|
+
"fs": {
|
|
230
|
+
"type": "object",
|
|
231
|
+
"properties": {
|
|
232
|
+
"read": {
|
|
233
|
+
"type": "array",
|
|
234
|
+
"items": {
|
|
235
|
+
"type": "string"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"write": {
|
|
239
|
+
"type": "array",
|
|
240
|
+
"items": {
|
|
241
|
+
"type": "string"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"additionalProperties": false
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"additionalProperties": false
|
|
249
|
+
},
|
|
250
|
+
"telemetry": {
|
|
251
|
+
"type": "object",
|
|
252
|
+
"properties": {
|
|
253
|
+
"instrumentations": {
|
|
254
|
+
"type": "array",
|
|
255
|
+
"description": "An array of instrumentations loaded if telemetry is enabled",
|
|
256
|
+
"items": {
|
|
257
|
+
"oneOf": [
|
|
258
|
+
{
|
|
259
|
+
"type": "string"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"type": "object",
|
|
263
|
+
"properties": {
|
|
264
|
+
"package": {
|
|
265
|
+
"type": "string"
|
|
266
|
+
},
|
|
267
|
+
"exportName": {
|
|
268
|
+
"type": "string"
|
|
269
|
+
},
|
|
270
|
+
"options": {
|
|
271
|
+
"type": "object",
|
|
272
|
+
"additionalProperties": true
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
"required": [
|
|
276
|
+
"package"
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
203
283
|
}
|
|
204
284
|
}
|
|
205
285
|
}
|
|
@@ -405,6 +485,30 @@
|
|
|
405
485
|
"nodeOptions": {
|
|
406
486
|
"type": "string"
|
|
407
487
|
},
|
|
488
|
+
"permissions": {
|
|
489
|
+
"type": "object",
|
|
490
|
+
"properties": {
|
|
491
|
+
"fs": {
|
|
492
|
+
"type": "object",
|
|
493
|
+
"properties": {
|
|
494
|
+
"read": {
|
|
495
|
+
"type": "array",
|
|
496
|
+
"items": {
|
|
497
|
+
"type": "string"
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
"write": {
|
|
501
|
+
"type": "array",
|
|
502
|
+
"items": {
|
|
503
|
+
"type": "string"
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
"additionalProperties": false
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
"additionalProperties": false
|
|
511
|
+
},
|
|
408
512
|
"telemetry": {
|
|
409
513
|
"type": "object",
|
|
410
514
|
"properties": {
|
|
@@ -641,6 +745,30 @@
|
|
|
641
745
|
"nodeOptions": {
|
|
642
746
|
"type": "string"
|
|
643
747
|
},
|
|
748
|
+
"permissions": {
|
|
749
|
+
"type": "object",
|
|
750
|
+
"properties": {
|
|
751
|
+
"fs": {
|
|
752
|
+
"type": "object",
|
|
753
|
+
"properties": {
|
|
754
|
+
"read": {
|
|
755
|
+
"type": "array",
|
|
756
|
+
"items": {
|
|
757
|
+
"type": "string"
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
"write": {
|
|
761
|
+
"type": "array",
|
|
762
|
+
"items": {
|
|
763
|
+
"type": "string"
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
"additionalProperties": false
|
|
768
|
+
}
|
|
769
|
+
},
|
|
770
|
+
"additionalProperties": false
|
|
771
|
+
},
|
|
644
772
|
"telemetry": {
|
|
645
773
|
"type": "object",
|
|
646
774
|
"properties": {
|
|
@@ -877,6 +1005,30 @@
|
|
|
877
1005
|
"nodeOptions": {
|
|
878
1006
|
"type": "string"
|
|
879
1007
|
},
|
|
1008
|
+
"permissions": {
|
|
1009
|
+
"type": "object",
|
|
1010
|
+
"properties": {
|
|
1011
|
+
"fs": {
|
|
1012
|
+
"type": "object",
|
|
1013
|
+
"properties": {
|
|
1014
|
+
"read": {
|
|
1015
|
+
"type": "array",
|
|
1016
|
+
"items": {
|
|
1017
|
+
"type": "string"
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
"write": {
|
|
1021
|
+
"type": "array",
|
|
1022
|
+
"items": {
|
|
1023
|
+
"type": "string"
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
"additionalProperties": false
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
"additionalProperties": false
|
|
1031
|
+
},
|
|
880
1032
|
"telemetry": {
|
|
881
1033
|
"type": "object",
|
|
882
1034
|
"properties": {
|
|
@@ -1691,6 +1843,17 @@
|
|
|
1691
1843
|
}
|
|
1692
1844
|
]
|
|
1693
1845
|
}
|
|
1846
|
+
},
|
|
1847
|
+
"timeout": {
|
|
1848
|
+
"anyOf": [
|
|
1849
|
+
{
|
|
1850
|
+
"type": "integer"
|
|
1851
|
+
},
|
|
1852
|
+
{
|
|
1853
|
+
"type": "string"
|
|
1854
|
+
}
|
|
1855
|
+
],
|
|
1856
|
+
"default": 10000
|
|
1694
1857
|
}
|
|
1695
1858
|
},
|
|
1696
1859
|
"additionalProperties": false
|
|
@@ -1873,6 +2036,10 @@
|
|
|
1873
2036
|
"type": "number",
|
|
1874
2037
|
"minimum": 0
|
|
1875
2038
|
},
|
|
2039
|
+
"gracePeriod": {
|
|
2040
|
+
"type": "number",
|
|
2041
|
+
"minimum": 0
|
|
2042
|
+
},
|
|
1876
2043
|
"applications": {
|
|
1877
2044
|
"type": "object",
|
|
1878
2045
|
"additionalProperties": {
|