@platformatic/service 0.21.0 → 0.22.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/fixtures/hello/warn-log.service.json +19 -0
- package/fixtures/hello-client/platformatic.service.json +1 -1
- package/fixtures/hello-client-ts/platformatic.service.json +1 -1
- package/index.js +93 -132
- package/lib/compile.js +9 -5
- package/lib/load-config.js +7 -14
- package/lib/start.mjs +2 -1
- package/lib/utils.js +0 -9
- package/package.json +20 -20
- package/test/cli/compile.test.mjs +0 -3
- package/test/cli/start.test.mjs +13 -1
- package/test/cli/watch.test.mjs +1 -2
- package/test/clients.test.js +3 -3
- package/test/config.test.js +68 -0
- package/test/fixtures/custom-port-placeholder.json +10 -0
- package/test/https.test.js +2 -3
- package/test/load-and-reload-files.test.js +6 -2
- package/test/utils.test.js +4 -13
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"server": {
|
|
3
|
+
"hostname": "127.0.0.1",
|
|
4
|
+
"port": 0,
|
|
5
|
+
"logger": {
|
|
6
|
+
"level": "warn",
|
|
7
|
+
"name": "hello server"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"plugins": {
|
|
11
|
+
"paths": ["./plugin.js"],
|
|
12
|
+
"hotReload": false
|
|
13
|
+
},
|
|
14
|
+
"service": {
|
|
15
|
+
"openapi": true
|
|
16
|
+
},
|
|
17
|
+
"metrics": false,
|
|
18
|
+
"watch": false
|
|
19
|
+
}
|
package/index.js
CHANGED
|
@@ -7,7 +7,7 @@ const { schema } = require('./lib/schema')
|
|
|
7
7
|
const ConfigManager = require('@platformatic/config')
|
|
8
8
|
const { loadConfig, generateDefaultConfig } = require('./lib/load-config')
|
|
9
9
|
const { addLoggerToTheConfig, getJSPluginPath, isFileAccessible } = require('./lib/utils')
|
|
10
|
-
const { isKeyEnabled
|
|
10
|
+
const { isKeyEnabled } = require('@platformatic/utils')
|
|
11
11
|
const compiler = require('./lib/compile')
|
|
12
12
|
const { join, dirname, resolve } = require('path')
|
|
13
13
|
const { readFile } = require('fs/promises')
|
|
@@ -15,14 +15,6 @@ const wrapperPath = join(__dirname, 'lib', 'sandbox-wrapper.js')
|
|
|
15
15
|
const setupOpenAPI = require('./lib/openapi.js')
|
|
16
16
|
const setupGraphQL = require('./lib/graphql.js')
|
|
17
17
|
|
|
18
|
-
function createServerConfig (config) {
|
|
19
|
-
// convert the config file to a new structure
|
|
20
|
-
// to make @fastify/restartable happy
|
|
21
|
-
const serverConfig = Object.assign({ ...config.server }, config)
|
|
22
|
-
delete serverConfig.server
|
|
23
|
-
return serverConfig
|
|
24
|
-
}
|
|
25
|
-
|
|
26
18
|
function originToRegexp (origin) {
|
|
27
19
|
if (typeof origin === 'object') {
|
|
28
20
|
if (origin.regexp) {
|
|
@@ -34,67 +26,44 @@ function originToRegexp (origin) {
|
|
|
34
26
|
}
|
|
35
27
|
|
|
36
28
|
async function platformaticService (app, opts, toLoad = []) {
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
const configManager = app.platformatic.configManager
|
|
30
|
+
const config = configManager.current
|
|
31
|
+
|
|
32
|
+
if (isKeyEnabled('metrics', config)) {
|
|
33
|
+
app.register(require('./lib/metrics-plugin'), config.metrics)
|
|
39
34
|
}
|
|
40
35
|
|
|
41
36
|
if (Array.isArray(toLoad)) {
|
|
42
37
|
for (const plugin of toLoad) {
|
|
43
|
-
await app.register(plugin
|
|
38
|
+
await app.register(plugin)
|
|
44
39
|
}
|
|
45
40
|
}
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
app.decorate('platformatic', {})
|
|
49
|
-
}
|
|
42
|
+
const serviceConfig = app.platformatic.config?.service
|
|
50
43
|
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
const configManager = opts.configManager
|
|
54
|
-
/* c8 ignore next 3 */
|
|
55
|
-
if (fileWatcher !== undefined) {
|
|
56
|
-
app.platformatic.fileWatcher = fileWatcher
|
|
57
|
-
}
|
|
58
|
-
if (configManager !== undefined) {
|
|
59
|
-
app.platformatic.configManager = configManager
|
|
60
|
-
app.platformatic.config = configManager.current
|
|
61
|
-
/* c8 ignore next 3 */
|
|
62
|
-
} else {
|
|
63
|
-
throw new Error('configManager is required')
|
|
64
|
-
}
|
|
44
|
+
if (serviceConfig?.openapi) {
|
|
45
|
+
await setupOpenAPI(app, serviceConfig.openapi)
|
|
65
46
|
}
|
|
66
47
|
|
|
67
|
-
{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// for some unknown reason, c8 is not detecting any of this
|
|
71
|
-
// despite being covered by test/routes.test.js
|
|
72
|
-
/* c8 ignore next 3 */
|
|
73
|
-
if (serviceConfig?.openapi) {
|
|
74
|
-
await setupOpenAPI(app, app.platformatic.config?.service?.openapi)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/* c8 ignore next 3 */
|
|
78
|
-
if (serviceConfig?.graphql) {
|
|
79
|
-
await setupGraphQL(app, app.platformatic.config?.service?.graphql)
|
|
80
|
-
}
|
|
48
|
+
if (serviceConfig?.graphql) {
|
|
49
|
+
await setupGraphQL(app, serviceConfig.graphql)
|
|
81
50
|
}
|
|
82
51
|
|
|
83
|
-
for (const plugin of (
|
|
52
|
+
for (const plugin of (config.clients || [])) {
|
|
84
53
|
app.register(require(plugin.path), {
|
|
85
54
|
url: plugin.url
|
|
86
55
|
})
|
|
87
56
|
}
|
|
88
57
|
|
|
89
|
-
if (
|
|
58
|
+
if (config.plugins) {
|
|
90
59
|
// if we don't have a fullPath, let's assume we are in a test and we can use the current working directory
|
|
91
|
-
const configPath =
|
|
60
|
+
const configPath = configManager.fullPath || join(process.cwd(), 'platformatic.db.json')
|
|
92
61
|
const tsConfigPath = join(dirname(configPath), 'tsconfig.json')
|
|
93
62
|
/* c8 ignore next 21 */
|
|
94
63
|
if (await isFileAccessible(tsConfigPath)) {
|
|
95
64
|
const tsConfig = JSON.parse(await readFile(tsConfigPath, 'utf8'))
|
|
96
65
|
const outDir = resolve(dirname(tsConfigPath), tsConfig.compilerOptions.outDir)
|
|
97
|
-
|
|
66
|
+
config.plugins.paths = config.plugins.paths.map((plugin) => {
|
|
98
67
|
if (typeof plugin === 'string') {
|
|
99
68
|
return getJSPluginPath(configPath, plugin, outDir)
|
|
100
69
|
} else {
|
|
@@ -105,7 +74,7 @@ async function platformaticService (app, opts, toLoad = []) {
|
|
|
105
74
|
}
|
|
106
75
|
})
|
|
107
76
|
} else {
|
|
108
|
-
for (const plugin of
|
|
77
|
+
for (const plugin of config.plugins.paths) {
|
|
109
78
|
const path = typeof plugin === 'string' ? plugin : plugin.path
|
|
110
79
|
if (path.endsWith('.ts')) {
|
|
111
80
|
throw new Error(`Cannot load plugin ${path}, tsconfig.json not found`)
|
|
@@ -117,14 +86,15 @@ async function platformaticService (app, opts, toLoad = []) {
|
|
|
117
86
|
// that's why we ignore the coverage of the `undefined` case, which cannot be covered in cli tests)
|
|
118
87
|
// all individual plugin hot reload settings will be overloaded by global hot reload
|
|
119
88
|
/* c8 ignore next 1 */
|
|
120
|
-
const hotReload =
|
|
121
|
-
const isWatchEnabled =
|
|
122
|
-
|
|
89
|
+
const hotReload = config.plugins.hotReload !== false
|
|
90
|
+
const isWatchEnabled = config.watch !== false
|
|
91
|
+
|
|
92
|
+
app.log.debug({ plugins: config.plugins.paths, hotReload, isWatchEnabled }, 'loading plugins')
|
|
123
93
|
|
|
124
94
|
if (isWatchEnabled && hotReload) {
|
|
125
95
|
await app.register(sandbox, {
|
|
126
96
|
path: wrapperPath,
|
|
127
|
-
options: { paths:
|
|
97
|
+
options: { paths: config.plugins.paths },
|
|
128
98
|
customizeGlobalThis (_globalThis) {
|
|
129
99
|
// Taken from https://github.com/nodejs/undici/blob/fa9fd9066569b6357acacffb806aa804b688c9d8/lib/global.js#L5
|
|
130
100
|
const globalDispatcher = Symbol.for('undici.globalDispatcher.1')
|
|
@@ -136,35 +106,36 @@ async function platformaticService (app, opts, toLoad = []) {
|
|
|
136
106
|
}
|
|
137
107
|
})
|
|
138
108
|
} else {
|
|
139
|
-
await app.register(require(wrapperPath), { paths:
|
|
109
|
+
await app.register(require(wrapperPath), { paths: config.plugins.paths })
|
|
140
110
|
}
|
|
141
111
|
}
|
|
142
112
|
|
|
143
113
|
// Enable CORS
|
|
144
|
-
if (
|
|
145
|
-
let origin =
|
|
114
|
+
if (config.server.cors) {
|
|
115
|
+
let origin = config.server.cors.origin
|
|
146
116
|
if (Array.isArray(origin)) {
|
|
147
117
|
origin = origin.map(originToRegexp)
|
|
148
118
|
} else {
|
|
149
119
|
origin = originToRegexp(origin)
|
|
150
120
|
}
|
|
151
121
|
|
|
152
|
-
|
|
122
|
+
config.server.cors.origin = origin
|
|
153
123
|
|
|
154
|
-
app.register(require('@fastify/cors'),
|
|
124
|
+
app.register(require('@fastify/cors'), config.server.cors)
|
|
155
125
|
}
|
|
156
126
|
|
|
157
|
-
if (isKeyEnabled('healthCheck',
|
|
127
|
+
if (isKeyEnabled('healthCheck', config.server)) {
|
|
128
|
+
const healthCheck = config.server.healthCheck
|
|
158
129
|
app.register(underPressure, {
|
|
159
130
|
exposeStatusRoute: '/status',
|
|
160
|
-
healthCheckInterval:
|
|
161
|
-
...
|
|
162
|
-
healthCheck:
|
|
131
|
+
healthCheckInterval: healthCheck.interval !== undefined ? healthCheck.interval : 5000,
|
|
132
|
+
...healthCheck,
|
|
133
|
+
healthCheck: healthCheck.fn
|
|
163
134
|
})
|
|
164
135
|
}
|
|
165
136
|
|
|
166
137
|
if (!app.hasRoute({ url: '/', method: 'GET' }) && !Array.isArray(toLoad)) {
|
|
167
|
-
await app.register(require('./lib/root-endpoint')
|
|
138
|
+
await app.register(require('./lib/root-endpoint'))
|
|
168
139
|
}
|
|
169
140
|
}
|
|
170
141
|
|
|
@@ -172,37 +143,6 @@ platformaticService[Symbol.for('skip-override')] = true
|
|
|
172
143
|
platformaticService.schema = schema
|
|
173
144
|
platformaticService.envWhitelist = ['PORT', 'HOSTNAME']
|
|
174
145
|
|
|
175
|
-
function adjustConfigBeforeMerge (cm) {
|
|
176
|
-
// This function and adjustConfigAfterMerge() are needed because there are
|
|
177
|
-
// edge cases that deepmerge() does not handle properly. This code does not
|
|
178
|
-
// live in the generic config manager because that object is not aware of
|
|
179
|
-
// these schema dependent details.
|
|
180
|
-
const stash = new Map()
|
|
181
|
-
|
|
182
|
-
// If a pino instance is passed as the logger, it will contain a child()
|
|
183
|
-
// function that is not enumerable. Non-enumerables are not copied by
|
|
184
|
-
// deepmerge(), so stash the logger here.
|
|
185
|
-
/* c8 ignore next 5 */
|
|
186
|
-
if (typeof cm.server?.logger?.child === 'function' &&
|
|
187
|
-
!Object.prototype.propertyIsEnumerable.call(cm.server.logger, 'child')) {
|
|
188
|
-
stash.set('server.logger', cm.server.logger)
|
|
189
|
-
cm.server.logger = null
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return stash
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function adjustConfigAfterMerge (options, stash) {
|
|
196
|
-
// Restore any config that needed to be stashed prior to merging.
|
|
197
|
-
const pinoLogger = stash.get('server.logger')
|
|
198
|
-
|
|
199
|
-
/* c8 ignore next 4 */
|
|
200
|
-
if (pinoLogger) {
|
|
201
|
-
options.server.logger = pinoLogger
|
|
202
|
-
options.configManager.current.server.logger = pinoLogger
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
146
|
async function adjustHttpsKeyAndCert (arg) {
|
|
207
147
|
if (typeof arg === 'string') {
|
|
208
148
|
return arg
|
|
@@ -238,30 +178,50 @@ function defaultConfig (app, source) {
|
|
|
238
178
|
|
|
239
179
|
async function buildServer (options, app) {
|
|
240
180
|
app = app || platformaticService
|
|
181
|
+
let cm
|
|
241
182
|
|
|
242
183
|
if (!options.configManager) {
|
|
243
184
|
// instantiate a new config manager from current options
|
|
244
|
-
|
|
185
|
+
cm = new ConfigManager(defaultConfig(app, options))
|
|
245
186
|
await cm.parseAndValidate()
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
options.configManager = cm
|
|
249
|
-
adjustConfigAfterMerge(options, stash)
|
|
187
|
+
} else {
|
|
188
|
+
cm = options.configManager
|
|
250
189
|
}
|
|
251
190
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
options
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
191
|
+
// options is a path
|
|
192
|
+
if (typeof options === 'string') {
|
|
193
|
+
options = cm.current
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function jumpApp (root) {
|
|
197
|
+
root.decorate('platformatic', {})
|
|
198
|
+
|
|
199
|
+
const fileWatcher = options.fileWatcher
|
|
200
|
+
/* c8 ignore next 3 */
|
|
201
|
+
if (fileWatcher !== undefined) {
|
|
202
|
+
root.platformatic.fileWatcher = fileWatcher
|
|
203
|
+
}
|
|
204
|
+
root.platformatic.configManager = cm
|
|
205
|
+
root.platformatic.config = cm.current
|
|
206
|
+
root.register(app)
|
|
207
|
+
}
|
|
208
|
+
jumpApp[Symbol.for('skip-override')] = true
|
|
209
|
+
|
|
210
|
+
const serverConfig = {
|
|
211
|
+
...(options.server),
|
|
212
|
+
configManager: cm,
|
|
213
|
+
app: jumpApp
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (serverConfig.https) {
|
|
217
|
+
serverConfig.key = await adjustHttpsKeyAndCert(options.server.https.key)
|
|
218
|
+
serverConfig.cert = await adjustHttpsKeyAndCert(options.server.https.cert)
|
|
219
|
+
delete serverConfig.https
|
|
220
|
+
serverConfig.protocol = 'https'
|
|
221
|
+
} else if (options.server) {
|
|
222
|
+
serverConfig.protocol = 'http'
|
|
260
223
|
}
|
|
261
|
-
const serverConfig = createServerConfig(options)
|
|
262
224
|
|
|
263
|
-
serverConfig.originalConfig = options
|
|
264
|
-
serverConfig.app = app
|
|
265
225
|
const handler = await start(serverConfig)
|
|
266
226
|
|
|
267
227
|
Object.defineProperty(handler, 'url', {
|
|
@@ -274,42 +234,44 @@ async function buildServer (options, app) {
|
|
|
274
234
|
}
|
|
275
235
|
})
|
|
276
236
|
|
|
277
|
-
|
|
237
|
+
restarter(handler, cm, jumpApp)
|
|
278
238
|
|
|
239
|
+
return handler
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function restarter (handler, cm, jumpApp) {
|
|
279
243
|
let debounce = null
|
|
280
|
-
handler.restart
|
|
281
|
-
|
|
244
|
+
const _restart = handler.restart
|
|
245
|
+
handler.restart = restart
|
|
246
|
+
handler.app.restart = restart
|
|
247
|
+
|
|
248
|
+
// This is covered by tests but c8 doesn't see it
|
|
249
|
+
/* c8 ignore next 30 */
|
|
250
|
+
async function restart (opts) {
|
|
282
251
|
if (debounce) {
|
|
283
252
|
return debounce
|
|
284
253
|
}
|
|
285
254
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
opts = configManager.current
|
|
255
|
+
if (opts && !await cm.update(opts)) {
|
|
256
|
+
const err = new Error('Invalid config')
|
|
257
|
+
err.validationErrors = cm.validationErrors
|
|
258
|
+
throw err
|
|
291
259
|
}
|
|
292
260
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
opts = createServerConfig(opts)
|
|
300
|
-
opts.app = app
|
|
261
|
+
const restartOpts = {
|
|
262
|
+
...(cm.current.server),
|
|
263
|
+
fileWatcher: handler.app.platformatic.fileWatcher,
|
|
264
|
+
configManager: cm,
|
|
265
|
+
app: jumpApp
|
|
266
|
+
}
|
|
301
267
|
|
|
302
|
-
debounce = _restart(
|
|
268
|
+
debounce = _restart(restartOpts).then(() => {
|
|
303
269
|
handler.app.log.info('restarted')
|
|
304
270
|
}).finally(() => {
|
|
305
271
|
debounce = null
|
|
306
272
|
})
|
|
307
273
|
return debounce
|
|
308
274
|
}
|
|
309
|
-
|
|
310
|
-
handler.app.restart = handler.restart
|
|
311
|
-
|
|
312
|
-
return handler
|
|
313
275
|
}
|
|
314
276
|
|
|
315
277
|
// This is for @platformatic/db to use
|
|
@@ -321,10 +283,9 @@ async function buildStart (loadConfig, buildServer) {
|
|
|
321
283
|
|
|
322
284
|
module.exports.buildServer = buildServer
|
|
323
285
|
module.exports.schema = require('./lib/schema')
|
|
324
|
-
module.exports.createServerConfig = createServerConfig
|
|
325
286
|
module.exports.platformaticService = platformaticService
|
|
326
|
-
module.exports.addLoggerToTheConfig = addLoggerToTheConfig
|
|
327
287
|
module.exports.loadConfig = loadConfig
|
|
288
|
+
module.exports.addLoggerToTheConfig = addLoggerToTheConfig
|
|
328
289
|
module.exports.generateConfigManagerConfig = generateDefaultConfig
|
|
329
290
|
module.exports.tsCompiler = compiler
|
|
330
291
|
module.exports.buildStart = buildStart
|
package/lib/compile.js
CHANGED
|
@@ -28,7 +28,7 @@ async function getTSCExecutablePath (cwd) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async function setup (cwd) {
|
|
31
|
+
async function setup (cwd, config) {
|
|
32
32
|
const logger = pino(
|
|
33
33
|
pretty({
|
|
34
34
|
translateTime: 'SYS:HH:MM:ss',
|
|
@@ -36,6 +36,10 @@ async function setup (cwd) {
|
|
|
36
36
|
})
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
+
if (config?.server.logger) {
|
|
40
|
+
logger.level = config.server.logger.level
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
const { execa } = await import('execa')
|
|
40
44
|
|
|
41
45
|
const tscExecutablePath = await getTSCExecutablePath(cwd)
|
|
@@ -56,8 +60,8 @@ async function setup (cwd) {
|
|
|
56
60
|
return { execa, logger, tscExecutablePath }
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
async function compile (cwd) {
|
|
60
|
-
const { execa, logger, tscExecutablePath } = await setup(cwd)
|
|
63
|
+
async function compile (cwd, config) {
|
|
64
|
+
const { execa, logger, tscExecutablePath } = await setup(cwd, config)
|
|
61
65
|
/* c8 ignore next 3 */
|
|
62
66
|
if (!tscExecutablePath) {
|
|
63
67
|
return false
|
|
@@ -76,8 +80,8 @@ async function compile (cwd) {
|
|
|
76
80
|
// This path is tested but C8 does not see it that way given it needs to work
|
|
77
81
|
// through execa.
|
|
78
82
|
/* c8 ignore next 20 */
|
|
79
|
-
async function compileWatch (cwd) {
|
|
80
|
-
const { execa, logger, tscExecutablePath } = await setup(cwd)
|
|
83
|
+
async function compileWatch (cwd, config) {
|
|
84
|
+
const { execa, logger, tscExecutablePath } = await setup(cwd, config)
|
|
81
85
|
if (!tscExecutablePath) {
|
|
82
86
|
return false
|
|
83
87
|
}
|
package/lib/load-config.js
CHANGED
|
@@ -4,18 +4,8 @@ const parseArgs = require('minimist')
|
|
|
4
4
|
const { access } = require('fs/promises')
|
|
5
5
|
const ConfigManager = require('@platformatic/config')
|
|
6
6
|
const deepmerge = require('@fastify/deepmerge')
|
|
7
|
-
const { findConfigFile } = require('./utils.js')
|
|
8
7
|
const { schema } = require('./schema.js')
|
|
9
8
|
|
|
10
|
-
const ourConfigFiles = [
|
|
11
|
-
'platformatic.service.json',
|
|
12
|
-
'platformatic.service.json5',
|
|
13
|
-
'platformatic.service.yaml',
|
|
14
|
-
'platformatic.service.yml',
|
|
15
|
-
'platformatic.service.toml',
|
|
16
|
-
'platformatic.service.tml'
|
|
17
|
-
]
|
|
18
|
-
|
|
19
9
|
function generateDefaultConfig () {
|
|
20
10
|
return {
|
|
21
11
|
schema,
|
|
@@ -30,7 +20,7 @@ function generateDefaultConfig () {
|
|
|
30
20
|
|
|
31
21
|
// Unfortunately c8 does not see those on Windows
|
|
32
22
|
/* c8 ignore next 70 */
|
|
33
|
-
async function loadConfig (minimistConfig, _args, defaultConfig,
|
|
23
|
+
async function loadConfig (minimistConfig, _args, defaultConfig, configType = 'service') {
|
|
34
24
|
defaultConfig ??= generateDefaultConfig()
|
|
35
25
|
const args = parseArgs(_args, deepmerge({ all: true })({
|
|
36
26
|
string: ['allow-env'],
|
|
@@ -49,22 +39,25 @@ async function loadConfig (minimistConfig, _args, defaultConfig, configFileNames
|
|
|
49
39
|
|
|
50
40
|
try {
|
|
51
41
|
if (!args.config) {
|
|
52
|
-
args.config = await findConfigFile(process.cwd(),
|
|
42
|
+
args.config = await ConfigManager.findConfigFile(process.cwd(), configType)
|
|
53
43
|
}
|
|
54
44
|
await access(args.config)
|
|
55
45
|
} catch (err) {
|
|
46
|
+
const configFiles = ConfigManager.listConfigFiles(configType)
|
|
56
47
|
console.error(`
|
|
57
48
|
Missing config file!
|
|
58
|
-
Be sure to have a config file with one of the following names: ${
|
|
49
|
+
Be sure to have a config file with one of the following names: ${configFiles.join('\n')}
|
|
59
50
|
In alternative run "npm create platformatic@latest" to generate a basic plt service config.
|
|
60
51
|
Error: ${err}
|
|
61
52
|
`)
|
|
62
53
|
process.exit(1)
|
|
63
54
|
}
|
|
64
55
|
|
|
56
|
+
const envWhitelist = args.allowEnv ? args.allowEnv : defaultConfig.envWhitelist
|
|
65
57
|
const configManager = new ConfigManager({
|
|
66
58
|
source: args.config,
|
|
67
|
-
...defaultConfig
|
|
59
|
+
...defaultConfig,
|
|
60
|
+
envWhitelist
|
|
68
61
|
})
|
|
69
62
|
|
|
70
63
|
const parsingResult = await configManager.parse()
|
package/lib/start.mjs
CHANGED
|
@@ -33,7 +33,7 @@ export function buildStart (_loadConfig, _buildServer) {
|
|
|
33
33
|
config.plugins?.watch !== false
|
|
34
34
|
) {
|
|
35
35
|
try {
|
|
36
|
-
await compileWatch(dirname(configManager.fullPath))
|
|
36
|
+
await compileWatch(dirname(configManager.fullPath), config)
|
|
37
37
|
} catch (error) {
|
|
38
38
|
// TODO route this to a logger
|
|
39
39
|
console.error(error)
|
|
@@ -190,6 +190,7 @@ async function onFilesUpdated (server, hotReload) {
|
|
|
190
190
|
if (hotReload === false && configManager.current.plugins) {
|
|
191
191
|
configManager.current.plugins.hotReload = false
|
|
192
192
|
}
|
|
193
|
+
addLoggerToTheConfig(configManager.current)
|
|
193
194
|
await server.restart(configManager.current)
|
|
194
195
|
} catch (err) {
|
|
195
196
|
// TODO: test this
|
package/lib/utils.js
CHANGED
|
@@ -3,12 +3,6 @@
|
|
|
3
3
|
const { access } = require('fs/promises')
|
|
4
4
|
const { resolve, join, relative, dirname, basename } = require('path')
|
|
5
5
|
|
|
6
|
-
async function findConfigFile (directory, configFileNames) {
|
|
7
|
-
const configFilesAccessibility = await Promise.all(configFileNames.map((fileName) => isFileAccessible(fileName, directory)))
|
|
8
|
-
const accessibleConfigFilename = configFileNames.find((value, index) => configFilesAccessibility[index])
|
|
9
|
-
return accessibleConfigFilename
|
|
10
|
-
}
|
|
11
|
-
|
|
12
6
|
async function isFileAccessible (filename, directory) {
|
|
13
7
|
try {
|
|
14
8
|
const filePath = directory ? resolve(directory, filename) : filename
|
|
@@ -21,8 +15,6 @@ async function isFileAccessible (filename, directory) {
|
|
|
21
15
|
|
|
22
16
|
/* c8 ignore start */
|
|
23
17
|
function addLoggerToTheConfig (config) {
|
|
24
|
-
if (config === undefined || config.server === undefined) return
|
|
25
|
-
|
|
26
18
|
// Set the logger if not present
|
|
27
19
|
let logger = config.server.logger
|
|
28
20
|
if (!logger) {
|
|
@@ -66,7 +58,6 @@ function getJSPluginPath (configPath, tsPluginPath, compileDir) {
|
|
|
66
58
|
}
|
|
67
59
|
|
|
68
60
|
module.exports = {
|
|
69
|
-
findConfigFile,
|
|
70
61
|
isFileAccessible,
|
|
71
62
|
getJSPluginPath,
|
|
72
63
|
addLoggerToTheConfig
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/service",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,50 +22,50 @@
|
|
|
22
22
|
"c8": "^7.13.0",
|
|
23
23
|
"self-cert": "^2.0.0",
|
|
24
24
|
"snazzy": "^9.0.0",
|
|
25
|
-
"split2": "^4.
|
|
25
|
+
"split2": "^4.2.0",
|
|
26
26
|
"standard": "^17.0.0",
|
|
27
27
|
"strip-ansi": "^7.0.1",
|
|
28
28
|
"tap": "^16.3.4",
|
|
29
|
-
"tsd": "^0.28.
|
|
30
|
-
"typescript": "^5.0.
|
|
31
|
-
"undici": "^5.
|
|
32
|
-
"vscode-json-languageservice": "^5.3.
|
|
29
|
+
"tsd": "^0.28.1",
|
|
30
|
+
"typescript": "^5.0.4",
|
|
31
|
+
"undici": "^5.22.0",
|
|
32
|
+
"vscode-json-languageservice": "^5.3.4",
|
|
33
33
|
"why-is-node-running": "^2.2.2",
|
|
34
|
-
"yaml": "^2.2.
|
|
34
|
+
"yaml": "^2.2.2"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@fastify/accepts": "^4.1.0",
|
|
38
38
|
"@fastify/autoload": "^5.7.1",
|
|
39
39
|
"@fastify/basic-auth": "^5.0.0",
|
|
40
|
-
"@fastify/cors": "^8.2.
|
|
40
|
+
"@fastify/cors": "^8.2.1",
|
|
41
41
|
"@fastify/deepmerge": "^1.3.0",
|
|
42
42
|
"@fastify/restartable": "^1.4.0",
|
|
43
|
-
"@fastify/static": "^6.
|
|
43
|
+
"@fastify/static": "^6.10.1",
|
|
44
44
|
"@fastify/swagger": "^8.3.1",
|
|
45
|
-
"@fastify/swagger-ui": "^1.
|
|
45
|
+
"@fastify/swagger-ui": "^1.8.0",
|
|
46
46
|
"@fastify/under-pressure": "^8.2.0",
|
|
47
|
-
"@mercuriusjs/federation": "^
|
|
48
|
-
"close-with-grace": "^1.
|
|
47
|
+
"@mercuriusjs/federation": "^2.0.0",
|
|
48
|
+
"close-with-grace": "^1.2.0",
|
|
49
49
|
"commist": "^3.2.0",
|
|
50
50
|
"desm": "^1.3.0",
|
|
51
51
|
"env-schema": "^5.2.0",
|
|
52
52
|
"es-main": "^1.2.0",
|
|
53
|
-
"execa": "^7.
|
|
54
|
-
"fastify": "^4.
|
|
55
|
-
"fastify-metrics": "^10.0
|
|
53
|
+
"execa": "^7.1.1",
|
|
54
|
+
"fastify": "^4.17.0",
|
|
55
|
+
"fastify-metrics": "^10.3.0",
|
|
56
56
|
"fastify-plugin": "^4.5.0",
|
|
57
57
|
"fastify-sandbox": "^0.11.0",
|
|
58
58
|
"graphql": "^16.6.0",
|
|
59
59
|
"help-me": "^4.2.0",
|
|
60
|
-
"mercurius": "^
|
|
60
|
+
"mercurius": "^13.0.0",
|
|
61
61
|
"minimist": "^1.2.8",
|
|
62
62
|
"pino": "^8.11.0",
|
|
63
63
|
"pino-pretty": "^10.0.0",
|
|
64
64
|
"rfdc": "^1.3.0",
|
|
65
|
-
"ua-parser-js": "^1.0.
|
|
66
|
-
"@platformatic/client": "0.
|
|
67
|
-
"@platformatic/config": "0.
|
|
68
|
-
"@platformatic/utils": "0.
|
|
65
|
+
"ua-parser-js": "^1.0.35",
|
|
66
|
+
"@platformatic/client": "0.22.0",
|
|
67
|
+
"@platformatic/config": "0.22.0",
|
|
68
|
+
"@platformatic/utils": "0.22.0"
|
|
69
69
|
},
|
|
70
70
|
"standard": {
|
|
71
71
|
"ignore": [
|
|
@@ -88,10 +88,8 @@ t.test('should compile typescript plugin with start command', async (t) => {
|
|
|
88
88
|
|
|
89
89
|
const splitter = split()
|
|
90
90
|
child.stdout.pipe(splitter)
|
|
91
|
-
child.stderr.pipe(process.stderr)
|
|
92
91
|
|
|
93
92
|
for await (const data of splitter) {
|
|
94
|
-
console.log(data)
|
|
95
93
|
const sanitized = stripAnsi(data)
|
|
96
94
|
if (sanitized.includes('Typescript plugin loaded')) {
|
|
97
95
|
t.pass()
|
|
@@ -228,7 +226,6 @@ t.test('should compile typescript plugin with start command with different cwd',
|
|
|
228
226
|
child.stderr.pipe(process.stderr)
|
|
229
227
|
|
|
230
228
|
for await (const data of splitter) {
|
|
231
|
-
console.log(data)
|
|
232
229
|
const sanitized = stripAnsi(data)
|
|
233
230
|
if (sanitized.includes('Typescript plugin loaded')) {
|
|
234
231
|
t.pass()
|
package/test/cli/start.test.mjs
CHANGED
|
@@ -30,6 +30,19 @@ test('start command', async ({ equal, same, match, teardown }) => {
|
|
|
30
30
|
child.kill('SIGINT')
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
+
test('allow custom env properties', async ({ equal, same, match, teardown }) => {
|
|
34
|
+
process.env.A_CUSTOM_PORT = '11111'
|
|
35
|
+
const { child, url } = await start('start', '-c', join(import.meta.url, '..', 'fixtures', 'custom-port-placeholder.json'), '--allow-env=A_CUSTOM_PORT')
|
|
36
|
+
equal(url, 'http://127.0.0.1:11111', 'A_CUSTOM_PORT env variable has been used')
|
|
37
|
+
const res = await request(`${url}`)
|
|
38
|
+
equal(res.statusCode, 200)
|
|
39
|
+
const body = await res.body.json()
|
|
40
|
+
match(body, {}, 'response')
|
|
41
|
+
|
|
42
|
+
child.kill('SIGINT')
|
|
43
|
+
delete process.env.A_CUSTOM_PORT
|
|
44
|
+
})
|
|
45
|
+
|
|
33
46
|
test('default logger', async ({ equal, same, match, teardown }) => {
|
|
34
47
|
const { child, url } = await start('-c', join(import.meta.url, '..', '..', 'fixtures', 'hello', 'no-server-logger.json'))
|
|
35
48
|
match(url, /http:\/\/127.0.0.1:[0-9]+/)
|
|
@@ -38,7 +51,6 @@ test('default logger', async ({ equal, same, match, teardown }) => {
|
|
|
38
51
|
|
|
39
52
|
test('plugin options', async ({ equal, same, match, teardown }) => {
|
|
40
53
|
const { child, url } = await start('-c', join(import.meta.url, '..', '..', 'fixtures', 'options', 'platformatic.service.yml'))
|
|
41
|
-
|
|
42
54
|
const res = await request(`${url}`)
|
|
43
55
|
equal(res.statusCode, 200)
|
|
44
56
|
const body = await res.body.json()
|
package/test/cli/watch.test.mjs
CHANGED
|
@@ -11,6 +11,7 @@ t.jobs = 5
|
|
|
11
11
|
function createLoggingPlugin (text, reloaded = false) {
|
|
12
12
|
return `\
|
|
13
13
|
module.exports = async (app) => {
|
|
14
|
+
app.log.info({ reloaded: ${reloaded}, text: '${text}' }, 'debugme')
|
|
14
15
|
if (${reloaded}) {
|
|
15
16
|
app.log.info('RELOADED')
|
|
16
17
|
}
|
|
@@ -45,8 +46,6 @@ test('should watch js files by default', async ({ equal, teardown, comment }) =>
|
|
|
45
46
|
])
|
|
46
47
|
|
|
47
48
|
const { child, url } = await start('-c', configFilePath)
|
|
48
|
-
child.stdout.pipe(process.stderr)
|
|
49
|
-
child.stderr.pipe(process.stderr)
|
|
50
49
|
teardown(() => child.kill('SIGINT'))
|
|
51
50
|
|
|
52
51
|
await writeFile(pluginFilePath, createLoggingPlugin('v2', true))
|
package/test/clients.test.js
CHANGED
|
@@ -9,7 +9,7 @@ const { compile } = require('../lib/compile')
|
|
|
9
9
|
const { rmdir } = require('fs/promises')
|
|
10
10
|
|
|
11
11
|
test('client is loaded', async ({ teardown, equal, pass, same, comment }) => {
|
|
12
|
-
const server1 = await buildServer(join(__dirname, '..', 'fixtures', 'hello', '
|
|
12
|
+
const server1 = await buildServer(join(__dirname, '..', 'fixtures', 'hello', 'warn-log.service.json'))
|
|
13
13
|
await server1.listen()
|
|
14
14
|
|
|
15
15
|
process.env.PLT_CLIENT_URL = server1.url
|
|
@@ -28,7 +28,7 @@ test('client is loaded', async ({ teardown, equal, pass, same, comment }) => {
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
test('client is loaded (ts)', async ({ teardown, equal, pass, same }) => {
|
|
31
|
-
const server1 = await buildServer(join(__dirname, '..', 'fixtures', 'hello', '
|
|
31
|
+
const server1 = await buildServer(join(__dirname, '..', 'fixtures', 'hello', 'warn-log.service.json'))
|
|
32
32
|
await server1.listen()
|
|
33
33
|
|
|
34
34
|
process.env.PLT_CLIENT_URL = server1.url
|
|
@@ -39,7 +39,7 @@ test('client is loaded (ts)', async ({ teardown, equal, pass, same }) => {
|
|
|
39
39
|
await rmdir(join(targetDir, 'dist'))
|
|
40
40
|
} catch {}
|
|
41
41
|
|
|
42
|
-
await compile(targetDir)
|
|
42
|
+
await compile(targetDir, { server: { logger: { level: 'warn' } } })
|
|
43
43
|
|
|
44
44
|
const server2 = await buildServer(join(targetDir, 'platformatic.service.json'))
|
|
45
45
|
teardown(async () => {
|
package/test/config.test.js
CHANGED
|
@@ -282,3 +282,71 @@ test('config reloads', async ({ teardown, equal, pass, same }) => {
|
|
|
282
282
|
same(await res.body.text(), 'ciao mondo', 'response')
|
|
283
283
|
}
|
|
284
284
|
})
|
|
285
|
+
|
|
286
|
+
test('restart throws if config is invalid', async ({ teardown, rejects }) => {
|
|
287
|
+
const server = await buildServer({
|
|
288
|
+
server: {
|
|
289
|
+
hostname: '127.0.0.1',
|
|
290
|
+
port: 0
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
teardown(server.stop)
|
|
294
|
+
await server.listen()
|
|
295
|
+
|
|
296
|
+
await rejects(server.restart({ foo: 'bar' }))
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test('config reloads by calling restart', async ({ teardown, equal, pass, same }) => {
|
|
300
|
+
const file = join(os.tmpdir(), `${process.pid}-1.js`)
|
|
301
|
+
|
|
302
|
+
await writeFile(file, `
|
|
303
|
+
module.exports = async function (app, options) {
|
|
304
|
+
app.get('/', () => options.message)
|
|
305
|
+
}`)
|
|
306
|
+
|
|
307
|
+
const server = await buildServer({
|
|
308
|
+
server: {
|
|
309
|
+
hostname: '127.0.0.1',
|
|
310
|
+
port: 0
|
|
311
|
+
},
|
|
312
|
+
plugins: {
|
|
313
|
+
paths: [{
|
|
314
|
+
path: file,
|
|
315
|
+
options: {
|
|
316
|
+
message: 'hello'
|
|
317
|
+
}
|
|
318
|
+
}]
|
|
319
|
+
},
|
|
320
|
+
metrics: false
|
|
321
|
+
})
|
|
322
|
+
teardown(server.stop)
|
|
323
|
+
await server.listen()
|
|
324
|
+
|
|
325
|
+
{
|
|
326
|
+
const res = await request(`${server.url}/`)
|
|
327
|
+
equal(res.statusCode, 200, 'add status code')
|
|
328
|
+
same(await res.body.text(), 'hello', 'response')
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
await server.restart({
|
|
332
|
+
server: {
|
|
333
|
+
hostname: '127.0.0.1',
|
|
334
|
+
port: 0
|
|
335
|
+
},
|
|
336
|
+
plugins: {
|
|
337
|
+
paths: [{
|
|
338
|
+
path: file,
|
|
339
|
+
options: {
|
|
340
|
+
message: 'ciao mondo'
|
|
341
|
+
}
|
|
342
|
+
}]
|
|
343
|
+
},
|
|
344
|
+
metrics: false
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
{
|
|
348
|
+
const res = await request(`${server.url}/`)
|
|
349
|
+
equal(res.statusCode, 200, 'add status code')
|
|
350
|
+
same(await res.body.text(), 'ciao mondo', 'response')
|
|
351
|
+
}
|
|
352
|
+
})
|
package/test/https.test.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { mkdtemp, writeFile } = require('fs/promises')
|
|
4
4
|
const { tmpdir } = require('os')
|
|
5
|
-
const {
|
|
5
|
+
const { join, relative } = require('path')
|
|
6
6
|
const selfCert = require('self-cert')
|
|
7
7
|
const { test } = require('tap')
|
|
8
8
|
const { Agent, setGlobalDispatcher, request } = require('undici')
|
|
@@ -10,7 +10,7 @@ const { buildServer } = require('..')
|
|
|
10
10
|
const { buildConfig } = require('./helper')
|
|
11
11
|
|
|
12
12
|
test('supports https options', async ({ teardown, equal, same, plan, comment }) => {
|
|
13
|
-
plan(
|
|
13
|
+
plan(6)
|
|
14
14
|
|
|
15
15
|
const { certificate, privateKey } = selfCert({})
|
|
16
16
|
const localDir = tmpdir()
|
|
@@ -45,7 +45,6 @@ test('supports https options', async ({ teardown, equal, same, plan, comment })
|
|
|
45
45
|
teardown(server.stop)
|
|
46
46
|
await server.listen()
|
|
47
47
|
|
|
48
|
-
equal(isAbsolute(server.app.platformatic.configManager.current.server.https.cert[0].path), true)
|
|
49
48
|
equal(server.url.startsWith('https://'), true)
|
|
50
49
|
let res = await (request(`${server.url}/`))
|
|
51
50
|
equal(res.statusCode, 200)
|
|
@@ -111,6 +111,10 @@ test('update config', async ({ teardown, equal, pass, same }) => {
|
|
|
111
111
|
}`)
|
|
112
112
|
|
|
113
113
|
await server.restart({
|
|
114
|
+
server: {
|
|
115
|
+
hostname: '127.0.0.1',
|
|
116
|
+
port: 0
|
|
117
|
+
},
|
|
114
118
|
plugins: {
|
|
115
119
|
paths: [file2]
|
|
116
120
|
}
|
|
@@ -248,7 +252,7 @@ test('load and reload ESM', async ({ teardown, equal, pass, same }) => {
|
|
|
248
252
|
}
|
|
249
253
|
})
|
|
250
254
|
|
|
251
|
-
test('server should be available after reload a compromised plugin', async ({ teardown, equal, pass, same }) => {
|
|
255
|
+
test('server should be available after reload a compromised plugin', async ({ teardown, equal, pass, same, rejects }) => {
|
|
252
256
|
const file = join(os.tmpdir(), `some-plugin-${process.pid}.js`)
|
|
253
257
|
|
|
254
258
|
const workingModule = `
|
|
@@ -285,7 +289,7 @@ test('server should be available after reload a compromised plugin', async ({ te
|
|
|
285
289
|
}
|
|
286
290
|
|
|
287
291
|
await writeFile(file, workingModule)
|
|
288
|
-
await server.restart(restartConfig)
|
|
292
|
+
await rejects(server.restart(restartConfig))
|
|
289
293
|
|
|
290
294
|
{
|
|
291
295
|
const res = await request(`${server.url}/`, { method: 'GET' })
|
package/test/utils.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('tap')
|
|
4
|
-
const { getJSPluginPath,
|
|
4
|
+
const { getJSPluginPath, isFileAccessible } = require('../lib/utils')
|
|
5
5
|
const { join, resolve } = require('path')
|
|
6
6
|
|
|
7
7
|
test('should get the path of a TS plugin', (t) => {
|
|
@@ -19,18 +19,9 @@ test('should get the path of a JS plugin', (t) => {
|
|
|
19
19
|
t.equal(result, '/something/plugin.js')
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
test('
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
])
|
|
26
|
-
t.equal(result, 'platformatic.service.json')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
test('findConfigFile / failure', async (t) => {
|
|
30
|
-
const result = await findConfigFile(join(__dirname, '..', 'fixtures', 'hello'), [
|
|
31
|
-
'foobar'
|
|
32
|
-
])
|
|
33
|
-
t.equal(result, undefined)
|
|
22
|
+
test('isFileAccessible with dir', async (t) => {
|
|
23
|
+
const dir = resolve(join(__dirname, '..', 'fixtures', 'hello'))
|
|
24
|
+
t.equal(await isFileAccessible('platformatic.service.json', dir), true)
|
|
34
25
|
})
|
|
35
26
|
|
|
36
27
|
test('isFileAccessible no dir', async (t) => {
|