fork of hey-api/openapi-ts because I need some additional things

Merge pull request #1511 from hey-api/feat/watch-mode

feat: add watch mode

authored by

Lubos and committed by
GitHub
dff8b812 780f6fb1

+632 -275
+31
.changeset/old-gorillas-speak.md
··· 1 + --- 2 + '@hey-api/openapi-ts': minor 3 + '@hey-api/docs': minor 4 + --- 5 + 6 + feat: add watch mode 7 + 8 + ## Watch Mode 9 + 10 + ::: warning 11 + Watch mode currently supports only remote files via URL. 12 + ::: 13 + 14 + If your schema changes frequently, you may want to automatically regenerate the output during development. To watch your input file for changes, enable `watch` mode in your configuration or pass the `--watch` flag to the CLI. 15 + 16 + ### Config 17 + 18 + ```js 19 + export default { 20 + client: '@hey-api/client-fetch', 21 + input: 'path/to/openapi.json', 22 + output: 'src/client', 23 + watch: true, // [!code ++] 24 + }; 25 + ``` 26 + 27 + ### CLI 28 + 29 + ```sh 30 + npx @hey-api/openapi-ts -i path/to/openapi.json -o src/client -c @hey-api/client-fetch -w 31 + ```
+89 -64
docs/openapi-ts/configuration.md
··· 43 43 44 44 ## Input 45 45 46 - Input is the first thing you must define. It can be a local path, remote URL, or a string content resolving to an OpenAPI specification. Hey API supports all valid OpenAPI versions and file formats. 46 + Input is the first thing you must define. It can be a path, URL, or a string content resolving to an OpenAPI specification. Hey API supports all valid OpenAPI versions and file formats. 47 47 48 48 ::: info 49 - We use [`@apidevtools/json-schema-ref-parser`](https://github.com/APIDevTools/json-schema-ref-parser) to resolve file locations. Please note that accessing a HTTPS URL on localhost has a known [workaround](https://github.com/hey-api/openapi-ts/issues/276). 49 + We use [`@hey-api/json-schema-ref-parser`](https://github.com/hey-api/json-schema-ref-parser) to resolve file locations. Please note that accessing a HTTPS URL on localhost has a known [workaround](https://github.com/hey-api/openapi-ts/issues/276). 50 50 ::: 51 51 52 52 ## Output ··· 56 56 ::: tip 57 57 You should treat the output folder as a dependency. Do not directly modify its contents as your changes might be erased when you run `@hey-api/openapi-ts` again. 58 58 ::: 59 + 60 + ## Clients 61 + 62 + Clients are responsible for sending the actual HTTP requests. The `client` value is not required, but you must define it if you're generating SDKs (enabled by default). 63 + 64 + You can learn more on the [Clients](/openapi-ts/clients) page. 65 + 66 + <!-- 67 + TODO: uncomment after c12 supports multiple configs 68 + see https://github.com/unjs/c12/issues/92 69 + --> 70 + <!-- ### Multiple Clients 71 + 72 + If you want to generate multiple clients with a single `openapi-ts` command, you can provide an array of configuration objects. 73 + 74 + ```js 75 + import { defineConfig } from '@hey-api/openapi-ts'; 76 + 77 + export default defineConfig([ 78 + { 79 + client: 'legacy/fetch', 80 + input: 'path/to/openapi_one.json', 81 + output: 'src/client_one', 82 + }, 83 + { 84 + client: 'legacy/axios', 85 + input: 'path/to/openapi_two.json', 86 + output: 'src/client_two', 87 + }, 88 + ]) 89 + ``` --> 90 + 91 + ## Plugins 92 + 93 + Plugins are responsible for generating artifacts from your input. By default, Hey API will generate TypeScript interfaces and SDK from your OpenAPI specification. You can add, remove, or customize any of the plugins. In fact, we highly encourage you to do so! 94 + 95 + You can learn more on the [Output](/openapi-ts/output) page. 96 + 97 + ## Parser 98 + 99 + If you're using OpenAPI 3.0 or newer, we encourage you to try out the experimental parser. In the future this will become the default parser, but until it's been tested it's an opt-in feature. To try it out, set the `experimentalParser` flag in your configuration to `true`. 100 + 101 + ::: code-group 102 + 103 + ```js [config] 104 + export default { 105 + client: '@hey-api/client-fetch', 106 + experimentalParser: true, // [!code ++] 107 + input: 'path/to/openapi.json', 108 + output: 'src/client', 109 + }; 110 + ``` 111 + 112 + ```sh [cli] 113 + npx @hey-api/openapi-ts -i path/to/openapi.json -o src/client -c @hey-api/client-fetch -e 114 + ``` 115 + 116 + ::: 117 + 118 + The experimental parser produces a cleaner output while being faster than the legacy parser. It also supports features such as [Filters](#filters) and more are being added. 119 + 120 + The legacy parser will be used with the [legacy clients](/openapi-ts/clients/legacy) regardless of the `experimentalParser` flag value. However, it's unlikely to receive any further updates. 59 121 60 122 ## Formatting 61 123 ··· 154 216 155 217 You can also prevent your output from being linted by adding your output path to the linter's ignore file. 156 218 157 - ## Clients 158 - 159 - Clients are responsible for sending the actual HTTP requests. The `client` value is not required, but you must define it if you're generating SDKs (enabled by default). 160 - 161 - You can learn more on the [Clients](/openapi-ts/clients) page. 162 - 163 - <!-- 164 - TODO: uncomment after c12 supports multiple configs 165 - see https://github.com/unjs/c12/issues/92 166 - --> 167 - <!-- ### Multiple Clients 168 - 169 - If you want to generate multiple clients with a single `openapi-ts` command, you can provide an array of configuration objects. 170 - 171 - ```js 172 - import { defineConfig } from '@hey-api/openapi-ts'; 173 - 174 - export default defineConfig([ 175 - { 176 - client: 'legacy/fetch', 177 - input: 'path/to/openapi_one.json', 178 - output: 'src/client_one', 179 - }, 180 - { 181 - client: 'legacy/axios', 182 - input: 'path/to/openapi_two.json', 183 - output: 'src/client_two', 184 - }, 185 - ]) 186 - ``` --> 187 - 188 - ## Plugins 189 - 190 - Plugins are responsible for generating artifacts from your input. By default, Hey API will generate TypeScript interfaces and SDK from your OpenAPI specification. You can add, remove, or customize any of the plugins. In fact, we highly encourage you to do so! 191 - 192 - You can learn more on the [Output](/openapi-ts/output) page. 193 - 194 - ## Parser 195 - 196 - If you're using OpenAPI 3.0 or newer, we encourage you to try out the experimental parser. In the future this will become the default parser, but until it's been tested it's an opt-in feature. To try it out, set the `experimentalParser` flag in your configuration to `true`. 197 - 198 - ::: code-group 199 - 200 - ```js [config] 201 - export default { 202 - client: '@hey-api/client-fetch', 203 - experimentalParser: true, // [!code ++] 204 - input: 'path/to/openapi.json', 205 - output: 'src/client', 206 - }; 207 - ``` 208 - 209 - ```sh [cli] 210 - npx @hey-api/openapi-ts -i path/to/openapi.json -o src/client -c @hey-api/client-fetch -e 211 - ``` 212 - 213 - ::: 214 - 215 - The experimental parser produces a cleaner output while being faster than the legacy parser. It also supports features such as [Filters](#filters) and more are being added. 216 - 217 - The legacy parser will be used with the [legacy clients](/openapi-ts/clients/legacy) regardless of the `experimentalParser` flag value. However, it's unlikely to receive any further updates. 218 - 219 219 ## Filters 220 220 221 221 ::: warning ··· 250 250 }, 251 251 output: 'src/client', 252 252 }; 253 + ``` 254 + 255 + ::: 256 + 257 + ## Watch Mode 258 + 259 + ::: warning 260 + Watch mode currently supports only remote files via URL. 261 + ::: 262 + 263 + If your schema changes frequently, you may want to automatically regenerate the output during development. To watch your input file for changes, enable `watch` mode in your configuration or pass the `--watch` flag to the CLI. 264 + 265 + ::: code-group 266 + 267 + ```js [config] 268 + export default { 269 + client: '@hey-api/client-fetch', 270 + input: 'path/to/openapi.json', 271 + output: 'src/client', 272 + watch: true, // [!code ++] 273 + }; 274 + ``` 275 + 276 + ```sh [cli] 277 + npx @hey-api/openapi-ts -i path/to/openapi.json -o src/client -c @hey-api/client-fetch -w 253 278 ``` 254 279 255 280 :::
+6
docs/openapi-ts/migrating.md
··· 27 27 28 28 This config option is deprecated and will be removed. 29 29 30 + ## v0.61.0 31 + 32 + ### Added `watch` option 33 + 34 + While this is a new feature, supporting it involved replacing the `@apidevtools/json-schema-ref-parser` dependency with our own implementation. Since this was a big change, we're applying caution and marking this as a breaking change. 35 + 30 36 ## v0.60.0 31 37 32 38 ### Added `sdk.transformer` option
+12 -2
packages/openapi-ts/bin/index.cjs
··· 34 34 'DEPRECATED. Manually set base in OpenAPI config instead of inferring from server value', 35 35 ) 36 36 .option('-s, --silent', 'Set log level to silent') 37 + .option( 38 + '-w, --watch [value]', 39 + 'Regenerate the client when the input file changes?', 40 + ) 37 41 .option('--exportCore [value]', 'DEPRECATED. Write core files to disk') 38 42 .option('--name <value>', 'DEPRECATED. Custom client class name') 39 43 .option('--request <value>', 'DEPRECATED. Path to custom request file') ··· 102 106 userConfig.logs.level = 'silent'; 103 107 } 104 108 105 - await createClient(userConfig); 106 - process.exit(0); 109 + if (typeof params.watch === 'string') { 110 + userConfig.watch = Number.parseInt(params.watch, 10); 111 + } 112 + 113 + const context = await createClient(userConfig); 114 + if (!context[0] || !context[0].config.watch) { 115 + process.exit(0); 116 + } 107 117 } catch (error) { 108 118 process.exit(1); 109 119 }
+3 -4
packages/openapi-ts/package.json
··· 13 13 "license": "MIT", 14 14 "author": { 15 15 "email": "lubos@heyapi.dev", 16 - "name": "Lubos Menus", 17 - "url": "https://lmen.us" 16 + "name": "Hey API", 17 + "url": "https://heyapi.dev" 18 18 }, 19 19 "funding": "https://github.com/sponsors/hey-api", 20 20 "keywords": [ ··· 67 67 "prepublishOnly": "pnpm build", 68 68 "test:coverage": "vitest run --config vitest.config.unit.ts --coverage", 69 69 "test:e2e": "vitest run --config vitest.config.e2e.ts", 70 - "test:sample": "node test/sample.cjs", 71 70 "test:update": "vitest watch --config vitest.config.unit.ts --update", 72 71 "test:watch": "vitest watch --config vitest.config.unit.ts", 73 72 "test": "vitest run --config vitest.config.unit.ts", ··· 77 76 "node": "^18.0.0 || >=20.0.0" 78 77 }, 79 78 "dependencies": { 80 - "@apidevtools/json-schema-ref-parser": "11.7.3", 79 + "@hey-api/json-schema-ref-parser": "0.0.2", 81 80 "c12": "2.0.1", 82 81 "commander": "12.1.0", 83 82 "handlebars": "4.7.8"
+6
packages/openapi-ts/src/generate/__tests__/class.test.ts
··· 2 2 3 3 import { describe, expect, it, vi } from 'vitest'; 4 4 5 + import type { Config } from '../../types/config'; 5 6 import { setConfig } from '../../utils/config'; 6 7 import { generateLegacyClientClass } from '../class'; 7 8 import { mockTemplates, openApi } from './mocks'; ··· 49 50 }, 50 51 }, 51 52 useOptions: true, 53 + watch: { 54 + enabled: false, 55 + interval: 1000, 56 + }, 52 57 }); 53 58 54 59 const client: Parameters<typeof generateLegacyClientClass>[2] = { 60 + config: {} as Config, 55 61 models: [], 56 62 server: 'http://localhost:8080', 57 63 services: [],
+16
packages/openapi-ts/src/generate/__tests__/core.test.ts
··· 3 3 4 4 import { beforeEach, describe, expect, it, vi } from 'vitest'; 5 5 6 + import type { Config } from '../../types/config'; 6 7 import { setConfig } from '../../utils/config'; 7 8 import { generateLegacyCore } from '../core'; 8 9 import { mockTemplates } from './mocks'; ··· 17 18 18 19 it('writes to filesystem', async () => { 19 20 const client: Parameters<typeof generateLegacyCore>[1] = { 21 + config: {} as Config, 20 22 models: [], 21 23 server: 'http://localhost:8080', 22 24 services: [], ··· 63 65 }, 64 66 }, 65 67 useOptions: true, 68 + watch: { 69 + enabled: false, 70 + interval: 1000, 71 + }, 66 72 }); 67 73 68 74 await generateLegacyCore('/', client, templates); ··· 95 101 96 102 it('uses client server value for base', async () => { 97 103 const client: Parameters<typeof generateLegacyCore>[1] = { 104 + config: {} as Config, 98 105 models: [], 99 106 server: 'http://localhost:8080', 100 107 services: [], ··· 141 148 }, 142 149 }, 143 150 useOptions: true, 151 + watch: { 152 + enabled: false, 153 + interval: 1000, 154 + }, 144 155 }); 145 156 146 157 await generateLegacyCore('/', client, templates); ··· 155 166 156 167 it('uses custom value for base', async () => { 157 168 const client: Parameters<typeof generateLegacyCore>[1] = { 169 + config: {} as Config, 158 170 models: [], 159 171 server: 'http://localhost:8080', 160 172 services: [], ··· 202 214 }, 203 215 }, 204 216 useOptions: true, 217 + watch: { 218 + enabled: false, 219 + interval: 1000, 220 + }, 205 221 }); 206 222 207 223 await generateLegacyCore('/', client, templates);
+4
packages/openapi-ts/src/generate/__tests__/index.test.ts
··· 49 49 }, 50 50 }, 51 51 useOptions: true, 52 + watch: { 53 + enabled: false, 54 + interval: 1000, 55 + }, 52 56 }); 53 57 54 58 const files: Parameters<typeof generateIndexFile>[0]['files'] = {
+2
packages/openapi-ts/src/generate/__tests__/mocks.ts
··· 2 2 3 3 import type { OpenApi } from '../../openApi'; 4 4 import type { Client } from '../../types/client'; 5 + import type { Config } from '../../types/config'; 5 6 import type { Templates } from '../../utils/handlebars'; 6 7 7 8 export const client: Client = { 9 + config: {} as Config, 8 10 models: [], 9 11 server: 'http://localhost:8080', 10 12 services: [],
+6
packages/openapi-ts/src/generate/__tests__/output.test.ts
··· 3 3 import { describe, expect, it, vi } from 'vitest'; 4 4 5 5 import type { Client } from '../../types/client'; 6 + import type { Config } from '../../types/config'; 6 7 import { setConfig } from '../../utils/config'; 7 8 import { generateLegacyOutput } from '../output'; 8 9 import { mockTemplates, openApi } from './mocks'; ··· 50 51 }, 51 52 }, 52 53 useOptions: false, 54 + watch: { 55 + enabled: false, 56 + interval: 1000, 57 + }, 53 58 }); 54 59 55 60 const client: Client = { 61 + config: {} as Config, 56 62 models: [], 57 63 server: 'http://localhost:8080', 58 64 services: [],
+237 -67
packages/openapi-ts/src/index.ts
··· 1 1 import fs from 'node:fs'; 2 2 import path from 'node:path'; 3 3 4 - import $RefParser from '@apidevtools/json-schema-ref-parser'; 4 + import { 5 + $RefParser, 6 + getResolvedInput, 7 + type JSONSchema, 8 + sendRequest, 9 + } from '@hey-api/json-schema-ref-parser'; 5 10 import { loadConfig } from 'c12'; 6 11 import { sync } from 'cross-spawn'; 7 12 ··· 95 100 const module = linters[config.output.lint]; 96 101 console.log(`✨ Running ${module.name}`); 97 102 sync(module.command, module.args(config.output.path)); 98 - } 99 - }; 100 - 101 - const logClientMessage = ({ config }: { config: Config }) => { 102 - switch (config.client.name) { 103 - case 'legacy/angular': 104 - return console.log('✨ Creating Angular client'); 105 - case '@hey-api/client-axios': 106 - case 'legacy/axios': 107 - return console.log('✨ Creating Axios client'); 108 - case '@hey-api/client-fetch': 109 - case 'legacy/fetch': 110 - return console.log('✨ Creating Fetch client'); 111 - case 'legacy/node': 112 - return console.log('✨ Creating Node.js client'); 113 - case 'legacy/xhr': 114 - return console.log('✨ Creating XHR client'); 115 103 } 116 104 }; 117 105 ··· 311 299 }); 312 300 }; 313 301 314 - const getSpec = async ({ config }: { config: Config }) => { 315 - let spec: unknown = config.input.path; 302 + const getSpec = async ({ 303 + inputPath, 304 + watch, 305 + }: { 306 + inputPath: Config['input']['path']; 307 + watch: { 308 + headers: Headers; 309 + lastValue: string | undefined; 310 + }; 311 + }): Promise< 312 + | { 313 + data: JSONSchema; 314 + error?: undefined; 315 + response?: undefined; 316 + } 317 + | { 318 + data?: undefined; 319 + error: 'not-modified' | 'not-ok'; 320 + response: Response; 321 + } 322 + > => { 323 + const refParser = new $RefParser(); 324 + const resolvedInput = getResolvedInput({ pathOrUrlOrSchema: inputPath }); 316 325 317 - if (typeof config.input.path === 'string') { 318 - const absolutePathOrUrl = fs.existsSync(config.input.path) 319 - ? path.resolve(config.input.path) 320 - : config.input.path; 321 - spec = await $RefParser.bundle(absolutePathOrUrl); 326 + let arrayBuffer: ArrayBuffer | undefined; 327 + // boolean signals whether the file has **definitely** changed 328 + let hasChanged: boolean | undefined; 329 + let response: Response | undefined; 330 + 331 + // no support for watching files and objects for now 332 + if (resolvedInput.type === 'url') { 333 + const request = await sendRequest({ 334 + init: { 335 + headers: watch.headers, 336 + method: 'HEAD', 337 + }, 338 + url: resolvedInput.path, 339 + }); 340 + response = request.response; 341 + 342 + if (!response.ok) { 343 + // assume the server is no longer running 344 + // do nothing, it might be restarted later 345 + return { 346 + error: 'not-ok', 347 + response, 348 + }; 349 + } 350 + 351 + if (response.status === 304) { 352 + return { 353 + error: 'not-modified', 354 + response, 355 + }; 356 + } 357 + 358 + if (hasChanged === undefined) { 359 + const eTag = response.headers.get('ETag'); 360 + if (eTag) { 361 + hasChanged = eTag !== watch.headers.get('If-None-Match'); 362 + 363 + if (hasChanged) { 364 + watch.headers.set('If-None-Match', eTag); 365 + } 366 + } 367 + } 368 + 369 + if (hasChanged === undefined) { 370 + const lastModified = response.headers.get('Last-Modified'); 371 + if (lastModified) { 372 + hasChanged = lastModified !== watch.headers.get('If-Modified-Since'); 373 + 374 + if (hasChanged) { 375 + watch.headers.set('If-Modified-Since', lastModified); 376 + } 377 + } 378 + } 379 + 380 + // we definitely know the input has not changed 381 + if (hasChanged === false) { 382 + return { 383 + error: 'not-modified', 384 + response, 385 + }; 386 + } 387 + 388 + const fileRequest = await sendRequest({ 389 + init: { 390 + method: 'GET', 391 + }, 392 + url: resolvedInput.path, 393 + }); 394 + response = fileRequest.response; 395 + 396 + if (!response.ok) { 397 + // assume the server is no longer running 398 + // do nothing, it might be restarted later 399 + return { 400 + error: 'not-ok', 401 + response, 402 + }; 403 + } 404 + 405 + arrayBuffer = response.body 406 + ? await response.arrayBuffer() 407 + : new ArrayBuffer(0); 408 + 409 + if (hasChanged === undefined) { 410 + const content = new TextDecoder().decode(arrayBuffer); 411 + hasChanged = content !== watch.lastValue; 412 + watch.lastValue = content; 413 + } 322 414 } 323 415 324 - return spec; 416 + if (hasChanged === false) { 417 + return { 418 + error: 'not-modified', 419 + response: response!, 420 + }; 421 + } 422 + 423 + const data = await refParser.bundle({ 424 + arrayBuffer, 425 + pathOrUrlOrSchema: undefined, 426 + resolvedInput, 427 + }); 428 + 429 + return { 430 + data, 431 + }; 432 + }; 433 + 434 + const getWatch = ( 435 + userConfig: Pick<ClientConfig, 'watch'> & Pick<Config, 'input'>, 436 + ): Config['watch'] => { 437 + let watch: Config['watch'] = { 438 + enabled: false, 439 + interval: 1000, 440 + }; 441 + // we cannot watch spec passed as an object 442 + if (typeof userConfig.input.path !== 'string') { 443 + return watch; 444 + } 445 + if (typeof userConfig.watch === 'boolean') { 446 + watch.enabled = userConfig.watch; 447 + } else if (typeof userConfig.watch === 'number') { 448 + watch = { 449 + enabled: true, 450 + interval: userConfig.watch, 451 + }; 452 + } else if (userConfig.watch) { 453 + watch = { 454 + ...watch, 455 + ...userConfig.watch, 456 + }; 457 + } 458 + return watch; 325 459 }; 326 460 327 461 const initConfigs = async (userConfig: UserConfig): Promise<Config[]> => { ··· 406 540 output, 407 541 request, 408 542 useOptions, 543 + watch: getWatch({ ...userConfig, input }), 409 544 }); 410 545 411 546 if (logs.level === 'debug') { ··· 424 559 */ 425 560 export async function createClient( 426 561 userConfig: UserConfig, 427 - ): Promise<ReadonlyArray<Client>> { 562 + ): Promise<ReadonlyArray<Client | IR.Context>> { 428 563 let configs: Config[] = []; 429 564 430 565 try { ··· 438 573 const templates = registerHandlebarTemplates(); 439 574 Performance.end('handlebars'); 440 575 441 - const pCreateClient = (config: Config) => async () => { 576 + const pCreateClient = async ({ 577 + config, 578 + watch: _watch, 579 + }: { 580 + config: Config; 581 + watch?: { 582 + headers: Headers; 583 + lastValue: string | undefined; 584 + }; 585 + }) => { 586 + const inputPath = config.input.path; 587 + 588 + const watch = _watch || { 589 + headers: new Headers(), 590 + lastValue: undefined, 591 + }; 592 + 442 593 Performance.start('spec'); 443 - const spec = await getSpec({ config }); 594 + const { data, error, response } = await getSpec({ inputPath, watch }); 444 595 Performance.end('spec'); 445 596 597 + // throw on first run if there's an error to preserve user experience 598 + // if in watch mode, subsequent errors won't throw to gracefully handle 599 + // cases where server might be reloading 600 + if (error && !_watch) { 601 + throw new Error( 602 + `Request failed with status ${response.status}: ${response.statusText}`, 603 + ); 604 + } 605 + 446 606 let client: Client | undefined; 447 607 let context: IR.Context | undefined; 448 608 449 - Performance.start('parser'); 450 - if ( 451 - config.experimentalParser && 452 - !isLegacyClient(config) && 453 - !legacyNameFromConfig(config) 454 - ) { 455 - context = parseExperimental({ config, spec }); 456 - } 609 + if (data) { 610 + if (_watch) { 611 + console.log(`⏳ Input changed, generating from ${inputPath}`); 612 + } else { 613 + console.log(`⏳ Generating from ${inputPath}`); 614 + } 457 615 458 - // fallback to legacy parser 459 - if (!context) { 460 - const parsed = parseLegacy({ openApi: spec }); 461 - client = postProcessClient(parsed); 462 - } 463 - Performance.end('parser'); 616 + Performance.start('parser'); 617 + if ( 618 + config.experimentalParser && 619 + !isLegacyClient(config) && 620 + !legacyNameFromConfig(config) 621 + ) { 622 + context = parseExperimental({ config, spec: data }); 623 + } 464 624 465 - logClientMessage({ config }); 625 + // fallback to legacy parser 626 + if (!context) { 627 + const parsed = parseLegacy({ openApi: data }); 628 + client = postProcessClient(parsed, config); 629 + } 630 + Performance.end('parser'); 466 631 467 - Performance.start('generator'); 468 - if (context) { 469 - await generateOutput({ context }); 470 - } else if (client) { 471 - await generateLegacyOutput({ client, openApi: spec, templates }); 472 - } 473 - Performance.end('generator'); 632 + Performance.start('generator'); 633 + if (context) { 634 + await generateOutput({ context }); 635 + } else if (client) { 636 + await generateLegacyOutput({ client, openApi: data, templates }); 637 + } 638 + Performance.end('generator'); 474 639 475 - Performance.start('postprocess'); 476 - if (!config.dryRun) { 477 - processOutput({ config }); 640 + Performance.start('postprocess'); 641 + if (!config.dryRun) { 642 + processOutput({ config }); 643 + 644 + console.log(`🚀 Done! Your output is in ${config.output.path}`); 645 + } 646 + Performance.end('postprocess'); 647 + } 478 648 479 - console.log('✨ Done! Your client is located in:', config.output.path); 649 + if (config.watch.enabled && typeof inputPath === 'string') { 650 + setTimeout(() => { 651 + pCreateClient({ config, watch }); 652 + }, config.watch.interval); 480 653 } 481 - Performance.end('postprocess'); 482 654 483 655 return context || client; 484 656 }; 485 657 486 - const clients: Array<Client> = []; 487 - 488 - const pClients = configs.map((config) => pCreateClient(config)); 489 - for (const pClient of pClients) { 490 - const client = await pClient(); 491 - if (client && 'version' in client) { 492 - clients.push(client); 493 - } 494 - } 658 + const clients = await Promise.all( 659 + configs.map((config) => pCreateClient({ config })), 660 + ); 661 + const result = clients.filter((client) => Boolean(client)) as ReadonlyArray< 662 + Client | IR.Context 663 + >; 495 664 496 665 Performance.end('createClient'); 497 666 498 - if (configs[0]!.logs.level === 'debug') { 667 + const config = configs[0]; 668 + if (config && config.logs.level === 'debug') { 499 669 const perfReport = new PerformanceReport({ 500 670 totalMark: 'createClient', 501 671 }); ··· 511 681 }); 512 682 } 513 683 514 - return clients; 684 + return result; 515 685 } catch (error) { 516 686 const config = configs[0] as Config | undefined; 517 687 const dryRun = config ? config.dryRun : userConfig?.dryRun;
+1 -1
packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts
··· 645 645 const irSchema: IR.SchemaObject = {}; 646 646 647 647 // refs using unicode characters become encoded, didn't investigate why 648 - // but the suspicion is this comes from `@apidevtools/json-schema-ref-parser` 648 + // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` 649 649 irSchema.$ref = decodeURI(schema.$ref); 650 650 651 651 return irSchema;
+1 -1
packages/openapi-ts/src/openApi/3.1.x/parser/schema.ts
··· 682 682 const irSchema = initIrSchema({ schema }); 683 683 684 684 // refs using unicode characters become encoded, didn't investigate why 685 - // but the suspicion is this comes from `@apidevtools/json-schema-ref-parser` 685 + // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` 686 686 irSchema.$ref = decodeURI(schema.$ref); 687 687 688 688 return irSchema;
+6
packages/openapi-ts/src/openApi/common/interfaces/client.ts
··· 1 + import type { Config } from '../../../types/config'; 1 2 import type { OpenApiParameter } from '../../v3/interfaces/OpenApiParameter'; 2 3 3 4 export interface ModelComposition ··· 150 151 } 151 152 152 153 export interface Client { 154 + /** 155 + * Configuration for parsing and generating the output. This 156 + * is a mix of user-provided and default values. 157 + */ 158 + config: Config; 153 159 models: Model[]; 154 160 operations: Operation[]; 155 161 server: string;
+5 -1
packages/openapi-ts/src/openApi/index.ts
··· 35 35 * all the models, services and schema's we should output. 36 36 * @param openApi The OpenAPI spec that we have loaded from disk. 37 37 */ 38 - export function parseLegacy({ openApi }: { openApi: unknown }): Client { 38 + export function parseLegacy({ 39 + openApi, 40 + }: { 41 + openApi: unknown; 42 + }): Omit<Client, 'config'> { 39 43 const spec = openApi as LegacyOpenApi; 40 44 41 45 if ('openapi' in spec) {
+1 -1
packages/openapi-ts/src/openApi/v2/index.ts
··· 10 10 * all the models, operations and schema's we should output. 11 11 * @param openApi The OpenAPI spec that we have loaded from disk. 12 12 */ 13 - export const parse = (openApi: OpenApi): Client => { 13 + export const parse = (openApi: OpenApi): Omit<Client, 'config'> => { 14 14 const version = getServiceVersion(openApi.info.version); 15 15 const server = getServer(openApi); 16 16 const { models, types } = getModels(openApi);
+1 -1
packages/openapi-ts/src/openApi/v3/index.ts
··· 10 10 * all the models, operations and schema's we should output. 11 11 * @param openApi The OpenAPI spec that we have loaded from disk. 12 12 */ 13 - export const parse = (openApi: OpenApi): Client => { 13 + export const parse = (openApi: OpenApi): Omit<Client, 'config'> => { 14 14 const version = getServiceVersion(openApi.info.version); 15 15 const server = getServer(openApi); 16 16 const { models, types } = getModels(openApi);
+8
packages/openapi-ts/src/plugins/@hey-api/schemas/__tests__/schemas.test.ts
··· 51 51 }, 52 52 }, 53 53 useOptions: true, 54 + watch: { 55 + enabled: false, 56 + interval: 1000, 57 + }, 54 58 }); 55 59 56 60 if ('openapi' in openApi) { ··· 127 131 }, 128 132 }, 129 133 useOptions: true, 134 + watch: { 135 + enabled: false, 136 + interval: 1000, 137 + }, 130 138 }); 131 139 132 140 const schema: OpenApiV3Schema = {
+2 -2
packages/openapi-ts/src/plugins/@hey-api/schemas/plugin.ts
··· 65 65 66 66 if ('$ref' in schema) { 67 67 // refs using unicode characters become encoded, didn't investigate why 68 - // but the suspicion is this comes from `@apidevtools/json-schema-ref-parser` 68 + // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` 69 69 schema.$ref = decodeURI(schema.$ref); 70 70 return schema; 71 71 } ··· 163 163 164 164 if (schema.$ref) { 165 165 // refs using unicode characters become encoded, didn't investigate why 166 - // but the suspicion is this comes from `@apidevtools/json-schema-ref-parser` 166 + // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` 167 167 schema.$ref = decodeURI(schema.$ref); 168 168 } 169 169
+19
packages/openapi-ts/src/plugins/@hey-api/sdk/__tests__/plugin.test.ts
··· 6 6 import { openApi } from '../../../../generate/__tests__/mocks'; 7 7 import { TypeScriptFile } from '../../../../generate/files'; 8 8 import type { Operation } from '../../../../types/client'; 9 + import type { Config } from '../../../../types/config'; 9 10 import type { Files } from '../../../../types/utils'; 10 11 import { setConfig } from '../../../../utils/config'; 11 12 import { handlerLegacy } from '../plugin-legacy'; ··· 52 53 }, 53 54 }, 54 55 useOptions: false, 56 + watch: { 57 + enabled: false, 58 + interval: 1000, 59 + }, 55 60 }); 56 61 57 62 const client: Parameters<typeof handlerLegacy>[0]['client'] = { 63 + config: {} as Config, 58 64 models: [], 59 65 server: 'http://localhost:8080', 60 66 services: [ ··· 143 149 }; 144 150 145 151 const client: Parameters<typeof handlerLegacy>[0]['client'] = { 152 + config: {} as Config, 146 153 models: [], 147 154 server: 'http://localhost:8080', 148 155 services: [ ··· 196 203 }, 197 204 }, 198 205 useOptions: false, 206 + watch: { 207 + enabled: false, 208 + interval: 1000, 209 + }, 199 210 }); 200 211 201 212 const files: Files = {}; ··· 265 276 }, 266 277 }, 267 278 useOptions: false, 279 + watch: { 280 + enabled: false, 281 + interval: 1000, 282 + }, 268 283 }); 269 284 270 285 const files: Files = {}; ··· 336 351 }, 337 352 }, 338 353 useOptions: false, 354 + watch: { 355 + enabled: false, 356 + interval: 1000, 357 + }, 339 358 }); 340 359 341 360 const files: Files = {};
+6
packages/openapi-ts/src/plugins/@hey-api/typescript/__tests__/plugin.test.ts
··· 4 4 5 5 import { openApi } from '../../../../generate/__tests__/mocks'; 6 6 import { TypeScriptFile } from '../../../../generate/files'; 7 + import type { Config } from '../../../../types/config'; 7 8 import { setConfig } from '../../../../utils/config'; 8 9 import { handlerLegacy } from '../plugin-legacy'; 9 10 ··· 50 51 }, 51 52 }, 52 53 useOptions: true, 54 + watch: { 55 + enabled: false, 56 + interval: 1000, 57 + }, 53 58 }); 54 59 55 60 const client: Parameters<typeof handlerLegacy>[0]['client'] = { 61 + config: {} as Config, 56 62 models: [ 57 63 { 58 64 $refs: [],
+24
packages/openapi-ts/src/types/config.ts
··· 245 245 * @default true 246 246 */ 247 247 useOptions?: boolean; 248 + /** 249 + * Regenerate the client when the input file changes? 250 + * 251 + * @default false 252 + */ 253 + watch?: 254 + | boolean 255 + | number 256 + | { 257 + /** 258 + * Regenerate the client when the input file changes? 259 + * 260 + * @default false 261 + */ 262 + enabled?: boolean; 263 + /** 264 + * How often should we attempt to detect the input file change? 265 + * 266 + * @default 1000 267 + */ 268 + interval?: number; 269 + }; 248 270 } 249 271 250 272 export interface UserConfig extends ClientConfig {} ··· 259 281 | 'output' 260 282 | 'plugins' 261 283 | 'request' 284 + | 'watch' 262 285 > & 263 286 Pick<ClientConfig, 'base' | 'name' | 'request'> & { 264 287 client: Extract<Required<ClientConfig>['client'], object>; ··· 270 293 ExtractArrayOfObjects<ReadonlyArray<ClientPlugins>, { name: string }>, 271 294 'name' 272 295 >; 296 + watch: Extract<ClientConfig['watch'], object>; 273 297 };
+8
packages/openapi-ts/src/utils/__tests__/handlebars.test.ts
··· 48 48 }, 49 49 }, 50 50 useOptions: false, 51 + watch: { 52 + enabled: false, 53 + interval: 1000, 54 + }, 51 55 }); 52 56 registerHandlebarHelpers(); 53 57 const helpers = Object.keys(Handlebars.helpers); ··· 101 105 }, 102 106 }, 103 107 useOptions: false, 108 + watch: { 109 + enabled: false, 110 + interval: 1000, 111 + }, 104 112 }); 105 113 const templates = registerHandlebarTemplates(); 106 114 expect(templates.core.settings).toBeDefined();
+4
packages/openapi-ts/src/utils/__tests__/parse.test.ts
··· 33 33 }, 34 34 }, 35 35 useOptions: false, 36 + watch: { 37 + enabled: false, 38 + interval: 1000, 39 + }, 36 40 }; 37 41 38 42 const options1: Parameters<typeof setConfig>[0] = {
+2 -2
packages/openapi-ts/src/utils/__tests__/postprocess.test.ts
··· 58 58 }, 59 59 }, 60 60 }); 61 - const { services } = postProcessClient(parserClient); 61 + const { services } = postProcessClient(parserClient, {} as Config); 62 62 63 63 expect(services).toHaveLength(1); 64 64 expect(services[0]!.name).toEqual('Default'); ··· 91 91 }, 92 92 }, 93 93 }); 94 - const { services } = postProcessClient(parserClient); 94 + const { services } = postProcessClient(parserClient, {} as Config); 95 95 96 96 expect(services).toHaveLength(1); 97 97 expect(services[0]!.name).toEqual('Default');
+6 -1
packages/openapi-ts/src/utils/postprocess.ts
··· 1 1 import type { Client as ParserClient, Model } from '../openApi'; 2 2 import { sanitizeNamespaceIdentifier } from '../openApi'; 3 3 import type { Client, Operation, Service } from '../types/client'; 4 + import type { Config } from '../types/config'; 4 5 import { getConfig, legacyNameFromConfig } from './config'; 5 6 import { sort } from './sort'; 6 7 import { stringCase } from './stringCase'; ··· 10 11 * Post process client 11 12 * @param client Client object with all the models, services, etc. 12 13 */ 13 - export function postProcessClient(client: ParserClient): Client { 14 + export function postProcessClient( 15 + client: Omit<ParserClient, 'config'>, 16 + config: Config, 17 + ): Client { 14 18 return { 15 19 ...client, 20 + config, 16 21 models: client.models.map((model) => postProcessModel(model)), 17 22 services: postProcessOperations(client.operations).map(postProcessService), 18 23 types: {},
+2 -2
packages/openapi-ts/src/utils/ref.ts
··· 13 13 const parts = refToParts($ref); 14 14 const name = parts[parts.length - 1]!; 15 15 // refs using unicode characters become encoded, didn't investigate why 16 - // but the suspicion is this comes from `@apidevtools/json-schema-ref-parser` 16 + // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` 17 17 return decodeURI(name); 18 18 }; 19 19 ··· 31 31 spec: Record<string, any>; 32 32 }): T => { 33 33 // refs using unicode characters become encoded, didn't investigate why 34 - // but the suspicion is this comes from `@apidevtools/json-schema-ref-parser` 34 + // but the suspicion is this comes from `@hey-api/json-schema-ref-parser` 35 35 const parts = refToParts(decodeURI($ref)); 36 36 37 37 let current = spec;
+6 -6
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/index.ts.snap
··· 93 93 }; 94 94 } 95 95 96 - if (opts.parseAs === 'stream') { 96 + const parseAs = 97 + (opts.parseAs === 'auto' 98 + ? getParseAs(response.headers.get('Content-Type')) 99 + : opts.parseAs) ?? 'json'; 100 + 101 + if (parseAs === 'stream') { 97 102 return { 98 103 data: response.body, 99 104 ...result, 100 105 }; 101 106 } 102 - 103 - const parseAs = 104 - (opts.parseAs === 'auto' 105 - ? getParseAs(response.headers.get('Content-Type')) 106 - : opts.parseAs) ?? 'json'; 107 107 108 108 let data = await response[parseAs](); 109 109 if (parseAs === 'json') {
+4 -2
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle/client/utils.ts.snap
··· 327 327 */ 328 328 export const getParseAs = ( 329 329 contentType: string | null, 330 - ): Exclude<Config['parseAs'], 'auto' | 'stream'> => { 330 + ): Exclude<Config['parseAs'], 'auto'> => { 331 331 if (!contentType) { 332 - return; 332 + // If no Content-Type header is provided, the best we can do is return the raw response body, 333 + // which is effectively the same as the 'stream' option. 334 + return 'stream'; 333 335 } 334 336 335 337 const cleanContent = contentType.split(';')[0]?.trim();
+6 -6
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/index.ts.snap
··· 93 93 }; 94 94 } 95 95 96 - if (opts.parseAs === 'stream') { 96 + const parseAs = 97 + (opts.parseAs === 'auto' 98 + ? getParseAs(response.headers.get('Content-Type')) 99 + : opts.parseAs) ?? 'json'; 100 + 101 + if (parseAs === 'stream') { 97 102 return { 98 103 data: response.body, 99 104 ...result, 100 105 }; 101 106 } 102 - 103 - const parseAs = 104 - (opts.parseAs === 'auto' 105 - ? getParseAs(response.headers.get('Content-Type')) 106 - : opts.parseAs) ?? 'json'; 107 107 108 108 let data = await response[parseAs](); 109 109 if (parseAs === 'json') {
+4 -2
packages/openapi-ts/test/__snapshots__/test/generated/v3-hey-api-client-fetch-bundle_transform/client/utils.ts.snap
··· 327 327 */ 328 328 export const getParseAs = ( 329 329 contentType: string | null, 330 - ): Exclude<Config['parseAs'], 'auto' | 'stream'> => { 330 + ): Exclude<Config['parseAs'], 'auto'> => { 331 331 if (!contentType) { 332 - return; 332 + // If no Content-Type header is provided, the best we can do is return the raw response body, 333 + // which is effectively the same as the 'stream' option. 334 + return 'stream'; 333 335 } 334 336 335 337 const cleanContent = contentType.split(';')[0]?.trim();
+2 -2
packages/openapi-ts/test/bin.test.ts
··· 14 14 '--dry-run', 15 15 'true', 16 16 ]); 17 - expect(result.stdout.toString()).toContain('Creating Fetch client'); 17 + expect(result.stdout.toString()).toContain('Generating from'); 18 18 expect(result.stderr.toString()).toContain('Duplicate operationId'); 19 19 }); 20 20 ··· 117 117 '--dry-run', 118 118 'true', 119 119 ]); 120 - expect(result.stdout.toString()).toContain('Creating Fetch client'); 120 + expect(result.stdout.toString()).toContain('Generating from'); 121 121 expect(result.stderr.toString()).toContain('Duplicate operationId'); 122 122 }); 123 123
+82
packages/openapi-ts/test/openapi-ts.config.ts
··· 1 + import { defineConfig } from '../src'; 2 + 3 + export default defineConfig({ 4 + client: { 5 + // bundle: true, 6 + // name: '@hey-api/client-axios', 7 + name: '@hey-api/client-fetch', 8 + // name: 'legacy/xhr', 9 + }, 10 + // experimentalParser: true, 11 + input: { 12 + // exclude: '^#/components/schemas/ModelWithCircularReference$', 13 + // include: 14 + // '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$', 15 + // path: './test/spec/3.0.x/validators.json', 16 + path: './test/spec/v3-transforms.json', 17 + // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 18 + // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', 19 + }, 20 + logs: { 21 + // level: 'debug', 22 + path: './logs', 23 + }, 24 + // name: 'foo', 25 + output: { 26 + // case: 'snake_case', 27 + // format: 'prettier', 28 + // lint: 'eslint', 29 + path: './packages/openapi-ts/test/generated/sample/', 30 + }, 31 + plugins: [ 32 + // @ts-ignore 33 + { 34 + // name: '@hey-api/schemas', 35 + // type: 'json', 36 + }, 37 + // @ts-ignore 38 + { 39 + asClass: true, 40 + // auth: false, 41 + // include... 42 + name: '@hey-api/sdk', 43 + // operationId: false, 44 + // serviceNameBuilder: '^Parameters', 45 + // transformer: '@hey-api/transformers', 46 + transformer: true, 47 + // validator: 'zod', 48 + }, 49 + // @ts-ignore 50 + { 51 + dates: true, 52 + // name: '@hey-api/transformers', 53 + }, 54 + // @ts-ignore 55 + { 56 + // enums: 'typescript', 57 + // enums: 'typescript+namespace', 58 + enums: 'javascript', 59 + // exportInlineEnums: true, 60 + // identifierCase: 'preserve', 61 + name: '@hey-api/typescript', 62 + // tree: true, 63 + }, 64 + // @ts-ignore 65 + { 66 + // name: 'fastify', 67 + }, 68 + // @ts-ignore 69 + { 70 + // name: '@tanstack/vue-query', 71 + }, 72 + // @ts-ignore 73 + { 74 + name: 'zod', 75 + }, 76 + ], 77 + // useOptions: false, 78 + watch: { 79 + enabled: true, 80 + interval: 1_000, 81 + }, 82 + });
-81
packages/openapi-ts/test/sample.cjs
··· 1 - const path = require('node:path'); 2 - 3 - const main = async () => { 4 - /** @type {import('../src').UserConfig} */ 5 - const config = { 6 - client: { 7 - // bundle: true, 8 - // name: '@hey-api/client-axios', 9 - name: '@hey-api/client-fetch', 10 - // name: 'legacy/xhr', 11 - }, 12 - experimentalParser: true, 13 - input: { 14 - // exclude: '^#/components/schemas/ModelWithCircularReference$', 15 - // include: 16 - // '^(#/components/schemas/import|#/paths/api/v{api-version}/simple/options)$', 17 - path: './test/spec/3.0.x/validators.json', 18 - // path: './test/spec/v3-transforms.json', 19 - // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 20 - // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', 21 - }, 22 - logs: { 23 - // level: 'debug', 24 - path: './logs', 25 - }, 26 - // name: 'foo', 27 - output: { 28 - // case: 'snake_case', 29 - // format: 'prettier', 30 - // lint: 'eslint', 31 - path: './test/generated/sample/', 32 - }, 33 - plugins: [ 34 - { 35 - // name: '@hey-api/schemas', 36 - // type: 'json', 37 - }, 38 - { 39 - asClass: true, 40 - // auth: false, 41 - // include... 42 - name: '@hey-api/sdk', 43 - // operationId: false, 44 - // serviceNameBuilder: '^Parameters', 45 - // transformer: '@hey-api/transformers', 46 - transformer: true, 47 - // validator: 'zod', 48 - }, 49 - { 50 - dates: true, 51 - // name: '@hey-api/transformers', 52 - }, 53 - { 54 - // enums: 'typescript', 55 - // enums: 'typescript+namespace', 56 - enums: 'javascript', 57 - // exportInlineEnums: true, 58 - // identifierCase: 'preserve', 59 - name: '@hey-api/typescript', 60 - // tree: true, 61 - }, 62 - { 63 - // name: 'fastify', 64 - }, 65 - { 66 - // name: '@tanstack/vue-query', 67 - }, 68 - { 69 - name: 'zod', 70 - }, 71 - ], 72 - // useOptions: false, 73 - }; 74 - 75 - const { createClient } = await import( 76 - path.resolve(process.cwd(), 'dist', 'index.cjs') 77 - ); 78 - await createClient(config); 79 - }; 80 - 81 - main();
+20 -27
pnpm-lock.yaml
··· 581 581 582 582 packages/openapi-ts: 583 583 dependencies: 584 - '@apidevtools/json-schema-ref-parser': 585 - specifier: 11.7.3 586 - version: 11.7.3 584 + '@hey-api/json-schema-ref-parser': 585 + specifier: 0.0.2 586 + version: 0.0.2 587 587 c12: 588 588 specifier: 2.0.1 589 589 version: 2.0.1 ··· 973 973 974 974 '@antfu/utils@0.7.10': 975 975 resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} 976 - 977 - '@apidevtools/json-schema-ref-parser@11.7.3': 978 - resolution: {integrity: sha512-WApSdLdXEBb/1FUPca2lteASewEfpjEYJ8oXZP+0gExK5qSfsEKBKcA+WjY6Q4wvXwyv0+W6Kvc372pSceib9w==} 979 - engines: {node: '>= 16'} 980 976 981 977 '@arethetypeswrong/cli@0.16.4': 982 978 resolution: {integrity: sha512-qMmdVlJon5FtA+ahn0c1oAVNxiq4xW5lqFiTZ21XHIeVwAVIQ+uRz4UEivqRMsjVV1grzRgJSKqaOrq1MvlVyQ==} ··· 2239 2235 '@fontsource/fira-mono@5.0.0': 2240 2236 resolution: {integrity: sha512-IsinH/oLYJyv/sQv7SbKmjoAXZsSjm6Q1Tz5GBBXCXi3Jg9MzXmKvWm9bSLC8lFI6CDsi8GkH/DAgZ98t8bhTQ==} 2241 2237 2238 + '@hey-api/json-schema-ref-parser@0.0.2': 2239 + resolution: {integrity: sha512-cwO2OAvMxyX+U6sPDWQ8+23OOOGvxH1pLODXA9rJCdCbxG9kUe+Uon3YPgQtT7uSkLis/BoWccmtqH5VGCs8gA==} 2240 + engines: {node: '>= 16'} 2241 + 2242 2242 '@humanwhocodes/config-array@0.12.3': 2243 2243 resolution: {integrity: sha512-jsNnTBlMWuTpDkeE3on7+dWJi0D6fdDfeANj/w7MpS8ztROCoLvIO2nG0CcFj+E4k8j4QrSTh4Oryi3i2G669g==} 2244 2244 engines: {node: '>=10.10.0'} ··· 6776 6776 magic-string@0.30.10: 6777 6777 resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} 6778 6778 6779 - magic-string@0.30.11: 6780 - resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} 6781 - 6782 6779 magic-string@0.30.12: 6783 6780 resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} 6784 6781 ··· 9877 9874 9878 9875 '@antfu/utils@0.7.10': {} 9879 9876 9880 - '@apidevtools/json-schema-ref-parser@11.7.3': 9881 - dependencies: 9882 - '@jsdevtools/ono': 7.1.3 9883 - '@types/json-schema': 7.0.15 9884 - js-yaml: 4.1.0 9885 - 9886 9877 '@arethetypeswrong/cli@0.16.4': 9887 9878 dependencies: 9888 9879 '@arethetypeswrong/core': 0.16.4 ··· 11322 11313 11323 11314 '@fontsource/fira-mono@5.0.0': {} 11324 11315 11316 + '@hey-api/json-schema-ref-parser@0.0.2': 11317 + dependencies: 11318 + '@jsdevtools/ono': 7.1.3 11319 + '@types/json-schema': 7.0.15 11320 + js-yaml: 4.1.0 11321 + 11325 11322 '@humanwhocodes/config-array@0.12.3': 11326 11323 dependencies: 11327 11324 '@humanwhocodes/object-schema': 2.0.3 ··· 12994 12991 12995 12992 '@types/cors@2.8.17': 12996 12993 dependencies: 12997 - '@types/node': 20.14.10 12994 + '@types/node': 22.8.5 12998 12995 12999 12996 '@types/cross-spawn@6.0.6': 13000 12997 dependencies: ··· 14508 14505 code-red@1.0.4: 14509 14506 dependencies: 14510 14507 '@jridgewell/sourcemap-codec': 1.5.0 14511 - '@types/estree': 1.0.5 14508 + '@types/estree': 1.0.6 14512 14509 acorn: 8.14.0 14513 14510 estree-walker: 3.0.3 14514 14511 periscopic: 3.1.0 ··· 15286 15283 15287 15284 estree-walker@3.0.3: 15288 15285 dependencies: 15289 - '@types/estree': 1.0.5 15286 + '@types/estree': 1.0.6 15290 15287 15291 15288 esutils@2.0.3: {} 15292 15289 ··· 15978 15975 15979 15976 is-reference@3.0.2: 15980 15977 dependencies: 15981 - '@types/estree': 1.0.5 15978 + '@types/estree': 1.0.6 15982 15979 15983 15980 is-stream@2.0.1: {} 15984 15981 ··· 16452 16449 lru-cache@7.18.3: {} 16453 16450 16454 16451 magic-string@0.30.10: 16455 - dependencies: 16456 - '@jridgewell/sourcemap-codec': 1.5.0 16457 - 16458 - magic-string@0.30.11: 16459 16452 dependencies: 16460 16453 '@jridgewell/sourcemap-codec': 1.5.0 16461 16454 ··· 17121 17114 17122 17115 periscopic@3.1.0: 17123 17116 dependencies: 17124 - '@types/estree': 1.0.5 17117 + '@types/estree': 1.0.6 17125 17118 estree-walker: 3.0.3 17126 17119 is-reference: 3.0.2 17127 17120 ··· 18284 18277 '@ampproject/remapping': 2.3.0 18285 18278 '@jridgewell/sourcemap-codec': 1.5.0 18286 18279 '@jridgewell/trace-mapping': 0.3.25 18287 - '@types/estree': 1.0.5 18280 + '@types/estree': 1.0.6 18288 18281 acorn: 8.14.0 18289 18282 aria-query: 5.3.0 18290 18283 axobject-query: 4.1.0 ··· 18293 18286 estree-walker: 3.0.3 18294 18287 is-reference: 3.0.2 18295 18288 locate-character: 3.0.0 18296 - magic-string: 0.30.11 18289 + magic-string: 0.30.12 18297 18290 periscopic: 3.1.0 18298 18291 18299 18292 svg-tags@1.0.0: {}