@platformatic/runtime 1.22.0 → 1.24.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/fixtures/configs/monorepo-hotreload.json +27 -0
- package/fixtures/configs/monorepo-with-management-api.json +21 -0
- package/fixtures/management-api/package.json +4 -0
- package/fixtures/management-api/platformatic.json +10 -0
- package/fixtures/management-api/services/service-1/platformatic.json +21 -0
- package/fixtures/management-api/services/service-1/plugin.js +8 -0
- package/fixtures/management-api/services/service-2/platformatic.json +13 -0
- package/fixtures/management-api/services/service-2/plugin.js +8 -0
- package/fixtures/monorepo/serviceAppWithLogger/platformatic.service.json +4 -1
- package/lib/api-client.js +23 -1
- package/lib/api.js +41 -8
- package/lib/app.js +14 -1
- package/lib/errors.js +2 -1
- package/lib/generator/runtime-generator.js +1 -1
- package/lib/management-api.js +211 -0
- package/lib/schema.js +9 -0
- package/lib/start.js +31 -5
- package/lib/worker.js +12 -10
- package/package.json +13 -10
- package/runtime.mjs +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.20.0/runtime",
|
|
3
|
+
"entrypoint": "serviceApp",
|
|
4
|
+
"allowCycles": true,
|
|
5
|
+
"hotReload": true,
|
|
6
|
+
"autoload": {
|
|
7
|
+
"path": "../monorepo",
|
|
8
|
+
"exclude": [
|
|
9
|
+
"docs",
|
|
10
|
+
"composerApp"
|
|
11
|
+
],
|
|
12
|
+
"mappings": {
|
|
13
|
+
"serviceAppWithLogger": {
|
|
14
|
+
"id": "with-logger",
|
|
15
|
+
"config": "platformatic.service.json"
|
|
16
|
+
},
|
|
17
|
+
"serviceAppWithMultiplePlugins": {
|
|
18
|
+
"id": "multi-plugin-service",
|
|
19
|
+
"config": "platformatic.service.json"
|
|
20
|
+
},
|
|
21
|
+
"dbApp": {
|
|
22
|
+
"id": "db-app",
|
|
23
|
+
"config": "platformatic.db.json"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v1.21.0/runtime",
|
|
3
|
+
"entrypoint": "serviceApp",
|
|
4
|
+
"allowCycles": true,
|
|
5
|
+
"hotReload": false,
|
|
6
|
+
"autoload": {
|
|
7
|
+
"path": "../monorepo",
|
|
8
|
+
"exclude": ["docs", "composerApp"],
|
|
9
|
+
"mappings": {
|
|
10
|
+
"serviceAppWithLogger": {
|
|
11
|
+
"id": "with-logger",
|
|
12
|
+
"config": "platformatic.service.json"
|
|
13
|
+
},
|
|
14
|
+
"serviceAppWithMultiplePlugins": {
|
|
15
|
+
"id": "multi-plugin-service",
|
|
16
|
+
"config": "platformatic.service.json"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"managementApi": {}
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v1.22.0/service",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "127.0.0.1",
|
|
5
|
+
"port": 0,
|
|
6
|
+
"logger": {
|
|
7
|
+
"name": "service-with-logger",
|
|
8
|
+
"level": "trace"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"service": {
|
|
12
|
+
"openapi": true
|
|
13
|
+
},
|
|
14
|
+
"plugins": {
|
|
15
|
+
"paths": ["plugin.js"]
|
|
16
|
+
},
|
|
17
|
+
"watch": true,
|
|
18
|
+
"metrics": {
|
|
19
|
+
"server": "parent"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/lib/api-client.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { once, EventEmitter } = require('node:events')
|
|
4
4
|
const { randomUUID } = require('node:crypto')
|
|
5
5
|
const errors = require('./errors')
|
|
6
|
+
const { setTimeout: sleep } = require('node:timers/promises')
|
|
6
7
|
|
|
7
8
|
const MAX_LISTENERS_COUNT = 100
|
|
8
9
|
|
|
@@ -29,13 +30,34 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
29
30
|
|
|
30
31
|
async close () {
|
|
31
32
|
await this.#sendCommand('plt:stop-services')
|
|
32
|
-
|
|
33
|
+
|
|
34
|
+
this.worker.postMessage({ command: 'plt:close' })
|
|
35
|
+
const res = await Promise.race([
|
|
36
|
+
this.#exitPromise,
|
|
37
|
+
// We must kill the worker if it doesn't exit in 10 seconds
|
|
38
|
+
// because it may be stuck in an infinite loop.
|
|
39
|
+
// This is a workaround for
|
|
40
|
+
// https://github.com/nodejs/node/issues/47748
|
|
41
|
+
// https://github.com/nodejs/node/issues/49344
|
|
42
|
+
// Remove once https://github.com/nodejs/node/pull/51290 is released
|
|
43
|
+
// on all lines.
|
|
44
|
+
// Likely to be removed when we drop support for Node.js 18.
|
|
45
|
+
sleep(10000, 'timeout', { ref: false })
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
if (res === 'timeout') {
|
|
49
|
+
this.worker.unref()
|
|
50
|
+
}
|
|
33
51
|
}
|
|
34
52
|
|
|
35
53
|
async restart () {
|
|
36
54
|
return this.#sendCommand('plt:restart-services')
|
|
37
55
|
}
|
|
38
56
|
|
|
57
|
+
async getEntrypointDetails () {
|
|
58
|
+
return this.#sendCommand('plt:get-entrypoint-details')
|
|
59
|
+
}
|
|
60
|
+
|
|
39
61
|
async getServices () {
|
|
40
62
|
return this.#sendCommand('plt:get-services')
|
|
41
63
|
}
|
package/lib/api.js
CHANGED
|
@@ -9,9 +9,11 @@ const { printSchema } = require('graphql')
|
|
|
9
9
|
class RuntimeApi {
|
|
10
10
|
#services
|
|
11
11
|
#dispatcher
|
|
12
|
+
#logger
|
|
12
13
|
|
|
13
14
|
constructor (config, logger, loaderPort) {
|
|
14
15
|
this.#services = new Map()
|
|
16
|
+
this.#logger = logger
|
|
15
17
|
const telemetryConfig = config.telemetry
|
|
16
18
|
|
|
17
19
|
for (let i = 0; i < config.services.length; ++i) {
|
|
@@ -47,12 +49,26 @@ class RuntimeApi {
|
|
|
47
49
|
parentPort.on('message', async (message) => {
|
|
48
50
|
const command = message?.command
|
|
49
51
|
if (command) {
|
|
52
|
+
if (command === 'plt:close') {
|
|
53
|
+
// We close everything because they might be using
|
|
54
|
+
// a FinalizationRegistry and it may stuck us in an infinite loop.
|
|
55
|
+
// This is a workaround for
|
|
56
|
+
// https://github.com/nodejs/node/issues/47748
|
|
57
|
+
// https://github.com/nodejs/node/issues/49344
|
|
58
|
+
// Remove once https://github.com/nodejs/node/pull/51290 is released
|
|
59
|
+
// on all lines.
|
|
60
|
+
// Likely to be removed when we drop support for Node.js 18.
|
|
61
|
+
if (this.#dispatcher) {
|
|
62
|
+
await this.#dispatcher.close()
|
|
63
|
+
}
|
|
64
|
+
await this.stopServices()
|
|
65
|
+
|
|
66
|
+
setImmediate(process.exit) // Exit the worker thread.
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
50
70
|
const res = await this.#executeCommand(message)
|
|
51
71
|
parentPort.postMessage(res)
|
|
52
|
-
|
|
53
|
-
if (command === 'plt:stop-services') {
|
|
54
|
-
process.exit() // Exit the worker thread.
|
|
55
|
-
}
|
|
56
72
|
return
|
|
57
73
|
}
|
|
58
74
|
await this.#handleProcessLevelEvent(message)
|
|
@@ -96,6 +112,8 @@ class RuntimeApi {
|
|
|
96
112
|
return this.stopServices(params)
|
|
97
113
|
case 'plt:restart-services':
|
|
98
114
|
return this.#restartServices(params)
|
|
115
|
+
case 'plt:get-entrypoint-details':
|
|
116
|
+
return this.#getEntrypointDetails(params)
|
|
99
117
|
case 'plt:get-services':
|
|
100
118
|
return this.#getServices(params)
|
|
101
119
|
case 'plt:get-service-details':
|
|
@@ -134,7 +152,7 @@ class RuntimeApi {
|
|
|
134
152
|
}
|
|
135
153
|
|
|
136
154
|
async stopServices () {
|
|
137
|
-
const stopServiceReqs = [
|
|
155
|
+
const stopServiceReqs = []
|
|
138
156
|
for (const service of this.#services.values()) {
|
|
139
157
|
const serviceStatus = service.getStatus()
|
|
140
158
|
if (serviceStatus === 'started') {
|
|
@@ -161,6 +179,15 @@ class RuntimeApi {
|
|
|
161
179
|
return entrypointUrl
|
|
162
180
|
}
|
|
163
181
|
|
|
182
|
+
#getEntrypointDetails () {
|
|
183
|
+
for (const service of this.#services.values()) {
|
|
184
|
+
if (service.appConfig.entrypoint) {
|
|
185
|
+
return this.#getServiceDetails({ id: service.appConfig.id })
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
190
|
+
|
|
164
191
|
#getServices () {
|
|
165
192
|
const services = { services: [] }
|
|
166
193
|
|
|
@@ -190,8 +217,15 @@ class RuntimeApi {
|
|
|
190
217
|
const service = this.#getServiceById(id)
|
|
191
218
|
const status = service.getStatus()
|
|
192
219
|
|
|
220
|
+
const type = service.config.configType
|
|
193
221
|
const { entrypoint, dependencies, localUrl } = service.appConfig
|
|
194
|
-
|
|
222
|
+
const serviceDetails = { id, type, status, localUrl, entrypoint, dependencies }
|
|
223
|
+
|
|
224
|
+
if (entrypoint) {
|
|
225
|
+
serviceDetails.url = status === 'started' ? service.server.url : null
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return serviceDetails
|
|
195
229
|
}
|
|
196
230
|
|
|
197
231
|
#getServiceConfig ({ id }) {
|
|
@@ -269,8 +303,7 @@ class RuntimeApi {
|
|
|
269
303
|
statusCode: res.statusCode,
|
|
270
304
|
statusMessage: res.statusMessage,
|
|
271
305
|
headers: res.headers,
|
|
272
|
-
body: res.body
|
|
273
|
-
payload: res.payload
|
|
306
|
+
body: res.body
|
|
274
307
|
}
|
|
275
308
|
}
|
|
276
309
|
}
|
package/lib/app.js
CHANGED
|
@@ -73,7 +73,10 @@ class PlatformaticApp {
|
|
|
73
73
|
this.#setuplogger(this.config.configManager)
|
|
74
74
|
await this.server.restart()
|
|
75
75
|
} catch (err) {
|
|
76
|
-
|
|
76
|
+
// The restart failed. Log the error and
|
|
77
|
+
// wait for another event.
|
|
78
|
+
// The old app is still available
|
|
79
|
+
this.#logger.error({ err })
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
this.#restarting = false
|
|
@@ -103,6 +106,16 @@ class PlatformaticApp {
|
|
|
103
106
|
})
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
if (!this.appConfig.entrypoint) {
|
|
110
|
+
configManager.update({
|
|
111
|
+
...configManager.current,
|
|
112
|
+
server: {
|
|
113
|
+
...(configManager.current.server || {}),
|
|
114
|
+
trustProxy: true
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
106
119
|
const config = configManager.current
|
|
107
120
|
|
|
108
121
|
this.#setuplogger(configManager)
|
package/lib/errors.js
CHANGED
|
@@ -21,5 +21,6 @@ module.exports = {
|
|
|
21
21
|
InspectorPortError: createError(`${ERROR_PREFIX}_INSPECTOR_PORT`, 'Inspector port must be 0 or in range 1024 to 65535'),
|
|
22
22
|
InspectorHostError: createError(`${ERROR_PREFIX}_INSPECTOR_HOST`, 'Inspector host cannot be empty'),
|
|
23
23
|
CannotMapSpecifierToAbsolutePathError: createError(`${ERROR_PREFIX}_CANNOT_MAP_SPECIFIER_TO_ABSOLUTE_PATH`, 'Cannot map "%s" to an absolute path'),
|
|
24
|
-
NodeInspectorFlagsNotSupportedError: createError(`${ERROR_PREFIX}_NODE_INSPECTOR_FLAGS_NOT_SUPPORTED`, 'The Node.js inspector flags are not supported. Please use \'platformatic start --inspect\' instead.')
|
|
24
|
+
NodeInspectorFlagsNotSupportedError: createError(`${ERROR_PREFIX}_NODE_INSPECTOR_FLAGS_NOT_SUPPORTED`, 'The Node.js inspector flags are not supported. Please use \'platformatic start --inspect\' instead.'),
|
|
25
|
+
FailedToUnlinkManagementApiSocket: createError(`${ERROR_PREFIX}_FAILED_TO_UNLINK_MANAGEMENT_API_SOCKET`, 'Failed to unlink management API socket "%s"')
|
|
25
26
|
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { tmpdir, platform } = require('node:os')
|
|
4
|
+
const { join } = require('node:path')
|
|
5
|
+
const { readFile, mkdir, unlink } = require('node:fs/promises')
|
|
6
|
+
const fastify = require('fastify')
|
|
7
|
+
const { prettyFactory } = require('pino-pretty')
|
|
8
|
+
const errors = require('./errors')
|
|
9
|
+
const platformaticVersion = require('../package.json').version
|
|
10
|
+
|
|
11
|
+
const PLATFORMATIC_TMP_DIR = join(tmpdir(), 'platformatic', 'pids')
|
|
12
|
+
|
|
13
|
+
const pinoLogLevels = {
|
|
14
|
+
fatal: 60,
|
|
15
|
+
error: 50,
|
|
16
|
+
warn: 40,
|
|
17
|
+
info: 30,
|
|
18
|
+
debug: 20,
|
|
19
|
+
trace: 10
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function createManagementApi (configManager, runtimeApiClient, loggingPort) {
|
|
23
|
+
let apiConfig = configManager.current.managementApi
|
|
24
|
+
if (!apiConfig || apiConfig === true) {
|
|
25
|
+
apiConfig = {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const app = fastify(apiConfig)
|
|
29
|
+
app.log.warn(
|
|
30
|
+
'Runtime Management API is in the experimental stage. ' +
|
|
31
|
+
'The feature is not subject to semantic versioning rules. ' +
|
|
32
|
+
'Non-backward compatible changes or removal may occur in any future release. ' +
|
|
33
|
+
'Use of the feature is not recommended in production environments.'
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
async function getRuntimePackageJson (cwd) {
|
|
37
|
+
const packageJsonPath = join(cwd, 'package.json')
|
|
38
|
+
const packageJsonFile = await readFile(packageJsonPath, 'utf8')
|
|
39
|
+
const packageJson = JSON.parse(packageJsonFile)
|
|
40
|
+
return packageJson
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
app.register(require('@fastify/websocket'))
|
|
44
|
+
|
|
45
|
+
app.register(async (app) => {
|
|
46
|
+
app.get('/metadata', async () => {
|
|
47
|
+
const packageJson = await getRuntimePackageJson(configManager.dirname).catch(() => ({}))
|
|
48
|
+
const entrypointDetails = await runtimeApiClient.getEntrypointDetails().catch(() => null)
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
pid: process.pid,
|
|
52
|
+
cwd: process.cwd(),
|
|
53
|
+
argv: process.argv,
|
|
54
|
+
uptimeSeconds: Math.floor(process.uptime()),
|
|
55
|
+
execPath: process.execPath,
|
|
56
|
+
nodeVersion: process.version,
|
|
57
|
+
projectDir: configManager.dirname,
|
|
58
|
+
packageName: packageJson.name ?? null,
|
|
59
|
+
packageVersion: packageJson.version ?? null,
|
|
60
|
+
url: entrypointDetails?.url ?? null,
|
|
61
|
+
platformaticVersion
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
app.get('/config', async () => {
|
|
66
|
+
return configManager.current
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
app.get('/env', async () => {
|
|
70
|
+
return process.env
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
app.post('/stop', async () => {
|
|
74
|
+
app.log.debug('stop services')
|
|
75
|
+
await runtimeApiClient.close()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
app.post('/reload', async () => {
|
|
79
|
+
app.log.debug('reload services')
|
|
80
|
+
await runtimeApiClient.restart()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
app.get('/services', async () => {
|
|
84
|
+
return runtimeApiClient.getServices()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
app.get('/services/:id', async (request) => {
|
|
88
|
+
const { id } = request.params
|
|
89
|
+
app.log.debug('get service details', { id })
|
|
90
|
+
return runtimeApiClient.getServiceDetails(id)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
app.get('/services/:id/config', async (request) => {
|
|
94
|
+
const { id } = request.params
|
|
95
|
+
app.log.debug('get service config', { id })
|
|
96
|
+
return runtimeApiClient.getServiceConfig(id)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
app.post('/services/:id/start', async (request) => {
|
|
100
|
+
const { id } = request.params
|
|
101
|
+
app.log.debug('start service', { id })
|
|
102
|
+
await runtimeApiClient.startService(id)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
app.post('/services/:id/stop', async (request) => {
|
|
106
|
+
const { id } = request.params
|
|
107
|
+
app.log.debug('stop service', { id })
|
|
108
|
+
await runtimeApiClient.stopService(id)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
app.all('/services/:id/proxy/*', async (request, reply) => {
|
|
112
|
+
const { id, '*': requestUrl } = request.params
|
|
113
|
+
app.log.debug('proxy request', { id, requestUrl })
|
|
114
|
+
|
|
115
|
+
const injectParams = {
|
|
116
|
+
method: request.method,
|
|
117
|
+
url: requestUrl || '/',
|
|
118
|
+
headers: request.headers,
|
|
119
|
+
query: request.query,
|
|
120
|
+
body: request.body
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const res = await runtimeApiClient.inject(id, injectParams)
|
|
124
|
+
|
|
125
|
+
reply
|
|
126
|
+
.code(res.statusCode)
|
|
127
|
+
.headers(res.headers)
|
|
128
|
+
.send(res.body)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
app.get('/logs', { websocket: true }, async (connection, req) => {
|
|
132
|
+
const logLevel = req.query.level || 'info'
|
|
133
|
+
const pretty = req.query.pretty !== 'false'
|
|
134
|
+
const serviceId = req.query.serviceId || null
|
|
135
|
+
|
|
136
|
+
const logLevelNumber = pinoLogLevels[logLevel]
|
|
137
|
+
const prettify = prettyFactory()
|
|
138
|
+
|
|
139
|
+
const handler = (message) => {
|
|
140
|
+
for (let log of message.logs) {
|
|
141
|
+
try {
|
|
142
|
+
const parsedLog = JSON.parse(log)
|
|
143
|
+
if (parsedLog.level < logLevelNumber) continue
|
|
144
|
+
if (serviceId && parsedLog.name !== serviceId) continue
|
|
145
|
+
if (pretty) {
|
|
146
|
+
log = prettify(parsedLog)
|
|
147
|
+
}
|
|
148
|
+
connection.socket.send(log)
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error('Failed to parse log message: ', log, err)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
loggingPort.on('message', handler)
|
|
156
|
+
connection.socket.on('close', () => {
|
|
157
|
+
loggingPort.off('message', handler)
|
|
158
|
+
})
|
|
159
|
+
connection.socket.on('error', () => {
|
|
160
|
+
loggingPort.off('message', handler)
|
|
161
|
+
})
|
|
162
|
+
connection.socket.on('end', () => {
|
|
163
|
+
loggingPort.off('message', handler)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
}, { prefix: '/api' })
|
|
167
|
+
|
|
168
|
+
return app
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function startManagementApi (configManager, runtimeApiClient, loggingPort) {
|
|
172
|
+
const runtimePID = process.pid
|
|
173
|
+
|
|
174
|
+
let socketPath = null
|
|
175
|
+
if (platform() === 'win32') {
|
|
176
|
+
socketPath = '\\\\.\\pipe\\platformatic-' + runtimePID
|
|
177
|
+
} else {
|
|
178
|
+
await mkdir(PLATFORMATIC_TMP_DIR, { recursive: true })
|
|
179
|
+
socketPath = join(PLATFORMATIC_TMP_DIR, `${runtimePID}.sock`)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
await mkdir(PLATFORMATIC_TMP_DIR, { recursive: true })
|
|
184
|
+
await unlink(socketPath).catch((err) => {
|
|
185
|
+
if (err.code !== 'ENOENT') {
|
|
186
|
+
throw new errors.FailedToUnlinkManagementApiSocket(err.message)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const managementApi = await createManagementApi(
|
|
191
|
+
configManager,
|
|
192
|
+
runtimeApiClient,
|
|
193
|
+
loggingPort
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if (platform() !== 'win32') {
|
|
197
|
+
managementApi.addHook('onClose', async () => {
|
|
198
|
+
await unlink(socketPath).catch(() => {})
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await managementApi.listen({ path: socketPath })
|
|
203
|
+
return managementApi
|
|
204
|
+
/* c8 ignore next 4 */
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error(err)
|
|
207
|
+
process.exit(1)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = { startManagementApi, createManagementApi }
|
package/lib/schema.js
CHANGED
package/lib/start.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
|
|
2
3
|
const { once } = require('node:events')
|
|
3
4
|
const inspector = require('node:inspector')
|
|
4
5
|
const { join, resolve, dirname } = require('node:path')
|
|
5
|
-
const
|
|
6
|
+
const { writeFile } = require('node:fs/promises')
|
|
6
7
|
const { pathToFileURL } = require('node:url')
|
|
7
8
|
const { Worker } = require('node:worker_threads')
|
|
8
|
-
const closeWithGrace = require('close-with-grace')
|
|
9
9
|
const { start: serviceStart } = require('@platformatic/service')
|
|
10
|
+
const { printConfigValidationErrors } = require('@platformatic/config')
|
|
11
|
+
const closeWithGrace = require('close-with-grace')
|
|
10
12
|
const { loadConfig } = require('./load-config')
|
|
13
|
+
const { startManagementApi } = require('./management-api')
|
|
11
14
|
const { parseInspectorOptions, wrapConfigInRuntimeConfig } = require('./config')
|
|
12
15
|
const RuntimeApiClient = require('./api-client.js')
|
|
13
|
-
const { printConfigValidationErrors } = require('@platformatic/config')
|
|
14
16
|
const errors = require('./errors')
|
|
15
17
|
const pkg = require('../package.json')
|
|
16
18
|
|
|
@@ -42,14 +44,27 @@ async function startWithConfig (configManager, env = process.env) {
|
|
|
42
44
|
// The configManager cannot be transferred to the worker, so remove it.
|
|
43
45
|
delete config.configManager
|
|
44
46
|
|
|
47
|
+
let mainLoggingPort = null
|
|
48
|
+
let childLoggingPort = config.loggingPort
|
|
49
|
+
|
|
50
|
+
if (!childLoggingPort && config.managementApi) {
|
|
51
|
+
const { port1, port2 } = new MessageChannel()
|
|
52
|
+
mainLoggingPort = port1
|
|
53
|
+
childLoggingPort = port2
|
|
54
|
+
|
|
55
|
+
config.loggingPort = childLoggingPort
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
const worker = new Worker(kWorkerFile, {
|
|
46
59
|
/* c8 ignore next */
|
|
47
60
|
execArgv: config.hotReload ? kWorkerExecArgv : [],
|
|
48
|
-
transferList:
|
|
61
|
+
transferList: childLoggingPort ? [childLoggingPort] : [],
|
|
49
62
|
workerData: { config, dirname },
|
|
50
63
|
env
|
|
51
64
|
})
|
|
52
65
|
|
|
66
|
+
let managementApi = null
|
|
67
|
+
|
|
53
68
|
let exited = null
|
|
54
69
|
let isWorkerAlive = true
|
|
55
70
|
worker.on('exit', (code) => {
|
|
@@ -57,6 +72,7 @@ async function startWithConfig (configManager, env = process.env) {
|
|
|
57
72
|
process.exitCode = code
|
|
58
73
|
isWorkerAlive = false
|
|
59
74
|
configManager.fileWatcher?.stopWatching()
|
|
75
|
+
managementApi?.close()
|
|
60
76
|
if (typeof exited === 'function') {
|
|
61
77
|
exited()
|
|
62
78
|
}
|
|
@@ -96,6 +112,16 @@ async function startWithConfig (configManager, env = process.env) {
|
|
|
96
112
|
await once(worker, 'message') // plt:init
|
|
97
113
|
|
|
98
114
|
const runtimeApiClient = new RuntimeApiClient(worker)
|
|
115
|
+
|
|
116
|
+
if (config.managementApi) {
|
|
117
|
+
managementApi = await startManagementApi(
|
|
118
|
+
configManager,
|
|
119
|
+
runtimeApiClient,
|
|
120
|
+
mainLoggingPort
|
|
121
|
+
)
|
|
122
|
+
runtimeApiClient.managementApi = managementApi
|
|
123
|
+
}
|
|
124
|
+
|
|
99
125
|
return runtimeApiClient
|
|
100
126
|
}
|
|
101
127
|
|
|
@@ -148,7 +174,7 @@ async function startCommand (args) {
|
|
|
148
174
|
}
|
|
149
175
|
const toWrite = join(dirname(resolve(args[0])), 'platformatic.service.json')
|
|
150
176
|
console.log(`No config file found, creating ${join(dirname(args[0]), 'platformatic.service.json')}`)
|
|
151
|
-
await
|
|
177
|
+
await writeFile(toWrite, JSON.stringify(config, null, 2))
|
|
152
178
|
return startCommand(['--config', toWrite])
|
|
153
179
|
}
|
|
154
180
|
logErrorAndExit(err)
|
package/lib/worker.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
} = require('node:worker_threads')
|
|
13
13
|
const undici = require('undici')
|
|
14
14
|
const pino = require('pino')
|
|
15
|
+
const pretty = require('pino-pretty')
|
|
15
16
|
const { setGlobalDispatcher, Agent } = require('undici')
|
|
16
17
|
const RuntimeApi = require('./api')
|
|
17
18
|
const { MessagePortWritable } = require('./message-port-writable')
|
|
@@ -34,7 +35,6 @@ globalThis.fetch = undici.fetch
|
|
|
34
35
|
const config = workerData.config
|
|
35
36
|
|
|
36
37
|
let loggerConfig = config.server?.logger
|
|
37
|
-
let destination
|
|
38
38
|
|
|
39
39
|
if (loggerConfig) {
|
|
40
40
|
loggerConfig = { ...loggerConfig }
|
|
@@ -42,21 +42,23 @@ if (loggerConfig) {
|
|
|
42
42
|
loggerConfig = {}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
const cliStream = isatty(1) ? pretty() : pino.destination(1)
|
|
46
|
+
|
|
47
|
+
let logger = null
|
|
46
48
|
if (config.loggingPort) {
|
|
47
|
-
|
|
49
|
+
const portStream = new MessagePortWritable({
|
|
48
50
|
metadata: config.loggingMetadata,
|
|
49
51
|
port: config.loggingPort
|
|
50
52
|
})
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
53
|
+
const multiStream = pino.multistream([
|
|
54
|
+
{ stream: portStream, level: 'trace' },
|
|
55
|
+
{ stream: cliStream, level: loggerConfig.level || 'info' }
|
|
56
|
+
])
|
|
57
|
+
logger = pino({ level: 'trace' }, multiStream)
|
|
58
|
+
} else {
|
|
59
|
+
logger = pino(loggerConfig, cliStream)
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
const logger = pino(loggerConfig, destination)
|
|
59
|
-
|
|
60
62
|
if (config.server) {
|
|
61
63
|
config.server.logger = logger
|
|
62
64
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@fastify/express": "^2.3.0",
|
|
21
|
+
"@matteo.collina/tspl": "^0.1.1",
|
|
21
22
|
"borp": "^0.9.0",
|
|
22
23
|
"c8": "^9.1.0",
|
|
23
24
|
"execa": "^8.0.1",
|
|
@@ -30,11 +31,13 @@
|
|
|
30
31
|
"tsd": "^0.30.4",
|
|
31
32
|
"typescript": "^5.3.3",
|
|
32
33
|
"undici-oauth-interceptor": "^0.4.2",
|
|
33
|
-
"
|
|
34
|
-
"@platformatic/sql-
|
|
34
|
+
"ws": "^8.16.0",
|
|
35
|
+
"@platformatic/sql-graphql": "1.24.0",
|
|
36
|
+
"@platformatic/sql-mapper": "1.24.0"
|
|
35
37
|
},
|
|
36
38
|
"dependencies": {
|
|
37
39
|
"@fastify/error": "^3.4.1",
|
|
40
|
+
"@fastify/websocket": "^9.0.0",
|
|
38
41
|
"@hapi/topo": "^6.0.2",
|
|
39
42
|
"boring-name-generator": "^1.0.3",
|
|
40
43
|
"close-with-grace": "^1.2.0",
|
|
@@ -52,13 +55,13 @@
|
|
|
52
55
|
"pino-pretty": "^10.3.1",
|
|
53
56
|
"undici": "^6.6.0",
|
|
54
57
|
"why-is-node-running": "^2.2.2",
|
|
55
|
-
"@platformatic/composer": "1.
|
|
56
|
-
"@platformatic/
|
|
57
|
-
"@platformatic/
|
|
58
|
-
"@platformatic/generators": "1.
|
|
59
|
-
"@platformatic/service": "1.
|
|
60
|
-
"@platformatic/telemetry": "1.
|
|
61
|
-
"@platformatic/utils": "1.
|
|
58
|
+
"@platformatic/composer": "1.24.0",
|
|
59
|
+
"@platformatic/config": "1.24.0",
|
|
60
|
+
"@platformatic/db": "1.24.0",
|
|
61
|
+
"@platformatic/generators": "1.24.0",
|
|
62
|
+
"@platformatic/service": "1.24.0",
|
|
63
|
+
"@platformatic/telemetry": "1.24.0",
|
|
64
|
+
"@platformatic/utils": "1.24.0"
|
|
62
65
|
},
|
|
63
66
|
"standard": {
|
|
64
67
|
"ignore": [
|
package/runtime.mjs
CHANGED