@platformatic/service 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/fixtures/hello/no-server-logger.json +2 -2
  2. package/fixtures/hello/platformatic.service.json +2 -2
  3. package/fixtures/options/platformatic.service.yml +5 -4
  4. package/index.js +64 -68
  5. package/lib/compile.js +63 -88
  6. package/lib/config.js +2 -11
  7. package/lib/load-config.js +6 -2
  8. package/lib/sandbox-wrapper.js +26 -0
  9. package/lib/schema.js +34 -48
  10. package/lib/start.mjs +97 -69
  11. package/lib/utils.js +4 -0
  12. package/package.json +19 -19
  13. package/service.mjs +2 -2
  14. package/test/autoload.test.js +33 -21
  15. package/test/cli/compile.test.mjs +12 -3
  16. package/test/cli/gen-schema.test.mjs +4 -2
  17. package/test/cli/watch.test.mjs +59 -15
  18. package/test/config.test.js +70 -50
  19. package/test/fixtures/bad-typescript-plugin/dist/tsconfig.tsbuildinfo +1 -0
  20. package/test/fixtures/bad-typescript-plugin/platformatic.service.json +3 -5
  21. package/test/fixtures/directories/platformatic.service.json +2 -2
  22. package/test/fixtures/not-load.service.json +2 -3
  23. package/test/fixtures/typescript-plugin/platformatic.service.json +3 -5
  24. package/test/fixtures/typescript-plugin-nocompile/platformatic.service.json +3 -6
  25. package/test/load-and-reload-files.test.js +20 -20
  26. package/test/routes.test.js +3 -13
  27. package/test/schema.test.js +12 -0
  28. package/test/tmp/typescript-plugin-clone-1/platformatic.service.json +3 -5
  29. package/test/tmp/typescript-plugin-clone-2/platformatic.service.json +3 -6
  30. package/test/tmp/typescript-plugin-clone-3/dist/tsconfig.tsbuildinfo +1 -0
  31. package/test/tmp/typescript-plugin-clone-3/platformatic.service.json +3 -5
  32. package/test/tmp/typescript-plugin-clone-4/dist/tsconfig.tsbuildinfo +1 -0
  33. package/test/tmp/typescript-plugin-clone-4/platformatic.service.json +3 -5
  34. package/test/tmp/typescript-plugin-clone-5/platformatic.service.json +3 -5
  35. package/test/tmp/typescript-plugin-clone-6/platformatic.service.json +3 -6
  36. package/test/utils.test.js +8 -1
  37. package/lib/autoload-wrapper.js +0 -11
package/lib/start.mjs CHANGED
@@ -9,89 +9,114 @@ import { addLoggerToTheConfig } from './utils.js'
9
9
  // TODO make sure coverage is reported for Windows
10
10
  // Currently C8 is not reporting it
11
11
  /* c8 ignore start */
