@platformatic/runtime 0.46.1 → 0.46.2

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/test/app.test.js DELETED
@@ -1,358 +0,0 @@
1
- 'use strict'
2
-
3
- const assert = require('node:assert')
4
- const { join } = require('node:path')
5
- const { test } = require('node:test')
6
- const { utimes } = require('node:fs/promises')
7
- const { PlatformaticApp } = require('../lib/app')
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
- }
17
-
18
- test('logs errors during startup', async (t) => {
19
- const { logger, stream } = getLoggerAndStream()
20
- const appPath = join(fixturesDir, 'serviceAppThrowsOnStart')
21
- const configFile = join(appPath, 'platformatic.service.json')
22
- const config = {
23
- id: 'serviceAppThrowsOnStart',
24
- config: configFile,
25
- path: appPath,
26
- entrypoint: true,
27
- hotReload: true
28
- }
29
- const app = new PlatformaticApp(config, null, logger)
30
-
31
- t.mock.method(process, 'exit', () => { throw new Error('exited') })
32
-
33
- await assert.rejects(async () => {
34
- await app.start()
35
- }, /exited/)
36
- assert.strictEqual(process.exit.mock.calls.length, 1)
37
- assert.strictEqual(process.exit.mock.calls[0].arguments[0], 1)
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')
46
- })
47
-
48
- test('errors when starting an already started application', async (t) => {
49
- const { logger } = getLoggerAndStream()
50
- const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
51
- const configFile = join(appPath, 'platformatic.service.json')
52
- const config = {
53
- id: 'serviceApp',
54
- config: configFile,
55
- path: appPath,
56
- entrypoint: true,
57
- hotReload: true,
58
- dependencies: [],
59
- dependents: [],
60
- localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
61
- }
62
- const app = new PlatformaticApp(config, null, logger)
63
-
64
- t.after(app.stop.bind(app))
65
- await app.start()
66
- await assert.rejects(async () => {
67
- await app.start()
68
- }, /Application is already started/)
69
- })
70
-
71
- test('errors when stopping an already stopped application', async (t) => {
72
- const { logger } = getLoggerAndStream()
73
- const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
74
- const configFile = join(appPath, 'platformatic.service.json')
75
- const config = {
76
- id: 'serviceApp',
77
- config: configFile,
78
- path: appPath,
79
- entrypoint: true,
80
- hotReload: true,
81
- dependencies: [],
82
- dependents: [],
83
- localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
84
- }
85
- const app = new PlatformaticApp(config, null, logger)
86
-
87
- await assert.rejects(async () => {
88
- await app.stop()
89
- }, /Application has not been started/)
90
- })
91
-
92
- test('does not restart while restarting', async (t) => {
93
- const { logger, stream } = getLoggerAndStream()
94
- const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
95
- const configFile = join(appPath, 'platformatic.service.json')
96
- const config = {
97
- id: 'serviceApp',
98
- config: configFile,
99
- path: appPath,
100
- entrypoint: true,
101
- hotReload: true,
102
- dependencies: [],
103
- dependents: [],
104
- localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
105
- }
106
- const app = new PlatformaticApp(config, null, logger)
107
-
108
- t.after(async () => {
109
- try {
110
- await app.stop()
111
- } catch {}
112
- })
113
- await app.start()
114
- await Promise.all([
115
- app.restart(),
116
- app.restart(),
117
- app.restart()
118
- ])
119
- await app.stop()
120
- stream.end()
121
- const lines = []
122
- for await (const line of stream) {
123
- lines.push(line)
124
- }
125
-
126
- let count = 0
127
- for (const line of lines) {
128
- // every time we restart we log listening
129
- if (line.msg.match(/listening/)) {
130
- count++
131
- }
132
- }
133
-
134
- assert.strictEqual(count, 2)
135
- })
136
-
137
- test('restarts on SIGUSR2', async (t) => {
138
- const { logger } = getLoggerAndStream()
139
- const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
140
- const configFile = join(appPath, 'platformatic.service.json')
141
- const config = {
142
- id: 'serviceApp',
143
- config: configFile,
144
- path: appPath,
145
- entrypoint: true,
146
- hotReload: true,
147
- dependencies: [],
148
- dependents: [],
149
- localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
150
- }
151
- const app = new PlatformaticApp(config, null, logger)
152
-
153
- t.after(app.stop.bind(app))
154
- await app.start()
155
- t.mock.method(app, 'restart')
156
- await app.handleProcessLevelEvent({ signal: 'SIGUSR2' })
157
- assert(app.restart.mock.calls.length)
158
- })
159
-
160
- test('stops on signals other than SIGUSR2', async (t) => {
161
- const { logger } = getLoggerAndStream()
162
- const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
163
- const configFile = join(appPath, 'platformatic.service.json')
164
- const config = {
165
- id: 'serviceApp',
166
- config: configFile,
167
- path: appPath,
168
- entrypoint: true,
169
- hotReload: true,
170
- dependencies: [],
171
- dependents: [],
172
- localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
173
- }
174
- const app = new PlatformaticApp(config, null, logger)
175
-
176
- t.after(async () => {
177
- try {
178
- await app.stop()
179
- } catch {
180
- // Ignore. The server should be stopped if nothing went wrong.
181
- }
182
- })
183
- await app.start()
184
- t.mock.method(app, 'stop')
185
- await app.handleProcessLevelEvent({ signal: 'SIGINT' })
186
- assert.strictEqual(app.stop.mock.calls.length, 1)
187
- })
188
-
189
- test('stops on uncaught exceptions', async (t) => {
190
- const { logger } = getLoggerAndStream()
191
- const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
192
- const configFile = join(appPath, 'platformatic.service.json')
193
- const config = {
194
- id: 'serviceApp',
195
- config: configFile,
196
- path: appPath,
197
- entrypoint: true,
198
- hotReload: true,
199
- dependencies: [],
200
- dependents: [],
201
- localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
202
- }
203
- const app = new PlatformaticApp(config, null, logger)
204
-
205
- t.after(async () => {
206
- try {
207
- await app.stop()
208
- } catch {
209
- // Ignore. The server should be stopped if nothing went wrong.
210
- }
211
- })
212
- await app.start()
213
- t.mock.method(app, 'stop')
214
- await app.handleProcessLevelEvent({ err: new Error('boom') })
215
- assert.strictEqual(app.stop.mock.calls.length, 1)
216
- })
217
-
218
- test('supports configuration overrides', async (t) => {
219
- const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
220
- const configFile = join(appPath, 'platformatic.service.json')
221
- const config = {
222
- id: 'serviceApp',
223
- config: configFile,
224
- path: appPath,
225
- entrypoint: true,
226
- hotReload: true,
227
- dependencies: [],
228
- dependents: [],
229
- localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
230
- }
231
-
232
- await t.test('throws on non-string config paths', async (t) => {
233
- const { logger } = getLoggerAndStream()
234
- config._configOverrides = new Map([[null, 5]])
235
- const app = new PlatformaticApp(config, null, logger)
236
-
237
- t.after(async () => {
238
- try {
239
- await app.stop()
240
- } catch {
241
- // Ignore. The server should be stopped if nothing went wrong.
242
- }
243
- })
244
-
245
- await assert.rejects(async () => {
246
- await app.start()
247
- }, /Config path must be a string/)
248
- })
249
-
250
- await t.test('ignores invalid config paths', async (t) => {
251
- const { logger } = getLoggerAndStream()
252
- config._configOverrides = new Map([['foo.bar.baz', 5]])
253
- const app = new PlatformaticApp(config, null, logger)
254
-
255
- t.after(async () => {
256
- try {
257
- await app.stop()
258
- } catch {
259
- // Ignore. The server should be stopped if nothing went wrong.
260
- }
261
- })
262
-
263
- await app.start()
264
- })
265
-
266
- await t.test('sets valid config paths', async (t) => {
267
- const { logger } = getLoggerAndStream()
268
- config._configOverrides = new Map([
269
- ['server.keepAliveTimeout', 1],
270
- ['server.port', 0],
271
- ['server.pluginTimeout', 99]
272
- ])
273
- const app = new PlatformaticApp(config, null, logger)
274
-
275
- t.after(async () => {
276
- try {
277
- await app.stop()
278
- } catch {
279
- // Ignore. The server should be stopped if nothing went wrong.
280
- }
281
- })
282
-
283
- await app.start()
284
- assert.strictEqual(app.config.configManager.current.server.keepAliveTimeout, 1)
285
- assert.strictEqual(app.config.configManager.current.server.pluginTimeout, 99)
286
- })
287
- })
288
-
289
- test('restarts on config change without overriding the configManager', async (t) => {
290
- const { logger, stream } = getLoggerAndStream()
291
- const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
292
- const configFile = join(appPath, 'platformatic.service.json')
293
- const config = {
294
- id: 'serviceApp',
295
- config: configFile,
296
- path: appPath,
297
- entrypoint: true,
298
- hotReload: true,
299
- dependencies: [],
300
- dependents: [],
301
- localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
302
- }
303
- const app = new PlatformaticApp(config, null, logger)
304
-
305
- t.after(async function () {
306
- try {
307
- await app.stop()
308
- } catch (err) {
309
- console.error(err)
310
- }
311
- })
312
- await app.start()
313
- const configManager = app.config.configManager
314
- await utimes(configFile, new Date(), new Date())
315
- let first = false
316
- for await (const log of stream) {
317
- // Wait for the server to restart, it will print a line containing "Server listening"
318
- if (log.msg.includes('listening')) {
319
- if (first) {
320
- break
321
- }
322
- first = true
323
- }
324
- }
325
- assert.strictEqual(configManager, app.server.platformatic.configManager)
326
- })
327
-
328
- test('logs errors if an env variable is missing', async (t) => {
329
- const { logger, stream } = getLoggerAndStream()
330
- const configFile = join(fixturesDir, 'no-env.service.json')
331
- const config = {
332
- id: 'no-env',
333
- config: configFile,
334
- path: fixturesDir,
335
- entrypoint: true,
336
- hotReload: true
337
- }
338
- const app = new PlatformaticApp(config, null, logger)
339
-
340
- t.mock.method(process, 'exit', () => {
341
- throw new Error('exited')
342
- })
343
-
344
- await assert.rejects(async () => {
345
- await app.start()
346
- }, /exited/)
347
- assert.strictEqual(process.exit.mock.calls.length, 1)
348
- assert.strictEqual(process.exit.mock.calls[0].arguments[0], 1)
349
-
350
- stream.end()
351
- const lines = []
352
- for await (const line of stream) {
353
- lines.push(line)
354
- }
355
- const lastLine = lines[lines.length - 1]
356
- assert.strictEqual(lastLine.name, 'no-env')
357
- assert.strictEqual(lastLine.msg, 'Cannot parse config file. Cannot read properties of undefined (reading \'get\')')
358
- })
@@ -1,69 +0,0 @@
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, delDir } from './helper.mjs'
6
- import { execa } from 'execa'
7
- import { mkdtemp, cp, mkdir } from 'node:fs/promises'
8
-
9
- const base = join(import.meta.url, '..', 'tmp')
10
-
11
- try {
12
- await mkdir(base, { recursive: true })
13
- } catch {
14
- }
15
-
16
- test('compile with tsconfig custom flags', async (t) => {
17
- const tmpDir = await mkdtemp(path.join(base, 'test-runtime-compile-'))
18
- const prev = process.cwd()
19
- process.chdir(tmpDir)
20
- t.after(() => {
21
- process.chdir(prev)
22
- })
23
-
24
- t.after(delDir(tmpDir))
25
-
26
- const folder = join(import.meta.url, '..', '..', 'fixtures', 'typescript-custom-flags')
27
- await cp(folder, tmpDir, { recursive: true })
28
-
29
- const { stdout } = await execa(cliPath, ['compile'])
30
-
31
- const lines = stdout.split('\n').map(JSON.parse)
32
- const expected = [{
33
- name: 'movies',
34
- msg: 'Typescript compilation completed successfully.'
35
- }, {
36
- name: 'titles',
37
- msg: 'Typescript compilation completed successfully.'
38
- }]
39
-
40
- for (let i = 0; i < expected.length; i++) {
41
- assert.deepStrictEqual(lines[i].name, expected[i].name)
42
- assert.deepStrictEqual(lines[i].msg, expected[i].msg)
43
- }
44
- })
45
-
46
- test('compile single service', async (t) => {
47
- const tmpDir = await mkdtemp(path.join(base, 'test-runtime-compile-'))
48
- const prev = process.cwd()
49
- process.chdir(tmpDir)
50
- t.after(() => {
51
- process.chdir(prev)
52
- })
53
-
54
- t.after(delDir(tmpDir))
55
-
56
- const folder = join(import.meta.url, '..', '..', 'fixtures', 'typescript', 'services', 'movies')
57
- await cp(folder, tmpDir, { recursive: true })
58
-
59
- const { stdout } = await execa(cliPath, ['compile'])
60
-
61
- const lines = stdout.split('\n').map(JSON.parse)
62
- const expected = [{
63
- msg: 'Typescript compilation completed successfully.'
64
- }]
65
-
66
- for (let i = 0; i < expected.length; i++) {
67
- assert.deepStrictEqual(lines[i].msg, expected[i].msg)
68
- }
69
- })
@@ -1,104 +0,0 @@
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, delDir } from './helper.mjs'
6
- import { execa } from 'execa'
7
- import { mkdtemp, cp, mkdir } from 'node:fs/promises'
8
-
9
- const base = join(import.meta.url, '..', 'tmp')
10
-
11
- try {
12
- await mkdir(base, { recursive: true })
13
- } catch {
14
- }
15
-
16
- test('compile without tsconfigs', async () => {
17
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
18
- await execa(cliPath, ['compile', '-c', config])
19
- })
20
-
21
- test('compile with tsconfig', async (t) => {
22
- const tmpDir = await mkdtemp(path.join(base, 'test-runtime-compile-'))
23
- const prev = process.cwd()
24
- process.chdir(tmpDir)
25
- t.after(() => {
26
- process.chdir(prev)
27
- })
28
-
29
- t.after(delDir(tmpDir))
30
-
31
- const folder = join(import.meta.url, '..', '..', 'fixtures', 'typescript')
32
- await cp(folder, tmpDir, { recursive: true })
33
-
34
- const { stdout } = await execa(cliPath, ['compile'])
35
-
36
- const lines = stdout.split('\n').map(JSON.parse)
37
- const expected = [{
38
- name: 'movies',
39
- msg: 'Typescript compilation completed successfully.'
40
- }, {
41
- name: 'titles',
42
- msg: 'Typescript compilation completed successfully.'
43
- }]
44
-
45
- for (let i = 0; i < expected.length; i++) {
46
- assert.deepStrictEqual(lines[i].name, expected[i].name)
47
- assert.deepStrictEqual(lines[i].msg, expected[i].msg)
48
- }
49
- })
50
-
51
- test('compile with tsconfig custom flags', async (t) => {
52
- const tmpDir = await mkdtemp(path.join(base, 'test-runtime-compile-'))
53
- const prev = process.cwd()
54
- process.chdir(tmpDir)
55
- t.after(() => {
56
- process.chdir(prev)
57
- })
58
-
59
- t.after(delDir(tmpDir))
60
-
61
- const folder = join(import.meta.url, '..', '..', 'fixtures', 'typescript-custom-flags')
62
- await cp(folder, tmpDir, { recursive: true })
63
-
64
- const { stdout } = await execa(cliPath, ['compile'])
65
-
66
- const lines = stdout.split('\n').map(JSON.parse)
67
- const expected = [{
68
- name: 'movies',
69
- msg: 'Typescript compilation completed successfully.'
70
- }, {
71
- name: 'titles',
72
- msg: 'Typescript compilation completed successfully.'
73
- }]
74
-
75
- for (let i = 0; i < expected.length; i++) {
76
- assert.deepStrictEqual(lines[i].name, expected[i].name)
77
- assert.deepStrictEqual(lines[i].msg, expected[i].msg)
78
- }
79
- })
80
-
81
- test('compile single service', async (t) => {
82
- const tmpDir = await mkdtemp(path.join(base, 'test-runtime-compile-'))
83
- const prev = process.cwd()
84
- process.chdir(tmpDir)
85
- t.after(() => {
86
- process.chdir(prev)
87
- })
88
-
89
- t.after(delDir(tmpDir))
90
-
91
- const folder = join(import.meta.url, '..', '..', 'fixtures', 'typescript', 'services', 'movies')
92
- await cp(folder, tmpDir, { recursive: true })
93
-
94
- const { stdout } = await execa(cliPath, ['compile'])
95
-
96
- const lines = stdout.split('\n').map(JSON.parse)
97
- const expected = [{
98
- msg: 'Typescript compilation completed successfully.'
99
- }]
100
-
101
- for (let i = 0; i < expected.length; i++) {
102
- assert.deepStrictEqual(lines[i].msg, expected[i].msg)
103
- }
104
- })
@@ -1,68 +0,0 @@
1
- import { on } from 'node:events'
2
- import { Agent, setGlobalDispatcher } from 'undici'
3
- import { execa } from 'execa'
4
- import split from 'split2'
5
- import { join } from 'desm'
6
- import { rm } from 'node:fs/promises'
7
-
8
- setGlobalDispatcher(new Agent({
9
- keepAliveTimeout: 10,
10
- keepAliveMaxTimeout: 10,
11
- tls: {
12
- rejectUnauthorized: false
13
- }
14
- }))
15
-
16
- export const cliPath = join(import.meta.url, '..', '..', 'runtime.mjs')
17
-
18
- export async function start (...args) {
19
- const child = execa(process.execPath, [cliPath, 'start', ...args])
20
- child.stderr.pipe(process.stdout)
21
-
22
- const output = child.stdout.pipe(split(function (line) {
23
- try {
24
- const obj = JSON.parse(line)
25
- return obj
26
- } catch (err) {
27
- console.log(line)
28
- }
29
- }))
30
- child.ndj = output
31
-
32
- const errorTimeout = setTimeout(() => {
33
- throw new Error('Couldn\'t start server')
34
- }, 30000)
35
-
36
- for await (const messages of on(output, 'data')) {
37
- for (const message of messages) {
38
- if (message.msg) {
39
- const url = message.url ??
40
- message.msg.match(/server listening at (.+)/i)?.[1]
41
-
42
- if (url !== undefined) {
43
- clearTimeout(errorTimeout)
44
- return { child, url, output }
45
- }
46
- }
47
- }
48
- }
49
- }
50
-
51
- export function delDir (tmpDir) {
52
- return async function () {
53
- console.time('delDir')
54
- // We give up after 10s.
55
- // This is because on Windows, it's very hard to delete files if the file
56
- // system is not collaborating.
57
- try {
58
- await rm(tmpDir, { recursive: true, force: true })
59
- } catch (err) {
60
- if (err.code !== 'EBUSY') {
61
- throw err
62
- } else {
63
- console.log('Could not delete directory, retrying', tmpDir)
64
- }
65
- }
66
- console.timeEnd('delDir')
67
- }
68
- }