@platformatic/runtime 0.28.1 → 0.30.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.
Files changed (42) hide show
  1. package/fixtures/dbApp/platformatic.db.json +5 -0
  2. package/fixtures/dbApp/plugin.js +12 -0
  3. package/fixtures/monorepo/serviceApp/plugin.js +4 -0
  4. package/fixtures/start-command-in-runtime.js +2 -1
  5. package/fixtures/typescript/platformatic.runtime.json +12 -0
  6. package/fixtures/typescript/services/composer/platformatic.composer.json +28 -0
  7. package/fixtures/typescript/services/movies/global.d.ts +24 -0
  8. package/fixtures/typescript/services/movies/migrations/001.do.sql +6 -0
  9. package/fixtures/typescript/services/movies/migrations/001.undo.sql +3 -0
  10. package/fixtures/typescript/services/movies/platformatic.db.json +33 -0
  11. package/fixtures/typescript/services/movies/plugin.ts +5 -0
  12. package/fixtures/typescript/services/movies/tsconfig.json +21 -0
  13. package/fixtures/typescript/services/movies/types/Movie.d.ts +9 -0
  14. package/fixtures/typescript/services/movies/types/index.d.ts +7 -0
  15. package/fixtures/typescript/services/titles/client/client.d.ts +141 -0
  16. package/fixtures/typescript/services/titles/client/client.openapi.json +630 -0
  17. package/fixtures/typescript/services/titles/client/package.json +4 -0
  18. package/fixtures/typescript/services/titles/platformatic.service.json +31 -0
  19. package/fixtures/typescript/services/titles/plugins/example.ts +6 -0
  20. package/fixtures/typescript/services/titles/routes/root.ts +21 -0
  21. package/fixtures/typescript/services/titles/tsconfig.json +21 -0
  22. package/help/compile.txt +8 -0
  23. package/index.js +7 -7
  24. package/lib/api-client.js +91 -0
  25. package/lib/api.js +26 -77
  26. package/lib/app.js +6 -2
  27. package/lib/compile.js +43 -0
  28. package/lib/config.js +77 -14
  29. package/lib/message-port-writable.js +42 -0
  30. package/lib/start.js +38 -19
  31. package/lib/unified-api.js +2 -0
  32. package/lib/worker.js +25 -26
  33. package/package.json +12 -8
  34. package/runtime.mjs +4 -0
  35. package/test/api.test.js +12 -1
  36. package/test/app.test.js +1 -1
  37. package/test/cli/compile.test.mjs +65 -0
  38. package/test/cli/start.test.mjs +56 -0
  39. package/test/cli/validations.test.mjs +2 -1
  40. package/test/cli/watch.test.mjs +15 -12
  41. package/test/config.test.js +137 -1
  42. package/test/start.test.js +57 -0
package/lib/worker.js CHANGED
@@ -1,36 +1,33 @@
1
1
  'use strict'
2
2
 
3
+ const inspector = require('node:inspector')
4
+ const { isatty } = require('node:tty')
3
5
  const { parentPort, workerData } = require('node:worker_threads')
4
- const FastifyUndiciDispatcher = require('fastify-undici-dispatcher')
5
- const { Agent, setGlobalDispatcher } = require('undici')
6
- const { PlatformaticApp } = require('./app')
7
- const { RuntimeApi } = require('./api')
8
-
9
- const loaderPort = globalThis.LOADER_PORT // Added by loader.mjs.
10
- const globalAgent = new Agent()
11
- const globalDispatcher = new FastifyUndiciDispatcher({
12
- dispatcher: globalAgent,
13
- // setting the domain here allows for fail-fast scenarios
14
- domain: '.plt.local'
15
- })
6
+ const undici = require('undici')
16
7
  const pino = require('pino')
17
- const { isatty } = require('tty')
18
-
19
- const applications = new Map()
8
+ const RuntimeApi = require('./api')
9
+ const { MessagePortWritable } = require('./message-port-writable')
10
+ const loaderPort = globalThis.LOADER_PORT // Added by loader.mjs.
20
11
 
