@platformatic/runtime 0.46.1 → 0.47.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.
@@ -1,131 +0,0 @@
1
- import assert from 'node:assert'
2
- import { on, once } from 'node:events'
3
- import { test } from 'node:test'
4
- import { join } from 'desm'
5
- import { request } from 'undici'
6
- import { cliPath, start } from './helper.mjs'
7
-
8
- test('autostart', async () => {
9
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
10
- const { child, url } = await start('-c', config)
11
- const res = await request(url)
12
-
13
- assert.strictEqual(res.statusCode, 200)
14
- assert.deepStrictEqual(await res.body.json(), { hello: 'hello123' })
15
- child.kill('SIGINT')
16
- })
17
-
18
- test('start command', async () => {
19
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
20
- const { child, url } = await start('start', '-c', config)
21
- const res = await request(url)
22
-
23
- assert.strictEqual(res.statusCode, 200)
24
- assert.deepStrictEqual(await res.body.json(), { hello: 'hello123' })
25
- child.kill('SIGINT')
26
- })
27
-
28
- test('handles startup errors', async (t) => {
29
- const { execa } = await import('execa')
30
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'service-throws-on-start.json')
31
- const child = execa(process.execPath, [cliPath, 'start', '-c', config], { encoding: 'utf8' })
32
- let stdout = ''
33
- let found = false
34
-
35
- for await (const messages of on(child.stdout, 'data')) {
36
- for (const message of messages) {
37
- stdout += message
38
-
39
- if (/Error: boom/.test(stdout)) {
40
- found = true
41
- break
42
- }
43
- }
44
-
45
- if (found) {
46
- break
47
- }
48
- }
49
-
50
- assert(found)
51
-
52
- // if we do not await this, the test will crash because the event loop has nothing to do
53
- // but there is still a promise waiting
54
- await child.catch(() => {})
55
- })
56
-
57
- test('exits on error', async () => {
58
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
59
- const { child, url } = await start('start', '-c', config)
60
- const res = await request(url + '/crash')
61
- const [exitCode] = await once(child, 'exit')
62
-
63
- assert.strictEqual(res.statusCode, 200)
64
- assert.strictEqual(exitCode, 1)
65
- })
66
-
67
- test('does not start if node inspector flags are provided', async (t) => {
68
- const { execa } = await import('execa')
69
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
70
- const child = execa(process.execPath, [cliPath, 'start', '-c', config], {
71
- env: { NODE_OPTIONS: '--inspect' },
72
- encoding: 'utf8'
73
- })
74
- let stderr = ''
75
- let found = false
76
-
77
- for await (const messages of on(child.stderr, 'data')) {
78
- for (const message of messages) {
79
- stderr += message
80
-
81
- if (/The Node.js inspector flags are not supported/.test(stderr)) {
82
- found = true
83
- break
84
- }
85
- }
86
-
87
- if (found) {
88
- break
89
- }
90
- }
91
-
92
- assert(found)
93
- })
94
-
95
- test('starts the inspector', async (t) => {
96
- const { execa } = await import('execa')
97
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
98
- const child = execa(process.execPath, [cliPath, 'start', '-c', config, '--inspect'], {
99
- encoding: 'utf8'
100
- })
101
- let stderr = ''
102
- let found = false
103
-
104
- for await (const messages of on(child.stderr, 'data')) {
105
- for (const message of messages) {
106
- stderr += message
107
-
108
- if (/Debugger listening on ws:\/\/127\.0\.0\.1:9229/.test(stderr)) {
109
- found = true
110
- break
111
- }
112
- }
113
-
114
- if (found) {
115
- break
116
- }
117
- }
118
-
119
- assert(found)
120
- child.kill('SIGINT')
121
- })
122
-
123
- test('stackable', async () => {
124
- const config = join(import.meta.url, '..', '..', 'fixtures', 'stackables', 'platformatic.json')
125
- const { child, url } = await start('start', '-c', config)
126
- const res = await request(url + '/foo')
127
-
128
- assert.strictEqual(res.statusCode, 200)
129
- assert.deepStrictEqual(await res.body.text(), 'Hello World')
130
- child.kill('SIGINT')
131
- })
@@ -1,53 +0,0 @@
1
- import assert from 'node:assert'
2
- import { readFile } from 'node:fs/promises'
3
- import { test } from 'node:test'
4
- import { stripVTControlCharacters } from 'node:util'
5
- import { join } from 'desm'
6
- import { execa } from 'execa'
7
- import { cliPath } from './helper.mjs'
8
-
9
- const version = JSON.parse(await readFile(join(import.meta.url, '..', '..', 'package.json'))).version
10
-
11
- test('version', async () => {
12
- const { stdout } = await execa(process.execPath, [cliPath, '--version'])
13
-
14
- assert.strictEqual(stdout.trim(), `v${version}`)
15
- })
16
-
17
- test('missing config', async () => {
18
- await assert.rejects(execa(process.execPath, [cliPath, 'start']))
19
- })
20
-
21
- test('no services specified by config', async () => {
22
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'no-services.config.json')
23
-
24
- await assert.rejects(execa(process.execPath, [cliPath, 'start', '--config', config]))
25
- })
26
-
27
- test('no services or autoload specified by config', async () => {
28
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'no-sources.config.json')
29
-
30
- await assert.rejects(execa(process.execPath, [cliPath, 'start', '--config', config]))
31
- })
32
-
33
- test('print validation errors', async () => {
34
- let error
35
-
36
- try {
37
- const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'missing-property.config.json')
38
-
39
- await execa(process.execPath, [cliPath, 'start', '--config', config])
40
- } catch (err) {
41
- error = err
42
- }
43
-
44
- assert(error)
45
- assert.strictEqual(error.exitCode, 1)
46
- assert.strictEqual(stripVTControlCharacters(error.stdout), `
47
- ┌─────────┬─────────────┬─────────────────────────────────────────────────────────────────┐
48
- │ (index) │ path │ message │
49
- ├─────────┼─────────────┼─────────────────────────────────────────────────────────────────┤
50
- │ 0 │ '/autoload' │ \`must have required property 'path' {"missingProperty":"path"}\` │
51
- └─────────┴─────────────┴─────────────────────────────────────────────────────────────────┘
52
- `.trim())
53
- })
@@ -1,309 +0,0 @@
1
- import assert from 'node:assert'
2
- import { cp, writeFile, mkdtemp, mkdir, rm, utimes } from 'node:fs/promises'
3
- import { join } from 'node:path'
4
- import { test } from 'node:test'
5
- import { setTimeout as sleep } from 'node:timers/promises'
6
- import desm from 'desm'
7
- import { request } from 'undici'
8
- import { start } from './helper.mjs'
9
- import { on } from 'node:events'
10
-
11
- const fixturesDir = join(desm(import.meta.url), '..', '..', 'fixtures')
12
-
13
- const base = join(desm(import.meta.url), '..', 'tmp')
14
-
15
- try {
16
- await mkdir(base, { recursive: true })
17
- } catch {
18
- }
19
-
20
- function createCjsLoggingPlugin (text, reloaded) {
21
- return `\
22
- module.exports = async (app) => {
23
- if (${reloaded}) {
24
- app.log.info('RELOADED ' + '${text}')
25
- }
26
- app.get('/version', () => '${text}')
27
- }
28
- `
29
- }
30
-
31
- function createEsmLoggingPlugin (text, reloaded) {
32
- return `\
33
- import fs from 'fs' // No node: scheme. Coverage for the loader.
34
- import dns from 'node:dns' // With node: scheme. Coverage for the loader.
35
-
36
- try {
37
- await import('./relative.mjs') // Relative path. Coverage for the loader.
38
- } catch {
39
- // Ignore err. File does not exist.
40
- }
41
-
42
- export default async function (app) {
43
- if (${reloaded}) {
44
- app.log.info('RELOADED ' + '${text}')
45
- }
46
- app.get('/version', () => '${text}')
47
- }
48
- `
49
- }
50
-
51
- function saferm (path) {
52
- return rm(path, { recursive: true, force: true }).catch(() => {})
53
- }
54
-
55
- test('watches CommonJS files', async (t) => {
56
- const tmpDir = await mkdtemp(join(base, 'watch-'))
57
- t.after(() => saferm(tmpDir))
58
- t.diagnostic(`using ${tmpDir}`)
59
- const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
60
- const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
61
- const appSrc = join(fixturesDir, 'monorepo')
62
- const appDst = join(tmpDir, 'monorepo')
63
- const cjsPluginFilePath = join(appDst, 'serviceAppWithLogger', 'plugin.js')
64
-
65
- await Promise.all([
66
- cp(configFileSrc, configFileDst),
67
- cp(appSrc, appDst, { recursive: true })
68
- ])
69
-
70
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
71
- const { child } = await start('-c', configFileDst)
72
- t.after(() => child.kill('SIGINT'))
73
-
74
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
75
-
76
- for await (const log of child.ndj) {
77
- if (log.msg === 'RELOADED v2') {
78
- break
79
- }
80
- }
81
- })
82
-
83
- test('watches ESM files', async (t) => {
84
- const tmpDir = await mkdtemp(join(base, 'watch-'))
85
- t.after(() => saferm(tmpDir))
86
- t.diagnostic(`using ${tmpDir}`)
87
- const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
88
- const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
89
- const appSrc = join(fixturesDir, 'monorepo')
90
- const appDst = join(tmpDir, 'monorepo')
91
- const esmPluginFilePath = join(appDst, 'serviceAppWithMultiplePlugins', 'plugin2.mjs')
92
-
93
- await Promise.all([
94
- cp(configFileSrc, configFileDst),
95
- cp(appSrc, appDst, { recursive: true })
96
- ])
97
-
98
- await writeFile(esmPluginFilePath, createEsmLoggingPlugin('v1', false))
99
- const { child } = await start('-c', configFileDst)
100
- t.after(() => child.kill('SIGINT'))
101
- await writeFile(esmPluginFilePath, createEsmLoggingPlugin('v2', true))
102
-
103
- for await (const log of child.ndj) {
104
- if (log.msg === 'RELOADED v2') {
105
- break
106
- }
107
- }
108
- })
109
-
110
- test('should not hot reload files with `--hot-reload false', async (t) => {
111
- const tmpDir = await mkdtemp(join(base, 'watch-'))
112
- t.after(() => saferm(tmpDir))
113
- t.diagnostic(`using ${tmpDir}`)
114
- const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
115
- const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
116
- const appSrc = join(fixturesDir, 'monorepo')
117
- const appDst = join(tmpDir, 'monorepo')
118
- const cjsPluginFilePath = join(appDst, 'serviceApp', 'plugin.js')
119
-
120
- await Promise.all([
121
- cp(configFileSrc, configFileDst),
122
- cp(appSrc, appDst, { recursive: true })
123
- ])
124
-
125
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
126
- const { child, url } = await start('-c', configFileDst, '--hot-reload', 'false')
127
- t.after(() => child.kill('SIGINT'))
128
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
129
- await sleep(5000)
130
- const res = await request(`${url}/version`)
131
- const version = await res.body.text()
132
- assert.strictEqual(version, 'v1')
133
- })
134
-
135
- test('watches CommonJS files with hotreload', { timeout: 30000 }, async (t) => {
136
- const tmpDir = await mkdtemp(join(base, 'watch-'))
137
- t.after(() => saferm(tmpDir))
138
- t.diagnostic(`using ${tmpDir}`)
139
- const configFileSrc = join(fixturesDir, 'configs', 'hotreload.json')
140
- const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
141
- const appSrc = join(fixturesDir, 'monorepo')
142
- const appDst = join(tmpDir, 'monorepo')
143
- const cjsPluginFilePath = join(appDst, 'serviceAppWithLogger', 'plugin.js')
144
-
145
- await Promise.all([
146
- cp(configFileSrc, configFileDst),
147
- cp(appSrc, appDst, { recursive: true })
148
- ])
149
-
150
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
151
- const { child } = await start('-c', configFileDst)
152
- t.after(() => child.kill('SIGINT'))
153
-
154
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
155
-
156
- let restartedSecondTime = false
157
- let restartedThirdTime = false
158
-
159
- for await (const log of child.ndj) {
160
- if (log.msg === 'RELOADED v2') {
161
- restartedSecondTime = true
162
- } else if (log.msg === 'RELOADED v3') {
163
- restartedThirdTime = true
164
- break
165
- } else if (log.msg?.match(/watching/)) {
166
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v3', true))
167
- }
168
- }
169
-
170
- assert.ok(restartedSecondTime)
171
- assert.ok(restartedThirdTime)
172
- })
173
-
174
- test('watches CommonJS files with hotreload on a single service', { timeout: 30000 }, async (t) => {
175
- const tmpDir = await mkdtemp(join(base, 'watch-'))
176
- t.after(() => saferm(tmpDir))
177
- t.diagnostic(`using ${tmpDir}`)
178
- const appSrc = join(fixturesDir, 'monorepo', 'serviceAppWithLogger')
179
- const appDst = join(tmpDir)
180
- const cjsPluginFilePath = join(appDst, 'plugin.js')
181
-
182
- await Promise.all([
183
- cp(appSrc, appDst, { recursive: true })
184
- ])
185
-
186
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
187
- const { child } = await start('-c', join(appDst, 'platformatic.service.json'))
188
- t.after(() => child.kill('SIGINT'))
189
-
190
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
191
-
192
- let restartedSecondTime = false
193
- let restartedThirdTime = false
194
-
195
- for await (const log of child.ndj) {
196
- if (log.msg === 'RELOADED v2') {
197
- restartedSecondTime = true
198
- } else if (log.msg === 'RELOADED v3') {
199
- assert.ok(restartedSecondTime)
200
- restartedThirdTime = true
201
- break
202
- } else if (log.msg?.match(/listening/)) {
203
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v3', true))
204
- }
205
- }
206
-
207
- assert.ok(restartedThirdTime)
208
- })
209
-
210
- test('do not hot reload dependencies', { timeout: 30000 }, async (t) => {
211
- process.env.PORT = 0
212
- const config = join(fixturesDir, 'do-not-reload-dependencies', 'platformatic.service.json')
213
- const { child, url } = await start('-c', config)
214
- t.after(() => child.kill('SIGINT'))
215
- t.after(() => delete process.env.PORT)
216
-
217
- const res1 = await request(`${url}/plugin1`)
218
- const plugin1 = (await res1.body.json()).hello
219
-
220
- const res2 = await request(`${url}/plugin2`)
221
- const plugin2 = (await res2.body.json()).hello
222
-
223
- utimes(config, new Date(), new Date()).catch(() => {})
224
-
225
- // wait for restart
226
- for await (const messages of on(child.ndj, 'data')) {
227
- let url
228
- for (const message of messages) {
229
- if (message.msg) {
230
- url = message.msg.match(/server listening at (.+)/i)?.[1]
231
-
232
- if (url !== undefined) {
233
- break
234
- }
235
- }
236
- }
237
-
238
- if (url !== undefined) {
239
- break
240
- }
241
- }
242
-
243
- const res3 = await request(`${url}/plugin1`)
244
- assert.strictEqual((await res3.body.json()).hello, plugin1)
245
-
246
- const res4 = await request(`${url}/plugin2`)
247
- assert.strictEqual((await res4.body.json()).hello, plugin2)
248
- })
249
-
250
- test('watches CommonJS files with hotreload on a single service', { timeout: 30000 }, async (t) => {
251
- const tmpDir = await mkdtemp(join(base, 'watch-'))
252
- t.after(() => saferm(tmpDir))
253
- t.diagnostic(`using ${tmpDir}`)
254
- const appSrc = join(fixturesDir, 'monorepo', 'serviceAppWithLogger')
255
- const appDst = join(tmpDir)
256
- const cjsPluginFilePath = join(appDst, 'plugin.js')
257
-
258
- await Promise.all([
259
- cp(appSrc, appDst, { recursive: true })
260
- ])
261
-
262
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
263
- const { child } = await start('-c', join(appDst, 'platformatic.service.json'))
264
- t.after(() => child.kill('SIGINT'))
265
-
266
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
267
-
268
- let restartedSecondTime = false
269
- let restartedThirdTime = false
270
-
271
- for await (const log of child.ndj) {
272
- if (log.msg === 'RELOADED v2') {
273
- restartedSecondTime = true
274
- } else if (log.msg === 'RELOADED v3') {
275
- assert.ok(restartedSecondTime)
276
- restartedThirdTime = true
277
- break
278
- } else if (log.msg?.match(/listening/)) {
279
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v3', true))
280
- }
281
- }
282
-
283
- assert.ok(restartedThirdTime)
284
- })
285
-
286
- test('should not watch files if watch = false', async (t) => {
287
- const tmpDir = await mkdtemp(join(base, 'watch-'))
288
- t.after(() => saferm(tmpDir))
289
- t.diagnostic(`using ${tmpDir}`)
290
- const configFileSrc = join(fixturesDir, 'configs', 'monorepo-watch.json')
291
- const configFileDst = join(tmpDir, 'configs', 'monorepo-watch.json')
292
- const appSrc = join(fixturesDir, 'monorepo-watch')
293
- const appDst = join(tmpDir, 'monorepo-watch')
294
- const cjsPluginFilePath = join(appDst, 'service1', 'plugin.js')
295
-
296
- await Promise.all([
297
- cp(configFileSrc, configFileDst),
298
- cp(appSrc, appDst, { recursive: true })
299
- ])
300
-
301
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
302
- const { child, url } = await start('-c', configFileDst)
303
- t.after(() => child.kill('SIGINT'))
304
- await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
305
- await sleep(5000)
306
- const res = await request(`${url}/version`)
307
- const version = await res.body.text()
308
- assert.strictEqual(version, 'v1')
309
- })