@platformatic/service 0.33.1 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +25 -9
- package/lib/plugins/sandbox-wrapper.js +60 -5
- package/lib/schema.js +42 -0
- package/package.json +7 -7
- package/test/autoload.test.js +142 -0
- package/test/config.test.js +20 -1
- package/test/fixtures/directories/routes/foo/baz/index2.js +11 -0
- package/test/fixtures/directories/routes/oof/auto.hooks.js +5 -0
- package/test/fixtures/directories/routes/oof/index.js +7 -0
- package/test/telemetry.test.js +3 -3
package/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { isKeyEnabled } = require('@platformatic/utils')
|
|
4
|
+
const { readFile } = require('fs/promises')
|
|
5
|
+
const { dirname, join } = require('path')
|
|
4
6
|
|
|
5
7
|
const compiler = require('./lib/compile')
|
|
6
8
|
const setupCors = require('./lib/plugins/cors')
|
|
@@ -65,13 +67,6 @@ async function platformaticService (app, opts, toLoad = []) {
|
|
|
65
67
|
await app.register(loadPlugins)
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
if (isKeyEnabled('watch', config)) {
|
|
69
|
-
// If file watching is enabled here, that means the service was started
|
|
70
|
-
// without the runtime because the runtime explicitly disables watching on
|
|
71
|
-
// services that it starts. Warn the user that things will not go as planned.
|
|
72
|
-
app.log.warn('service was started with file watching enabled but watching is only available via the runtime')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
70
|
if (config.server.cors) {
|
|
76
71
|
app.register(setupCors, config.server.cors)
|
|
77
72
|
}
|
|
@@ -98,11 +93,32 @@ platformaticService.configManagerConfig = {
|
|
|
98
93
|
allErrors: true,
|
|
99
94
|
strict: false
|
|
100
95
|
},
|
|
101
|
-
transformConfig () {
|
|
96
|
+
async transformConfig () {
|
|
102
97
|
// Set watch to true by default. This is not possible
|
|
103
98
|
// to do in the schema, because it is uses an anyOf.
|
|
104
99
|
if (this.current.watch === undefined) {
|
|
105
|
-
this.current.watch =
|
|
100
|
+
this.current.watch = { enabled: false }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (typeof this.current.watch !== 'object') {
|
|
104
|
+
this.current.watch = { enabled: this.current.watch || false }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const typescript = this.current.plugins?.typescript
|
|
108
|
+
if (typescript) {
|
|
109
|
+
let outDir = typescript.outDir
|
|
110
|
+
if (outDir === undefined) {
|
|
111
|
+
let tsConfigFile = typescript.tsConfigFile || 'tsconfig.json'
|
|
112
|
+
tsConfigFile = join(dirname(this.fullPath), tsConfigFile)
|
|
113
|
+
try {
|
|
114
|
+
const tsConfig = JSON.parse(await readFile(tsConfigFile, 'utf8'))
|
|
115
|
+
outDir = tsConfig.compilerOptions.outDir
|
|
116
|
+
} catch {}
|
|
117
|
+
outDir ||= 'dist'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.current.watch.ignore ||= []
|
|
121
|
+
this.current.watch.ignore.push(outDir + '/**/*')
|
|
106
122
|
}
|
|
107
123
|
}
|
|
108
124
|
}
|
|
@@ -10,11 +10,21 @@ module.exports = fp(async function (app, opts) {
|
|
|
10
10
|
plugin = { path: plugin, encapsulate: true }
|
|
11
11
|
}
|
|
12
12
|
if ((await stat(plugin.path)).isDirectory()) {
|
|
13
|
+
const patternOptions = patternOptionsFromPlugin(plugin)
|
|
14
|
+
|
|
13
15
|
app.register(autoload, {
|
|
14
16
|
dir: plugin.path,
|
|
15
17
|
encapsulate: plugin.encapsulate !== false,
|
|
16
18
|
maxDepth: plugin.maxDepth,
|
|
17
|
-
options: plugin.options
|
|
19
|
+
options: plugin.options,
|
|
20
|
+
autoHooks: plugin.autoHooks,
|
|
21
|
+
cascadeHooks: plugin.cascadeHooks,
|
|
22
|
+
overwriteHooks: plugin.overwriteHooks,
|
|
23
|
+
routeParams: plugin.routeParams,
|
|
24
|
+
forceESM: plugin.forceESM,
|
|
25
|
+
ignoreFilter: plugin.ignoreFilter,
|
|
26
|
+
matchFilter: plugin.matchFilter,
|
|
27
|
+
...patternOptions
|
|
18
28
|
})
|
|
19
29
|
} else {
|
|
20
30
|
let loaded = await import(`file://${plugin.path}`)
|
|
@@ -23,13 +33,58 @@ module.exports = fp(async function (app, opts) {
|
|
|
23
33
|
loaded = loaded.default
|
|
24
34
|
}
|
|
25
35
|
|
|
26
|
-
let skipOverride
|
|
27
36
|
if (plugin.encapsulate === false) {
|
|
28
|
-
skipOverride = loaded[Symbol.for('skip-override')]
|
|
37
|
+
const skipOverride = loaded[Symbol.for('skip-override')]
|
|
29
38
|
loaded[Symbol.for('skip-override')] = true
|
|
39
|
+
await app.register(loaded, plugin.options)
|
|
40
|
+
loaded[Symbol.for('skip-override')] = skipOverride
|
|
41
|
+
} else {
|
|
42
|
+
await app.register(loaded, plugin.options)
|
|
30
43
|
}
|
|
31
|
-
await app.register(loaded, plugin.options)
|
|
32
|
-
loaded[Symbol.for('skip-override')] = skipOverride
|
|
33
44
|
}
|
|
34
45
|
}
|
|
35
46
|
})
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates an object for pattern specific options. This ensures that
|
|
50
|
+
* only configurations that have been provided are included in the
|
|
51
|
+
* final result. This prevents 'cannot read properties of undefined'
|
|
52
|
+
* errors when undefined configs are provided to the underlying
|
|
53
|
+
* @fastify/autoload plugin.
|
|
54
|
+
*/
|
|
55
|
+
function patternOptionsFromPlugin (plugin) {
|
|
56
|
+
const config = {}
|
|
57
|
+
|
|
58
|
+
// Expected keys for autoload plugin options that expect regexp patterns
|
|
59
|
+
const patternOptionKeys = ['ignorePattern', 'indexPattern', 'autoHooksPattern']
|
|
60
|
+
|
|
61
|
+
for (const key of patternOptionKeys) {
|
|
62
|
+
const pattern = plugin[key]
|
|
63
|
+
|
|
64
|
+
// If pattern key not found in plugin object, move on
|
|
65
|
+
if (!pattern) {
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Build an instance of a RegExp. If this comes back undefined,
|
|
70
|
+
// then an invalid value was provided. Move on.
|
|
71
|
+
const regExpPattern = stringPatternToRegExp(pattern)
|
|
72
|
+
if (!regExpPattern) {
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// We have a valid RegExp so add the option to the config to pass along to
|
|
77
|
+
// autoload.
|
|
78
|
+
config[key] = regExpPattern
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return config
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function stringPatternToRegExp (stringPattern) {
|
|
85
|
+
try {
|
|
86
|
+
return new RegExp(stringPattern)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
return undefined
|
|
89
|
+
}
|
|
90
|
+
}
|
package/lib/schema.js
CHANGED
|
@@ -328,12 +328,21 @@ const server = {
|
|
|
328
328
|
},
|
|
329
329
|
cors
|
|
330
330
|
},
|
|
331
|
+
additionalProperties: false,
|
|
331
332
|
required: ['hostname', 'port']
|
|
332
333
|
}
|
|
333
334
|
|
|
334
335
|
const watch = {
|
|
335
336
|
type: 'object',
|
|
336
337
|
properties: {
|
|
338
|
+
enabled: {
|
|
339
|
+
default: true,
|
|
340
|
+
anyOf: [{
|
|
341
|
+
type: 'boolean'
|
|
342
|
+
}, {
|
|
343
|
+
type: 'string'
|
|
344
|
+
}]
|
|
345
|
+
},
|
|
337
346
|
allow: {
|
|
338
347
|
type: 'array',
|
|
339
348
|
items: {
|
|
@@ -379,6 +388,39 @@ const plugins = {
|
|
|
379
388
|
maxDepth: {
|
|
380
389
|
type: 'integer'
|
|
381
390
|
},
|
|
391
|
+
autoHooks: {
|
|
392
|
+
type: 'boolean'
|
|
393
|
+
},
|
|
394
|
+
autoHooksPattern: {
|
|
395
|
+
type: 'string'
|
|
396
|
+
},
|
|
397
|
+
cascadeHooks: {
|
|
398
|
+
type: 'boolean'
|
|
399
|
+
},
|
|
400
|
+
overwriteHooks: {
|
|
401
|
+
type: 'boolean'
|
|
402
|
+
},
|
|
403
|
+
routeParams: {
|
|
404
|
+
type: 'boolean'
|
|
405
|
+
},
|
|
406
|
+
forceESM: {
|
|
407
|
+
type: 'boolean'
|
|
408
|
+
},
|
|
409
|
+
ignoreFilter: {
|
|
410
|
+
type: 'string'
|
|
411
|
+
},
|
|
412
|
+
matchFilter: {
|
|
413
|
+
type: 'string'
|
|
414
|
+
},
|
|
415
|
+
ignorePattern: {
|
|
416
|
+
type: 'string'
|
|
417
|
+
},
|
|
418
|
+
scriptPattern: {
|
|
419
|
+
type: 'string'
|
|
420
|
+
},
|
|
421
|
+
indexPattern: {
|
|
422
|
+
type: 'string'
|
|
423
|
+
},
|
|
382
424
|
options: {
|
|
383
425
|
type: 'object',
|
|
384
426
|
additionalProperties: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/service",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -65,12 +65,12 @@
|
|
|
65
65
|
"pino-pretty": "^10.0.0",
|
|
66
66
|
"rfdc": "^1.3.0",
|
|
67
67
|
"ua-parser-js": "^1.0.35",
|
|
68
|
-
"@platformatic/client": "0.
|
|
69
|
-
"@platformatic/config": "0.
|
|
70
|
-
"@platformatic/swagger-ui-theme": "0.
|
|
71
|
-
"@platformatic/types": "0.
|
|
72
|
-
"@platformatic/utils": "0.
|
|
73
|
-
"@platformatic/telemetry": "0.
|
|
68
|
+
"@platformatic/client": "0.34.1",
|
|
69
|
+
"@platformatic/config": "0.34.1",
|
|
70
|
+
"@platformatic/swagger-ui-theme": "0.34.1",
|
|
71
|
+
"@platformatic/types": "0.34.1",
|
|
72
|
+
"@platformatic/utils": "0.34.1",
|
|
73
|
+
"@platformatic/telemetry": "0.34.1"
|
|
74
74
|
},
|
|
75
75
|
"standard": {
|
|
76
76
|
"ignore": [
|
package/test/autoload.test.js
CHANGED
|
@@ -342,3 +342,145 @@ test('disable encapsulation for a single file / different order', async ({ teard
|
|
|
342
342
|
equal(body, 'bar')
|
|
343
343
|
}
|
|
344
344
|
})
|
|
345
|
+
|
|
346
|
+
test('autoload with ignorePattern, indexPattern and autoHooksPattern options', async ({ teardown, equal }) => {
|
|
347
|
+
const config = {
|
|
348
|
+
server: {
|
|
349
|
+
hostname: '127.0.0.1',
|
|
350
|
+
port: 0
|
|
351
|
+
},
|
|
352
|
+
plugins: {
|
|
353
|
+
paths: [
|
|
354
|
+
{
|
|
355
|
+
path: join(__dirname, 'fixtures', 'directories', 'routes'),
|
|
356
|
+
|
|
357
|
+
// Ignore the bar.js which should return a 404 for requests made to /bar
|
|
358
|
+
ignorePattern: '^.*(?:bar).js$',
|
|
359
|
+
|
|
360
|
+
// Set index2.js as the index file which sets the root as /index2
|
|
361
|
+
indexPattern: '^index2(?:.js)$',
|
|
362
|
+
|
|
363
|
+
// Override default autohooks.js with auto.hooks.js which overrides
|
|
364
|
+
// the response body
|
|
365
|
+
autoHooksPattern: '^auto.hooks.js$',
|
|
366
|
+
autoHooks: true
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
},
|
|
370
|
+
watch: false,
|
|
371
|
+
metrics: false
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const app = await buildServer(config)
|
|
375
|
+
teardown(async () => {
|
|
376
|
+
await app.close()
|
|
377
|
+
})
|
|
378
|
+
await app.start()
|
|
379
|
+
|
|
380
|
+
{
|
|
381
|
+
const res = await request(`${app.url}/`)
|
|
382
|
+
equal(res.statusCode, 200, 'status code')
|
|
383
|
+
const body = await res.body.json()
|
|
384
|
+
equal(body.hello, 'from root', 'body')
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
{
|
|
388
|
+
const res = await request(`${app.url}/foo/bar`)
|
|
389
|
+
equal(res.statusCode, 404, 'status code')
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
{
|
|
393
|
+
const res = await request(`${app.url}/foo/baz/index2`)
|
|
394
|
+
equal(res.statusCode, 200, 'status code')
|
|
395
|
+
const body = await res.body.json()
|
|
396
|
+
equal(body.hello, 'from baz with index2.js', 'body')
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
{
|
|
400
|
+
const res = await request(`${app.url}/oof`)
|
|
401
|
+
equal(res.statusCode, 200, 'status code')
|
|
402
|
+
const body = await res.body.json()
|
|
403
|
+
equal(body.hello, 'from auto.hooks.js', 'body')
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
test('autoload with INVALID ignorePattern, indexPattern and autoHooksPattern options', async ({ teardown, equal }) => {
|
|
408
|
+
const config = {
|
|
409
|
+
server: {
|
|
410
|
+
hostname: '127.0.0.1',
|
|
411
|
+
port: 0
|
|
412
|
+
},
|
|
413
|
+
plugins: {
|
|
414
|
+
paths: [
|
|
415
|
+
{
|
|
416
|
+
path: join(__dirname, 'fixtures', 'directories', 'routes'),
|
|
417
|
+
ignorePattern: '***',
|
|
418
|
+
indexPattern: '***terrible)))_pattern',
|
|
419
|
+
autoHooksPattern: ''
|
|
420
|
+
}
|
|
421
|
+
]
|
|
422
|
+
},
|
|
423
|
+
watch: false,
|
|
424
|
+
metrics: false
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const app = await buildServer(config)
|
|
428
|
+
teardown(async () => {
|
|
429
|
+
await app.close()
|
|
430
|
+
})
|
|
431
|
+
await app.start()
|
|
432
|
+
|
|
433
|
+
{
|
|
434
|
+
const res = await request(`${app.url}/`)
|
|
435
|
+
equal(res.statusCode, 200, 'status code')
|
|
436
|
+
}
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
test('loads encapsulated plugin twice', async ({ teardown, equal, strictSame }) => {
|
|
440
|
+
const config = {
|
|
441
|
+
server: {
|
|
442
|
+
hostname: '127.0.0.1',
|
|
443
|
+
port: 0,
|
|
444
|
+
// Windows CI is slow
|
|
445
|
+
pluginTimeout: 60 * 1000
|
|
446
|
+
},
|
|
447
|
+
service: {
|
|
448
|
+
openapi: true
|
|
449
|
+
},
|
|
450
|
+
plugins: {
|
|
451
|
+
paths: [{
|
|
452
|
+
path: join(__dirname, 'fixtures', 'directories', 'routes')
|
|
453
|
+
}, {
|
|
454
|
+
path: join(__dirname, 'fixtures', 'directories', 'plugins', 'decorator.js')
|
|
455
|
+
}]
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
{
|
|
460
|
+
// First time plugin is loaded from file
|
|
461
|
+
const app = await buildServer(config)
|
|
462
|
+
teardown(async () => {
|
|
463
|
+
await app.close()
|
|
464
|
+
})
|
|
465
|
+
await app.start()
|
|
466
|
+
|
|
467
|
+
const res = await request(`${app.url}/foo/with-decorator`)
|
|
468
|
+
equal(res.statusCode, 200, 'status code')
|
|
469
|
+
const body = await res.body.json()
|
|
470
|
+
strictSame(body, { hello: 'bar' })
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
{
|
|
474
|
+
// Second time plugin is loaded from cache
|
|
475
|
+
const app = await buildServer(config)
|
|
476
|
+
teardown(async () => {
|
|
477
|
+
await app.close()
|
|
478
|
+
})
|
|
479
|
+
await app.start()
|
|
480
|
+
|
|
481
|
+
const res = await request(`${app.url}/foo/with-decorator`)
|
|
482
|
+
equal(res.statusCode, 200, 'status code')
|
|
483
|
+
const body = await res.body.json()
|
|
484
|
+
strictSame(body, { hello: 'bar' })
|
|
485
|
+
}
|
|
486
|
+
})
|
package/test/config.test.js
CHANGED
|
@@ -6,7 +6,7 @@ const { buildServer } = require('..')
|
|
|
6
6
|
const { request } = require('undici')
|
|
7
7
|
const { join } = require('path')
|
|
8
8
|
const os = require('os')
|
|
9
|
-
const { writeFile } = require('fs/promises')
|
|
9
|
+
const { writeFile, rm } = require('fs/promises')
|
|
10
10
|
|
|
11
11
|
test('config reloads', async ({ teardown, equal, pass, same }) => {
|
|
12
12
|
const file = join(os.tmpdir(), `${process.pid}-1.js`)
|
|
@@ -295,3 +295,22 @@ test('config reloads', async ({ teardown, equal, pass, same }) => {
|
|
|
295
295
|
same(await res.body.text(), 'ciao mondo', 'response')
|
|
296
296
|
}
|
|
297
297
|
})
|
|
298
|
+
|
|
299
|
+
test('do not watch typescript outDir', async ({ teardown, equal, pass, same }) => {
|
|
300
|
+
process.env.PLT_CLIENT_URL = 'http://localhost:3042'
|
|
301
|
+
const targetDir = join(__dirname, '..', 'fixtures', 'hello-client-ts')
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
await rm(join(targetDir, 'dist'), { recursive: true })
|
|
305
|
+
} catch {}
|
|
306
|
+
|
|
307
|
+
const app = await buildServer(join(targetDir, 'platformatic.service.json'))
|
|
308
|
+
teardown(async () => {
|
|
309
|
+
await app.close()
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
same(app.platformatic.configManager.current.watch, {
|
|
313
|
+
enabled: false,
|
|
314
|
+
ignore: ['dist/**/*']
|
|
315
|
+
})
|
|
316
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Used for providing an alternative `index.js` naming convention
|
|
5
|
+
* when specified from the autoload `indexPattern` property.
|
|
6
|
+
*/
|
|
7
|
+
module.exports = async function (fastify, opts) {
|
|
8
|
+
fastify.get('/index2', async function (request, reply) {
|
|
9
|
+
return { hello: 'from baz with index2.js' }
|
|
10
|
+
})
|
|
11
|
+
}
|
package/test/telemetry.test.js
CHANGED
|
@@ -81,7 +81,7 @@ test('should setup telemetry if configured', async ({ teardown, equal, pass, sam
|
|
|
81
81
|
equal(finishedSpans.length, 1)
|
|
82
82
|
const span = finishedSpans[0]
|
|
83
83
|
equal(span.name, 'GET /')
|
|
84
|
-
equal(span.attributes['
|
|
85
|
-
equal(span.attributes['
|
|
86
|
-
equal(span.attributes['
|
|
84
|
+
equal(span.attributes['http.request.method'], 'GET')
|
|
85
|
+
equal(span.attributes['url.path'], '/')
|
|
86
|
+
equal(span.attributes['http.response.status_code'], 200)
|
|
87
87
|
})
|