@platformatic/runtime 0.26.1 → 0.28.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/lib/worker.js CHANGED
@@ -1,8 +1,11 @@
1
1
  'use strict'
2
+
2
3
  const { parentPort, workerData } = require('node:worker_threads')
3
4
  const FastifyUndiciDispatcher = require('fastify-undici-dispatcher')
4
5
  const { Agent, setGlobalDispatcher } = require('undici')
5
6
  const { PlatformaticApp } = require('./app')
7
+ const { RuntimeApi } = require('./api')
8
+
6
9
  const loaderPort = globalThis.LOADER_PORT // Added by loader.mjs.
7
10
  const globalAgent = new Agent()
8
11
  const globalDispatcher = new FastifyUndiciDispatcher({
@@ -10,41 +13,36 @@ const globalDispatcher = new FastifyUndiciDispatcher({
10
13
  // setting the domain here allows for fail-fast scenarios
11
14
  domain: '.plt.local'
12
15
  })
16
+ const pino = require('pino')
17
+ const { isatty } = require('tty')
18
+
13
19
  const applications = new Map()
14
- let entrypoint
15
20
 
16
21
  delete globalThis.LOADER_PORT
17
22
  setGlobalDispatcher(globalDispatcher)
18
23
 
19
- parentPort.on('message', async (msg) => {
20
- for (const app of applications.values()) {
21
- await app.handleProcessLevelEvent(msg)
24
+ let transport
22
25
 
23
- if (msg?.msg === 'plt:start' || msg?.msg === 'plt:restart') {
24
- const serviceUrl = new URL(app.appConfig.localUrl)
26
+ /* c8 ignore next 5 */
27
+ if (isatty(1)) {
28
+ transport = pino.transport({
29
+ target: 'pino-pretty'
30
+ })
31
+ }
25
32
 
26
- globalDispatcher.route(serviceUrl.host, app.server)
27
- }
28
- }
33
+ const logger = pino(transport)
29
34
 
30
- switch (msg?.msg) {
31
- case 'plt:start':
32
- configureDispatcher()
33
- parentPort.postMessage({ msg: 'plt:started', url: entrypoint.server.url })
34
- break
35
- case 'plt:restart':
36
- configureDispatcher()
37
- parentPort.postMessage({ msg: 'plt:restarted', url: entrypoint.server.url })
38
- break
39
- case 'plt:stop':
40
- process.exit() // Exit the worker thread.
41
- break
42
- case undefined:
43
- // Ignore
44
- break
45
- default:
46
- throw new Error(`unknown message type: '${msg.msg}'`)
47
- }
35
+ /* c8 ignore next 4 */
36
+ process.once('uncaughtException', (err) => {
37
+ logger.error({ err }, 'runtime error')
38
+ throw err
39
+ })
40
+
41
+ // Tested by test/cli/start.test.mjs by C8 does not see it.
42
+ /* c8 ignore next 4 */
43
+ process.once('unhandledRejection', (err) => {
44
+ logger.error({ err }, 'runtime error')
45
+ throw err
48
46
  })
49
47
 
50
48
  async function main () {
@@ -52,37 +50,15 @@ async function main () {
52
50
 
53
51
  for (let i = 0; i < services.length; ++i) {
54
52
  const service = services[i]
55
- const app = new PlatformaticApp(service, loaderPort)
53
+ const app = new PlatformaticApp(service, loaderPort, logger)
56
54
 
57
55
  applications.set(service.id, app)
58
-
59
- if (service.entrypoint) {
60
- entrypoint = app
61
- }
62
56
  }
63
57
 
64
- parentPort.postMessage('plt:init')
65
- }
66
-
67
- function configureDispatcher () {
68
- const { services } = workerData.config
69
-
70
- // Setup the local services in the global dispatcher.
71
- for (let i = 0; i < services.length; ++i) {
72
- const service = services[i]
73
- const serviceApp = applications.get(service.id)
74
- const serviceUrl = new URL(service.localUrl)
75
-
76
- globalDispatcher.route(serviceUrl.host, serviceApp.server)
77
-
78
- for (let j = 0; j < service.dependencies.length; ++j) {
79
- const depConfig = service.dependencies[j]
80
- const depApp = applications.get(depConfig.id)
81
- const depUrl = new URL(depConfig.url)
58
+ const runtime = new RuntimeApi(applications, globalDispatcher)
59
+ runtime.startListening(parentPort)
82
60
 
83
- globalDispatcher.route(depUrl.host, depApp.server)
84
- }
85
- }
61
+ parentPort.postMessage('plt:init')
86
62
  }
87
63
 
88
64
  main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "0.26.1",
3
+ "version": "0.28.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -17,12 +17,12 @@
17
17
  },
18
18
  "homepage": "https://github.com/platformatic/platformatic#readme",
19
19
  "devDependencies": {
20
- "c8": "^7.13.0",
21
- "execa": "^7.0.0",
20
+ "c8": "^8.0.0",
21
+ "execa": "^7.1.1",
22
22
  "snazzy": "^9.0.0",
23
- "split2": "^4.1.0",
24
- "standard": "^17.0.0",
25
- "tsd": "^0.28.0"
23
+ "split2": "^4.2.0",
24
+ "standard": "^17.1.0",
25
+ "tsd": "^0.28.1"
26
26
  },
27
27
  "dependencies": {
28
28
  "@hapi/topo": "^6.0.2",
@@ -30,15 +30,18 @@
30
30
  "commist": "^3.2.0",
31
31
  "desm": "^1.3.0",
32
32
  "es-main": "^1.2.0",
33
- "fastify": "^4.17.0",
34
- "fastify-undici-dispatcher": "^0.4.0",
33
+ "fastify": "^4.18.0",
34
+ "fastify-undici-dispatcher": "^0.4.1",
35
35
  "help-me": "^4.2.0",
36
36
  "minimist": "^1.2.8",
37
- "undici": "^5.20.0",
38
- "@platformatic/config": "0.26.1",
39
- "@platformatic/service": "0.26.1",
40
- "@platformatic/start": "0.26.1",
41
- "@platformatic/utils": "0.26.1"
37
+ "pino": "^8.14.1",
38
+ "pino-pretty": "^10.0.0",
39
+ "undici": "^5.22.1",
40
+ "@platformatic/composer": "0.28.1",
41
+ "@platformatic/config": "0.28.1",
42
+ "@platformatic/db": "0.28.1",
43
+ "@platformatic/service": "0.28.1",
44
+ "@platformatic/utils": "0.28.1"
42
45
  },
43
46
  "standard": {
44
47
  "ignore": [
@@ -0,0 +1,294 @@
1
+ 'use strict'
2
+
3
+ const assert = require('node:assert')
4
+ const { join } = require('node:path')
5
+ const { test } = require('node:test')
6
+
7
+ const { loadConfig } = require('@platformatic/service')
8
+ const { buildServer, platformaticRuntime } = require('..')
9
+ const fixturesDir = join(__dirname, '..', 'fixtures')
10
+
11
+ // Each test runtime app adds own process listeners
12
+ process.setMaxListeners(100)
13
+
14
+ test('should get service details', async (t) => {
15
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
16
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
17
+ const app = await buildServer(config.configManager.current)
18
+
19
+ await app.start()
20
+
21
+ t.after(async () => {
22
+ await app.close()
23
+ })
24
+
25
+ const serviceDetails = await app.getServiceDetails('with-logger')
26
+ assert.deepStrictEqual(serviceDetails, {
27
+ id: 'with-logger',
28
+ status: 'started',
29
+ entrypoint: false,
30
+ localUrl: 'http://with-logger.plt.local',
31
+ dependencies: []
32
+ })
33
+ })
34
+
35
+ test('should get service config', async (t) => {
36
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
37
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
38
+ const app = await buildServer(config.configManager.current)
39
+
40
+ await app.start()
41
+
42
+ t.after(async () => {
43
+ await app.close()
44
+ })
45
+
46
+ const serviceConfig = await app.getServiceConfig('with-logger')
47
+
48
+ // TODO: should return correct logger config
49
+ assert.deepStrictEqual(serviceConfig, {
50
+ $schema: 'https://platformatic.dev/schemas/v0.27.0/service',
51
+ server: {
52
+ hostname: '127.0.0.1',
53
+ port: 0,
54
+ logger: {},
55
+ keepAliveTimeout: 5000
56
+ },
57
+ service: { openapi: true },
58
+ plugins: {
59
+ paths: [
60
+ join(fixturesDir, 'monorepo', 'serviceAppWithLogger', 'plugin.js')
61
+ ]
62
+ },
63
+ watch: false
64
+ })
65
+ })
66
+
67
+ test('should fail to get service config if service is not started', async (t) => {
68
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
69
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
70
+ const app = await buildServer(config.configManager.current)
71
+
72
+ t.after(async () => {
73
+ await app.close()
74
+ })
75
+
76
+ try {
77
+ await app.getServiceConfig('with-logger')
78
+ assert.fail('should have thrown')
79
+ } catch (err) {
80
+ assert.strictEqual(err.message, 'Service with id \'with-logger\' is not started')
81
+ }
82
+ })
83
+
84
+ test('should get services topology', async (t) => {
85
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
86
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
87
+ const app = await buildServer(config.configManager.current)
88
+
89
+ await app.start()
90
+
91
+ t.after(async () => {
92
+ await app.close()
93
+ })
94
+
95
+ const topology = await app.getServices()
96
+
97
+ assert.deepStrictEqual(topology, {
98
+ entrypoint: 'serviceApp',
99
+ services: [
100
+ {
101
+ id: 'serviceApp',
102
+ status: 'started',
103
+ entrypoint: true,
104
+ localUrl: 'http://serviceApp.plt.local',
105
+ dependencies: [
106
+ {
107
+ id: 'with-logger',
108
+ url: 'http://with-logger.plt.local',
109
+ local: true
110
+ }
111
+ ]
112
+ },
113
+ {
114
+ id: 'with-logger',
115
+ status: 'started',
116
+ entrypoint: false,
117
+ localUrl: 'http://with-logger.plt.local',
118
+ dependencies: []
119
+ },
120
+ {
121
+ id: 'multi-plugin-service',
122
+ status: 'started',
123
+ entrypoint: false,
124
+ localUrl: 'http://multi-plugin-service.plt.local',
125
+ dependencies: []
126
+ }
127
+ ]
128
+ })
129
+ })
130
+
131
+ test('should stop service by service id', async (t) => {
132
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
133
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
134
+ const app = await buildServer(config.configManager.current)
135
+
136
+ await app.start()
137
+
138
+ t.after(async () => {
139
+ await app.close()
140
+ })
141
+
142
+ {
143
+ const serviceDetails = await app.getServiceDetails('with-logger')
144
+ assert.strictEqual(serviceDetails.status, 'started')
145
+ }
146
+
147
+ await app.stopService('with-logger')
148
+
149
+ {
150
+ const serviceDetails = await app.getServiceDetails('with-logger')
151
+ assert.strictEqual(serviceDetails.status, 'stopped')
152
+ }
153
+ })
154
+
155
+ test('should fail to stop service with a wrong id', async (t) => {
156
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
157
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
158
+ const app = await buildServer(config.configManager.current)
159
+
160
+ t.after(async () => {
161
+ await app.close()
162
+ })
163
+
164
+ try {
165
+ await app.stopService('wrong-service-id')
166
+ assert.fail('should have thrown')
167
+ } catch (err) {
168
+ assert.strictEqual(err.message, 'Service with id \'wrong-service-id\' not found')
169
+ }
170
+ })
171
+
172
+ test('should start stopped service by service id', async (t) => {
173
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
174
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
175
+ const app = await buildServer(config.configManager.current)
176
+
177
+ await app.start()
178
+
179
+ t.after(async () => {
180
+ await app.close()
181
+ })
182
+
183
+ await app.stopService('with-logger')
184
+
185
+ {
186
+ const serviceDetails = await app.getServiceDetails('with-logger')
187
+ assert.strictEqual(serviceDetails.status, 'stopped')
188
+ }
189
+
190
+ await app.startService('with-logger')
191
+
192
+ {
193
+ const serviceDetails = await app.getServiceDetails('with-logger')
194
+ assert.strictEqual(serviceDetails.status, 'started')
195
+ }
196
+ })
197
+
198
+ test('should fail to start service with a wrong id', async (t) => {
199
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
200
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
201
+ const app = await buildServer(config.configManager.current)
202
+
203
+ t.after(async () => {
204
+ await app.close()
205
+ })
206
+
207
+ try {
208
+ await app.startService('wrong-service-id')
209
+ assert.fail('should have thrown')
210
+ } catch (err) {
211
+ assert.strictEqual(err.message, 'Service with id \'wrong-service-id\' not found')
212
+ }
213
+ })
214
+
215
+ test('should fail to start running service', async (t) => {
216
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
217
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
218
+ const app = await buildServer(config.configManager.current)
219
+
220
+ await app.start()
221
+
222
+ t.after(async () => {
223
+ await app.close()
224
+ })
225
+
226
+ try {
227
+ await app.startService('with-logger')
228
+ assert.fail('should have thrown')
229
+ } catch (err) {
230
+ assert.strictEqual(err.message, 'application is already started')
231
+ }
232
+ })
233
+
234
+ test('should inject request to service', async (t) => {
235
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
236
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
237
+ const app = await buildServer(config.configManager.current)
238
+
239
+ await app.start()
240
+
241
+ t.after(async () => {
242
+ await app.close()
243
+ })
244
+
245
+ const res = await app.inject('with-logger', {
246
+ method: 'GET',
247
+ url: '/'
248
+ })
249
+
250
+ assert.strictEqual(res.statusCode, 200)
251
+ assert.strictEqual(res.statusMessage, 'OK')
252
+
253
+ assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
254
+ assert.strictEqual(res.headers['content-length'], '17')
255
+ assert.strictEqual(res.headers.connection, 'keep-alive')
256
+
257
+ assert.strictEqual(res.body, '{"hello":"world"}')
258
+ assert.strictEqual(res.payload, '{"hello":"world"}')
259
+ })
260
+
261
+ test('should fail inject request is service is not started', async (t) => {
262
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
263
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
264
+ const app = await buildServer(config.configManager.current)
265
+
266
+ t.after(async () => {
267
+ await app.close()
268
+ })
269
+
270
+ try {
271
+ await app.inject('with-logger', { method: 'GET', url: '/' })
272
+ } catch (err) {
273
+ assert.strictEqual(err.message, 'Service with id \'with-logger\' is not started')
274
+ }
275
+ })
276
+
277
+ test('should handle a lot of runtime api requests', async (t) => {
278
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
279
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
280
+ const app = await buildServer(config.configManager.current)
281
+
282
+ await app.start()
283
+
284
+ t.after(async () => {
285
+ await app.close()
286
+ })
287
+
288
+ const promises = []
289
+ for (let i = 0; i < 100; i++) {
290
+ promises.push(app.getServiceDetails('with-logger'))
291
+ }
292
+
293
+ await Promise.all(promises)
294
+ })
package/test/app.test.js CHANGED
@@ -3,10 +3,20 @@
3
3
  const assert = require('node:assert')
4
4
  const { join } = require('node:path')
5
5
  const { test } = require('node:test')
6
+ const { utimes } = require('node:fs/promises')
6
7
  const { PlatformaticApp } = require('../lib/app')
7
8
  const fixturesDir = join(__dirname, '..', 'fixtures')
9
+ const pino = require('pino')
10
+ const split = require('split2')
11
+
12
+ function getLoggerAndStream () {
13
+ const stream = split(JSON.parse)
14
+ const logger = pino(stream)
15
+ return { logger, stream }
16
+ }
8
17
 
9
18
  test('logs errors during startup', async (t) => {
19
+ const { logger, stream } = getLoggerAndStream()
10
20
  const appPath = join(fixturesDir, 'serviceAppThrowsOnStart')
11
21
  const configFile = join(appPath, 'platformatic.service.json')
12
22
  const config = {
@@ -16,9 +26,8 @@ test('logs errors during startup', async (t) => {
16
26
  entrypoint: true,
17
27
  hotReload: true
18
28
  }
19
- const app = new PlatformaticApp(config, null)
29
+ const app = new PlatformaticApp(config, null, logger)
20
30
 
21
- t.mock.method(console, 'error', () => {})
22
31
  t.mock.method(process, 'exit', () => { throw new Error('exited') })
23
32
 
24
33
  await assert.rejects(async () => {
@@ -26,11 +35,18 @@ test('logs errors during startup', async (t) => {
26
35
  }, /exited/)
27
36
  assert.strictEqual(process.exit.mock.calls.length, 1)
28
37
  assert.strictEqual(process.exit.mock.calls[0].arguments[0], 1)
29
- assert.strictEqual(console.error.mock.calls.length, 1)
30
- assert.strictEqual(console.error.mock.calls[0].arguments[0].message, 'boom')
38
+
39
+ stream.end()
40
+ const lines = []
41
+ for await (const line of stream) {
42
+ lines.push(line)
43
+ }
44
+ const lastLine = lines[lines.length - 1]
45
+ assert.strictEqual(lastLine.msg, 'boom')
31
46
  })
32
47
 
33
48
  test('errors when starting an already started application', async (t) => {
49
+ const { logger } = getLoggerAndStream()
34
50
  const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
35
51
  const configFile = join(appPath, 'platformatic.service.json')
36
52
  const config = {
@@ -43,7 +59,7 @@ test('errors when starting an already started application', async (t) => {
43
59
  dependents: [],
44
60
  localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
45
61
  }
46
- const app = new PlatformaticApp(config, null)
62
+ const app = new PlatformaticApp(config, null, logger)
47
63
 
48
64
  t.after(app.stop.bind(app))
49
65
  await app.start()
@@ -53,6 +69,7 @@ test('errors when starting an already started application', async (t) => {
53
69
  })
54
70
 
55
71
  test('errors when stopping an already stopped application', async (t) => {
72
+ const { logger } = getLoggerAndStream()
56
73
  const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
57
74
  const configFile = join(appPath, 'platformatic.service.json')
58
75
  const config = {
@@ -65,7 +82,7 @@ test('errors when stopping an already stopped application', async (t) => {
65
82
  dependents: [],
66
83
  localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
67
84
  }
68
- const app = new PlatformaticApp(config, null)
85
+ const app = new PlatformaticApp(config, null, logger)
69
86
 
70
87
  await assert.rejects(async () => {
71
88
  await app.stop()
@@ -73,6 +90,7 @@ test('errors when stopping an already stopped application', async (t) => {
73
90
  })
74
91
 
75
92
  test('does not restart while restarting', async (t) => {
93
+ const { logger } = getLoggerAndStream()
76
94
  const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
77
95
  const configFile = join(appPath, 'platformatic.service.json')
78
96
  const config = {
@@ -85,7 +103,7 @@ test('does not restart while restarting', async (t) => {
85
103
  dependents: [],
86
104
  localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
87
105
  }
88
- const app = new PlatformaticApp(config, null)
106
+ const app = new PlatformaticApp(config, null, logger)
89
107
 
90
108
  t.after(app.stop.bind(app))
91
109
  await app.start()
@@ -101,6 +119,7 @@ test('does not restart while restarting', async (t) => {
101
119
  })
102
120
 
103
121
  test('restarts on SIGUSR2', async (t) => {
122
+ const { logger } = getLoggerAndStream()
104
123
  const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
105
124
  const configFile = join(appPath, 'platformatic.service.json')
106
125
  const config = {
@@ -113,7 +132,7 @@ test('restarts on SIGUSR2', async (t) => {
113
132
  dependents: [],
114
133
  localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
115
134
  }
116
- const app = new PlatformaticApp(config, null)
135
+ const app = new PlatformaticApp(config, null, logger)
117
136
 
118
137
  t.after(app.stop.bind(app))
119
138
  await app.start()
@@ -123,6 +142,7 @@ test('restarts on SIGUSR2', async (t) => {
123
142
  })
124
143
 
125
144
  test('stops on signals other than SIGUSR2', async (t) => {
145
+ const { logger } = getLoggerAndStream()
126
146
  const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
127
147
  const configFile = join(appPath, 'platformatic.service.json')
128
148
  const config = {
@@ -135,7 +155,7 @@ test('stops on signals other than SIGUSR2', async (t) => {
135
155
  dependents: [],
136
156
  localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
137
157
  }
138
- const app = new PlatformaticApp(config, null)
158
+ const app = new PlatformaticApp(config, null, logger)
139
159
 
140
160
  t.after(async () => {
141
161
  try {
@@ -151,6 +171,7 @@ test('stops on signals other than SIGUSR2', async (t) => {
151
171
  })
152
172
 
153
173
  test('stops on uncaught exceptions', async (t) => {
174
+ const { logger } = getLoggerAndStream()
154
175
  const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
155
176
  const configFile = join(appPath, 'platformatic.service.json')
156
177
  const config = {
@@ -163,7 +184,7 @@ test('stops on uncaught exceptions', async (t) => {
163
184
  dependents: [],
164
185
  localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
165
186
  }
166
- const app = new PlatformaticApp(config, null)
187
+ const app = new PlatformaticApp(config, null, logger)
167
188
 
168
189
  t.after(async () => {
169
190
  try {
@@ -193,8 +214,9 @@ test('supports configuration overrides', async (t) => {
193
214
  }
194
215
 
195
216
  await t.test('throws on non-string config paths', async (t) => {
217
+ const { logger } = getLoggerAndStream()
196
218
  config._configOverrides = new Map([[null, 5]])
197
- const app = new PlatformaticApp(config, null)
219
+ const app = new PlatformaticApp(config, null, logger)
198
220
 
199
221
  t.after(async () => {
200
222
  try {
@@ -210,8 +232,9 @@ test('supports configuration overrides', async (t) => {
210
232
  })
211
233
 
212
234
  await t.test('ignores invalid config paths', async (t) => {
235
+ const { logger } = getLoggerAndStream()
213
236
  config._configOverrides = new Map([['foo.bar.baz', 5]])
214
- const app = new PlatformaticApp(config, null)
237
+ const app = new PlatformaticApp(config, null, logger)
215
238
 
216
239
  t.after(async () => {
217
240
  try {
@@ -225,11 +248,12 @@ test('supports configuration overrides', async (t) => {
225
248
  })
226
249
 
227
250
  await t.test('sets valid config paths', async (t) => {
251
+ const { logger } = getLoggerAndStream()
228
252
  config._configOverrides = new Map([
229
253
  ['server.keepAliveTimeout', 1],
230
254
  ['server.port', 0]
231
255
  ])
232
- const app = new PlatformaticApp(config, null)
256
+ const app = new PlatformaticApp(config, null, logger)
233
257
 
234
258
  t.after(async () => {
235
259
  try {
@@ -243,3 +267,42 @@ test('supports configuration overrides', async (t) => {
243
267
  assert.strictEqual(app.config.configManager.current.server.keepAliveTimeout, 1)
244
268
  })
245
269
  })
270
+
271
+ test('restarts on config change without overriding the configManager', { only: true }, async (t) => {
272
+ const { logger, stream } = getLoggerAndStream()
273
+ const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
274
+ const configFile = join(appPath, 'platformatic.service.json')
275
+ const config = {
276
+ id: 'serviceApp',
277
+ config: configFile,
278
+ path: appPath,
279
+ entrypoint: true,
280
+ hotReload: true,
281
+ dependencies: [],
282
+ dependents: [],
283
+ localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
284
+ }
285
+ const app = new PlatformaticApp(config, null, logger)
286
+
287
+ t.after(async function () {
288
+ try {
289
+ await app.stop()
290
+ } catch (err) {
291
+ console.error(err)
292
+ }
293
+ })
294
+ await app.start()
295
+ const configManager = app.config.configManager
296
+ await utimes(configFile, new Date(), new Date())
297
+ let first = false
298
+ for await (const log of stream) {
299
+ // Wait for the server to restart, it will print a line containing "Server listening"
300
+ if (log.msg.includes('listening')) {
301
+ if (first) {
302
+ break
303
+ }
304
+ first = true
305
+ }
306
+ }
307
+ assert.strictEqual(configManager, app.server.platformatic.configManager)
308
+ })