12
- async function start (_args) {
13
- const { configManager } = await loadConfig({}, _args, { watch: true })
14
12
 
15
- const config = configManager.current
13
+ export function buildStart (_loadConfig, _buildServer) {
14
+ return async function start (_args) {
15
+ const { configManager, args } = await _loadConfig({}, _args, { watch: true })
16
16
 
17
- // Set the logger if not present
18
- addLoggerToTheConfig(config)
17
+ const config = configManager.current
19
18
 
20
- if (
21
- config.plugin?.typescript !== undefined &&
22
- config.plugin?.watch !== false &&
23
- config.plugin?.typescript?.build !== false
24
- ) {
25
- try {
26
- await compileWatch(dirname(configManager.fullPath))
27
- } catch (error) {
28
- console.error(error)
29
- process.exit(1)
19
+ // Set the logger if not present
20
+ addLoggerToTheConfig(config)
21
+
22
+ if (
23
+ config.plugins?.typescript &&
24
+ config.plugins?.watch !== false
25
+ ) {
26
+ try {
27
+ await compileWatch(dirname(configManager.fullPath))
28
+ } catch (error) {
29
+ // TODO route this to a logger
30
+ console.error(error)
31
+ process.exit(1)
32
+ }
30
33
  }
31
- } else if (config.plugin?.typescript !== undefined && config.plugin?.typescript?.build === false) {
32
- // we don't have the logger here, shall we create one just for this message?
33
- console.log(`TS build is disabled, expecting compiled js files in ${config.plugin.typescript.outDir} folder`)
34
- }
35
34
 
36
- // Set the location of the config
37
- const server = await buildServer({
38
- ...config,
39
- configManager
40
- })
35
+ let server = null
41
36
 
42
- server.app.platformatic.configManager = configManager
43
- server.app.platformatic.config = config
44
- configManager.on('update', (newConfig) => onConfigUpdated(newConfig, server))
37
+ // Disable hot reload from the CLI
38
+ if (args.hotReload === false && configManager.current.plugins) {
39
+ configManager.current.plugins.hotReload = false
40
+ }
45
41
 
46
- if (
47
- config.plugin !== undefined &&
48
- config.watch !== false
49
- ) {
50
- await startFileWatching(server)
51
- }
42
+ try {
43
+ // Set the location of the config
44
+ server = await _buildServer({
45
+ ...config,
46
+ configManager
47
+ })
48
+ } catch (err) {
49
+ // TODO route this to a logger
50
+ console.error(err)
51
+ process.exit(1)
52
+ }
52
53
 
53
- await server.listen()
54
+ server.app.platformatic.configManager = configManager
55
+ server.app.platformatic.config = config
56
+ configManager.on('update', (newConfig) => {
57
+ if (args.hotReload === false && configManager.current.plugins) {
58
+ configManager.current.plugins.hotReload = false
59
+ }
60
+ onConfigUpdated(newConfig, server)
61
+ })
54
62
 
55
- configManager.on('error', function (err) {
56
- server.app.log.error({
57
- err
58
- }, 'error reloading the configuration')
59
- })
63
+ if (
64
+ config.plugins !== undefined &&
65
+ config.watch !== false
66
+ ) {
67
+ await startFileWatching(server, args.hotReload)
68
+ }
60
69
 
61
- // Ignore from CI because SIGUSR2 is not available
62
- // on Windows
63
- process.on('SIGUSR2', function () {
64
- server.app.log.info('reloading configuration')
65
- server.restart()
66
- .catch((err) => {
70
+ try {
71
+ await server.listen()
72
+ } catch (err) {
73
+ server.app.log.error({ err })
74
+ process.exit(1)
75
+ }
76
+
77
+ configManager.on('error', function (err) {
78
+ server.app.log.error({
79
+ err
80
+ }, 'error reloading the configuration')
81
+ })
82
+
83
+ // Ignore from CI because SIGUSR2 is not available
84
+ // on Windows
85
+ process.on('SIGUSR2', function () {
86
+ server.app.log.info('reloading configuration')
87
+ server.restart()
88
+ .catch((err) => {
89
+ server.app.log.error({
90
+ err: {
91
+ message: err.message,
92
+ stack: err.stack
93
+ }
94
+ }, 'failed to restart')
95
+ })
96
+ return false
97
+ })
98
+
99
+ close(async ({ signal, err }) => {
100
+ // Windows does not support trapping signals
101
+ if (err) {
67
102
  server.app.log.error({
68
103
  err: {
69
104
  message: err.message,
70
105
  stack: err.stack
71
106
  }
72
- }, 'failed to restart')
73
- })
74
- return false
75
- })
76
-
77
- close(async ({ signal, err }) => {
78
- // Windows does not support trapping signals
79
- if (err) {
80
- server.app.log.error({
81
- err: {
82
- message: err.message,
83
- stack: err.stack
84
- }
85
- }, 'exiting')
86
- } else if (signal) {
87
- server.app.log.info({ signal }, 'received signal')
88
- }
107
+ }, 'exiting')
108
+ } else if (signal) {
109
+ server.app.log.info({ signal }, 'received signal')
110
+ }
89
111
 
90
- await server.stop()
91
- })
112
+ await server.stop()
113
+ })
114
+ }
92
115
  }
93
116
 
