@platformatic/runtime 3.27.0 → 3.28.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 CHANGED
@@ -48,6 +48,7 @@ export type PlatformaticRuntimeConfig = {
48
48
  };
49
49
  envfile?: string;
50
50
  sourceMaps?: boolean;
51
+ nodeModulesSourceMaps?: string[];
51
52
  packageManager?: "npm" | "pnpm" | "yarn";
52
53
  preload?: string | string[];
53
54
  nodeOptions?: string;
@@ -473,6 +474,7 @@ export type PlatformaticRuntimeConfig = {
473
474
  [k: string]: string;
474
475
  };
475
476
  sourceMaps?: boolean;
477
+ nodeModulesSourceMaps?: string[];
476
478
  scheduler?: {
477
479
  enabled?: boolean | string;
478
480
  name: string;
@@ -288,6 +288,44 @@ export async function managementApiPlugin (app, opts) {
288
288
  app.get('/logs/live', { websocket: true }, async socket => {
289
289
  runtime.addLoggerDestination(createWebSocketStream(socket))
290
290
  })
291
+
292
+ app.get('/applications/:id/repl', { websocket: true }, async (socket, request) => {
293
+ const { id } = request.params
294
+
295
+ try {
296
+ // Start REPL and get the communication port
297
+ const port = await runtime.startApplicationRepl(id)
298
+
299
+ // Forward messages between WebSocket and MessagePort
300
+ port.on('message', (message) => {
301
+ if (message.type === 'output') {
302
+ socket.send(message.data)
303
+ } else if (message.type === 'exit') {
304
+ socket.close()
305
+ }
306
+ })
307
+
308
+ socket.on('message', (data) => {
309
+ port.postMessage({ type: 'input', data: data.toString() })
310
+ })
311
+
312
+ socket.on('close', () => {
313
+ port.postMessage({ type: 'close' })
314
+ port.close()
315
+ })
316
+
317
+ socket.on('error', () => {
318
+ port.postMessage({ type: 'close' })
319
+ port.close()
320
+ })
321
+ } catch (error) {
322
+ socket.send(JSON.stringify({
323
+ error: error.message,
324
+ code: error.code
325
+ }))
326
+ socket.close()
327
+ }
328
+ })
291
329
  }
292
330
 
293
331
  export async function startManagementApi (runtime) {
package/lib/runtime.js CHANGED
@@ -679,6 +679,19 @@ export class Runtime extends EventEmitter {
679
679
  return sendViaITC(service, 'stopProfiling', options)
680
680
  }
681
681
 
682
+ async startApplicationRepl (id, ensureStarted = true) {
683
+ const service = await this.#getApplicationById(id, ensureStarted)
684
+
685
+ // Create a MessageChannel for REPL communication
686
+ const { port1, port2 } = new MessageChannel()
687
+
688
+ // Send port1 to the worker to start the REPL
689
+ await sendViaITC(service, 'startRepl', port1, [port1])
690
+
691
+ // Return port2 for the caller to use
692
+ return port2
693
+ }
694
+
682
695
  async updateUndiciInterceptors (undiciConfig) {
683
696
  this.#config.undici = undiciConfig
684
697
 
@@ -885,6 +898,10 @@ export class Runtime extends EventEmitter {
885
898
  this.#concurrency = concurrency
886
899
  }
887
900
 
901
+ getRoot () {
902
+ return this.#root
903
+ }
904
+
888
905
  getUrl () {
889
906
  return this.#url
890
907
  }
@@ -2081,6 +2098,14 @@ export class Runtime extends EventEmitter {
2081
2098
  const label = this.#workerExtendedLabel(applicationId, index, workersCount)
2082
2099
  let newWorker
2083
2100
 
2101
+ const stopBeforeStart =
2102
+ applicationConfig.entrypoint &&
2103
+ (config.reuseTcpPorts === false || applicationConfig.reuseTcpPorts === false || !features.node.reusePort)
2104
+
2105
+ if (stopBeforeStart) {
2106
+ await this.#removeWorker(workersCount, applicationId, index, worker, silent, label)
2107
+ }
2108
+
2084
2109
  try {
2085
2110
  if (!silent) {
2086
2111
  this.logger.debug(`Preparing to start a replacement for ${label} ...`)
@@ -2104,17 +2129,25 @@ export class Runtime extends EventEmitter {
2104
2129
 
2105
2130
  this.#workers.set(workerId, newWorker)
2106
2131
  this.#meshInterceptor.route(applicationId, newWorker)
2107
-
2108
- // Remove the old worker and then kill it
2109
- await sendViaITC(worker, 'removeFromMesh')
2110
2132
  } catch (e) {
2111
2133
  newWorker?.terminate?.()
2112
2134
  throw e
2113
2135
  }
2114
2136
 
2137
+ if (!stopBeforeStart) {
2138
+ await this.#removeWorker(workersCount, applicationId, index, worker, silent, label)
2139
+ }
2140
+ }
2141
+
2142
+ async #removeWorker (workersCount, applicationId, index, worker, silent, label) {
2115
2143
  if (!silent) {
2116
2144
  this.logger.debug(`Preparing to stop the old version of ${label} ...`)
2117
2145
  }
2146
+
2147
+ // Remove the old worker and then kill it
2148
+ await sendViaITC(worker, 'removeFromMesh')
2149
+
2150
+ // Stop the old worker to free the port
2118
2151
  await this.#stopWorker(workersCount, applicationId, index, false, worker, [])
2119
2152
  }
2120
2153
 
package/lib/worker/itc.js CHANGED
@@ -2,6 +2,8 @@ import { ensureLoggableError, executeInParallel, executeWithTimeout, kTimeout }
2
2
  import { ITC } from '@platformatic/itc'
3
3
  import { Unpromise } from '@watchable/unpromise'
4
4
  import { once } from 'node:events'
5
+ import repl from 'node:repl'
6
+ import { Duplex } from 'node:stream'
5
7
  import { parentPort, workerData } from 'node:worker_threads'
6
8
  import {
7
9
  ApplicationExitedError,
@@ -261,6 +263,55 @@ export function setupITC (controller, application, dispatcher, sharedContext) {
261
263
 
262
264
  saveMessagingChannel (channel) {
263
265
  messaging.addSource(channel)
266
+ },
267
+
268
+ startRepl (port) {
269
+ // Create a duplex stream that wraps the MessagePort
270
+ const replStream = new Duplex({
271
+ read () {},
272
+ write (chunk, encoding, callback) {
273
+ port.postMessage({ type: 'output', data: chunk.toString() })
274
+ callback()
275
+ }
276
+ })
277
+
278
+ port.on('message', (message) => {
279
+ if (message.type === 'input') {
280
+ replStream.push(message.data)
281
+ } else if (message.type === 'close') {
282
+ replStream.push(null)
283
+ }
284
+ })
285
+
286
+ port.on('close', () => {
287
+ replStream.push(null)
288
+ })
289
+
290
+ // Start the REPL with the stream
291
+ const replServer = repl.start({
292
+ prompt: `${controller.applicationConfig.id}> `,
293
+ input: replStream,
294
+ output: replStream,
295
+ terminal: false,
296
+ useColors: true,
297
+ ignoreUndefined: true,
298
+ preview: false
299
+ })
300
+
301
+ // Expose useful context
302
+ // For service-based capabilities, expose the Fastify app
303
+ replServer.context.app = controller.capability?.getApplication?.()
304
+ replServer.context.capability = controller.capability
305
+ replServer.context.platformatic = globalThis.platformatic
306
+ replServer.context.config = controller.applicationConfig
307
+ replServer.context.logger = globalThis.platformatic?.logger
308
+
309
+ replServer.on('exit', () => {
310
+ port.postMessage({ type: 'exit' })
311
+ port.close()
312
+ })
313
+
314
+ return { started: true }
264
315
  }
265
316
  }
266
317
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "3.27.0",
3
+ "version": "3.28.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -35,14 +35,14 @@
35
35
  "typescript": "^5.5.4",
36
36
  "undici-oidc-interceptor": "^0.5.0",
37
37
  "why-is-node-running": "^2.2.2",
38
- "@platformatic/composer": "3.27.0",
39
- "@platformatic/gateway": "3.27.0",
40
- "@platformatic/db": "3.27.0",
41
- "@platformatic/service": "3.27.0",
42
- "@platformatic/node": "3.27.0",
43
- "@platformatic/sql-graphql": "3.27.0",
44
- "@platformatic/sql-mapper": "3.27.0",
45
- "@platformatic/wattpm-pprof-capture": "3.27.0"
38
+ "@platformatic/composer": "3.28.0",
39
+ "@platformatic/db": "3.28.0",
40
+ "@platformatic/gateway": "3.28.0",
41
+ "@platformatic/node": "3.28.0",
42
+ "@platformatic/sql-graphql": "3.28.0",
43
+ "@platformatic/service": "3.28.0",
44
+ "@platformatic/sql-mapper": "3.28.0",
45
+ "@platformatic/wattpm-pprof-capture": "3.28.0"
46
46
  },
47
47
  "dependencies": {
48
48
  "@fastify/accepts": "^5.0.0",
@@ -71,12 +71,12 @@
71
71
  "undici": "^7.0.0",
72
72
  "undici-thread-interceptor": "^0.15.0",
73
73
  "ws": "^8.16.0",
74
- "@platformatic/basic": "3.27.0",
75
- "@platformatic/foundation": "3.27.0",
76
- "@platformatic/generators": "3.27.0",
77
- "@platformatic/metrics": "3.27.0",
78
- "@platformatic/itc": "3.27.0",
79
- "@platformatic/telemetry": "3.27.0"
74
+ "@platformatic/basic": "3.28.0",
75
+ "@platformatic/generators": "3.28.0",
76
+ "@platformatic/foundation": "3.28.0",
77
+ "@platformatic/itc": "3.28.0",
78
+ "@platformatic/metrics": "3.28.0",
79
+ "@platformatic/telemetry": "3.28.0"
80
80
  },
81
81
  "engines": {
82
82
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.27.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/runtime/3.28.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",
@@ -227,6 +227,12 @@
227
227
  "sourceMaps": {
228
228
  "type": "boolean"
229
229
  },
230
+ "nodeModulesSourceMaps": {
231
+ "type": "array",
232
+ "items": {
233
+ "type": "string"
234
+ }
235
+ },
230
236
  "packageManager": {
231
237
  "type": "string",
232
238
  "enum": [
@@ -525,6 +531,12 @@
525
531
  "sourceMaps": {
526
532
  "type": "boolean"
527
533
  },
534
+ "nodeModulesSourceMaps": {
535
+ "type": "array",
536
+ "items": {
537
+ "type": "string"
538
+ }
539
+ },
528
540
  "packageManager": {
529
541
  "type": "string",
530
542
  "enum": [
@@ -821,6 +833,12 @@
821
833
  "sourceMaps": {
822
834
  "type": "boolean"
823
835
  },
836
+ "nodeModulesSourceMaps": {
837
+ "type": "array",
838
+ "items": {
839
+ "type": "string"
840
+ }
841
+ },
824
842
  "packageManager": {
825
843
  "type": "string",
826
844
  "enum": [
@@ -1117,6 +1135,12 @@
1117
1135
  "sourceMaps": {
1118
1136
  "type": "boolean"
1119
1137
  },
1138
+ "nodeModulesSourceMaps": {
1139
+ "type": "array",
1140
+ "items": {
1141
+ "type": "string"
1142
+ }
1143
+ },
1120
1144
  "packageManager": {
1121
1145
  "type": "string",
1122
1146
  "enum": [
@@ -2388,6 +2412,13 @@
2388
2412
  "type": "boolean",
2389
2413
  "default": false
2390
2414
  },
2415
+ "nodeModulesSourceMaps": {
2416
+ "type": "array",
2417
+ "items": {
2418
+ "type": "string"
2419
+ },
2420
+ "default": []
2421
+ },
2391
2422
  "scheduler": {
2392
2423
  "type": "array",
2393
2424
  "items": {