@platformatic/service 0.23.1 → 0.24.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/index.d.ts CHANGED
@@ -1,16 +1,13 @@
1
- import { FastifyInstance, InjectOptions, LightMyRequestResponse } from "fastify"
1
+ import { FastifyInstance } from "fastify"
2
2
 
3
3
  export type pltServiceHandlerBuildServer = {
4
4
  app: FastifyInstance
5
5
  address: string
6
6
  port: number
7
7
  restart: () => Promise<void>
8
- listen: () => Promise<{
9
- address: string
10
- port: number
11
- }>
12
- stop: () => Promise<void>
13
- inject: (opts: InjectOptions | string) => Promise<LightMyRequestResponse>
8
+ listen: FastifyInstance['listen']
9
+ close: FastifyInstance['close']
10
+ inject: FastifyInstance['inject']
14
11
  }
15
12
 
16
13
  declare module '@platformatic/service' {
package/index.js CHANGED
@@ -1,9 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { readFile } = require('fs/promises')
4
-
5
- const ConfigManager = require('@platformatic/config')
6
- const { restartable } = require('@fastify/restartable')
7
3
  const { isKeyEnabled } = require('@platformatic/utils')
8
4
 
9
5
  const compiler = require('./lib/compile')
@@ -18,8 +14,9 @@ const setupHealthCheck = require('./lib/plugins/health-check')
18
14
  const loadPlugins = require('./lib/plugins/plugins')
19
15
 
20
16
  const { schema } = require('./lib/schema')
21
- const { loadConfig, generateDefaultConfig } = require('./lib/load-config')
17
+ const { loadConfig } = require('./lib/load-config')
22
18
  const { addLoggerToTheConfig } = require('./lib/utils')
19
+ const { start, buildServer } = require('./lib/start')
23
20
 
24
21
  async function platformaticService (app, opts, toLoad = []) {
25
22
  const configManager = app.platformatic.configManager
@@ -29,6 +26,17 @@ async function platformaticService (app, opts, toLoad = []) {
29
26
  app.register(setupMetrics, config.metrics)
30
27
  }
31
28
 
29
+ app.setErrorHandler(async (err, req, reply) => {
30
+ if (!(err instanceof Error)) {
31
+ req.log.debug({ err }, 'error encountered within the sandbox, wrapping it')
32
+ const newError = new Error(err.message)
33
+ newError.cause = err
34
+ newError.statusCode = err.statusCode || 500
35
+ err = newError
36
+ }
37
+ throw err
38
+ })
39
+
32
40
  if (Array.isArray(toLoad)) {
33
41
  for (const plugin of toLoad) {
34
42
  await app.register(plugin)
@@ -73,123 +81,6 @@ async function platformaticService (app, opts, toLoad = []) {
73
81
  }
74
82
  }
75
83
 
76
- platformaticService[Symbol.for('skip-override')] = true
77
- platformaticService.schema = schema
78
- platformaticService.envWhitelist = ['PORT', 'HOSTNAME']
79
-
80
- async function adjustHttpsKeyAndCert (arg) {
81
- if (typeof arg === 'string') {
82
- return arg
83
- }
84
-
85
- if (!Array.isArray(arg)) {
86
- // { path: pathToKeyOrCert }
87
- return readFile(arg.path)
88
- }
89
-
90
- // Array of strings or objects.
91
- for (let i = 0; i < arg.length; ++i) {
92
- arg[i] = await adjustHttpsKeyAndCert(arg[i])
93
- }
94
-
95
- return arg
96
- }
97
-
98
- function defaultConfig (app, source) {
99
- const res = {
100
- source,
101
- ...generateDefaultConfig(),
102
- allowToWatch: ['.env', ...(app?.allowToWatch || [])],
103
- envWhitelist: ['PORT', ...(app?.envWhitelist || [])]
104
- }
105
-
106
- if (app.schema) {
107
- res.schema = app.schema
108
- }
109
-
110
- return res
111
- }
112
-
113
- async function buildServer (options, app) {
114
- app = app || platformaticService
115
-
116
- let configManager = options.configManager
117
- if (!configManager) {
118
- // instantiate a new config manager from current options
119
- configManager = new ConfigManager(defaultConfig(app, options))
120
- await configManager.parseAndValidate()
121
- }
122
-
123
- // options is a path
124
- if (typeof options === 'string') {
125
- options = configManager.current
126
- }
127
-
128
- let url = null
129
-
130
- async function createRestartable (fastify) {
131
- const config = configManager.current
132
- const root = fastify(config.server)
133
-
134
- root.decorate('platformatic', { configManager, config })
135
- root.register(app)
136
-
137
- root.decorate('url', {
138
- getter () {
139
- return url
140
- }
141
- })
142
-
143
- if (root.restarted) {
144
- root.log.info('restarted')
145
- }
146
-
147
- return root
148
- }
149
-
150
- const { port, hostname, ...serverOptions } = options.server
151
-
152
- if (serverOptions.https) {
153
- serverOptions.https.key = await adjustHttpsKeyAndCert(serverOptions.https.key)
154
- serverOptions.https.cert = await adjustHttpsKeyAndCert(serverOptions.https.cert)
155
- }
156
-
157
- const handler = await restartable(createRestartable)
158
-
159
- configManager.on('update', async (newConfig) => {
160
- handler.log.debug('config changed')
161
- handler.log.trace({ newConfig }, 'new config')
162
-
163
- if (newConfig.watch === false) {
164
- /* c8 ignore next 4 */
165
- if (handler.tsCompilerWatcher) {
166
- handler.tsCompilerWatcher.kill('SIGTERM')
167
- handler.log.debug('stop watching typescript files')
168
- }
169
-
170
- if (handler.fileWatcher) {
171
- await handler.fileWatcher.stopWatching()
172
- handler.log.debug('stop watching files')
173
- }
174
- }
175
-
176
- await safeRestart(handler)
177
- /* c8 ignore next 1 */
178
- })
179
-
180
- configManager.on('error', function (err) {
181
- /* c8 ignore next 1 */
182
- handler.log.error({ err }, 'error reloading the configuration')
183
- })
184
-
185
- handler.decorate('start', async () => {
186
- url = await handler.listen({ host: hostname, port })
187
- return url
188
- })
189
-
190
- return handler
191
- }
192
-
193
84
  async function onFilesUpdated (app) {
194
85
  // Reload the config as well, otherwise we will have problems
195
86
  // in case the files watcher triggers the config watcher too
@@ -209,32 +100,29 @@ async function onFilesUpdated (app) {
209
100
  }
210
101
  }
211
102
 
212
- async function safeRestart (app) {
213
- try {
214
- await app.restart()
215
- /* c8 ignore next 8 */
216
- } catch (err) {
217
- app.log.error({
218
- err: {
219
- message: err.message,
220
- stack: err.stack
221
- }
222
- }, 'failed to reload server')
103
+ platformaticService[Symbol.for('skip-override')] = true
104
+ platformaticService.schema = schema
105
+ platformaticService.configType = 'service'
106
+ platformaticService.configManagerConfig = {
107
+ schema,
108
+ envWhitelist: ['PORT', 'HOSTNAME'],
109
+ allowToWatch: ['.env'],
110
+ schemaOptions: {
111
+ useDefaults: true,
112
+ coerceTypes: true,
113
+ allErrors: true,
114
+ strict: false
223
115
  }
224
116
  }
225
117
 
226
- // This is for @platformatic/db to use
227
- /* c8 ignore next 4 */
228
- async function buildStart (loadConfig, buildServer, configManagerConfig) {
229
- const { buildStart } = await import('./lib/start.mjs')
230
- return buildStart(loadConfig, buildServer, configManagerConfig)
118
+ function _buildServer (options, app) {
119
+ return buildServer(options, app || platformaticService)
231
120
  }
232
121
 
233
- module.exports.buildServer = buildServer
122
+ module.exports.buildServer = _buildServer
234
123
  module.exports.schema = require('./lib/schema')
235
124
  module.exports.platformaticService = platformaticService
236
125
  module.exports.loadConfig = loadConfig
237
126
  module.exports.addLoggerToTheConfig = addLoggerToTheConfig
238
- module.exports.generateConfigManagerConfig = generateDefaultConfig
239
127
  module.exports.tsCompiler = compiler
240
- module.exports.buildStart = buildStart
128
+ module.exports.start = start
package/index.test-d.ts CHANGED
@@ -1,8 +1,5 @@
1
1
  import { expectError, expectType } from 'tsd';
2
- import {
3
- FastifyInstance,
4
- LightMyRequestResponse,
5
- } from 'fastify';
2
+ import { FastifyInstance } from 'fastify';
6
3
  import { pltServiceHandlerBuildServer } from '.';
7
4
 
8
5
  const server: pltServiceHandlerBuildServer = {
@@ -10,9 +7,9 @@ const server: pltServiceHandlerBuildServer = {
10
7
  address: 'localhost',
11
8
  port: 3000,
12
9
  restart: async () => {},
13
- listen: async () => ({ address: 'localhost', port: 3000 }),
14
- stop: async () => {},
15
- inject: async () => ({} as LightMyRequestResponse),
10
+ listen: async () => '',
11
+ close: (async () => undefined) as unknown as FastifyInstance['close'],
12
+ inject: (async () => undefined) as unknown as FastifyInstance['inject']
16
13
  };
17
14
 
18
15
  expectType<pltServiceHandlerBuildServer>(server);
package/lib/compile.js CHANGED
@@ -102,11 +102,13 @@ async function compileWatch (cwd, config) {
102
102
  return { child }
103
103
  }
104
104
 
105
- function buildCompileCmd (_loadConfig) {
105
+ function buildCompileCmd (app) {
106
106
  return async function compileCmd (_args) {
107
107
  let fullPath = null
108
108
  try {
109
- const { configManager } = await _loadConfig({}, _args)
109
+ const { configManager } = await loadConfig({}, _args, app, {
110
+ watch: false
111
+ })
110
112
  await configManager.parseAndValidate()
111
113
  fullPath = dirname(configManager.fullPath)
112
114
  /* c8 ignore next 4 */
@@ -121,9 +123,6 @@ function buildCompileCmd (_loadConfig) {
121
123
  }
122
124
  }
123
125
 
124
- const compileCmd = buildCompileCmd(loadConfig)
125
-
126
126
  module.exports.compile = compile
127
127
  module.exports.compileWatch = compileWatch
128
128
  module.exports.buildCompileCmd = buildCompileCmd
129
- module.exports.compileCmd = compileCmd
@@ -4,32 +4,19 @@ 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 { schema } = require('./schema.js')
8
-
9
- function generateDefaultConfig () {
10
- return {
11
- schema,
12
- schemaOptions: {
13
- useDefaults: true,
14
- coerceTypes: true,
15
- allErrors: true,
16
- strict: false
17
- }
18
- }
19
- }
20
7
 
21
8
  // Unfortunately c8 does not see those on Windows
22
9
  /* c8 ignore next 70 */
23
- async function loadConfig (minimistConfig, _args, defaultConfig, configType = 'service') {
24
- if (defaultConfig === undefined) {
25
- defaultConfig = generateDefaultConfig()
26
- } else if (defaultConfig?.mergeDefaults) {
27
- defaultConfig = { ...generateDefaultConfig(), ...defaultConfig }
10
+ async function loadConfig (minimistConfig, _args, app, overrides = {}) {
11
+ const configManagerConfig = {
12
+ ...app.configManagerConfig,
13
+ ...overrides
28
14
  }
29
15
 
16
+ const configType = app.configType
17
+
30
18
  const args = parseArgs(_args, deepmerge({ all: true })({
31
19
  string: ['allow-env'],
32
- boolean: ['hotReload'],
33
20
  default: {
34
21
  allowEnv: '', // The default is set in ConfigManager
35
22
  hotReload: true
@@ -37,8 +24,7 @@ async function loadConfig (minimistConfig, _args, defaultConfig, configType = 's
37
24
  alias: {
38
25
  v: 'version',
39
26
  c: 'config',
40
- allowEnv: ['allow-env', 'E'],
41
- hotReload: ['hot-reload']
27
+ allowEnv: ['allow-env', 'E']
42
28
  }
43
29
  }, minimistConfig))
44
30
 
@@ -58,10 +44,10 @@ Error: ${err}
58
44
  process.exit(1)
59
45
  }
60
46
 
61
- const envWhitelist = args.allowEnv ? args.allowEnv : defaultConfig.envWhitelist
47
+ const envWhitelist = args.allowEnv ? args.allowEnv : configManagerConfig.envWhitelist
62
48
  const configManager = new ConfigManager({
63
49
  source: args.config,
64
- ...defaultConfig,
50
+ ...configManagerConfig,
65
51
  envWhitelist
66
52
  })
67
53
 
@@ -85,4 +71,3 @@ function printConfigValidationErrors (configManager) {
85
71
  }
86
72
 
87
73
  module.exports.loadConfig = loadConfig
88
- module.exports.generateDefaultConfig = generateDefaultConfig
@@ -19,10 +19,17 @@ module.exports = fp(async function (app, opts) {
19
19
  } else {
20
20
  let loaded = await import(`file://${plugin.path}`)
21
21
  /* c8 ignore next 3 */
22
- if (loaded.__esModule === true) {
22
+ if (loaded.__esModule === true || typeof loaded.default === 'function') {
23
23
  loaded = loaded.default
24
24
  }
25
+
26
+ let skipOverride
27
+ if (plugin.encapsulate === false) {
28
+ skipOverride = loaded[Symbol.for('skip-override')]
29
+ loaded[Symbol.for('skip-override')] = true
30
+ }
25
31
  await app.register(loaded, plugin.options)
32
+ loaded[Symbol.for('skip-override')] = skipOverride
26
33
  }
27
34
  }
28
35
  })
package/lib/start.js ADDED
@@ -0,0 +1,176 @@
1
+ 'use strict'
2
+
3
+ const { readFile } = require('fs/promises')
4
+ const close = require('close-with-grace')
5
+ const { loadConfig } = require('./load-config')
6
+ const { addLoggerToTheConfig } = require('./utils.js')
7
+ const ConfigManager = require('@platformatic/config')
8
+ const { restartable } = require('@fastify/restartable')
9
+
10
+ async function adjustHttpsKeyAndCert (arg) {
11
+ if (typeof arg === 'string') {
12
+ return arg
13
+ }
14
+
15
+ if (!Array.isArray(arg)) {
16
+ // { path: pathToKeyOrCert }
17
+ return readFile(arg.path)
18
+ }
19
+
20
+ // Array of strings or objects.
21
+ for (let i = 0; i < arg.length; ++i) {
22
+ arg[i] = await adjustHttpsKeyAndCert(arg[i])
23
+ }
24
+
25
+ return arg
26
+ }
27
+
28
+ async function buildServer (options, app) {
29
+ let configManager = options.configManager
30
+ if (!configManager) {
31
+ // instantiate a new config manager from current options
32
+ configManager = new ConfigManager({ ...app.configManagerConfig, source: options })
33
+ await configManager.parseAndValidate()
34
+ }
35
+
36
+ // options is a path
37
+ if (typeof options === 'string') {
38
+ options = configManager.current
39
+ }
40
+
41
+ let url = null
42
+
43
+ async function createRestartable (fastify) {
44
+ const config = configManager.current
45
+ const root = fastify(config.server)
46
+
47
+ root.decorate('platformatic', { configManager, config })
48
+ root.register(app)
49
+
50
+ root.decorate('url', {
51
+ getter () {
52
+ return url
53
+ }
54
+ })
55
+
56
+ if (root.restarted) {
57
+ root.log.info('restarted')
58
+ }
59
+
60
+ return root
61
+ }
62
+
63
+ const { port, hostname, ...serverOptions } = options.server
64
+
65
+ if (serverOptions.https) {
66
+ serverOptions.https.key = await adjustHttpsKeyAndCert(serverOptions.https.key)
67
+ serverOptions.https.cert = await adjustHttpsKeyAndCert(serverOptions.https.cert)
68
+ }
69
+
70
+ const handler = await restartable(createRestartable)
71
+
72
+ configManager.on('update', async (newConfig) => {
73
+ handler.log.debug('config changed')
74
+ handler.log.trace({ newConfig }, 'new config')
75
+
76
+ if (newConfig.watch === false) {
77
+ /* c8 ignore next 4 */
78
+ if (handler.tsCompilerWatcher) {
79
+ handler.tsCompilerWatcher.kill('SIGTERM')
80
+ handler.log.debug('stop watching typescript files')
81
+ }
82
+
83
+ if (handler.fileWatcher) {
84
+ await handler.fileWatcher.stopWatching()
85
+ handler.log.debug('stop watching files')
86
+ }
87
+ }
88
+
89
+ await safeRestart(handler)
90
+ /* c8 ignore next 1 */
91
+ })
92
+
93
+ configManager.on('error', function (err) {
94
+ /* c8 ignore next 1 */
95
+ handler.log.error({ err }, 'error reloading the configuration')
96
+ })
97
+
98
+ handler.decorate('start', async () => {
99
+ url = await handler.listen({ host: hostname, port })
100
+ return url
101
+ })
102
+
103
+ return handler
104
+ }
105
+
106
+ async function safeRestart (app) {
107
+ try {
108
+ await app.restart()
109
+ /* c8 ignore next 8 */
110
+ } catch (err) {
111
+ app.log.error({
112
+ err: {
113
+ message: err.message,
114
+ stack: err.stack
115
+ }
116
+ }, 'failed to reload server')
117
+ }
118
+ }
119
+
120
+ async function start (appType, _args) {
121
+ const { configManager } = await loadConfig({}, _args, appType)
122
+
123
+ const config = configManager.current
124
+
125
+ addLoggerToTheConfig(config)
126
+
127
+ const _transformConfig = configManager._transformConfig.bind(configManager)
128
+ configManager._transformConfig = function () {
129
+ const config = configManager.current
130
+ addLoggerToTheConfig(config)
131
+ return _transformConfig(config)
132
+ }
133
+
134
+ let app = null
135
+
136
+ try {
137
+ // Set the location of the config
138
+ app = await buildServer({ ...config, configManager }, appType)
139
+
140
+ await app.start()
141
+ // TODO: this log is used in the start command. Should be replaced
142
+ app.log.info({ url: app.url })
143
+ } catch (err) {
144
+ // TODO route this to a logger
145
+ console.error(err)
146
+ process.exit(1)
147
+ }
148
+
149
+ // Ignore from CI because SIGUSR2 is not available
150
+ // on Windows
151
+ /* c8 ignore next 25 */
152
+ process.on('SIGUSR2', function () {
153
+ app.log.info('reloading configuration')
154
+ safeRestart(app)
155
+ return false
156
+ })
157
+
158
+ close(async ({ signal, err }) => {
159
+ // Windows does not support trapping signals
160
+ if (err) {
161
+ app.log.error({
162
+ err: {
163
+ message: err.message,
164
+ stack: err.stack
165
+ }
166
+ }, 'exiting')
167
+ } else if (signal) {
168
+ app.log.info({ signal }, 'received signal')
169
+ }
170
+
171
+ await app.close()
172
+ })
173
+ }
174
+
175
+ module.exports.buildServer = buildServer
176
+ module.exports.start = start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/service",
3
- "version": "0.23.1",
3
+ "version": "0.24.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -55,7 +55,7 @@
55
55
  "fastify": "^4.17.0",
56
56
  "fastify-metrics": "^10.3.0",
57
57
  "fastify-plugin": "^4.5.0",
58
- "fastify-sandbox": "^0.11.0",
58
+ "fastify-sandbox": "^0.12.0",
59
59
  "graphql": "^16.6.0",
60
60
  "help-me": "^4.2.0",
61
61
  "mercurius": "^13.0.0",
@@ -64,9 +64,9 @@
64
64
  "pino-pretty": "^10.0.0",
65
65
  "rfdc": "^1.3.0",
66
66
  "ua-parser-js": "^1.0.35",
67
- "@platformatic/client": "0.23.1",
68
- "@platformatic/config": "0.23.1",
69
- "@platformatic/utils": "0.23.1"
67
+ "@platformatic/client": "0.24.0",
68
+ "@platformatic/config": "0.24.0",
69
+ "@platformatic/utils": "0.24.0"
70
70
  },
71
71
  "standard": {
72
72
  "ignore": [
package/service.mjs CHANGED
@@ -8,8 +8,9 @@ import { readFile } from 'fs/promises'
8
8
  import { join } from 'desm'
9
9
  import { generateJsonSchemaConfig } from './lib/gen-schema.js'
10
10
 
11
- import start from './lib/start.mjs'
12
- import { compileCmd } from './lib/compile.js'
11
+ import { buildCompileCmd } from './lib/compile.js'
12
+
13
+ import { start, platformaticService } from './index.js'
13
14
 
14
15
  const help = helpMe({
15
16
  dir: join(import.meta.url, 'help'),
@@ -22,8 +23,14 @@ const program = commist({ maxDistance: 2 })
22
23
  program.register('help', help.toStdout)
23
24
  program.register('help start', help.toStdout.bind(null, ['start']))
24
25
 
25
- program.register('start', start)
26
- program.register('compile', compileCmd)
26
+ program.register('start', (argv) => {
27
+ start(platformaticService, argv).catch((err) => {
28
+ /* c8 ignore next 2 */
29
+ console.error(err)
30
+ process.exit(1)
31
+ })
32
+ })
33
+ program.register('compile', buildCompileCmd(platformaticService))
27
34
  program.register('schema config', generateJsonSchemaConfig)
28
35
  program.register('schema', help.toStdout.bind(null, ['schema']))
29
36
 
@@ -430,3 +430,97 @@ test('nested directories', async ({ teardown, equal, same }) => {
430
430
  equal(body, 'I\'m sorry, there was an error processing your request.')
431
431
  }
432
432
  })
433
+
434
+ test('disable encapsulation for a single file', async ({ teardown, equal, same }) => {
435
+ const config = {
436
+ server: {
437
+ hostname: '127.0.0.1',
438
+ port: 0,
439
+ // Windows CI is slow
440
+ pluginTimeout: 60 * 1000
441
+ },
442
+ service: {
443
+ openapi: true
444
+ },
445
+ plugins: {
446
+ paths: [{
447
+ path: join(__dirname, 'fixtures', 'nested-directories', 'plugins', 'decorator.js'),
448
+ encapsulate: false
449
+ }, {
450
+ path: join(__dirname, 'fixtures', 'nested-directories', 'plugins', 'handlers.js'),
451
+ encapsulate: false
452
+ }, {
453
+ path: join(__dirname, 'fixtures', 'nested-directories', 'modules'),
454
+ encapsulate: false,
455
+ maxDepth: 1
456
+ }]
457
+ }
458
+ }
459
+
460
+ const app = await buildServer(config)
461
+ teardown(async () => {
462
+ await app.close()
463
+ })
464
+ await app.start()
465
+
466
+ {
467
+ const res = await request(`${app.url}/foo/baz`)
468
+ equal(res.statusCode, 404, 'status code')
469
+ const body = await res.body.text()
470
+ equal(body, 'I\'m sorry, I couldn\'t find what you were looking for.')
471
+ }
472
+
473
+ {
474
+ const res = await request(`${app.url}/foo`)
475
+ equal(res.statusCode, 200, 'status code')
476
+ const body = await res.body.text()
477
+ equal(body, 'bar')
478
+ }
479
+ })
480
+
481
+ test('disable encapsulation for a single file / different order', async ({ teardown, equal, same }) => {
482
+ const config = {
483
+ server: {
484
+ hostname: '127.0.0.1',
485
+ port: 0,
486
+ // Windows CI is slow
487
+ pluginTimeout: 60 * 1000
488
+ },
489
+ service: {
490
+ openapi: true
491
+ },
492
+ plugins: {
493
+ paths: [{
494
+ path: join(__dirname, 'fixtures', 'nested-directories', 'modules'),
495
+ encapsulate: false,
496
+ maxDepth: 1
497
+ }, {
498
+ path: join(__dirname, 'fixtures', 'nested-directories', 'plugins', 'decorator.js'),
499
+ encapsulate: false
500
+ }, {
501
+ path: join(__dirname, 'fixtures', 'nested-directories', 'plugins', 'handlers.js'),
502
+ encapsulate: false
503
+ }]
504
+ }
505
+ }
506
+
507
+ const app = await buildServer(config)
508
+ teardown(async () => {
509
+ await app.close()
510
+ })
511
+ await app.start()
512
+
513
+ {
514
+ const res = await request(`${app.url}/foo/baz`)
515
+ equal(res.statusCode, 404, 'status code')
516
+ const body = await res.body.text()
517
+ equal(body, 'I\'m sorry, I couldn\'t find what you were looking for.')
518
+ }
519
+
520
+ {
521
+ const res = await request(`${app.url}/foo`)
522
+ equal(res.statusCode, 200, 'status code')
523
+ const body = await res.body.text()
524
+ equal(body, 'bar')
525
+ }
526
+ })
@@ -254,7 +254,7 @@ t.test('valid tsconfig file inside an inner folder', async (t) => {
254
254
  await cp(testDir, cwd, { recursive: true })
255
255
 
256
256
  try {
257
- await execa('node', [cliPath, 'compile'], { cwd })
257
+ await execa('node', [cliPath, 'compile'], { cwd, stdio: 'inherit' })
258
258
  } catch (err) {
259
259
  t.fail('should not catch any error')
260
260
  }
@@ -21,8 +21,8 @@ tap.teardown(() => {
21
21
 
22
22
  export const cliPath = join(import.meta.url, '..', '..', 'service.mjs')
23
23
 
24
- export async function start (...args) {
25
- const child = execa('node', [cliPath, 'start', ...args])
24
+ export async function start (commandOpts, exacaOpts = {}) {
25
+ const child = execa('node', [cliPath, 'start', ...commandOpts], exacaOpts)
26
26
  child.stderr.pipe(process.stdout)
27
27
 
28
28
  const output = child.stdout.pipe(split(function (line) {
@@ -5,7 +5,7 @@ import { request } from 'undici'
5
5
  import { execa } from 'execa'
6
6
 
7
7
  test('autostart', async ({ equal, same, match, teardown }) => {
8
- const { child, url } = await start('-c', join(import.meta.url, '..', '..', 'fixtures', 'hello', 'platformatic.service.json'))
8
+ const { child, url } = await start(['-c', join(import.meta.url, '..', '..', 'fixtures', 'hello', 'platformatic.service.json')])
9
9
 
10
10
  const res = await request(`${url}`)
11
11
  equal(res.statusCode, 200)
@@ -18,7 +18,7 @@ test('autostart', async ({ equal, same, match, teardown }) => {
18
18
  })
19
19
 
20
20
  test('start command', async ({ equal, same, match, teardown }) => {
21
- const { child, url } = await start('start', '-c', join(import.meta.url, '..', '..', 'fixtures', 'hello', 'platformatic.service.json'))
21
+ const { child, url } = await start(['-c', join(import.meta.url, '..', '..', 'fixtures', 'hello', 'platformatic.service.json')])
22
22
 
23
23
  const res = await request(`${url}`)
24
24
  equal(res.statusCode, 200)
@@ -31,8 +31,17 @@ test('start command', async ({ equal, same, match, teardown }) => {
31
31
  })
32
32
 
33
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')
34
+ const { child, url } = await start(
35
+ [
36
+ '-c', join(import.meta.url, '..', 'fixtures', 'custom-port-placeholder.json'),
37
+ '--allow-env', 'A_CUSTOM_PORT'
38
+ ],
39
+ {
40
+ env: {
41
+ A_CUSTOM_PORT: '11111'
42
+ }
43
+ }
44
+ )
36
45
  equal(url, 'http://127.0.0.1:11111', 'A_CUSTOM_PORT env variable has been used')
37
46
  const res = await request(`${url}`)
38
47
  equal(res.statusCode, 200)
@@ -43,14 +52,37 @@ test('allow custom env properties', async ({ equal, same, match, teardown }) =>
43
52
  delete process.env.A_CUSTOM_PORT
44
53
  })
45
54
 
55
+ test('use default env variables names', async ({ equal, match }) => {
56
+ const { child, url } = await start(
57
+ [
58
+ '-c', join(import.meta.url, '..', 'fixtures', 'default-env-var-names.json')
59
+ ],
60
+ {
61
+ env: {
62
+ PORT: '11111',
63
+ HOSTNAME: '127.0.0.1'
64
+ }
65
+ }
66
+ )
67
+
68
+ equal(url, 'http://127.0.0.1:11111', 'default env variable names has been used')
69
+ const res = await request(`${url}`)
70
+ equal(res.statusCode, 200)
71
+ const body = await res.body.json()
72
+ match(body, {}, 'response')
73
+
74
+ child.kill('SIGINT')
75
+ delete process.env.A_CUSTOM_PORT
76
+ })
77
+
46
78
  test('default logger', async ({ equal, same, match, teardown }) => {
47
- const { child, url } = await start('-c', join(import.meta.url, '..', '..', 'fixtures', 'hello', 'no-server-logger.json'))
79
+ const { child, url } = await start(['-c', join(import.meta.url, '..', '..', 'fixtures', 'hello', 'no-server-logger.json')])
48
80
  match(url, /http:\/\/127.0.0.1:[0-9]+/)
49
81
  child.kill('SIGINT')
50
82
  })
51
83
 
52
84
  test('plugin options', async ({ equal, same, match, teardown }) => {
53
- const { child, url } = await start('-c', join(import.meta.url, '..', '..', 'fixtures', 'options', 'platformatic.service.yml'))
85
+ const { child, url } = await start(['-c', join(import.meta.url, '..', '..', 'fixtures', 'options', 'platformatic.service.yml')])
54
86
  const res = await request(`${url}`)
55
87
  equal(res.statusCode, 200)
56
88
  const body = await res.body.json()
@@ -62,7 +94,7 @@ test('plugin options', async ({ equal, same, match, teardown }) => {
62
94
  })
63
95
 
64
96
  test('https embedded pem', async ({ equal, same, match, teardown }) => {
65
- const { child, url } = await start('-c', join(import.meta.url, '..', '..', 'fixtures', 'https', 'embedded-pem.json'))
97
+ const { child, url } = await start(['-c', join(import.meta.url, '..', '..', 'fixtures', 'https', 'embedded-pem.json')])
66
98
 
67
99
  match(url, /https:\/\//)
68
100
  const res = await request(`${url}`)
@@ -76,7 +108,7 @@ test('https embedded pem', async ({ equal, same, match, teardown }) => {
76
108
  })
77
109
 
78
110
  test('https pem path', async ({ equal, same, match, teardown }) => {
79
- const { child, url } = await start('-c', join(import.meta.url, '..', '..', 'fixtures', 'https', 'pem-path.json'))
111
+ const { child, url } = await start(['-c', join(import.meta.url, '..', '..', 'fixtures', 'https', 'pem-path.json')])
80
112
 
81
113
  match(url, /https:\/\//)
82
114
  const res = await request(`${url}`)
@@ -45,7 +45,7 @@ test('should watch js files by default', async ({ equal, teardown, comment }) =>
45
45
  writeFile(pluginFilePath, createLoggingPlugin('v1'))
46
46
  ])
47
47
 
48
- const { child, url } = await start('-c', configFilePath)
48
+ const { child, url } = await start(['-c', configFilePath])
49
49
  teardown(() => child.kill('SIGINT'))
50
50
 
51
51
  await writeFile(pluginFilePath, createLoggingPlugin('v2', true))
@@ -97,7 +97,7 @@ test('should watch allowed file', async ({ comment, teardown }) => {
97
97
  writeFile(pluginFilePath, pluginCode)
98
98
  ])
99
99
 
100
- const { child } = await start('-c', configFilePath)
100
+ const { child } = await start(['-c', configFilePath])
101
101
  teardown(() => child.kill('SIGINT'))
102
102
 
103
103
  writeFile(jsonFilePath, 'RESTARTED')
@@ -132,7 +132,7 @@ test('should not watch ignored file', async ({ teardown, equal }) => {
132
132
  writeFile(pluginFilePath, createLoggingPlugin('v1'))
133
133
  ])
134
134
 
135
- const { child, url } = await start('-c', configFilePath)
135
+ const { child, url } = await start(['-c', configFilePath])
136
136
  teardown(() => child.kill('SIGINT'))
137
137
 
138
138
  await writeFile(pluginFilePath, createLoggingPlugin('v2', true))
@@ -170,7 +170,7 @@ test('should not loop forever when doing ESM', async ({ comment, fail }) => {
170
170
  writeFile(pluginFilePath, 'export default async (app) => {}')
171
171
  ])
172
172
 
173
- const { child } = await start('-c', configFilePath)
173
+ const { child } = await start(['-c', configFilePath])
174
174
 
175
175
  await sleep(1000)
176
176
 
@@ -239,7 +239,7 @@ test('should watch config file', async ({ comment, teardown }) => {
239
239
  writeFile(pluginFilePath, pluginCode)
240
240
  ])
241
241
 
242
- const { child } = await start('-c', configFilePath)
242
+ const { child } = await start(['-c', configFilePath])
243
243
  teardown(() => child.kill('SIGINT'))
244
244
 
245
245
  // We do not await
@@ -249,49 +249,6 @@ test('should watch config file', async ({ comment, teardown }) => {
249
249
  }
250
250
  })
251
251
 
252
- test('should not hot reload files with `--hot-reload false`', async ({ teardown, equal }) => {
253
- const tmpDir = await mkdtemp(join(os.tmpdir(), 'watch-'))
254
- const pluginFilePath = join(tmpDir, 'plugin.js')
255
- const configFilePath = join(tmpDir, 'platformatic.service.json')
256
-
257
- const config = {
258
- server: {
259
- logger: {
260
- level: 'info'
261
- },
262
- hostname: '127.0.0.1',
263
- port: 0
264
- },
265
- plugins: {
266
- paths: [pluginFilePath]
267
- }
268
- }
269
-
270
- await Promise.all([
271
- writeFile(configFilePath, JSON.stringify(config)),
272
- writeFile(pluginFilePath, createLoggingPlugin('v1'))
273
- ])
274
-
275
- const { child, url } = await start('-c', configFilePath, '--hot-reload', 'false')
276
- teardown(() => child.kill('SIGINT'))
277
-
278
- {
279
- const res = await request(`${url}/version`)
280
- const version = await res.body.text()
281
- equal(version, 'v1')
282
- }
283
-
284
- await writeFile(pluginFilePath, createLoggingPlugin('v2', true))
285
-
286
- await sleep(5000)
287
-
288
- {
289
- const res = await request(`${url}/version`)
290
- const version = await res.body.text()
291
- equal(version, 'v1')
292
- }
293
- })
294
-
295
252
  test('should not fail when updating wrong config', async ({ equal, teardown, comment }) => {
296
253
  const tmpDir = await mkdtemp(join(os.tmpdir(), 'watch-'))
297
254
  comment(`using ${tmpDir}`)
@@ -309,7 +266,7 @@ test('should not fail when updating wrong config', async ({ equal, teardown, com
309
266
  plugins: {
310
267
  paths: [pluginFilePath]
311
268
  },
312
- watch: false
269
+ watch: true
313
270
  }
314
271
 
315
272
  await Promise.all([
@@ -317,13 +274,13 @@ test('should not fail when updating wrong config', async ({ equal, teardown, com
317
274
  writeFile(pluginFilePath, createLoggingPlugin('v1', true))
318
275
  ])
319
276
 
320
- const { child, url } = await start('-c', configFilePath)
277
+ const { child, url } = await start(['-c', configFilePath])
321
278
  teardown(() => child.kill('SIGINT'))
322
279
 
323
280
  writeFile(configFilePath, 'this is not a valid config')
324
281
 
325
282
  for await (const log of child.ndj) {
326
- if (log.msg === 'error reloading the configuration') break
283
+ if (log.msg === 'failed to reload server') break
327
284
  }
328
285
 
329
286
  const res = await request(`${url}/version`)
@@ -3,7 +3,6 @@
3
3
  require('./helper')
4
4
  const { test } = require('tap')
5
5
  const { buildServer } = require('..')
6
- const { loadConfig } = require('../lib/load-config')
7
6
  const { request } = require('undici')
8
7
  const { join } = require('path')
9
8
  const os = require('os')
@@ -295,30 +294,3 @@ test('config reloads', async ({ teardown, equal, pass, same }) => {
295
294
  same(await res.body.text(), 'ciao mondo', 'response')
296
295
  }
297
296
  })
298
-
299
- test('can merge provided config with default config', async ({ plan, same }) => {
300
- plan(6)
301
- const configFile = join(__dirname, 'fixtures', 'custom-port-placeholder.json')
302
- const defaultConfig = {
303
- mergeDefaults: true,
304
- onMissingEnv (key) {
305
- same(key, 'A_CUSTOM_PORT')
306
- return '42'
307
- }
308
- }
309
-
310
- {
311
- const config = await loadConfig({}, ['-c', configFile], defaultConfig)
312
- same(config.configManager.current.server.port, 42)
313
- // This comes from the default config.
314
- same(config.configManager.schemaOptions.useDefaults, true)
315
- }
316
-
317
- {
318
- defaultConfig.mergeDefaults = false
319
- const config = await loadConfig({}, ['-c', configFile], defaultConfig)
320
- same(config.configManager.current.server.port, 42)
321
- // This comes from the default config.
322
- same(config.configManager.schemaOptions.useDefaults, undefined)
323
- }
324
- })
@@ -0,0 +1,7 @@
1
+ {
2
+ "server": {
3
+ "hostname": "{HOSTNAME}",
4
+ "port": "{PORT}",
5
+ "pluginTimeout": "10"
6
+ }
7
+ }
@@ -24,6 +24,10 @@ async function inventory (fastify, opts) {
24
24
  prefix: opts.prefix
25
25
  }
26
26
  })
27
+
28
+ fastify.get('/foo', async function (req) {
29
+ return req.foo
30
+ })
27
31
  }
28
32
 
29
33
  export default fp(inventory)
@@ -0,0 +1,3 @@
1
+ export default async function foo (app) {
2
+ app.decorateRequest('foo', 'bar')
3
+ }
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ module.exports = async function (app) {
4
+ app.addHook('onRequest', async () => {
5
+ throw new TypeError('kaboom')
6
+ })
7
+ }
8
+
9
+ module.exports[Symbol.for('skip-override')] = true
@@ -31,3 +31,37 @@ test('customize service', async ({ teardown, equal }) => {
31
31
  equal(res.statusCode, 200)
32
32
  equal(body, 'hello world')
33
33
  })
34
+
35
+ test('catch errors from the other side', async ({ teardown, equal, same }) => {
36
+ async function myApp (app, opts) {
37
+ await platformaticService(app, opts, [async function (app) {
38
+ app.get('/', () => 'hello world')
39
+ }])
40
+ }
41
+
42
+ const app = await buildServer({
43
+ server: {
44
+ hostname: '127.0.0.1',
45
+ port: 0
46
+ },
47
+ plugins: {
48
+ paths: [{
49
+ path: require.resolve('./fixtures/other-side.js')
50
+ }]
51
+ }
52
+ }, myApp)
53
+
54
+ teardown(async () => {
55
+ await app.close()
56
+ })
57
+ await app.start()
58
+
59
+ const res = await (request(app.url))
60
+ const body = await res.body.json()
61
+ equal(res.statusCode, 500)
62
+ same(body, {
63
+ statusCode: 500,
64
+ error: 'Internal Server Error',
65
+ message: 'kaboom'
66
+ })
67
+ })
package/lib/start.mjs DELETED
@@ -1,105 +0,0 @@
1
- import { buildServer } from '../index.js'
2
- import close from 'close-with-grace'
3
- import { loadConfig, generateDefaultConfig } from './load-config.js'
4
- import { addLoggerToTheConfig } from './utils.js'
5
-
6
- // TODO make sure coverage is reported for Windows
7
- // Currently C8 is not reporting it
8
- /* c8 ignore start */
9
-
10
- function defaultConfig () {
11
- const _defaultConfig = generateDefaultConfig()
12
- return {
13
- watch: true,
14
- ..._defaultConfig
15
- }
16
- }
17
-
18
- export function buildStart (_loadConfig, _buildServer, _configManagerConfig) {
19
- return async function start (_args) {
20
- const _defaultConfig = _configManagerConfig ?? defaultConfig()
21
- const { configManager, args } = await _loadConfig({}, _args, _defaultConfig)
22
-
23
- const config = configManager.current
24
-
25
- // Disable hot reload from the CLI
26
- if (args.hotReload === false && configManager.current.plugins) {
27
- configManager.current.plugins.hotReload = false
28
- }
29
- addLoggerToTheConfig(config)
30
-
31
- const _transformConfig = configManager._transformConfig.bind(configManager)
32
- configManager._transformConfig = function () {
33
- const config = configManager.current
34
- if (args.hotReload === false && config.plugins) {
35
- config.plugins.hotReload = false
36
- }
37
- addLoggerToTheConfig(config)
38
- return _transformConfig(config)
39
- }
40
-
41
- let app = null
42
-
43
- try {
44
- // Set the location of the config
45
- app = await _buildServer({ ...config, configManager })
46
-
47
- await app.start()
48
- // TODO: this log is used in the start command. Should be replaced
49
- app.log.info({ url: app.url })
50
- } catch (err) {
51
- // TODO route this to a logger
52
- console.error(err)
53
- process.exit(1)
54
- }
55
-
56
- // Ignore from CI because SIGUSR2 is not available
57
- // on Windows
58
- process.on('SIGUSR2', function () {
59
- app.log.info('reloading configuration')
60
- safeRestart(app)
61
- return false
62
- })
63
-
64
- close(async ({ signal, err }) => {
65
- // Windows does not support trapping signals
66
- if (err) {
67
- app.log.error({
68
- err: {
69
- message: err.message,
70
- stack: err.stack
71
- }
72
- }, 'exiting')
73
- } else if (signal) {
74
- app.log.info({ signal }, 'received signal')
75
- }
76
-
77
- await app.close()
78
- })
79
- }
80
- }
81
-
82
- const start = buildStart(loadConfig, buildServer)
83
-
84
- async function safeRestart (app) {
85
- try {
86
- await app.restart()
87
- } catch (err) {
88
- app.log.error({
89
- err: {
90
- message: err.message,
91
- stack: err.stack
92
- }
93
- }, 'failed to reload server')
94
- }
95
- }
96
-
97
- export default function (args) {
98
- start(args).catch(exit)
99
- }
100
-
101
- function exit (err) {
102
- console.error(err)
103
- process.exit(1)
104
- }
105
- /* c8 ignore stop */