@platformatic/service 0.12.0 → 0.13.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.js CHANGED
@@ -22,6 +22,16 @@ function createServerConfig (config) {
22
22
  return serverConfig
23
23
  }
24
24
 
25
+ function originToRegexp (origin) {
26
+ if (typeof origin === 'object') {
27
+ if (origin.regexp) {
28
+ origin = new RegExp(origin.regexp)
29
+ }
30
+ }
31
+
32
+ return origin
33
+ }
34
+
25
35
  async function platformaticService (app, opts, toLoad = []) {
26
36
  if (isKeyEnabled('metrics', opts)) {
27
37
  app.register(require('./lib/metrics-plugin'), opts.metrics)
@@ -62,6 +72,15 @@ async function platformaticService (app, opts, toLoad = []) {
62
72
 
63
73
  // Enable CORS
64
74
  if (opts.cors) {
75
+ let origin = opts.cors.origin
76
+ if (Array.isArray(origin)) {
77
+ origin = origin.map(originToRegexp)
78
+ } else {
79
+ origin = originToRegexp(origin)
80
+ }
81
+
82
+ opts.cors.origin = origin
83
+
65
84
  app.register(require('@fastify/cors'), opts.cors)
66
85
  }
67
86
  if (isKeyEnabled('healthCheck', opts)) {
@@ -127,17 +146,51 @@ async function loadPlugin (app, config, pluginOptions) {
127
146
  }
128
147
 
129
148
  platformaticService[Symbol.for('skip-override')] = true
149
+ platformaticService.schema = schema
150
+
151
+ function adjustConfigBeforeMerge (cm) {
152
+ // This function and adjustConfigAfterMerge() are needed because there are
153
+ // edge cases that deepmerge() does not handle properly. This code does not
154
+ // live in the generic config manager because that object is not aware of
155
+ // these schema dependent details.
156
+ const stash = new Map()
157
+
158
+ // If a pino instance is passed as the logger, it will contain a child()
159
+ // function that is not enumerable. Non-enumerables are not copied by
160
+ // deepmerge(), so stash the logger here.
161
+ /* c8 ignore next 5 */
162
+ if (typeof cm.server?.logger?.child === 'function' &&
163
+ !Object.prototype.propertyIsEnumerable.call(cm.server.logger, 'child')) {
164
+ stash.set('server.logger', cm.server.logger)
165
+ cm.server.logger = null
166
+ }
167
+
168
+ return stash
169
+ }
170
+
171
+ function adjustConfigAfterMerge (options, stash) {
172
+ // Restore any config that needed to be stashed prior to merging.
173
+ const pinoLogger = stash.get('server.logger')
174
+
175
+ /* c8 ignore next 4 */
176
+ if (pinoLogger) {
177
+ options.server.logger = pinoLogger
178
+ options.configManager.current.server.logger = pinoLogger
179
+ }
180
+ }
130
181
 
131
182
  async function buildServer (options, app = platformaticService) {
132
183
  if (!options.configManager) {
133
184
  // instantiate a new config manager from current options
134
185
  const cm = new ConfigManager({
135
186
  source: options,
136
- schema
187
+ schema: app?.schema ?? schema
137
188
  })
138
189
  await cm.parseAndValidate()
190
+ const stash = adjustConfigBeforeMerge(cm.current)
139
191
  options = deepmerge({}, options, cm.current)
140
192
  options.configManager = cm
193
+ adjustConfigAfterMerge(options, stash)
141
194
  }
142
195
  const serverConfig = createServerConfig(options)
143
196
 
package/lib/schema.js CHANGED
@@ -11,8 +11,27 @@ const cors = {
11
11
  {
12
12
  type: 'array',
13
13
  items: {
14
- type: 'string'
14
+ anyOf: [{
15
+ type: 'string'
16
+ }, {
17
+ type: 'object',
18
+ properties: {
19
+ regexp: {
20
+ type: 'string'
21
+ }
22
+ },
23
+ required: ['regexp']
24
+ }]
15
25
  }
26
+ },
27
+ {
28
+ type: 'object',
29
+ properties: {
30
+ regexp: {
31
+ type: 'string'
32
+ }
33
+ },
34
+ required: ['regexp']
16
35
  }
17
36
  ]
18
37
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/service",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "author": "Matteo Collina <hello@matteocollina.com>",
@@ -55,8 +55,8 @@
55
55
  "pino": "^8.8.0",
56
56
  "pino-pretty": "^9.1.1",
57
57
  "rfdc": "^1.3.0",
58
- "@platformatic/config": "0.12.0",
59
- "@platformatic/utils": "0.12.0"
58
+ "@platformatic/config": "0.13.0",
59
+ "@platformatic/utils": "0.13.0"
60
60
  },
61
61
  "standard": {
62
62
  "ignore": [
@@ -184,3 +184,31 @@ test('config reloads from a written file from a route', async ({ teardown, equal
184
184
  same(await res.body.text(), 'ciao mondo', 'response')
185
185
  }
186
186
  })
187
+
188
+ test('config is adjusted to handle custom loggers', async (t) => {
189
+ const options = {
190
+ server: {
191
+ hostname: '127.0.0.1',
192
+ port: 0,
193
+ logger: {
194
+ info () {},
195
+ error () {},
196
+ debug () {},
197
+ fatal () {},
198
+ warn () {},
199
+ trace () {}
200
+ }
201
+ }
202
+ }
203
+
204
+ let called = false
205
+ Object.defineProperty(options.server.logger, 'child', {
206
+ value: function child () {
207
+ called = true
208
+ },
209
+ enumerable: false
210
+ })
211
+
212
+ await buildServer(options)
213
+ t.equal(called, true)
214
+ })
package/test/cors.test.js CHANGED
@@ -47,14 +47,227 @@ test('CORS can be enabled', async ({ teardown, equal, pass, same }) => {
47
47
  })
48
48
  teardown(server.stop)
49
49
  await server.listen()
50
- const res = await (request(`${server.url}/_admin/login`, {
51
- method: 'OPTIONS',
52
- headers: {
53
- 'Access-Control-Request-Method': 'POST',
54
- Origin: 'https://foo.bar.org'
55
- }
56
- }))
57
- equal(res.statusCode, 204)
58
- equal(res.headers['access-control-allow-origin'], 'https://foo.bar.org')
59
- equal(res.headers['access-control-allow-methods'], 'GET, POST')
50
+
51
+ {
52
+ const res = await (request(`${server.url}/_admin/login`, {
53
+ method: 'OPTIONS',
54
+ headers: {
55
+ 'Access-Control-Request-Method': 'POST',
56
+ Origin: 'https://foo.bar.org'
57
+ }
58
+ }))
59
+ equal(res.statusCode, 204)
60
+ equal(res.headers['access-control-allow-origin'], 'https://foo.bar.org')
61
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
62
+ }
63
+ })
64
+
65
+ test('CORS with a regexp', async ({ teardown, equal, pass, same }) => {
66
+ const server = await buildServer({
67
+ server: {
68
+ hostname: '127.0.0.1',
69
+ port: 0,
70
+ cors: {
71
+ origin: {
72
+ regexp: 'https://[a-z-]*.deploy.space|https://platformatic.cloud'
73
+ },
74
+ methods: ['GET', 'POST']
75
+ }
76
+ },
77
+ metrics: false
78
+ }, async function (app, opts) {
79
+ app.register(platformaticService, opts)
80
+ app.post('/login', (req, reply) => {})
81
+ })
82
+ teardown(server.stop)
83
+ await server.listen()
84
+
85
+ {
86
+ const res = await (request(`${server.url}/_admin/login`, {
87
+ method: 'OPTIONS',
88
+ headers: {
89
+ 'Access-Control-Request-Method': 'POST',
90
+ Origin: 'https://platformatic.cloud'
91
+ }
92
+ }))
93
+ equal(res.statusCode, 204)
94
+ equal(res.headers['access-control-allow-origin'], 'https://platformatic.cloud')
95
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
96
+ }
97
+
98
+ {
99
+ const res = await (request(`${server.url}/_admin/login`, {
100
+ method: 'OPTIONS',
101
+ headers: {
102
+ 'Access-Control-Request-Method': 'POST',
103
+ Origin: 'https://foo.deploy.space'
104
+ }
105
+ }))
106
+ equal(res.statusCode, 204)
107
+ equal(res.headers['access-control-allow-origin'], 'https://foo.deploy.space')
108
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
109
+ }
110
+
111
+ {
112
+ const res = await (request(`${server.url}/_admin/login`, {
113
+ method: 'OPTIONS',
114
+ headers: {
115
+ 'Access-Control-Request-Method': 'POST',
116
+ Origin: 'https://foo.space'
117
+ }
118
+ }))
119
+ equal(res.statusCode, 204)
120
+ equal(res.headers['access-control-allow-origin'], undefined)
121
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
122
+ }
123
+ })
124
+
125
+ test('CORS with an array of strings', async ({ teardown, equal, pass, same }) => {
126
+ const server = await buildServer({
127
+ server: {
128
+ hostname: '127.0.0.1',
129
+ port: 0,
130
+ cors: {
131
+ origin: ['https://foo.deploy.space', 'https://platformatic.cloud'],
132
+ methods: ['GET', 'POST']
133
+ }
134
+ },
135
+ metrics: false
136
+ }, async function (app, opts) {
137
+ app.register(platformaticService, opts)
138
+ app.post('/login', (req, reply) => {})
139
+ })
140
+ teardown(server.stop)
141
+ await server.listen()
142
+
143
+ {
144
+ const res = await (request(`${server.url}/_admin/login`, {
145
+ method: 'OPTIONS',
146
+ headers: {
147
+ 'Access-Control-Request-Method': 'POST',
148
+ Origin: 'https://platformatic.cloud'
149
+ }
150
+ }))
151
+ equal(res.statusCode, 204)
152
+ equal(res.headers['access-control-allow-origin'], 'https://platformatic.cloud')
153
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
154
+ }
155
+
156
+ {
157
+ const res = await (request(`${server.url}/_admin/login`, {
158
+ method: 'OPTIONS',
159
+ headers: {
160
+ 'Access-Control-Request-Method': 'POST',
161
+ Origin: 'https://foo.deploy.space'
162
+ }
163
+ }))
164
+ equal(res.statusCode, 204)
165
+ equal(res.headers['access-control-allow-origin'], 'https://foo.deploy.space')
166
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
167
+ }
168
+
169
+ {
170
+ const res = await (request(`${server.url}/_admin/login`, {
171
+ method: 'OPTIONS',
172
+ headers: {
173
+ 'Access-Control-Request-Method': 'POST',
174
+ Origin: 'https://foo.cloud'
175
+ }
176
+ }))
177
+ equal(res.statusCode, 204)
178
+ equal(res.headers['access-control-allow-origin'], undefined)
179
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
180
+ }
181
+ })
182
+
183
+ test('CORS with an array and a regexp', async ({ teardown, equal, pass, same }) => {
184
+ const server = await buildServer({
185
+ server: {
186
+ hostname: '127.0.0.1',
187
+ port: 0,
188
+ cors: {
189
+ origin: [{
190
+ regexp: 'https://[a-z-]*.deploy.space'
191
+ }, 'https://platformatic.cloud'],
192
+ methods: ['GET', 'POST']
193
+ }
194
+ },
195
+ metrics: false
196
+ }, async function (app, opts) {
197
+ app.register(platformaticService, opts)
198
+ app.post('/login', (req, reply) => {})
199
+ })
200
+ teardown(server.stop)
201
+ await server.listen()
202
+
203
+ {
204
+ const res = await (request(`${server.url}/_admin/login`, {
205
+ method: 'OPTIONS',
206
+ headers: {
207
+ 'Access-Control-Request-Method': 'POST',
208
+ Origin: 'https://platformatic.cloud'
209
+ }
210
+ }))
211
+ equal(res.statusCode, 204)
212
+ equal(res.headers['access-control-allow-origin'], 'https://platformatic.cloud')
213
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
214
+ }
215
+
216
+ {
217
+ const res = await (request(`${server.url}/_admin/login`, {
218
+ method: 'OPTIONS',
219
+ headers: {
220
+ 'Access-Control-Request-Method': 'POST',
221
+ Origin: 'https://foo.deploy.space'
222
+ }
223
+ }))
224
+ equal(res.statusCode, 204)
225
+ equal(res.headers['access-control-allow-origin'], 'https://foo.deploy.space')
226
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
227
+ }
228
+
229
+ {
230
+ const res = await (request(`${server.url}/_admin/login`, {
231
+ method: 'OPTIONS',
232
+ headers: {
233
+ 'Access-Control-Request-Method': 'POST',
234
+ Origin: 'https://foo.cloud'
235
+ }
236
+ }))
237
+ equal(res.statusCode, 204)
238
+ equal(res.headers['access-control-allow-origin'], undefined)
239
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
240
+ }
241
+ })
242
+
243
+ test('CORS with a string', async ({ teardown, equal, pass, same }) => {
244
+ const server = await buildServer({
245
+ server: {
246
+ hostname: '127.0.0.1',
247
+ port: 0,
248
+ cors: {
249
+ origin: 'https://platformatic.cloud',
250
+ methods: ['GET', 'POST']
251
+ }
252
+ },
253
+ metrics: false
254
+ }, async function (app, opts) {
255
+ app.register(platformaticService, opts)
256
+ app.post('/login', (req, reply) => {})
257
+ })
258
+ teardown(server.stop)
259
+ await server.listen()
260
+
261
+ {
262
+ const res = await (request(`${server.url}/_admin/login`, {
263
+ method: 'OPTIONS',
264
+ headers: {
265
+ 'Access-Control-Request-Method': 'POST',
266
+ Origin: 'https://foo.cloud'
267
+ }
268
+ }))
269
+ equal(res.statusCode, 204)
270
+ equal(res.headers['access-control-allow-origin'], 'https://platformatic.cloud')
271
+ equal(res.headers['access-control-allow-methods'], 'GET, POST')
272
+ }
60
273
  })
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ function default_1(app) {
13
+ return __awaiter(this, void 0, void 0, function* () {
14
+ app.log.info('Typescript plugin loaded');
15
+ });
16
+ }
17
+ exports.default = default_1;
18
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../plugin.ts"],"names":[],"mappings":";;;;;;;;;;;AAEA,mBAA+B,GAAoB;;QACjD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;IAC1C,CAAC;CAAA;AAFD,4BAEC"}
@@ -9,7 +9,8 @@
9
9
  "plugin": {
10
10
  "path": "plugin.ts",
11
11
  "typescript": {
12
- "outDir": "dist"
12
+ "outDir": "dist",
13
+ "build": false
13
14
  }
14
15
  }
15
16
  }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "esModuleInterop": true,
5
+ "target": "es6",
6
+ "moduleResolution": "node",
7
+ "sourceMap": true,
8
+ "pretty": true,
9
+ "noEmitOnError": true,
10
+ "outDir": "dist"
11
+ },
12
+ "watchOptions": {
13
+ "watchFile": "fixedPollingInterval",
14
+ "watchDirectory": "fixedPollingInterval",
15
+ "fallbackPolling": "dynamicPriority",
16
+ "synchronousWatchDirectory": true,
17
+ "excludeDirectories": [
18
+ "**/node_modules",
19
+ "dist"
20
+ ]
21
+ }
22
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "server": {
3
+ "logger": {
4
+ "level": "info"
5
+ },
6
+ "hostname": "127.0.0.1",
7
+ "port": "0"
8
+ },
9
+ "plugin": {
10
+ "path": "plugin.ts",
11
+ "typescript": {
12
+ "outDir": "dist"
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,5 @@
1
+ import { FastifyInstance } from 'fastify'
2
+
3
+ export default async function (app: FastifyInstance) {
4
+ app.log.info('Typescript plugin loaded')
5
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "esModuleInterop": true,
5
+ "target": "es6",
6
+ "moduleResolution": "node",
7
+ "sourceMap": true,
8
+ "pretty": true,
9
+ "noEmitOnError": true,
10
+ "outDir": "dist"
11
+ },
12
+ "watchOptions": {
13
+ "watchFile": "fixedPollingInterval",
14
+ "watchDirectory": "fixedPollingInterval",
15
+ "fallbackPolling": "dynamicPriority",
16
+ "synchronousWatchDirectory": true,
17
+ "excludeDirectories": [
18
+ "**/node_modules",
19
+ "dist"
20
+ ]
21
+ }
22
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "server": {
3
+ "logger": {
4
+ "level": "info"
5
+ },
6
+ "hostname": "127.0.0.1",
7
+ "port": "0"
8
+ },
9
+ "plugin": {
10
+ "path": "plugin.ts",
11
+ "typescript": {
12
+ "outDir": "dist",
13
+ "build": false
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,5 @@
1
+ import { FastifyInstance } from 'fastify'
2
+
3
+ export default async function (app: FastifyInstance) {
4
+ app.log.info('Typescript plugin loaded')
5
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "esModuleInterop": true,
5
+ "target": "es6",
6
+ "moduleResolution": "node",
7
+ "sourceMap": true,
8
+ "pretty": true,
9
+ "noEmitOnError": true,
10
+ "outDir": "dist"
11
+ },
12
+ "watchOptions": {
13
+ "watchFile": "fixedPollingInterval",
14
+ "watchDirectory": "fixedPollingInterval",
15
+ "fallbackPolling": "dynamicPriority",
16
+ "synchronousWatchDirectory": true,
17
+ "excludeDirectories": [
18
+ "**/node_modules",
19
+ "dist"
20
+ ]
21
+ }
22
+ }