@platformatic/runtime 0.27.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fixtures/configs/invalid-schema-type.config.json +1 -2
- package/fixtures/configs/monorepo-client-without-id.json +24 -0
- package/fixtures/configs/no-schema.config.json +1 -2
- package/fixtures/monorepo/serviceApp/platformatic.service-client-without-id.json +21 -0
- package/fixtures/monorepo/serviceApp/platformatic.service.json +1 -1
- package/fixtures/serviceAppThrowsOnStart/platformatic.service.json +0 -1
- package/fixtures/start-command-in-runtime.js +14 -0
- package/lib/api.js +257 -0
- package/lib/app.js +45 -57
- package/lib/config.js +36 -8
- package/lib/loader.mjs +21 -13
- package/lib/start.js +5 -17
- package/lib/unified-api.js +34 -7
- package/lib/worker.js +7 -58
- package/package.json +6 -6
- package/test/api.test.js +294 -0
- package/test/app.test.js +40 -0
- package/test/config.test.js +9 -0
- package/test/unified-api.test.js +33 -0
- package/test/worker.test.js +0 -21
package/lib/loader.mjs
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { createRequire, isBuiltin } from 'node:module'
|
|
2
2
|
import { dirname, isAbsolute, resolve as pathResolve } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
4
|
const require = createRequire(import.meta.url)
|
|
5
|
-
const thisFile = fileURLToPath(import.meta.url)
|
|
6
5
|
const isWindows = process.platform === 'win32'
|
|
7
6
|
let timestamp = process.hrtime.bigint()
|
|
8
7
|
let port
|
|
9
8
|
|
|
9
|
+
/* c8 ignore next 3 - c8 upgrade marked many existing things as uncovered */
|
|
10
10
|
function bustEsmCache () {
|
|
11
11
|
timestamp = process.hrtime.bigint()
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/* c8 ignore next 11 - c8 upgrade marked many existing things as uncovered */
|
|
14
15
|
function clearCjsCache () {
|
|
15
16
|
// This evicts all of the modules from the require() cache.
|
|
16
17
|
// Note: This does not clean up children references to the deleted module.
|
|
@@ -25,6 +26,7 @@ function clearCjsCache () {
|
|
|
25
26
|
|
|
26
27
|
function isRelativePath (p) {
|
|
27
28
|
// This function is extracted from Node core, so it should work.
|
|
29
|
+
/* c8 ignore next - c8 upgrade marked many existing things as uncovered */
|
|
28
30
|
return p.charAt(0) === '.' &&
|
|
29
31
|
/* c8 ignore next 9 */
|
|
30
32
|
(
|
|
@@ -38,15 +40,15 @@ function isRelativePath (p) {
|
|
|
38
40
|
)
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
function
|
|
42
|
-
// Convert the specifier into an absolute path if possible. If the
|
|
43
|
-
// cannot be converted to a path (for example for a core module),
|
|
44
|
-
// null.
|
|
43
|
+
function specifierToFileUrl (specifier, referencingModuleId) {
|
|
44
|
+
// Convert the specifier into an absolute path URL if possible. If the
|
|
45
|
+
// specifier cannot be converted to a path (for example for a core module),
|
|
46
|
+
// then return null.
|
|
45
47
|
try {
|
|
46
48
|
const url = new URL(specifier)
|
|
47
49
|
|
|
48
50
|
if (url.protocol === 'file:') {
|
|
49
|
-
|
|
51
|
+
return url.href
|
|
50
52
|
} else {
|
|
51
53
|
return null
|
|
52
54
|
}
|
|
@@ -54,12 +56,14 @@ function specifierToPath (specifier, referencingModuleId) {
|
|
|
54
56
|
// Ignore error.
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
/* c8 ignore next 3 - c8 upgrade marked many existing things as uncovered */
|
|
57
60
|
if (isBuiltin(specifier)) {
|
|
58
61
|
return null
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
/* c8 ignore next 3 */
|
|
61
65
|
if (isAbsolute(specifier)) {
|
|
62
|
-
return specifier
|
|
66
|
+
return pathToFileURL(specifier).href
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
/* c8 ignore next 3 */
|
|
@@ -67,31 +71,35 @@ function specifierToPath (specifier, referencingModuleId) {
|
|
|
67
71
|
throw new Error(`cannot map '${specifier}' to an absolute path`)
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
/* c8 ignore next 5 - c8 upgrade marked many existing things as uncovered */
|
|
70
75
|
if (isRelativePath(specifier)) {
|
|
71
|
-
return
|
|
76
|
+
return pathToFileURL(
|
|
77
|
+
pathResolve(dirname(fileURLToPath(referencingModuleId)), specifier)
|
|
78
|
+
).href
|
|
72
79
|
} else {
|
|
73
80
|
// The specifier is something in node_modules/.
|
|
74
81
|
const req = createRequire(referencingModuleId)
|
|
75
82
|
|
|
76
|
-
return req.resolve(specifier)
|
|
83
|
+
return pathToFileURL(req.resolve(specifier)).href
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
export async function resolve (specifier, context, nextResolve) {
|
|
81
|
-
const
|
|
88
|
+
const url = specifierToFileUrl(specifier, context.parentURL)
|
|
82
89
|
|
|
83
90
|
// If the specifier could not be mapped to a file, or the path is this file,
|
|
84
91
|
// then don't do anything.
|
|
85
|
-
if (typeof
|
|
92
|
+
if (typeof url !== 'string' || url === import.meta.url) {
|
|
86
93
|
return nextResolve(specifier, context)
|
|
87
94
|
}
|
|
88
95
|
|
|
89
|
-
return nextResolve(`${
|
|
96
|
+
return nextResolve(`${url}?ts=${timestamp}`, context)
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
export function globalPreload (context) {
|
|
93
100
|
port = context.port
|
|
94
101
|
port.on('message', () => {
|
|
102
|
+
/* c8 ignore next 3 - c8 upgrade marked many existing things as uncovered */
|
|
95
103
|
bustEsmCache()
|
|
96
104
|
clearCjsCache()
|
|
97
105
|
port.postMessage('plt:cache-cleared')
|
package/lib/start.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const { once } = require('node:events')
|
|
3
3
|
const { join } = require('node:path')
|
|
4
|
+
const { pathToFileURL } = require('node:url')
|
|
4
5
|
const { Worker } = require('node:worker_threads')
|
|
5
6
|
const closeWithGrace = require('close-with-grace')
|
|
6
7
|
const { loadConfig } = require('@platformatic/service')
|
|
7
8
|
const { platformaticRuntime } = require('./config')
|
|
8
|
-
const
|
|
9
|
+
const { RuntimeApiClient } = require('./api.js')
|
|
10
|
+
const kLoaderFile = pathToFileURL(join(__dirname, 'loader.mjs')).href
|
|
9
11
|
const kWorkerFile = join(__dirname, 'worker.js')
|
|
10
12
|
const kWorkerExecArgv = [
|
|
11
13
|
'--no-warnings',
|
|
@@ -56,22 +58,8 @@ async function startWithConfig (configManager) {
|
|
|
56
58
|
|
|
57
59
|
await once(worker, 'message') // plt:init
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
worker.postMessage({ msg: 'plt:start' })
|
|
62
|
-
const [msg] = await once(worker, 'message') // plt:started
|
|
63
|
-
|
|
64
|
-
return msg.url
|
|
65
|
-
},
|
|
66
|
-
async close () {
|
|
67
|
-
worker.postMessage({ msg: 'plt:stop' })
|
|
68
|
-
await once(worker, 'exit')
|
|
69
|
-
},
|
|
70
|
-
async restart () {
|
|
71
|
-
worker.postMessage({ msg: 'plt:restart' })
|
|
72
|
-
await once(worker, 'message') // plt:restarted
|
|
73
|
-
}
|
|
74
|
-
}
|
|
61
|
+
const runtimeApiClient = new RuntimeApiClient(worker)
|
|
62
|
+
return runtimeApiClient
|
|
75
63
|
}
|
|
76
64
|
|
|
77
65
|
module.exports = { start, startWithConfig }
|
package/lib/unified-api.js
CHANGED
|
@@ -18,9 +18,12 @@ const {
|
|
|
18
18
|
platformaticComposer
|
|
19
19
|
} = require('@platformatic/composer')
|
|
20
20
|
const { buildServer: runtimeBuildServer } = require('./build-server')
|
|
21
|
-
const { platformaticRuntime } = require('./config')
|
|
21
|
+
const { platformaticRuntime, wrapConfigInRuntimeConfig } = require('./config')
|
|
22
22
|
const { schema: runtimeSchema } = require('./schema')
|
|
23
|
-
const {
|
|
23
|
+
const {
|
|
24
|
+
start: runtimeStart,
|
|
25
|
+
startWithConfig: runtimeStartWithConfig
|
|
26
|
+
} = require('./start')
|
|
24
27
|
|
|
25
28
|
const kSupportedAppTypes = new Set(['service', 'db', 'composer', 'runtime'])
|
|
26
29
|
|
|
@@ -149,15 +152,38 @@ async function startCommand (args) {
|
|
|
149
152
|
try {
|
|
150
153
|
await _start(args)
|
|
151
154
|
} catch (err) {
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
logErrorAndExit(err)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
154
158
|
|
|
155
|
-
|
|
156
|
-
|
|
159
|
+
async function startCommandInRuntime (args) {
|
|
160
|
+
try {
|
|
161
|
+
const configType = await getConfigType(args)
|
|
162
|
+
const config = await _loadConfig({}, args, configType)
|
|
163
|
+
let runtime
|
|
164
|
+
|
|
165
|
+
if (configType === 'runtime') {
|
|
166
|
+
runtime = await runtimeStartWithConfig(config.configManager)
|
|
167
|
+
} else {
|
|
168
|
+
const wrappedConfig = await wrapConfigInRuntimeConfig(config)
|
|
169
|
+
runtime = await runtimeStartWithConfig(wrappedConfig)
|
|
157
170
|
}
|
|
158
171
|
|
|
159
|
-
|
|
172
|
+
return await runtime.start()
|
|
173
|
+
} catch (err) {
|
|
174
|
+
logErrorAndExit(err)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function logErrorAndExit (err) {
|
|
179
|
+
delete err?.stack
|
|
180
|
+
console.error(err?.message)
|
|
181
|
+
|
|
182
|
+
if (err?.cause) {
|
|
183
|
+
console.error(`${err.cause}`)
|
|
160
184
|
}
|
|
185
|
+
|
|
186
|
+
process.exit(1)
|
|
161
187
|
}
|
|
162
188
|
|
|
163
189
|
module.exports = {
|
|
@@ -167,5 +193,6 @@ module.exports = {
|
|
|
167
193
|
loadConfig: _loadConfig,
|
|
168
194
|
start: _start,
|
|
169
195
|
startCommand,
|
|
196
|
+
startCommandInRuntime,
|
|
170
197
|
getApp
|
|
171
198
|
}
|
package/lib/worker.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
|
|
2
3
|
const { parentPort, workerData } = require('node:worker_threads')
|
|
3
4
|
const FastifyUndiciDispatcher = require('fastify-undici-dispatcher')
|
|
4
5
|
const { Agent, setGlobalDispatcher } = require('undici')
|
|
5
6
|
const { PlatformaticApp } = require('./app')
|
|
7
|
+
const { RuntimeApi } = require('./api')
|
|
8
|
+
|
|
6
9
|
const loaderPort = globalThis.LOADER_PORT // Added by loader.mjs.
|
|
7
10
|
const globalAgent = new Agent()
|
|
8
11
|
const globalDispatcher = new FastifyUndiciDispatcher({
|
|
@@ -14,7 +17,6 @@ const pino = require('pino')
|
|
|
14
17
|
const { isatty } = require('tty')
|
|
15
18
|
|
|
16
19
|
const applications = new Map()
|
|
17
|
-
let entrypoint
|
|
18
20
|
|
|
19
21
|
delete globalThis.LOADER_PORT
|
|
20
22
|
setGlobalDispatcher(globalDispatcher)
|
|
@@ -30,6 +32,7 @@ if (isatty(1)) {
|
|
|
30
32
|
|
|
31
33
|
const logger = pino(transport)
|
|
32
34
|
|
|
35
|
+
/* c8 ignore next 4 */
|
|
33
36
|
process.once('uncaughtException', (err) => {
|
|
34
37
|
logger.error({ err }, 'runtime error')
|
|
35
38
|
throw err
|
|
@@ -42,38 +45,6 @@ process.once('unhandledRejection', (err) => {
|
|
|
42
45
|
throw err
|
|
43
46
|
})
|
|
44
47
|
|
|
45
|
-
parentPort.on('message', async (msg) => {
|
|
46
|
-
for (const app of applications.values()) {
|
|
47
|
-
await app.handleProcessLevelEvent(msg)
|
|
48
|
-
|
|
49
|
-
if (msg?.msg === 'plt:start' || msg?.msg === 'plt:restart') {
|
|
50
|
-
const serviceUrl = new URL(app.appConfig.localUrl)
|
|
51
|
-
|
|
52
|
-
globalDispatcher.route(serviceUrl.host, app.server)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
switch (msg?.msg) {
|
|
57
|
-
case 'plt:start':
|
|
58
|
-
configureDispatcher()
|
|
59
|
-
parentPort.postMessage({ msg: 'plt:started', url: entrypoint.server.url })
|
|
60
|
-
break
|
|
61
|
-
case 'plt:restart':
|
|
62
|
-
configureDispatcher()
|
|
63
|
-
parentPort.postMessage({ msg: 'plt:restarted', url: entrypoint.server.url })
|
|
64
|
-
break
|
|
65
|
-
case 'plt:stop':
|
|
66
|
-
process.exit() // Exit the worker thread.
|
|
67
|
-
break
|
|
68
|
-
/* c8 ignore next 3 */
|
|
69
|
-
case undefined:
|
|
70
|
-
// Ignore
|
|
71
|
-
break
|
|
72
|
-
default:
|
|
73
|
-
throw new Error(`unknown message type: '${msg.msg}'`)
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
48
|
async function main () {
|
|
78
49
|
const { services } = workerData.config
|
|
79
50
|
|
|
@@ -82,34 +53,12 @@ async function main () {
|
|
|
82
53
|
const app = new PlatformaticApp(service, loaderPort, logger)
|
|
83
54
|
|
|
84
55
|
applications.set(service.id, app)
|
|
85
|
-
|
|
86
|
-
if (service.entrypoint) {
|
|
87
|
-
entrypoint = app
|
|
88
|
-
}
|
|
89
56
|
}
|
|
90
57
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
function configureDispatcher () {
|
|
95
|
-
const { services } = workerData.config
|
|
58
|
+
const runtime = new RuntimeApi(applications, globalDispatcher)
|
|
59
|
+
runtime.startListening(parentPort)
|
|
96
60
|
|
|
97
|
-
|
|
98
|
-
for (let i = 0; i < services.length; ++i) {
|
|
99
|
-
const service = services[i]
|
|
100
|
-
const serviceApp = applications.get(service.id)
|
|
101
|
-
const serviceUrl = new URL(service.localUrl)
|
|
102
|
-
|
|
103
|
-
globalDispatcher.route(serviceUrl.host, serviceApp.server)
|
|
104
|
-
|
|
105
|
-
for (let j = 0; j < service.dependencies.length; ++j) {
|
|
106
|
-
const depConfig = service.dependencies[j]
|
|
107
|
-
const depApp = applications.get(depConfig.id)
|
|
108
|
-
const depUrl = new URL(depConfig.url)
|
|
109
|
-
|
|
110
|
-
globalDispatcher.route(depUrl.host, depApp.server)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
61
|
+
parentPort.postMessage('plt:init')
|
|
113
62
|
}
|
|
114
63
|
|
|
115
64
|
main()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
"pino": "^8.14.1",
|
|
38
38
|
"pino-pretty": "^10.0.0",
|
|
39
39
|
"undici": "^5.22.1",
|
|
40
|
-
"@platformatic/composer": "0.
|
|
41
|
-
"@platformatic/config": "0.
|
|
42
|
-
"@platformatic/db": "0.
|
|
43
|
-
"@platformatic/service": "0.
|
|
44
|
-
"@platformatic/utils": "0.
|
|
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"
|
|
45
45
|
},
|
|
46
46
|
"standard": {
|
|
47
47
|
"ignore": [
|
package/test/api.test.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
const { join } = require('node:path')
|
|
5
|
+
const { test } = require('node:test')
|
|
6
|
+
|
|
7
|
+
const { loadConfig } = require('@platformatic/service')
|
|
8
|
+
const { buildServer, platformaticRuntime } = require('..')
|
|
9
|
+
const fixturesDir = join(__dirname, '..', 'fixtures')
|
|
10
|
+
|
|
11
|
+
// Each test runtime app adds own process listeners
|
|
12
|
+
process.setMaxListeners(100)
|
|
13
|
+
|
|
14
|
+
test('should get service details', async (t) => {
|
|
15
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
16
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
17
|
+
const app = await buildServer(config.configManager.current)
|
|
18
|
+
|
|
19
|
+
await app.start()
|
|
20
|
+
|
|
21
|
+
t.after(async () => {
|
|
22
|
+
await app.close()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const serviceDetails = await app.getServiceDetails('with-logger')
|
|
26
|
+
assert.deepStrictEqual(serviceDetails, {
|
|
27
|
+
id: 'with-logger',
|
|
28
|
+
status: 'started',
|
|
29
|
+
entrypoint: false,
|
|
30
|
+
localUrl: 'http://with-logger.plt.local',
|
|
31
|
+
dependencies: []
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('should get service config', async (t) => {
|
|
36
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
37
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
38
|
+
const app = await buildServer(config.configManager.current)
|
|
39
|
+
|
|
40
|
+
await app.start()
|
|
41
|
+
|
|
42
|
+
t.after(async () => {
|
|
43
|
+
await app.close()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const serviceConfig = await app.getServiceConfig('with-logger')
|
|
47
|
+
|
|
48
|
+
// TODO: should return correct logger config
|
|
49
|
+
assert.deepStrictEqual(serviceConfig, {
|
|
50
|
+
$schema: 'https://platformatic.dev/schemas/v0.27.0/service',
|
|
51
|
+
server: {
|
|
52
|
+
hostname: '127.0.0.1',
|
|
53
|
+
port: 0,
|
|
54
|
+
logger: {},
|
|
55
|
+
keepAliveTimeout: 5000
|
|
56
|
+
},
|
|
57
|
+
service: { openapi: true },
|
|
58
|
+
plugins: {
|
|
59
|
+
paths: [
|
|
60
|
+
join(fixturesDir, 'monorepo', 'serviceAppWithLogger', 'plugin.js')
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
watch: false
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('should fail to get service config if service is not started', async (t) => {
|
|
68
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
69
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
70
|
+
const app = await buildServer(config.configManager.current)
|
|
71
|
+
|
|
72
|
+
t.after(async () => {
|
|
73
|
+
await app.close()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await app.getServiceConfig('with-logger')
|
|
78
|
+
assert.fail('should have thrown')
|
|
79
|
+
} catch (err) {
|
|
80
|
+
assert.strictEqual(err.message, 'Service with id \'with-logger\' is not started')
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('should get services topology', async (t) => {
|
|
85
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
86
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
87
|
+
const app = await buildServer(config.configManager.current)
|
|
88
|
+
|
|
89
|
+
await app.start()
|
|
90
|
+
|
|
91
|
+
t.after(async () => {
|
|
92
|
+
await app.close()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const topology = await app.getServices()
|
|
96
|
+
|
|
97
|
+
assert.deepStrictEqual(topology, {
|
|
98
|
+
entrypoint: 'serviceApp',
|
|
99
|
+
services: [
|
|
100
|
+
{
|
|
101
|
+
id: 'serviceApp',
|
|
102
|
+
status: 'started',
|
|
103
|
+
entrypoint: true,
|
|
104
|
+
localUrl: 'http://serviceApp.plt.local',
|
|
105
|
+
dependencies: [
|
|
106
|
+
{
|
|
107
|
+
id: 'with-logger',
|
|
108
|
+
url: 'http://with-logger.plt.local',
|
|
109
|
+
local: true
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'with-logger',
|
|
115
|
+
status: 'started',
|
|
116
|
+
entrypoint: false,
|
|
117
|
+
localUrl: 'http://with-logger.plt.local',
|
|
118
|
+
dependencies: []
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'multi-plugin-service',
|
|
122
|
+
status: 'started',
|
|
123
|
+
entrypoint: false,
|
|
124
|
+
localUrl: 'http://multi-plugin-service.plt.local',
|
|
125
|
+
dependencies: []
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('should stop service by service id', async (t) => {
|
|
132
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
133
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
134
|
+
const app = await buildServer(config.configManager.current)
|
|
135
|
+
|
|
136
|
+
await app.start()
|
|
137
|
+
|
|
138
|
+
t.after(async () => {
|
|
139
|
+
await app.close()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
{
|
|
143
|
+
const serviceDetails = await app.getServiceDetails('with-logger')
|
|
144
|
+
assert.strictEqual(serviceDetails.status, 'started')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
await app.stopService('with-logger')
|
|
148
|
+
|
|
149
|
+
{
|
|
150
|
+
const serviceDetails = await app.getServiceDetails('with-logger')
|
|
151
|
+
assert.strictEqual(serviceDetails.status, 'stopped')
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('should fail to stop service with a wrong id', async (t) => {
|
|
156
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
157
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
158
|
+
const app = await buildServer(config.configManager.current)
|
|
159
|
+
|
|
160
|
+
t.after(async () => {
|
|
161
|
+
await app.close()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
await app.stopService('wrong-service-id')
|
|
166
|
+
assert.fail('should have thrown')
|
|
167
|
+
} catch (err) {
|
|
168
|
+
assert.strictEqual(err.message, 'Service with id \'wrong-service-id\' not found')
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test('should start stopped service by service id', async (t) => {
|
|
173
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
174
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
175
|
+
const app = await buildServer(config.configManager.current)
|
|
176
|
+
|
|
177
|
+
await app.start()
|
|
178
|
+
|
|
179
|
+
t.after(async () => {
|
|
180
|
+
await app.close()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
await app.stopService('with-logger')
|
|
184
|
+
|
|
185
|
+
{
|
|
186
|
+
const serviceDetails = await app.getServiceDetails('with-logger')
|
|
187
|
+
assert.strictEqual(serviceDetails.status, 'stopped')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
await app.startService('with-logger')
|
|
191
|
+
|
|
192
|
+
{
|
|
193
|
+
const serviceDetails = await app.getServiceDetails('with-logger')
|
|
194
|
+
assert.strictEqual(serviceDetails.status, 'started')
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('should fail to start service with a wrong id', async (t) => {
|
|
199
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
200
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
201
|
+
const app = await buildServer(config.configManager.current)
|
|
202
|
+
|
|
203
|
+
t.after(async () => {
|
|
204
|
+
await app.close()
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
await app.startService('wrong-service-id')
|
|
209
|
+
assert.fail('should have thrown')
|
|
210
|
+
} catch (err) {
|
|
211
|
+
assert.strictEqual(err.message, 'Service with id \'wrong-service-id\' not found')
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('should fail to start running service', async (t) => {
|
|
216
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
217
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
218
|
+
const app = await buildServer(config.configManager.current)
|
|
219
|
+
|
|
220
|
+
await app.start()
|
|
221
|
+
|
|
222
|
+
t.after(async () => {
|
|
223
|
+
await app.close()
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
await app.startService('with-logger')
|
|
228
|
+
assert.fail('should have thrown')
|
|
229
|
+
} catch (err) {
|
|
230
|
+
assert.strictEqual(err.message, 'application is already started')
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('should inject request to service', async (t) => {
|
|
235
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
236
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
237
|
+
const app = await buildServer(config.configManager.current)
|
|
238
|
+
|
|
239
|
+
await app.start()
|
|
240
|
+
|
|
241
|
+
t.after(async () => {
|
|
242
|
+
await app.close()
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
const res = await app.inject('with-logger', {
|
|
246
|
+
method: 'GET',
|
|
247
|
+
url: '/'
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
assert.strictEqual(res.statusCode, 200)
|
|
251
|
+
assert.strictEqual(res.statusMessage, 'OK')
|
|
252
|
+
|
|
253
|
+
assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
|
|
254
|
+
assert.strictEqual(res.headers['content-length'], '17')
|
|
255
|
+
assert.strictEqual(res.headers.connection, 'keep-alive')
|
|
256
|
+
|
|
257
|
+
assert.strictEqual(res.body, '{"hello":"world"}')
|
|
258
|
+
assert.strictEqual(res.payload, '{"hello":"world"}')
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test('should fail inject request is service is not started', async (t) => {
|
|
262
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
263
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
264
|
+
const app = await buildServer(config.configManager.current)
|
|
265
|
+
|
|
266
|
+
t.after(async () => {
|
|
267
|
+
await app.close()
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
await app.inject('with-logger', { method: 'GET', url: '/' })
|
|
272
|
+
} catch (err) {
|
|
273
|
+
assert.strictEqual(err.message, 'Service with id \'with-logger\' is not started')
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
test('should handle a lot of runtime api requests', async (t) => {
|
|
278
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo.json')
|
|
279
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
280
|
+
const app = await buildServer(config.configManager.current)
|
|
281
|
+
|
|
282
|
+
await app.start()
|
|
283
|
+
|
|
284
|
+
t.after(async () => {
|
|
285
|
+
await app.close()
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const promises = []
|
|
289
|
+
for (let i = 0; i < 100; i++) {
|
|
290
|
+
promises.push(app.getServiceDetails('with-logger'))
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await Promise.all(promises)
|
|
294
|
+
})
|
package/test/app.test.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const assert = require('node:assert')
|
|
4
4
|
const { join } = require('node:path')
|
|
5
5
|
const { test } = require('node:test')
|
|
6
|
+
const { utimes } = require('node:fs/promises')
|
|
6
7
|
const { PlatformaticApp } = require('../lib/app')
|
|
7
8
|
const fixturesDir = join(__dirname, '..', 'fixtures')
|
|
8
9
|
const pino = require('pino')
|
|
@@ -266,3 +267,42 @@ test('supports configuration overrides', async (t) => {
|
|
|
266
267
|
assert.strictEqual(app.config.configManager.current.server.keepAliveTimeout, 1)
|
|
267
268
|
})
|
|
268
269
|
})
|
|
270
|
+
|
|
271
|
+
test('restarts on config change without overriding the configManager', { only: true }, async (t) => {
|
|
272
|
+
const { logger, stream } = getLoggerAndStream()
|
|
273
|
+
const appPath = join(fixturesDir, 'monorepo', 'serviceApp')
|
|
274
|
+
const configFile = join(appPath, 'platformatic.service.json')
|
|
275
|
+
const config = {
|
|
276
|
+
id: 'serviceApp',
|
|
277
|
+
config: configFile,
|
|
278
|
+
path: appPath,
|
|
279
|
+
entrypoint: true,
|
|
280
|
+
hotReload: true,
|
|
281
|
+
dependencies: [],
|
|
282
|
+
dependents: [],
|
|
283
|
+
localServiceEnvVars: new Map([['PLT_WITH_LOGGER_URL', ' ']])
|
|
284
|
+
}
|
|
285
|
+
const app = new PlatformaticApp(config, null, logger)
|
|
286
|
+
|
|
287
|
+
t.after(async function () {
|
|
288
|
+
try {
|
|
289
|
+
await app.stop()
|
|
290
|
+
} catch (err) {
|
|
291
|
+
console.error(err)
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
await app.start()
|
|
295
|
+
const configManager = app.config.configManager
|
|
296
|
+
await utimes(configFile, new Date(), new Date())
|
|
297
|
+
let first = false
|
|
298
|
+
for await (const log of stream) {
|
|
299
|
+
// Wait for the server to restart, it will print a line containing "Server listening"
|
|
300
|
+
if (log.msg.includes('listening')) {
|
|
301
|
+
if (first) {
|
|
302
|
+
break
|
|
303
|
+
}
|
|
304
|
+
first = true
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
assert.strictEqual(configManager, app.server.platformatic.configManager)
|
|
308
|
+
})
|
package/test/config.test.js
CHANGED
|
@@ -56,3 +56,12 @@ test('performs a topological sort on services depending on allowCycles', async (
|
|
|
56
56
|
})
|
|
57
57
|
})
|
|
58
58
|
})
|
|
59
|
+
|
|
60
|
+
test('can resolve service id from client package.json if not provided', async () => {
|
|
61
|
+
const configFile = join(fixturesDir, 'configs', 'monorepo-client-without-id.json')
|
|
62
|
+
const config = await loadConfig({}, ['-c', configFile], platformaticRuntime)
|
|
63
|
+
const entry = config.configManager.current.serviceMap.get('serviceApp')
|
|
64
|
+
|
|
65
|
+
assert.strictEqual(entry.dependencies.length, 1)
|
|
66
|
+
assert.strictEqual(entry.dependencies[0].id, 'with-logger')
|
|
67
|
+
})
|