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

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2602 from hey-api/feat--multiple-configs-functionality

feat: multiple configs functionality

authored by

Lubos and committed by
GitHub
4f90ded6 34bc6e75

+2079 -1207
+5
.changeset/dry-jeans-ring.md
··· 1 + --- 2 + '@hey-api/openapi-ts': patch 3 + --- 4 + 5 + feat: support multiple configurations
+102 -25
docs/openapi-ts/configuration.md
··· 38 38 39 39 Alternatively, you can use `openapi-ts.config.js` and configure the export statement depending on your project setup. 40 40 41 - <!-- 42 - TODO: uncomment after c12 supports multiple configs 43 - see https://github.com/unjs/c12/issues/92 44 - --> 45 - <!-- ### Multiple Clients 46 - 47 - If you want to generate multiple clients with a single `openapi-ts` command, you can provide an array of configuration objects. 48 - 49 - ```js 50 - import { defineConfig } from '@hey-api/openapi-ts'; 51 - 52 - export default defineConfig([ 53 - { 54 - input: 'path/to/openapi_one.json', 55 - output: 'src/client_one', 56 - plugins: ['legacy/fetch'], 57 - }, 58 - { 59 - input: 'path/to/openapi_two.json', 60 - output: 'src/client_two', 61 - plugins: ['legacy/axios'], 62 - }, 63 - ]) 64 - ``` --> 65 - 66 41 ## Input 67 42 68 43 You must provide an input so we can load your OpenAPI specification. ··· 162 137 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! 163 138 164 139 You can learn more on the [Output](/openapi-ts/output) page. 140 + 141 + ## Advanced 142 + 143 + More complex configuration scenarios can be handled by providing an array of inputs, outputs, or configurations. 144 + 145 + ### Multiple jobs 146 + 147 + Throughout this documentation, we generally reference single job configurations. However, you can easily run multiple jobs by providing an array of configuration objects. 148 + 149 + ::: code-group 150 + 151 + ```js [config] 152 + export default [ 153 + { 154 + input: 'foo.yaml', 155 + output: 'src/foo', 156 + }, 157 + { 158 + input: 'bar.yaml', 159 + output: 'src/bar', 160 + }, 161 + ]; 162 + ``` 163 + 164 + ```md [example] 165 + src/ 166 + ├── foo/ 167 + │ ├── client/ 168 + │ ├── core/ 169 + │ ├── client.gen.ts 170 + │ ├── index.ts 171 + │ ├── sdk.gen.ts 172 + │ └── types.gen.ts 173 + └── bar/ 174 + ├── client/ 175 + ├── core/ 176 + ├── client.gen.ts 177 + ├── index.ts 178 + ├── sdk.gen.ts 179 + └── types.gen.ts 180 + ``` 181 + 182 + ::: 183 + 184 + ### Job matrix 185 + 186 + Reusing configuration across multiple jobs is possible by defining a job matrix. You can create a job matrix by providing `input` and `output` arrays of matching length. 187 + 188 + ::: code-group 189 + 190 + ```js [config] 191 + export default { 192 + input: ['foo.yaml', 'bar.yaml'], 193 + output: ['src/foo', 'src/bar'], 194 + }; 195 + ``` 196 + 197 + ```md [example] 198 + src/ 199 + ├── foo/ 200 + │ ├── client/ 201 + │ ├── core/ 202 + │ ├── client.gen.ts 203 + │ ├── index.ts 204 + │ ├── sdk.gen.ts 205 + │ └── types.gen.ts 206 + └── bar/ 207 + ├── client/ 208 + ├── core/ 209 + ├── client.gen.ts 210 + ├── index.ts 211 + ├── sdk.gen.ts 212 + └── types.gen.ts 213 + ``` 214 + 215 + ::: 216 + 217 + ### Merging inputs 218 + 219 + You can merge inputs by defining multiple inputs and a single output. 220 + 221 + ::: code-group 222 + 223 + ```js [config] 224 + export default { 225 + input: ['foo.yaml', 'bar.yaml'], 226 + output: 'src/client', 227 + }; 228 + ``` 229 + 230 + ```md [example] 231 + src/ 232 + └── client/ 233 + ├── client/ 234 + ├── core/ 235 + ├── client.gen.ts 236 + ├── index.ts 237 + ├── sdk.gen.ts 238 + └── types.gen.ts 239 + ``` 240 + 241 + ::: 165 242 166 243 ## API 167 244
+2
docs/openapi-ts/configuration/input.md
··· 54 54 55 55 ::: 56 56 57 + You can learn more about complex use cases in the [Advanced](/openapi-ts/configuration#advanced) section. 58 + 57 59 ::: info 58 60 If you use an HTTPS URL with a self-signed certificate in development, you will need to set [`NODE_TLS_REJECT_UNAUTHORIZED=0`](https://github.com/hey-api/openapi-ts/issues/276#issuecomment-2043143501) in your environment. 59 61 :::
+2
docs/openapi-ts/configuration/output.md
··· 34 34 35 35 ::: 36 36 37 + You can learn more about complex use cases in the [Advanced](/openapi-ts/configuration#advanced) section. 38 + 37 39 ## File Name 38 40 39 41 You can customize the naming and casing pattern for files using the `fileName` option.
+1 -1
package.json
··· 65 65 "rollup": "4.31.0", 66 66 "rollup-plugin-dts": "6.1.1", 67 67 "tsup": "8.4.0", 68 - "turbo": "2.5.6", 68 + "turbo": "2.5.8", 69 69 "typescript": "5.8.3", 70 70 "typescript-eslint": "8.29.1", 71 71 "vitest": "3.1.1"
+3 -1
packages/nuxt/src/module.ts
··· 60 60 config.watch = false; 61 61 } 62 62 63 + const output = 64 + config.output instanceof Array ? config.output[0] : config.output; 63 65 const folder = path.resolve( 64 66 nuxt.options.rootDir, 65 - typeof config.output === 'string' ? config.output : config.output.path, 67 + typeof output === 'string' ? output : (output?.path ?? ''), 66 68 ); 67 69 68 70 nuxt.options.alias[options.alias!] = folder;
+8 -8
packages/openapi-ts-tests/main/test/2.0.x.test.ts
··· 15 15 const outputDir = path.join(__dirname, 'generated', version); 16 16 17 17 describe(`OpenAPI ${version}`, () => { 18 - const createConfig = (userConfig: UserConfig): UserConfig => { 18 + const createConfig = (userConfig: UserConfig) => { 19 + const input = 20 + userConfig.input instanceof Array 21 + ? userConfig.input[0] 22 + : userConfig.input; 19 23 const inputPath = path.join( 20 24 getSpecsPath(), 21 25 version, 22 - typeof userConfig.input === 'string' 23 - ? userConfig.input 24 - : (userConfig.input.path as string), 26 + typeof input === 'string' ? input : ((input?.path as string) ?? ''), 25 27 ); 26 28 return { 27 29 plugins: ['@hey-api/typescript'], ··· 40 42 outputDir, 41 43 typeof userConfig.output === 'string' ? userConfig.output : '', 42 44 ), 43 - }; 45 + } as const satisfies UserConfig; 44 46 }; 45 47 46 48 const scenarios = [ ··· 391 393 it.each(scenarios)('$description', async ({ config }) => { 392 394 await createClient(config); 393 395 394 - const outputPath = 395 - typeof config.output === 'string' ? config.output : config.output.path; 396 - const filePaths = getFilePaths(outputPath); 396 + const filePaths = getFilePaths(config.output); 397 397 398 398 await Promise.all( 399 399 filePaths.map(async (filePath) => {
+8 -8
packages/openapi-ts-tests/main/test/3.0.x.test.ts
··· 15 15 const outputDir = path.join(__dirname, 'generated', version); 16 16 17 17 describe(`OpenAPI ${version}`, () => { 18 - const createConfig = (userConfig: UserConfig): UserConfig => { 18 + const createConfig = (userConfig: UserConfig) => { 19 + const input = 20 + userConfig.input instanceof Array 21 + ? userConfig.input[0] 22 + : userConfig.input; 19 23 const inputPath = path.join( 20 24 getSpecsPath(), 21 25 version, 22 - typeof userConfig.input === 'string' 23 - ? userConfig.input 24 - : (userConfig.input.path as string), 26 + typeof input === 'string' ? input : ((input?.path as string) ?? ''), 25 27 ); 26 28 return { 27 29 plugins: ['@hey-api/typescript'], ··· 40 42 outputDir, 41 43 typeof userConfig.output === 'string' ? userConfig.output : '', 42 44 ), 43 - }; 45 + } as const satisfies UserConfig; 44 46 }; 45 47 46 48 const scenarios = [ ··· 668 670 it.each(scenarios)('$description', async ({ config }) => { 669 671 await createClient(config); 670 672 671 - const outputPath = 672 - typeof config.output === 'string' ? config.output : config.output.path; 673 - const filePaths = getFilePaths(outputPath); 673 + const filePaths = getFilePaths(config.output); 674 674 675 675 await Promise.all( 676 676 filePaths.map(async (filePath) => {
+13 -9
packages/openapi-ts-tests/main/test/3.1.x.test.ts
··· 15 15 const outputDir = path.join(__dirname, 'generated', version); 16 16 17 17 describe(`OpenAPI ${version}`, () => { 18 - const createConfig = (userConfig: UserConfig): UserConfig => { 18 + const createConfig = (userConfig: UserConfig) => { 19 + const input = 20 + userConfig.input instanceof Array 21 + ? userConfig.input[0] 22 + : userConfig.input; 19 23 const inputPath = path.join( 20 24 getSpecsPath(), 21 25 version, 22 - typeof userConfig.input === 'string' 23 - ? userConfig.input 24 - : (userConfig.input.path as string), 26 + typeof input === 'string' ? input : ((input?.path as string) ?? ''), 25 27 ); 28 + const output = 29 + userConfig.output instanceof Array 30 + ? userConfig.output[0] 31 + : userConfig.output; 26 32 return { 27 33 plugins: ['@hey-api/typescript'], 28 34 ...userConfig, ··· 38 44 }, 39 45 output: path.join( 40 46 outputDir, 41 - typeof userConfig.output === 'string' ? userConfig.output : '', 47 + typeof output === 'string' ? output : (output?.path ?? ''), 42 48 ), 43 - }; 49 + } as const satisfies UserConfig; 44 50 }; 45 51 46 52 const scenarios = [ ··· 969 975 it.each(scenarios)('$description', async ({ config }) => { 970 976 await createClient(config); 971 977 972 - const outputPath = 973 - typeof config.output === 'string' ? config.output : config.output.path; 974 - const filePaths = getFilePaths(outputPath); 978 + const filePaths = getFilePaths(config.output); 975 979 976 980 await Promise.all( 977 981 filePaths.map(async (filePath) => {
+21 -31
packages/openapi-ts-tests/main/test/bin.test.ts
··· 195 195 '--dry-run', 196 196 'true', 197 197 ]); 198 - expect(result.stdout.toString()).toBe(''); 199 - expect(result.stderr.toString()).toContain('encountered an error'); 200 198 expect(result.stderr.toString()).toContain('missing input'); 201 199 }); 202 200 ··· 216 214 '--dry-run', 217 215 'true', 218 216 ]); 219 - expect(result.stdout.toString()).toBe(''); 220 - expect(result.stderr.toString()).toContain('encountered an error'); 221 217 expect(result.stderr.toString()).toContain('missing output'); 222 218 }); 223 219 ··· 240 236 '--dry-run', 241 237 'true', 242 238 ]); 243 - expect(result.stdout.toString()).toBe(''); 244 239 expect(result.stderr.toString()).toContain( 245 240 `error: unknown option '--unknown'`, 246 241 ); ··· 285 280 '--output', 286 281 path.resolve(__dirname, 'generated', 'bin'), 287 282 '--debug', 288 - '--exportCore', 289 - 'false', 290 283 '--plugins', 291 284 '--useOptions', 292 285 'false', 293 286 '--dry-run', 294 287 'true', 295 288 ]); 296 - expect(result.stderr.toString()).toContain('debug: true'); 297 - expect(result.stderr.toString()).toContain('dryRun: true'); 298 - expect(result.stderr.toString()).toContain('exportCore: false'); 299 - expect(result.stderr.toString()).not.toContain('@hey-api/typescript'); 300 - expect(result.stderr.toString()).not.toContain('@hey-api/sdk'); 301 - expect(result.stderr.toString()).not.toContain('@hey-api/schemas'); 302 - expect(result.stderr.toString()).toContain('useOptions: false'); 289 + const stderr = result.stderr.toString(); 290 + expect(stderr).toMatch(/level: 'debug'/); 291 + expect(stderr).toMatch(/dryRun: true/); 292 + expect(stderr).not.toMatch(/@hey-api\/schemas/); 293 + expect(stderr).not.toMatch(/@hey-api\/sdk/); 294 + expect(stderr).not.toMatch(/@hey-api\/typescript/); 295 + expect(stderr).toMatch(/useOptions: false/); 303 296 }); 304 297 305 298 it('handles true booleans', () => { ··· 320 313 '--client', 321 314 '@hey-api/client-fetch', 322 315 '--debug', 323 - '--exportCore', 324 - 'true', 325 316 '--plugins', 326 317 '@hey-api/schemas', 327 318 '@hey-api/sdk', ··· 331 322 '--dry-run', 332 323 'true', 333 324 ]); 334 - expect(result.stderr.toString()).toContain('debug: true'); 335 - expect(result.stderr.toString()).toContain('dryRun: true'); 336 - expect(result.stderr.toString()).toContain('exportCore: true'); 337 - expect(result.stderr.toString()).toContain('@hey-api/typescript'); 338 - expect(result.stderr.toString()).toContain('@hey-api/sdk'); 339 - expect(result.stderr.toString()).toContain('@hey-api/schemas'); 340 - expect(result.stderr.toString()).toContain('useOptions: true'); 325 + const stderr = result.stderr.toString(); 326 + expect(stderr).toMatch(/level: 'debug'/); 327 + expect(stderr).toMatch(/dryRun: true/); 328 + expect(stderr).toMatch(/@hey-api\/schemas/); 329 + expect(stderr).toMatch(/@hey-api\/sdk/); 330 + expect(stderr).toMatch(/@hey-api\/typescript/); 331 + expect(stderr).toMatch(/useOptions: true/); 341 332 }); 342 333 343 334 it('handles optional booleans', () => { ··· 358 349 '--client', 359 350 '@hey-api/client-fetch', 360 351 '--debug', 361 - '--exportCore', 362 352 '--plugins', 363 353 '@hey-api/schemas', 364 354 '@hey-api/sdk', ··· 367 357 '--dry-run', 368 358 'true', 369 359 ]); 370 - expect(result.stderr.toString()).toContain('debug: true'); 371 - expect(result.stderr.toString()).toContain('dryRun: true'); 372 - expect(result.stderr.toString()).toContain('exportCore: true'); 373 - expect(result.stderr.toString()).toContain('@hey-api/schemas'); 374 - expect(result.stderr.toString()).toContain('@hey-api/sdk'); 375 - expect(result.stderr.toString()).toContain('@hey-api/typescript'); 376 - expect(result.stderr.toString()).toContain('useOptions: true'); 360 + const stderr = result.stderr.toString(); 361 + expect(stderr).toMatch(/level: 'debug'/); 362 + expect(stderr).toMatch(/dryRun: true/); 363 + expect(stderr).toMatch(/@hey-api\/schemas/); 364 + expect(stderr).toMatch(/@hey-api\/sdk/); 365 + expect(stderr).toMatch(/@hey-api\/typescript/); 366 + expect(stderr).toMatch(/useOptions: true/); 377 367 }); 378 368 });
+46 -42
packages/openapi-ts-tests/main/test/clients.test.ts
··· 36 36 const createConfig = ( 37 37 userConfig: Omit<UserConfig, 'input'> & 38 38 Pick<Partial<UserConfig>, 'input'>, 39 - ): UserConfig => ({ 40 - ...userConfig, 41 - input: path.join(getSpecsPath(), '3.1.x', 'full.yaml'), 42 - logs: { 43 - level: 'silent', 44 - }, 45 - output: 46 - typeof userConfig.output === 'string' 47 - ? path.join(outputDir, userConfig.output) 48 - : { 49 - ...userConfig.output, 50 - path: path.join(outputDir, userConfig.output.path), 51 - }, 52 - }); 39 + ) => { 40 + const output = 41 + userConfig.output instanceof Array 42 + ? userConfig.output[0] 43 + : userConfig.output; 44 + return { 45 + ...userConfig, 46 + input: path.join(getSpecsPath(), '3.1.x', 'full.yaml'), 47 + logs: { 48 + level: 'silent', 49 + }, 50 + output: 51 + typeof output === 'string' 52 + ? path.join(outputDir, output) 53 + : { 54 + ...output, 55 + path: path.join(outputDir, output?.path ?? ''), 56 + }, 57 + } as const satisfies UserConfig; 58 + }; 53 59 54 60 const scenarios = [ 55 61 { ··· 226 232 227 233 const createConfig = ( 228 234 userConfig: Omit<UserConfig, 'input'> & Pick<Partial<UserConfig>, 'input'>, 229 - ): UserConfig => ({ 230 - ...userConfig, 231 - input: path.join(getSpecsPath(), '3.1.x', 'full.yaml'), 232 - logs: { 233 - level: 'silent', 234 - }, 235 - output: path.join( 236 - outputDir, 237 - typeof userConfig.output === 'string' ? userConfig.output : '', 238 - ), 239 - }); 235 + ) => 236 + ({ 237 + ...userConfig, 238 + input: path.join(getSpecsPath(), '3.1.x', 'full.yaml'), 239 + logs: { 240 + level: 'silent', 241 + }, 242 + output: path.join( 243 + outputDir, 244 + typeof userConfig.output === 'string' ? userConfig.output : '', 245 + ), 246 + }) as const satisfies UserConfig; 240 247 241 248 const scenarios = [ 242 249 { ··· 336 343 it.each(scenarios)('$description', async ({ config }) => { 337 344 await createClient(config); 338 345 339 - const outputPath = 340 - typeof config.output === 'string' ? config.output : config.output.path; 341 - const filePaths = getFilePaths(outputPath); 346 + const filePaths = getFilePaths(config.output); 342 347 343 348 await Promise.all( 344 349 filePaths.map(async (filePath) => { ··· 372 377 373 378 const createConfig = ( 374 379 userConfig: Omit<UserConfig, 'input'> & Pick<Partial<UserConfig>, 'input'>, 375 - ): UserConfig => ({ 376 - ...userConfig, 377 - input: path.join(getSpecsPath(), '3.1.x', 'full.yaml'), 378 - logs: { 379 - level: 'silent', 380 - }, 381 - output: path.join( 382 - outputDir, 383 - typeof userConfig.output === 'string' ? userConfig.output : '', 384 - ), 385 - }); 380 + ) => 381 + ({ 382 + ...userConfig, 383 + input: path.join(getSpecsPath(), '3.1.x', 'full.yaml'), 384 + logs: { 385 + level: 'silent', 386 + }, 387 + output: path.join( 388 + outputDir, 389 + typeof userConfig.output === 'string' ? userConfig.output : '', 390 + ), 391 + }) as const satisfies UserConfig; 386 392 387 393 const scenarios = [ 388 394 { ··· 482 488 it.each(scenarios)('$description', async ({ config }) => { 483 489 await createClient(config); 484 490 485 - const outputPath = 486 - typeof config.output === 'string' ? config.output : config.output.path; 487 - const filePaths = getFilePaths(outputPath); 491 + const filePaths = getFilePaths(config.output); 488 492 489 493 await Promise.all( 490 494 filePaths.map(async (filePath) => {
+15
packages/openapi-ts-tests/main/test/index.test.ts
··· 518 518 createClient({ 519 519 dryRun: true, 520 520 input: '../specs/v2.json', 521 + logs: { 522 + level: 'silent', 523 + }, 521 524 output: './generated/v2/', 522 525 plugins: ['@hey-api/client-fetch'], 523 526 }), ··· 529 532 createClient({ 530 533 dryRun: true, 531 534 input: '../specs/v3.json', 535 + logs: { 536 + level: 'silent', 537 + }, 532 538 output: './generated/v3/', 533 539 plugins: ['@hey-api/client-fetch'], 534 540 }), ··· 540 546 createClient({ 541 547 dryRun: true, 542 548 input: '../specs/v3-transforms.json', 549 + logs: { 550 + level: 'silent', 551 + }, 543 552 output: './generated/v3/', 544 553 plugins: [ 545 554 '@hey-api/client-fetch', ··· 561 570 dryRun: true, 562 571 input: 563 572 'https://raw.githubusercontent.com/hey-api/openapi-ts/main/packages/openapi-ts-tests/specs/v2.json', 573 + logs: { 574 + level: 'silent', 575 + }, 564 576 output: './generated/v2-downloaded/', 565 577 plugins: ['@hey-api/client-fetch'], 566 578 }), ··· 573 585 dryRun: true, 574 586 input: 575 587 'https://raw.githubusercontent.com/hey-api/openapi-ts/main/packages/openapi-ts-tests/specs/v3.json', 588 + logs: { 589 + level: 'silent', 590 + }, 576 591 output: './generated/v3-downloaded/', 577 592 plugins: ['@hey-api/client-fetch'], 578 593 }),
+371 -349
packages/openapi-ts-tests/main/test/openapi-ts.config.ts
··· 14 14 // eslint-disable-next-line arrow-body-style 15 15 export default defineConfig(() => { 16 16 // ... 17 - return { 18 - // experimentalParser: false, 19 - input: { 20 - // branch: 'main', 21 - // fetch: { 22 - // headers: { 23 - // 'x-foo': 'bar', 24 - // }, 25 - // }, 26 - // organization: 'hey-api', 27 - // path: { 28 - // components: {}, 29 - // info: { 30 - // version: '1.0.0', 31 - // }, 32 - // openapi: '3.1.0', 33 - // paths: {}, 34 - // }, 35 - path: path.resolve( 36 - getSpecsPath(), 37 - '3.0.x', 38 - // 'circular.yaml', 39 - 'dutchie.json', 40 - // 'invalid', 41 - // 'openai.yaml', 42 - // 'full.yaml', 43 - // 'opencode.yaml', 44 - // 'sdk-instance.yaml', 45 - // 'string-with-format.yaml', 46 - // 'transformers.json', 47 - // 'type-format.yaml', 48 - // 'validators.yaml', 49 - // 'validators-circular-ref-2.yaml', 50 - // 'zoom-video-sdk.json', 51 - ), 52 - // path: 'scalar:@scalar/access-service', 53 - // path: 'hey-api/backend', 54 - // path: 'hey-api/backend?branch=main&version=1.0.0', 55 - // path: 'https://get.heyapi.dev/hey-api/backend?branch=main&version=1.0.0', 56 - // path: 'http://localhost:4000/', 57 - // path: 'http://localhost:8000/openapi.json', 58 - // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 59 - // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', 60 - // project: 'backend', 61 - // project: 'upload-openapi-spec', 62 - // version: '1.0.0', 63 - // watch: { 64 - // enabled: true, 65 - // interval: 500, 66 - // timeout: 30_000, 67 - // }, 68 - }, 69 - logs: { 70 - // level: 'debug', 71 - path: './logs', 72 - }, 73 - // name: 'foo', 74 - output: { 75 - // case: 'snake_case', 76 - clean: true, 77 - fileName: { 78 - // case: 'snake_case', 79 - // name: '{{name}}.renamed', 80 - suffix: '.meh', 81 - }, 82 - // format: 'prettier', 83 - importFileExtension: '.ts', 84 - // indexFile: false, 85 - // lint: 'eslint', 86 - path: path.resolve(__dirname, 'generated', 'sample'), 87 - tsConfigPath: path.resolve( 88 - __dirname, 89 - 'tsconfig', 90 - 'tsconfig.nodenext.json', 91 - ), 92 - }, 93 - parser: { 94 - filters: { 95 - // deprecated: false, 96 - operations: { 97 - include: [ 98 - // 'GET /event', 99 - // '/^[A-Z]+ /v1//', 100 - ], 101 - }, 102 - // orphans: true, 103 - // preserveOrder: true, 104 - // schemas: { 105 - // include: ['Foo'], 106 - // }, 107 - // tags: { 108 - // exclude: ['bar'], 109 - // }, 110 - }, 111 - hooks: { 112 - operations: { 113 - getKind() { 114 - // noop 115 - }, 116 - isMutation() { 117 - // noop 118 - }, 119 - isQuery: (op) => { 120 - if (op.method === 'post' && op.path === '/search') { 121 - return true; 122 - } 123 - return; 124 - }, 125 - }, 126 - symbols: { 127 - // getFilePath: (symbol) => { 128 - // if (symbol.name) { 129 - // return symbol.name[0]?.toLowerCase(); 130 - // } 131 - // return; 17 + return [ 18 + { 19 + // experimentalParser: false, 20 + input: [ 21 + { 22 + // fetch: { 23 + // headers: { 24 + // 'x-foo': 'bar', 25 + // }, 26 + // }, 27 + // path: { 28 + // components: {}, 29 + // info: { 30 + // version: '1.0.0', 31 + // }, 32 + // openapi: '3.1.0', 33 + // paths: {}, 34 + // }, 35 + path: path.resolve( 36 + getSpecsPath(), 37 + '3.0.x', 38 + // 'circular.yaml', 39 + 'dutchie.json', 40 + // 'invalid', 41 + // 'openai.yaml', 42 + // 'full.yaml', 43 + // 'opencode.yaml', 44 + // 'sdk-instance.yaml', 45 + // 'string-with-format.yaml', 46 + // 'transformers.json', 47 + // 'type-format.yaml', 48 + // 'validators.yaml', 49 + // 'validators-circular-ref-2.yaml', 50 + // 'zoom-video-sdk.json', 51 + ), 52 + // path: 'https://get.heyapi.dev/hey-api/backend?branch=main&version=1.0.0', 53 + // path: 'http://localhost:4000/', 54 + // path: 'http://localhost:8000/openapi.json', 55 + // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 56 + // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', 57 + // watch: { 58 + // enabled: true, 59 + // interval: 500, 60 + // timeout: 30_000, 132 61 // }, 133 62 }, 134 - }, 135 - pagination: { 136 - // keywords: ['aa'], 137 - }, 138 - patch: { 139 - // operations: { 140 - // 'GET /foo': (operation: any) => { 141 - // operation.responses['200'].description = 'foo'; 142 - // }, 143 - // }, 144 - // version: () => '3.1.1', 145 - }, 146 - transforms: { 147 - // enums: { 148 - // enabled: false, 149 - // mode: 'root', 150 - // // name: '{{name}}', 151 - // }, 152 - propertiesRequiredByDefault: true, 153 - // readWrite: { 154 - // // enabled: false, 155 - // requests: '{{name}}Writable', 156 - // responses: '{{name}}', 157 - // }, 158 - }, 159 - validate_EXPERIMENTAL: true, 160 - }, 161 - plugins: [ 162 - // customClientPlugin({ 163 - // baseUrl: false, 164 - // }), 165 - // myClientPlugin(), 166 - { 167 - // baseUrl: false, 168 - exportFromIndex: true, 169 - name: '@hey-api/client-fetch', 170 - // name: 'legacy/angular', 171 - // runtimeConfigPath: path.resolve(__dirname, 'hey-api.ts'), 172 - runtimeConfigPath: './src/hey-api.ts', 173 - // strictBaseUrl: true, 174 - // throwOnError: true, 175 - }, 176 - { 177 - // case: 'snake_case', 178 - // definitions: '你_snake_{{name}}', 179 - // enums: { 180 - // // case: 'PascalCase', 181 - // // constantsIgnoreNull: true, 182 - // // enabled: false, 183 - // mode: 'javascript', 184 - // }, 185 - // errors: { 186 - // error: '他們_error_{{name}}', 187 - // name: '你們_errors_{{name}}', 188 - // }, 189 - name: '@hey-api/typescript', 190 - // requests: '我們_data_{{name}}', 191 - // responses: { 192 - // name: '我_responses_{{name}}', 193 - // response: '他_response_{{name}}', 194 - // }, 195 - // topType: 'any', 196 - // tree: true, 197 - // webhooks: { 198 - // name: 'Webby{{name}}Hook', 199 - // payload: '{{name}}WebhookEvent', 63 + // path.resolve(getSpecsPath(), '3.1.x', 'full.yaml'), 64 + // { 65 + // branch: 'main', 66 + // organization: 'hey-api', 67 + // path: 'hey-api/backend', 68 + // project: 'backend', 69 + // project: 'upload-openapi-spec', 70 + // version: '1.0.0', 200 71 // }, 72 + // 'hey-api/backend?branch=main&version=1.0.0', 73 + // 'scalar:@scalar/access-service', 74 + // 'readme:@developers/v2.0#nysezql0wwo236', 75 + // 'readme:nysezql0wwo236', 76 + // 'https://dash.readme.com/api/v1/api-registry/nysezql0wwo236', 77 + // 'https://somefakedomain.com/openapi.yaml', 78 + ], 79 + logs: { 80 + // level: 'debug', 81 + path: './logs', 201 82 }, 202 - { 203 - asClass: true, 204 - // auth: false, 205 - // classNameBuilder: '{{name}}', 206 - // classNameBuilder: '{{name}}Service', 207 - // classStructure: 'off', 208 - // client: false, 209 - // include... 210 - // instance: true, 211 - name: '@hey-api/sdk', 212 - // operationId: false, 213 - // params: 'experiment', 214 - // responseStyle: 'data', 215 - // transformer: '@hey-api/transformers', 216 - // transformer: true, 217 - // validator: { 218 - // request: 'valibot', 219 - // response: 'zod', 83 + // name: 'foo', 84 + output: [ 85 + // { 86 + // // case: 'snake_case', 87 + // clean: true, 88 + // fileName: { 89 + // // case: 'snake_case', 90 + // // name: '{{name}}.renamed', 91 + // suffix: '.meh', 92 + // }, 93 + // // format: 'prettier', 94 + // importFileExtension: '.ts', 95 + // // indexFile: false, 96 + // // lint: 'eslint', 97 + // path: path.resolve(__dirname, 'generated', 'sample'), 98 + // tsConfigPath: path.resolve( 99 + // __dirname, 100 + // 'tsconfig', 101 + // 'tsconfig.nodenext.json', 102 + // ), 220 103 // }, 221 - '~hooks': { 222 - symbols: { 223 - // getFilePath: (symbol) => { 224 - // if (symbol.name) { 225 - // return utils.stringCase({ 226 - // case: 'camelCase', 227 - // value: symbol.name, 228 - // }); 229 - // } 230 - // return; 231 - // }, 104 + path.resolve(__dirname, 'generated', 'sample'), 105 + ], 106 + parser: { 107 + filters: { 108 + // deprecated: false, 109 + operations: { 110 + include: [ 111 + // 'GET /event', 112 + // '/^[A-Z]+ /v1//', 113 + ], 232 114 }, 115 + // orphans: true, 116 + // preserveOrder: true, 117 + // schemas: { 118 + // include: ['Foo'], 119 + // }, 120 + // tags: { 121 + // exclude: ['bar'], 122 + // }, 233 123 }, 234 - }, 235 - { 236 - // bigInt: true, 237 - // dates: true, 238 - // name: '@hey-api/transformers', 239 - }, 240 - { 241 - // name: 'fastify', 242 - }, 243 - { 244 - // case: 'SCREAMING_SNAKE_CASE', 245 - // comments: false, 246 - exportFromIndex: true, 247 - // infiniteQueryKeys: { 248 - // name: '{{name}}IQK', 249 - // }, 250 - // infiniteQueryOptions: { 251 - // name: '{{name}}IQO', 252 - // }, 253 - // mutationOptions: { 254 - // name: '{{name}}MO', 255 - // }, 256 - name: '@tanstack/react-query', 257 - // queryKeys: { 258 - // name: '{{name}}QK', 259 - // }, 260 - // queryOptions: false, 261 - // queryOptions: { 262 - // name: '{{name}}QO', 263 - // }, 264 - useQuery: true, 265 - '~hooks': { 124 + hooks: { 266 125 operations: { 267 - getKind: (op) => { 268 - if (op.method === 'post' && op.path === '/search') { 269 - return ['query']; 270 - } 271 - return; 126 + getKind() { 127 + // noop 272 128 }, 273 129 isMutation() { 274 130 // noop 275 131 }, 276 - isQuery: () => { 277 - // noop 132 + isQuery: (op) => { 133 + if (op.method === 'post' && op.path === '/search') { 134 + return true; 135 + } 136 + return; 278 137 }, 279 138 }, 280 - }, 281 - }, 282 - { 283 - // case: 'SCREAMING_SNAKE_CASE', 284 - // comments: false, 285 - // definitions: 'z{{name}}Definition', 286 - exportFromIndex: true, 287 - // metadata: true, 288 - name: 'valibot', 289 - // requests: { 290 - // case: 'PascalCase', 291 - // name: '{{name}}Data', 292 - // }, 293 - // responses: { 294 - // // case: 'snake_case', 295 - // name: 'z{{name}}TestResponse', 296 - // }, 297 - // webhooks: { 298 - // name: 'q{{name}}CoolWebhook', 299 - // }, 300 - '~hooks': { 301 139 symbols: { 302 140 // getFilePath: (symbol) => { 303 141 // if (symbol.name) { 304 - // return utils.stringCase({ 305 - // case: 'camelCase', 306 - // value: symbol.name, 307 - // }); 142 + // return symbol.name[0]?.toLowerCase(); 308 143 // } 309 144 // return; 310 145 // }, 311 146 }, 312 147 }, 313 - }, 314 - { 315 - // case: 'snake_case', 316 - // comments: false, 317 - compatibilityVersion: 'mini', 318 - dates: { 319 - local: true, 320 - // offset: true, 148 + pagination: { 149 + // keywords: ['aa'], 321 150 }, 322 - definitions: { 323 - // name: 'z{{name}}Definition', 324 - // types: { 325 - // infer: 'D{{name}}ZodType', 151 + patch: { 152 + // operations: { 153 + // 'GET /foo': (operation: any) => { 154 + // operation.responses['200'].description = 'foo'; 326 155 // }, 156 + // }, 157 + // version: () => '3.1.1', 327 158 }, 328 - exportFromIndex: true, 329 - metadata: true, 330 - name: 'zod', 331 - // requests: { 332 - // // case: 'SCREAMING_SNAKE_CASE', 333 - // // name: 'z{{name}}TestData', 334 - // types: { 335 - // infer: 'E{{name}}DataZodType', 336 - // }, 337 - // }, 338 - responses: { 159 + transforms: { 160 + // enums: { 161 + // enabled: false, 162 + // mode: 'root', 163 + // // name: '{{name}}', 164 + // }, 165 + // propertiesRequiredByDefault: true, 166 + // readWrite: { 167 + // // enabled: false, 168 + // requests: '{{name}}Writable', 169 + // responses: '{{name}}', 170 + // }, 171 + }, 172 + // validate_EXPERIMENTAL: true, 173 + }, 174 + plugins: [ 175 + // customClientPlugin({ 176 + // baseUrl: false, 177 + // }), 178 + // myClientPlugin(), 179 + { 180 + // baseUrl: false, 181 + exportFromIndex: true, 182 + name: '@hey-api/client-fetch', 183 + // name: 'legacy/angular', 184 + // runtimeConfigPath: path.resolve(__dirname, 'hey-api.ts'), 185 + runtimeConfigPath: './src/hey-api.ts', 186 + // strictBaseUrl: true, 187 + // throwOnError: true, 188 + }, 189 + { 339 190 // case: 'snake_case', 340 - // name: (name) => { 341 - // if (name === 'complexTypes') { 342 - // return 'z'; 343 - // } 344 - // return 'z{{name}}Response'; 191 + // definitions: '你_snake_{{name}}', 192 + // enums: { 193 + // // case: 'PascalCase', 194 + // // constantsIgnoreNull: true, 195 + // // enabled: false, 196 + // mode: 'javascript', 345 197 // }, 346 - // types: { 347 - // infer: 'F{{name}}ResponseZodType', 198 + // errors: { 199 + // error: '他們_error_{{name}}', 200 + // name: '你們_errors_{{name}}', 201 + // }, 202 + name: '@hey-api/typescript', 203 + // requests: '我們_data_{{name}}', 204 + // responses: { 205 + // name: '我_responses_{{name}}', 206 + // response: '他_response_{{name}}', 207 + // }, 208 + // topType: 'any', 209 + // tree: true, 210 + // webhooks: { 211 + // name: 'Webby{{name}}Hook', 212 + // payload: '{{name}}WebhookEvent', 348 213 // }, 349 214 }, 350 - types: { 351 - // infer: { 352 - // case: 'snake_case', 215 + { 216 + asClass: true, 217 + // auth: false, 218 + // classNameBuilder: '{{name}}', 219 + // classNameBuilder: '{{name}}Service', 220 + // classStructure: 'off', 221 + // client: false, 222 + // include... 223 + // instance: true, 224 + name: '@hey-api/sdk', 225 + // operationId: false, 226 + // params: 'experiment', 227 + // responseStyle: 'data', 228 + // transformer: '@hey-api/transformers', 229 + // transformer: true, 230 + // validator: { 231 + // request: 'valibot', 232 + // response: 'zod', 353 233 // }, 234 + '~hooks': { 235 + symbols: { 236 + // getFilePath: (symbol) => { 237 + // if (symbol.name) { 238 + // return utils.stringCase({ 239 + // case: 'camelCase', 240 + // value: symbol.name, 241 + // }); 242 + // } 243 + // return; 244 + // }, 245 + }, 246 + }, 354 247 }, 355 - '~hooks': { 356 - symbols: { 357 - // getFilePath: (symbol) => { 358 - // if (symbol.name === 'z') { 359 - // return 'complexService'; 248 + { 249 + // bigInt: true, 250 + // dates: true, 251 + // name: '@hey-api/transformers', 252 + }, 253 + { 254 + // name: 'fastify', 255 + }, 256 + { 257 + // case: 'SCREAMING_SNAKE_CASE', 258 + // comments: false, 259 + exportFromIndex: true, 260 + // infiniteQueryKeys: { 261 + // name: '{{name}}IQK', 262 + // }, 263 + // infiniteQueryOptions: { 264 + // name: '{{name}}IQO', 265 + // }, 266 + // mutationOptions: { 267 + // name: '{{name}}MO', 268 + // }, 269 + name: '@tanstack/react-query', 270 + // queryKeys: { 271 + // name: '{{name}}QK', 272 + // }, 273 + // queryOptions: false, 274 + // queryOptions: { 275 + // name: '{{name}}QO', 276 + // }, 277 + useQuery: true, 278 + '~hooks': { 279 + operations: { 280 + getKind: (op) => { 281 + if (op.method === 'post' && op.path === '/search') { 282 + return ['query']; 283 + } 284 + return; 285 + }, 286 + isMutation() { 287 + // noop 288 + }, 289 + isQuery: () => { 290 + // noop 291 + }, 292 + }, 293 + }, 294 + }, 295 + { 296 + // case: 'SCREAMING_SNAKE_CASE', 297 + // comments: false, 298 + // definitions: 'z{{name}}Definition', 299 + exportFromIndex: true, 300 + // metadata: true, 301 + name: 'valibot', 302 + // requests: { 303 + // case: 'PascalCase', 304 + // name: '{{name}}Data', 305 + // }, 306 + // responses: { 307 + // // case: 'snake_case', 308 + // name: 'z{{name}}TestResponse', 309 + // }, 310 + // webhooks: { 311 + // name: 'q{{name}}CoolWebhook', 312 + // }, 313 + '~hooks': { 314 + symbols: { 315 + // getFilePath: (symbol) => { 316 + // if (symbol.name) { 317 + // return utils.stringCase({ 318 + // case: 'camelCase', 319 + // value: symbol.name, 320 + // }); 321 + // } 322 + // return; 323 + // }, 324 + }, 325 + }, 326 + }, 327 + { 328 + // case: 'snake_case', 329 + // comments: false, 330 + compatibilityVersion: 'mini', 331 + dates: { 332 + local: true, 333 + // offset: true, 334 + }, 335 + definitions: { 336 + // name: 'z{{name}}Definition', 337 + // types: { 338 + // infer: 'D{{name}}ZodType', 339 + // }, 340 + }, 341 + exportFromIndex: true, 342 + metadata: true, 343 + name: 'zod', 344 + // requests: { 345 + // // case: 'SCREAMING_SNAKE_CASE', 346 + // // name: 'z{{name}}TestData', 347 + // types: { 348 + // infer: 'E{{name}}DataZodType', 349 + // }, 350 + // }, 351 + responses: { 352 + // case: 'snake_case', 353 + // name: (name) => { 354 + // if (name === 'complexTypes') { 355 + // return 'z'; 360 356 // } 361 - // return; 357 + // return 'z{{name}}Response'; 358 + // }, 359 + // types: { 360 + // infer: 'F{{name}}ResponseZodType', 362 361 // }, 363 362 }, 364 - }, 365 - }, 366 - { 367 - exportFromIndex: true, 368 - // name: '@hey-api/schemas', 369 - // type: 'json', 370 - }, 371 - { 372 - exportFromIndex: true, 373 - httpRequests: { 374 - // asClass: true, 363 + types: { 364 + // infer: { 365 + // case: 'snake_case', 366 + // }, 367 + }, 368 + '~hooks': { 369 + symbols: { 370 + // getFilePath: (symbol) => { 371 + // if (symbol.name === 'z') { 372 + // return 'complexService'; 373 + // } 374 + // return; 375 + // }, 376 + }, 377 + }, 375 378 }, 376 - httpResources: { 377 - // asClass: true, 379 + { 380 + exportFromIndex: true, 381 + // name: '@hey-api/schemas', 382 + // type: 'json', 378 383 }, 379 - // name: '@angular/common', 380 - }, 381 - { 382 - exportFromIndex: true, 383 - // mutationOptions: '{{name}}Mutationssss', 384 - // name: '@pinia/colada', 385 - // queryOptions: { 386 - // name: '{{name}}Queryyyyy', 387 - // }, 388 - queryKeys: { 389 - tags: true, 384 + { 385 + exportFromIndex: true, 386 + httpRequests: { 387 + // asClass: true, 388 + }, 389 + httpResources: { 390 + // asClass: true, 391 + }, 392 + // name: '@angular/common', 390 393 }, 391 - '~hooks': { 392 - operations: { 393 - getKind: (op) => { 394 - if (op.method === 'post' && op.path === '/search') { 395 - return ['query']; 396 - } 397 - return; 394 + { 395 + exportFromIndex: true, 396 + // mutationOptions: '{{name}}Mutationssss', 397 + // name: '@pinia/colada', 398 + // queryOptions: { 399 + // name: '{{name}}Queryyyyy', 400 + // }, 401 + queryKeys: { 402 + tags: true, 403 + }, 404 + '~hooks': { 405 + operations: { 406 + getKind: (op) => { 407 + if (op.method === 'post' && op.path === '/search') { 408 + return ['query']; 409 + } 410 + return; 411 + }, 398 412 }, 399 413 }, 400 414 }, 401 - }, 402 - ], 403 - // useOptions: false, 404 - // watch: 3_000, 405 - }; 415 + ], 416 + // useOptions: false, 417 + // watch: 3_000, 418 + }, 419 + // { 420 + // input: 'scalar:@scalar/access-service', 421 + // logs: { 422 + // // level: 'debug', 423 + // path: './logs', 424 + // }, 425 + // output: path.resolve(__dirname, 'generated', 'sample'), 426 + // }, 427 + ]; 406 428 });
+22 -23
packages/openapi-ts-tests/main/test/plugins.test.ts
··· 26 26 userConfig: Omit<UserConfig, 'input'> & 27 27 Pick<Required<UserConfig>, 'plugins'> & 28 28 Pick<Partial<UserConfig>, 'input'>, 29 - ): UserConfig => ({ 30 - ...userConfig, 31 - input: path.join( 32 - getSpecsPath(), 33 - version, 34 - typeof userConfig.input === 'string' ? userConfig.input : 'full.yaml', 35 - ), 36 - logs: { 37 - level: 'silent', 38 - }, 39 - output: path.join( 40 - outputDir, 41 - typeof userConfig.plugins[0] === 'string' 42 - ? userConfig.plugins[0] 43 - : userConfig.plugins[0]!.name, 44 - typeof userConfig.output === 'string' ? userConfig.output : '', 45 - ), 46 - plugins: userConfig.plugins ?? ['@hey-api/client-fetch'], 47 - }); 29 + ) => 30 + ({ 31 + ...userConfig, 32 + input: path.join( 33 + getSpecsPath(), 34 + version, 35 + typeof userConfig.input === 'string' ? userConfig.input : 'full.yaml', 36 + ), 37 + logs: { 38 + level: 'silent', 39 + }, 40 + output: path.join( 41 + outputDir, 42 + typeof userConfig.plugins[0] === 'string' 43 + ? userConfig.plugins[0] 44 + : userConfig.plugins[0]!.name, 45 + typeof userConfig.output === 'string' ? userConfig.output : '', 46 + ), 47 + plugins: userConfig.plugins ?? ['@hey-api/client-fetch'], 48 + }) as const satisfies UserConfig; 48 49 49 50 const scenarios = [ 50 51 { ··· 566 567 it.each(scenarios)('$description', async ({ config }) => { 567 568 await createClient(config); 568 569 569 - const outputPath = 570 - typeof config.output === 'string' ? config.output : config.output.path; 571 - const filePaths = getFilePaths(outputPath); 570 + const filePaths = getFilePaths(config.output); 572 571 573 572 await Promise.all( 574 573 filePaths.map(async (filePath) => { ··· 641 640 output: path.join(outputDir, myPlugin.name, 'default'), 642 641 plugins: [myPlugin, '@hey-api/client-fetch'], 643 642 }), 644 - ).rejects.toThrowError(/unknown plugin/g); 643 + ).rejects.toThrowError(/Found 1 configuration error./g); 645 644 646 645 expect(myPlugin.handler).not.toHaveBeenCalled(); 647 646 expect(myPlugin.handlerLegacy).not.toHaveBeenCalled();
+1 -3
packages/openapi-ts-tests/zod/v3/test/3.0.x.test.ts
··· 68 68 it.each(scenarios)('$description', async ({ config }) => { 69 69 await createClient(config); 70 70 71 - const outputPath = 72 - typeof config.output === 'string' ? config.output : config.output.path; 73 - const filePaths = getFilePaths(outputPath); 71 + const filePaths = getFilePaths(config.output); 74 72 75 73 await Promise.all( 76 74 filePaths.map(async (filePath) => {
+1 -3
packages/openapi-ts-tests/zod/v3/test/3.1.x.test.ts
··· 150 150 it.each(scenarios)('$description', async ({ config }) => { 151 151 await createClient(config); 152 152 153 - const outputPath = 154 - typeof config.output === 'string' ? config.output : config.output.path; 155 - const filePaths = getFilePaths(outputPath); 153 + const filePaths = getFilePaths(config.output); 156 154 157 155 await Promise.all( 158 156 filePaths.map(async (filePath) => {
+1 -5
packages/openapi-ts-tests/zod/v3/test/openapi.test.ts
··· 54 54 it.each(scenarios)('$description', async ({ config }) => { 55 55 await createClient(config); 56 56 57 - const outputPath = 58 - typeof config.output === 'string' 59 - ? config.output 60 - : config.output.path; 61 - const filePaths = getFilePaths(outputPath); 57 + const filePaths = getFilePaths(config.output); 62 58 63 59 await Promise.all( 64 60 filePaths.map(async (filePath) => {
+7 -5
packages/openapi-ts-tests/zod/v3/test/utils.ts
··· 18 18 outputDir: string; 19 19 zodVersion: (typeof zodVersions)[number]; 20 20 }) => 21 - (userConfig: UserConfig): UserConfig => { 21 + (userConfig: UserConfig) => { 22 + const input = 23 + userConfig.input instanceof Array 24 + ? userConfig.input[0]! 25 + : userConfig.input; 22 26 const inputPath = path.join( 23 27 getSpecsPath(), 24 28 openApiVersion, 25 - typeof userConfig.input === 'string' 26 - ? userConfig.input 27 - : (userConfig.input.path as string), 29 + typeof input === 'string' ? input : (input.path as string), 28 30 ); 29 31 return { 30 32 plugins: [ ··· 48 50 outputDir, 49 51 typeof userConfig.output === 'string' ? userConfig.output : '', 50 52 ), 51 - }; 53 + } as const satisfies UserConfig; 52 54 }; 53 55 54 56 export const getSnapshotsPath = (): string =>
+1 -3
packages/openapi-ts-tests/zod/v4/test/3.0.x.test.ts
··· 68 68 it.each(scenarios)('$description', async ({ config }) => { 69 69 await createClient(config); 70 70 71 - const outputPath = 72 - typeof config.output === 'string' ? config.output : config.output.path; 73 - const filePaths = getFilePaths(outputPath); 71 + const filePaths = getFilePaths(config.output); 74 72 75 73 await Promise.all( 76 74 filePaths.map(async (filePath) => {
+1 -3
packages/openapi-ts-tests/zod/v4/test/3.1.x.test.ts
··· 150 150 it.each(scenarios)('$description', async ({ config }) => { 151 151 await createClient(config); 152 152 153 - const outputPath = 154 - typeof config.output === 'string' ? config.output : config.output.path; 155 - const filePaths = getFilePaths(outputPath); 153 + const filePaths = getFilePaths(config.output); 156 154 157 155 await Promise.all( 158 156 filePaths.map(async (filePath) => {
+1 -5
packages/openapi-ts-tests/zod/v4/test/openapi.test.ts
··· 54 54 it.each(scenarios)('$description', async ({ config }) => { 55 55 await createClient(config); 56 56 57 - const outputPath = 58 - typeof config.output === 'string' 59 - ? config.output 60 - : config.output.path; 61 - const filePaths = getFilePaths(outputPath); 57 + const filePaths = getFilePaths(config.output); 62 58 63 59 await Promise.all( 64 60 filePaths.map(async (filePath) => {
+7 -5
packages/openapi-ts-tests/zod/v4/test/utils.ts
··· 18 18 outputDir: string; 19 19 zodVersion: (typeof zodVersions)[number]; 20 20 }) => 21 - (userConfig: UserConfig): UserConfig => { 21 + (userConfig: UserConfig) => { 22 + const input = 23 + userConfig.input instanceof Array 24 + ? userConfig.input[0]! 25 + : userConfig.input; 22 26 const inputPath = path.join( 23 27 getSpecsPath(), 24 28 openApiVersion, 25 - typeof userConfig.input === 'string' 26 - ? userConfig.input 27 - : (userConfig.input.path as string), 29 + typeof input === 'string' ? input : (input.path as string), 28 30 ); 29 31 return { 30 32 plugins: [ ··· 48 50 outputDir, 49 51 typeof userConfig.output === 'string' ? userConfig.output : '', 50 52 ), 51 - }; 53 + } as const satisfies UserConfig; 52 54 }; 53 55 54 56 export const getSnapshotsPath = (): string =>
+2 -1
packages/openapi-ts/bin/index.cjs
··· 36 36 .option('-s, --silent', 'Set log level to silent') 37 37 .option( 38 38 '--no-log-file', 39 - 'Disable writing a log file. Works like --silent but without supressing console output', 39 + 'Disable writing a log file. Works like --silent but without suppressing console output', 40 40 ) 41 41 .option( 42 42 '-w, --watch [value]', ··· 119 119 } 120 120 121 121 userConfig.logs.file = userConfig.logFile; 122 + delete userConfig.logFile; 122 123 123 124 if (typeof params.watch === 'string') { 124 125 userConfig.watch = Number.parseInt(params.watch, 10);
+2 -2
packages/openapi-ts/package.json
··· 91 91 }, 92 92 "dependencies": { 93 93 "@hey-api/codegen-core": "workspace:^0.2.0", 94 - "@hey-api/json-schema-ref-parser": "1.1.0", 94 + "@hey-api/json-schema-ref-parser": "1.2.0", 95 95 "ansi-colors": "4.1.3", 96 - "c12": "2.0.1", 96 + "c12": "3.3.0", 97 97 "color-support": "1.1.3", 98 98 "commander": "13.0.0", 99 99 "handlebars": "4.7.8",
+4
packages/openapi-ts/src/__tests__/createClient.test.ts
··· 34 34 it('with platform string', () => { 35 35 const path = compileInputPath({ 36 36 path: 'https://get.heyapi.dev/foo/bar?branch=main&commit_sha=sha&tags=a,b,c&version=1.0.0', 37 + registry: 'hey-api', 37 38 }); 38 39 expect(path).toEqual({ 39 40 branch: 'main', ··· 41 42 organization: 'foo', 42 43 path: 'https://get.heyapi.dev/foo/bar?branch=main&commit_sha=sha&tags=a,b,c&version=1.0.0', 43 44 project: 'bar', 45 + registry: 'hey-api', 44 46 tags: ['a', 'b', 'c'], 45 47 version: '1.0.0', 46 48 }); ··· 71 73 process.env.HEY_API_TOKEN = 'foo'; 72 74 const path = compileInputPath({ 73 75 path: 'https://get.heyapi.dev/foo/bar', 76 + registry: 'hey-api', 74 77 }); 75 78 delete process.env.HEY_API_TOKEN; 76 79 expect(path).toEqual({ ··· 78 81 organization: 'foo', 79 82 path: 'https://get.heyapi.dev/foo/bar?api_key=foo', 80 83 project: 'bar', 84 + registry: 'hey-api', 81 85 }); 82 86 }); 83 87 });
+11 -17
packages/openapi-ts/src/__tests__/error.test.ts
··· 20 20 }); 21 21 22 22 it('should not prompt when isInteractive is explicitly false', async () => { 23 - // Mock stdin/stdout to ensure we don't wait for user input 24 - const writeSpy = vi 25 - .spyOn(process.stdout, 'write') 26 - .mockImplementation(() => true); 23 + // Mock stdin and console.log to ensure we don't wait for user input 24 + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined); 27 25 const setEncodingSpy = vi 28 26 .spyOn(process.stdin, 'setEncoding') 29 27 .mockImplementation(() => process.stdin as any); ··· 37 35 }); 38 36 39 37 expect(result).toBe(false); 40 - expect(writeSpy).not.toHaveBeenCalled(); 38 + expect(logSpy).not.toHaveBeenCalled(); 41 39 expect(setEncodingSpy).not.toHaveBeenCalled(); 42 40 expect(onceSpy).not.toHaveBeenCalled(); 43 41 44 - writeSpy.mockRestore(); 42 + logSpy.mockRestore(); 45 43 setEncodingSpy.mockRestore(); 46 44 onceSpy.mockRestore(); 47 45 }); 48 46 49 47 it('should prompt when isInteractive is true', async () => { 50 - // Mock stdin/stdout for interactive session 51 - const writeSpy = vi 52 - .spyOn(process.stdout, 'write') 53 - .mockImplementation(() => true); 48 + // Mock stdin and console.log for interactive session 49 + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined); 54 50 const setEncodingSpy = vi 55 51 .spyOn(process.stdin, 'setEncoding') 56 52 .mockImplementation(() => process.stdin as any); ··· 70 66 }); 71 67 72 68 expect(result).toBe(false); // User said 'n' 73 - expect(writeSpy).toHaveBeenCalledWith( 69 + expect(logSpy).toHaveBeenCalledWith( 74 70 expect.stringContaining('📢 Open a GitHub issue with crash details?'), 75 71 ); 76 72 77 - writeSpy.mockRestore(); 73 + logSpy.mockRestore(); 78 74 setEncodingSpy.mockRestore(); 79 75 onceSpy.mockRestore(); 80 76 }); 81 77 82 78 it('should handle user saying yes to crash report', async () => { 83 - // Mock stdin/stdout for interactive session 84 - const writeSpy = vi 85 - .spyOn(process.stdout, 'write') 86 - .mockImplementation(() => true); 79 + // Mock stdin and console.log for interactive session 80 + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined); 87 81 const setEncodingSpy = vi 88 82 .spyOn(process.stdin, 'setEncoding') 89 83 .mockImplementation(() => process.stdin as any); ··· 104 98 105 99 expect(result).toBe(true); // User said 'y' 106 100 107 - writeSpy.mockRestore(); 101 + logSpy.mockRestore(); 108 102 setEncodingSpy.mockRestore(); 109 103 onceSpy.mockRestore(); 110 104 });
+149 -6
packages/openapi-ts/src/__tests__/index.test.ts
··· 1 1 import { describe, expect, it } from 'vitest'; 2 2 3 - describe('main entry index', () => { 4 - describe('createClient', () => { 5 - it('should be exported', async () => { 6 - const { createClient } = await import('../index'); 7 - expect(createClient).toBeDefined(); 8 - }); 3 + import { createClient } from '../index'; 4 + 5 + type Config = Parameters<typeof createClient>[0]; 6 + 7 + describe('createClient', () => { 8 + it('1 config, 1 input, 1 output', async () => { 9 + const config: Config = { 10 + dryRun: true, 11 + input: { 12 + info: { title: 'foo', version: '1.0.0' }, 13 + openapi: '3.0.0', 14 + }, 15 + logs: { 16 + level: 'silent', 17 + }, 18 + output: 'output', 19 + plugins: ['@hey-api/typescript'], 20 + }; 21 + 22 + const results = await createClient(config); 23 + expect(results).toHaveLength(1); 24 + }); 25 + 26 + it('1 config, 2 inputs, 1 output', async () => { 27 + const config: Config = { 28 + dryRun: true, 29 + input: [ 30 + { 31 + info: { title: 'foo', version: '1.0.0' }, 32 + openapi: '3.0.0', 33 + }, 34 + { 35 + info: { title: 'bar', version: '1.0.0' }, 36 + openapi: '3.0.0', 37 + paths: {}, 38 + }, 39 + ], 40 + logs: { 41 + level: 'silent', 42 + }, 43 + output: 'output', 44 + plugins: ['@hey-api/typescript'], 45 + }; 46 + 47 + const results = await createClient(config); 48 + expect(results).toHaveLength(1); 49 + }); 50 + 51 + it('1 config, 2 inputs, 2 outputs', async () => { 52 + const config: Config = { 53 + dryRun: true, 54 + input: [ 55 + { 56 + info: { title: 'foo', version: '1.0.0' }, 57 + openapi: '3.0.0', 58 + }, 59 + { 60 + info: { title: 'bar', version: '1.0.0' }, 61 + openapi: '3.0.0', 62 + paths: {}, 63 + }, 64 + ], 65 + logs: { 66 + level: 'silent', 67 + }, 68 + output: ['output', 'output2'], 69 + plugins: ['@hey-api/typescript'], 70 + }; 71 + 72 + const results = await createClient(config); 73 + expect(results).toHaveLength(2); 74 + }); 75 + 76 + it('2 configs, 1 input, 1 output', async () => { 77 + const config: Config = [ 78 + { 79 + dryRun: true, 80 + input: { 81 + info: { title: 'foo', version: '1.0.0' }, 82 + openapi: '3.0.0', 83 + }, 84 + logs: { 85 + level: 'silent', 86 + }, 87 + output: 'output', 88 + plugins: ['@hey-api/typescript'], 89 + }, 90 + { 91 + dryRun: true, 92 + input: { 93 + info: { title: 'bar', version: '1.0.0' }, 94 + openapi: '3.0.0', 95 + }, 96 + logs: { 97 + level: 'silent', 98 + }, 99 + output: 'output2', 100 + plugins: ['@hey-api/typescript'], 101 + }, 102 + ]; 103 + 104 + const results = await createClient(config); 105 + expect(results).toHaveLength(2); 106 + }); 107 + 108 + it('2 configs, 2 inputs, 2 outputs', async () => { 109 + const config: Config = [ 110 + { 111 + dryRun: true, 112 + input: [ 113 + { 114 + info: { title: 'foo', version: '1.0.0' }, 115 + openapi: '3.0.0', 116 + }, 117 + { 118 + info: { title: 'bar', version: '1.0.0' }, 119 + openapi: '3.0.0', 120 + paths: {}, 121 + }, 122 + ], 123 + logs: { 124 + level: 'silent', 125 + }, 126 + output: ['output', 'output2'], 127 + plugins: ['@hey-api/typescript'], 128 + }, 129 + { 130 + dryRun: true, 131 + input: [ 132 + { 133 + info: { title: 'baz', version: '1.0.0' }, 134 + openapi: '3.0.0', 135 + }, 136 + { 137 + info: { title: 'qux', version: '1.0.0' }, 138 + openapi: '3.0.0', 139 + paths: {}, 140 + }, 141 + ], 142 + logs: { 143 + level: 'silent', 144 + }, 145 + output: ['output3', 'output4'], 146 + plugins: ['@hey-api/typescript'], 147 + }, 148 + ]; 149 + 150 + const results = await createClient(config); 151 + expect(results).toHaveLength(4); 9 152 }); 10 153 });
+24 -8
packages/openapi-ts/src/__tests__/interactive.test.ts
··· 2 2 3 3 import { detectInteractiveSession, initConfigs } from '../config/init'; 4 4 import { mergeConfigs } from '../config/merge'; 5 + import { Logger } from '../utils/logger'; 5 6 6 7 describe('interactive config', () => { 7 8 it('should use detectInteractiveSession when not provided', async () => { 8 9 const result = await initConfigs({ 9 - input: 'test.json', 10 - output: './test', 10 + logger: new Logger(), 11 + userConfigs: [ 12 + { 13 + input: 'test.json', 14 + output: './test', 15 + }, 16 + ], 11 17 }); 12 18 13 19 // In test environment, TTY is typically not available, so it should be false ··· 16 22 17 23 it('should respect user config when set to true', async () => { 18 24 const result = await initConfigs({ 19 - input: 'test.json', 20 - interactive: true, 21 - output: './test', 25 + logger: new Logger(), 26 + userConfigs: [ 27 + { 28 + input: 'test.json', 29 + interactive: true, 30 + output: './test', 31 + }, 32 + ], 22 33 }); 23 34 24 35 expect(result.results[0]?.config.interactive).toBe(true); ··· 26 37 27 38 it('should respect user config when set to false', async () => { 28 39 const result = await initConfigs({ 29 - input: 'test.json', 30 - interactive: false, 31 - output: './test', 40 + logger: new Logger(), 41 + userConfigs: [ 42 + { 43 + input: 'test.json', 44 + interactive: false, 45 + output: './test', 46 + }, 47 + ], 32 48 }); 33 49 34 50 expect(result.results[0]?.config.interactive).toBe(false);
+11 -11
packages/openapi-ts/src/config/__tests__/input.test.ts
··· 11 11 output: 'src/client', 12 12 }; 13 13 14 - const result = getInput(userConfig); 14 + const result = getInput(userConfig)[0]!; 15 15 expect(result.path).toBe('https://example.com/openapi.yaml'); 16 16 }); 17 17 ··· 21 21 output: 'src/client', 22 22 }; 23 23 24 - const result = getInput(userConfig); 24 + const result = getInput(userConfig)[0]!; 25 25 expect(result.path).toBe( 26 26 'https://dash.readme.com/api/v1/api-registry/abc123', 27 27 ); ··· 33 33 output: 'src/client', 34 34 }; 35 35 36 - const result = getInput(userConfig); 36 + const result = getInput(userConfig)[0]!; 37 37 expect(result.path).toBe( 38 38 'https://dash.readme.com/api/v1/api-registry/uuid123', 39 39 ); ··· 45 45 output: 'src/client', 46 46 }; 47 47 48 - const result = getInput(userConfig); 48 + const result = getInput(userConfig)[0]!; 49 49 expect(result.path).toBe( 50 50 'https://dash.readme.com/api/v1/api-registry/test-uuid-123', 51 51 ); ··· 64 64 output: 'src/client', 65 65 }; 66 66 67 - const result = getInput(userConfig); 67 + const result = getInput(userConfig)[0]!; 68 68 expect(result.path).toBe( 69 69 'https://dash.readme.com/api/v1/api-registry/abc123', 70 70 ); ··· 79 79 output: 'src/client', 80 80 }; 81 81 82 - const result = getInput(userConfig); 82 + const result = getInput(userConfig)[0]!; 83 83 expect(result.path).toBe( 84 84 'https://dash.readme.com/api/v1/api-registry/uuid', 85 85 ); ··· 95 95 output: 'src/client', 96 96 }; 97 97 98 - const result = getInput(userConfig); 98 + const result = getInput(userConfig)[0]!; 99 99 expect(result.path).toBe('https://get.heyapi.dev/myorg/myproject'); 100 100 }); 101 101 ··· 108 108 output: 'src/client', 109 109 }; 110 110 111 - const result = getInput(userConfig); 111 + const result = getInput(userConfig)[0]!; 112 112 expect(result.path).toEqual({ 113 113 info: { title: 'Test API', version: '1.0.0' }, 114 114 openapi: '3.0.0', ··· 125 125 126 126 inputs.forEach((input) => { 127 127 const userConfig: UserConfig = { input, output: 'src/client' }; 128 - const result = getInput(userConfig); 128 + const result = getInput(userConfig)[0]!; 129 129 expect(result.path).toBe(input); 130 130 }); 131 131 }); ··· 140 140 }, 141 141 }; 142 142 143 - const result = getInput(userConfig); 143 + const result = getInput(userConfig)[0]!; 144 144 expect(result.path).toBe( 145 145 'https://dash.readme.com/api/v1/api-registry/abc123', 146 146 ); ··· 160 160 output: 'src/client', 161 161 }; 162 162 163 - const result = getInput(userConfig); 163 + const result = getInput(userConfig)[0]!; 164 164 expect(result.path).toBe( 165 165 'https://dash.readme.com/api/v1/api-registry/test123', 166 166 );
+93 -44
packages/openapi-ts/src/config/init.ts
··· 1 1 import path from 'node:path'; 2 2 3 + import colors from 'ansi-colors'; 3 4 import { loadConfig } from 'c12'; 4 5 5 6 import { ConfigError } from '../error'; 6 7 import type { Config, UserConfig } from '../types/config'; 8 + import type { ArrayOnly } from '../types/utils'; 7 9 import { isLegacyClient, setConfig } from '../utils/config'; 10 + import type { Logger } from '../utils/logger'; 8 11 import { getInput } from './input'; 9 12 import { getLogs } from './logs'; 10 13 import { mergeConfigs } from './merge'; ··· 13 16 import { getParser } from './parser'; 14 17 import { getPlugins } from './plugins'; 15 18 19 + type ConfigResult = { 20 + config: Config; 21 + errors: ReadonlyArray<Error>; 22 + jobIndex: number; 23 + }; 24 + 25 + export type Configs = { 26 + dependencies: Record<string, string>; 27 + results: ReadonlyArray<ConfigResult>; 28 + }; 29 + 16 30 /** 17 31 * Detect if the current session is interactive based on TTY status and environment variables. 18 32 * This is used as a fallback when the user doesn't explicitly set the interactive option. ··· 30 44 /** 31 45 * @internal 32 46 */ 33 - export const initConfigs = async ( 34 - userConfig: UserConfig | undefined, 35 - ): Promise<{ 36 - dependencies: Record<string, string>; 37 - results: ReadonlyArray<{ 38 - config: Config; 39 - errors: ReadonlyArray<Error>; 40 - }>; 41 - }> => { 42 - let configurationFile: string | undefined = undefined; 43 - if (userConfig?.configFile) { 44 - const parts = userConfig.configFile.split('.'); 45 - configurationFile = parts.slice(0, parts.length - 1).join('.'); 46 - } 47 + export const initConfigs = async ({ 48 + logger, 49 + userConfigs, 50 + }: { 51 + logger: Logger; 52 + userConfigs: ReadonlyArray<UserConfig>; 53 + }): Promise<Configs> => { 54 + const configs: Array<UserConfig> = []; 55 + let dependencies: Record<string, string> = {}; 56 + 57 + const eventLoad = logger.timeEvent('load'); 58 + for (const userConfig of userConfigs) { 59 + let configurationFile: string | undefined = undefined; 60 + if (userConfig?.configFile) { 61 + const parts = userConfig.configFile.split('.'); 62 + configurationFile = parts.slice(0, parts.length - 1).join('.'); 63 + } 64 + 65 + const eventC12 = logger.timeEvent('c12'); 66 + const { config: configFromFile, configFile: loadedConfigFile } = 67 + await loadConfig<UserConfig>({ 68 + configFile: configurationFile, 69 + name: 'openapi-ts', 70 + }); 71 + eventC12.timeEnd(); 47 72 48 - const { config: configFromFile, configFile: loadedConfigFile } = 49 - await loadConfig<UserConfig>({ 50 - configFile: configurationFile, 51 - name: 'openapi-ts', 52 - }); 73 + if (!Object.keys(dependencies).length) { 74 + // TODO: handle dependencies for multiple configs properly? 75 + dependencies = getProjectDependencies( 76 + Object.keys(configFromFile).length ? loadedConfigFile : undefined, 77 + ); 78 + } 79 + 80 + const mergedConfigs = 81 + configFromFile instanceof Array 82 + ? configFromFile.map((config) => mergeConfigs(config, userConfig)) 83 + : [mergeConfigs(configFromFile, userConfig)]; 53 84 54 - const dependencies = getProjectDependencies( 55 - Object.keys(configFromFile).length ? loadedConfigFile : undefined, 56 - ); 85 + for (const mergedConfig of mergedConfigs) { 86 + const input = getInput(mergedConfig); 57 87 58 - const userConfigs: ReadonlyArray<UserConfig> = Array.isArray(userConfig) 59 - ? userConfig 60 - : Array.isArray(configFromFile) 61 - ? configFromFile.map((config) => mergeConfigs(config, userConfig)) 62 - : [mergeConfigs(configFromFile, userConfig)]; 88 + if (mergedConfig.output instanceof Array) { 89 + const countInputs = input.length; 90 + const countOutputs = mergedConfig.output.length; 91 + if (countOutputs > 1) { 92 + if (countInputs !== countOutputs) { 93 + console.warn( 94 + `⚙️ ${colors.yellow('Warning:')} You provided ${colors.cyan(String(countInputs))} ${colors.cyan(countInputs === 1 ? 'input' : 'inputs')} and ${colors.yellow(String(countOutputs))} ${colors.yellow('outputs')}. This is probably not what you want as it will produce identical output in multiple locations. You most likely want to provide a single output or the same number of outputs as inputs.`, 95 + ); 96 + for (const output of mergedConfig.output) { 97 + configs.push({ ...mergedConfig, input, output }); 98 + } 99 + } else { 100 + mergedConfig.output.forEach((output, index) => { 101 + configs.push({ ...mergedConfig, input: input[index]!, output }); 102 + }); 103 + } 104 + } else { 105 + configs.push({ 106 + ...mergedConfig, 107 + input, 108 + output: mergedConfig.output[0] ?? '', 109 + }); 110 + } 111 + } else { 112 + configs.push({ ...mergedConfig, input }); 113 + } 114 + } 115 + } 116 + eventLoad.timeEnd(); 63 117 64 - const results: Array<{ 65 - config: Config; 66 - errors: Array<Error>; 67 - }> = []; 118 + const results: Array<ArrayOnly<ConfigResult>> = []; 68 119 69 - for (const userConfig of userConfigs) { 120 + const eventBuild = logger.timeEvent('build'); 121 + for (const userConfig of configs) { 70 122 const { 71 123 base, 72 124 configFile = '', ··· 83 135 ? userConfig.interactive 84 136 : detectInteractiveSession(); 85 137 86 - const errors: Array<Error> = []; 87 - 88 138 const logs = getLogs(userConfig); 89 139 90 - if (logs.level === 'debug') { 91 - console.warn('userConfig:', userConfig); 92 - } 93 - 94 140 const input = getInput(userConfig); 95 141 const output = getOutput(userConfig); 96 142 const parser = getParser(userConfig); 97 143 98 - if (!input.path) { 144 + const errors: Array<Error> = []; 145 + 146 + if (!input.length) { 99 147 errors.push( 100 148 new ConfigError( 101 149 'missing input - which OpenAPI specification should we use to generate your output?', ··· 149 197 }); 150 198 config.exportCore = isLegacyClient(config) ? exportCore : false; 151 199 200 + const jobIndex = results.length; 201 + 152 202 if (logs.level === 'debug') { 153 - console.warn('config:', config); 203 + const jobPrefix = colors.gray(`[Job ${jobIndex + 1}] `); 204 + console.warn(`${jobPrefix}${colors.cyan('config:')}`, config); 154 205 } 155 206 156 - results.push({ 157 - config, 158 - errors, 159 - }); 207 + results.push({ config, errors, jobIndex }); 160 208 } 209 + eventBuild.timeEnd(); 161 210 162 211 return { dependencies, results }; 163 212 };
+53 -45
packages/openapi-ts/src/config/input.ts
··· 1 1 import type { Config, UserConfig } from '../types/config'; 2 - import type { Input } from '../types/input'; 2 + import type { Input, Watch } from '../types/input'; 3 3 import { inputToApiRegistry } from '../utils/input'; 4 4 import { heyApiRegistryBaseUrl } from '../utils/input/heyApi'; 5 5 6 - const defaultWatch: Config['input']['watch'] = { 6 + const defaultWatch: Watch = { 7 7 enabled: false, 8 8 interval: 1_000, 9 9 timeout: 60_000, 10 10 }; 11 11 12 - const getWatch = ( 13 - input: Pick<Config['input'], 'path' | 'watch'>, 14 - ): Config['input']['watch'] => { 12 + // watch only remote files 13 + const getWatch = (input: Pick<Input, 'path' | 'watch'>): Watch => { 15 14 let watch = { ...defaultWatch }; 16 15 17 16 // we cannot watch spec passed as an object ··· 35 34 }; 36 35 37 36 export const getInput = (userConfig: UserConfig): Config['input'] => { 38 - let input: Config['input'] = { 39 - path: '', 40 - watch: defaultWatch, 41 - }; 37 + const userInputs = 38 + userConfig.input instanceof Array ? userConfig.input : [userConfig.input]; 42 39 43 - if (typeof userConfig.input === 'string') { 44 - input.path = userConfig.input; 45 - } else if ( 46 - userConfig.input && 47 - (userConfig.input.path !== undefined || 48 - userConfig.input.organization !== undefined) 49 - ) { 50 - // @ts-expect-error 51 - input = { 52 - ...input, 53 - path: heyApiRegistryBaseUrl, 54 - ...userConfig.input, 40 + const inputs: Array<Input> = []; 41 + 42 + for (const userInput of userInputs) { 43 + let input: Input = { 44 + path: '', 45 + watch: defaultWatch, 55 46 }; 56 47 57 - // watch only remote files 58 - if (input.watch !== undefined) { 59 - input.watch = getWatch(input); 48 + if (typeof userInput === 'string') { 49 + input.path = userInput; 50 + } else if ( 51 + userInput && 52 + (userInput.path !== undefined || userInput.organization !== undefined) 53 + ) { 54 + // @ts-expect-error 55 + input = { 56 + ...input, 57 + path: heyApiRegistryBaseUrl, 58 + ...userInput, 59 + }; 60 + 61 + if (input.watch !== undefined) { 62 + input.watch = getWatch(input); 63 + } 64 + } else { 65 + input = { 66 + ...input, 67 + path: userInput, 68 + }; 60 69 } 61 - } else { 62 - input = { 63 - ...input, 64 - path: userConfig.input as Record<string, unknown>, 65 - }; 66 - } 70 + 71 + if (typeof input.path === 'string') { 72 + inputToApiRegistry(input as Input & { path: string }); 73 + } 67 74 68 - if (typeof input.path === 'string') { 69 - inputToApiRegistry(input as Input & { path: string }); 70 - } 75 + if ( 76 + userConfig.watch !== undefined && 77 + input.watch.enabled === defaultWatch.enabled && 78 + input.watch.interval === defaultWatch.interval && 79 + input.watch.timeout === defaultWatch.timeout 80 + ) { 81 + input.watch = getWatch({ 82 + path: input.path, 83 + // @ts-expect-error 84 + watch: userConfig.watch, 85 + }); 86 + } 71 87 72 - if ( 73 - userConfig.watch !== undefined && 74 - input.watch.enabled === defaultWatch.enabled && 75 - input.watch.interval === defaultWatch.interval && 76 - input.watch.timeout === defaultWatch.timeout 77 - ) { 78 - input.watch = getWatch({ 79 - path: input.path, 80 - // @ts-expect-error 81 - watch: userConfig.watch, 82 - }); 88 + if (input.path) { 89 + inputs.push(input); 90 + } 83 91 } 84 92 85 - return input; 93 + return inputs; 86 94 };
+3 -1
packages/openapi-ts/src/config/logs.ts
··· 1 1 import type { Config, UserConfig } from '../types/config'; 2 2 3 - export const getLogs = (userConfig: UserConfig | undefined): Config['logs'] => { 3 + export const getLogs = ( 4 + userConfig: Pick<UserConfig, 'logs'> | undefined, 5 + ): Config['logs'] => { 4 6 let logs: Config['logs'] = { 5 7 file: true, 6 8 level: 'info',
+6
packages/openapi-ts/src/config/output.ts
··· 5 5 import { valueToObject } from './utils/config'; 6 6 7 7 export const getOutput = (userConfig: UserConfig): Config['output'] => { 8 + if (userConfig.output instanceof Array) { 9 + throw new Error( 10 + 'Unexpected array of outputs in user configuration. This should have been expanded already.', 11 + ); 12 + } 13 + 8 14 const output = valueToObject({ 9 15 defaultValue: { 10 16 clean: true,
+177 -66
packages/openapi-ts/src/createClient.ts
··· 1 1 import path from 'node:path'; 2 2 3 + import { $RefParser } from '@hey-api/json-schema-ref-parser'; 3 4 import colors from 'ansi-colors'; 4 5 5 6 import { generateLegacyOutput } from './generate/legacy/output'; ··· 11 12 import { processOutput } from './processOutput'; 12 13 import type { Client } from './types/client'; 13 14 import type { Config } from './types/config'; 15 + import type { Input } from './types/input'; 14 16 import type { WatchValues } from './types/types'; 15 17 import { isLegacyClient, legacyNameFromConfig } from './utils/config'; 16 18 import type { Templates } from './utils/handlebars'; 17 - import { heyApiRegistryBaseUrl } from './utils/input/heyApi'; 18 19 import type { Logger } from './utils/logger'; 19 20 import { postProcessClient } from './utils/postprocess'; 20 21 21 - const isHeyApiRegistryPath = (path: string) => 22 - path.startsWith(heyApiRegistryBaseUrl); 23 - // || path.startsWith('http://localhost:4000') 24 - 25 - export const compileInputPath = (input: Omit<Config['input'], 'watch'>) => { 22 + export const compileInputPath = (input: Omit<Input, 'watch'>) => { 26 23 const result: Pick< 27 - Partial<Config['input']>, 24 + Partial<Input>, 28 25 | 'api_key' 29 26 | 'branch' 30 27 | 'commit_sha' 31 28 | 'organization' 32 29 | 'project' 30 + | 'registry' 33 31 | 'tags' 34 32 | 'version' 35 33 > & 36 - Pick<Required<Config['input']>, 'path'> = { 34 + Pick<Input, 'path'> = { 35 + ...input, 37 36 path: '', 38 37 }; 39 38 40 39 if ( 41 40 input.path && 42 - (typeof input.path !== 'string' || !isHeyApiRegistryPath(input.path)) 41 + (typeof input.path !== 'string' || input.registry !== 'hey-api') 43 42 ) { 44 43 result.path = input.path; 45 44 return result; ··· 131 130 return result; 132 131 }; 133 132 134 - const logInputPath = (inputPath: ReturnType<typeof compileInputPath>) => { 135 - const baseString = colors.cyan('Generating from'); 133 + const logInputPaths = ( 134 + inputPaths: ReadonlyArray<ReturnType<typeof compileInputPath>>, 135 + jobIndex: number, 136 + ) => { 137 + const lines: Array<string> = []; 136 138 137 - if (typeof inputPath.path === 'string') { 138 - const baseInput = isHeyApiRegistryPath(inputPath.path) 139 - ? `${inputPath.organization ?? ''}/${inputPath.project ?? ''}` 140 - : inputPath.path; 141 - console.log(`⏳ ${baseString} ${baseInput}`); 142 - if (isHeyApiRegistryPath(inputPath.path)) { 143 - if (inputPath.branch) { 144 - console.log( 145 - `${colors.gray('branch:')} ${colors.green(inputPath.branch)}`, 139 + const jobPrefix = colors.gray(`[Job ${jobIndex + 1}] `); 140 + const count = inputPaths.length; 141 + const baseString = colors.cyan( 142 + `Generating from ${count} ${count === 1 ? 'input' : 'inputs'}:`, 143 + ); 144 + lines.push(`${jobPrefix}⏳ ${baseString}`); 145 + 146 + inputPaths.forEach((inputPath, index) => { 147 + const itemPrefixStr = ` [${index + 1}] `; 148 + const itemPrefix = colors.cyan(itemPrefixStr); 149 + const detailIndent = ' '.repeat(itemPrefixStr.length); 150 + 151 + if (typeof inputPath.path !== 'string') { 152 + lines.push(`${jobPrefix}${itemPrefix}raw OpenAPI specification`); 153 + return; 154 + } 155 + 156 + switch (inputPath.registry) { 157 + case 'hey-api': { 158 + const baseInput = [inputPath.organization, inputPath.project] 159 + .filter(Boolean) 160 + .join('/'); 161 + lines.push(`${jobPrefix}${itemPrefix}${baseInput}`); 162 + if (inputPath.branch) { 163 + lines.push( 164 + `${jobPrefix}${detailIndent}${colors.gray('branch:')} ${colors.green( 165 + inputPath.branch, 166 + )}`, 167 + ); 168 + } 169 + if (inputPath.commit_sha) { 170 + lines.push( 171 + `${jobPrefix}${detailIndent}${colors.gray('commit:')} ${colors.green( 172 + inputPath.commit_sha, 173 + )}`, 174 + ); 175 + } 176 + if (inputPath.tags?.length) { 177 + lines.push( 178 + `${jobPrefix}${detailIndent}${colors.gray('tags:')} ${colors.green( 179 + inputPath.tags.join(', '), 180 + )}`, 181 + ); 182 + } 183 + if (inputPath.version) { 184 + lines.push( 185 + `${jobPrefix}${detailIndent}${colors.gray('version:')} ${colors.green( 186 + inputPath.version, 187 + )}`, 188 + ); 189 + } 190 + lines.push( 191 + `${jobPrefix}${detailIndent}${colors.gray('registry:')} ${colors.green('Hey API')}`, 146 192 ); 193 + break; 147 194 } 148 - if (inputPath.commit_sha) { 149 - console.log( 150 - `${colors.gray('commit:')} ${colors.green(inputPath.commit_sha)}`, 195 + case 'readme': { 196 + const baseInput = [inputPath.organization, inputPath.project] 197 + .filter(Boolean) 198 + .join('/'); 199 + if (!baseInput) { 200 + lines.push(`${jobPrefix}${itemPrefix}${inputPath.path}`); 201 + } else { 202 + lines.push(`${jobPrefix}${itemPrefix}${baseInput}`); 203 + } 204 + // @ts-expect-error 205 + if (inputPath.uuid) { 206 + lines.push( 207 + `${jobPrefix}${detailIndent}${colors.gray('uuid:')} ${colors.green( 208 + // @ts-expect-error 209 + inputPath.uuid, 210 + )}`, 211 + ); 212 + } 213 + lines.push( 214 + `${jobPrefix}${detailIndent}${colors.gray('registry:')} ${colors.green('ReadMe')}`, 151 215 ); 216 + break; 152 217 } 153 - if (inputPath.tags?.length) { 154 - console.log( 155 - `${colors.gray('tags:')} ${colors.green(inputPath.tags.join(', '))}`, 218 + case 'scalar': { 219 + const baseInput = [inputPath.organization, inputPath.project] 220 + .filter(Boolean) 221 + .join('/'); 222 + lines.push(`${jobPrefix}${itemPrefix}${baseInput}`); 223 + lines.push( 224 + `${jobPrefix}${detailIndent}${colors.gray('registry:')} ${colors.green('Scalar')}`, 156 225 ); 226 + break; 157 227 } 158 - if (inputPath.version) { 159 - console.log( 160 - `${colors.gray('version:')} ${colors.green(inputPath.version)}`, 161 - ); 162 - } 228 + default: 229 + lines.push(`${jobPrefix}${itemPrefix}${inputPath.path}`); 230 + break; 163 231 } 164 - } else { 165 - console.log(`⏳ ${baseString} raw OpenAPI specification`); 232 + }); 233 + 234 + for (const line of lines) { 235 + console.log(line); 166 236 } 167 237 }; 168 238 169 239 export const createClient = async ({ 170 240 config, 171 241 dependencies, 242 + jobIndex, 172 243 logger, 173 244 templates, 174 - watch: _watch, 245 + watches: _watches, 175 246 }: { 176 247 config: Config; 177 248 dependencies: Record<string, string>; 249 + jobIndex: number; 178 250 logger: Logger; 179 251 templates: Templates; 180 252 /** 181 - * Always falsy on the first run, truthy on subsequent runs. 253 + * Always undefined on the first run, defined on subsequent runs. 182 254 */ 183 - watch?: WatchValues; 184 - }) => { 185 - const inputPath = compileInputPath(config.input); 186 - const { timeout } = config.input.watch; 255 + watches?: ReadonlyArray<WatchValues>; 256 + }): Promise<Client | undefined | IR.Context> => { 257 + const watches: ReadonlyArray<WatchValues> = 258 + _watches || 259 + Array.from({ length: config.input.length }, () => ({ 260 + headers: new Headers(), 261 + })); 187 262 188 - const watch: WatchValues = _watch || { headers: new Headers() }; 263 + const inputPaths = config.input.map((input) => compileInputPath(input)); 189 264 190 265 // on first run, print the message as soon as possible 191 - if (config.logs.level !== 'silent' && !_watch) { 192 - logInputPath(inputPath); 266 + if (config.logs.level !== 'silent' && !_watches) { 267 + logInputPaths(inputPaths, jobIndex); 193 268 } 194 269 195 - const eventSpec = logger.timeEvent('spec'); 196 - const { data, error, response } = await getSpec({ 197 - fetchOptions: config.input.fetch, 198 - inputPath: inputPath.path, 199 - timeout, 200 - watch, 201 - }); 202 - eventSpec.timeEnd(); 270 + const getSpecData = async (input: Input, index: number) => { 271 + const eventSpec = logger.timeEvent('spec'); 272 + const { arrayBuffer, error, resolvedInput, response } = await getSpec({ 273 + fetchOptions: input.fetch, 274 + inputPath: inputPaths[index]!.path, 275 + timeout: input.watch.timeout, 276 + watch: watches[index]!, 277 + }); 278 + eventSpec.timeEnd(); 279 + 280 + // throw on first run if there's an error to preserve user experience 281 + // if in watch mode, subsequent errors won't throw to gracefully handle 282 + // cases where server might be reloading 283 + if (error && !_watches) { 284 + throw new Error( 285 + `Request failed with status ${response.status}: ${response.statusText}`, 286 + ); 287 + } 203 288 204 - // throw on first run if there's an error to preserve user experience 205 - // if in watch mode, subsequent errors won't throw to gracefully handle 206 - // cases where server might be reloading 207 - if (error && !_watch) { 208 - throw new Error( 209 - `Request failed with status ${response.status}: ${response.statusText}`, 210 - ); 211 - } 289 + return { arrayBuffer, resolvedInput }; 290 + }; 291 + const specData = ( 292 + await Promise.all( 293 + config.input.map((input, index) => getSpecData(input, index)), 294 + ) 295 + ).filter((data) => data.arrayBuffer || data.resolvedInput); 212 296 213 297 let client: Client | undefined; 214 298 let context: IR.Context | undefined; 215 299 216 - if (data) { 217 - // on subsequent runs in watch mode, print the mssage only if we know we're 300 + if (specData.length) { 301 + const refParser = new $RefParser(); 302 + const data = 303 + specData.length > 1 304 + ? await refParser.bundleMany({ 305 + arrayBuffer: specData.map((data) => data.arrayBuffer!), 306 + pathOrUrlOrSchemas: [], 307 + resolvedInputs: specData.map((data) => data.resolvedInput!), 308 + }) 309 + : await refParser.bundle({ 310 + arrayBuffer: specData[0]!.arrayBuffer, 311 + pathOrUrlOrSchema: undefined, 312 + resolvedInput: specData[0]!.resolvedInput, 313 + }); 314 + 315 + // on subsequent runs in watch mode, print the message only if we know we're 218 316 // generating the output 219 - if (config.logs.level !== 'silent' && _watch) { 317 + if (config.logs.level !== 'silent' && _watches) { 220 318 console.clear(); 221 - logInputPath(inputPath); 319 + logInputPaths(inputPaths, jobIndex); 222 320 } 223 321 224 322 const eventInputPatch = logger.timeEvent('input.patch'); ··· 257 355 const outputPath = process.env.INIT_CWD 258 356 ? `./${path.relative(process.env.INIT_CWD, config.output.path)}` 259 357 : config.output.path; 358 + const jobPrefix = colors.gray(`[Job ${jobIndex + 1}] `); 260 359 console.log( 261 - `${colors.green('🚀 Done!')} Your output is in ${colors.cyanBright(outputPath)}`, 360 + `${jobPrefix}${colors.green('✅ Done!')} Your output is in ${colors.cyanBright(outputPath)}`, 262 361 ); 263 362 } 264 363 } 265 364 eventPostprocess.timeEnd(); 266 365 } 267 366 268 - if (config.input.watch.enabled && typeof inputPath.path === 'string') { 367 + const watchedInput = config.input.find( 368 + (input, index) => 369 + input.watch.enabled && typeof inputPaths[index]!.path === 'string', 370 + ); 371 + 372 + if (watchedInput) { 269 373 setTimeout(() => { 270 - createClient({ config, dependencies, logger, templates, watch }); 271 - }, config.input.watch.interval); 374 + createClient({ 375 + config, 376 + dependencies, 377 + jobIndex, 378 + logger, 379 + templates, 380 + watches, 381 + }); 382 + }, watchedInput.watch.interval); 272 383 } 273 384 274 385 return context || client;
+1 -1
packages/openapi-ts/src/debug/ir.ts
··· 43 43 if (verbosity === 'summary' && obj && typeof obj === 'object') { 44 44 if (kind === 'responses') { 45 45 const count = Object.keys(obj).length; 46 - const noun = count !== 1 ? 'codes' : 'code'; 46 + const noun = count === 1 ? 'code' : 'codes'; 47 47 log(`responses: ${colors.yellow(`${count} ${noun}`)}`, level); 48 48 } else if (kind === 'requestBody') { 49 49 log(`requestBody: ${Object.keys(obj).join(', ')}`, level);
+113 -46
packages/openapi-ts/src/error.ts
··· 3 3 4 4 import colors from 'ansi-colors'; 5 5 6 - import { findPackageJson } from './generate/tsConfig'; 6 + import { loadPackageJson } from './generate/tsConfig'; 7 7 import { ensureDirSync } from './generate/utils'; 8 8 9 - export class ConfigError extends Error {} 9 + type IJobError = { 10 + error: Error; 11 + jobIndex: number; 12 + }; 13 + 14 + /** 15 + * Represents a single configuration error. 16 + * 17 + * Used for reporting issues with a specific config instance. 18 + */ 19 + export class ConfigError extends Error { 20 + constructor(message: string) { 21 + super(message); 22 + this.name = 'ConfigError'; 23 + } 24 + } 25 + 26 + /** 27 + * Aggregates multiple config errors with their job indices for reporting. 28 + */ 29 + export class ConfigValidationError extends Error { 30 + readonly errors: ReadonlyArray<IJobError>; 31 + 32 + constructor(errors: Array<IJobError>) { 33 + super( 34 + `Found ${errors.length} configuration ${errors.length === 1 ? 'error' : 'errors'}.`, 35 + ); 36 + this.name = 'ConfigValidationError'; 37 + this.errors = errors; 38 + } 39 + } 40 + 41 + /** 42 + * Represents a runtime error originating from a specific job. 43 + * 44 + * Used for reporting job-level failures that are not config validation errors. 45 + */ 46 + export class JobError extends Error { 47 + readonly originalError: IJobError; 48 + 49 + constructor(message: string, error: IJobError) { 50 + super(message); 51 + this.name = 'JobError'; 52 + this.originalError = error; 53 + } 54 + } 10 55 11 56 export class HeyApiError extends Error { 12 57 args: ReadonlyArray<unknown>; ··· 42 87 error: unknown, 43 88 logsDir: string, 44 89 ): string | undefined => { 45 - if (error instanceof ConfigError) { 90 + if (error instanceof ConfigError || error instanceof ConfigValidationError) { 46 91 return; 92 + } 93 + 94 + if (error instanceof JobError) { 95 + error = error.originalError.error; 47 96 } 48 97 49 98 const logName = `openapi-ts-error-${Date.now()}.log`; ··· 75 124 }; 76 125 77 126 export const openGitHubIssueWithCrashReport = async (error: unknown) => { 127 + const packageJson = loadPackageJson(); 128 + if (!packageJson.bugs.url) return; 129 + 130 + if (error instanceof JobError) { 131 + error = error.originalError.error; 132 + } 133 + 78 134 let body = ''; 79 135 80 136 if (error instanceof HeyApiError) { ··· 98 154 labels: 'bug 🔥', 99 155 title: 'Crash Report', 100 156 }); 101 - 102 - const packageJson = findPackageJson(); 103 - let bugsUrl: string | undefined; 104 - if ( 105 - packageJson && 106 - typeof packageJson === 'object' && 107 - 'bugs' in packageJson && 108 - packageJson.bugs && 109 - typeof packageJson.bugs === 'object' && 110 - 'url' in packageJson.bugs && 111 - typeof packageJson.bugs.url === 'string' 112 - ) { 113 - bugsUrl = packageJson.bugs.url; 114 - if (bugsUrl && !bugsUrl.endsWith('/')) { 115 - bugsUrl += '/'; 116 - } 117 - } 118 - 119 - if (bugsUrl) { 120 - const url = `${bugsUrl}new?${search.toString()}`; 121 - const open = (await import('open')).default; 122 - await open(url); 123 - } 157 + const url = `${packageJson.bugs.url}new?${search.toString()}`; 158 + const open = (await import('open')).default; 159 + await open(url); 124 160 }; 125 161 126 162 export const printCrashReport = ({ ··· 130 166 error: unknown; 131 167 logPath: string | undefined; 132 168 }) => { 133 - const packageJson = findPackageJson(); 134 - let name: string | undefined; 135 - if ( 136 - packageJson && 137 - typeof packageJson === 'object' && 138 - 'name' in packageJson && 139 - typeof packageJson.name === 'string' 140 - ) { 141 - name = packageJson.name; 169 + if (error instanceof ConfigValidationError && error.errors.length) { 170 + const groupByJob = new Map<number, Array<Error>>(); 171 + for (const { error: err, jobIndex } of error.errors) { 172 + if (!groupByJob.has(jobIndex)) { 173 + groupByJob.set(jobIndex, []); 174 + } 175 + groupByJob.get(jobIndex)!.push(err); 176 + } 177 + 178 + for (const [jobIndex, errors] of groupByJob.entries()) { 179 + const jobPrefix = colors.gray(`[Job ${jobIndex + 1}] `); 180 + const count = errors.length; 181 + const baseString = colors.red( 182 + `Found ${count} configuration ${count === 1 ? 'error' : 'errors'}:`, 183 + ); 184 + console.error(`${jobPrefix}❗️ ${baseString}`); 185 + errors.forEach((err, index) => { 186 + const itemPrefixStr = ` [${index + 1}] `; 187 + const itemPrefix = colors.red(itemPrefixStr); 188 + console.error(`${jobPrefix}${itemPrefix}${colors.white(err.message)}`); 189 + }); 190 + } 191 + } else { 192 + let jobPrefix = colors.gray('[root] '); 193 + if (error instanceof JobError) { 194 + jobPrefix = colors.gray(`[Job ${error.originalError.jobIndex + 1}] `); 195 + error = error.originalError.error; 196 + } 197 + 198 + const baseString = colors.red('Failed with the message:'); 199 + console.error(`${jobPrefix}❌ ${baseString}`); 200 + const itemPrefixStr = ` `; 201 + const itemPrefix = colors.red(itemPrefixStr); 202 + console.error( 203 + `${jobPrefix}${itemPrefix}${typeof error === 'string' ? error : error instanceof Error ? error.message : 'Unknown error'}`, 204 + ); 205 + } 206 + 207 + if (logPath) { 208 + const jobPrefix = colors.gray('[root] '); 209 + console.error( 210 + `${jobPrefix}${colors.cyan('📄 Crash log saved to:')} ${colors.gray(logPath)}`, 211 + ); 142 212 } 143 - process.stderr.write( 144 - `\n🛑 ${colors.cyan(name || '')} ${colors.red('encountered an error.')}` + 145 - `\n\n${colors.red('❗️ Error:')} ${colors.white(typeof error === 'string' ? error : error instanceof Error ? error.message : 'Unknown error')}` + 146 - (logPath 147 - ? `\n\n${colors.cyan('📄 Crash log saved to:')} ${colors.gray(logPath)}` 148 - : '') + 149 - '\n', 150 - ); 151 213 }; 152 214 153 215 export const shouldReportCrash = async ({ ··· 157 219 error: unknown; 158 220 isInteractive: boolean | undefined; 159 221 }): Promise<boolean> => { 160 - if (!isInteractive || error instanceof ConfigError) { 222 + if ( 223 + !isInteractive || 224 + error instanceof ConfigError || 225 + error instanceof ConfigValidationError 226 + ) { 161 227 return false; 162 228 } 163 229 164 230 return new Promise((resolve) => { 165 - process.stdout.write( 166 - `${colors.yellow('\n📢 Open a GitHub issue with crash details?')} ${colors.yellow('(y/N):')}`, 231 + const jobPrefix = colors.gray('[root] '); 232 + console.log( 233 + `${jobPrefix}${colors.yellow('📢 Open a GitHub issue with crash details? (y/N):')}`, 167 234 ); 168 235 process.stdin.setEncoding('utf8'); 169 236 process.stdin.once('data', (data: string) => {
+9 -7
packages/openapi-ts/src/generate/__tests__/class.test.ts
··· 17 17 dryRun: false, 18 18 experimentalParser: false, 19 19 exportCore: true, 20 - input: { 21 - path: '', 22 - watch: { 23 - enabled: false, 24 - interval: 1_000, 25 - timeout: 60_000, 20 + input: [ 21 + { 22 + path: '', 23 + watch: { 24 + enabled: false, 25 + interval: 1_000, 26 + timeout: 60_000, 27 + }, 26 28 }, 27 - }, 29 + ], 28 30 interactive: false, 29 31 logs: { 30 32 file: true,
+27 -21
packages/openapi-ts/src/generate/__tests__/core.test.ts
··· 32 32 dryRun: false, 33 33 experimentalParser: false, 34 34 exportCore: true, 35 - input: { 36 - path: '', 37 - watch: { 38 - enabled: false, 39 - interval: 1_000, 40 - timeout: 60_000, 35 + input: [ 36 + { 37 + path: '', 38 + watch: { 39 + enabled: false, 40 + interval: 1_000, 41 + timeout: 60_000, 42 + }, 41 43 }, 42 - }, 44 + ], 43 45 interactive: false, 44 46 logs: { 45 47 file: true, ··· 187 189 dryRun: false, 188 190 experimentalParser: false, 189 191 exportCore: true, 190 - input: { 191 - path: '', 192 - watch: { 193 - enabled: false, 194 - interval: 1_000, 195 - timeout: 60_000, 192 + input: [ 193 + { 194 + path: '', 195 + watch: { 196 + enabled: false, 197 + interval: 1_000, 198 + timeout: 60_000, 199 + }, 196 200 }, 197 - }, 201 + ], 198 202 interactive: false, 199 203 logs: { 200 204 file: true, ··· 325 329 dryRun: false, 326 330 experimentalParser: false, 327 331 exportCore: true, 328 - input: { 329 - path: '', 330 - watch: { 331 - enabled: false, 332 - interval: 1_000, 333 - timeout: 60_000, 332 + input: [ 333 + { 334 + path: '', 335 + watch: { 336 + enabled: false, 337 + interval: 1_000, 338 + timeout: 60_000, 339 + }, 334 340 }, 335 - }, 341 + ], 336 342 interactive: false, 337 343 logs: { 338 344 file: true,
+9 -7
packages/openapi-ts/src/generate/legacy/__tests__/index.test.ts
··· 17 17 dryRun: false, 18 18 experimentalParser: false, 19 19 exportCore: true, 20 - input: { 21 - path: '', 22 - watch: { 23 - enabled: false, 24 - interval: 1_000, 25 - timeout: 60_000, 20 + input: [ 21 + { 22 + path: '', 23 + watch: { 24 + enabled: false, 25 + interval: 1_000, 26 + timeout: 60_000, 27 + }, 26 28 }, 27 - }, 29 + ], 28 30 interactive: false, 29 31 logs: { 30 32 file: true,
+9 -7
packages/openapi-ts/src/generate/legacy/__tests__/output.test.ts
··· 30 30 dryRun: false, 31 31 experimentalParser: false, 32 32 exportCore: true, 33 - input: { 34 - path: '', 35 - watch: { 36 - enabled: false, 37 - interval: 1_000, 38 - timeout: 60_000, 33 + input: [ 34 + { 35 + path: '', 36 + watch: { 37 + enabled: false, 38 + interval: 1_000, 39 + timeout: 60_000, 40 + }, 39 41 }, 40 - }, 42 + ], 41 43 interactive: false, 42 44 logs: { 43 45 file: true,
+40
packages/openapi-ts/src/generate/tsConfig.ts
··· 30 30 return; 31 31 }; 32 32 33 + export const loadPackageJson = () => { 34 + const packageJson = findPackageJson(); 35 + 36 + const safePackage = { 37 + bugs: { 38 + url: '', 39 + }, 40 + name: '', 41 + version: '', 42 + }; 43 + 44 + if (packageJson && typeof packageJson === 'object') { 45 + if ('name' in packageJson && typeof packageJson.name === 'string') { 46 + safePackage.name = packageJson.name; 47 + } 48 + 49 + if ('version' in packageJson && typeof packageJson.version === 'string') { 50 + safePackage.version = packageJson.version; 51 + } 52 + 53 + if ( 54 + 'bugs' in packageJson && 55 + packageJson.bugs && 56 + typeof packageJson.bugs === 'object' 57 + ) { 58 + if ( 59 + 'url' in packageJson.bugs && 60 + typeof packageJson.bugs.url === 'string' 61 + ) { 62 + safePackage.bugs.url = packageJson.bugs.url; 63 + if (safePackage.bugs.url && !safePackage.bugs.url.endsWith('/')) { 64 + safePackage.bugs.url += '/'; 65 + } 66 + } 67 + } 68 + } 69 + 70 + return safePackage; 71 + }; 72 + 33 73 export const findTsConfigPath = ( 34 74 tsConfigPath?: UserOutput['tsConfigPath'], 35 75 ): string | null => {
+14 -23
packages/openapi-ts/src/getSpec.ts
··· 1 - import { 2 - $RefParser, 3 - getResolvedInput, 4 - type JSONSchema, 5 - sendRequest, 6 - } from '@hey-api/json-schema-ref-parser'; 1 + import { getResolvedInput, sendRequest } from '@hey-api/json-schema-ref-parser'; 7 2 8 3 import { mergeHeaders } from './plugins/@hey-api/client-fetch/bundle'; 9 - import type { Config } from './types/config'; 4 + import type { Input } from './types/input'; 10 5 import type { WatchValues } from './types/types'; 11 6 12 - interface SpecResponse { 13 - data: JSONSchema; 14 - error?: undefined; 15 - response?: undefined; 16 - } 7 + type SpecResponse = { 8 + arrayBuffer: ArrayBuffer | undefined; 9 + error?: never; 10 + resolvedInput: ReturnType<typeof getResolvedInput>; 11 + response?: never; 12 + }; 17 13 18 - interface SpecError { 19 - data?: undefined; 14 + type SpecError = { 15 + arrayBuffer?: never; 20 16 error: 'not-modified' | 'not-ok'; 17 + resolvedInput?: never; 21 18 response: Response; 22 - } 19 + }; 23 20 24 21 /** 25 22 * @internal ··· 31 28 watch, 32 29 }: { 33 30 fetchOptions?: RequestInit; 34 - inputPath: Config['input']['path']; 31 + inputPath: Input['path']; 35 32 timeout: number | undefined; 36 33 watch: WatchValues; 37 34 }): Promise<SpecResponse | SpecError> => { 38 - const refParser = new $RefParser(); 39 35 const resolvedInput = getResolvedInput({ pathOrUrlOrSchema: inputPath }); 40 36 41 37 let arrayBuffer: ArrayBuffer | undefined; ··· 182 178 }; 183 179 } 184 180 185 - const data = await refParser.bundle({ 181 + return { 186 182 arrayBuffer, 187 - pathOrUrlOrSchema: undefined, 188 183 resolvedInput, 189 - }); 190 - 191 - return { 192 - data, 193 184 }; 194 185 };
+75 -37
packages/openapi-ts/src/index.ts
··· 5 5 import colorSupport from 'color-support'; 6 6 7 7 import { checkNodeVersion } from './config/engine'; 8 + import type { Configs } from './config/init'; 8 9 import { initConfigs } from './config/init'; 9 10 import { getLogs } from './config/logs'; 10 11 import { createClient as pCreateClient } from './createClient'; 11 12 import { 13 + ConfigValidationError, 14 + JobError, 12 15 logCrashReport, 13 16 openGitHubIssueWithCrashReport, 14 17 printCrashReport, ··· 16 19 } from './error'; 17 20 import type { IR } from './ir/types'; 18 21 import type { Client } from './types/client'; 19 - import type { Config, UserConfig } from './types/config'; 22 + import type { UserConfig } from './types/config'; 23 + import type { LazyOrAsync, MaybeArray } from './types/utils'; 24 + import { printCliIntro } from './utils/cli'; 20 25 import { registerHandlebarTemplates } from './utils/handlebars'; 21 26 import { Logger } from './utils/logger'; 22 27 23 - type Configs = UserConfig | (() => UserConfig) | (() => Promise<UserConfig>); 24 - 25 28 colors.enabled = colorSupport().hasBasic; 26 29 27 30 /** 28 31 * Generate a client from the provided configuration. 29 32 * 30 - * @param userConfig User provided {@link UserConfig} configuration. 33 + * @param userConfig User provided {@link UserConfig} configuration(s). 31 34 */ 32 35 export const createClient = async ( 33 - userConfig?: Configs, 36 + userConfig?: LazyOrAsync<MaybeArray<UserConfig>>, 34 37 logger = new Logger(), 35 38 ): Promise<ReadonlyArray<Client | IR.Context>> => { 36 39 const resolvedConfig = 37 40 typeof userConfig === 'function' ? await userConfig() : userConfig; 41 + const userConfigs = resolvedConfig 42 + ? resolvedConfig instanceof Array 43 + ? resolvedConfig 44 + : [resolvedConfig] 45 + : []; 38 46 39 - const configs: Array<Config> = []; 47 + let rawLogs = userConfigs.find( 48 + (config) => getLogs(config).level !== 'silent', 49 + )?.logs; 50 + if (typeof rawLogs === 'string') { 51 + rawLogs = getLogs({ logs: rawLogs }); 52 + } 53 + 54 + let configs: Configs | undefined; 40 55 41 56 try { 42 57 checkNodeVersion(); ··· 44 59 const eventCreateClient = logger.timeEvent('createClient'); 45 60 46 61 const eventConfig = logger.timeEvent('config'); 47 - const configResults = await initConfigs(resolvedConfig); 48 - for (const result of configResults.results) { 49 - configs.push(result.config); 50 - if (result.errors.length) { 51 - throw result.errors[0]; 52 - } 62 + configs = await initConfigs({ logger, userConfigs }); 63 + const printIntro = configs.results.some( 64 + (result) => result.config.logs.level !== 'silent', 65 + ); 66 + if (printIntro) { 67 + printCliIntro(); 53 68 } 54 69 eventConfig.timeEnd(); 55 70 71 + const allConfigErrors = configs.results.flatMap((result) => 72 + result.errors.map((error) => ({ error, jobIndex: result.jobIndex })), 73 + ); 74 + if (allConfigErrors.length) { 75 + throw new ConfigValidationError(allConfigErrors); 76 + } 77 + 56 78 const eventHandlebars = logger.timeEvent('handlebars'); 57 79 const templates = registerHandlebarTemplates(); 58 80 eventHandlebars.timeEnd(); 59 81 60 82 const clients = await Promise.all( 61 - configs.map((config) => 62 - pCreateClient({ 63 - config, 64 - dependencies: configResults.dependencies, 65 - logger, 66 - templates, 67 - }), 68 - ), 83 + configs.results.map(async (result) => { 84 + try { 85 + return await pCreateClient({ 86 + config: result.config, 87 + dependencies: configs!.dependencies, 88 + jobIndex: result.jobIndex, 89 + logger, 90 + templates, 91 + }); 92 + } catch (error) { 93 + throw new JobError('', { 94 + error, 95 + jobIndex: result.jobIndex, 96 + }); 97 + } 98 + }), 69 99 ); 70 100 const result = clients.filter((client) => Boolean(client)) as ReadonlyArray< 71 101 Client | IR.Context ··· 73 103 74 104 eventCreateClient.timeEnd(); 75 105 76 - const config = configs[0]; 77 - logger.report(config && config.logs.level === 'debug'); 106 + const printLogs = configs.results.some( 107 + (result) => result.config.logs.level === 'debug', 108 + ); 109 + logger.report(printLogs); 78 110 79 111 return result; 80 112 } catch (error) { 81 - const config = configs[0] as Config | undefined; 82 - const dryRun = config ? config.dryRun : resolvedConfig?.dryRun; 83 - const isInteractive = config 84 - ? config.interactive 85 - : resolvedConfig?.interactive; 86 - const logs = config?.logs ?? getLogs(resolvedConfig); 87 - 88 - let logPath: string | undefined; 113 + const results = configs?.results ?? []; 89 114 90 - if (logs.level !== 'silent' && logs.file && !dryRun) { 91 - logPath = logCrashReport(error, logs.path ?? ''); 92 - } 115 + const logs = 116 + results.find((result) => result.config.logs.level !== 'silent')?.config 117 + .logs ?? rawLogs; 118 + if (!logs || logs.level !== 'silent') { 119 + const dryRun = 120 + results.some((result) => result.config.dryRun) ?? 121 + userConfigs.some((config) => config.dryRun) ?? 122 + false; 123 + const logPath = 124 + logs?.file && !dryRun 125 + ? logCrashReport(error, logs.path ?? '') 126 + : undefined; 93 127 94 - if (logs.level !== 'silent') { 95 128 printCrashReport({ error, logPath }); 129 + const isInteractive = 130 + results.some((result) => result.config.interactive) ?? 131 + userConfigs.some((config) => config.interactive) ?? 132 + false; 96 133 if (await shouldReportCrash({ error, isInteractive })) { 97 134 await openGitHubIssueWithCrashReport(error); 98 135 } ··· 103 140 }; 104 141 105 142 /** 106 - * Type helper for openapi-ts.config.ts, returns {@link UserConfig} object 143 + * Type helper for openapi-ts.config.ts, returns {@link MaybeArray<UserConfig>} object(s) 107 144 */ 108 - export const defineConfig = async (config: Configs): Promise<UserConfig> => 109 - typeof config === 'function' ? await config() : config; 145 + export const defineConfig = async <T extends MaybeArray<UserConfig>>( 146 + config: LazyOrAsync<T>, 147 + ): Promise<T> => (typeof config === 'function' ? await config() : config); 110 148 111 149 export { defaultPaginationKeywords } from './config/parser'; 112 150 export { defaultPlugins } from './config/plugins';
+30 -20
packages/openapi-ts/src/openApi/2.0.x/parser/__tests__/server.test.ts
··· 8 8 it('host + basePath + schemes', () => { 9 9 const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = { 10 10 config: { 11 - // @ts-expect-error 12 - input: { 13 - path: '', 14 - }, 11 + input: [ 12 + // @ts-expect-error 13 + { 14 + path: '', 15 + }, 16 + ], 15 17 }, 16 18 ir: {}, 17 19 spec: { ··· 34 36 it('schemes + host', () => { 35 37 const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = { 36 38 config: { 37 - // @ts-expect-error 38 - input: { 39 - path: '', 40 - }, 39 + input: [ 40 + // @ts-expect-error 41 + { 42 + path: '', 43 + }, 44 + ], 41 45 }, 42 46 ir: {}, 43 47 spec: { ··· 56 60 it('host + basePath', () => { 57 61 const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = { 58 62 config: { 59 - // @ts-expect-error 60 - input: { 61 - path: '', 62 - }, 63 + input: [ 64 + // @ts-expect-error 65 + { 66 + path: '', 67 + }, 68 + ], 63 69 }, 64 70 ir: {}, 65 71 spec: { ··· 78 84 it('host', () => { 79 85 const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = { 80 86 config: { 81 - // @ts-expect-error 82 - input: { 83 - path: '', 84 - }, 87 + input: [ 88 + // @ts-expect-error 89 + { 90 + path: '', 91 + }, 92 + ], 85 93 }, 86 94 ir: {}, 87 95 spec: { ··· 99 107 it('basePath', () => { 100 108 const context: Partial<IR.Context<Partial<OpenApi.V2_0_X>>> = { 101 109 config: { 102 - // @ts-expect-error 103 - input: { 104 - path: '', 105 - }, 110 + input: [ 111 + // @ts-expect-error 112 + { 113 + path: '', 114 + }, 115 + ], 106 116 }, 107 117 ir: {}, 108 118 spec: {
+1 -1
packages/openapi-ts/src/openApi/2.0.x/parser/schema.ts
··· 232 232 const isEmptyObjectInAllOf = 233 233 state.inAllOf && 234 234 schema.additionalProperties === false && 235 - (!schema.properties || Object.keys(schema.properties).length === 0); 235 + (!schema.properties || !Object.keys(schema.properties).length); 236 236 237 237 if (!isEmptyObjectInAllOf) { 238 238 irSchema.additionalProperties = {
+10 -8
packages/openapi-ts/src/openApi/2.0.x/parser/server.ts
··· 6 6 let host = context.spec.host ?? ''; 7 7 const path = context.spec.basePath ?? ''; 8 8 9 - if (typeof context.config.input.path === 'string') { 10 - const url = parseUrl(context.config.input.path); 9 + for (const input of context.config.input) { 10 + if (typeof input.path === 'string') { 11 + const url = parseUrl(input.path); 11 12 12 - if (!schemes.length) { 13 - if (url.protocol) { 14 - schemes = [url.protocol] as typeof schemes; 13 + if (!schemes.length) { 14 + if (url.protocol) { 15 + schemes = [url.protocol] as typeof schemes; 16 + } 15 17 } 16 - } 17 18 18 - if (!host) { 19 - host = `${url.host}${url.port ? `:${url.port}` : ''}`; 19 + if (!host) { 20 + host = `${url.host}${url.port ? `:${url.port}` : ''}`; 21 + } 20 22 } 21 23 } 22 24
+1 -1
packages/openapi-ts/src/openApi/3.0.x/parser/schema.ts
··· 238 238 const isEmptyObjectInAllOf = 239 239 state.inAllOf && 240 240 schema.additionalProperties === false && 241 - (!schema.properties || Object.keys(schema.properties).length === 0); 241 + (!schema.properties || !Object.keys(schema.properties).length); 242 242 243 243 if (!isEmptyObjectInAllOf) { 244 244 irSchema.additionalProperties = {
+9 -7
packages/openapi-ts/src/openApi/3.0.x/parser/server.ts
··· 7 7 return; 8 8 } 9 9 10 - if (typeof context.config.input.path === 'string') { 11 - const url = parseUrl(context.config.input.path); 12 - context.ir.servers = [ 13 - { 14 - url: `${url.protocol ? `${url.protocol}://` : ''}${url.host}${url.port ? `:${url.port}` : ''}`, 15 - }, 16 - ]; 10 + for (const input of context.config.input) { 11 + if (typeof input.path === 'string') { 12 + const url = parseUrl(input.path); 13 + context.ir.servers = [ 14 + { 15 + url: `${url.protocol ? `${url.protocol}://` : ''}${url.host}${url.port ? `:${url.port}` : ''}`, 16 + }, 17 + ]; 18 + } 17 19 } 18 20 19 21 if (!context.ir.servers) {
+2 -2
packages/openapi-ts/src/openApi/3.1.x/parser/schema.ts
··· 292 292 const isEmptyObjectInAllOf = 293 293 state.inAllOf && 294 294 schema.additionalProperties === false && 295 - (!schema.properties || Object.keys(schema.properties).length === 0) && 295 + (!schema.properties || !Object.keys(schema.properties).length) && 296 296 (!schema.patternProperties || 297 - Object.keys(schema.patternProperties).length === 0); 297 + !Object.keys(schema.patternProperties).length); 298 298 299 299 if (!isEmptyObjectInAllOf) { 300 300 irSchema.additionalProperties = {
+9 -7
packages/openapi-ts/src/openApi/3.1.x/parser/server.ts
··· 7 7 return; 8 8 } 9 9 10 - if (typeof context.config.input.path === 'string') { 11 - const url = parseUrl(context.config.input.path); 12 - context.ir.servers = [ 13 - { 14 - url: `${url.protocol ? `${url.protocol}://` : ''}${url.host}${url.port ? `:${url.port}` : ''}`, 15 - }, 16 - ]; 10 + for (const input of context.config.input) { 11 + if (typeof input.path === 'string') { 12 + const url = parseUrl(input.path); 13 + context.ir.servers = [ 14 + { 15 + url: `${url.protocol ? `${url.protocol}://` : ''}${url.host}${url.port ? `:${url.port}` : ''}`, 16 + }, 17 + ]; 18 + } 17 19 } 18 20 19 21 if (!context.ir.servers) {
+2 -1
packages/openapi-ts/src/openApi/3.1.x/types/json-schema-draft-2020-12.d.ts
··· 1 + import type { MaybeArray } from '../../../types/utils'; 1 2 import type { EnumExtensions } from '../../shared/types/openapi-spec-extensions'; 2 3 import type { OpenApiSchemaExtensions } from './spec-extensions'; 3 4 ··· 144 145 /** 145 146 * If it is an array, it must be an array of strings, where each string is the name of one of the basic types, and each element is unique. In this case, the JSON snippet is valid if it matches any of the given types. 146 147 */ 147 - type?: JsonSchemaTypes | ReadonlyArray<JsonSchemaTypes>; 148 + type?: MaybeArray<JsonSchemaTypes>; 148 149 /** 149 150 * The boolean keywords `readOnly` and `writeOnly` are typically used in an API context. `readOnly` indicates that a value should not be modified. It could be used to indicate that a `PUT` request that changes a value would result in a `400 Bad Request` response. `writeOnly` indicates that a value may be set, but will remain hidden. In could be used to indicate you can set a value with a `PUT` request, but it would not be included when retrieving that record with a `GET` request. 150 151 */
+1 -1
packages/openapi-ts/src/openApi/shared/transforms/readWrite.ts
··· 233 233 (prop) => !removedProperties.has(prop), 234 234 ); 235 235 236 - if (filteredRequired.length === 0) { 236 + if (!filteredRequired.length) { 237 237 delete (schema as Record<string, unknown>).required; 238 238 } else { 239 239 (schema as Record<string, unknown>).required = filteredRequired;
+18 -14
packages/openapi-ts/src/plugins/@hey-api/schemas/__tests__/schemas.test.ts
··· 20 20 dryRun: false, 21 21 experimentalParser: false, 22 22 exportCore: true, 23 - input: { 24 - path: '', 25 - watch: { 26 - enabled: false, 27 - interval: 1_000, 28 - timeout: 60_000, 23 + input: [ 24 + { 25 + path: '', 26 + watch: { 27 + enabled: false, 28 + interval: 1_000, 29 + timeout: 60_000, 30 + }, 29 31 }, 30 - }, 32 + ], 31 33 interactive: false, 32 34 logs: { 33 35 file: true, ··· 180 182 dryRun: false, 181 183 experimentalParser: false, 182 184 exportCore: true, 183 - input: { 184 - path: '', 185 - watch: { 186 - enabled: false, 187 - interval: 1_000, 188 - timeout: 60_000, 185 + input: [ 186 + { 187 + path: '', 188 + watch: { 189 + enabled: false, 190 + interval: 1_000, 191 + timeout: 60_000, 192 + }, 189 193 }, 190 - }, 194 + ], 191 195 interactive: false, 192 196 logs: { 193 197 file: true,
+36 -28
packages/openapi-ts/src/plugins/@hey-api/sdk/__tests__/plugin.test.ts
··· 23 23 dryRun: false, 24 24 experimentalParser: false, 25 25 exportCore: true, 26 - input: { 27 - path: '', 28 - watch: { 29 - enabled: false, 30 - interval: 1_000, 31 - timeout: 60_000, 26 + input: [ 27 + { 28 + path: '', 29 + watch: { 30 + enabled: false, 31 + interval: 1_000, 32 + timeout: 60_000, 33 + }, 32 34 }, 33 - }, 35 + ], 34 36 interactive: false, 35 37 logs: { 36 38 file: true, ··· 256 258 dryRun: false, 257 259 experimentalParser: false, 258 260 exportCore: true, 259 - input: { 260 - path: '', 261 - watch: { 262 - enabled: false, 263 - interval: 1_000, 264 - timeout: 60_000, 261 + input: [ 262 + { 263 + path: '', 264 + watch: { 265 + enabled: false, 266 + interval: 1_000, 267 + timeout: 60_000, 268 + }, 265 269 }, 266 - }, 270 + ], 267 271 interactive: false, 268 272 logs: { 269 273 file: true, ··· 411 415 dryRun: false, 412 416 experimentalParser: false, 413 417 exportCore: true, 414 - input: { 415 - path: '', 416 - watch: { 417 - enabled: false, 418 - interval: 1_000, 419 - timeout: 60_000, 418 + input: [ 419 + { 420 + path: '', 421 + watch: { 422 + enabled: false, 423 + interval: 1_000, 424 + timeout: 60_000, 425 + }, 420 426 }, 421 - }, 427 + ], 422 428 interactive: false, 423 429 logs: { 424 430 file: true, ··· 569 575 dryRun: false, 570 576 experimentalParser: false, 571 577 exportCore: true, 572 - input: { 573 - path: '', 574 - watch: { 575 - enabled: false, 576 - interval: 1_000, 577 - timeout: 60_000, 578 + input: [ 579 + { 580 + path: '', 581 + watch: { 582 + enabled: false, 583 + interval: 1_000, 584 + timeout: 60_000, 585 + }, 578 586 }, 579 - }, 587 + ], 580 588 interactive: false, 581 589 logs: { 582 590 file: true,
+9 -7
packages/openapi-ts/src/plugins/@hey-api/typescript/__tests__/plugin.test.ts
··· 20 20 dryRun: false, 21 21 experimentalParser: false, 22 22 exportCore: true, 23 - input: { 24 - path: '', 25 - watch: { 26 - enabled: false, 27 - interval: 1_000, 28 - timeout: 60_000, 23 + input: [ 24 + { 25 + path: '', 26 + watch: { 27 + enabled: false, 28 + interval: 1_000, 29 + timeout: 60_000, 30 + }, 29 31 }, 30 - }, 32 + ], 31 33 interactive: false, 32 34 logs: { 33 35 file: true,
+18 -13
packages/openapi-ts/src/types/config.d.ts
··· 1 1 import type { PluginConfigMap } from '../plugins/config'; 2 2 import type { Plugin, PluginNames } from '../plugins/types'; 3 - import type { Input, Watch } from './input'; 3 + import type { Input, UserInput, Watch } from './input'; 4 4 import type { Logs } from './logs'; 5 5 import type { Output, UserOutput } from './output'; 6 6 import type { Parser, UserParser } from './parser'; 7 + import type { MaybeArray } from './utils'; 7 8 8 9 export interface UserConfig { 9 10 /** ··· 27 28 * object directly if you're fetching the file yourself. 28 29 * 29 30 * Alternatively, you can define a configuration object with more options. 31 + * 32 + * If you define an array, we will generate a single output from multiple 33 + * inputs. If you define an array of outputs with the same length, we will 34 + * generate multiple outputs, one for each input. 30 35 */ 31 - input: 32 - | `https://get.heyapi.dev/${string}/${string}` 33 - | `${string}/${string}` 34 - | `readme:@${string}/${string}#${string}` 35 - | `readme:${string}` 36 - | `scalar:@${string}/${string}` 37 - | (string & {}) 38 - | (Record<string, unknown> & { path?: never }) 39 - | Input; 36 + input: MaybeArray<UserInput | Required<UserInput>['path']>; 40 37 /** 41 38 * Show an interactive error reporting tool when the program crashes? You 42 39 * generally want to keep this disabled (default). ··· 52 49 logs?: string | Logs; 53 50 /** 54 51 * Path to the output folder. 52 + * 53 + * If you define an array of outputs with the same length as inputs, we will 54 + * generate multiple outputs, one for each input. 55 55 */ 56 - output: string | UserOutput; 56 + output: MaybeArray<string | UserOutput>; 57 57 /** 58 58 * Customize how the input is parsed and transformed before it's passed to 59 59 * plugins. ··· 142 142 | 'watch' 143 143 > & 144 144 Pick<UserConfig, 'base' | 'name' | 'request'> & { 145 - input: Omit<Input, 'path' | 'watch'> & 146 - Pick<Required<Input>, 'path'> & { watch: Watch }; 145 + /** 146 + * Path to the input specification. 147 + */ 148 + input: ReadonlyArray<Input>; 147 149 logs: Logs; 150 + /** 151 + * Path to the output folder. 152 + */ 148 153 output: Output; 149 154 /** 150 155 * Customize how the input is parsed and transformed before it's passed to
+94 -9
packages/openapi-ts/src/types/input.d.ts
··· 1 - export type Input = { 1 + type JsonSchema = Record<string, unknown>; 2 + 3 + type ApiRegistryShorthands = 4 + | `https://get.heyapi.dev/${string}/${string}` 5 + | `${string}/${string}` 6 + | `readme:@${string}/${string}#${string}` 7 + | `readme:${string}` 8 + | `scalar:@${string}/${string}`; 9 + 10 + export type UserInput = { 2 11 /** 3 12 * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 4 13 * ··· 44 53 * Both JSON and YAML file formats are supported. You can also pass the parsed 45 54 * object directly if you're fetching the file yourself. 46 55 */ 47 - path?: 48 - | `https://get.heyapi.dev/${string}/${string}` 49 - | `${string}/${string}` 50 - | `readme:@${string}/${string}#${string}` 51 - | `readme:${string}` 52 - | `scalar:@${string}/${string}` 53 - | (string & {}) 54 - | Record<string, unknown>; 56 + path?: ApiRegistryShorthands | (string & {}) | JsonSchema; 55 57 /** 56 58 * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 57 59 * ··· 81 83 * @default false 82 84 */ 83 85 watch?: boolean | number | Watch; 86 + }; 87 + 88 + export type Input = { 89 + /** 90 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 91 + * 92 + * Projects are private by default, you will need to be authenticated 93 + * to download OpenAPI specifications. We recommend using project API 94 + * keys in CI workflows and personal API keys for local development. 95 + * 96 + * API key isn't required for public projects. You can also omit this 97 + * parameter and provide an environment variable `HEY_API_TOKEN`. 98 + */ 99 + api_key?: string; 100 + /** 101 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 102 + * 103 + * You can fetch the last build from branch by providing the branch 104 + * name. 105 + */ 106 + branch?: string; 107 + /** 108 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 109 + * 110 + * You can fetch an exact specification by providing a commit sha. 111 + * This will always return the same file. 112 + */ 113 + commit_sha?: string; 114 + /** 115 + * You can pass any valid Fetch API options to the request for fetching your 116 + * specification. This is useful if your file is behind auth for example. 117 + */ 118 + fetch?: RequestInit; 119 + /** 120 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 121 + * 122 + * Organization created in Hey API Platform. 123 + */ 124 + organization?: string; 125 + /** 126 + * Path to the OpenAPI specification. This can be: 127 + * - path 128 + * - URL 129 + * - API registry shorthand 130 + * 131 + * Both JSON and YAML file formats are supported. You can also pass the parsed 132 + * object directly if you're fetching the file yourself. 133 + */ 134 + path: ApiRegistryShorthands | (string & {}) | JsonSchema; 135 + /** 136 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 137 + * 138 + * Project created in Hey API Platform. 139 + */ 140 + project?: string; 141 + /** 142 + * If input path was resolved to a registry, this contains the registry 143 + * identifier so we don't need to parse it again. 144 + * 145 + * @default undefined 146 + */ 147 + registry?: 'hey-api' | 'readme' | 'scalar'; 148 + /** 149 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 150 + * 151 + * If you're tagging your specifications with custom tags, you can use 152 + * them to filter the results. When you provide multiple tags, only 153 + * the first match will be returned. 154 + */ 155 + tags?: ReadonlyArray<string>; 156 + /** 157 + * **Requires `path` to start with `https://get.heyapi.dev` or be undefined** 158 + * 159 + * Every OpenAPI document contains a required version field. You can 160 + * use this value to fetch the last uploaded specification matching 161 + * the value. 162 + */ 163 + version?: string; 164 + /** 165 + * Regenerate the client when the input file changes? You can alternatively 166 + * pass a numeric value for the interval in ms. 167 + */ 168 + watch: Watch; 84 169 }; 85 170 86 171 export type Watch = {
+28 -1
packages/openapi-ts/src/types/utils.d.ts
··· 1 1 import type { GeneratedFile } from '../generate/file'; 2 2 3 - /** Recursively make all non-function properties optional */ 3 + /** 4 + * Converts all top-level ReadonlyArray properties to Array (shallow). 5 + */ 6 + export type ArrayOnly<T> = { 7 + [K in keyof T]: T[K] extends ReadonlyArray<infer U> ? Array<U> : T[K]; 8 + }; 9 + 10 + /** 11 + * Recursively makes all non-function properties optional. 12 + */ 4 13 export type DeepPartial<T> = { 5 14 [K in keyof T]?: T[K] extends (...args: any[]) => any 6 15 ? T[K] ··· 9 18 : T[K]; 10 19 }; 11 20 21 + /** @deprecated */ 12 22 export type Files = Record<string, GeneratedFile>; 23 + 24 + /** 25 + * Accepts a value, a function returning a value, or a function returning a promise of a value. 26 + */ 27 + export type LazyOrAsync<T> = T | (() => T) | (() => Promise<T>); 28 + 29 + /** 30 + * Accepts a value or a readonly array of values of type T. 31 + */ 32 + export type MaybeArray<T> = T | ReadonlyArray<T>; 33 + 34 + /** 35 + * Converts all top-level Array properties to ReadonlyArray (shallow). 36 + */ 37 + export type ReadonlyArrayOnly<T> = { 38 + [K in keyof T]: T[K] extends Array<infer U> ? ReadonlyArray<U> : T[K]; 39 + };
+18 -14
packages/openapi-ts/src/utils/__tests__/handlebars.test.ts
··· 15 15 dryRun: false, 16 16 experimentalParser: false, 17 17 exportCore: true, 18 - input: { 19 - path: '', 20 - watch: { 21 - enabled: false, 22 - interval: 1_000, 23 - timeout: 60_000, 18 + input: [ 19 + { 20 + path: '', 21 + watch: { 22 + enabled: false, 23 + interval: 1_000, 24 + timeout: 60_000, 25 + }, 24 26 }, 25 - }, 27 + ], 26 28 interactive: false, 27 29 logs: { 28 30 file: true, ··· 143 145 dryRun: false, 144 146 experimentalParser: false, 145 147 exportCore: true, 146 - input: { 147 - path: '', 148 - watch: { 149 - enabled: false, 150 - interval: 1_000, 151 - timeout: 60_000, 148 + input: [ 149 + { 150 + path: '', 151 + watch: { 152 + enabled: false, 153 + interval: 1_000, 154 + timeout: 60_000, 155 + }, 152 156 }, 153 - }, 157 + ], 154 158 interactive: false, 155 159 logs: { 156 160 file: true,
+9 -7
packages/openapi-ts/src/utils/__tests__/parse.test.ts
··· 9 9 dryRun: true, 10 10 experimentalParser: false, 11 11 exportCore: false, 12 - input: { 13 - path: '', 14 - watch: { 15 - enabled: false, 16 - interval: 1_000, 17 - timeout: 60_000, 12 + input: [ 13 + { 14 + path: '', 15 + watch: { 16 + enabled: false, 17 + interval: 1_000, 18 + timeout: 60_000, 19 + }, 18 20 }, 19 - }, 21 + ], 20 22 interactive: false, 21 23 logs: { 22 24 file: true,
+63
packages/openapi-ts/src/utils/cli.ts
··· 1 + import colors from 'ansi-colors'; 2 + 3 + import { loadPackageJson } from '../generate/tsConfig'; 4 + 5 + const logoAscii = ` 6 + db e888888e 7 + d8 Y88Y ~8b 8 + dY "" "" Yb 9 + 8 88 88 8 10 + 8 o 8 8 11 + Yb b8d eb ,b 12 + Yb_____Y ,b 13 + 8 dY 14 + 8 e 8 15 + `; 16 + 17 + const textAscii = ` 18 + 888 | e 888~-_ 888 19 + 888___| e88~~8e Y88b / d8b 888 \\ 888 20 + 888 | d888 88b Y888/ /Y88b 888 | 888 21 + 888 | 8888__888 Y8/ / Y88b 888 / 888 22 + 888 | Y888 , Y /____Y88b 888_-~ 888 23 + 888 | "88___/ / / Y88b 888 888 24 + _/ 25 + `; 26 + 27 + const asciiToLines = (ascii: string) => { 28 + const lines: Array<string> = []; 29 + let maxLineLength = 0; 30 + let line = ''; 31 + for (const char of ascii) { 32 + if (char === '\n') { 33 + if (line) { 34 + lines.push(line); 35 + maxLineLength = Math.max(maxLineLength, line.length); 36 + line = ''; 37 + } 38 + } else { 39 + line += char; 40 + } 41 + } 42 + return { lines, maxLineLength }; 43 + }; 44 + 45 + export function printCliIntro() { 46 + const packageJson = loadPackageJson(); 47 + const logo = asciiToLines(logoAscii); 48 + const text = asciiToLines(textAscii); 49 + const padding = Math.floor((logo.lines.length - text.lines.length) / 2); 50 + const lines = logo.lines.map((logoLine, index) => { 51 + let line = logoLine.padEnd(logo.maxLineLength); 52 + if (index >= padding && logo.lines.length - index - 1 >= padding) { 53 + line += ` ${text.lines[index - padding]}`; 54 + } 55 + return line; 56 + }); 57 + for (const line of lines) { 58 + console.log(colors.cyan(line)); 59 + } 60 + console.log(''); 61 + console.log(colors.gray(`${packageJson.name} v${packageJson.version}`)); 62 + console.log(''); 63 + }
+19 -7
packages/openapi-ts/src/utils/input/__tests__/readme.test.ts
··· 91 91 describe('inputToReadmePath', () => { 92 92 it('should transform simple UUID format to API URL', () => { 93 93 const result = inputToReadmePath('readme:abc123'); 94 - expect(result).toBe('https://dash.readme.com/api/v1/api-registry/abc123'); 94 + expect(result).toEqual({ 95 + path: 'https://dash.readme.com/api/v1/api-registry/abc123', 96 + registry: 'readme', 97 + uuid: 'abc123', 98 + }); 95 99 }); 96 100 97 101 it('should transform full format to API URL', () => { 98 102 const result = inputToReadmePath('readme:@myorg/myproject#uuid123'); 99 - expect(result).toBe( 100 - 'https://dash.readme.com/api/v1/api-registry/uuid123', 101 - ); 103 + expect(result).toEqual({ 104 + organization: 'myorg', 105 + path: 'https://dash.readme.com/api/v1/api-registry/uuid123', 106 + project: 'myproject', 107 + registry: 'readme', 108 + uuid: 'uuid123', 109 + }); 102 110 }); 103 111 104 112 it('should throw error for invalid inputs', () => { ··· 137 145 'should handle $input correctly', 138 146 ({ expected, input }) => { 139 147 expect(parseShorthand(input)).toEqual(expected); 140 - expect(inputToReadmePath(`readme:${input}`)).toBe( 141 - `https://dash.readme.com/api/v1/api-registry/${expected.uuid}`, 142 - ); 148 + expect(inputToReadmePath(`readme:${input}`)).toEqual({ 149 + organization: expected.organization, 150 + path: `https://dash.readme.com/api/v1/api-registry/${expected.uuid}`, 151 + project: expected.project, 152 + registry: 'readme', 153 + uuid: expected.uuid, 154 + }); 143 155 }, 144 156 ); 145 157
+12 -6
packages/openapi-ts/src/utils/input/__tests__/scalar.test.ts
··· 76 76 describe('inputToScalarPath', () => { 77 77 it('should transform full format to API URL', () => { 78 78 const result = inputToScalarPath('scalar:@foo/bar'); 79 - expect(result).toBe( 80 - 'https://registry.scalar.com/@foo/apis/bar/latest?format=json', 81 - ); 79 + expect(result).toEqual({ 80 + organization: '@foo', 81 + path: 'https://registry.scalar.com/@foo/apis/bar/latest?format=json', 82 + project: 'bar', 83 + registry: 'scalar', 84 + }); 82 85 }); 83 86 84 87 it('should throw error for invalid inputs', () => { ··· 110 113 'should handle $input correctly', 111 114 ({ expected, input }) => { 112 115 expect(parseShorthand(input)).toEqual(expected); 113 - expect(inputToScalarPath(`scalar:${input}`)).toBe( 114 - `https://registry.scalar.com/${expected.organization}/apis/${expected.project}/latest?format=json`, 115 - ); 116 + expect(inputToScalarPath(`scalar:${input}`)).toEqual({ 117 + organization: expected.organization, 118 + path: `https://registry.scalar.com/${expected.organization}/apis/${expected.project}/latest?format=json`, 119 + project: expected.project, 120 + registry: 'scalar', 121 + }); 116 122 }, 117 123 ); 118 124
+12 -10
packages/openapi-ts/src/utils/input/heyApi.ts
··· 1 - // Regular expression to match Hey API Registry input formats: 2 - 3 1 import type { Input } from '../../types/input'; 4 2 3 + // Regular expression to match Hey API Registry input formats: 5 4 // - {organization}/{project}?{queryParams} 6 5 const registryRegExp = /^([\w-]+)\/([\w-]+)(?:\?([\w=&.-]*))?$/; 7 6 ··· 36 35 * @throws Error if the input format is invalid 37 36 */ 38 37 export const parseShorthand = ( 39 - input: Omit<Input, 'path'> & { 38 + input: Input & { 40 39 path: string; 41 40 }, 42 41 ): Parsed => { ··· 82 81 * @returns The Hey API Registry URL 83 82 */ 84 83 export const inputToHeyApiPath = ( 85 - input: Omit<Input, 'path'> & { 84 + input: Input & { 86 85 path: string; 87 86 }, 88 - ): string => { 87 + ): Partial<Input> => { 89 88 const parsed = parseShorthand(input); 90 - return getRegistryUrl( 91 - parsed.organization, 92 - parsed.project, 93 - parsed.queryParams, 94 - ); 89 + return { 90 + path: getRegistryUrl( 91 + parsed.organization, 92 + parsed.project, 93 + parsed.queryParams, 94 + ), 95 + registry: 'hey-api', 96 + }; 95 97 };
+6 -6
packages/openapi-ts/src/utils/input/index.ts
··· 9 9 }, 10 10 ) => { 11 11 if (input.path.startsWith('readme:')) { 12 - input.path = inputToReadmePath(input.path); 12 + Object.assign(input, inputToReadmePath(input.path)); 13 13 return; 14 14 } 15 15 16 16 if (input.path.startsWith('scalar:')) { 17 - input.path = inputToScalarPath(input.path); 17 + Object.assign(input, inputToScalarPath(input.path)); 18 18 return; 19 19 } 20 20 ··· 24 24 25 25 if (input.path.startsWith(heyApiRegistryBaseUrl)) { 26 26 input.path = input.path.slice(heyApiRegistryBaseUrl.length + 1); 27 - input.path = inputToHeyApiPath(input as Input & { path: string }); 27 + Object.assign(input, inputToHeyApiPath(input as Input & { path: string })); 28 28 return; 29 29 } 30 30 31 31 const parts = input.path.split('/'); 32 - const cleanParts = parts.filter(Boolean); 33 - if (parts.length === 2 && cleanParts.length === 2) { 34 - input.path = inputToHeyApiPath(input as Input & { path: string }); 32 + if (parts.length === 2 && parts.filter(Boolean).length === 2) { 33 + Object.assign(input, inputToHeyApiPath(input as Input & { path: string })); 34 + return; 35 35 } 36 36 };
+8 -2
packages/openapi-ts/src/utils/input/readme.ts
··· 1 + import type { Input } from '../../types/input'; 2 + 1 3 // Regular expression to match ReadMe API Registry input formats: 2 4 // - @{organization}/{project}#{uuid} 3 5 // - {uuid} ··· 57 59 * @param input - ReadMe format string 58 60 * @returns The ReadMe API Registry URL 59 61 */ 60 - export const inputToReadmePath = (input: string): string => { 62 + export const inputToReadmePath = (input: string): Partial<Input> => { 61 63 const shorthand = input.slice(`${namespace}:`.length); 62 64 const parsed = parseShorthand(shorthand); 63 - return getRegistryUrl(parsed.uuid); 65 + return { 66 + ...parsed, 67 + path: getRegistryUrl(parsed.uuid), 68 + registry: 'readme', 69 + }; 64 70 };
+8 -2
packages/openapi-ts/src/utils/input/scalar.ts
··· 1 + import type { Input } from '../../types/input'; 2 + 1 3 // Regular expression to match Scalar API Registry input formats: 2 4 // - @{organization}/{project} 3 5 const registryRegExp = /^(@[\w-]+)\/([\w.-]+)$/; ··· 59 61 * @param input - Scalar format string 60 62 * @returns The Scalar API Registry URL 61 63 */ 62 - export const inputToScalarPath = (input: string): string => { 64 + export const inputToScalarPath = (input: string): Partial<Input> => { 63 65 const shorthand = input.slice(`${namespace}:`.length); 64 66 const parsed = parseShorthand(shorthand); 65 - return getRegistryUrl(parsed.organization, parsed.project); 67 + return { 68 + ...parsed, 69 + path: getRegistryUrl(parsed.organization, parsed.project), 70 + registry: 'scalar', 71 + }; 66 72 };
+4 -4
packages/openapi-ts/src/utils/logger.ts
··· 135 135 if (severity?.type === 'percentage') { 136 136 percentageLabel = severity.color(percentageLabel); 137 137 } 138 + const jobPrefix = colors.gray('[root] '); 138 139 console.log( 139 - colors.gray(prefix) + 140 - color( 141 - `${event.name.padEnd(maxLength)} ${durationLabel} (${percentageLabel})`, 142 - ), 140 + `${jobPrefix}${colors.gray(prefix)}${color( 141 + `${event.name.padEnd(maxLength)} ${durationLabel} (${percentageLabel})`, 142 + )}`, 143 143 ); 144 144 this.reportEvent({ ...event, indent: indent + 1, measure }); 145 145 });
+181 -150
pnpm-lock.yaml
··· 92 92 specifier: 8.4.0 93 93 version: 8.4.0(jiti@2.5.1)(postcss@8.5.6)(typescript@5.8.3)(yaml@2.8.0) 94 94 turbo: 95 - specifier: 2.5.6 96 - version: 2.5.6 95 + specifier: 2.5.8 96 + version: 2.5.8 97 97 typescript: 98 98 specifier: 5.8.3 99 99 version: 5.8.3 ··· 1265 1265 specifier: workspace:^0.2.0 1266 1266 version: link:../codegen-core 1267 1267 '@hey-api/json-schema-ref-parser': 1268 - specifier: 1.1.0 1269 - version: 1.1.0 1268 + specifier: 1.2.0 1269 + version: 1.2.0 1270 1270 ansi-colors: 1271 1271 specifier: 4.1.3 1272 1272 version: 4.1.3 1273 1273 c12: 1274 - specifier: 2.0.1 1275 - version: 2.0.1(magicast@0.3.5) 1274 + specifier: 3.3.0 1275 + version: 3.3.0(magicast@0.3.5) 1276 1276 color-support: 1277 1277 specifier: 1.1.3 1278 1278 version: 1.1.3 ··· 1345 1345 version: 3.3.2 1346 1346 nuxt: 1347 1347 specifier: 3.14.1592 1348 - version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 1348 + version: 3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 1349 1349 ofetch: 1350 1350 specifier: 1.4.1 1351 1351 version: 1.4.1 ··· 3953 3953 '@fontsource/fira-mono@5.0.0': 3954 3954 resolution: {integrity: sha512-IsinH/oLYJyv/sQv7SbKmjoAXZsSjm6Q1Tz5GBBXCXi3Jg9MzXmKvWm9bSLC8lFI6CDsi8GkH/DAgZ98t8bhTQ==} 3955 3955 3956 - '@hey-api/json-schema-ref-parser@1.1.0': 3957 - resolution: {integrity: sha512-+5eg9pgAAM9oSqJQuUtfTKbLz8yieFKN91myyXiLnprqFj8ROfxUKJLr9DKq/hGKyybKT1WxFSetDqCFm80pCA==} 3956 + '@hey-api/json-schema-ref-parser@1.2.0': 3957 + resolution: {integrity: sha512-BMnIuhVgNmSudadw1GcTsP18Yk5l8FrYrg/OSYNxz0D2E0vf4D5e4j5nUbuY8MU6p1vp7ev0xrfP6A/NWazkzQ==} 3958 3958 engines: {node: '>= 16'} 3959 3959 3960 3960 '@humanfs/core@0.19.1': ··· 7452 7452 magicast: 7453 7453 optional: true 7454 7454 7455 + c12@3.3.0: 7456 + resolution: {integrity: sha512-K9ZkuyeJQeqLEyqldbYLG3wjqwpw4BVaAqvmxq3GYKK0b1A/yYQdIcJxkzAOWcNVWhJpRXAPfZFueekiY/L8Dw==} 7457 + peerDependencies: 7458 + magicast: ^0.3.5 7459 + peerDependenciesMeta: 7460 + magicast: 7461 + optional: true 7462 + 7455 7463 cac@6.7.14: 7456 7464 resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 7457 7465 engines: {node: '>=8'} ··· 8217 8225 8218 8226 dotenv@17.2.1: 8219 8227 resolution: {integrity: sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==} 8228 + engines: {node: '>=12'} 8229 + 8230 + dotenv@17.2.2: 8231 + resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==} 8220 8232 engines: {node: '>=12'} 8221 8233 8222 8234 dunder-proto@1.0.1: ··· 12751 12763 resolution: {integrity: sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==} 12752 12764 engines: {node: ^18.17.0 || >=20.5.0} 12753 12765 12754 - turbo-darwin-64@2.5.6: 12755 - resolution: {integrity: sha512-3C1xEdo4aFwMJAPvtlPqz1Sw/+cddWIOmsalHFMrsqqydcptwBfu26WW2cDm3u93bUzMbBJ8k3zNKFqxJ9ei2A==} 12766 + turbo-darwin-64@2.5.8: 12767 + resolution: {integrity: sha512-Dh5bCACiHO8rUXZLpKw+m3FiHtAp2CkanSyJre+SInEvEr5kIxjGvCK/8MFX8SFRjQuhjtvpIvYYZJB4AGCxNQ==} 12756 12768 cpu: [x64] 12757 12769 os: [darwin] 12758 12770 12759 - turbo-darwin-arm64@2.5.6: 12760 - resolution: {integrity: sha512-LyiG+rD7JhMfYwLqB6k3LZQtYn8CQQUePbpA8mF/hMLPAekXdJo1g0bUPw8RZLwQXUIU/3BU7tXENvhSGz5DPA==} 12771 + turbo-darwin-arm64@2.5.8: 12772 + resolution: {integrity: sha512-f1H/tQC9px7+hmXn6Kx/w8Jd/FneIUnvLlcI/7RGHunxfOkKJKvsoiNzySkoHQ8uq1pJnhJ0xNGTlYM48ZaJOQ==} 12761 12773 cpu: [arm64] 12762 12774 os: [darwin] 12763 12775 12764 - turbo-linux-64@2.5.6: 12765 - resolution: {integrity: sha512-GOcUTT0xiT/pSnHL4YD6Yr3HreUhU8pUcGqcI2ksIF9b2/r/kRHwGFcsHgpG3+vtZF/kwsP0MV8FTlTObxsYIA==} 12776 + turbo-linux-64@2.5.8: 12777 + resolution: {integrity: sha512-hMyvc7w7yadBlZBGl/bnR6O+dJTx3XkTeyTTH4zEjERO6ChEs0SrN8jTFj1lueNXKIHh1SnALmy6VctKMGnWfw==} 12766 12778 cpu: [x64] 12767 12779 os: [linux] 12768 12780 12769 - turbo-linux-arm64@2.5.6: 12770 - resolution: {integrity: sha512-10Tm15bruJEA3m0V7iZcnQBpObGBcOgUcO+sY7/2vk1bweW34LMhkWi8svjV9iDF68+KJDThnYDlYE/bc7/zzQ==} 12781 + turbo-linux-arm64@2.5.8: 12782 + resolution: {integrity: sha512-LQELGa7bAqV2f+3rTMRPnj5G/OHAe2U+0N9BwsZvfMvHSUbsQ3bBMWdSQaYNicok7wOZcHjz2TkESn1hYK6xIQ==} 12771 12783 cpu: [arm64] 12772 12784 os: [linux] 12773 12785 12774 - turbo-windows-64@2.5.6: 12775 - resolution: {integrity: sha512-FyRsVpgaj76It0ludwZsNN40ytHN+17E4PFJyeliBEbxrGTc5BexlXVpufB7XlAaoaZVxbS6KT8RofLfDRyEPg==} 12786 + turbo-windows-64@2.5.8: 12787 + resolution: {integrity: sha512-3YdcaW34TrN1AWwqgYL9gUqmZsMT4T7g8Y5Azz+uwwEJW+4sgcJkIi9pYFyU4ZBSjBvkfuPZkGgfStir5BBDJQ==} 12776 12788 cpu: [x64] 12777 12789 os: [win32] 12778 12790 12779 - turbo-windows-arm64@2.5.6: 12780 - resolution: {integrity: sha512-j/tWu8cMeQ7HPpKri6jvKtyXg9K1gRyhdK4tKrrchH8GNHscPX/F71zax58yYtLRWTiK04zNzPcUJuoS0+v/+Q==} 12791 + turbo-windows-arm64@2.5.8: 12792 + resolution: {integrity: sha512-eFC5XzLmgXJfnAK3UMTmVECCwuBcORrWdewoiXBnUm934DY6QN8YowC/srhNnROMpaKaqNeRpoB5FxCww3eteQ==} 12781 12793 cpu: [arm64] 12782 12794 os: [win32] 12783 12795 12784 - turbo@2.5.6: 12785 - resolution: {integrity: sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w==} 12796 + turbo@2.5.8: 12797 + resolution: {integrity: sha512-5c9Fdsr9qfpT3hA0EyYSFRZj1dVVsb6KIWubA9JBYZ/9ZEAijgUEae0BBR/Xl/wekt4w65/lYLTFaP3JmwSO8w==} 12786 12798 hasBin: true 12787 12799 12788 12800 type-check@0.4.0: ··· 14110 14122 dependencies: 14111 14123 '@ampproject/remapping': 2.3.0 14112 14124 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) 14113 - '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.0)))(webpack@5.98.0(esbuild@0.25.0)) 14125 + '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.9)))(webpack@5.98.0(esbuild@0.25.0)) 14114 14126 '@angular-devkit/core': 19.2.0(chokidar@4.0.3) 14115 14127 '@angular/build': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) 14116 14128 '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3) ··· 14162 14174 typescript: 5.8.3 14163 14175 webpack: 5.98.0(esbuild@0.25.9) 14164 14176 webpack-dev-middleware: 7.4.2(webpack@5.98.0(esbuild@0.25.9)) 14165 - webpack-dev-server: 5.2.0(webpack@5.98.0(esbuild@0.25.0)) 14177 + webpack-dev-server: 5.2.0(webpack@5.98.0(esbuild@0.25.9)) 14166 14178 webpack-merge: 6.0.1 14167 14179 webpack-subresource-integrity: 5.1.0(webpack@5.98.0(esbuild@0.25.9)) 14168 14180 optionalDependencies: ··· 14198 14210 dependencies: 14199 14211 '@ampproject/remapping': 2.3.0 14200 14212 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) 14201 - '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.0)))(webpack@5.98.0(esbuild@0.25.0)) 14213 + '@angular-devkit/build-webpack': 0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.9)))(webpack@5.98.0(esbuild@0.25.0)) 14202 14214 '@angular-devkit/core': 19.2.0(chokidar@4.0.3) 14203 14215 '@angular/build': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/platform-server@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.0(@angular/animations@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1))))(@angular/ssr@19.2.15(5c03da8199d2fcdf9ff93b70f9349edd))(@types/node@22.10.5)(chokidar@4.0.3)(jiti@2.5.1)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.8.3)))(terser@5.39.0)(typescript@5.8.3)(yaml@2.8.0) 14204 14216 '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.2)(zone.js@0.15.1)))(typescript@5.8.3) ··· 14250 14262 typescript: 5.8.3 14251 14263 webpack: 5.98.0(esbuild@0.25.9) 14252 14264 webpack-dev-middleware: 7.4.2(webpack@5.98.0(esbuild@0.25.9)) 14253 - webpack-dev-server: 5.2.0(webpack@5.98.0(esbuild@0.25.0)) 14265 + webpack-dev-server: 5.2.0(webpack@5.98.0(esbuild@0.25.9)) 14254 14266 webpack-merge: 6.0.1 14255 14267 webpack-subresource-integrity: 5.1.0(webpack@5.98.0(esbuild@0.25.9)) 14256 14268 optionalDependencies: ··· 14457 14469 - webpack-cli 14458 14470 - yaml 14459 14471 14460 - '@angular-devkit/build-webpack@0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.0)))(webpack@5.98.0(esbuild@0.25.0))': 14472 + '@angular-devkit/build-webpack@0.1902.0(chokidar@4.0.3)(webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.9)))(webpack@5.98.0(esbuild@0.25.0))': 14461 14473 dependencies: 14462 14474 '@angular-devkit/architect': 0.1902.0(chokidar@4.0.3) 14463 14475 rxjs: 7.8.1 14464 14476 webpack: 5.98.0(esbuild@0.25.9) 14465 - webpack-dev-server: 5.2.0(webpack@5.98.0(esbuild@0.25.0)) 14477 + webpack-dev-server: 5.2.0(webpack@5.98.0(esbuild@0.25.9)) 14466 14478 transitivePeerDependencies: 14467 14479 - chokidar 14468 14480 ··· 17242 17254 17243 17255 '@fontsource/fira-mono@5.0.0': {} 17244 17256 17245 - '@hey-api/json-schema-ref-parser@1.1.0': 17257 + '@hey-api/json-schema-ref-parser@1.2.0': 17246 17258 dependencies: 17247 17259 '@jsdevtools/ono': 7.1.3 17248 17260 '@types/json-schema': 7.0.15 ··· 17942 17954 17943 17955 '@nuxt/devalue@2.0.2': {} 17944 17956 17957 + '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))': 17958 + dependencies: 17959 + '@nuxt/kit': 3.15.4(magicast@0.3.5) 17960 + '@nuxt/schema': 3.16.2 17961 + execa: 7.2.0 17962 + vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 17963 + transitivePeerDependencies: 17964 + - magicast 17965 + - supports-color 17966 + 17945 17967 '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': 17946 17968 dependencies: 17947 17969 '@nuxt/kit': 3.15.4(magicast@0.3.5) ··· 17958 17980 '@nuxt/schema': 3.16.2 17959 17981 execa: 7.2.0 17960 17982 vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 17961 - transitivePeerDependencies: 17962 - - magicast 17963 - - supports-color 17964 - 17965 - '@nuxt/devtools-kit@1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))': 17966 - dependencies: 17967 - '@nuxt/kit': 3.15.4(magicast@0.3.5) 17968 - '@nuxt/schema': 3.16.2 17969 - execa: 7.2.0 17970 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 17971 17983 transitivePeerDependencies: 17972 17984 - magicast 17973 17985 - supports-color ··· 18018 18030 semver: 7.7.2 18019 18031 simple-git: 3.28.0 18020 18032 sirv: 3.0.1 18021 - tinyglobby: 0.2.10 18033 + tinyglobby: 0.2.14 18022 18034 unimport: 3.14.6(rollup@3.29.5) 18023 18035 vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18024 18036 vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@3.29.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) ··· 18032 18044 - utf-8-validate 18033 18045 - vue 18034 18046 18035 - '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2))': 18047 + '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3))': 18036 18048 dependencies: 18037 18049 '@antfu/utils': 0.7.10 18038 - '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18050 + '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 18039 18051 '@nuxt/devtools-wizard': 1.7.0 18040 18052 '@nuxt/kit': 3.15.4(magicast@0.3.5) 18041 - '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2)) 18053 + '@vue/devtools-core': 7.6.8(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) 18042 18054 '@vue/devtools-kit': 7.6.8 18043 18055 birpc: 0.2.19 18044 18056 consola: 3.4.2 ··· 18065 18077 semver: 7.7.2 18066 18078 simple-git: 3.28.0 18067 18079 sirv: 3.0.1 18068 - tinyglobby: 0.2.10 18080 + tinyglobby: 0.2.14 18069 18081 unimport: 3.14.6(rollup@4.50.0) 18070 - vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18071 - vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18072 - vite-plugin-vue-inspector: 5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18082 + vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 18083 + vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 18084 + vite-plugin-vue-inspector: 5.3.2(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 18073 18085 which: 3.0.1 18074 18086 ws: 8.18.3 18075 18087 transitivePeerDependencies: ··· 18079 18091 - utf-8-validate 18080 18092 - vue 18081 18093 18082 - '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': 18094 + '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2))': 18083 18095 dependencies: 18084 18096 '@antfu/utils': 0.7.10 18085 - '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 18097 + '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18086 18098 '@nuxt/devtools-wizard': 1.7.0 18087 18099 '@nuxt/kit': 3.15.4(magicast@0.3.5) 18088 - '@vue/devtools-core': 7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) 18100 + '@vue/devtools-core': 7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.9.2)) 18089 18101 '@vue/devtools-kit': 7.6.8 18090 18102 birpc: 0.2.19 18091 18103 consola: 3.4.2 ··· 18112 18124 semver: 7.7.2 18113 18125 simple-git: 3.28.0 18114 18126 sirv: 3.0.1 18115 - tinyglobby: 0.2.10 18127 + tinyglobby: 0.2.14 18116 18128 unimport: 3.14.6(rollup@4.50.0) 18117 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 18118 - vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 18119 - vite-plugin-vue-inspector: 5.3.2(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 18129 + vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18130 + vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18131 + vite-plugin-vue-inspector: 5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18120 18132 which: 3.0.1 18121 18133 ws: 8.18.3 18122 18134 transitivePeerDependencies: ··· 18126 18138 - utf-8-validate 18127 18139 - vue 18128 18140 18129 - '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': 18141 + '@nuxt/devtools@1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': 18130 18142 dependencies: 18131 18143 '@antfu/utils': 0.7.10 18132 - '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18144 + '@nuxt/devtools-kit': 1.7.0(magicast@0.3.5)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 18133 18145 '@nuxt/devtools-wizard': 1.7.0 18134 18146 '@nuxt/kit': 3.15.4(magicast@0.3.5) 18135 - '@vue/devtools-core': 7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) 18147 + '@vue/devtools-core': 7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) 18136 18148 '@vue/devtools-kit': 7.6.8 18137 18149 birpc: 0.2.19 18138 18150 consola: 3.4.2 ··· 18159 18171 semver: 7.7.2 18160 18172 simple-git: 3.28.0 18161 18173 sirv: 3.0.1 18162 - tinyglobby: 0.2.10 18174 + tinyglobby: 0.2.14 18163 18175 unimport: 3.14.6(rollup@4.50.0) 18164 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 18165 - vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18166 - vite-plugin-vue-inspector: 5.3.2(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 18176 + vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 18177 + vite-plugin-inspect: 0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 18178 + vite-plugin-vue-inspector: 5.3.2(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 18167 18179 which: 3.0.1 18168 18180 ws: 8.18.3 18169 18181 transitivePeerDependencies: ··· 20478 20490 dependencies: 20479 20491 '@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13) 20480 20492 '@rollup/pluginutils': 5.2.0(rollup@4.50.0) 20481 - acorn: 8.14.0 20482 - acorn-import-attributes: 1.9.5(acorn@8.14.0) 20493 + acorn: 8.15.0 20494 + acorn-import-attributes: 1.9.5(acorn@8.15.0) 20483 20495 async-sema: 3.1.1 20484 20496 bindings: 1.5.0 20485 20497 estree-walker: 2.0.2 ··· 20863 20875 dependencies: 20864 20876 '@vue/devtools-kit': 8.0.2 20865 20877 20866 - '@vue/devtools-core@7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': 20878 + '@vue/devtools-core@7.6.8(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3))': 20867 20879 dependencies: 20868 20880 '@vue/devtools-kit': 7.7.7 20869 20881 '@vue/devtools-shared': 7.7.7 20870 20882 mitt: 3.0.1 20871 20883 nanoid: 5.1.5 20872 20884 pathe: 1.1.2 20885 + vite-hot-client: 0.2.4(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)) 20886 + vue: 3.5.13(typescript@5.8.3) 20887 + transitivePeerDependencies: 20888 + - vite 20889 + 20890 + '@vue/devtools-core@7.6.8(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': 20891 + dependencies: 20892 + '@vue/devtools-kit': 7.7.7 20893 + '@vue/devtools-shared': 7.7.7 20894 + mitt: 3.0.1 20895 + nanoid: 5.1.5 20896 + pathe: 1.1.2 20873 20897 vite-hot-client: 0.2.4(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 20874 20898 vue: 3.5.13(typescript@5.8.3) 20875 20899 transitivePeerDependencies: ··· 20894 20919 nanoid: 5.1.5 20895 20920 pathe: 1.1.2 20896 20921 vite-hot-client: 0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)) 20897 - vue: 3.5.13(typescript@5.8.3) 20898 - transitivePeerDependencies: 20899 - - vite 20900 - 20901 - '@vue/devtools-core@7.6.8(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3))': 20902 - dependencies: 20903 - '@vue/devtools-kit': 7.7.7 20904 - '@vue/devtools-shared': 7.7.7 20905 - mitt: 3.0.1 20906 - nanoid: 5.1.5 20907 - pathe: 1.1.2 20908 - vite-hot-client: 0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 20909 20922 vue: 3.5.13(typescript@5.8.3) 20910 20923 transitivePeerDependencies: 20911 20924 - vite ··· 21222 21235 mime-types: 2.1.35 21223 21236 negotiator: 0.6.3 21224 21237 21225 - acorn-import-attributes@1.9.5(acorn@8.14.0): 21238 + acorn-import-attributes@1.9.5(acorn@8.15.0): 21226 21239 dependencies: 21227 - acorn: 8.14.0 21240 + acorn: 8.15.0 21228 21241 21229 21242 acorn-jsx@5.3.2(acorn@7.4.1): 21230 21243 dependencies: ··· 21755 21768 ohash: 2.0.11 21756 21769 pathe: 2.0.3 21757 21770 perfect-debounce: 1.0.0 21771 + pkg-types: 2.3.0 21772 + rc9: 2.1.2 21773 + optionalDependencies: 21774 + magicast: 0.3.5 21775 + 21776 + c12@3.3.0(magicast@0.3.5): 21777 + dependencies: 21778 + chokidar: 4.0.3 21779 + confbox: 0.2.2 21780 + defu: 6.1.4 21781 + dotenv: 17.2.2 21782 + exsolve: 1.0.7 21783 + giget: 2.0.0 21784 + jiti: 2.5.1 21785 + ohash: 2.0.11 21786 + pathe: 2.0.3 21787 + perfect-debounce: 2.0.0 21758 21788 pkg-types: 2.3.0 21759 21789 rc9: 2.1.2 21760 21790 optionalDependencies: ··· 22492 22522 22493 22523 dotenv@17.2.1: {} 22494 22524 22525 + dotenv@17.2.2: {} 22526 + 22495 22527 dunder-proto@1.0.1: 22496 22528 dependencies: 22497 22529 call-bind-apply-helpers: 1.0.2 ··· 22929 22961 '@typescript-eslint/parser': 8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3) 22930 22962 eslint: 9.17.0(jiti@2.5.1) 22931 22963 eslint-import-resolver-node: 0.3.9 22932 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 22933 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 22964 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.17.0(jiti@2.5.1)) 22965 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)) 22934 22966 eslint-plugin-jsx-a11y: 6.10.2(eslint@9.17.0(jiti@2.5.1)) 22935 22967 eslint-plugin-react: 7.37.5(eslint@9.17.0(jiti@2.5.1)) 22936 22968 eslint-plugin-react-hooks: 5.2.0(eslint@9.17.0(jiti@2.5.1)) ··· 22953 22985 transitivePeerDependencies: 22954 22986 - supports-color 22955 22987 22956 - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)): 22988 + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.17.0(jiti@2.5.1)): 22957 22989 dependencies: 22958 22990 '@nolyfill/is-core-module': 1.0.39 22959 22991 debug: 4.4.1 ··· 22964 22996 tinyglobby: 0.2.14 22965 22997 unrs-resolver: 1.11.1 22966 22998 optionalDependencies: 22967 - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 22999 + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)) 22968 23000 transitivePeerDependencies: 22969 23001 - supports-color 22970 23002 22971 - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)): 23003 + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)): 22972 23004 dependencies: 22973 23005 debug: 3.2.7 22974 23006 optionalDependencies: 22975 23007 '@typescript-eslint/parser': 8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3) 22976 23008 eslint: 9.17.0(jiti@2.5.1) 22977 23009 eslint-import-resolver-node: 0.3.9 22978 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 23010 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.17.0(jiti@2.5.1)) 22979 23011 transitivePeerDependencies: 22980 23012 - supports-color 22981 23013 22982 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)): 23014 + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)): 22983 23015 dependencies: 22984 23016 '@rtsao/scc': 1.1.0 22985 23017 array-includes: 3.1.9 ··· 22990 23022 doctrine: 2.1.0 22991 23023 eslint: 9.17.0(jiti@2.5.1) 22992 23024 eslint-import-resolver-node: 0.3.9 22993 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)))(eslint@9.17.0(jiti@2.5.1)) 23025 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.29.1(eslint@9.17.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.17.0(jiti@2.5.1)) 22994 23026 hasown: 2.0.2 22995 23027 is-core-module: 2.16.1 22996 23028 is-glob: 4.0.3 ··· 25499 25531 '@rollup/plugin-terser': 0.4.4(rollup@4.50.0) 25500 25532 '@vercel/nft': 0.29.4(encoding@0.1.13)(rollup@4.50.0) 25501 25533 archiver: 7.0.1 25502 - c12: 3.2.0(magicast@0.3.5) 25534 + c12: 3.3.0(magicast@0.3.5) 25503 25535 chokidar: 4.0.3 25504 25536 citty: 0.1.6 25505 25537 compatx: 0.2.0 ··· 25983 26015 - vue-tsc 25984 26016 - xml2js 25985 26017 25986 - nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 26018 + nuxt@3.14.1592(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.10.5)(db0@0.3.2)(encoding@0.1.13)(eslint@9.17.0(jiti@2.5.1))(ioredis@5.7.0)(less@4.2.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.50.0)(sass@1.85.0)(terser@5.43.1)(typescript@5.8.3)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): 25987 26019 dependencies: 25988 26020 '@nuxt/devalue': 2.0.2 25989 - '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.13(typescript@5.8.3)) 26021 + '@nuxt/devtools': 1.7.0(rollup@4.50.0)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1))(vue@3.5.13(typescript@5.8.3)) 25990 26022 '@nuxt/kit': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) 25991 26023 '@nuxt/schema': 3.14.1592(magicast@0.3.5)(rollup@4.50.0) 25992 26024 '@nuxt/telemetry': 2.6.6(magicast@0.3.5) ··· 28312 28344 28313 28345 term-size@2.2.1: {} 28314 28346 28315 - terser-webpack-plugin@5.3.14(esbuild@0.25.4)(webpack@5.98.0(esbuild@0.25.9)): 28347 + terser-webpack-plugin@5.3.14(esbuild@0.25.0)(webpack@5.98.0(esbuild@0.25.9)): 28316 28348 dependencies: 28317 28349 '@jridgewell/trace-mapping': 0.3.30 28318 28350 jest-worker: 27.5.1 ··· 28321 28353 terser: 5.43.1 28322 28354 webpack: 5.98.0(esbuild@0.25.9) 28323 28355 optionalDependencies: 28324 - esbuild: 0.25.4 28356 + esbuild: 0.25.0 28325 28357 28326 - terser-webpack-plugin@5.3.14(esbuild@0.25.9)(webpack@5.98.0(esbuild@0.25.0)): 28358 + terser-webpack-plugin@5.3.14(esbuild@0.25.4)(webpack@5.98.0(esbuild@0.25.9)): 28327 28359 dependencies: 28328 28360 '@jridgewell/trace-mapping': 0.3.30 28329 28361 jest-worker: 27.5.1 ··· 28332 28364 terser: 5.43.1 28333 28365 webpack: 5.98.0(esbuild@0.25.9) 28334 28366 optionalDependencies: 28335 - esbuild: 0.25.9 28367 + esbuild: 0.25.4 28336 28368 28337 28369 terser@5.39.0: 28338 28370 dependencies: ··· 28545 28577 transitivePeerDependencies: 28546 28578 - supports-color 28547 28579 28548 - turbo-darwin-64@2.5.6: 28580 + turbo-darwin-64@2.5.8: 28549 28581 optional: true 28550 28582 28551 - turbo-darwin-arm64@2.5.6: 28583 + turbo-darwin-arm64@2.5.8: 28552 28584 optional: true 28553 28585 28554 - turbo-linux-64@2.5.6: 28586 + turbo-linux-64@2.5.8: 28555 28587 optional: true 28556 28588 28557 - turbo-linux-arm64@2.5.6: 28589 + turbo-linux-arm64@2.5.8: 28558 28590 optional: true 28559 28591 28560 - turbo-windows-64@2.5.6: 28592 + turbo-windows-64@2.5.8: 28561 28593 optional: true 28562 28594 28563 - turbo-windows-arm64@2.5.6: 28595 + turbo-windows-arm64@2.5.8: 28564 28596 optional: true 28565 28597 28566 - turbo@2.5.6: 28598 + turbo@2.5.8: 28567 28599 optionalDependencies: 28568 - turbo-darwin-64: 2.5.6 28569 - turbo-darwin-arm64: 2.5.6 28570 - turbo-linux-64: 2.5.6 28571 - turbo-linux-arm64: 2.5.6 28572 - turbo-windows-64: 2.5.6 28573 - turbo-windows-arm64: 2.5.6 28600 + turbo-darwin-64: 2.5.8 28601 + turbo-darwin-arm64: 2.5.8 28602 + turbo-linux-64: 2.5.8 28603 + turbo-linux-arm64: 2.5.8 28604 + turbo-windows-64: 2.5.8 28605 + turbo-windows-arm64: 2.5.8 28574 28606 28575 28607 type-check@0.4.0: 28576 28608 dependencies: ··· 28755 28787 unimport@3.14.6(rollup@3.29.5): 28756 28788 dependencies: 28757 28789 '@rollup/pluginutils': 5.2.0(rollup@3.29.5) 28758 - acorn: 8.14.0 28790 + acorn: 8.15.0 28759 28791 escape-string-regexp: 5.0.0 28760 28792 estree-walker: 3.0.3 28761 28793 fast-glob: 3.3.3 ··· 28774 28806 unimport@3.14.6(rollup@4.50.0): 28775 28807 dependencies: 28776 28808 '@rollup/pluginutils': 5.2.0(rollup@4.50.0) 28777 - acorn: 8.14.0 28809 + acorn: 8.15.0 28778 28810 escape-string-regexp: 5.0.0 28779 28811 estree-walker: 3.0.3 28780 28812 fast-glob: 3.3.3 ··· 28951 28983 28952 28984 unplugin@1.16.1: 28953 28985 dependencies: 28954 - acorn: 8.14.0 28986 + acorn: 8.15.0 28955 28987 webpack-virtual-modules: 0.6.2 28956 28988 28957 28989 unplugin@2.0.0-beta.1: 28958 28990 dependencies: 28959 - acorn: 8.14.0 28991 + acorn: 8.15.0 28960 28992 webpack-virtual-modules: 0.6.2 28961 28993 28962 28994 unplugin@2.3.10: ··· 29122 29154 vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29123 29155 vite-hot-client: 2.1.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)) 29124 29156 29157 + vite-hot-client@0.2.4(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): 29158 + dependencies: 29159 + vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 29160 + 29125 29161 vite-hot-client@0.2.4(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29126 29162 dependencies: 29127 29163 vite: 7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) ··· 29129 29165 vite-hot-client@0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0)): 29130 29166 dependencies: 29131 29167 vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 29132 - 29133 - vite-hot-client@0.2.4(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29134 - dependencies: 29135 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29136 29168 29137 29169 vite-hot-client@2.1.0(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29138 29170 dependencies: ··· 29300 29332 - rollup 29301 29333 - supports-color 29302 29334 29335 + vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): 29336 + dependencies: 29337 + '@antfu/utils': 0.7.10 29338 + '@rollup/pluginutils': 5.2.0(rollup@4.50.0) 29339 + debug: 4.4.1 29340 + error-stack-parser-es: 0.1.5 29341 + fs-extra: 11.3.1 29342 + open: 10.1.2 29343 + perfect-debounce: 1.0.0 29344 + picocolors: 1.1.1 29345 + sirv: 3.0.1 29346 + vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 29347 + optionalDependencies: 29348 + '@nuxt/kit': 3.15.4(magicast@0.3.5) 29349 + transitivePeerDependencies: 29350 + - rollup 29351 + - supports-color 29352 + 29303 29353 vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29304 29354 dependencies: 29305 29355 '@antfu/utils': 0.7.10 ··· 29330 29380 picocolors: 1.1.1 29331 29381 sirv: 3.0.1 29332 29382 vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 29333 - optionalDependencies: 29334 - '@nuxt/kit': 3.15.4(magicast@0.3.5) 29335 - transitivePeerDependencies: 29336 - - rollup 29337 - - supports-color 29338 - 29339 - vite-plugin-inspect@0.8.9(@nuxt/kit@3.15.4(magicast@0.3.5))(rollup@4.50.0)(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29340 - dependencies: 29341 - '@antfu/utils': 0.7.10 29342 - '@rollup/pluginutils': 5.2.0(rollup@4.50.0) 29343 - debug: 4.4.1 29344 - error-stack-parser-es: 0.1.5 29345 - fs-extra: 11.3.1 29346 - open: 10.1.2 29347 - perfect-debounce: 1.0.0 29348 - picocolors: 1.1.1 29349 - sirv: 3.0.1 29350 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29351 29383 optionalDependencies: 29352 29384 '@nuxt/kit': 3.15.4(magicast@0.3.5) 29353 29385 transitivePeerDependencies: ··· 29384 29416 - supports-color 29385 29417 - vue 29386 29418 29419 + vite-plugin-vue-inspector@5.3.2(vite@5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)): 29420 + dependencies: 29421 + '@babel/core': 7.28.3 29422 + '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) 29423 + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) 29424 + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) 29425 + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) 29426 + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) 29427 + '@vue/compiler-dom': 3.5.21 29428 + kolorist: 1.8.0 29429 + magic-string: 0.30.18 29430 + vite: 5.4.19(@types/node@22.10.5)(less@4.2.2)(sass@1.85.0)(terser@5.43.1) 29431 + transitivePeerDependencies: 29432 + - supports-color 29433 + 29387 29434 vite-plugin-vue-inspector@5.3.2(vite@7.1.2(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29388 29435 dependencies: 29389 29436 '@babel/core': 7.28.3 ··· 29411 29458 kolorist: 1.8.0 29412 29459 magic-string: 0.30.18 29413 29460 vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.0) 29414 - transitivePeerDependencies: 29415 - - supports-color 29416 - 29417 - vite-plugin-vue-inspector@5.3.2(vite@7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0)): 29418 - dependencies: 29419 - '@babel/core': 7.28.3 29420 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) 29421 - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) 29422 - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) 29423 - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) 29424 - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.3) 29425 - '@vue/compiler-dom': 3.5.21 29426 - kolorist: 1.8.0 29427 - magic-string: 0.30.18 29428 - vite: 7.1.5(@types/node@22.10.5)(jiti@2.5.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.0) 29429 29461 transitivePeerDependencies: 29430 29462 - supports-color 29431 29463 ··· 29893 29925 optionalDependencies: 29894 29926 webpack: 5.98.0(esbuild@0.25.9) 29895 29927 29896 - webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.0)): 29928 + webpack-dev-server@5.2.0(webpack@5.98.0(esbuild@0.25.9)): 29897 29929 dependencies: 29898 29930 '@types/bonjour': 3.5.13 29899 29931 '@types/connect-history-api-fallback': 1.5.4 ··· 30035 30067 neo-async: 2.6.2 30036 30068 schema-utils: 4.3.2 30037 30069 tapable: 2.2.3 30038 - terser-webpack-plugin: 5.3.14(esbuild@0.25.9)(webpack@5.98.0(esbuild@0.25.0)) 30070 + terser-webpack-plugin: 5.3.14(esbuild@0.25.0)(webpack@5.98.0(esbuild@0.25.9)) 30039 30071 watchpack: 2.4.4 30040 30072 webpack-sources: 3.3.3 30041 30073 transitivePeerDependencies: