@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.
@@ -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
+ }
@@ -3,7 +3,7 @@
3
3
  "hostname": "127.0.0.1",
4
4
  "port": 0,
5
5
  "logger": {
6
- "level": "info",
6
+ "level": "warn",
7
7
  "name": "hello client"
8
8
  }
9
9
  },
@@ -3,7 +3,7 @@
3
3
  "hostname": "127.0.0.1",
4
4
  "port": 0,
5
5
  "logger": {
6
- "level": "info",
6
+ "level": "warn",
7
7
  "name": "hello client ts"
8
8
  }
9
9
  },
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, deepmerge } = require('@platformatic/utils')
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
- if (isKeyEnabled('metrics', opts)) {
38
- app.register(require('./lib/metrics-plugin'), opts.metrics)
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, opts)
38
+ await app.register(plugin)
44
39
  }
45
40
  }
46
41
 
47
- if (!app.hasDecorator('platformatic')) {
48
- app.decorate('platformatic', {})
49
- }
42
+ const serviceConfig = app.platformatic.config?.service
50
43
 
51
- {
52
- const fileWatcher = opts.fileWatcher
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
- const serviceConfig = app.platformatic.config?.service
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 (app.platformatic.config.clients || [])) {
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 (opts.plugins) {
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 = app.platformatic.configManager.fullPath || join(process.cwd(), 'platformatic.db.json')
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
- opts.plugins.paths = opts.plugins.paths.map((plugin) => {
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 opts.plugins.paths) {
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 = opts.plugins.hotReload !== false
121
- const isWatchEnabled = app.platformatic.config.watch !== false
122
- app.log.debug({ plugins: opts.plugins.path }, 'loading plugin')
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: opts.plugins.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: opts.plugins.paths })
109
+ await app.register(require(wrapperPath), { paths: config.plugins.paths })
140
110
  }
141
111
  }
142
112
 
143
113
  // Enable CORS
144
- if (opts.cors) {
145
- let origin = opts.cors.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
- opts.cors.origin = origin
122
+ config.server.cors.origin = origin
153
123
 
154
- app.register(require('@fastify/cors'), opts.cors)
124
+ app.register(require('@fastify/cors'), config.server.cors)
155
125
  }
156
126
 
157
- if (isKeyEnabled('healthCheck', opts)) {
127
+ if (isKeyEnabled('healthCheck', config.server)) {
128
+ const healthCheck = config.server.healthCheck
158
129
  app.register(underPressure, {
159
130
  exposeStatusRoute: '/status',
160
- healthCheckInterval: opts.healthCheck.interval !== undefined ? opts.healthCheck.interval : 5000,
161
- ...opts.healthCheck,
162
- healthCheck: opts.healthCheck.fn
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'), opts)
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
- const cm = new ConfigManager(defaultConfig(app, options))
185
+ cm = new ConfigManager(defaultConfig(app, options))
245
186
  await cm.parseAndValidate()
246
- const stash = adjustConfigBeforeMerge(cm.current)
247
- options = deepmerge({}, options, cm.current)
248
- options.configManager = cm
249
- adjustConfigAfterMerge(options, stash)
187
+ } else {
188
+ cm = options.configManager
250
189
  }
251
190
 
252
- if (options.server.https) {
253
- options.server.https.key = await adjustHttpsKeyAndCert(options.server.https.key)
254
- options.server.https.cert = await adjustHttpsKeyAndCert(options.server.https.cert)
255
- options.server = { ...options.server, ...options.server.https }
256
- delete options.server.https
257
- options.server.protocol = 'https'
258
- } else {
259
- options.server.protocol = 'http'
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
- const _restart = handler.restart
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 = (opts) => {
281
- /* c8 ignore next 3 */
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
- addLoggerToTheConfig(opts)
287
- const configManager = handler.app.platformatic.configManager
288
-
289
- if (!opts) {
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
- // Ignore because not tested on Windows
294
- // TODO: remove the ignore, we shoduld be testing
295
- // this on Windows
296
- const fileWatcher = handler.app.platformatic.fileWatcher
297
- opts.fileWatcher = fileWatcher
298
- opts.configManager = configManager
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(opts).then(() => {
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
  }
@@ -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, configFileNames = ourConfigFiles) {
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(), configFileNames)
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: ${ourConfigFiles.join('\n')}
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.21.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.1.0",
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.0",
30
- "typescript": "^5.0.0",
31
- "undici": "^5.20.0",
32
- "vscode-json-languageservice": "^5.3.0",
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.1"
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.0",
40
+ "@fastify/cors": "^8.2.1",
41
41
  "@fastify/deepmerge": "^1.3.0",
42
42
  "@fastify/restartable": "^1.4.0",
43
- "@fastify/static": "^6.9.0",
43
+ "@fastify/static": "^6.10.1",
44
44
  "@fastify/swagger": "^8.3.1",
45
- "@fastify/swagger-ui": "^1.4.0",
45
+ "@fastify/swagger-ui": "^1.8.0",
46
46
  "@fastify/under-pressure": "^8.2.0",
47
- "@mercuriusjs/federation": "^1.0.1",
48
- "close-with-grace": "^1.1.0",
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.0.0",
54
- "fastify": "^4.13.0",
55
- "fastify-metrics": "^10.0.3",
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": "^12.2.0",
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.33",
66
- "@platformatic/client": "0.21.0",
67
- "@platformatic/config": "0.21.0",
68
- "@platformatic/utils": "0.21.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()
@@ -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()
@@ -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))
@@ -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', 'platformatic.service.json'))
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', 'platformatic.service.json'))
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 () => {
@@ -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
+ })
@@ -0,0 +1,10 @@
1
+ {
2
+ "server": {
3
+ "logger": {
4
+ "level": "info"
5
+ },
6
+ "hostname": "127.0.0.1",
7
+ "port": "{A_CUSTOM_PORT}",
8
+ "pluginTimeout": "10"
9
+ }
10
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { mkdtemp, writeFile } = require('fs/promises')
4
4
  const { tmpdir } = require('os')
5
- const { isAbsolute, join, relative } = require('path')
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(7)
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' })
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('tap')
4
- const { getJSPluginPath, findConfigFile, isFileAccessible } = require('../lib/utils')
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('findConfigFile', async (t) => {
23
- const result = await findConfigFile(join(__dirname, '..', 'fixtures', 'hello'), [
24
- 'platformatic.service.json'
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) => {