12
+ globalThis.fetch = undici.fetch
21
13
  delete globalThis.LOADER_PORT
22
- setGlobalDispatcher(globalDispatcher)
23
14
 
24
15
  let transport
16
+ let destination
25
17
 
26
- /* c8 ignore next 5 */
27
- if (isatty(1)) {
18
+ /* c8 ignore next 10 */
19
+ if (workerData.config.loggingPort) {
20
+ destination = new MessagePortWritable({
21
+ metadata: workerData.config.loggingMetadata,
22
+ port: workerData.config.loggingPort
23
+ })
24
+ } else if (isatty(1)) {
28
25
  transport = pino.transport({
29
26
  target: 'pino-pretty'
30
27
  })
31
28
  }
32
29
 
33
- const logger = pino(transport)
30
+ const logger = pino(transport, destination)
34
31
 
35
32
  /* c8 ignore next 4 */
36
33
  process.once('uncaughtException', (err) => {
@@ -45,17 +42,19 @@ process.once('unhandledRejection', (err) => {
45
42
  throw err
46
43
  })
47
44
 
48
- async function main () {
49
- const { services } = workerData.config
45
+ function main () {
46
+ const { inspectorOptions } = workerData.config
50
47
 
51
- for (let i = 0; i < services.length; ++i) {
52
- const service = services[i]
53
- const app = new PlatformaticApp(service, loaderPort, logger)
48
+ if (inspectorOptions) {
49
+ /* c8 ignore next 6 */
50
+ if (inspectorOptions.hotReloadDisabled) {
51
+ logger.info('debugging flags were detected. hot reloading has been disabled')
52
+ }
54
53
 
55
- applications.set(service.id, app)
54
+ inspector.open(inspectorOptions.port, inspectorOptions.host, inspectorOptions.breakFirstLine)
56
55
  }
57
56
 
58
- const runtime = new RuntimeApi(applications, globalDispatcher)
57
+ const runtime = new RuntimeApi(workerData.config, logger, loaderPort)
59
58
  runtime.startListening(parentPort)
60
59
 
61
60
  parentPort.postMessage('plt:init')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "0.28.1",
3
+ "version": "0.30.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -22,7 +22,10 @@
22
22
  "snazzy": "^9.0.0",
23
23
  "split2": "^4.2.0",
24
24
  "standard": "^17.1.0",
25
- "tsd": "^0.28.1"
25
+ "tsd": "^0.28.1",
26
+ "typescript": "^5.1.6",
27
+ "@platformatic/sql-mapper": "0.30.0",
28
+ "@platformatic/sql-graphql": "0.30.0"
26
29
  },
27
30
  "dependencies": {
28
31
  "@hapi/topo": "^6.0.2",
@@ -37,15 +40,16 @@
37
40
  "pino": "^8.14.1",
38
41
  "pino-pretty": "^10.0.0",
39
42
  "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"
43
+ "@platformatic/composer": "0.30.0",
44
+ "@platformatic/config": "0.30.0",
45
+ "@platformatic/db": "0.30.0",
46
+ "@platformatic/service": "0.30.0",
47
+ "@platformatic/utils": "0.30.0"
45
48
  },
46
49
  "standard": {
47
50
  "ignore": [
48
- "**/dist/*"
51
+ "**/dist/*",
52
+ "**/test/tmp"
49
53
  ]
50
54
  },
51
55
  "scripts": {
package/runtime.mjs CHANGED
@@ -7,6 +7,7 @@ import isMain from 'es-main'
7
7
  import helpMe from 'help-me'
8
8
  import parseArgs from 'minimist'
9
9
  import { start } from './lib/start.js'
10
+ import { compile } from './lib/compile.js'
10
11
 
11
12
  const help = helpMe({
12
13
  dir: join(import.meta.url, 'help'),
@@ -18,7 +19,9 @@ const program = commist({ maxDistance: 2 })
18
19
 
19
20
  program.register('help', help.toStdout)
20
21
  program.register('help start', help.toStdout.bind(null, ['start']))
22
+ program.register('help compile', help.toStdout.bind(null, ['compile']))
21
23
  program.register('start', start)
24
+ program.register('compile', compile)
22
25
 
23
26
  export async function run (argv) {
24
27
  const args = parseArgs(argv, {
@@ -32,6 +35,7 @@ export async function run (argv) {
32
35
  process.exit(0)
33
36
  }
34
37
 
38
+ /* c8 ignore next 4 */
35
39
  return {
36
40
  output: await program.parseAsync(argv),
37
41
  help
package/test/api.test.js CHANGED
@@ -45,9 +45,10 @@ test('should get service config', async (t) => {
45
45
 
46
46
  const serviceConfig = await app.getServiceConfig('with-logger')
47
47
 
48
+ delete serviceConfig.$schema
49
+
48
50
  // TODO: should return correct logger config
49
51
  assert.deepStrictEqual(serviceConfig, {
50
- $schema: 'https://platformatic.dev/schemas/v0.27.0/service',
51
52
  server: {
52
53
  hostname: '127.0.0.1',
53
54
  port: 0,
@@ -274,6 +275,16 @@ test('should fail inject request is service is not started', async (t) => {
274
275
  }
275
276
  })
276
277
 
278
+ test('does not wait forever if worker exits during api operation', async (t) => {
279
+ const configFile = join(fixturesDir, 'configs', 'service-throws-on-start.json')
280
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
281
+ const app = await buildServer(config.configManager.current)
282
+
283
+ await assert.rejects(async () => {
284
+ await app.start()
285
+ }, /The runtime exited before the operation completed/)
286
+ })
287
+
277
288
  test('should handle a lot of runtime api requests', async (t) => {
278
289
  const configFile = join(fixturesDir, 'configs', 'monorepo.json')
279
290
  const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
package/test/app.test.js CHANGED
@@ -268,7 +268,7 @@ test('supports configuration overrides', async (t) => {
268
268
  })
269
269
  })
270
270
 
271
- test('restarts on config change without overriding the configManager', { only: true }, async (t) => {
271
+ test('restarts on config change without overriding the configManager', async (t) => {
272
272
  const { logger, stream } = getLoggerAndStream()
273
273
  const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
274
274
  const configFile = join(appPath, 'platformatic.service.json')
@@ -0,0 +1,65 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { join } from 'desm'
4
+ import path from 'node:path'
5
+ import { cliPath } from './helper.mjs'
6
+ import { execa } from 'execa'
7
+ import { mkdtemp, rm, cp, mkdir } from 'node:fs/promises'
8
+ import { setTimeout as sleep } from 'node:timers/promises'
9
+
10
+ const base = join(import.meta.url, '..', 'tmp')
11
+
12
+ try {
13
+ await mkdir(base, { recursive: true })
14
+ } catch {
15
+ }
16
+
17
+ test('compile without tsconfigs', async () => {
18
+ const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
19
+ await execa(cliPath, ['compile', '-c', config])
20
+ })
21
+
22
+ test('compile with tsconfig', async (t) => {
23
+ const tmpDir = await mkdtemp(path.join(base, 'test-runtime-compile-'))
24
+ const prev = process.cwd()
25
+ process.chdir(tmpDir)
26
+ t.after(() => {
27
+ process.chdir(prev)
28
+ })
29
+
30
+ t.after(async () => {
31
+ // We give up after 10s.
32
+ // This is because on Windows, it's very hard to delete files if the file
33
+ // system is not collaborating.
34
+ for (let i = 0; i < 10; i++) {
35
+ try {
36
+ await rm(tmpDir, { recursive: true, force: true })
37
+ break
38
+ } catch (err) {
39
+ if (err.code === 'EBUSY') {
40
+ await sleep(1000)
41
+ continue
42
+ }
43
+ }
44
+ }
45
+ })
46
+
47
+ const folder = join(import.meta.url, '..', '..', 'fixtures', 'typescript')
48
+ await cp(folder, tmpDir, { recursive: true })
49
+
50
+ const { stdout } = await execa(cliPath, ['compile'])
51
+
52
+ const lines = stdout.split('\n').map(JSON.parse)
53
+ const expected = [{
54
+ name: 'movies',
55
+ msg: 'Typescript compilation completed successfully.'
56
+ }, {
57
+ name: 'titles',
58
+ msg: 'Typescript compilation completed successfully.'
59
+ }]
60
+
61
+ for (let i = 0; i < expected.length; i++) {
62
+ assert.deepStrictEqual(lines[i].name, expected[i].name)
63
+ assert.deepStrictEqual(lines[i].msg, expected[i].msg)
64
+ }
65
+ })
@@ -59,3 +59,59 @@ test('exits on error', async () => {
59
59
  assert.strictEqual(res.statusCode, 200)
60
60
  assert.strictEqual(exitCode, 1)
61
61
  })
62
+
63
+ test('does not start if node inspector flags are provided', async (t) => {
64
+ const { execa } = await import('execa')
65
+ const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
66
+ const child = execa(process.execPath, [cliPath, 'start', '-c', config], {
67
+ env: { NODE_OPTIONS: '--inspect' },
68
+ encoding: 'utf8'
69
+ })
70
+ let stderr = ''
71
+ let found = false
72
+
73
+ for await (const messages of on(child.stderr, 'data')) {
74
+ for (const message of messages) {
75
+ stderr += message
76
+
77
+ if (/Error: The Node.js inspector flags are not supported/.test(stderr)) {
78
+ found = true
79
+ break
80
+ }
81
+ }
82
+
83
+ if (found) {
84
+ break
85
+ }
86
+ }
87
+
88
+ assert(found)
89
+ })
90
+
91
+ test('starts the inspector', async (t) => {
92
+ const { execa } = await import('execa')
93
+ const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
94
+ const child = execa(process.execPath, [cliPath, 'start', '-c', config, '--inspect'], {
95
+ encoding: 'utf8'
96
+ })
97
+ let stderr = ''
98
+ let found = false
99
+
100
+ for await (const messages of on(child.stderr, 'data')) {
101
+ for (const message of messages) {
102
+ stderr += message
103
+
104
+ if (/Debugger listening on ws:\/\/127\.0\.0\.1:9229/.test(stderr)) {
105
+ found = true
106
+ break
107
+ }
108
+ }
109
+
110
+ if (found) {
111
+ break
112
+ }
113
+ }
114
+
115
+ assert(found)
116
+ child.kill('SIGINT')
117
+ })
@@ -1,6 +1,7 @@
1
1
  import assert from 'node:assert'
2
2
  import { readFile } from 'node:fs/promises'
3
3
  import { test } from 'node:test'
4
+ import { stripVTControlCharacters } from 'node:util'
4
5
  import { join } from 'desm'
5
6
  import { execa } from 'execa'
6
7
  import { cliPath } from './helper.mjs'
@@ -42,7 +43,7 @@ test('print validation errors', async () => {
42
43
 
43
44
  assert(error)
44
45
  assert.strictEqual(error.exitCode, 1)
45
- assert.strictEqual(error.stdout, `
46
+ assert.strictEqual(stripVTControlCharacters(error.stdout), `
46
47
  ┌─────────┬─────────────┬─────────────────────────────────────────────────────────────────┐
47
48
  │ (index) │ path │ message │
48
49
  ├─────────┼─────────────┼─────────────────────────────────────────────────────────────────┤
@@ -1,14 +1,20 @@
1
1
  import assert from 'node:assert'
2
- import { cp, writeFile, mkdtemp } from 'node:fs/promises'
3
- import { tmpdir } from 'node:os'
2
+ import { cp, writeFile, mkdtemp, mkdir, rm } from 'node:fs/promises'
4
3
  import { join } from 'node:path'
5
4
  import { test } from 'node:test'
6
5
  import { setTimeout as sleep } from 'node:timers/promises'
7
6
  import desm from 'desm'
8
7
  import { request } from 'undici'
9
8
  import { start } from './helper.mjs'
9
+
10
10
  const fixturesDir = join(desm(import.meta.url), '..', '..', 'fixtures')
11
- const undiciPath = join(desm(import.meta.url), '..', '..', 'node_modules', 'undici')
11
+
12
+ const base = join(desm(import.meta.url), '..', 'tmp')
13
+
14
+ try {
15
+ await mkdir(base, { recursive: true })
16
+ } catch {
17
+ }
12
18
 
13
19
  function createCjsLoggingPlugin (text, reloaded) {
14
20
  return `\
@@ -42,7 +48,8 @@ function createEsmLoggingPlugin (text, reloaded) {
42
48
  }
43
49
 
44
50
  test('watches CommonJS files', async (t) => {
45
- const tmpDir = await mkdtemp(join(tmpdir(), 'watch-'))
51
+ const tmpDir = await mkdtemp(join(base, 'watch-'))
52
+ t.after(() => rm(tmpDir, { recursive: true, force: true }))
46
53
  t.diagnostic(`using ${tmpDir}`)
47
54
  const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
48
55
  const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
@@ -55,8 +62,6 @@ test('watches CommonJS files', async (t) => {
55
62
  cp(appSrc, appDst, { recursive: true })
56
63
  ])
57
64
 
58
- await cp(undiciPath, join(appDst, 'serviceApp', 'node_modules', 'undici'), { recursive: true })
59
-
60
65
  await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
61
66
  const { child } = await start('-c', configFileDst)
62
67
  t.after(() => child.kill('SIGINT'))
@@ -73,7 +78,8 @@ test('watches CommonJS files', async (t) => {
73
78
  })
74
79
 
75
80
  test('watches ESM files', async (t) => {
76
- const tmpDir = await mkdtemp(join(tmpdir(), 'watch-'))
81
+ const tmpDir = await mkdtemp(join(base, 'watch-'))
82
+ t.after(() => rm(tmpDir, { recursive: true, force: true }))
77
83
  t.diagnostic(`using ${tmpDir}`)
78
84
  const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
79
85
  const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
@@ -86,8 +92,6 @@ test('watches ESM files', async (t) => {
86
92
  cp(appSrc, appDst, { recursive: true })
87
93
  ])
88
94
 
89
- await cp(undiciPath, join(appDst, 'serviceApp', 'node_modules', 'undici'), { recursive: true })
90
-
91
95
  await writeFile(esmPluginFilePath, createEsmLoggingPlugin('v1', false))
92
96
  const { child } = await start('-c', configFileDst)
93
97
  t.after(() => child.kill('SIGINT'))
@@ -103,7 +107,8 @@ test('watches ESM files', async (t) => {
103
107
  })
104
108
 
105
109
  test('should not hot reload files with `--hot-reload false', async (t) => {
106
- const tmpDir = await mkdtemp(join(tmpdir(), 'watch-'))
110
+ const tmpDir = await mkdtemp(join(base, 'watch-'))
111
+ t.after(() => rm(tmpDir, { recursive: true, force: true }))
107
112
  t.diagnostic(`using ${tmpDir}`)
108
113
  const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
109
114
  const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
@@ -116,8 +121,6 @@ test('should not hot reload files with `--hot-reload false', async (t) => {
116
121
  cp(appSrc, appDst, { recursive: true })
117
122
  ])
118
123
 
119
- await cp(undiciPath, join(appDst, 'serviceApp', 'node_modules', 'undici'), { recursive: true })
120
-
121
124
  await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
122
125
  const { child, url } = await start('-c', configFileDst, '--hot-reload', 'false')
123
126
  t.after(() => child.kill('SIGINT'))
@@ -4,7 +4,7 @@ const assert = require('node:assert')
4
4
  const { join } = require('node:path')
5
5
  const { test } = require('node:test')
6
6
  const { loadConfig } = require('@platformatic/service')
7
- const { platformaticRuntime } = require('../lib/config')
7
+ const { parseInspectorOptions, platformaticRuntime } = require('../lib/config')
8
8
  const fixturesDir = join(__dirname, '..', 'fixtures')
9
9
 
10
10
  test('throws if no entrypoint is found', async (t) => {
@@ -65,3 +65,139 @@ test('can resolve service id from client package.json if not provided', async ()
65
65
  assert.strictEqual(entry.dependencies.length, 1)
66
66
  assert.strictEqual(entry.dependencies[0].id, 'with-logger')
67
67
  })
68
+
69
+ test('parseInspectorOptions()', async (t) => {
70
+ await t.test('throws if --inspect and --inspect-brk are both used', () => {
71
+ assert.throws(() => {
72
+ const cm = {
73
+ args: { inspect: '', 'inspect-brk': '' },
74
+ current: {}
75
+ }
76
+
77
+ parseInspectorOptions(cm)
78
+ }, /--inspect and --inspect-brk cannot be used together/)
79
+ })
80
+
81
+ await t.test('--inspect default settings', () => {
82
+ const cm = {
83
+ args: { inspect: '' },
84
+ current: {}
85
+ }
86
+
87
+ parseInspectorOptions(cm)
88
+ assert.deepStrictEqual(cm.current.inspectorOptions, {
89
+ host: '127.0.0.1',
90
+ port: 9229,
91
+ breakFirstLine: false,
92
+ hotReloadDisabled: false
93
+ })
94
+ })
95
+
96
+ await t.test('--inspect-brk default settings', () => {
97
+ const cm = {
98
+ args: { 'inspect-brk': '' },
99
+ current: {}
100
+ }
101
+
102
+ parseInspectorOptions(cm)
103
+ assert.deepStrictEqual(cm.current.inspectorOptions, {
104
+ host: '127.0.0.1',
105
+ port: 9229,
106
+ breakFirstLine: true,
107
+ hotReloadDisabled: false
108
+ })
109
+ })
110
+
111
+ await t.test('hot reloading is disabled if the inspector is used', () => {
112
+ const cm1 = {
113
+ args: { 'inspect-brk': '' },
114
+ current: { hotReload: true }
115
+ }
116
+
117
+ parseInspectorOptions(cm1)
118
+ assert.strictEqual(cm1.current.hotReload, false)
119
+
120
+ const cm2 = {
121
+ args: {},
122
+ current: { hotReload: true }
123
+ }
124
+
125
+ parseInspectorOptions(cm2)
126
+ assert.strictEqual(cm2.current.hotReload, true)
127
+ })
128
+
129
+ await t.test('sets port to a custom value', () => {
130
+ const cm = {
131
+ args: { inspect: '6666' },
132
+ current: {}
133
+ }
134
+
135
+ parseInspectorOptions(cm)
136
+ assert.deepStrictEqual(cm.current.inspectorOptions, {
137
+ host: '127.0.0.1',
138
+ port: 6666,
139
+ breakFirstLine: false,
140
+ hotReloadDisabled: false
141
+ })
142
+ })
143
+
144
+ await t.test('sets host and port to custom values', () => {
145
+ const cm = {
146
+ args: { inspect: '0.0.0.0:6666' },
147
+ current: {}
148
+ }
149
+
150
+ parseInspectorOptions(cm)
151
+ assert.deepStrictEqual(cm.current.inspectorOptions, {
152
+ host: '0.0.0.0',
153
+ port: 6666,
154
+ breakFirstLine: false,
155
+ hotReloadDisabled: false
156
+ })
157
+ })
158
+
159
+ await t.test('throws if the host is empty', () => {
160
+ assert.throws(() => {
161
+ const cm = {
162
+ args: { inspect: ':9229' },
163
+ current: {}
164
+ }
165
+
166
+ parseInspectorOptions(cm)
167
+ }, /inspector host cannot be empty/)
168
+ })
169
+
170
+ await t.test('differentiates valid and invalid ports', () => {
171
+ ['127.0.0.1:', 'foo', '1', '-1', '1023', '65536'].forEach((inspectFlag) => {
172
+ assert.throws(() => {
173
+ const cm = {
174
+ args: { inspect: inspectFlag },
175
+ current: {}
176
+ }
177
+
178
+ parseInspectorOptions(cm)
179
+ }, /inspector port must be 0 or in range 1024 to 65535/)
180
+ })
181
+
182
+ const cm = {
183
+ args: {},
184
+ current: {}
185
+ }
186
+
187
+ cm.args.inspect = '0'
188
+ parseInspectorOptions(cm)
189
+ assert.strictEqual(cm.current.inspectorOptions.port, 0)
190
+ cm.args.inspect = '1024'
191
+ parseInspectorOptions(cm)
192
+ assert.strictEqual(cm.current.inspectorOptions.port, 1024)
193
+ cm.args.inspect = '1025'
194
+ parseInspectorOptions(cm)
195
+ assert.strictEqual(cm.current.inspectorOptions.port, 1025)
196
+ cm.args.inspect = '65534'
197
+ parseInspectorOptions(cm)
198
+ assert.strictEqual(cm.current.inspectorOptions.port, 65534)
199
+ cm.args.inspect = '65535'
200
+ parseInspectorOptions(cm)
201
+ assert.strictEqual(cm.current.inspectorOptions.port, 65535)
202
+ })
203
+ })
@@ -1,10 +1,14 @@
1
1
  'use strict'
2
2
  const assert = require('node:assert')
3
+ const { spawn } = require('node:child_process')
4
+ const { once } = require('node:events')
3
5
  const { join } = require('node:path')
4
6
  const { test } = require('node:test')
7
+ const { MessageChannel } = require('node:worker_threads')
5
8
  const { request } = require('undici')
6
9
  const { loadConfig } = require('@platformatic/service')
7
10
  const { buildServer, platformaticRuntime } = require('..')
11
+ const { startWithConfig } = require('../lib/start')
8
12
  const fixturesDir = join(__dirname, '..', 'fixtures')
9
13
 
10
14
  test('can start applications programmatically from object', async (t) => {
@@ -99,3 +103,56 @@ test('can restart the runtime apps', async (t) => {
99
103
  assert.deepStrictEqual(await res.body.json(), { hello: 'world' })
100
104
  }
101
105
  })
106
+
107
+ test('supports logging via message port', async (t) => {
108
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
109
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
110
+ const { port1, port2 } = new MessageChannel()
111
+ config.configManager.current.loggingPort = port2
112
+ config.configManager.current.loggingMetadata = { foo: 1, bar: 2 }
113
+ const app = await buildServer(config.configManager.current)
114
+ await app.start()
115
+
116
+ t.after(async () => {
117
+ await app.close()
118
+ })
119
+
120
+ const [msg] = await once(port1, 'message')
121
+
122
+ assert.deepStrictEqual(msg.metadata, { foo: 1, bar: 2 })
123
+ assert(Array.isArray(msg.logs))
124
+ assert(msg.logs.length > 0)
125
+
126
+ for (let i = 0; i < msg.logs.length; ++i) {
127
+ // Verify that each log is valid JSON.
128
+ JSON.parse(msg.logs[i])
129
+ }
130
+ })
131
+
132
+ test('can start with a custom environment', async (t) => {
133
+ const configFile = join(fixturesDir, 'configs', 'monorepo.json')
134
+ const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
135
+ const app = await startWithConfig(config.configManager, { A_CUSTOM_ENV_VAR: 'foobar' })
136
+
137
+ t.after(async () => {
138
+ await app.close()
139
+ })
140
+
141
+ const entryUrl = await app.start()
142
+ const res = await request(entryUrl + '/env')
143
+
144
+ assert.strictEqual(res.statusCode, 200)
145
+ assert.deepStrictEqual(await res.body.json(), { A_CUSTOM_ENV_VAR: 'foobar' })
146
+ })
147
+
148
+ test('handles uncaught exceptions with db app', async (t) => {
149
+ // Test for https://github.com/platformatic/platformatic/issues/1193
150
+ const scriptFile = join(fixturesDir, 'start-command-in-runtime.js')
151
+ const configFile = join(fixturesDir, 'dbApp', 'platformatic.db.json')
152
+ const child = spawn(process.execPath, [scriptFile, configFile, '/async_crash'])
153
+ child.stdout.pipe(process.stdout)
154
+ child.stderr.pipe(process.stderr)
155
+ const [exitCode] = await once(child, 'exit')
156
+
157
+ assert.strictEqual(exitCode, 42)
158
+ })