@platformatic/service 1.27.0 → 1.28.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/config.d.ts CHANGED
@@ -144,6 +144,12 @@ export interface PlatformaticService {
144
144
  | {
145
145
  port?: number | string;
146
146
  hostname?: string;
147
+ endpoint?: string;
148
+ server?: "own" | "parent";
149
+ defaultMetrics?: {
150
+ enabled: boolean;
151
+ };
152
+ prefix?: string;
147
153
  auth?: {
148
154
  username: string;
149
155
  password: string;
package/index.d.ts CHANGED
@@ -28,7 +28,7 @@ declare module 'fastify' {
28
28
  }
29
29
  }
30
30
 
31
- export interface ConfigManagerConfig<T> extends Omit<IConfigManagerOptions, 'source' | 'watch' | 'schema'> {
31
+ export interface ConfigManagerConfig<T> extends Omit<IConfigManagerOptions, 'source' | 'watch' | 'schema' | 'configVersion'> {
32
32
  transformConfig: (this: ConfigManager<T>) => Promise<void>
33
33
  schema: object
34
34
  }
@@ -40,6 +40,9 @@ export interface Stackable<ConfigType> {
40
40
  configManagerConfig: ConfigManagerConfig<ConfigType>
41
41
  schema: object
42
42
  Generator?: new () => BaseGenerator.BaseGenerator
43
+
44
+ version?: string
45
+ upgrade?: (config: any, version: string) => Promise<any>
43
46
  }
44
47
 
45
48
  interface SchemaExport {
package/index.js CHANGED
@@ -14,6 +14,7 @@ const setupTsCompiler = require('./lib/plugins/typescript')
14
14
  const setupHealthCheck = require('./lib/plugins/health-check')
15
15
  const loadPlugins = require('./lib/plugins/plugins')
16
16
  const loadVersions = require('./lib/plugins/versions')
17
+ const upgrade = require('./lib/upgrade')
17
18
  const { telemetry } = require('@platformatic/telemetry')
18
19
 
19
20
  const { schema } = require('./lib/schema')
@@ -21,6 +22,8 @@ const { addLoggerToTheConfig } = require('./lib/utils')
21
22
  const { start, buildServer } = require('./lib/start')
22
23
  const ServiceGenerator = require('./lib/generator/service-generator.js')
23
24
 
25
+ const { version } = require('./package.json')
26
+
24
27
  // TODO(mcollina): arugments[2] is deprecated, remove it in the next major version.
25
28
  async function platformaticService (app, opts) {
26
29
  const configManager = app.platformatic.configManager
@@ -102,6 +105,7 @@ platformaticService[Symbol.for('skip-override')] = true
102
105
  platformaticService.schema = schema
103
106
  platformaticService.configType = 'service'
104
107
  platformaticService.configManagerConfig = {
108
+ version,
105
109
  schema,
106
110
  envWhitelist: ['PORT', 'HOSTNAME'],
107
111
  allowToWatch: ['.env'],
@@ -138,7 +142,8 @@ platformaticService.configManagerConfig = {
138
142
  this.current.watch.ignore ||= []
139
143
  this.current.watch.ignore.push(outDir + '/**/*')
140
144
  }
141
- }
145
+ },
146
+ upgrade
142
147
  }
143
148
 
144
149
  function _buildServer (options, app) {
package/index.test-d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { expectType } from 'tsd'
1
+ import { expectType, expectError } from 'tsd'
2
2
  import { FastifyInstance } from 'fastify'
3
3
  import ConfigManager from '@platformatic/config'
4
4
  import { OpenAPI } from 'openapi-types'
@@ -39,6 +39,7 @@ function buildStackable (): Stackable<PlatformaticServiceConfig> {
39
39
  myApp.schema = platformaticService.configManagerConfig.schema
40
40
  myApp.configType = 'myApp'
41
41
  myApp.configManagerConfig = {
42
+ version: platformaticService.configManagerConfig.version,
42
43
  ...platformaticService.configManagerConfig,
43
44
  async transformConfig (this: ConfigManager<PlatformaticServiceConfig>) {
44
45
  this.current.plugins = {
@@ -46,9 +47,19 @@ function buildStackable (): Stackable<PlatformaticServiceConfig> {
46
47
  path: 'my-plugin'
47
48
  }]
48
49
  }
50
+ },
51
+ async upgrade (config: PlatformaticServiceConfig, version: string) {
52
+ const upgrade = platformaticService.configManagerConfig.upgrade
53
+ if (typeof upgrade === 'function') {
54
+ return upgrade.call(this, config, version)
55
+ }
56
+ return config
49
57
  }
50
58
  }
51
59
 
60
+ // configVersion is not part of ConfigManagerConfig
61
+ expectError(myApp.configManagerConfig.configVersion)
62
+
52
63
  await start(myApp, ['--help'])
53
64
 
54
65
  return myApp
@@ -64,3 +75,28 @@ const myGenerator = new MyGenerator()
64
75
 
65
76
  expectType<MyGenerator>(myGenerator)
66
77
  expectType<BaseGenerator.BaseGeneratorConfig>(myGenerator.config)
78
+
79
+ function buildStackable2 (): Stackable<PlatformaticServiceConfig> {
80
+ async function myApp (app: FastifyInstance, opts: object): Promise<void> {
81
+ await platformaticService(app, opts)
82
+ }
83
+
84
+ myApp.schema = platformaticService.configManagerConfig.schema
85
+ myApp.configType = 'myApp'
86
+ myApp.configManagerConfig = {
87
+ ...platformaticService.configManagerConfig,
88
+ async transformConfig (this: ConfigManager<PlatformaticServiceConfig>) {
89
+ this.current.plugins = {
90
+ paths: [{
91
+ path: 'my-plugin'
92
+ }]
93
+ }
94
+ }
95
+ }
96
+
97
+ await start(myApp, ['--help'])
98
+
99
+ return myApp
100
+ }
101
+
102
+ expectType<Stackable<PlatformaticServiceConfig>>(buildStackable2())
@@ -4,8 +4,7 @@ const { join, relative } = require('node:path')
4
4
  const { mkdir, readFile, writeFile } = require('node:fs/promises')
5
5
  const pino = require('pino')
6
6
  const pretty = require('pino-pretty')
7
- const { loadConfig } = require('@platformatic/config')
8
- const { analyze, write: writeConfig } = require('@platformatic/metaconfig')
7
+ const { loadConfig, getParser, getStringifier } = require('@platformatic/config')
9
8
  const { platformaticService } = require('../index.js')
10
9
  const { getOpenapiSchema } = require('./get-openapi-schema.js')
11
10
  const { createMappersPlugins } = require('./update-version.js')
@@ -23,8 +22,9 @@ async function execute ({
23
22
  }) {
24
23
  const config = configManager.current
25
24
 
26
- const metaConfig = await analyze({ file: configManager.fullPath })
27
- const rawConfig = metaConfig.config
25
+ const parse = getParser(configManager.fullPath)
26
+ const stringify = getStringifier(configManager.fullPath)
27
+ const rawConfig = parse(await readFile(configManager.fullPath, 'utf8'))
28
28
 
29
29
  const versionsDirName = 'versions'
30
30
  const versionsDirPath = config.versions?.dir ??
@@ -102,7 +102,7 @@ async function execute ({
102
102
  config.versions = versionsConfigs
103
103
  rawConfig.versions = rawVersionsConfigs
104
104
 
105
- await Promise.all([configManager.update(), writeConfig(metaConfig)])
105
+ await Promise.all([configManager.update(), writeFile(configManager.fullPath, stringify(rawConfig))])
106
106
 
107
107
  if (latestVersionConfig) {
108
108
  logger.info(`Reading openapi schema for "${latestVersion}"`)
@@ -1,40 +1,120 @@
1
1
  'use strict'
2
2
 
3
+ const os = require('node:os')
3
4
  const http = require('node:http')
4
5
  const { eventLoopUtilization } = require('node:perf_hooks').performance
5
6
  const fastify = require('fastify')
6
7
  const fp = require('fastify-plugin')
7
8
 
8
- const metricsPlugin = fp(async function (app) {
9
+ const metricsPlugin = fp(async function (app, opts = {}) {
10
+ const defaultMetrics = opts.defaultMetrics ?? { enabled: true }
11
+ const prefix = opts.prefix ?? ''
12
+
9
13
  app.register(require('fastify-metrics'), {
10
- defaultMetrics: { enabled: true },
14
+ defaultMetrics: defaultMetrics || { enabled: true },
11
15
  endpoint: null,
12
16
  name: 'metrics',
13
- routeMetrics: { enabled: true },
14
- clearRegisterOnInit: true
17
+ clearRegisterOnInit: false,
18
+ routeMetrics: {
19
+ enabled: true,
20
+ overrides: {
21
+ histogram: {
22
+ name: prefix + 'http_request_duration_seconds'
23
+ },
24
+ summary: {
25
+ name: prefix + 'http_request_summary_seconds'
26
+ }
27
+ }
28
+ }
15
29
  })
16
30
 
17
- app.register(async (app) => {
18
- const eluMetric = new app.metrics.client.Summary({
19
- name: 'nodejs_eventloop_utilization',
20
- help: 'The event loop utilization as a fraction of the loop time. 1 is fully utilized, 0 is fully idle.',
21
- maxAgeSeconds: 60,
22
- ageBuckets: 5,
23
- labelNames: ['idle', 'active', 'utilization']
31
+ app.register(fp(async (app) => {
32
+ const httpLatencyMetric = new app.metrics.client.Summary({
33
+ name: prefix + 'http_request_all_summary_seconds',
34
+ help: 'request duration in seconds summary for all requests',
35
+ collect: () => {
36
+ process.nextTick(() => httpLatencyMetric.reset())
37
+ }
24
38
  })
25
-
26
- let startELU = eventLoopUtilization()
27
- const eluTimeout = setInterval(() => {
28
- const endELU = eventLoopUtilization()
29
- eluMetric.observe(eventLoopUtilization(endELU, startELU).utilization)
30
- startELU = endELU
31
- }, 100)
32
-
33
- app.addHook('onClose', () => {
34
- clearInterval(eluTimeout)
39
+ const ignoredMethods = ['HEAD', 'OPTIONS', 'TRACE', 'CONNECT']
40
+ const timers = new WeakMap()
41
+ app.addHook('onRequest', async (req) => {
42
+ if (ignoredMethods.includes(req.method)) return
43
+ const timer = httpLatencyMetric.startTimer()
44
+ timers.set(req, timer)
45
+ })
46
+ app.addHook('onResponse', async (req) => {
47
+ if (ignoredMethods.includes(req.method)) return
48
+ const timer = timers.get(req)
49
+ if (timer) {
50
+ timer()
51
+ timers.delete(req)
52
+ }
35
53
  })
54
+ }, {
55
+ encapsulate: false
56
+ }))
57
+
58
+ if (defaultMetrics.enabled) {
59
+ app.register(async (app) => {
60
+ let startELU = eventLoopUtilization()
61
+ const eluMetric = new app.metrics.client.Gauge({
62
+ name: 'nodejs_eventloop_utilization',
63
+ help: 'The event loop utilization as a fraction of the loop time. 1 is fully utilized, 0 is fully idle.',
64
+ collect: () => {
65
+ const endELU = eventLoopUtilization()
66
+ const result = eventLoopUtilization(endELU, startELU).utilization
67
+ eluMetric.set(result)
68
+ startELU = endELU
69
+ }
70
+ })
71
+ app.metrics.client.register.registerMetric(eluMetric)
72
+
73
+ let previousIdleTime = 0
74
+ let previousTotalTime = 0
75
+ const cpuMetric = new app.metrics.client.Gauge({
76
+ name: 'process_cpu_percent_usage',
77
+ help: 'The process CPU percent usage.',
78
+ collect: () => {
79
+ const cpus = os.cpus()
80
+ let idleTime = 0
81
+ let totalTime = 0
82
+
83
+ cpus.forEach(cpu => {
84
+ for (const type in cpu.times) {
85
+ totalTime += cpu.times[type]
86
+ if (type === 'idle') {
87
+ idleTime += cpu.times[type]
88
+ }
89
+ }
90
+ })
91
+
92
+ const idleDiff = idleTime - previousIdleTime
93
+ const totalDiff = totalTime - previousTotalTime
94
+
95
+ const usagePercent = 100 - ((100 * idleDiff) / totalDiff)
96
+ const roundedUsage = Math.round(usagePercent * 100) / 100
97
+ cpuMetric.set(roundedUsage)
98
+
99
+ previousIdleTime = idleTime
100
+ previousTotalTime = totalTime
101
+ }
102
+ })
103
+ app.metrics.client.register.registerMetric(cpuMetric)
104
+ })
105
+ }
36
106
 
37
- app.metrics.client.register.registerMetric(eluMetric)
107
+ let isRestarting = false
108
+ app.addHook('onReady', async () => {
109
+ app.addPreRestartHook(async () => {
110
+ isRestarting = true
111
+ app.metrics.client.register.clear()
112
+ })
113
+ })
114
+ app.addHook('onClose', async () => {
115
+ if (!isRestarting) {
116
+ app.metrics.client.register.clear()
117
+ }
38
118
  })
39
119
  }, {
40
120
  encapsulate: false
@@ -88,7 +168,7 @@ module.exports = fp(async function (app, opts) {
88
168
  const metricsEndpoint = opts.endpoint ?? '/metrics'
89
169
  const auth = opts.auth ?? null
90
170
 
91
- app.register(metricsPlugin)
171
+ app.register(metricsPlugin, opts)
92
172
 
93
173
  let metricsServer = app
94
174
  if (server === 'own') {
package/lib/schema.js CHANGED
@@ -560,6 +560,15 @@ const metrics = {
560
560
  type: 'string',
561
561
  enum: ['own', 'parent']
562
562
  },
563
+ defaultMetrics: {
564
+ type: 'object',
565
+ properties: {
566
+ enabled: { type: 'boolean' }
567
+ },
568
+ required: ['enabled'],
569
+ additionalProperties: false
570
+ },
571
+ prefix: { type: 'string' },
563
572
  auth: {
564
573
  type: 'object',
565
574
  properties: {
@@ -747,6 +756,7 @@ const versions = {
747
756
 
748
757
  const platformaticServiceSchema = {
749
758
  $id: `https://platformatic.dev/schemas/v${pkg.version}/service`,
759
+ version: pkg.version,
750
760
  title: 'Platformatic Service',
751
761
  type: 'object',
752
762
  properties: {
@@ -10,8 +10,7 @@ const { request } = require('undici')
10
10
  const { green } = require('colorette')
11
11
  const compareOpenApiSchemas = require('openapi-schema-diff')
12
12
  const { default: CodeBlockWriter } = require('code-block-writer')
13
- const { loadConfig } = require('@platformatic/config')
14
- const { analyze, write: writeConfig } = require('@platformatic/metaconfig')
13
+ const { loadConfig, getParser, getStringifier } = require('@platformatic/config')
15
14
  const { getOpenapiSchema } = require('./get-openapi-schema.js')
16
15
  const { platformaticService } = require('../index.js')
17
16
  const {
@@ -744,8 +743,10 @@ async function addMappersToConfig (mappersDir, version, configManager) {
744
743
  if (mappersDir.startsWith(foundPluginPath)) return
745
744
  }
746
745
 
747
- const metaConfig = await analyze({ file: configManager.fullPath })
748
- const rawConfig = metaConfig.config
746
+ // TODO(mcollina) this must auto-upgrade
747
+ const parse = getParser(configManager.fullPath)
748
+ const stringify = getStringifier(configManager.fullPath)
749
+ const rawConfig = parse(await readFile(configManager.fullPath, 'utf8'))
749
750
  const rawPrevVersionConfig = rawConfig.versions.configs.find(c => c.version === version)
750
751
 
751
752
  const relativePath = relative(configManager.dirname, mappersDir)
@@ -755,7 +756,7 @@ async function addMappersToConfig (mappersDir, version, configManager) {
755
756
  }
756
757
  prevVersionConfig.plugins.paths.push(relativePath)
757
758
  rawPrevVersionConfig.plugins.paths.push(relativePath)
758
- await Promise.all([configManager.update(), writeConfig(metaConfig)])
759
+ await Promise.all([configManager.update(), writeFile(configManager.fullPath, stringify(rawConfig), 'utf8')])
759
760
  }
760
761
 
761
762
  async function execute ({
@@ -854,7 +855,7 @@ async function updateVersion (_args) {
854
855
  // TODO: find out why process stucks sometimes
855
856
  process.exit(0)
856
857
  } catch (err) {
857
- logger.error(err.message)
858
+ logger.error({ err })
858
859
  process.exit(1)
859
860
  }
860
861
  }
package/lib/upgrade.js ADDED
@@ -0,0 +1,25 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('path')
4
+ const pkg = require('../package.json')
5
+
6
+ module.exports = async function upgrade (config, version) {
7
+ const { semgrator } = await import('semgrator')
8
+
9
+ const iterator = semgrator({
10
+ version,
11
+ path: join(__dirname, 'versions'),
12
+ input: config,
13
+ logger: this.logger.child({ name: '@platformatic/service' })
14
+ })
15
+
16
+ let result
17
+
18
+ for await (const updated of iterator) {
19
+ result = updated.result
20
+ }
21
+
22
+ result.$schema = `https://platformatic.dev/schemas/v${pkg.version}/service`
23
+
24
+ return result
25
+ }
@@ -0,0 +1,82 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ version: '0.16.0',
5
+ toVersion: '0.17.0',
6
+ up: function (config) {
7
+ let kind = 'service'
8
+ // This file will be reused in platformatic/db
9
+ if (config.core) {
10
+ kind = 'db'
11
+ }
12
+
13
+ if (config.plugin) {
14
+ if (Array.isArray(config.plugin)) {
15
+ config.plugins = {
16
+ paths: config.plugin.map((p) => {
17
+ if (typeof p === 'string') {
18
+ return p
19
+ } else if (p.options) {
20
+ return {
21
+ path: p.path,
22
+ options: p.options
23
+ }
24
+ } else {
25
+ return p.path
26
+ }
27
+ })
28
+ }
29
+
30
+ if (typeof config.plugin[0] === 'object') {
31
+ if ('hotReload' in config.plugin[0]) {
32
+ config.plugins.hotReload = config.plugin[0].hotReload
33
+ }
34
+ if ('stopTimeout' in config.plugin[0]) {
35
+ config.plugins.stopTimeout = config.plugin[0].stopTimeout
36
+ }
37
+ if ('typescript' in config.plugin[0]) {
38
+ config.plugins.typescript = !!config.plugin[0].typescript
39
+ }
40
+ }
41
+ } else if (typeof config.plugin === 'object') {
42
+ if (config.plugin.options) {
43
+ config.plugins = {
44
+ paths: [{
45
+ path: config.plugin.path,
46
+ options: config.plugin.options
47
+ }]
48
+ }
49
+ } else {
50
+ config.plugins = {
51
+ paths: [config.plugin.path]
52
+ }
53
+ }
54
+
55
+ if ('hotReload' in config.plugin) {
56
+ config.plugins.hotReload = config.plugin.hotReload
57
+ }
58
+
59
+ if ('stopTimeout' in config.plugin) {
60
+ config.plugins.stopTimeout = config.plugin.stopTimeout
61
+ }
62
+
63
+ if ('typescript' in config.plugin) {
64
+ // typescript is a boolean in 0.17.0
65
+ config.plugins.typescript = !!config.plugin.typescript
66
+ }
67
+ } else {
68
+ config.plugins = {
69
+ paths: [config.plugin]
70
+ }
71
+ }
72
+
73
+ delete config.plugin
74
+ }
75
+
76
+ // TODO missing watch mode and other options
77
+
78
+ config.$schema = 'https://platformatic.dev/schemas/v0.17.0/' + kind
79
+
80
+ return config
81
+ }
82
+ }
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const { version } = require('../../package.json')
4
+
5
+ module.exports.migration = {
6
+ version: '0.28.0',
7
+ toVersion: version,
8
+ up: function (config) {
9
+ if (config.watch !== false) {
10
+ config.watch = typeof config.watch === 'object' ? config.watch : {}
11
+ }
12
+ delete config.plugins?.hotReload
13
+
14
+ return config
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/service",
3
- "version": "1.27.0",
3
+ "version": "1.28.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -30,31 +30,31 @@
30
30
  "standard": "^17.1.0",
31
31
  "strip-ansi": "^7.1.0",
32
32
  "ts-standard": "^12.0.2",
33
- "tsd": "^0.30.4",
34
- "typescript": "^5.3.3",
33
+ "tsd": "^0.30.7",
34
+ "typescript": "^5.4.2",
35
35
  "undici": "^6.0.0",
36
36
  "vscode-json-languageservice": "^5.3.9",
37
37
  "why-is-node-running": "^2.2.2",
38
- "yaml": "^2.3.4"
38
+ "yaml": "^2.4.1"
39
39
  },
40
40
  "dependencies": {
41
41
  "@fastify/accepts": "^4.3.0",
42
42
  "@fastify/autoload": "^5.8.0",
43
43
  "@fastify/basic-auth": "^5.1.1",
44
- "@fastify/cors": "^9.0.0",
44
+ "@fastify/cors": "^9.0.1",
45
45
  "@fastify/deepmerge": "^1.3.0",
46
46
  "@fastify/error": "^3.4.1",
47
47
  "@fastify/restartable": "^2.2.0",
48
- "@fastify/static": "^7.0.0",
48
+ "@fastify/static": "^7.0.1",
49
49
  "@fastify/swagger": "^8.14.0",
50
50
  "@fastify/under-pressure": "^8.3.0",
51
51
  "@mercuriusjs/federation": "^2.0.0",
52
- "@scalar/fastify-api-reference": "^1.13.18",
52
+ "@scalar/fastify-api-reference": "^1.19.5",
53
53
  "@types/ws": "^8.5.10",
54
54
  "ajv": "^8.12.0",
55
55
  "cli-progress": "^3.12.0",
56
- "close-with-grace": "^1.2.0",
57
- "code-block-writer": "^13.0.0",
56
+ "close-with-grace": "^1.3.0",
57
+ "code-block-writer": "^13.0.1",
58
58
  "colorette": "^2.0.20",
59
59
  "commist": "^3.2.0",
60
60
  "console-table-printer": "^2.12.0",
@@ -62,29 +62,29 @@
62
62
  "env-schema": "^5.2.1",
63
63
  "es-main": "^1.3.0",
64
64
  "execa": "^8.0.1",
65
- "fastify": "^4.26.0",
65
+ "fastify": "^4.26.2",
66
66
  "fastify-metrics": "^11.0.0",
67
67
  "fastify-openapi-glue": "^4.4.3",
68
68
  "fastify-plugin": "^4.5.1",
69
69
  "graphql": "^16.8.1",
70
70
  "help-me": "^5.0.0",
71
- "mercurius": "^13.3.3",
71
+ "mercurius": "^13.4.0",
72
72
  "minimist": "^1.2.8",
73
73
  "openapi-schema-diff": "^0.0.1",
74
74
  "ora": "^6.3.1",
75
- "pino": "^8.17.2",
75
+ "pino": "^8.19.0",
76
76
  "pino-pretty": "^10.3.1",
77
77
  "rfdc": "^1.3.1",
78
+ "semgrator": "^0.3.0",
78
79
  "ua-parser-js": "^1.0.37",
79
- "undici": "^6.6.0",
80
- "@platformatic/client": "1.27.0",
81
- "@platformatic/config": "1.27.0",
82
- "@platformatic/generators": "1.27.0",
83
- "@platformatic/authenticate": "1.27.0",
84
- "@platformatic/metaconfig": "1.27.0",
85
- "@platformatic/scalar-theme": "1.27.0",
86
- "@platformatic/utils": "1.27.0",
87
- "@platformatic/telemetry": "1.27.0"
80
+ "undici": "^6.9.0",
81
+ "@platformatic/authenticate": "1.28.0",
82
+ "@platformatic/config": "1.28.0",
83
+ "@platformatic/generators": "1.28.0",
84
+ "@platformatic/client": "1.28.0",
85
+ "@platformatic/scalar-theme": "1.28.0",
86
+ "@platformatic/telemetry": "1.28.0",
87
+ "@platformatic/utils": "1.28.0"
88
88
  },
89
89
  "standard": {
90
90
  "ignore": [
@@ -101,7 +101,9 @@
101
101
  "scripts": {
102
102
  "test": "pnpm run lint && borp -T --concurrency=1 --timeout 120000 && tsd",
103
103
  "unit": "borp --pattern 'test/**/*.test.{js,mjs}' --ignore 'fixtures/**/*' --concurrency=1 --timeout=120000 --no-typescript",
104
- "build": "node lib/schema.js | json2ts > config.d.ts",
104
+ "gen-schema": "node lib/schema.js > schema.json",
105
+ "gen-types": "json2ts > config.d.ts < schema.json",
106
+ "build": "pnpm run gen-schema && pnpm run gen-types",
105
107
  "lint": "standard | snazzy && ts-standard | snazzy && tsd"
106
108
  }
107
109
  }