94
- async function startFileWatching (server) {
117
+ const start = buildStart(loadConfig, buildServer)
118
+
119
+ async function startFileWatching (server, hotReload) {
95
120
  const configManager = server.app.platformatic.configManager
96
121
  const config = configManager.current
97
122
 
@@ -101,7 +126,7 @@ async function startFileWatching (server) {
101
126
  watchIgnore: config.watch?.ignore
102
127
  })
103
128
  fileWatcher.on('update', () => {
104
- onFilesUpdated(server)
129
+ onFilesUpdated(server, hotReload)
105
130
  })
106
131
  fileWatcher.startWatching()
107
132
 
@@ -138,21 +163,24 @@ async function onConfigUpdated (newConfig, server) {
138
163
  }, 'failed to reload config')
139
164
  } finally {
140
165
  if (
141
- newConfig.plugin !== undefined &&
142
- newConfig.plugin.watch !== false
166
+ newConfig.plugins !== undefined &&
167
+ newConfig.plugins.watch !== false
143
168
  ) {
144
169
  await startFileWatching(server)
145
170
  }
146
171
  }
147
172
  }
148
173
 
149
- async function onFilesUpdated (server) {
174
+ async function onFilesUpdated (server, hotReload) {
150
175
  // Reload the config as well, otherwise we will have problems
151
176
  // in case the files watcher triggers the config watcher too
152
177
  const configManager = server.app.platformatic.configManager
153
178
  try {
154
179
  server.app.log.debug('files changed')
155
180
  await configManager.parse()
181
+ if (hotReload === false && configManager.current.plugins) {
182
+ configManager.current.plugins.hotReload = false
183
+ }
156
184
  await server.restart(configManager.current)
157
185
  } catch (err) {
158
186
  // TODO: test this
package/lib/utils.js CHANGED
@@ -42,6 +42,10 @@ function addLoggerToTheConfig (config) {
42
42
  /* c8 ignore stop */
43
43
 
44
44
  function getJSPluginPath (configPath, tsPluginPath, compileDir) {
45
+ if (tsPluginPath.endsWith('js')) {
46
+ return tsPluginPath
47
+ }
48
+
45
49
  const tsPluginRelativePath = relative(dirname(configPath), tsPluginPath)
46
50
  const jsPluginRelativePath = join(
47
51
  dirname(tsPluginRelativePath),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/service",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "author": "Matteo Collina <hello@matteocollina.com>",
@@ -15,27 +15,27 @@
15
15
  "homepage": "https://github.com/platformatic/platformatic#readme",
16
16
  "devDependencies": {
17
17
  "bindings": "^1.5.0",
18
- "c8": "^7.12.0",
18
+ "c8": "^7.13.0",
19
19
  "snazzy": "^9.0.0",
20
20
  "split2": "^4.1.0",
21
21
  "standard": "^17.0.0",
22
22
  "strip-ansi": "^7.0.1",
23
- "tap": "^16.3.2",
24
- "typescript": "^4.9.4",
25
- "undici": "^5.14.0",
26
- "vscode-json-languageservice": "^5.1.3",
23
+ "tap": "^16.3.4",
24
+ "typescript": "^4.9.5",
25
+ "undici": "^5.20.0",
26
+ "vscode-json-languageservice": "^5.3.0",
27
27
  "why-is-node-running": "^2.2.2",
28
- "yaml": "^2.1.3"
28
+ "yaml": "^2.2.1"
29
29
  },
30
30
  "dependencies": {
31
31
  "@fastify/accepts": "^4.1.0",
32
- "@fastify/autoload": "^5.6.0",
32
+ "@fastify/autoload": "^5.7.1",
33
33
  "@fastify/basic-auth": "^5.0.0",
34
34
  "@fastify/cors": "^8.2.0",
35
35
  "@fastify/deepmerge": "^1.3.0",
36
36
  "@fastify/restartable": "^1.4.0",
37
- "@fastify/static": "^6.6.0",
38
- "@fastify/swagger": "^8.2.1",
37
+ "@fastify/static": "^6.9.0",
38
+ "@fastify/swagger": "^8.3.1",
39
39
  "@fastify/under-pressure": "^8.2.0",
40
40
  "close-with-grace": "^1.1.0",
41
41
  "commist": "^3.2.0",
@@ -43,19 +43,19 @@
43
43
  "env-schema": "^5.2.0",
44
44
  "es-main": "^1.2.0",
45
45
  "execa": "^7.0.0",
46
- "fastify": "^4.10.2",
47
- "fastify-metrics": "^10.0.0",
48
- "fastify-plugin": "^4.4.0",
46
+ "fastify": "^4.13.0",
47
+ "fastify-metrics": "^10.0.3",
48
+ "fastify-plugin": "^4.5.0",
49
49
  "fastify-sandbox": "^0.11.0",
50
50
  "graphql": "^16.6.0",
51
51
  "help-me": "^4.2.0",
52
- "minimist": "^1.2.7",
53
- "pino": "^8.8.0",
54
- "pino-pretty": "^9.1.1",
52
+ "minimist": "^1.2.8",
53
+ "pino": "^8.11.0",
54
+ "pino-pretty": "^9.3.0",
55
55
  "rfdc": "^1.3.0",
56
- "ua-parser-js": "^1.0.32",
57
- "@platformatic/config": "0.16.0",
58
- "@platformatic/utils": "0.16.0"
56
+ "ua-parser-js": "^1.0.33",
57
+ "@platformatic/utils": "0.17.1",
58
+ "@platformatic/config": "0.17.1"
59
59
  },
60
60
  "standard": {
61
61
  "ignore": [
package/service.mjs CHANGED
@@ -9,7 +9,7 @@ import { join } from 'desm'
9
9
  import { generateJsonSchemaConfig } from './lib/gen-schema.js'
10
10
 
11
11
  import start from './lib/start.mjs'
12
- import { compile } from './lib/compile.js'
12
+ import { compileCmd } from './lib/compile.js'
13
13
 
14
14
  const help = helpMe({
15
15
  dir: join(import.meta.url, 'help'),
@@ -23,7 +23,7 @@ program.register('help', help.toStdout)
23
23
  program.register('help start', help.toStdout.bind(null, ['start']))
24
24
 
25
25
  program.register('start', start)
26
- program.register('compile', compile)
26
+ program.register('compile', compileCmd)
27
27
  program.register('schema config', generateJsonSchemaConfig)
28
28
  program.register('schema', help.toStdout.bind(null, ['schema']))
29
29
 
@@ -12,8 +12,8 @@ test('autoload & filesystem based routing / watch disabled', async ({ teardown,
12
12
  hostname: '127.0.0.1',
13
13
  port: 0
14
14
  },
15
- plugin: {
16
- path: join(__dirname, 'fixtures', 'directories', 'routes')
15
+ plugins: {
16
+ paths: [join(__dirname, 'fixtures', 'directories', 'routes')]
17
17
  },
18
18
  watch: false,
19
19
  metrics: false
@@ -51,8 +51,8 @@ test('autoload & filesystem based routing / watch enabled', async ({ teardown, e
51
51
  hostname: '127.0.0.1',
52
52
  port: 0
53
53
  },
54
- plugin: {
55
- path: join(__dirname, 'fixtures', 'directories', 'routes')
54
+ plugins: {
55
+ paths: [join(__dirname, 'fixtures', 'directories', 'routes')]
56
56
  },
57
57
  watch: true,
58
58
  metrics: false
@@ -90,11 +90,13 @@ test('multiple files', async ({ teardown, equal }) => {
90
90
  hostname: '127.0.0.1',
91
91
  port: 0
92
92
  },
93
- plugin: [{
94
- path: join(__dirname, 'fixtures', 'directories', 'plugins')
95
- }, {
96
- path: join(__dirname, 'fixtures', 'directories', 'routes')
97
- }],
93
+ plugins: {
94
+ paths: [{
95
+ path: join(__dirname, 'fixtures', 'directories', 'plugins')
96
+ }, {
97
+ path: join(__dirname, 'fixtures', 'directories', 'routes')
98
+ }]
99
+ },
98
100
  watch: true,
99
101
  metrics: false
100
102
  }
@@ -138,11 +140,13 @@ test('multiple files / watch false', async ({ teardown, equal }) => {
138
140
  hostname: '127.0.0.1',
139
141
  port: 0
140
142
  },
141
- plugin: [{
142
- path: join(__dirname, 'fixtures', 'directories', 'plugins')
143
- }, {
144
- path: join(__dirname, 'fixtures', 'directories', 'routes')
145
- }],
143
+ plugins: {
144
+ paths: [{
145
+ path: join(__dirname, 'fixtures', 'directories', 'plugins')
146
+ }, {
147
+ path: join(__dirname, 'fixtures', 'directories', 'routes')
148
+ }]
149
+ },
146
150
  watch: false,
147
151
  metrics: false
148
152
  }
@@ -186,7 +190,9 @@ test('autoload & filesystem based routing / watch disabled / no object', async (
186
190
  hostname: '127.0.0.1',
187
191
  port: 0
188
192
  },
189
- plugin: join(__dirname, 'fixtures', 'directories', 'routes'),
193
+ plugins: {
194
+ paths: [join(__dirname, 'fixtures', 'directories', 'routes')]
195
+ },
190
196
  watch: false,
191
197
  metrics: false
192
198
  }
@@ -223,7 +229,9 @@ test('autoload & filesystem based routing / watch enabled / no object', async ({
223
229
  hostname: '127.0.0.1',
224
230
  port: 0
225
231
  },
226
- plugin: join(__dirname, 'fixtures', 'directories', 'routes'),
232
+ plugins: {
233
+ paths: [join(__dirname, 'fixtures', 'directories', 'routes')]
234
+ },
227
235
  watch: true,
228
236
  metrics: false
229
237
  }
@@ -260,7 +268,9 @@ test('multiple files / no object', async ({ teardown, equal }) => {
260
268
  hostname: '127.0.0.1',
261
269
  port: 0
262
270
  },
263
- plugin: [join(__dirname, 'fixtures', 'directories', 'plugins'), join(__dirname, 'fixtures', 'directories', 'routes')],
271
+ plugins: {
272
+ paths: [join(__dirname, 'fixtures', 'directories', 'plugins'), join(__dirname, 'fixtures', 'directories', 'routes')]
273
+ },
264
274
  watch: true,
265
275
  metrics: false
266
276
  }
@@ -304,10 +314,12 @@ test('multiple files / watch false / no object', async ({ teardown, equal }) =>
304
314
  hostname: '127.0.0.1',
305
315
  port: 0
306
316
  },
307
- plugin: [
308
- join(__dirname, 'fixtures', 'directories', 'plugins'),
309
- join(__dirname, 'fixtures', 'directories', 'routes')
310
- ],
317
+ plugins: {
318
+ paths: [
319
+ join(__dirname, 'fixtures', 'directories', 'plugins'),
320
+ join(__dirname, 'fixtures', 'directories', 'routes')
321
+ ]
322
+ },
311
323
  watch: false,
312
324
  metrics: false
313
325
  }
@@ -37,7 +37,7 @@ t.test('should compile typescript plugin', async (t) => {
37
37
  t.pass()
38
38
  })
39
39
 
40
- t.test('should compile typescript plugin even if build is `false`', async (t) => {
40
+ t.test('should compile typescript plugin even if typescript is `false`', async (t) => {
41
41
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
42
42
  const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-2')
43
43
 
@@ -84,8 +84,10 @@ t.test('should compile typescript plugin with start command', async (t) => {
84
84
 
85
85
  const splitter = split()
86
86
  child.stdout.pipe(splitter)
87
+ child.stderr.pipe(process.stderr)
87
88
 
88
89
  for await (const data of splitter) {
90
+ console.log(data)
89
91
  const sanitized = stripAnsi(data)
90
92
  if (sanitized.includes('Typescript plugin loaded')) {
91
93
  t.pass()
@@ -102,6 +104,8 @@ t.test('should not compile bad typescript plugin', async (t) => {
102
104
  await execa('node', [cliPath, 'compile'], { cwd })
103
105
  t.fail('should not compile bad typescript plugin')
104
106
  } catch (err) {
107
+ t.comment(err.stdout)
108
+ t.comment(err.stderr)
105
109
  t.equal(err.stdout.includes('Found 1 error in plugin.ts'), true)
106
110
  }
107
111
 
@@ -129,6 +133,8 @@ t.test('missing tsconfig file', async (t) => {
129
133
  await execa('node', [cliPath, 'compile'], { cwd })
130
134
  t.fail('should not compile typescript plugin')
131
135
  } catch (err) {
136
+ t.comment(err.stdout)
137
+ t.comment(err.stderr)
132
138
  t.equal(err.stdout.includes('The tsconfig.json file was not found.'), true)
133
139
  }
134
140
 
@@ -148,7 +154,7 @@ t.test('start command should not compile typescript plugin with errors', async (
148
154
  await childProcess
149
155
  t.fail('should not compile bad typescript plugin')
150
156
  } catch (err) {
151
- t.equal(err.stdout.includes('Found 1 error'), true)
157
+ t.equal(err.stderr.includes('Found 1 error'), true)
152
158
  childProcess.kill('SIGINT')
153
159
  }
154
160
 
@@ -177,11 +183,12 @@ t.test('should not compile typescript plugin with start without tsconfig', async
177
183
  t.teardown(() => child.kill('SIGINT'))
178
184
  t.fail('should not compile typescript plugin with start without tsconfig')
179
185
  } catch (err) {
186
+ t.comment(err.stdout)
180
187
  t.equal(err.stdout.includes('The tsconfig.json file was not found.'), true)
181
188
  }
182
189
  })
183
190
 
184
- t.test('start command should not compile typescript if `build` is false', async (t) => {
191
+ t.test('start command should not compile typescript if `typescript` is false', async (t) => {
185
192
  const testDir = path.join(urlDirname(import.meta.url), '..', 'fixtures', 'typescript-plugin-nocompile')
186
193
  const cwd = path.join(urlDirname(import.meta.url), '..', 'tmp', 'typescript-plugin-clone-6')
187
194
 
@@ -234,8 +241,10 @@ t.test('should compile typescript plugin with start command with different cwd',
234
241
 
235
242
  const splitter = split()
236
243
  child.stdout.pipe(splitter)
244
+ child.stderr.pipe(process.stderr)
237
245
 
238
246
  for await (const data of splitter) {
247
+ console.log(data)
239
248
  const sanitized = stripAnsi(data)
240
249
  if (sanitized.includes('Typescript plugin loaded')) {
241
250
  t.pass()
@@ -6,6 +6,8 @@ import { generateJsonSchemaConfig } from '../../lib/gen-schema.js'
6
6
  import { join } from 'path'
7
7
  import jsonLanguageService from 'vscode-json-languageservice'
8
8
 
9
+ const pkg = JSON.parse(await fs.readFile('../../package.json', 'utf8'))
10
+
9
11
  test('generateJsonSchemaConfig generates the file', async (t) => {
10
12
  const tmpDir = await mkdtempSync(join(tmpdir(), 'test-create-platformatic-'))
11
13
  process.chdir(tmpDir)
@@ -17,7 +19,7 @@ test('generateJsonSchemaConfig generates the file', async (t) => {
17
19
  t.has(required, ['server'])
18
20
  t.has(additionalProperties, { watch: {} })
19
21
  const { $id, type } = schema
20
- t.equal($id, 'https://schemas.platformatic.dev/service')
22
+ t.equal($id, `https://platformatic.dev/schemas/v${pkg.version}/service`)
21
23
  t.equal(type, 'object')
22
24
 
23
25
  const languageservice = jsonLanguageService.getLanguageService({
@@ -29,7 +31,7 @@ test('generateJsonSchemaConfig generates the file', async (t) => {
29
31
  languageservice.configure({ allowComments: false, schemas: [{ fileMatch: ['*.data.json'], uri: $id }] })
30
32
 
31
33
  const jsonContent = `{
32
- "$schema": "https://schemas.platformatic.dev/service",
34
+ "$schema": "https://platformatic.dev/schemas/v${pkg.version}/service",
33
35
  "server": {
34
36
  "hostname": "127.0.0.1",
35
37
  "port": 3000
@@ -30,8 +30,8 @@ test('should watch js files by default', async ({ equal, teardown }) => {
30
30
  port: 0
31
31
  },
32
32
  watch: true,
33
- plugin: {
34
- path: pluginFilePath
33
+ plugins: {
34
+ paths: [pluginFilePath]
35
35
  }
36
36
  }
37
37
 
@@ -69,8 +69,8 @@ test('should watch allowed file', async ({ comment, teardown }) => {
69
69
  watch: {
70
70
  allow: ['*.js', '*.json']
71
71
  },
72
- plugin: {
73
- path: pluginFilePath
72
+ plugins: {
73
+ paths: [pluginFilePath]
74
74
  }
75
75
  }
76
76
 
@@ -115,8 +115,8 @@ test('should not watch ignored file', async ({ teardown, equal }) => {
115
115
  watch: {
116
116
  ignore: [basename(pluginFilePath)]
117
117
  },
118
- plugin: {
119
- path: pluginFilePath
118
+ plugins: {
119
+ paths: [pluginFilePath]
120
120
  }
121
121
  }
122
122
 
@@ -152,8 +152,8 @@ test('should not loop forever when doing ESM', async ({ comment, fail }) => {
152
152
  watch: {
153
153
  ignore: [basename(pluginFilePath)]
154
154
  },
155
- plugin: {
156
- path: pluginFilePath
155
+ plugins: {
156
+ paths: [pluginFilePath]
157
157
  }
158
158
  }
159
159
 
@@ -196,8 +196,8 @@ test('should watch config file', async ({ comment, teardown }) => {
196
196
  watch: {
197
197
  allow: ['*.js', '*.json']
198
198
  },
199
- plugin: {
200
- path: pluginFilePath
199
+ plugins: {
200
+ paths: [pluginFilePath]
201
201
  }
202
202
  }
203
203
 
@@ -209,11 +209,13 @@ test('should watch config file', async ({ comment, teardown }) => {
209
209
  hostname: '127.0.0.1',
210
210
  port: 0
211
211
  },
212
- plugin: {
213
- path: pluginFilePath,
214
- options: {
215
- log: true
216
- }
212
+ plugins: {
213
+ paths: [{
214
+ path: pluginFilePath,
215
+ options: {
216
+ log: true
217
+ }
218
+ }]
217
219
  }
218
220
  }
219
221
 
@@ -238,3 +240,45 @@ test('should watch config file', async ({ comment, teardown }) => {
238
240
  if (log.msg === 'RESTARTED') break
239
241
  }
240
242
  })
243
+
244
+ test('should not hot reload files with `--hot-reload false`', async ({ teardown, equal }) => {
245
+ const tmpDir = await mkdtemp(join(os.tmpdir(), 'watch-'))
246
+ const pluginFilePath = join(tmpDir, 'plugin.js')
247
+ const configFilePath = join(tmpDir, 'platformatic.service.json')
248
+
249
+ const config = {
250
+ server: {
251
+ logger: {
252
+ level: 'info'
253
+ },
254
+ hostname: '127.0.0.1',
255
+ port: 0
256
+ },
257
+ plugins: {
258
+ paths: [pluginFilePath]
259
+ }
260
+ }
261
+
262
+ await Promise.all([
263
+ writeFile(configFilePath, JSON.stringify(config)),
264
+ writeFile(pluginFilePath, createLoggingPlugin('v1'))
265
+ ])
266
+
267
+ const { child, url } = await start('-c', configFilePath, '--hot-reload', 'false')
268
+ teardown(() => child.kill('SIGINT'))
269
+
270
+ {
271
+ const res = await request(`${url}/version`)
272
+ const version = await res.body.text()
273
+ equal(version, 'v1')
274
+ }
275
+
276
+ await writeFile(pluginFilePath, createLoggingPlugin('v2'))
277
+ await sleep(5000)
278
+
279
+ {
280
+ const res = await request(`${url}/version`)
281
+ const version = await res.body.text()
282
+ equal(version, 'v1')
283
+ }
284
+ })