@platformatic/runtime 0.35.4 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,61 @@
1
+ 'use strict'
2
+
3
+ const { schema } = require('@platformatic/service')
4
+
5
+ /** @type {import('fastify').FastifyPluginAsync<{}>} */
6
+ async function foo (app) {
7
+ const text = app.platformatic.config.foo.text
8
+ app.get('/foo', async (request, reply) => {
9
+ return text
10
+ })
11
+ }
12
+
13
+ foo.configType = 'foo'
14
+ foo.schema = {
15
+ $id: 'https://example.com/schemas/foo.json',
16
+ title: 'Platformatic Service',
17
+ type: 'object',
18
+ properties: {
19
+ server: schema.server,
20
+ plugins: schema.plugins,
21
+ metrics: schema.metrics,
22
+ watch: {
23
+ anyOf: [schema.watch, {
24
+ type: 'boolean'
25
+ }, {
26
+ type: 'string'
27
+ }]
28
+ },
29
+ $schema: {
30
+ type: 'string'
31
+ },
32
+ module: {
33
+ type: 'string'
34
+ },
35
+ foo: {
36
+ type: 'object',
37
+ properties: {
38
+ text: {
39
+ type: 'string'
40
+ }
41
+ },
42
+ required: ['text']
43
+ }
44
+ },
45
+ additionalProperties: false,
46
+ required: ['server']
47
+ }
48
+
49
+ foo.configManagerConfig = {
50
+ schema: foo.schema,
51
+ envWhitelist: ['PORT', 'HOSTNAME'],
52
+ allowToWatch: ['.env'],
53
+ schemaOptions: {
54
+ useDefaults: true,
55
+ coerceTypes: true,
56
+ allErrors: true,
57
+ strict: false
58
+ }
59
+ }
60
+
61
+ module.exports = foo
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "foo",
3
+ "main": "foo.js"
4
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://example.com/schemas/foo.json",
3
+ "module": "foo",
4
+ "server": {
5
+ "port": 0,
6
+ "hostname": "127.0.0.1"
7
+ },
8
+ "foo": {
9
+ "text": "Hello World"
10
+ }
11
+ }
package/lib/app.js CHANGED
@@ -90,6 +90,7 @@ class PlatformaticApp {
90
90
  // If this is a restart, have the fastify server restart itself. If this
91
91
  // is not a restart, then create a new server.
92
92
  this.server = await buildServer({
93
+ app: this.config.app,
93
94
  ...config,
94
95
  configManager
95
96
  })
@@ -157,7 +158,7 @@ class PlatformaticApp {
157
158
 
158
159
  let _config
159
160
  try {
160
- _config = await loadConfig({}, ['-c', appConfig.config], null, {
161
+ _config = await loadConfig({}, ['-c', appConfig.config], {
161
162
  watch: true,
162
163
  onMissingEnv (key) {
163
164
  return appConfig.localServiceEnvVars.get(key)
package/lib/compile.js CHANGED
@@ -9,7 +9,7 @@ const pretty = require('pino-pretty')
9
9
  const { isatty } = require('node:tty')
10
10
 
11
11
  async function compile (argv, logger) {
12
- const { configManager, configType } = await loadConfig({}, argv, undefined, {
12
+ const { configManager, configType } = await loadConfig({}, argv, {
13
13
  watch: false
14
14
  })
15
15
 
@@ -33,7 +33,7 @@ async function compile (argv, logger) {
33
33
  for (const service of configManager.current.services) {
34
34
  const childLogger = logger.child({ name: service.id })
35
35
 
36
- const serviceConfig = await loadConfig({}, argv, undefined, {
36
+ const serviceConfig = await loadConfig({}, argv, {
37
37
  watch: false
38
38
  })
39
39
 
package/lib/start.js CHANGED
@@ -5,7 +5,7 @@ const { join } = require('node:path')
5
5
  const { pathToFileURL } = require('node:url')
6
6
  const { Worker } = require('node:worker_threads')
7
7
  const closeWithGrace = require('close-with-grace')
8
- const { loadConfig } = require('@platformatic/service')
8
+ const { loadConfig } = require('@platformatic/config')
9
9
  const { parseInspectorOptions, platformaticRuntime } = require('./config')
10
10
  const RuntimeApiClient = require('./api-client.js')
11
11
  const kLoaderFile = pathToFileURL(join(__dirname, 'loader.mjs')).href
@@ -1,112 +1,41 @@
1
1
  'use strict'
2
- const { resolve } = require('node:path')
3
- const parseArgs = require('minimist')
4
- const ConfigManager = require('@platformatic/config')
2
+ const { Store, loadConfig, printConfigValidationErrors } = require('@platformatic/config')
5
3
  const {
6
4
  platformaticService,
7
5
  buildServer,
8
- loadConfig,
9
- start,
10
- schema: serviceSchema
6
+ start
11
7
  } = require('@platformatic/service')
12
8
  const {
13
- schema: dbSchema,
14
9
  platformaticDB
15
10
  } = require('@platformatic/db')
16
11
  const {
17
- schema: composerSchema,
18
12
  platformaticComposer
19
13
  } = require('@platformatic/composer')
20
14
  const { buildServer: runtimeBuildServer } = require('./build-server')
21
15
  const { platformaticRuntime, wrapConfigInRuntimeConfig } = require('./config')
22
- const { schema: runtimeSchema } = require('./schema')
23
16
  const {
24
17
  start: runtimeStart,
25
18
  startWithConfig: runtimeStartWithConfig
26
19
  } = require('./start')
27
20
 
28
- const kSupportedAppTypes = new Set(['service', 'db', 'composer', 'runtime'])
21
+ const store = new Store()
22
+ store.add(platformaticService)
23
+ store.add(platformaticDB)
24
+ store.add(platformaticComposer)
25
+ store.add(platformaticRuntime)
29
26
 
30
- async function tryGetConfigTypeFromSchema (config) {
31
- /* c8 ignore next 6 - c8 is not seeing this as covered for some reason. */
32
- if (typeof config === 'string') {
33
- // Handle config file paths.
34
- const loadedConfig = await loadConfig({}, ['-c', config], platformaticService)
35
-
36
- config = loadedConfig.configManager.current
37
- }
38
-
39
- const schema = config?.$schema
40
-
41
- if (typeof schema !== 'string') {
42
- throw new Error('configuration is missing a schema')
43
- }
44
-
45
- const configType = schema.split('/').pop()
46
-
47
- if (!kSupportedAppTypes.has(configType)) {
48
- throw new Error(`unknown configuration type: '${configType}'`)
49
- }
50
-
51
- return configType
52
- }
53
-
54
- async function getConfigType (args = [], directory) {
55
- try {
56
- // The config type was not specified, so we need to figure it out.
57
- // Try to get the config file from the provided arguments.
58
- let { config } = parseArgs(args, { alias: { c: 'config' } })
59
-
60
- if (!config) {
61
- // Couldn't get the config file from the arguments, so look in the
62
- // provided directory (or current directory) for any recognized
63
- // config files.
64
- const searchDir = directory ?? process.cwd()
65
- const configFile = await ConfigManager.findConfigFile(searchDir)
66
-
67
- config = resolve(searchDir, configFile)
68
- }
69
-
70
- // At this point, we have the config file. However, several different
71
- // file formats are supported, so use the config manager to parse the
72
- // file (without worrying about the validity of the file). We can then
73
- // use the $schema field to determine the config type.
74
- const configManager = new ConfigManager({ source: config })
75
- const configString = await configManager.load()
76
- const parsedConfig = configManager._parser(configString)
77
-
78
- return await tryGetConfigTypeFromSchema(parsedConfig)
79
- } catch (err) {
80
- const configFiles = ConfigManager.listConfigFiles()
81
- const msg = `
82
- Missing config file!
83
- Be sure to have a config file with one of the following names:
84
- ${configFiles.map((s) => ' * ' + s).join('\n')}
85
- Alternatively run "npm create platformatic@latest" to generate a basic plt service config.
86
- `
87
-
88
- throw new Error(msg, { cause: err })
89
- }
90
- }
91
-
92
- async function getCurrentSchema (configType) {
93
- if (configType === 'service') {
94
- return serviceSchema.schema
95
- } else if (configType === 'db') {
96
- return dbSchema
97
- } else if (configType === 'composer') {
98
- return composerSchema
99
- } else if (configType === 'runtime') {
100
- return runtimeSchema
27
+ /* c8 ignore next 10 - for some reason c8 is not seeing this as covered. */
28
+ async function _buildServer (options) {
29
+ if (typeof options === 'string') {
30
+ const config = await _loadConfig({}, ['-c', options])
31
+ options = config.configManager.current
32
+ options.configManager = config.configManager
33
+ options.app = config.app
101
34
  }
102
35
 
103
- throw new Error(`unknown configuration type: '${configType}'`)
104
- }
36
+ const app = options.app
105
37
 
106
- /* c8 ignore next 10 - for some reason c8 is not seeing this as covered. */
107
- async function _buildServer (options) {
108
- const configType = await tryGetConfigTypeFromSchema(options)
109
- const app = getApp(configType)
38
+ delete options.app
110
39
 
111
40
  if (app === platformaticRuntime) {
112
41
  return runtimeBuildServer(options)
@@ -115,51 +44,26 @@ async function _buildServer (options) {
115
44
  return buildServer(options, app)
116
45
  }
117
46
 
118
- function getApp (configType) {
119
- if (configType === 'service') {
120
- return platformaticService
121
- } else if (configType === 'db') {
122
- return platformaticDB
123
- } else if (configType === 'composer') {
124
- return platformaticComposer
125
- } else if (configType === 'runtime') {
126
- return platformaticRuntime
127
- }
128
-
129
- throw new Error('unknown kind: ' + configType)
130
- }
131
-
132
- async function _loadConfig (minimistConfig, args, configType, overrides) {
133
- // If the config type was specified, then use that. Otherwise, compute it.
134
- if (typeof configType !== 'string') {
135
- configType = await getConfigType(args)
136
- }
137
-
138
- const app = getApp(configType)
139
- const res = await loadConfig(minimistConfig, args, app, overrides)
140
- res.configType = configType
141
- res.app = app
142
-
143
- return res
47
+ function _loadConfig (minimistConfig, args, overrides) {
48
+ return loadConfig(minimistConfig, args, store, overrides)
144
49
  }
145
50
 
146
51
  async function _start (args) {
147
- const configType = await getConfigType(args)
52
+ const config = await _loadConfig({}, args)
148
53
 
149
- if (configType === 'runtime') {
54
+ if (config.configType === 'runtime') {
150
55
  return runtimeStart(args)
151
56
  }
152
57
 
153
- return start(getApp(configType), args)
58
+ return start(config.app, args)
154
59
  }
155
60
 
156
61
  async function startCommand (args) {
157
62
  try {
158
- const configType = await getConfigType(args)
159
- const config = await _loadConfig({}, args, configType)
63
+ const config = await _loadConfig({}, args)
160
64
  let runtime
161
65
 
162
- if (configType === 'runtime') {
66
+ if (config.configType === 'runtime') {
163
67
  config.configManager.args = config.args
164
68
  runtime = await runtimeStartWithConfig(config.configManager)
165
69
  } else {
@@ -175,6 +79,19 @@ async function startCommand (args) {
175
79
  }
176
80
 
177
81
  function logErrorAndExit (err) {
82
+ if (err.filenames) {
83
+ console.error(`Missing config file!
84
+ Be sure to have a config file with one of the following names:
85
+
86
+ ${err.filenames.map((s) => ' * ' + s).join('\n')}
87
+
88
+ In alternative run "npm create platformatic@latest" to generate a basic plt service config.`)
89
+ process.exit(1)
90
+ } else if (err.validationErrors) {
91
+ printConfigValidationErrors(err)
92
+ process.exit(1)
93
+ }
94
+
178
95
  delete err?.stack
179
96
  console.error(err?.message)
180
97
 
@@ -187,10 +104,7 @@ function logErrorAndExit (err) {
187
104
 
188
105
  module.exports = {
189
106
  buildServer: _buildServer,
190
- getConfigType,
191
- getCurrentSchema,
192
107
  loadConfig: _loadConfig,
193
108
  start: _start,
194
- startCommand,
195
- getApp
109
+ startCommand
196
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/runtime",
3
- "version": "0.35.4",
3
+ "version": "0.36.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -24,8 +24,8 @@
24
24
  "standard": "^17.1.0",
25
25
  "tsd": "^0.28.1",
26
26
  "typescript": "^5.1.6",
27
- "@platformatic/sql-graphql": "0.35.4",
28
- "@platformatic/sql-mapper": "0.35.4"
27
+ "@platformatic/sql-graphql": "0.36.0",
28
+ "@platformatic/sql-mapper": "0.36.0"
29
29
  },
30
30
  "dependencies": {
31
31
  "@hapi/topo": "^6.0.2",
@@ -42,12 +42,12 @@
42
42
  "pino": "^8.14.1",
43
43
  "pino-pretty": "^10.0.0",
44
44
  "undici": "^5.22.1",
45
- "@platformatic/composer": "0.35.4",
46
- "@platformatic/config": "0.35.4",
47
- "@platformatic/db": "0.35.4",
48
- "@platformatic/service": "0.35.4",
49
- "@platformatic/telemetry": "0.35.4",
50
- "@platformatic/utils": "0.35.4"
45
+ "@platformatic/composer": "0.36.0",
46
+ "@platformatic/db": "0.36.0",
47
+ "@platformatic/config": "0.36.0",
48
+ "@platformatic/service": "0.36.0",
49
+ "@platformatic/telemetry": "0.36.0",
50
+ "@platformatic/utils": "0.36.0"
51
51
  },
52
52
  "standard": {
53
53
  "ignore": [
package/test/api.test.js CHANGED
@@ -4,7 +4,7 @@ const assert = require('node:assert')
4
4
  const { join } = require('node:path')
5
5
  const { test } = require('node:test')
6
6
 
7
- const { loadConfig } = require('@platformatic/service')
7
+ const { loadConfig } = require('@platformatic/config')
8
8
  const { buildServer, platformaticRuntime } = require('..')
9
9
  const fixturesDir = join(__dirname, '..', 'fixtures')
10
10
 
@@ -115,3 +115,13 @@ test('starts the inspector', async (t) => {
115
115
  assert(found)
116
116
  child.kill('SIGINT')
117
117
  })
118
+
119
+ test('stackable', async () => {
120
+ const config = join(import.meta.url, '..', '..', 'fixtures', 'stackables', 'platformatic.json')
121
+ const { child, url } = await start('start', '-c', config)
122
+ const res = await request(url + '/foo')
123
+
124
+ assert.strictEqual(res.statusCode, 200)
125
+ assert.deepStrictEqual(await res.body.text(), 'Hello World')
126
+ child.kill('SIGINT')
127
+ })
@@ -3,7 +3,8 @@
3
3
  const assert = require('node:assert')
4
4
  const { join } = require('node:path')
5
5
  const { test } = require('node:test')
6
- const { loadConfig, platformaticService } = require('@platformatic/service')
6
+ const { loadConfig } = require('@platformatic/config')
7
+ const { platformaticService } = require('@platformatic/service')
7
8
  const { parseInspectorOptions, platformaticRuntime } = require('../lib/config')
8
9
  const fixturesDir = join(__dirname, '..', 'fixtures')
9
10
 
@@ -6,7 +6,8 @@ const { join } = require('node:path')
6
6
  const { test } = require('node:test')
7
7
  const { MessageChannel } = require('node:worker_threads')
8
8
  const { request } = require('undici')
9
- const { loadConfig } = require('@platformatic/service')
9
+ const { loadConfig } = require('@platformatic/config')
10
+ const { platformaticDB } = require('@platformatic/db')
10
11
  const { buildServer, platformaticRuntime } = require('..')
11
12
  const { wrapConfigInRuntimeConfig } = require('../lib/config')
12
13
  const { startWithConfig } = require('../lib/start')
@@ -160,7 +161,7 @@ test('handles uncaught exceptions with db app', async (t) => {
160
161
 
161
162
  test('logs errors during db migrations', async (t) => {
162
163
  const configFile = join(fixturesDir, 'dbAppWithMigrationError', 'platformatic.db.json')
163
- const config = await loadConfig({}, ['-c', configFile], 'db')
164
+ const config = await loadConfig({}, ['-c', configFile], platformaticDB)
164
165
  const runtimeConfig = await wrapConfigInRuntimeConfig(config)
165
166
  const { port1, port2 } = new MessageChannel()
166
167
  runtimeConfig.current.loggingPort = port2
@@ -4,7 +4,7 @@ const assert = require('node:assert')
4
4
  const { request } = require('undici')
5
5
  const { test } = require('node:test')
6
6
  const { join } = require('node:path')
7
- const { loadConfig } = require('@platformatic/service')
7
+ const { loadConfig } = require('@platformatic/config')
8
8
  const { platformaticRuntime } = require('..')
9
9
  const { startWithConfig } = require('../lib/start')
10
10
  const fixturesDir = join(__dirname, '..', 'fixtures')
@@ -6,117 +6,10 @@ const { join } = require('node:path')
6
6
  const { test } = require('node:test')
7
7
  const {
8
8
  buildServer,
9
- getConfigType,
10
- getCurrentSchema,
11
9
  loadConfig
12
10
  } = require('../lib/unified-api')
13
- const { version } = require('../package.json')
14
11
  const fixturesDir = join(__dirname, '..', 'fixtures')
15
12
 
16
- test('getConfigType()', async (t) => {
17
- await t.test('throws if there is no $schema', async () => {
18
- const configFile = join(fixturesDir, 'configs', 'no-schema.config.json')
19
- let err
20
-
21
- try {
22
- await getConfigType(['-c', configFile])
23
- } catch (error) {
24
- err = error
25
- }
26
-
27
- assert(err)
28
- assert.strictEqual(err.cause.message, 'configuration is missing a schema')
29
- })
30
-
31
- await t.test('throws if the schema type is unsupported', async () => {
32
- const configFile = join(fixturesDir, 'configs', 'invalid-schema-type.config.json')
33
- let err
34
-
35
- try {
36
- await getConfigType(['-c', configFile])
37
- } catch (error) {
38
- err = error
39
- }
40
-
41
- assert(err)
42
- assert.strictEqual(err.cause.message, 'unknown configuration type: \'trickortreat\'')
43
- })
44
-
45
- await t.test('gets type from config via args', async () => {
46
- const configFile = join(fixturesDir, 'monorepo', 'serviceApp', 'platformatic.service.json')
47
- const type = await getConfigType(['-c', configFile])
48
-
49
- assert.strictEqual(type, 'service')
50
- })
51
-
52
- await t.test('gets type from config in provided directory', async () => {
53
- const configDir = join(fixturesDir, 'monorepo', 'serviceApp')
54
- const type = await getConfigType(undefined, configDir)
55
-
56
- assert.strictEqual(type, 'service')
57
- })
58
-
59
- await t.test('gets db type from config in cwd', async (t) => {
60
- const cwd = process.cwd()
61
-
62
- t.after(() => {
63
- process.chdir(cwd)
64
- })
65
-
66
- const configDir = join(fixturesDir, 'dbApp')
67
- process.chdir(configDir)
68
- const type = await getConfigType()
69
-
70
- assert.strictEqual(type, 'db')
71
- })
72
-
73
- await t.test('gets composer type from config in cwd', async (t) => {
74
- const cwd = process.cwd()
75
-
76
- t.after(() => {
77
- process.chdir(cwd)
78
- })
79
-
80
- const configDir = join(fixturesDir, 'monorepo', 'composerApp')
81
- process.chdir(configDir)
82
- const type = await getConfigType()
83
-
84
- assert.strictEqual(type, 'composer')
85
- })
86
- })
87
-
88
- test('getCurrentSchema()', async (t) => {
89
- await t.test('gets service schema', async () => {
90
- const schema = await getCurrentSchema('service')
91
-
92
- assert(schema.$id.endsWith(`/v${version}/service`))
93
- })
94
-
95
- await t.test('gets db schema', async () => {
96
- const schema = await getCurrentSchema('db')
97
-
98
- assert(schema.$id.endsWith(`/v${version}/db`))
99
- })
100
-
101
- await t.test('gets composer schema', async () => {
102
- const schema = await getCurrentSchema('composer')
103
-
104
- assert(schema.$id.endsWith(`/v${version}/composer`))
105
- })
106
-
107
- await t.test('gets runtime schema', async () => {
108
- const schema = await getCurrentSchema('runtime')
109
-
110
- assert(schema.$id.endsWith(`/v${version}/runtime`))
111
- })
112
-
113
- await t.test('throws for unknown types', async () => {
114
- await assert.rejects(async () => {
115
- await getCurrentSchema('not-a-real-type')
116
- }, /unknown configuration type/)
117
- })
118
- })
119
-
120
13
  test('loadConfig()', async (t) => {
121
14
  await t.test('can explicitly provide config type', async () => {
122
15
  const configFile = join(fixturesDir, 'monorepo', 'serviceAppWithLogger', 'platformatic.service.json')
@@ -128,18 +21,6 @@ test('loadConfig()', async (t) => {
128
21
  assert.strictEqual(config.configManager.schemaOptions.useDefaults, true)
129
22
  })
130
23
 
131
- await t.test('throws if explicit type is wrong', async () => {
132
- // Prevent the failed validation from logging and exiting the process.
133
- t.mock.method(process, 'exit', () => {})
134
- t.mock.method(console, 'table', () => {})
135
-
136
- const configFile = join(fixturesDir, 'monorepo', 'serviceAppWithLogger', 'platformatic.service.json')
137
-
138
- await assert.rejects(async () => {
139
- await loadConfig({}, ['-c', configFile], 'kaboom')
140
- })
141
- })
142
-
143
24
  await t.test('can load a platformatic service project', async () => {
144
25
  const configFile = join(fixturesDir, 'monorepo', 'serviceAppWithLogger', 'platformatic.service.json')
145
26
  const config = await loadConfig({}, ['-c', configFile])
@@ -182,7 +63,10 @@ test('buildServer()', async (t) => {
182
63
  await t.test('can build a service server', async (t) => {
183
64
  const configFile = join(fixturesDir, 'monorepo', 'serviceAppWithLogger', 'platformatic.service.json')
184
65
  const config = await loadConfig({}, ['-c', configFile])
185
- const server = await buildServer(config.configManager.current)
66
+ const server = await buildServer({
67
+ app: config.app,
68
+ ...config.configManager.current
69
+ })
186
70
 
187
71
  t.after(async () => {
188
72
  await server.close()
@@ -196,7 +80,10 @@ test('buildServer()', async (t) => {
196
80
  await t.test('can build a db server', async (t) => {
197
81
  const configFile = join(fixturesDir, 'dbApp', 'platformatic.db.json')
198
82
  const config = await loadConfig({}, ['-c', configFile])
199
- const server = await buildServer(config.configManager.current)
83
+ const server = await buildServer({
84
+ app: config.app,
85
+ ...config.configManager.current
86
+ })
200
87
 
201
88
  t.after(async () => {
202
89
  await server.close()
@@ -210,7 +97,10 @@ test('buildServer()', async (t) => {
210
97
  await t.test('can build a composer server', async (t) => {
211
98
  const configFile = join(fixturesDir, 'composerApp', 'platformatic.composer.json')
212
99
  const config = await loadConfig({}, ['-c', configFile])
213
- const server = await buildServer(config.configManager.current)
100
+ const server = await buildServer({
101
+ app: config.app,
102
+ ...config.configManager.current
103
+ })
214
104
 
215
105
  t.after(async () => {
216
106
  await server.close()
@@ -224,7 +114,10 @@ test('buildServer()', async (t) => {
224
114
  await t.test('can build a runtime application', async (t) => {
225
115
  const configFile = join(fixturesDir, 'configs', 'monorepo.json')
226
116
  const config = await loadConfig({}, ['-c', configFile])
227
- const server = await buildServer(config.configManager.current)
117
+ const server = await buildServer({
118
+ app: config.app,
119
+ ...config.configManager.current
120
+ })
228
121
 
229
122
  t.after(async () => {
230
123
  await server.close()
@@ -244,6 +137,9 @@ test('buildServer()', async (t) => {
244
137
  })
245
138
 
246
139
  const address = await server.start()
140
+
141
+ assert.strictEqual(server.platformatic.configManager.fullPath, configFile)
142
+
247
143
  // The address should be a valid URL.
248
144
  new URL(address) // eslint-disable-line no-new
249
145
  })