@platformatic/runtime 0.31.1 → 0.33.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/README.md +1 -1
- package/fixtures/configs/hotreload.json +20 -0
- package/fixtures/leven/.env.sample +4 -0
- package/fixtures/leven/platformatic.runtime.json +12 -0
- package/fixtures/leven/services/deeply-spittle/platformatic.service.json +25 -0
- package/fixtures/leven/services/deeply-spittle/plugins/example.js +6 -0
- package/fixtures/leven/services/deeply-spittle/routes/root.js +8 -0
- package/fixtures/leven/services/rainy-empire/platformatic.composer.json +21 -0
- package/fixtures/monorepo/serviceAppWithLogger/platformatic.service.json +2 -1
- package/fixtures/no-env.service.json +25 -0
- package/fixtures/start-command-in-runtime.js +2 -2
- package/fixtures/telemetry/platformatic.runtime.json +19 -0
- package/fixtures/telemetry/services/echo/platformatic.service.json +19 -0
- package/fixtures/telemetry/services/echo/routes/span.js +8 -0
- package/lib/api.js +5 -3
- package/lib/app.js +67 -48
- package/lib/config.js +12 -3
- package/lib/schema.js +10 -1
- package/lib/start.js +6 -1
- package/lib/unified-api.js +0 -9
- package/package.json +13 -9
- package/runtime.mjs +2 -2
- package/test/app.test.js +53 -5
- package/test/cli/compile-2.test.mjs +69 -0
- package/test/cli/compile.test.mjs +2 -22
- package/test/cli/helper.mjs +27 -5
- package/test/cli/start.test.mjs +1 -1
- package/test/cli/watch.test.mjs +82 -9
- package/test/config.test.js +8 -0
- package/test/start.test.js +1 -1
- package/test/telemetry.test.js +37 -0
- package/test/unified-api.test.js +0 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @platformatic/runtime
|
|
2
2
|
|
|
3
|
-
Check out the full documentation for Platformatic Runtime on [our website](https://
|
|
3
|
+
Check out the full documentation for Platformatic Runtime on [our website](https://docs.platformatic.dev/docs/getting-started/quick-start-guide).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.32.0/runtime",
|
|
3
|
+
"entrypoint": "serviceApp",
|
|
4
|
+
"allowCycles": true,
|
|
5
|
+
"hotReload": true,
|
|
6
|
+
"autoload": {
|
|
7
|
+
"path": "../monorepo",
|
|
8
|
+
"exclude": ["docs", "composerApp"],
|
|
9
|
+
"mappings": {
|
|
10
|
+
"serviceAppWithLogger": {
|
|
11
|
+
"id": "with-logger",
|
|
12
|
+
"config": "platformatic.service.json"
|
|
13
|
+
},
|
|
14
|
+
"serviceAppWithMultiplePlugins": {
|
|
15
|
+
"id": "multi-plugin-service",
|
|
16
|
+
"config": "platformatic.service.json"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.31.0/service",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "{PLT_SERVER_HOSTNAME}",
|
|
5
|
+
"port": "{PORT}",
|
|
6
|
+
"logger": {
|
|
7
|
+
"level": "{PLT_SERVER_LOGGER_LEVEL}"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"service": {
|
|
11
|
+
"openapi": true
|
|
12
|
+
},
|
|
13
|
+
"plugins": {
|
|
14
|
+
"paths": [
|
|
15
|
+
{
|
|
16
|
+
"path": "./plugins",
|
|
17
|
+
"encapsulate": false,
|
|
18
|
+
"options": {
|
|
19
|
+
"example": "{PLT_EXAMPLE}"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"./routes"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="@platformatic/service" />
|
|
2
|
+
'use strict'
|
|
3
|
+
/** @param {import('fastify').FastifyInstance} fastify */
|
|
4
|
+
module.exports = async function (fastify, opts) {
|
|
5
|
+
fastify.get('/', async (request, reply) => {
|
|
6
|
+
return { hello: fastify.example }
|
|
7
|
+
})
|
|
8
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.31.0/composer",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "{PLT_SERVER_HOSTNAME}",
|
|
5
|
+
"port": "{PORT}",
|
|
6
|
+
"logger": {
|
|
7
|
+
"level": "{PLT_SERVER_LOGGER_LEVEL}"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"composer": {
|
|
11
|
+
"services": [
|
|
12
|
+
{
|
|
13
|
+
"id": "deeply-splitte",
|
|
14
|
+
"openapi": {
|
|
15
|
+
"url": "/documentation/json"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"watch": false
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.31.0/service",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "{PLT_SERVER_HOSTNAME}",
|
|
5
|
+
"port": "{PORT}",
|
|
6
|
+
"logger": {
|
|
7
|
+
"level": "{PLT_SERVER_LOGGER_LEVEL}"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"service": {
|
|
11
|
+
"openapi": true
|
|
12
|
+
},
|
|
13
|
+
"plugins": {
|
|
14
|
+
"paths": [
|
|
15
|
+
{
|
|
16
|
+
"path": "./plugins",
|
|
17
|
+
"encapsulate": false,
|
|
18
|
+
"options": {
|
|
19
|
+
"example": "{PLT_EXAMPLE}"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"./routes"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const assert = require('node:assert')
|
|
3
3
|
const { request } = require('undici')
|
|
4
|
-
const {
|
|
4
|
+
const { startCommand } = require('../lib/unified-api')
|
|
5
5
|
|
|
6
6
|
async function main () {
|
|
7
|
-
const entrypoint = await
|
|
7
|
+
const entrypoint = await startCommand(['-c', process.argv[2]])
|
|
8
8
|
const endpoint = process.argv[3] ?? '/'
|
|
9
9
|
const res = await request(entrypoint + endpoint)
|
|
10
10
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.31.0/runtime",
|
|
3
|
+
"entrypoint": "echo",
|
|
4
|
+
"allowCycles": false,
|
|
5
|
+
"hotReload": true,
|
|
6
|
+
"autoload": {
|
|
7
|
+
"path": "services",
|
|
8
|
+
"exclude": [
|
|
9
|
+
"docs"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"telemetry": {
|
|
13
|
+
"serviceName": "test-runtime",
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"exporter": {
|
|
16
|
+
"type": "memory"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://platformatic.dev/schemas/v0.28.1/service",
|
|
3
|
+
"server": {
|
|
4
|
+
"hostname": "127.0.0.1",
|
|
5
|
+
"port": "0",
|
|
6
|
+
"logger": {
|
|
7
|
+
"level": "info"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"service": {
|
|
11
|
+
"openapi": true
|
|
12
|
+
},
|
|
13
|
+
"plugins": {
|
|
14
|
+
"paths": [
|
|
15
|
+
"./routes"
|
|
16
|
+
],
|
|
17
|
+
"typescript": false
|
|
18
|
+
}
|
|
19
|
+
}
|
package/lib/api.js
CHANGED
|
@@ -11,10 +11,12 @@ class RuntimeApi {
|
|
|
11
11
|
|
|
12
12
|
constructor (config, logger, loaderPort) {
|
|
13
13
|
this.#services = new Map()
|
|
14
|
+
const telemetryConfig = config.telemetry
|
|
14
15
|
|
|
15
16
|
for (let i = 0; i < config.services.length; ++i) {
|
|
16
17
|
const service = config.services[i]
|
|
17
|
-
const
|
|
18
|
+
const serviceTelemetryConfig = telemetryConfig ? { ...telemetryConfig, serviceName: `${telemetryConfig.serviceName}-${service.id}` } : null
|
|
19
|
+
const app = new PlatformaticApp(service, loaderPort, logger, serviceTelemetryConfig)
|
|
18
20
|
|
|
19
21
|
this.#services.set(service.id, app)
|
|
20
22
|
}
|
|
@@ -47,9 +49,9 @@ class RuntimeApi {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
async #handleProcessLevelEvent (message) {
|
|
50
|
-
|
|
52
|
+
await Promise.allSettled(this.#services.values().map(async (service) => {
|
|
51
53
|
await service.handleProcessLevelEvent(message)
|
|
52
|
-
}
|
|
54
|
+
}))
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
async #executeCommand (message) {
|
package/lib/app.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
|
|
2
3
|
const { once } = require('node:events')
|
|
3
4
|
const { dirname, basename } = require('node:path')
|
|
4
5
|
const { FileWatcher } = require('@platformatic/utils')
|
|
6
|
+
const debounce = require('debounce')
|
|
5
7
|
const {
|
|
6
8
|
buildServer,
|
|
7
9
|
loadConfig
|
|
@@ -13,9 +15,11 @@ class PlatformaticApp {
|
|
|
13
15
|
#restarting
|
|
14
16
|
#started
|
|
15
17
|
#originalWatch
|
|
18
|
+
#fileWatcher
|
|
16
19
|
#logger
|
|
20
|
+
#telemetryConfig
|
|
17
21
|
|
|
18
|
-
constructor (appConfig, loaderPort, logger) {
|
|
22
|
+
constructor (appConfig, loaderPort, logger, telemetryConfig) {
|
|
19
23
|
this.appConfig = appConfig
|
|
20
24
|
this.config = null
|
|
21
25
|
this.#hotReload = false
|
|
@@ -24,7 +28,11 @@ class PlatformaticApp {
|
|
|
24
28
|
this.server = null
|
|
25
29
|
this.#started = false
|
|
26
30
|
this.#originalWatch = null
|
|
27
|
-
this.#
|
|
31
|
+
this.#fileWatcher = null
|
|
32
|
+
this.#logger = logger.child({
|
|
33
|
+
name: this.appConfig.id
|
|
34
|
+
})
|
|
35
|
+
this.#telemetryConfig = telemetryConfig
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
getStatus () {
|
|
@@ -36,16 +44,11 @@ class PlatformaticApp {
|
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
async restart (force) {
|
|
39
|
-
if (this.#restarting) {
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!this.#hotReload && !force) {
|
|
47
|
+
if (this.#restarting || !this.#started || (!this.#hotReload && !force)) {
|
|
44
48
|
return
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
this.#restarting = true
|
|
48
|
-
await this.stop()
|
|
49
52
|
|
|
50
53
|
/* c8 ignore next 4 - tests may not pass in a MessagePort. */
|
|
51
54
|
if (this.#loaderPort) {
|
|
@@ -53,7 +56,13 @@ class PlatformaticApp {
|
|
|
53
56
|
await once(this.#loaderPort, 'message')
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
this.#setuplogger(this.config.configManager)
|
|
60
|
+
try {
|
|
61
|
+
await this.server.restart()
|
|
62
|
+
} catch (err) {
|
|
63
|
+
this.#logAndExit(err)
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
this.#restarting = false
|
|
58
67
|
}
|
|
59
68
|
|
|
@@ -62,38 +71,37 @@ class PlatformaticApp {
|
|
|
62
71
|
throw new Error('application is already started')
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
this.#started = true
|
|
75
|
+
|
|
76
|
+
await this.#initializeConfig()
|
|
77
|
+
this.#originalWatch = this.config.configManager.current.watch
|
|
78
|
+
this.config.configManager.current.watch = false
|
|
79
|
+
|
|
68
80
|
const { configManager } = this.config
|
|
81
|
+
configManager.update({
|
|
82
|
+
...configManager.current,
|
|
83
|
+
telemetry: this.#telemetryConfig
|
|
84
|
+
})
|
|
69
85
|
const config = configManager.current
|
|
70
86
|
|
|
71
|
-
this.#originalWatch = config.watch
|
|
72
|
-
config.watch = false
|
|
73
87
|
this.#setuplogger(configManager)
|
|
74
88
|
|
|
75
89
|
try {
|
|
76
90
|
// If this is a restart, have the fastify server restart itself. If this
|
|
77
91
|
// is not a restart, then create a new server.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
...config,
|
|
83
|
-
configManager
|
|
84
|
-
})
|
|
85
|
-
}
|
|
92
|
+
this.server = await buildServer({
|
|
93
|
+
...config,
|
|
94
|
+
configManager
|
|
95
|
+
})
|
|
86
96
|
} catch (err) {
|
|
87
97
|
this.#logAndExit(err)
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
if (config.plugins !== undefined
|
|
100
|
+
if (config.plugins !== undefined) {
|
|
91
101
|
this.#startFileWatching()
|
|
92
102
|
}
|
|
93
103
|
|
|
94
|
-
this
|
|
95
|
-
|
|
96
|
-
if (this.appConfig.entrypoint && !this.#restarting) {
|
|
104
|
+
if (this.appConfig.entrypoint) {
|
|
97
105
|
try {
|
|
98
106
|
await this.server.start()
|
|
99
107
|
/* c8 ignore next 5 */
|
|
@@ -109,11 +117,9 @@ class PlatformaticApp {
|
|
|
109
117
|
throw new Error('application has not been started')
|
|
110
118
|
}
|
|
111
119
|
|
|
112
|
-
if (!this.#restarting) {
|
|
113
|
-
await this.server.close()
|
|
114
|
-
}
|
|
115
|
-
|
|
116
120
|
await this.#stopFileWatching()
|
|
121
|
+
await this.server.close()
|
|
122
|
+
|
|
117
123
|
this.#started = false
|
|
118
124
|
}
|
|
119
125
|
|
|
@@ -148,12 +154,19 @@ class PlatformaticApp {
|
|
|
148
154
|
async #initializeConfig () {
|
|
149
155
|
const appConfig = this.appConfig
|
|
150
156
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
let _config
|
|
158
|
+
try {
|
|
159
|
+
_config = await loadConfig({}, ['-c', appConfig.config], null, {
|
|
160
|
+
watch: true,
|
|
161
|
+
onMissingEnv (key) {
|
|
162
|
+
return appConfig.localServiceEnvVars.get(key)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
} catch (err) {
|
|
166
|
+
this.#logAndExit(err)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.config = _config
|
|
157
170
|
const { configManager } = this.config
|
|
158
171
|
|
|
159
172
|
function applyOverrides () {
|
|
@@ -190,11 +203,13 @@ class PlatformaticApp {
|
|
|
190
203
|
this.#hotReload = this.appConfig.hotReload
|
|
191
204
|
|
|
192
205
|
configManager.on('update', async (newConfig) => {
|
|
193
|
-
this.server
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
if (this.server) { // when we setup telemetry on config, we don't have a server yet
|
|
207
|
+
this.server.platformatic.config = newConfig
|
|
208
|
+
applyOverrides()
|
|
209
|
+
this.server.log.debug('config changed')
|
|
210
|
+
this.server.log.trace({ newConfig }, 'new config')
|
|
211
|
+
await this.restart()
|
|
212
|
+
}
|
|
198
213
|
})
|
|
199
214
|
|
|
200
215
|
configManager.on('error', (err) => {
|
|
@@ -206,14 +221,15 @@ class PlatformaticApp {
|
|
|
206
221
|
#setuplogger (configManager) {
|
|
207
222
|
// Set the logger if not present (and the config supports it).
|
|
208
223
|
if (configManager.current.server) {
|
|
209
|
-
const childLogger = this.#logger.child({
|
|
210
|
-
name: this.appConfig.id
|
|
211
|
-
}, { level: configManager.current.server.logger?.level || 'info' })
|
|
224
|
+
const childLogger = this.#logger.child({}, { level: configManager.current.server.logger?.level || 'info' })
|
|
212
225
|
configManager.current.server.logger = childLogger
|
|
213
226
|
}
|
|
214
227
|
}
|
|
215
228
|
|
|
216
229
|
#startFileWatching () {
|
|
230
|
+
if (this.#fileWatcher) {
|
|
231
|
+
return
|
|
232
|
+
}
|
|
217
233
|
const server = this.server
|
|
218
234
|
const { configManager } = server.platformatic
|
|
219
235
|
// TODO FileWatcher and ConfigManager both watch the configuration file
|
|
@@ -226,27 +242,30 @@ class PlatformaticApp {
|
|
|
226
242
|
})
|
|
227
243
|
|
|
228
244
|
/* c8 ignore next 4 */
|
|
229
|
-
|
|
230
|
-
this.server.log.
|
|
245
|
+
const restart = debounce(() => {
|
|
246
|
+
this.server.log.info('files changed')
|
|
231
247
|
this.restart()
|
|
232
|
-
})
|
|
248
|
+
}, 100) // debounce restart for 100ms
|
|
249
|
+
fileWatcher.on('update', restart)
|
|
233
250
|
|
|
234
251
|
fileWatcher.startWatching()
|
|
235
252
|
server.log.debug('start watching files')
|
|
236
253
|
server.platformatic.fileWatcher = fileWatcher
|
|
237
254
|
server.platformatic.configManager.startWatching()
|
|
255
|
+
this.#fileWatcher = fileWatcher
|
|
238
256
|
}
|
|
239
257
|
|
|
240
258
|
async #stopFileWatching () {
|
|
241
|
-
const watcher = this.server.platformatic.fileWatcher
|
|
242
259
|
// The configManager automatically watches for the config file changes
|
|
243
260
|
// therefore we need to stop it all the times.
|
|
244
261
|
await this.config.configManager.stopWatching()
|
|
245
262
|
|
|
263
|
+
const watcher = this.#fileWatcher
|
|
246
264
|
if (watcher) {
|
|
247
265
|
this.server.log.debug('stop watching files')
|
|
248
266
|
await watcher.stopWatching()
|
|
249
267
|
this.server.platformatic.fileWatcher = undefined
|
|
268
|
+
this.#fileWatcher = null
|
|
250
269
|
}
|
|
251
270
|
}
|
|
252
271
|
|
package/lib/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const { readFile, readdir } = require('node:fs/promises')
|
|
3
3
|
const { basename, join, resolve: pathResolve } = require('node:path')
|
|
4
|
+
const { closest } = require('fastest-levenshtein')
|
|
4
5
|
const Topo = require('@hapi/topo')
|
|
5
6
|
const ConfigManager = require('@platformatic/config')
|
|
6
7
|
const { schema } = require('./schema')
|
|
@@ -71,6 +72,15 @@ async function _transformConfig (configManager) {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
function missingDependencyErrorMessage (clientName, service, configManager) {
|
|
76
|
+
const closestName = closest(clientName, [...configManager.current.serviceMap.keys()])
|
|
77
|
+
let errorMsg = `service '${service.id}' has unknown dependency: '${clientName}'.`
|
|
78
|
+
if (closestName) {
|
|
79
|
+
errorMsg += ` Did you mean '${closestName}'?`
|
|
80
|
+
}
|
|
81
|
+
return errorMsg
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
async function parseClientsAndComposer (configManager) {
|
|
75
85
|
for (let i = 0; i < configManager.current.services.length; ++i) {
|
|
76
86
|
const service = configManager.current.services[i]
|
|
@@ -86,8 +96,7 @@ async function parseClientsAndComposer (configManager) {
|
|
|
86
96
|
const dependency = configManager.current.serviceMap.get(clientName)
|
|
87
97
|
|
|
88
98
|
if (dependency === undefined) {
|
|
89
|
-
|
|
90
|
-
throw new Error(`service '${service.id}' has unknown dependency: '${clientName}'`)
|
|
99
|
+
throw new Error(missingDependencyErrorMessage(clientName, service, configManager))
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
dependency.dependents.push(service.id)
|
|
@@ -170,7 +179,7 @@ async function parseClientsAndComposer (configManager) {
|
|
|
170
179
|
|
|
171
180
|
/* c8 ignore next 4 */
|
|
172
181
|
if (dependency === undefined) {
|
|
173
|
-
reject(new Error(
|
|
182
|
+
reject(new Error(missingDependencyErrorMessage(clientName, service, configManager)))
|
|
174
183
|
return
|
|
175
184
|
}
|
|
176
185
|
|
package/lib/schema.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
+
const telemetry = require('@platformatic/telemetry').schema
|
|
4
5
|
const pkg = require('../package.json')
|
|
5
6
|
const version = 'v' + pkg.version
|
|
6
7
|
const platformaticRuntimeSchema = {
|
|
@@ -42,6 +43,7 @@ const platformaticRuntimeSchema = {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
},
|
|
46
|
+
telemetry,
|
|
45
47
|
services: {
|
|
46
48
|
type: 'array',
|
|
47
49
|
default: [],
|
|
@@ -67,7 +69,14 @@ const platformaticRuntimeSchema = {
|
|
|
67
69
|
type: 'string'
|
|
68
70
|
},
|
|
69
71
|
hotReload: {
|
|
70
|
-
|
|
72
|
+
anyOf: [
|
|
73
|
+
{
|
|
74
|
+
type: 'boolean'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'string'
|
|
78
|
+
}
|
|
79
|
+
]
|
|
71
80
|
},
|
|
72
81
|
allowCycles: {
|
|
73
82
|
type: 'boolean'
|
package/lib/start.js
CHANGED
|
@@ -46,8 +46,12 @@ async function startWithConfig (configManager, env = process.env) {
|
|
|
46
46
|
env
|
|
47
47
|
})
|
|
48
48
|
|
|
49
|
+
let exited = null
|
|
49
50
|
worker.on('exit', () => {
|
|
50
51
|
configManager.fileWatcher?.stopWatching()
|
|
52
|
+
if (typeof exited === 'function') {
|
|
53
|
+
exited()
|
|
54
|
+
}
|
|
51
55
|
})
|
|
52
56
|
|
|
53
57
|
worker.on('error', () => {
|
|
@@ -65,8 +69,9 @@ async function startWithConfig (configManager, env = process.env) {
|
|
|
65
69
|
worker.postMessage({ signal: 'SIGUSR2' })
|
|
66
70
|
})
|
|
67
71
|
|
|
68
|
-
closeWithGrace((event) => {
|
|
72
|
+
closeWithGrace((event, cb) => {
|
|
69
73
|
worker.postMessage(event)
|
|
74
|
+
exited = cb
|
|
70
75
|
})
|
|
71
76
|
|
|
72
77
|
/* c8 ignore next 3 */
|
package/lib/unified-api.js
CHANGED
|
@@ -154,14 +154,6 @@ async function _start (args) {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
async function startCommand (args) {
|
|
157
|
-
try {
|
|
158
|
-
await _start(args)
|
|
159
|
-
} catch (err) {
|
|
160
|
-
logErrorAndExit(err)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function startCommandInRuntime (args) {
|
|
165
157
|
try {
|
|
166
158
|
const configType = await getConfigType(args)
|
|
167
159
|
const config = await _loadConfig({}, args, configType)
|
|
@@ -200,6 +192,5 @@ module.exports = {
|
|
|
200
192
|
loadConfig: _loadConfig,
|
|
201
193
|
start: _start,
|
|
202
194
|
startCommand,
|
|
203
|
-
startCommandInRuntime,
|
|
204
195
|
getApp
|
|
205
196
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,15 +24,17 @@
|
|
|
24
24
|
"standard": "^17.1.0",
|
|
25
25
|
"tsd": "^0.28.1",
|
|
26
26
|
"typescript": "^5.1.6",
|
|
27
|
-
"@platformatic/sql-
|
|
28
|
-
"@platformatic/sql-
|
|
27
|
+
"@platformatic/sql-graphql": "0.33.0",
|
|
28
|
+
"@platformatic/sql-mapper": "0.33.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@hapi/topo": "^6.0.2",
|
|
32
32
|
"close-with-grace": "^1.2.0",
|
|
33
33
|
"commist": "^3.2.0",
|
|
34
|
+
"debounce": "^1.2.1",
|
|
34
35
|
"desm": "^1.3.0",
|
|
35
36
|
"es-main": "^1.2.0",
|
|
37
|
+
"fastest-levenshtein": "^1.0.16",
|
|
36
38
|
"fastify": "^4.18.0",
|
|
37
39
|
"fastify-undici-dispatcher": "^0.4.1",
|
|
38
40
|
"help-me": "^4.2.0",
|
|
@@ -40,11 +42,12 @@
|
|
|
40
42
|
"pino": "^8.14.1",
|
|
41
43
|
"pino-pretty": "^10.0.0",
|
|
42
44
|
"undici": "^5.22.1",
|
|
43
|
-
"@platformatic/composer": "0.
|
|
44
|
-
"@platformatic/config": "0.
|
|
45
|
-
"@platformatic/db": "0.
|
|
46
|
-
"@platformatic/service": "0.
|
|
47
|
-
"@platformatic/
|
|
45
|
+
"@platformatic/composer": "0.33.0",
|
|
46
|
+
"@platformatic/config": "0.33.0",
|
|
47
|
+
"@platformatic/db": "0.33.0",
|
|
48
|
+
"@platformatic/service": "0.33.0",
|
|
49
|
+
"@platformatic/telemetry": "0.33.0",
|
|
50
|
+
"@platformatic/utils": "0.33.0"
|
|
48
51
|
},
|
|
49
52
|
"standard": {
|
|
50
53
|
"ignore": [
|
|
@@ -53,7 +56,8 @@
|
|
|
53
56
|
]
|
|
54
57
|
},
|
|
55
58
|
"scripts": {
|
|
56
|
-
"test": "npm run lint &&
|
|
59
|
+
"test": "npm run lint && node --test && tsd",
|
|
60
|
+
"coverage": "npm run lint && c8 -x fixtures -x test node --test && tsd",
|
|
57
61
|
"lint": "standard | snazzy"
|
|
58
62
|
}
|
|
59
63
|
}
|
package/runtime.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'desm'
|
|
|
6
6
|
import isMain from 'es-main'
|
|
7
7
|
import helpMe from 'help-me'
|
|
8
8
|
import parseArgs from 'minimist'
|
|
9
|
-
import {
|
|
9
|
+
import { startCommand } from './lib/unified-api.js'
|
|
10
10
|
import { compile as compileCmd } from './lib/compile.js'
|
|
11
11
|
|
|
12
12
|
export const compile = compileCmd
|
|
@@ -22,7 +22,7 @@ const program = commist({ maxDistance: 2 })
|
|
|
22
22
|
program.register('help', help.toStdout)
|
|
23
23
|
program.register('help start', help.toStdout.bind(null, ['start']))
|
|
24
24
|
program.register('help compile', help.toStdout.bind(null, ['compile']))
|
|
25
|
-
program.register('start',
|
|
25
|
+
program.register('start', startCommand)
|
|
26
26
|
program.register('compile', compile)
|
|
27
27
|
|
|
28
28
|
export async function run (argv) {
|
package/test/app.test.js
CHANGED
|
@@ -90,7 +90,7 @@ test('errors when stopping an already stopped application', async (t) => {
|
|
|
90
90
|
})
|
|
91
91
|
|
|
92
92
|
test('does not restart while restarting', async (t) => {
|
|
93
|
-
const { logger } = getLoggerAndStream()
|
|
93
|
+
const { logger, stream } = getLoggerAndStream()
|
|
94
94
|
const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
|
|
95
95
|
const configFile = join(appPath, 'platformatic.service.json')
|
|
96
96
|
const config = {
|
|
@@ -105,17 +105,33 @@ test('does not restart while restarting', async (t) => {
|
|
|
105
105
|
}
|
|
106
106
|
const app = new PlatformaticApp(config, null, logger)
|
|
107
107
|
|
|
108
|
-
t.after(
|
|
108
|
+
t.after(async () => {
|
|
109
|
+
try {
|
|
110
|
+
await app.stop()
|
|
111
|
+
} catch {}
|
|
112
|
+
})
|
|
109
113
|
await app.start()
|
|
110
|
-
t.mock.method(app, 'stop')
|
|
111
114
|
await Promise.all([
|
|
112
115
|
app.restart(),
|
|
113
116
|
app.restart(),
|
|
114
117
|
app.restart()
|
|
115
118
|
])
|
|
119
|
+
await app.stop()
|
|
120
|
+
stream.end()
|
|
121
|
+
const lines = []
|
|
122
|
+
for await (const line of stream) {
|
|
123
|
+
lines.push(line)
|
|
124
|
+
}
|
|
116
125
|
|
|
117
|
-
|
|
118
|
-
|
|
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)
|
|
119
135
|
})
|
|
120
136
|
|
|
121
137
|
test('restarts on SIGUSR2', async (t) => {
|
|
@@ -308,3 +324,35 @@ test('restarts on config change without overriding the configManager', async (t)
|
|
|
308
324
|
}
|
|
309
325
|
assert.strictEqual(configManager, app.server.platformatic.configManager)
|
|
310
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
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
})
|
|
@@ -2,10 +2,9 @@ import { test } from 'node:test'
|
|
|
2
2
|
import assert from 'node:assert'
|
|
3
3
|
import { join } from 'desm'
|
|
4
4
|
import path from 'node:path'
|
|
5
|
-
import { cliPath } from './helper.mjs'
|
|
5
|
+
import { cliPath, delDir } from './helper.mjs'
|
|
6
6
|
import { execa } from 'execa'
|
|
7
|
-
import { mkdtemp,
|
|
8
|
-
import { setTimeout as sleep } from 'node:timers/promises'
|
|
7
|
+
import { mkdtemp, cp, mkdir } from 'node:fs/promises'
|
|
9
8
|
|
|
10
9
|
const base = join(import.meta.url, '..', 'tmp')
|
|
11
10
|
|
|
@@ -14,25 +13,6 @@ try {
|
|
|
14
13
|
} catch {
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
function delDir (tmpDir) {
|
|
18
|
-
return async function () {
|
|
19
|
-
// We give up after 10s.
|
|
20
|
-
// This is because on Windows, it's very hard to delete files if the file
|
|
21
|
-
// system is not collaborating.
|
|
22
|
-
for (let i = 0; i < 10; i++) {
|
|
23
|
-
try {
|
|
24
|
-
await rm(tmpDir, { recursive: true, force: true })
|
|
25
|
-
break
|
|
26
|
-
} catch (err) {
|
|
27
|
-
if (err.code === 'EBUSY') {
|
|
28
|
-
await sleep(1000)
|
|
29
|
-
continue
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
16
|
test('compile without tsconfigs', async () => {
|
|
37
17
|
const config = join(import.meta.url, '..', '..', 'fixtures', 'configs', 'monorepo.json')
|
|
38
18
|
await execa(cliPath, ['compile', '-c', config])
|
package/test/cli/helper.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { Agent, setGlobalDispatcher } from 'undici'
|
|
|
3
3
|
import { execa } from 'execa'
|
|
4
4
|
import split from 'split2'
|
|
5
5
|
import { join } from 'desm'
|
|
6
|
+
import { rm } from 'node:fs/promises'
|
|
6
7
|
|
|
7
8
|
setGlobalDispatcher(new Agent({
|
|
8
9
|
keepAliveTimeout: 10,
|
|
@@ -34,13 +35,34 @@ export async function start (...args) {
|
|
|
34
35
|
|
|
35
36
|
for await (const messages of on(output, 'data')) {
|
|
36
37
|
for (const message of messages) {
|
|
37
|
-
|
|
38
|
-
message.
|
|
38
|
+
if (message.msg) {
|
|
39
|
+
const url = message.url ??
|
|
40
|
+
message.msg.match(/server listening at (.+)/i)?.[1]
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
if (url !== undefined) {
|
|
43
|
+
clearTimeout(errorTimeout)
|
|
44
|
+
return { child, url, output }
|
|
45
|
+
}
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
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
|
+
}
|
package/test/cli/start.test.mjs
CHANGED
|
@@ -74,7 +74,7 @@ test('does not start if node inspector flags are provided', async (t) => {
|
|
|
74
74
|
for (const message of messages) {
|
|
75
75
|
stderr += message
|
|
76
76
|
|
|
77
|
-
if (/
|
|
77
|
+
if (/The Node.js inspector flags are not supported/.test(stderr)) {
|
|
78
78
|
found = true
|
|
79
79
|
break
|
|
80
80
|
}
|
package/test/cli/watch.test.mjs
CHANGED
|
@@ -47,9 +47,13 @@ function createEsmLoggingPlugin (text, reloaded) {
|
|
|
47
47
|
`
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function saferm (path) {
|
|
51
|
+
return rm(path, { recursive: true, force: true }).catch(() => {})
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
test('watches CommonJS files', async (t) => {
|
|
51
55
|
const tmpDir = await mkdtemp(join(base, 'watch-'))
|
|
52
|
-
t.after(() =>
|
|
56
|
+
t.after(() => saferm(tmpDir))
|
|
53
57
|
t.diagnostic(`using ${tmpDir}`)
|
|
54
58
|
const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
|
|
55
59
|
const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
|
|
@@ -65,8 +69,6 @@ test('watches CommonJS files', async (t) => {
|
|
|
65
69
|
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
|
|
66
70
|
const { child } = await start('-c', configFileDst)
|
|
67
71
|
t.after(() => child.kill('SIGINT'))
|
|
68
|
-
child.stdout.pipe(process.stderr)
|
|
69
|
-
child.stderr.pipe(process.stderr)
|
|
70
72
|
|
|
71
73
|
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
|
|
72
74
|
|
|
@@ -79,7 +81,7 @@ test('watches CommonJS files', async (t) => {
|
|
|
79
81
|
|
|
80
82
|
test('watches ESM files', async (t) => {
|
|
81
83
|
const tmpDir = await mkdtemp(join(base, 'watch-'))
|
|
82
|
-
t.after(() =>
|
|
84
|
+
t.after(() => saferm(tmpDir))
|
|
83
85
|
t.diagnostic(`using ${tmpDir}`)
|
|
84
86
|
const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
|
|
85
87
|
const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
|
|
@@ -95,8 +97,6 @@ test('watches ESM files', async (t) => {
|
|
|
95
97
|
await writeFile(esmPluginFilePath, createEsmLoggingPlugin('v1', false))
|
|
96
98
|
const { child } = await start('-c', configFileDst)
|
|
97
99
|
t.after(() => child.kill('SIGINT'))
|
|
98
|
-
child.stdout.pipe(process.stderr)
|
|
99
|
-
child.stderr.pipe(process.stderr)
|
|
100
100
|
await writeFile(esmPluginFilePath, createEsmLoggingPlugin('v2', true))
|
|
101
101
|
|
|
102
102
|
for await (const log of child.ndj) {
|
|
@@ -108,7 +108,7 @@ test('watches ESM files', async (t) => {
|
|
|
108
108
|
|
|
109
109
|
test('should not hot reload files with `--hot-reload false', async (t) => {
|
|
110
110
|
const tmpDir = await mkdtemp(join(base, 'watch-'))
|
|
111
|
-
t.after(() =>
|
|
111
|
+
t.after(() => saferm(tmpDir))
|
|
112
112
|
t.diagnostic(`using ${tmpDir}`)
|
|
113
113
|
const configFileSrc = join(fixturesDir, 'configs', 'monorepo.json')
|
|
114
114
|
const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
|
|
@@ -124,11 +124,84 @@ test('should not hot reload files with `--hot-reload false', async (t) => {
|
|
|
124
124
|
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
|
|
125
125
|
const { child, url } = await start('-c', configFileDst, '--hot-reload', 'false')
|
|
126
126
|
t.after(() => child.kill('SIGINT'))
|
|
127
|
-
child.stdout.pipe(process.stderr)
|
|
128
|
-
child.stderr.pipe(process.stderr)
|
|
129
127
|
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
|
|
130
128
|
await sleep(5000)
|
|
131
129
|
const res = await request(`${url}/version`)
|
|
132
130
|
const version = await res.body.text()
|
|
133
131
|
assert.strictEqual(version, 'v1')
|
|
134
132
|
})
|
|
133
|
+
|
|
134
|
+
test('watches CommonJS files with hotreload', { timeout: 30000, skip: process.env.CI }, async (t) => {
|
|
135
|
+
const tmpDir = await mkdtemp(join(base, 'watch-'))
|
|
136
|
+
t.after(() => saferm(tmpDir))
|
|
137
|
+
t.diagnostic(`using ${tmpDir}`)
|
|
138
|
+
const configFileSrc = join(fixturesDir, 'configs', 'hotreload.json')
|
|
139
|
+
const configFileDst = join(tmpDir, 'configs', 'monorepo.json')
|
|
140
|
+
const appSrc = join(fixturesDir, 'monorepo')
|
|
141
|
+
const appDst = join(tmpDir, 'monorepo')
|
|
142
|
+
const cjsPluginFilePath = join(appDst, 'serviceAppWithLogger', 'plugin.js')
|
|
143
|
+
|
|
144
|
+
await Promise.all([
|
|
145
|
+
cp(configFileSrc, configFileDst),
|
|
146
|
+
cp(appSrc, appDst, { recursive: true })
|
|
147
|
+
])
|
|
148
|
+
|
|
149
|
+
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
|
|
150
|
+
const { child } = await start('-c', configFileDst)
|
|
151
|
+
t.after(() => child.kill('SIGINT'))
|
|
152
|
+
|
|
153
|
+
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
|
|
154
|
+
|
|
155
|
+
let restartedSecondTime = false
|
|
156
|
+
let restartedThirdTime = false
|
|
157
|
+
|
|
158
|
+
for await (const log of child.ndj) {
|
|
159
|
+
if (log.msg === 'RELOADED v2') {
|
|
160
|
+
restartedSecondTime = true
|
|
161
|
+
} else if (log.msg === 'RELOADED v3') {
|
|
162
|
+
restartedThirdTime = true
|
|
163
|
+
break
|
|
164
|
+
} else if (log.msg?.match(/watching/)) {
|
|
165
|
+
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v3', true))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
assert.ok(restartedSecondTime)
|
|
170
|
+
assert.ok(restartedThirdTime)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('watches CommonJS files with hotreload on a single service', { timeout: 30000, skip: process.env.CI }, async (t) => {
|
|
174
|
+
const tmpDir = await mkdtemp(join(base, 'watch-'))
|
|
175
|
+
t.after(() => saferm(tmpDir))
|
|
176
|
+
t.diagnostic(`using ${tmpDir}`)
|
|
177
|
+
const appSrc = join(fixturesDir, 'monorepo', 'serviceAppWithLogger')
|
|
178
|
+
const appDst = join(tmpDir)
|
|
179
|
+
const cjsPluginFilePath = join(appDst, 'plugin.js')
|
|
180
|
+
|
|
181
|
+
await Promise.all([
|
|
182
|
+
cp(appSrc, appDst, { recursive: true })
|
|
183
|
+
])
|
|
184
|
+
|
|
185
|
+
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v1', false))
|
|
186
|
+
const { child } = await start('-c', join(appDst, 'platformatic.service.json'))
|
|
187
|
+
t.after(() => child.kill('SIGINT'))
|
|
188
|
+
|
|
189
|
+
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v2', true))
|
|
190
|
+
|
|
191
|
+
let restartedSecondTime = false
|
|
192
|
+
let restartedThirdTime = false
|
|
193
|
+
|
|
194
|
+
for await (const log of child.ndj) {
|
|
195
|
+
if (log.msg === 'RELOADED v2') {
|
|
196
|
+
restartedSecondTime = true
|
|
197
|
+
} else if (log.msg === 'RELOADED v3') {
|
|
198
|
+
assert.ok(restartedSecondTime)
|
|
199
|
+
restartedThirdTime = true
|
|
200
|
+
break
|
|
201
|
+
} else if (log.msg?.match(/listening/)) {
|
|
202
|
+
await writeFile(cjsPluginFilePath, createCjsLoggingPlugin('v3', true))
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
assert.ok(restartedThirdTime)
|
|
207
|
+
})
|
package/test/config.test.js
CHANGED
|
@@ -55,6 +55,14 @@ test('performs a topological sort on services depending on allowCycles', async (
|
|
|
55
55
|
await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
56
56
|
})
|
|
57
57
|
})
|
|
58
|
+
|
|
59
|
+
await t.test('throws by adding the most probable service ', async () => {
|
|
60
|
+
const configFile = join(fixturesDir, 'leven', 'platformatic.runtime.json')
|
|
61
|
+
|
|
62
|
+
await assert.rejects(async () => {
|
|
63
|
+
await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
64
|
+
}, 'service \'rainy-empire\' has unordered dependency: \'deeply-splitte\'. Did you mean \'deeply-spittle\'?')
|
|
65
|
+
})
|
|
58
66
|
})
|
|
59
67
|
|
|
60
68
|
test('can resolve service id from client package.json if not provided', async () => {
|
package/test/start.test.js
CHANGED
|
@@ -68,7 +68,7 @@ test('composer', async (t) => {
|
|
|
68
68
|
const res = await request(entryUrl)
|
|
69
69
|
|
|
70
70
|
assert.strictEqual(res.statusCode, 200)
|
|
71
|
-
assert.deepStrictEqual(await res.body.json(), { message: 'Welcome to Platformatic! Please visit https://
|
|
71
|
+
assert.deepStrictEqual(await res.body.json(), { message: 'Welcome to Platformatic! Please visit https://docs.platformatic.dev' })
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
{
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
const { request } = require('undici')
|
|
5
|
+
const { test } = require('node:test')
|
|
6
|
+
const { join } = require('node:path')
|
|
7
|
+
const { loadConfig } = require('@platformatic/service')
|
|
8
|
+
const { platformaticRuntime } = require('..')
|
|
9
|
+
const { startWithConfig } = require('../lib/start')
|
|
10
|
+
const fixturesDir = join(__dirname, '..', 'fixtures')
|
|
11
|
+
|
|
12
|
+
test('propagate the traceId correctly to runtime services', async (t) => {
|
|
13
|
+
const configFile = join(fixturesDir, 'telemetry', 'platformatic.runtime.json')
|
|
14
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
15
|
+
const app = await startWithConfig(config.configManager)
|
|
16
|
+
|
|
17
|
+
t.after(async () => {
|
|
18
|
+
await app.close()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const entryUrl = await app.start()
|
|
22
|
+
|
|
23
|
+
const traceId = '5e994e8fb53b27c91dcd2fec22771d15'
|
|
24
|
+
const spanId = '166f3ab30f21800b'
|
|
25
|
+
const traceparent = `00-${traceId}-${spanId}-01`
|
|
26
|
+
const res = await request(entryUrl, {
|
|
27
|
+
method: 'GET',
|
|
28
|
+
path: '/',
|
|
29
|
+
headers: {
|
|
30
|
+
traceparent
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
assert.strictEqual(res.statusCode, 200)
|
|
35
|
+
const response = await res.body.json()
|
|
36
|
+
assert.strictEqual(response.traceId, traceId)
|
|
37
|
+
})
|
package/test/unified-api.test.js
CHANGED
|
@@ -254,7 +254,6 @@ test('start()', async (t) => {
|
|
|
254
254
|
const scriptFile = join(fixturesDir, 'starter.js')
|
|
255
255
|
const configFile = join(fixturesDir, 'monorepo', 'serviceAppWithLogger', 'platformatic.service.json')
|
|
256
256
|
const child = spawn(process.execPath, [scriptFile, configFile])
|
|
257
|
-
child.stdout.pipe(process.stdout)
|
|
258
257
|
child.stderr.pipe(process.stderr)
|
|
259
258
|
const [exitCode] = await once(child, 'exit')
|
|
260
259
|
|
|
@@ -318,14 +317,11 @@ test('startCommand()', async (t) => {
|
|
|
318
317
|
|
|
319
318
|
assert.strictEqual(exitCode, 42)
|
|
320
319
|
})
|
|
321
|
-
})
|
|
322
320
|
|
|
323
|
-
test('startCommandInRuntime()', async (t) => {
|
|
324
321
|
await t.test('can start a non-runtime application', async (t) => {
|
|
325
322
|
const scriptFile = join(fixturesDir, 'start-command-in-runtime.js')
|
|
326
323
|
const configFile = join(fixturesDir, 'monorepo', 'serviceAppWithLogger', 'platformatic.service.json')
|
|
327
324
|
const child = spawn(process.execPath, [scriptFile, configFile])
|
|
328
|
-
child.stdout.pipe(process.stdout)
|
|
329
325
|
child.stderr.pipe(process.stderr)
|
|
330
326
|
const [exitCode] = await once(child, 'exit')
|
|
331
327
|
|
|
@@ -336,7 +332,6 @@ test('startCommandInRuntime()', async (t) => {
|
|
|
336
332
|
const scriptFile = join(fixturesDir, 'start-command-in-runtime.js')
|
|
337
333
|
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
338
334
|
const child = spawn(process.execPath, [scriptFile, configFile])
|
|
339
|
-
child.stdout.pipe(process.stdout)
|
|
340
335
|
child.stderr.pipe(process.stderr)
|
|
341
336
|
const [exitCode] = await once(child, 'exit')
|
|
342
337
|
|