@platformatic/runtime 1.33.0 → 1.35.1
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/dbApp/db.sqlite +0 -0
- package/fixtures/dbAppWithMigrationError/db.sqlite +0 -0
- package/fixtures/monorepo/dbApp/db.sqlite +0 -0
- package/fixtures/typescript/services/composer/.env +4 -0
- package/fixtures/typescript/services/movies/dist/plugin.js +6 -0
- package/fixtures/typescript/services/movies/dist/plugin.js.map +1 -0
- package/fixtures/typescript/services/titles/.env +3 -0
- package/fixtures/typescript/services/titles/dist/plugins/example.js +7 -0
- package/fixtures/typescript/services/titles/dist/plugins/example.js.map +1 -0
- package/fixtures/typescript/services/titles/dist/routes/root.js +14 -0
- package/fixtures/typescript/services/titles/dist/routes/root.js.map +1 -0
- package/fixtures/typescript-custom-flags/services/composer/.env +4 -0
- package/fixtures/typescript-custom-flags/services/movies/.env +4 -0
- package/fixtures/typescript-custom-flags/services/titles/.env +3 -0
- package/lib/api-client.js +217 -4
- package/lib/api.js +38 -12
- package/lib/app.js +29 -16
- package/lib/config.js +14 -2
- package/lib/management-api.js +77 -71
- package/lib/schema.js +25 -26
- package/lib/start.js +9 -7
- package/lib/worker.js +1 -4
- package/package.json +11 -11
- package/lib/logs.js +0 -123
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../plugin.ts"],"names":[],"mappings":";;AAGe,KAAK,oBAAW,GAAoB;AACnD,CAAC;AADD,4BACC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example.js","sourceRoot":"","sources":["../../plugins/example.ts"],"names":[],"mappings":";;AAGe,KAAK,oBAAW,OAAwB,EAAE,IAA0B;IACjF,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;AACvC,CAAC;AAFD,4BAEC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
async function default_1(fastify, opts) {
|
|
4
|
+
fastify.get('/', async (request, reply) => {
|
|
5
|
+
return { hello: fastify.example };
|
|
6
|
+
});
|
|
7
|
+
fastify.get('/titles', async (request, reply) => {
|
|
8
|
+
const movies = await fastify.client.getMovies({});
|
|
9
|
+
const titles = movies.map((movie) => movie.title);
|
|
10
|
+
return { titles };
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
exports.default = default_1;
|
|
14
|
+
//# sourceMappingURL=root.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root.js","sourceRoot":"","sources":["../../routes/root.ts"],"names":[],"mappings":";;AAUe,KAAK,oBAAW,OAAwB,EAAE,IAA0B;IACjF,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACxC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAC9C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACjD,OAAO,EAAE,MAAM,EAAE,CAAA;IACnB,CAAC,CAAC,CAAA;AACJ,CAAC;AAVD,4BAUC"}
|
package/lib/api-client.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { tmpdir } = require('node:os')
|
|
4
|
+
const { join } = require('node:path')
|
|
3
5
|
const { once, EventEmitter } = require('node:events')
|
|
4
|
-
const { randomUUID } = require('node:crypto')
|
|
5
|
-
const
|
|
6
|
+
const { randomUUID, createHash } = require('node:crypto')
|
|
7
|
+
const { createReadStream, watch } = require('node:fs')
|
|
8
|
+
const { readdir, readFile, stat, access } = require('node:fs/promises')
|
|
6
9
|
const { setTimeout: sleep } = require('node:timers/promises')
|
|
10
|
+
const errors = require('./errors')
|
|
11
|
+
const ts = require('tail-file-stream')
|
|
12
|
+
|
|
13
|
+
const platformaticVersion = require('../package.json').version
|
|
7
14
|
|
|
8
15
|
const MAX_LISTENERS_COUNT = 100
|
|
9
16
|
const MAX_METRICS_QUEUE_LENGTH = 5 * 60 // 5 minutes in seconds
|
|
@@ -12,14 +19,18 @@ const COLLECT_METRICS_TIMEOUT = 1000
|
|
|
12
19
|
class RuntimeApiClient extends EventEmitter {
|
|
13
20
|
#exitCode
|
|
14
21
|
#exitPromise
|
|
22
|
+
#configManager
|
|
23
|
+
#runtimeTmpDir
|
|
15
24
|
#metrics
|
|
16
25
|
#metricsTimeout
|
|
17
26
|
|
|
18
|
-
constructor (worker) {
|
|
27
|
+
constructor (worker, configManager) {
|
|
19
28
|
super()
|
|
20
29
|
this.setMaxListeners(MAX_LISTENERS_COUNT)
|
|
21
30
|
|
|
22
31
|
this.worker = worker
|
|
32
|
+
this.#configManager = configManager
|
|
33
|
+
this.#runtimeTmpDir = getRuntimeTmpDir(configManager.dirname)
|
|
23
34
|
this.#exitPromise = this.#exitHandler()
|
|
24
35
|
this.worker.on('message', (message) => {
|
|
25
36
|
if (message.operationId) {
|
|
@@ -28,6 +39,37 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
28
39
|
})
|
|
29
40
|
}
|
|
30
41
|
|
|
42
|
+
async #getRuntimePackageJson () {
|
|
43
|
+
const runtimeDir = this.#configManager.dirname
|
|
44
|
+
const packageJsonPath = join(runtimeDir, 'package.json')
|
|
45
|
+
const packageJsonFile = await readFile(packageJsonPath, 'utf8')
|
|
46
|
+
const packageJson = JSON.parse(packageJsonFile)
|
|
47
|
+
return packageJson
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async getRuntimeMetadata () {
|
|
51
|
+
const packageJson = await this.#getRuntimePackageJson()
|
|
52
|
+
const entrypointDetails = await this.getEntrypointDetails()
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
pid: process.pid,
|
|
56
|
+
cwd: process.cwd(),
|
|
57
|
+
argv: process.argv,
|
|
58
|
+
uptimeSeconds: Math.floor(process.uptime()),
|
|
59
|
+
execPath: process.execPath,
|
|
60
|
+
nodeVersion: process.version,
|
|
61
|
+
projectDir: this.#configManager.dirname,
|
|
62
|
+
packageName: packageJson.name ?? null,
|
|
63
|
+
packageVersion: packageJson.version ?? null,
|
|
64
|
+
url: entrypointDetails?.url ?? null,
|
|
65
|
+
platformaticVersion
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getRuntimeConfig () {
|
|
70
|
+
return this.#configManager.current
|
|
71
|
+
}
|
|
72
|
+
|
|
31
73
|
async start () {
|
|
32
74
|
const address = await this.#sendCommand('plt:start-services')
|
|
33
75
|
this.emit('start', address)
|
|
@@ -217,6 +259,162 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
217
259
|
}, COLLECT_METRICS_TIMEOUT).unref()
|
|
218
260
|
}
|
|
219
261
|
|
|
262
|
+
async pipeLogsStream (writableStream, logger, startLogId, endLogId, runtimePID) {
|
|
263
|
+
endLogId = endLogId || Infinity
|
|
264
|
+
runtimePID = runtimePID ?? process.pid
|
|
265
|
+
|
|
266
|
+
const runtimeLogFiles = await this.#getRuntimeLogFiles(runtimePID)
|
|
267
|
+
if (runtimeLogFiles.length === 0) {
|
|
268
|
+
writableStream.end()
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let latestFileId = parseInt(runtimeLogFiles.at(-1).slice('logs.'.length))
|
|
273
|
+
|
|
274
|
+
let waiting = false
|
|
275
|
+
let fileStream = null
|
|
276
|
+
let fileId = startLogId ?? latestFileId
|
|
277
|
+
|
|
278
|
+
const runtimeLogsDir = this.#getRuntimeLogsDir(runtimePID)
|
|
279
|
+
|
|
280
|
+
const watcher = watch(runtimeLogsDir, async (event, filename) => {
|
|
281
|
+
if (event === 'rename' && filename.startsWith('logs')) {
|
|
282
|
+
const logFileId = parseInt(filename.slice('logs.'.length))
|
|
283
|
+
if (logFileId > latestFileId) {
|
|
284
|
+
latestFileId = logFileId
|
|
285
|
+
if (waiting) {
|
|
286
|
+
streamLogFile(++fileId)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}).unref()
|
|
291
|
+
|
|
292
|
+
const streamLogFile = () => {
|
|
293
|
+
if (fileId > endLogId) {
|
|
294
|
+
writableStream.end()
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const fileName = 'logs.' + fileId
|
|
299
|
+
const filePath = join(runtimeLogsDir, fileName)
|
|
300
|
+
|
|
301
|
+
const prevFileStream = fileStream
|
|
302
|
+
|
|
303
|
+
fileStream = ts.createReadStream(filePath)
|
|
304
|
+
fileStream.pipe(writableStream, { end: false })
|
|
305
|
+
|
|
306
|
+
if (prevFileStream) {
|
|
307
|
+
prevFileStream.unpipe(writableStream)
|
|
308
|
+
prevFileStream.destroy()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
fileStream.on('error', (err) => {
|
|
312
|
+
logger.error(err, 'Error streaming log file')
|
|
313
|
+
fileStream.destroy()
|
|
314
|
+
watcher.close()
|
|
315
|
+
writableStream.end()
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
fileStream.on('data', () => {
|
|
319
|
+
waiting = false
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
fileStream.on('eof', () => {
|
|
323
|
+
if (fileId >= endLogId) {
|
|
324
|
+
writableStream.end()
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
if (latestFileId > fileId) {
|
|
328
|
+
streamLogFile(++fileId)
|
|
329
|
+
} else {
|
|
330
|
+
waiting = true
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
return fileStream
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
streamLogFile(fileId)
|
|
338
|
+
|
|
339
|
+
writableStream.on('close', () => {
|
|
340
|
+
watcher.close()
|
|
341
|
+
fileStream.destroy()
|
|
342
|
+
})
|
|
343
|
+
writableStream.on('error', () => {
|
|
344
|
+
watcher.close()
|
|
345
|
+
fileStream.destroy()
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async getLogIds () {
|
|
350
|
+
const runtimesLogFiles = await this.#getAllLogsFiles()
|
|
351
|
+
const runtimesLogsIds = []
|
|
352
|
+
|
|
353
|
+
for (const runtime of runtimesLogFiles) {
|
|
354
|
+
const runtimeLogIds = []
|
|
355
|
+
for (const logFile of runtime.runtimeLogFiles) {
|
|
356
|
+
const logId = parseInt(logFile.slice('logs.'.length))
|
|
357
|
+
runtimeLogIds.push(logId)
|
|
358
|
+
}
|
|
359
|
+
runtimesLogsIds.push({
|
|
360
|
+
pid: runtime.runtimePID,
|
|
361
|
+
indexes: runtimeLogIds
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return runtimesLogsIds
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async getLogFileStream (logFileId, runtimePID) {
|
|
369
|
+
const runtimeLogsDir = this.#getRuntimeLogsDir(runtimePID)
|
|
370
|
+
const filePath = join(runtimeLogsDir, `logs.${logFileId}`)
|
|
371
|
+
return createReadStream(filePath)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#getRuntimeLogsDir (runtimePID) {
|
|
375
|
+
return join(this.#runtimeTmpDir, runtimePID.toString(), 'logs')
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async #getRuntimeLogFiles (runtimePID) {
|
|
379
|
+
const runtimeLogsDir = this.#getRuntimeLogsDir(runtimePID)
|
|
380
|
+
const runtimeLogsFiles = await readdir(runtimeLogsDir)
|
|
381
|
+
return runtimeLogsFiles
|
|
382
|
+
.filter((file) => file.startsWith('logs'))
|
|
383
|
+
.sort((log1, log2) => {
|
|
384
|
+
const index1 = parseInt(log1.slice('logs.'.length))
|
|
385
|
+
const index2 = parseInt(log2.slice('logs.'.length))
|
|
386
|
+
return index1 - index2
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async #getAllLogsFiles () {
|
|
391
|
+
try {
|
|
392
|
+
await access(this.#runtimeTmpDir)
|
|
393
|
+
} catch (error) {
|
|
394
|
+
return []
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const runtimePIDs = await readdir(this.#runtimeTmpDir)
|
|
398
|
+
const runtimesLogFiles = []
|
|
399
|
+
|
|
400
|
+
for (const runtimePID of runtimePIDs) {
|
|
401
|
+
const runtimeLogsDir = this.#getRuntimeLogsDir(runtimePID)
|
|
402
|
+
const runtimeLogsDirStat = await stat(runtimeLogsDir)
|
|
403
|
+
const runtimeLogFiles = await this.#getRuntimeLogFiles(runtimePID)
|
|
404
|
+
const lastModified = runtimeLogsDirStat.mtime
|
|
405
|
+
|
|
406
|
+
runtimesLogFiles.push({
|
|
407
|
+
runtimePID: parseInt(runtimePID),
|
|
408
|
+
runtimeLogFiles,
|
|
409
|
+
lastModified
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return runtimesLogFiles.sort(
|
|
414
|
+
(runtime1, runtime2) => runtime1.lastModified - runtime2.lastModified
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
|
|
220
418
|
async #sendCommand (command, params = {}) {
|
|
221
419
|
const operationId = randomUUID()
|
|
222
420
|
|
|
@@ -248,4 +446,19 @@ class RuntimeApiClient extends EventEmitter {
|
|
|
248
446
|
}
|
|
249
447
|
}
|
|
250
448
|
|
|
251
|
-
|
|
449
|
+
function getRuntimeTmpDir (runtimeDir) {
|
|
450
|
+
const platformaticTmpDir = join(tmpdir(), 'platformatic', 'applications')
|
|
451
|
+
const runtimeDirHash = createHash('md5').update(runtimeDir).digest('hex')
|
|
452
|
+
return join(platformaticTmpDir, runtimeDirHash)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function getRuntimeLogsDir (runtimeDir, runtimePID) {
|
|
456
|
+
const runtimeTmpDir = getRuntimeTmpDir(runtimeDir)
|
|
457
|
+
return join(runtimeTmpDir, runtimePID.toString(), 'logs')
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
module.exports = {
|
|
461
|
+
RuntimeApiClient,
|
|
462
|
+
getRuntimeTmpDir,
|
|
463
|
+
getRuntimeLogsDir
|
|
464
|
+
}
|
package/lib/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { once } = require('node:events')
|
|
3
4
|
const { getGlobalDispatcher, setGlobalDispatcher } = require('undici')
|
|
4
5
|
const { createFastifyInterceptor } = require('fastify-undici-dispatcher')
|
|
5
6
|
const { PlatformaticApp } = require('./app')
|
|
@@ -84,9 +85,11 @@ class RuntimeApi {
|
|
|
84
85
|
}))
|
|
85
86
|
|
|
86
87
|
for (const service of services) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
const serviceStatus = service.getStatus()
|
|
89
|
+
if (
|
|
90
|
+
serviceStatus === 'starting' ||
|
|
91
|
+
serviceStatus === 'started'
|
|
92
|
+
) return
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
if (this.#dispatcher) {
|
|
@@ -159,6 +162,11 @@ class RuntimeApi {
|
|
|
159
162
|
const stopServiceReqs = []
|
|
160
163
|
for (const service of this.#services.values()) {
|
|
161
164
|
const serviceStatus = service.getStatus()
|
|
165
|
+
if (serviceStatus === 'starting') {
|
|
166
|
+
stopServiceReqs.push(
|
|
167
|
+
once(service, 'start').then(() => service.stop())
|
|
168
|
+
)
|
|
169
|
+
}
|
|
162
170
|
if (serviceStatus === 'started') {
|
|
163
171
|
stopServiceReqs.push(service.stop())
|
|
164
172
|
}
|
|
@@ -169,10 +177,13 @@ class RuntimeApi {
|
|
|
169
177
|
async #restartServices () {
|
|
170
178
|
let entrypointUrl = null
|
|
171
179
|
for (const service of this.#services.values()) {
|
|
172
|
-
|
|
173
|
-
|
|
180
|
+
const serviceStatus = service.getStatus()
|
|
181
|
+
if (serviceStatus === 'starting') {
|
|
182
|
+
await once(service, 'start')
|
|
174
183
|
}
|
|
175
184
|
|
|
185
|
+
await service.restart(true)
|
|
186
|
+
|
|
176
187
|
if (service.appConfig.entrypoint) {
|
|
177
188
|
entrypointUrl = service.server.url
|
|
178
189
|
}
|
|
@@ -236,18 +247,19 @@ class RuntimeApi {
|
|
|
236
247
|
#getServiceConfig ({ id }) {
|
|
237
248
|
const service = this.#getServiceById(id)
|
|
238
249
|
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
250
|
+
const serviceStatus = service.getStatus()
|
|
251
|
+
if (serviceStatus !== 'started') {
|
|
241
252
|
throw new errors.ServiceNotStartedError(id)
|
|
242
253
|
}
|
|
243
254
|
|
|
244
|
-
return config.configManager.current
|
|
255
|
+
return service.config.configManager.current
|
|
245
256
|
}
|
|
246
257
|
|
|
247
258
|
async #getServiceOpenapiSchema ({ id }) {
|
|
248
259
|
const service = this.#getServiceById(id)
|
|
249
260
|
|
|
250
|
-
|
|
261
|
+
const serviceStatus = service.getStatus()
|
|
262
|
+
if (serviceStatus !== 'started') {
|
|
251
263
|
throw new errors.ServiceNotStartedError(id)
|
|
252
264
|
}
|
|
253
265
|
|
|
@@ -267,7 +279,8 @@ class RuntimeApi {
|
|
|
267
279
|
async #getServiceGraphqlSchema ({ id }) {
|
|
268
280
|
const service = this.#getServiceById(id)
|
|
269
281
|
|
|
270
|
-
|
|
282
|
+
const serviceStatus = service.getStatus()
|
|
283
|
+
if (serviceStatus !== 'started') {
|
|
271
284
|
throw new errors.ServiceNotStartedError(id)
|
|
272
285
|
}
|
|
273
286
|
|
|
@@ -293,7 +306,8 @@ class RuntimeApi {
|
|
|
293
306
|
}
|
|
294
307
|
}
|
|
295
308
|
|
|
296
|
-
|
|
309
|
+
const entrypointStatus = entrypoint.getStatus()
|
|
310
|
+
if (entrypointStatus !== 'started') {
|
|
297
311
|
throw new errors.ServiceNotStartedError(entrypoint.id)
|
|
298
312
|
}
|
|
299
313
|
|
|
@@ -313,11 +327,23 @@ class RuntimeApi {
|
|
|
313
327
|
|
|
314
328
|
async #startService ({ id }) {
|
|
315
329
|
const service = this.#getServiceById(id)
|
|
316
|
-
|
|
330
|
+
const serviceStatus = service.getStatus()
|
|
331
|
+
|
|
332
|
+
if (serviceStatus === 'starting') {
|
|
333
|
+
await once(service, 'start')
|
|
334
|
+
} else {
|
|
335
|
+
await service.start()
|
|
336
|
+
}
|
|
317
337
|
}
|
|
318
338
|
|
|
319
339
|
async #stopService ({ id }) {
|
|
320
340
|
const service = this.#getServiceById(id)
|
|
341
|
+
const serviceStatus = service.getStatus()
|
|
342
|
+
|
|
343
|
+
if (serviceStatus === 'starting') {
|
|
344
|
+
await once(service, 'start')
|
|
345
|
+
}
|
|
346
|
+
|
|
321
347
|
await service.stop()
|
|
322
348
|
}
|
|
323
349
|
|
package/lib/app.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { once } = require('node:events')
|
|
4
3
|
const { dirname } = require('node:path')
|
|
4
|
+
const { EventEmitter, once } = require('node:events')
|
|
5
5
|
const { FileWatcher } = require('@platformatic/utils')
|
|
6
6
|
const debounce = require('debounce')
|
|
7
7
|
const { snakeCase } = require('change-case-all')
|
|
@@ -9,10 +9,10 @@ const { buildServer } = require('./build-server')
|
|
|
9
9
|
const { loadConfig } = require('./load-config')
|
|
10
10
|
const errors = require('./errors')
|
|
11
11
|
|
|
12
|
-
class PlatformaticApp {
|
|
12
|
+
class PlatformaticApp extends EventEmitter {
|
|
13
13
|
#hotReload
|
|
14
14
|
#loaderPort
|
|
15
|
-
#
|
|
15
|
+
#starting
|
|
16
16
|
#started
|
|
17
17
|
#originalWatch
|
|
18
18
|
#fileWatcher
|
|
@@ -23,13 +23,14 @@ class PlatformaticApp {
|
|
|
23
23
|
#hasManagementApi
|
|
24
24
|
|
|
25
25
|
constructor (appConfig, loaderPort, logger, telemetryConfig, serverConfig, hasManagementApi) {
|
|
26
|
+
super()
|
|
26
27
|
this.appConfig = appConfig
|
|
27
28
|
this.config = null
|
|
28
29
|
this.#hotReload = false
|
|
29
30
|
this.#loaderPort = loaderPort
|
|
30
|
-
this.#
|
|
31
|
-
this.server = null
|
|
31
|
+
this.#starting = false
|
|
32
32
|
this.#started = false
|
|
33
|
+
this.server = null
|
|
33
34
|
this.#originalWatch = null
|
|
34
35
|
this.#fileWatcher = null
|
|
35
36
|
this.#hasManagementApi = !!hasManagementApi
|
|
@@ -47,19 +48,18 @@ class PlatformaticApp {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
getStatus () {
|
|
50
|
-
if (this.#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return 'stopped'
|
|
54
|
-
}
|
|
51
|
+
if (this.#starting) return 'starting'
|
|
52
|
+
if (this.#started) return 'started'
|
|
53
|
+
return 'stopped'
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
async restart (force) {
|
|
58
|
-
if (this.#
|
|
57
|
+
if (this.#starting || !this.#started || (!this.#hotReload && !force)) {
|
|
59
58
|
return
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
this.#
|
|
61
|
+
this.#starting = true
|
|
62
|
+
this.#started = false
|
|
63
63
|
|
|
64
64
|
// The CJS cache should not be cleared from the loader because v20 moved
|
|
65
65
|
// the loader to a different thread with a different CJS cache.
|
|
@@ -84,15 +84,18 @@ class PlatformaticApp {
|
|
|
84
84
|
this.#logger.error({ err })
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
this.#
|
|
87
|
+
this.#started = true
|
|
88
|
+
this.#starting = false
|
|
89
|
+
this.emit('start')
|
|
90
|
+
this.emit('restart')
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
async start () {
|
|
91
|
-
if (this.#started) {
|
|
94
|
+
if (this.#starting || this.#started) {
|
|
92
95
|
throw new errors.ApplicationAlreadyStartedError()
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
this.#
|
|
98
|
+
this.#starting = true
|
|
96
99
|
|
|
97
100
|
await this.#initializeConfig()
|
|
98
101
|
await this.#updateConfig()
|
|
@@ -136,10 +139,14 @@ class PlatformaticApp {
|
|
|
136
139
|
// Make sure the server has run all the onReady hooks before returning.
|
|
137
140
|
await this.server.ready()
|
|
138
141
|
}
|
|
142
|
+
|
|
143
|
+
this.#started = true
|
|
144
|
+
this.#starting = false
|
|
145
|
+
this.emit('start')
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
async stop () {
|
|
142
|
-
if (!this.#started) {
|
|
149
|
+
if (!this.#started || this.#starting) {
|
|
143
150
|
throw new errors.ApplicationNotStartedError()
|
|
144
151
|
}
|
|
145
152
|
|
|
@@ -147,6 +154,8 @@ class PlatformaticApp {
|
|
|
147
154
|
await this.server.close()
|
|
148
155
|
|
|
149
156
|
this.#started = false
|
|
157
|
+
this.#starting = false
|
|
158
|
+
this.emit('stop')
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
async handleProcessLevelEvent ({ signal, err }) {
|
|
@@ -172,6 +181,10 @@ class PlatformaticApp {
|
|
|
172
181
|
this.server.log.info({ signal }, 'received signal')
|
|
173
182
|
}
|
|
174
183
|
|
|
184
|
+
if (this.#starting) {
|
|
185
|
+
await once(this, 'start')
|
|
186
|
+
}
|
|
187
|
+
|
|
175
188
|
if (this.#started) {
|
|
176
189
|
await this.stop()
|
|
177
190
|
this.server.log.info('server stopped')
|
package/lib/config.js
CHANGED
|
@@ -13,7 +13,15 @@ async function _transformConfig (configManager) {
|
|
|
13
13
|
const services = config.services ?? []
|
|
14
14
|
|
|
15
15
|
if (config.autoload) {
|
|
16
|
-
const {
|
|
16
|
+
const { exclude = [], mappings = {} } = config.autoload
|
|
17
|
+
let { path } = config.autoload
|
|
18
|
+
|
|
19
|
+
// This is a hack, but it's the only way to not fix the paths for the autoloaded services
|
|
20
|
+
// while we are upgrading the config
|
|
21
|
+
if (configManager._fixPaths) {
|
|
22
|
+
path = pathResolve(configManager.dirname, path)
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
const entries = await readdir(path, { withFileTypes: true })
|
|
18
26
|
|
|
19
27
|
for (let i = 0; i < entries.length; ++i) {
|
|
@@ -46,6 +54,7 @@ async function _transformConfig (configManager) {
|
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
configManager.current.allowCycles = !!configManager.current.allowCycles
|
|
57
|
+
|
|
49
58
|
configManager.current.serviceMap = new Map()
|
|
50
59
|
configManager.current.inspectorOptions = undefined
|
|
51
60
|
|
|
@@ -54,7 +63,9 @@ async function _transformConfig (configManager) {
|
|
|
54
63
|
for (let i = 0; i < services.length; ++i) {
|
|
55
64
|
const service = services[i]
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
if (configManager._fixPaths) {
|
|
67
|
+
service.config = pathResolve(service.path, service.config)
|
|
68
|
+
}
|
|
58
69
|
service.entrypoint = service.id === config.entrypoint
|
|
59
70
|
service.hotReload = !!config.hotReload
|
|
60
71
|
service.dependencies = []
|
|
@@ -74,6 +85,7 @@ async function _transformConfig (configManager) {
|
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
configManager.current.services = services
|
|
88
|
+
|
|
77
89
|
await parseClientsAndComposer(configManager)
|
|
78
90
|
|
|
79
91
|
if (!configManager.current.allowCycles) {
|
package/lib/management-api.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { tmpdir
|
|
3
|
+
const { tmpdir } = require('node:os')
|
|
4
|
+
const { platform } = require('node:os')
|
|
4
5
|
const { join } = require('node:path')
|
|
5
|
-
const {
|
|
6
|
+
const { mkdir, rm } = require('node:fs/promises')
|
|
6
7
|
const fastify = require('fastify')
|
|
7
8
|
const ws = require('ws')
|
|
9
|
+
const { getRuntimeLogsDir } = require('./api-client.js')
|
|
8
10
|
const errors = require('./errors')
|
|
9
|
-
const { pipeLogsStream, getLogFileStream, getLogIndexes } = require('./logs')
|
|
10
|
-
const platformaticVersion = require('../package.json').version
|
|
11
11
|
|
|
12
12
|
const PLATFORMATIC_TMP_DIR = join(tmpdir(), 'platformatic', 'runtimes')
|
|
13
|
-
const runtimeTmpDir = join(PLATFORMATIC_TMP_DIR, process.pid.toString())
|
|
14
13
|
|
|
15
|
-
async function createManagementApi (
|
|
14
|
+
async function createManagementApi (runtimeApiClient) {
|
|
16
15
|
const app = fastify()
|
|
17
16
|
app.log.warn(
|
|
18
17
|
'Runtime Management API is in the experimental stage. ' +
|
|
@@ -21,37 +20,15 @@ async function createManagementApi (configManager, runtimeApiClient) {
|
|
|
21
20
|
'Use of the feature is not recommended in production environments.'
|
|
22
21
|
)
|
|
23
22
|
|
|
24
|
-
async function getRuntimePackageJson (cwd) {
|
|
25
|
-
const packageJsonPath = join(cwd, 'package.json')
|
|
26
|
-
const packageJsonFile = await readFile(packageJsonPath, 'utf8')
|
|
27
|
-
const packageJson = JSON.parse(packageJsonFile)
|
|
28
|
-
return packageJson
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
app.register(require('@fastify/websocket'))
|
|
32
24
|
|
|
33
25
|
app.register(async (app) => {
|
|
34
26
|
app.get('/metadata', async () => {
|
|
35
|
-
|
|
36
|
-
const entrypointDetails = await runtimeApiClient.getEntrypointDetails().catch(() => null)
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
pid: process.pid,
|
|
40
|
-
cwd: process.cwd(),
|
|
41
|
-
argv: process.argv,
|
|
42
|
-
uptimeSeconds: Math.floor(process.uptime()),
|
|
43
|
-
execPath: process.execPath,
|
|
44
|
-
nodeVersion: process.version,
|
|
45
|
-
projectDir: configManager.dirname,
|
|
46
|
-
packageName: packageJson.name ?? null,
|
|
47
|
-
packageVersion: packageJson.version ?? null,
|
|
48
|
-
url: entrypointDetails?.url ?? null,
|
|
49
|
-
platformaticVersion
|
|
50
|
-
}
|
|
27
|
+
return runtimeApiClient.getRuntimeMetadata()
|
|
51
28
|
})
|
|
52
29
|
|
|
53
30
|
app.get('/config', async () => {
|
|
54
|
-
return
|
|
31
|
+
return runtimeApiClient.getRuntimeConfig()
|
|
55
32
|
})
|
|
56
33
|
|
|
57
34
|
app.get('/env', async () => {
|
|
@@ -100,6 +77,11 @@ async function createManagementApi (configManager, runtimeApiClient) {
|
|
|
100
77
|
const { id, '*': requestUrl } = request.params
|
|
101
78
|
app.log.debug('proxy request', { id, requestUrl })
|
|
102
79
|
|
|
80
|
+
delete request.headers.connection
|
|
81
|
+
delete request.headers['content-length']
|
|
82
|
+
delete request.headers['content-encoding']
|
|
83
|
+
delete request.headers['transfer-encoding']
|
|
84
|
+
|
|
103
85
|
const injectParams = {
|
|
104
86
|
method: request.method,
|
|
105
87
|
url: requestUrl || '/',
|
|
@@ -142,43 +124,67 @@ async function createManagementApi (configManager, runtimeApiClient) {
|
|
|
142
124
|
})
|
|
143
125
|
|
|
144
126
|
app.get('/logs/live', { websocket: true }, async (socket, req) => {
|
|
145
|
-
const
|
|
127
|
+
const startLogId = req.query.start ? parseInt(req.query.start) : null
|
|
146
128
|
|
|
147
|
-
if (
|
|
148
|
-
const
|
|
149
|
-
if (!
|
|
150
|
-
throw new errors.LogFileNotFound(
|
|
129
|
+
if (startLogId) {
|
|
130
|
+
const logIds = await runtimeApiClient.getLogIds()
|
|
131
|
+
if (!logIds.includes(startLogId)) {
|
|
132
|
+
throw new errors.LogFileNotFound(startLogId)
|
|
151
133
|
}
|
|
152
134
|
}
|
|
153
135
|
|
|
154
136
|
const stream = ws.createWebSocketStream(socket)
|
|
155
|
-
pipeLogsStream(stream, req.log,
|
|
137
|
+
runtimeApiClient.pipeLogsStream(stream, req.log, startLogId)
|
|
156
138
|
})
|
|
157
139
|
|
|
158
|
-
app.get('/logs/indexes', async () => {
|
|
159
|
-
const
|
|
160
|
-
|
|
140
|
+
app.get('/logs/indexes', async (req) => {
|
|
141
|
+
const returnAllIds = req.query.all === 'true'
|
|
142
|
+
|
|
143
|
+
const runtimesLogsIds = await runtimeApiClient.getLogIds()
|
|
144
|
+
if (returnAllIds) {
|
|
145
|
+
return runtimesLogsIds
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (runtimesLogsIds.length === 0) {
|
|
149
|
+
return []
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { indexes: runtimesLogsIds.at(-1).indexes }
|
|
161
153
|
})
|
|
162
154
|
|
|
163
155
|
app.get('/logs/all', async (req, reply) => {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
const
|
|
156
|
+
const runtimePID = parseInt(req.query.pid) || process.pid
|
|
157
|
+
|
|
158
|
+
const allLogIds = await runtimeApiClient.getLogIds()
|
|
159
|
+
const logsIds = allLogIds.find((logs) => logs.pid === runtimePID)
|
|
160
|
+
const startLogId = logsIds.indexes.at(0)
|
|
161
|
+
const endLogId = logsIds.indexes.at(-1)
|
|
167
162
|
|
|
168
163
|
reply.hijack()
|
|
169
|
-
|
|
164
|
+
|
|
165
|
+
runtimeApiClient.pipeLogsStream(
|
|
166
|
+
reply.raw,
|
|
167
|
+
req.log,
|
|
168
|
+
startLogId,
|
|
169
|
+
endLogId,
|
|
170
|
+
runtimePID
|
|
171
|
+
)
|
|
170
172
|
})
|
|
171
173
|
|
|
172
174
|
app.get('/logs/:id', async (req) => {
|
|
173
|
-
const
|
|
175
|
+
const logId = parseInt(req.params.id)
|
|
176
|
+
const runtimePID = parseInt(req.query.pid) || process.pid
|
|
174
177
|
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
if (!
|
|
178
|
-
throw new errors.LogFileNotFound(
|
|
178
|
+
const allLogIds = await runtimeApiClient.getLogIds()
|
|
179
|
+
const runtimeLogsIds = allLogIds.find((logs) => logs.pid === runtimePID)
|
|
180
|
+
if (!runtimeLogsIds || !runtimeLogsIds.indexes.includes(logId)) {
|
|
181
|
+
throw new errors.LogFileNotFound(logId)
|
|
179
182
|
}
|
|
180
183
|
|
|
181
|
-
const logFileStream = await getLogFileStream(
|
|
184
|
+
const logFileStream = await runtimeApiClient.getLogFileStream(
|
|
185
|
+
logId,
|
|
186
|
+
runtimePID
|
|
187
|
+
)
|
|
182
188
|
return logFileStream
|
|
183
189
|
})
|
|
184
190
|
}, { prefix: '/api/v1' })
|
|
@@ -186,35 +192,35 @@ async function createManagementApi (configManager, runtimeApiClient) {
|
|
|
186
192
|
return app
|
|
187
193
|
}
|
|
188
194
|
|
|
189
|
-
async function startManagementApi (
|
|
195
|
+
async function startManagementApi (runtimeApiClient, configManager) {
|
|
190
196
|
const runtimePID = process.pid
|
|
191
197
|
|
|
192
|
-
let socketPath = null
|
|
193
|
-
if (platform() === 'win32') {
|
|
194
|
-
socketPath = '\\\\.\\pipe\\platformatic-' + runtimePID
|
|
195
|
-
} else {
|
|
196
|
-
socketPath = join(runtimeTmpDir, 'socket')
|
|
197
|
-
}
|
|
198
|
-
|
|
199
198
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
await mkdir(runtimeTmpDir, { recursive: true })
|
|
199
|
+
const runtimePIDDir = join(PLATFORMATIC_TMP_DIR, runtimePID.toString())
|
|
200
|
+
if (platform() !== 'win32') {
|
|
201
|
+
await rm(runtimePIDDir, { recursive: true, force: true }).catch()
|
|
202
|
+
await mkdir(runtimePIDDir, { recursive: true })
|
|
203
|
+
}
|
|
206
204
|
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
)
|
|
205
|
+
const runtimeLogsDir = getRuntimeLogsDir(configManager.dirname, process.pid)
|
|
206
|
+
await rm(runtimeLogsDir, { recursive: true, force: true }).catch()
|
|
207
|
+
await mkdir(runtimeLogsDir, { recursive: true })
|
|
211
208
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
209
|
+
let socketPath = null
|
|
210
|
+
if (platform() === 'win32') {
|
|
211
|
+
socketPath = '\\\\.\\pipe\\platformatic-' + runtimePID.toString()
|
|
212
|
+
} else {
|
|
213
|
+
socketPath = join(runtimePIDDir, 'socket')
|
|
216
214
|
}
|
|
217
215
|
|
|
216
|
+
const managementApi = await createManagementApi(runtimeApiClient)
|
|
217
|
+
|
|
218
|
+
managementApi.addHook('onClose', async () => {
|
|
219
|
+
if (platform() !== 'win32') {
|
|
220
|
+
await rm(runtimePIDDir, { recursive: true, force: true }).catch()
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
218
224
|
await managementApi.listen({ path: socketPath })
|
|
219
225
|
return managementApi
|
|
220
226
|
/* c8 ignore next 4 */
|
package/lib/schema.js
CHANGED
|
@@ -10,6 +10,9 @@ const platformaticRuntimeSchema = {
|
|
|
10
10
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
11
11
|
type: 'object',
|
|
12
12
|
properties: {
|
|
13
|
+
$schema: {
|
|
14
|
+
type: 'string'
|
|
15
|
+
},
|
|
13
16
|
autoload: {
|
|
14
17
|
type: 'object',
|
|
15
18
|
additionalProperties: false,
|
|
@@ -49,29 +52,6 @@ const platformaticRuntimeSchema = {
|
|
|
49
52
|
},
|
|
50
53
|
telemetry,
|
|
51
54
|
server,
|
|
52
|
-
services: {
|
|
53
|
-
type: 'array',
|
|
54
|
-
default: [],
|
|
55
|
-
items: {
|
|
56
|
-
type: 'object',
|
|
57
|
-
required: ['id', 'path', 'config'],
|
|
58
|
-
properties: {
|
|
59
|
-
id: {
|
|
60
|
-
type: 'string'
|
|
61
|
-
},
|
|
62
|
-
path: {
|
|
63
|
-
type: 'string',
|
|
64
|
-
resolvePath: true
|
|
65
|
-
},
|
|
66
|
-
config: {
|
|
67
|
-
type: 'string'
|
|
68
|
-
},
|
|
69
|
-
useHttp: {
|
|
70
|
-
type: 'boolean'
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
55
|
entrypoint: {
|
|
76
56
|
type: 'string'
|
|
77
57
|
},
|
|
@@ -144,9 +124,6 @@ const platformaticRuntimeSchema = {
|
|
|
144
124
|
}
|
|
145
125
|
}
|
|
146
126
|
},
|
|
147
|
-
$schema: {
|
|
148
|
-
type: 'string'
|
|
149
|
-
},
|
|
150
127
|
managementApi: {
|
|
151
128
|
anyOf: [
|
|
152
129
|
{ type: 'boolean' },
|
|
@@ -194,6 +171,28 @@ const platformaticRuntimeSchema = {
|
|
|
194
171
|
additionalProperties: false
|
|
195
172
|
}
|
|
196
173
|
]
|
|
174
|
+
},
|
|
175
|
+
services: {
|
|
176
|
+
type: 'array',
|
|
177
|
+
items: {
|
|
178
|
+
type: 'object',
|
|
179
|
+
required: ['id', 'path', 'config'],
|
|
180
|
+
properties: {
|
|
181
|
+
id: {
|
|
182
|
+
type: 'string'
|
|
183
|
+
},
|
|
184
|
+
path: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
resolvePath: true
|
|
187
|
+
},
|
|
188
|
+
config: {
|
|
189
|
+
type: 'string'
|
|
190
|
+
},
|
|
191
|
+
useHttp: {
|
|
192
|
+
type: 'boolean'
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
197
196
|
}
|
|
198
197
|
},
|
|
199
198
|
anyOf: [
|
package/lib/start.js
CHANGED
|
@@ -13,7 +13,7 @@ const { loadConfig } = require('./load-config')
|
|
|
13
13
|
const { startManagementApi } = require('./management-api')
|
|
14
14
|
const { startPrometheusServer } = require('./prom-server.js')
|
|
15
15
|
const { parseInspectorOptions, wrapConfigInRuntimeConfig } = require('./config')
|
|
16
|
-
const RuntimeApiClient = require('./api-client.js')
|
|
16
|
+
const { RuntimeApiClient, getRuntimeLogsDir } = require('./api-client.js')
|
|
17
17
|
const errors = require('./errors')
|
|
18
18
|
const pkg = require('../package.json')
|
|
19
19
|
|
|
@@ -41,6 +41,7 @@ async function buildRuntime (configManager, env = process.env) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const dirname = configManager.dirname
|
|
44
|
+
const runtimeLogsDir = getRuntimeLogsDir(dirname, process.pid)
|
|
44
45
|
|
|
45
46
|
// The configManager cannot be transferred to the worker, so remove it.
|
|
46
47
|
delete config.configManager
|
|
@@ -49,7 +50,7 @@ async function buildRuntime (configManager, env = process.env) {
|
|
|
49
50
|
/* c8 ignore next */
|
|
50
51
|
execArgv: config.hotReload ? kWorkerExecArgv : [],
|
|
51
52
|
transferList: config.loggingPort ? [config.loggingPort] : [],
|
|
52
|
-
workerData: { config, dirname },
|
|
53
|
+
workerData: { config, dirname, runtimeLogsDir },
|
|
53
54
|
env
|
|
54
55
|
})
|
|
55
56
|
|
|
@@ -101,13 +102,14 @@ async function buildRuntime (configManager, env = process.env) {
|
|
|
101
102
|
|
|
102
103
|
await once(worker, 'message') // plt:init
|
|
103
104
|
|
|
104
|
-
const runtimeApiClient = new RuntimeApiClient(
|
|
105
|
+
const runtimeApiClient = new RuntimeApiClient(
|
|
106
|
+
worker,
|
|
107
|
+
configManager,
|
|
108
|
+
runtimeLogsDir
|
|
109
|
+
)
|
|
105
110
|
|
|
106
111
|
if (config.managementApi) {
|
|
107
|
-
managementApi = await startManagementApi(
|
|
108
|
-
configManager,
|
|
109
|
-
runtimeApiClient
|
|
110
|
-
)
|
|
112
|
+
managementApi = await startManagementApi(runtimeApiClient, configManager)
|
|
111
113
|
runtimeApiClient.managementApi = managementApi
|
|
112
114
|
runtimeApiClient.on('start', () => {
|
|
113
115
|
runtimeApiClient.startCollectingMetrics()
|
package/lib/worker.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const inspector = require('node:inspector')
|
|
4
|
-
const { tmpdir } = require('node:os')
|
|
5
4
|
const { register, createRequire } = require('node:module')
|
|
6
5
|
const { isatty } = require('node:tty')
|
|
7
6
|
const { join } = require('node:path')
|
|
@@ -19,8 +18,6 @@ const { MessagePortWritable } = require('./message-port-writable')
|
|
|
19
18
|
const loadInterceptors = require('./interceptors')
|
|
20
19
|
let loaderPort
|
|
21
20
|
|
|
22
|
-
const PLATFORMATIC_TMP_DIR = join(tmpdir(), 'platformatic', 'runtimes')
|
|
23
|
-
|
|
24
21
|
if (typeof register === 'function' && workerData.config.loaderFile) {
|
|
25
22
|
const { port1, port2 } = new MessageChannel()
|
|
26
23
|
register(workerData.config.loaderFile, {
|
|
@@ -69,7 +66,7 @@ function createLogger (config) {
|
|
|
69
66
|
logsLimitCount = 1
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
const logsPath = join(
|
|
69
|
+
const logsPath = join(workerData.runtimeLogsDir, 'logs')
|
|
73
70
|
const pinoRoll = pino.transport({
|
|
74
71
|
target: 'pino-roll',
|
|
75
72
|
options: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@fastify/express": "^2.3.0",
|
|
21
21
|
"@fastify/formbody": "^7.4.0",
|
|
22
22
|
"@matteo.collina/tspl": "^0.1.1",
|
|
23
|
-
"borp": "^0.
|
|
23
|
+
"borp": "^0.11.0",
|
|
24
24
|
"c8": "^9.1.0",
|
|
25
25
|
"execa": "^8.0.1",
|
|
26
26
|
"express": "^4.18.3",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"typescript": "^5.4.2",
|
|
34
34
|
"undici-oidc-interceptor": "^0.5.0",
|
|
35
35
|
"why-is-node-running": "^2.2.2",
|
|
36
|
-
"@platformatic/sql-
|
|
37
|
-
"@platformatic/sql-
|
|
36
|
+
"@platformatic/sql-mapper": "1.35.1",
|
|
37
|
+
"@platformatic/sql-graphql": "1.35.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"ws": "^8.16.0",
|
|
@@ -62,13 +62,13 @@
|
|
|
62
62
|
"tail-file-stream": "^0.1.0",
|
|
63
63
|
"undici": "^6.9.0",
|
|
64
64
|
"why-is-node-running": "^2.2.2",
|
|
65
|
-
"@platformatic/composer": "1.
|
|
66
|
-
"@platformatic/
|
|
67
|
-
"@platformatic/db": "1.
|
|
68
|
-
"@platformatic/
|
|
69
|
-
"@platformatic/
|
|
70
|
-
"@platformatic/
|
|
71
|
-
"@platformatic/utils": "1.
|
|
65
|
+
"@platformatic/composer": "1.35.1",
|
|
66
|
+
"@platformatic/generators": "1.35.1",
|
|
67
|
+
"@platformatic/db": "1.35.1",
|
|
68
|
+
"@platformatic/config": "1.35.1",
|
|
69
|
+
"@platformatic/telemetry": "1.35.1",
|
|
70
|
+
"@platformatic/service": "1.35.1",
|
|
71
|
+
"@platformatic/utils": "1.35.1"
|
|
72
72
|
},
|
|
73
73
|
"standard": {
|
|
74
74
|
"ignore": [
|
package/lib/logs.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { tmpdir } = require('node:os')
|
|
4
|
-
const { join } = require('node:path')
|
|
5
|
-
const { createReadStream, watch } = require('node:fs')
|
|
6
|
-
const { readdir } = require('node:fs/promises')
|
|
7
|
-
const ts = require('tail-file-stream')
|
|
8
|
-
|
|
9
|
-
const PLATFORMATIC_TMP_DIR = join(tmpdir(), 'platformatic', 'runtimes')
|
|
10
|
-
const runtimeTmpDir = join(PLATFORMATIC_TMP_DIR, process.pid.toString())
|
|
11
|
-
|
|
12
|
-
async function getLogFiles () {
|
|
13
|
-
const runtimeTmpFiles = await readdir(runtimeTmpDir)
|
|
14
|
-
const runtimeLogFiles = runtimeTmpFiles
|
|
15
|
-
.filter((file) => file.startsWith('logs'))
|
|
16
|
-
.sort((log1, log2) => {
|
|
17
|
-
const index1 = parseInt(log1.slice('logs.'.length))
|
|
18
|
-
const index2 = parseInt(log2.slice('logs.'.length))
|
|
19
|
-
return index1 - index2
|
|
20
|
-
})
|
|
21
|
-
return runtimeLogFiles
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function pipeLogsStream (writableStream, logger, startLogIndex, endLogIndex) {
|
|
25
|
-
endLogIndex = endLogIndex || Infinity
|
|
26
|
-
|
|
27
|
-
const runtimeLogFiles = await getLogFiles()
|
|
28
|
-
if (runtimeLogFiles.length === 0) {
|
|
29
|
-
writableStream.end()
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let latestFileIndex = parseInt(runtimeLogFiles.at(-1).slice('logs.'.length))
|
|
34
|
-
|
|
35
|
-
let waiting = false
|
|
36
|
-
let fileStream = null
|
|
37
|
-
let fileIndex = startLogIndex ?? latestFileIndex
|
|
38
|
-
|
|
39
|
-
const watcher = watch(runtimeTmpDir, async (event, filename) => {
|
|
40
|
-
if (event === 'rename' && filename.startsWith('logs')) {
|
|
41
|
-
const logFileIndex = parseInt(filename.slice('logs.'.length))
|
|
42
|
-
if (logFileIndex > latestFileIndex) {
|
|
43
|
-
latestFileIndex = logFileIndex
|
|
44
|
-
if (waiting) {
|
|
45
|
-
streamLogFile(++fileIndex)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}).unref()
|
|
50
|
-
|
|
51
|
-
const streamLogFile = () => {
|
|
52
|
-
if (fileIndex > endLogIndex) {
|
|
53
|
-
writableStream.end()
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const fileName = 'logs.' + fileIndex
|
|
58
|
-
const filePath = join(runtimeTmpDir, fileName)
|
|
59
|
-
|
|
60
|
-
const prevFileStream = fileStream
|
|
61
|
-
|
|
62
|
-
fileStream = ts.createReadStream(filePath)
|
|
63
|
-
fileStream.pipe(writableStream, { end: false })
|
|
64
|
-
|
|
65
|
-
if (prevFileStream) {
|
|
66
|
-
prevFileStream.unpipe(writableStream)
|
|
67
|
-
prevFileStream.destroy()
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
fileStream.on('error', (err) => {
|
|
71
|
-
logger.error(err, 'Error streaming log file')
|
|
72
|
-
fileStream.destroy()
|
|
73
|
-
watcher.close()
|
|
74
|
-
writableStream.end()
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
fileStream.on('data', () => {
|
|
78
|
-
waiting = false
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
fileStream.on('eof', () => {
|
|
82
|
-
if (fileIndex >= endLogIndex) {
|
|
83
|
-
writableStream.end()
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
if (latestFileIndex > fileIndex) {
|
|
87
|
-
streamLogFile(++fileIndex)
|
|
88
|
-
} else {
|
|
89
|
-
waiting = true
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
return fileStream
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
streamLogFile(fileIndex)
|
|
97
|
-
|
|
98
|
-
writableStream.on('close', () => {
|
|
99
|
-
watcher.close()
|
|
100
|
-
fileStream.destroy()
|
|
101
|
-
})
|
|
102
|
-
writableStream.on('error', () => {
|
|
103
|
-
watcher.close()
|
|
104
|
-
fileStream.destroy()
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function getLogIndexes () {
|
|
109
|
-
const runtimeLogFiles = await getLogFiles()
|
|
110
|
-
return runtimeLogFiles
|
|
111
|
-
.map((file) => parseInt(file.slice('logs.'.length)))
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function getLogFileStream (logFileIndex) {
|
|
115
|
-
const filePath = join(runtimeTmpDir, `logs.${logFileIndex}`)
|
|
116
|
-
return createReadStream(filePath)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
module.exports = {
|
|
120
|
-
pipeLogsStream,
|
|
121
|
-
getLogFileStream,
|
|
122
|
-
getLogIndexes
|
|
123
|
-
}
|