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 branch 'main' into fix/refs-broken

authored by

Lubos and committed by
GitHub
e28d22a1 4999aad1

+1862 -674
+5
.changeset/cold-ideas-eat-2.md
··· 1 + --- 2 + "@hey-api/openapi-ts": patch 3 + --- 4 + 5 + **plugin(@tanstack/solid-query)**: index mutation options symbol
+5
.changeset/cold-ideas-eat-3.md
··· 1 + --- 2 + "@hey-api/openapi-ts": patch 3 + --- 4 + 5 + **plugin(@tanstack/svelte-query)**: index mutation options symbol
+5
.changeset/cold-ideas-eat-4.md
··· 1 + --- 2 + "@hey-api/openapi-ts": patch 3 + --- 4 + 5 + **plugin(@tanstack/vue-query)**: index mutation options symbol
+5
.changeset/cold-ideas-eat.md
··· 1 + --- 2 + "@hey-api/openapi-ts": patch 3 + --- 4 + 5 + **plugin(@tanstack/react-query)**: index mutation options symbol
-6
.changeset/cold-peas-speak.md
··· 1 - --- 2 - '@hey-api/codegen-core': patch 3 - '@hey-api/openapi-ts': patch 4 - --- 5 - 6 - **deps**: move @hey-api/types to dependencies to fix broken types
+9
.changeset/rare-coins-heal.md
··· 1 + --- 2 + "@hey-api/openapi-ts": patch 3 + --- 4 + 5 + **plugin(@tanstack/angular-query-experimental)**: index mutation options symbol 6 + **plugin(@tanstack/react-query)**: index mutation options symbol 7 + **plugin(@tanstack/solid-query)**: index mutation options symbol 8 + **plugin(@tanstack/svelte-query)**: index mutation options symbol 9 + **plugin(@tanstack/vue-query)**: index mutation options symbol
+1 -1
.vscode/extensions.json
··· 2 2 "recommendations": [ 3 3 "dbaeumer.vscode-eslint", 4 4 "EditorConfig.EditorConfig", 5 + "esbenp.prettier-vscode", 5 6 "hilleer.yaml-plus-json", 6 7 "oxc.oxc-vscode", 7 - "prettier.prettier-vscode", 8 8 "streetsidesoftware.code-spell-checker", 9 9 "usernamehw.errorlens" 10 10 ]
+14
.vscode/launch.json
··· 16 16 "cwd": "${workspaceFolder}/dev", 17 17 "runtimeExecutable": "node", 18 18 "program": "${workspaceFolder}/packages/openapi-ts/dist/run.mjs", 19 + "args": [], 20 + "env": { 21 + "DEBUG": "false" 22 + } 23 + }, 24 + { 25 + "type": "node", 26 + "request": "launch", 27 + "name": "openapi-python", 28 + "skipFiles": ["<node_internals>/**"], 29 + "cwd": "${workspaceFolder}/dev", 30 + "runtimeExecutable": "node", 31 + "program": "${workspaceFolder}/packages/openapi-python/dist/run.mjs", 32 + "args": [], 19 33 "env": { 20 34 "DEBUG": "false" 21 35 }
+732
dev/openapi-python.config.ts
··· 1 + /* eslint-disable @typescript-eslint/no-unused-vars, arrow-body-style */ 2 + // @ts-ignore 3 + import path from 'node:path'; 4 + 5 + // @ts-ignore 6 + import { customClientPlugin } from '@hey-api/custom-client/plugin'; 7 + import { 8 + // @ts-ignore 9 + defineConfig, 10 + } from '@hey-api/openapi-python'; 11 + import { 12 + // @ts-ignore 13 + OperationPath, 14 + // @ts-ignore 15 + OperationStrategy, 16 + // @ts-ignore 17 + reserved, 18 + // @ts-ignore 19 + utils, 20 + } from '@hey-api/openapi-ts'; 21 + 22 + // @ts-ignore 23 + import { myClientPlugin } from '../packages/openapi-ts-tests/main/test/custom/client/plugin'; 24 + // @ts-ignore 25 + import { getSpecsPath } from '../packages/openapi-ts-tests/utils'; 26 + 27 + reserved.runtime.set((list) => [...list, 'Agent']); 28 + reserved.type.set((list) => [...list, 'Agent']); 29 + 30 + console.log('yo'); 31 + 32 + // @ts-ignore 33 + export default defineConfig(() => { 34 + // ... 35 + return [ 36 + { 37 + input: [ 38 + { 39 + // fetch: { 40 + // headers: { 41 + // 'x-foo': 'bar', 42 + // }, 43 + // }, 44 + // path: { 45 + // components: {}, 46 + // info: { 47 + // version: '1.0.0', 48 + // }, 49 + // openapi: '3.1.0', 50 + // paths: {}, 51 + // }, 52 + path: path.resolve( 53 + getSpecsPath(), 54 + // '2.0.x', 55 + // '3.0.x', 56 + '3.1.x', 57 + // 'circular.yaml', 58 + // 'dutchie.json', 59 + // 'enum-names-values.yaml', 60 + // 'full.yaml', 61 + // 'integer-formats.yaml', 62 + // 'invalid', 63 + // 'object-property-names.yaml', 64 + // 'openai.yaml', 65 + // 'opencode.yaml', 66 + // 'pagination-ref.yaml', 67 + // 'schema-const.yaml', 68 + // 'sdk-instance.yaml', 69 + // 'sdk-method-class-conflict.yaml', 70 + // 'sdk-nested-classes.yaml', 71 + // 'sdk-nested-conflict.yaml', 72 + 'security-api-key.yaml', 73 + // 'string-with-format.yaml', 74 + // 'transformers.json', 75 + // 'transformers-recursive.json', 76 + // 'type-format.yaml', 77 + // 'validators.yaml', 78 + // 'validators-circular-ref.json', 79 + // 'validators-circular-ref-2.yaml', 80 + // 'zoom-video-sdk.json', 81 + ), 82 + // path: 'https://get.heyapi.dev/hey-api/backend?branch=main&version=1.0.0', 83 + // path: 'http://localhost:4000/', 84 + // path: 'http://localhost:8000/openapi.json', 85 + // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', 86 + // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', 87 + // watch: { 88 + // enabled: true, 89 + // interval: 500, 90 + // timeout: 30_000, 91 + // }, 92 + }, 93 + // path.resolve(getSpecsPath(), '3.1.x', 'full.yaml'), 94 + // { 95 + // branch: 'main', 96 + // organization: 'hey-api', 97 + // path: 'hey-api/backend', 98 + // project: 'backend', 99 + // project: 'upload-openapi-spec', 100 + // version: '1.0.0', 101 + // }, 102 + // 'hey-api/backend?branch=main&version=1.0.0', 103 + // 'scalar:@scalar/access-service', 104 + // 'readme:@developers/v2.0#nysezql0wwo236', 105 + // 'readme:nysezql0wwo236', 106 + // 'https://dash.readme.com/api/v1/api-registry/nysezql0wwo236', 107 + // 'https://somefakedomain.com/openapi.yaml', 108 + ], 109 + logs: { 110 + // level: 'debug', 111 + path: './logs', 112 + }, 113 + output: [ 114 + { 115 + // case: 'snake_case', 116 + // clean: true, 117 + // fileName: { 118 + // // case: 'snake_case', 119 + // // name: '{{name}}.renamed', 120 + // suffix: '.meh', 121 + // }, 122 + // header: null, 123 + header: [ 124 + '/* eslint-disable */', 125 + '// This file is auto-generated by @hey-api/openapi-ts', 126 + ], 127 + // importFileExtension: '.js', 128 + // indexFile: false, 129 + // nameConflictResolver({ attempt, baseName }) { 130 + // // console.log('resolving conflict for:', { attempt, baseName }); 131 + // return attempt === 0 ? baseName : `${baseName}_N${attempt + 1}`; 132 + // }, 133 + path: path.resolve(__dirname, '.gen'), 134 + postProcess: ['eslint'], 135 + // preferExportAll: true, 136 + resolveModuleName: (moduleName) => { 137 + if (moduleName === 'valibot') { 138 + return 'valibot'; 139 + } 140 + return; 141 + }, 142 + source: { 143 + // callback(source) { 144 + // console.log('Source generated, length:', source.length); 145 + // }, 146 + enabled: false, 147 + // extension: 'yaml', 148 + // fileName: 'spec', 149 + // path: null, 150 + // serialize(input) { 151 + // return JSON.stringify(input, null, 0); 152 + // }, 153 + }, 154 + // tsConfigPath: path.resolve( 155 + // __dirname, 156 + // 'tsconfig', 157 + // 'tsconfig.nodenext.json', 158 + // ), 159 + }, 160 + // '.gen', 161 + ], 162 + parser: { 163 + filters: { 164 + // deprecated: false, 165 + operations: { 166 + include: [ 167 + // 'GET /event', 168 + // '/^[A-Z]+ /v1//', 169 + ], 170 + }, 171 + // orphans: true, 172 + // preserveOrder: true, 173 + // schemas: { 174 + // include: ['Foo'], 175 + // }, 176 + // tags: { 177 + // exclude: ['bar'], 178 + // }, 179 + }, 180 + hooks: { 181 + events: { 182 + // 'node:set:after': ({ node, plugin }) => { 183 + // if (node) { 184 + // console.log(`(${plugin.name}) set node:`, node.symbol); 185 + // } 186 + // }, 187 + // 'node:set:before': ({ node, plugin }) => { 188 + // console.log(`(${plugin.name}) setting node:`, node?.symbol?.id); 189 + // }, 190 + // 'plugin:handler:after': ({ plugin }) => { 191 + // console.log(`(${plugin.name}): handler finished`); 192 + // }, 193 + // 'plugin:handler:before': ({ plugin }) => { 194 + // console.log(`(${plugin.name}): handler starting`); 195 + // }, 196 + // 'symbol:register:after': ({ plugin, symbol }) => { 197 + // console.log(`(global, ${plugin.name}) registered:`, symbol.id); 198 + // }, 199 + 'symbol:register:before': ({ plugin, symbol }) => { 200 + // if (!symbol.external && !symbol.meta?.resourceType) { 201 + // console.log(`[${plugin.name}]:`, symbol.name); 202 + // } 203 + if (plugin && !symbol.external && !symbol.meta?.path) { 204 + // console.log(`[${plugin.name}]:`, symbol.name, symbol.meta); 205 + } 206 + // if (symbol.meta?.tags && symbol.meta?.tags.size > 0) { 207 + // console.log( 208 + // `[${plugin.name}]:`, 209 + // symbol.name, 210 + // symbol.meta.path, 211 + // symbol.meta.tags, 212 + // ); 213 + // } 214 + }, 215 + }, 216 + operations: { 217 + getKind() { 218 + // noop 219 + }, 220 + isMutation() { 221 + // noop 222 + }, 223 + isQuery: (op) => { 224 + if (op.method === 'post' && op.path === '/search') { 225 + return true; 226 + } 227 + return; 228 + }, 229 + }, 230 + symbols: { 231 + // getFilePath: (symbol) => { 232 + // if (symbol.name) { 233 + // return symbol.name[0]?.toLowerCase(); 234 + // } 235 + // return; 236 + // }, 237 + }, 238 + }, 239 + pagination: { 240 + // keywords: ['aa'], 241 + }, 242 + patch: { 243 + // operations: { 244 + // 'GET /foo': (operation: any) => { 245 + // operation.responses['200'].description = 'foo'; 246 + // }, 247 + // }, 248 + // version: () => '3.1.1', 249 + }, 250 + transforms: { 251 + // enums: { 252 + // enabled: false, 253 + // mode: 'root', 254 + // // name: '{{name}}', 255 + // }, 256 + // propertiesRequiredByDefault: true, 257 + // readWrite: { 258 + // // enabled: false, 259 + // requests: '{{name}}Writable', 260 + // responses: '{{name}}', 261 + // }, 262 + }, 263 + // validate_EXPERIMENTAL: true, 264 + }, 265 + plugins: [ 266 + // customClientPlugin({ 267 + // baseUrl: false, 268 + // }), 269 + // myClientPlugin(), 270 + { 271 + // baseUrl: false, 272 + // exportFromIndex: true, 273 + // name: '@hey-api/client-angular', 274 + // runtimeConfigPath: path.resolve(__dirname, 'hey-api.ts'), 275 + // runtimeConfigPath: './src/hey-api.ts', 276 + // strictBaseUrl: true, 277 + throwOnError: true, 278 + }, 279 + { 280 + // case: 'snake_case', 281 + // definitions: '你_snake_{{name}}', 282 + enums: { 283 + // case: 'PascalCase', 284 + // constantsIgnoreNull: true, 285 + // enabled: false, 286 + // mode: 'typescript', 287 + }, 288 + // errors: { 289 + // error: '他們_error_{{name}}', 290 + // name: '你們_errors_{{name}}', 291 + // }, 292 + // exportFromIndex: false, 293 + // name: '@hey-api/typescript', 294 + // requests: '我們_data_{{name}}', 295 + // responses: { 296 + // name: '我_responses_{{name}}', 297 + // response: '他_response_{{name}}', 298 + // }, 299 + // topType: 'any', 300 + // tree: true, 301 + // webhooks: { 302 + // name: 'Webby{{name}}Hook', 303 + // payload: '{{name}}WebhookEvent', 304 + // }, 305 + }, 306 + { 307 + // auth: false, 308 + // client: false, 309 + examples: { 310 + // enabled: false, 311 + importKind: 'default', 312 + importName: 'CatStore', 313 + importSetup: ({ $, node }) => 314 + $.new( 315 + node.name, 316 + $.object().pretty().prop('apiKey', $.literal('YOUR_API_KEY')), 317 + ), 318 + // language: 'TypeScript', 319 + moduleName: '@petstore/client', 320 + payload(operation, ctx) { 321 + const { $ } = ctx; 322 + if ( 323 + operation.path === '/pet/{petId}' || 324 + operation.path === '/pet' 325 + ) { 326 + return $.object().pretty().prop('petId', $.literal(1234)); 327 + } 328 + return; 329 + }, 330 + setupName: 'client', 331 + // transform: (example) => example.replace(/\({}\)/, '({\n ...\n})'), 332 + }, 333 + // getSignature: ({ fields, signature, operation }) => { 334 + // // ... 335 + // fields.unwrap('path') 336 + // }, 337 + // include... 338 + name: '@hey-api/sdk', 339 + operations: { 340 + // container: 'object', 341 + // containerName: { 342 + // // casing: 'snake_case', 343 + // name: 'OpencodeClient', 344 + // }, 345 + containerName: 'PetStore', 346 + // nesting(operation) { 347 + // if (operation.path === '/pet/{petId}' || operation.path === '/pet') { 348 + // return ['pet', operation.operationId?.replace(/Pet/, '') || operation.method.toLocaleLowerCase()]; 349 + // } 350 + // return OperationPath.fromOperationId()(operation); 351 + // }, 352 + // methodName: { 353 + // // casing: 'SCREAMING_SNAKE_CASE', 354 + // name: '{{name}}Yummy', 355 + // }, 356 + // methods: 'static', 357 + // nesting: 'id', 358 + // segmentName: '{{name}}Seggy', 359 + strategy: 'single', 360 + // strategy(operation) { 361 + // const locations = OperationStrategy.byTags({ 362 + // fallback: 'default', 363 + // path: OperationPath.fromOperationId({ 364 + // delimiters: /[./]/, 365 + // }), 366 + // })(operation) 367 + // return locations.map((loc) => { 368 + // if (loc[0] && loc[1] && loc[0] === loc[1]) { 369 + // return loc.slice(1); 370 + // } 371 + // return loc; 372 + // }); 373 + // }, 374 + // strategyDefaultTag: 'DaxLikedThis', 375 + }, 376 + paramsStructure: 'flat', 377 + // responseStyle: 'data', 378 + // signature: 'auto', 379 + // signature: 'client', 380 + // signature: 'object', 381 + // transformer: '@hey-api/transformers', 382 + // transformer: true, 383 + // validator: 'valibot', 384 + // validator: { 385 + // request: 'zod', 386 + // response: 'zod', 387 + // }, 388 + '~hooks': { 389 + symbols: { 390 + // getFilePath: (symbol) => { 391 + // if (symbol.name) { 392 + // return utils.toCase(symbol.name, 'camelCase'); 393 + // } 394 + // return; 395 + // }, 396 + }, 397 + }, 398 + }, 399 + { 400 + // bigInt: true, 401 + dates: true, 402 + // name: '@hey-api/transformers', 403 + }, 404 + { 405 + // name: 'fastify', 406 + }, 407 + { 408 + // name: 'swr', 409 + }, 410 + { 411 + // case: 'SCREAMING_SNAKE_CASE', 412 + // comments: false, 413 + exportFromIndex: true, 414 + infiniteQueryKeys: { 415 + // name: '{{name}}IQK', 416 + // name: 'options', 417 + }, 418 + infiniteQueryOptions: { 419 + meta() { 420 + return { 421 + custom: 'value', 422 + }; 423 + }, 424 + // name: '{{name}}IQO', 425 + // name: 'options', 426 + }, 427 + mutationOptions: { 428 + meta() { 429 + return { 430 + custom: 'value', 431 + }; 432 + }, 433 + // name: '{{name}}MO', 434 + // name: 'options', 435 + }, 436 + // name: '@tanstack/react-query', 437 + queryKeys: { 438 + // name: '{{name}}QK', 439 + // name: 'options', 440 + tags: true, 441 + }, 442 + // queryOptions: false, 443 + queryOptions: { 444 + // meta() { 445 + // return { 446 + // custom: 'value', 447 + // } 448 + // }, 449 + // name: '{{name}}QO', 450 + // name: 'options', 451 + }, 452 + useQuery: true, 453 + '~hooks': { 454 + operations: { 455 + getKind: (op) => { 456 + if (op.method === 'post' && op.path === '/search') { 457 + return ['query']; 458 + } 459 + return; 460 + }, 461 + isMutation() { 462 + // noop 463 + }, 464 + isQuery: () => { 465 + // noop 466 + }, 467 + }, 468 + }, 469 + }, 470 + { 471 + // name: 'arktype', 472 + types: { 473 + infer: true, 474 + }, 475 + }, 476 + { 477 + // case: 'SCREAMING_SNAKE_CASE', 478 + // comments: false, 479 + // definitions: 'z{{name}}', 480 + exportFromIndex: true, 481 + // metadata: true, 482 + // name: 'valibot', 483 + // requests: { 484 + // case: 'PascalCase', 485 + // name: '{{name}}Data', 486 + // }, 487 + // responses: { 488 + // // case: 'snake_case', 489 + // name: 'z{{name}}TestResponse', 490 + // }, 491 + // webhooks: { 492 + // name: 'q{{name}}CoolWebhook', 493 + // }, 494 + '~hooks': { 495 + events: { 496 + // 'symbol:register:after': ({ plugin, symbol }) => { 497 + // console.log(`(${plugin.name}) registered:`, symbol.id); 498 + // }, 499 + // 'symbol:register:before': ({ plugin, symbol }) => { 500 + // console.log(`(${plugin.name}):`, symbol.name); 501 + // }, 502 + }, 503 + symbols: { 504 + // getFilePath: (symbol) => { 505 + // if (symbol.name) { 506 + // return utils.toCase(symbol.name, 'camelCase'); 507 + // } 508 + // return; 509 + // }, 510 + }, 511 + }, 512 + '~resolvers': { 513 + // number(ctx) { 514 + // const { $, plugin, symbols } = ctx; 515 + // const { v } = symbols; 516 + // // ctx.nodes.base = () => { 517 + // // // implement custom base number resolver 518 + // // } 519 + // const big = plugin.symbolOnce('Big', { 520 + // external: 'big.js', 521 + // importKind: 'default', 522 + // }); 523 + // return $(v).attr('instance').call(big); 524 + // }, 525 + object(ctx) { 526 + const { $, symbols } = ctx; 527 + const { v } = symbols; 528 + const additional = ctx.nodes.additionalProperties(ctx); 529 + if (additional === undefined) { 530 + const shape = ctx.nodes.shape(ctx); 531 + ctx.nodes.base = () => $(v).attr('looseObject').call(shape); 532 + } 533 + }, 534 + // string(ctx) { 535 + // const { $, schema, symbols } = ctx; 536 + // const { v } = symbols; 537 + // if (schema.format === 'date' || schema.format === 'date-time') { 538 + // ctx.nodes.format = () => $(v).attr('isoDateTime').call(); 539 + // } 540 + // }, 541 + // validator(ctx) { 542 + // const { $, plugin, symbols } = ctx; 543 + // const { schema, v } = symbols; 544 + // const vShadow = plugin.symbol('v'); 545 + // const test = plugin.symbol('test'); 546 + // const e = plugin.symbol('err'); 547 + // return [ 548 + // $.const(vShadow).assign($.literal('hi')), 549 + // $('console').attr('log').call(vShadow), 550 + // $.try( 551 + // $.const(test).assign($.literal('test')), 552 + // $('console').attr('log').call($.literal('hi'), test), 553 + // ).catchArg(e), 554 + // $.const('parsed').assign( 555 + // $(v).attr('safeParseAsync').call(schema, 'data').await(), 556 + // ), 557 + // $('parsed').return(), 558 + // ]; 559 + // }, 560 + }, 561 + }, 562 + { 563 + // case: 'snake_case', 564 + // comments: false, 565 + compatibilityVersion: 'mini', 566 + dates: { 567 + // local: true, 568 + // offset: true, 569 + }, 570 + definitions: { 571 + // name: 'z{{name}}Definition', 572 + // types: { 573 + // infer: 'D{{name}}ZodType', 574 + // }, 575 + }, 576 + exportFromIndex: true, 577 + metadata: true, 578 + // name: 'zod', 579 + // requests: { 580 + // // case: 'SCREAMING_SNAKE_CASE', 581 + // // name: 'z{{name}}TestData', 582 + // types: { 583 + // infer: 'E{{name}}DataZodType', 584 + // }, 585 + // }, 586 + responses: { 587 + // case: 'snake_case', 588 + // name: (name) => { 589 + // if (name === 'complexTypes') { 590 + // return 'z'; 591 + // } 592 + // return 'z{{name}}Response'; 593 + // }, 594 + // types: { 595 + // infer: 'F{{name}}ResponseZodType', 596 + // }, 597 + }, 598 + types: { 599 + // infer: { 600 + // case: 'snake_case', 601 + // }, 602 + }, 603 + '~hooks': { 604 + symbols: { 605 + // getFilePath: (symbol) => { 606 + // if (symbol.name === 'z') { 607 + // return 'complexService'; 608 + // } 609 + // return; 610 + // }, 611 + }, 612 + }, 613 + '~resolvers': { 614 + // number(ctx) { 615 + // const { $, plugin, symbols } = ctx; 616 + // const { z } = symbols; 617 + // // ctx.nodes.base = () => { 618 + // // // implement custom base number resolver 619 + // // } 620 + // const big = plugin.symbolOnce('Big', { 621 + // external: 'big.js', 622 + // importKind: 'default', 623 + // }); 624 + // return $(z).attr('instanceof').call(big); 625 + // }, 626 + // object(ctx) { 627 + // const { $, symbols } = ctx; 628 + // const { z } = symbols; 629 + // const additional = ctx.nodes.additionalProperties(ctx); 630 + // if (additional === undefined) { 631 + // const shape = ctx.nodes.shape(ctx); 632 + // // return $('z').attr('object').call(shape).attr('passthrough').call() 633 + // ctx.nodes.base = () => 634 + // $(z).attr('object').call(shape).attr('strict').call(); 635 + // } 636 + // }, 637 + // string(ctx) { 638 + // const { $, schema, symbols } = ctx; 639 + // const { z } = symbols; 640 + // if (schema.format === 'date' || schema.format === 'date-time') { 641 + // ctx.nodes.format = () => $(z).attr('date').call(); 642 + // } 643 + // if (schema.format === 'int64') { 644 + // ctx.nodes.format = () => 645 + // $(z) 646 + // .attr('string') 647 + // .call() 648 + // .attr('refine') 649 + // .call( 650 + // $.func() 651 + // .param('val') 652 + // .do( 653 + // $.try( 654 + // $(z) 655 + // .attr('int64') 656 + // .call() 657 + // .attr('parse') 658 + // .call($('BigInt').call('val')), 659 + // $.return($.literal(true)), 660 + // ).catch($.return($.literal(false))), 661 + // ), 662 + // $.object().prop( 663 + // 'message', 664 + // $.literal('Must be a valid int64 string'), 665 + // ), 666 + // ); 667 + // } 668 + // }, 669 + // validator({ $, schema }) { 670 + // return [ 671 + // $.const('parsed').assign( 672 + // $(schema).attr('safeParseAsync').call('data').await(), 673 + // ), 674 + // $('parsed').return(), 675 + // ]; 676 + // }, 677 + }, 678 + }, 679 + { 680 + exportFromIndex: true, 681 + // name: '@hey-api/schemas', 682 + // type: 'json', 683 + }, 684 + { 685 + exportFromIndex: true, 686 + httpRequests: { 687 + // enabled: false, 688 + // containerName: 'AngularTest', 689 + // segmentName: '{{name}}Seggy', 690 + // strategy: 'byTags', 691 + }, 692 + httpResources: { 693 + // enabled: false, 694 + // segmentName: '{{name}}Seggy', 695 + strategy: 'byTags', 696 + }, 697 + // name: '@angular/common', 698 + }, 699 + { 700 + exportFromIndex: true, 701 + // mutationOptions: '{{name}}Mutationssss', 702 + // name: '@pinia/colada', 703 + // queryOptions: { 704 + // name: '{{name}}Queryyyyy', 705 + // }, 706 + queryKeys: { 707 + tags: true, 708 + }, 709 + '~hooks': { 710 + operations: { 711 + getKind: (op) => { 712 + if (op.method === 'post' && op.path === '/search') { 713 + return ['query']; 714 + } 715 + return; 716 + }, 717 + }, 718 + }, 719 + }, 720 + ], 721 + // watch: 3_000, 722 + }, 723 + // { 724 + // input: 'scalar:@scalar/access-service', 725 + // logs: { 726 + // // level: 'debug', 727 + // path: './logs', 728 + // }, 729 + // output: '.gen', 730 + // }, 731 + ]; 732 + });
+2 -3
dev/openapi-ts.config.ts
··· 30 30 // ... 31 31 return [ 32 32 { 33 - // experimentalParser: false, 34 33 input: [ 35 34 { 36 35 // fetch: { ··· 108 107 // level: 'debug', 109 108 path: './logs', 110 109 }, 111 - // name: 'foo', 112 110 output: [ 113 111 { 114 112 // case: 'snake_case', ··· 478 476 // definitions: 'z{{name}}', 479 477 exportFromIndex: true, 480 478 // metadata: true, 481 - // name: 'valibot', 479 + name: 'valibot', 482 480 // requests: { 483 481 // case: 'PascalCase', 484 482 // name: '{{name}}Data', ··· 527 525 const additional = ctx.nodes.additionalProperties(ctx); 528 526 if (additional === undefined) { 529 527 const shape = ctx.nodes.shape(ctx); 528 + shape.prop('body', $(v).attr('never').call()); 530 529 ctx.nodes.base = () => $(v).attr('looseObject').call(shape); 531 530 } 532 531 },
+1
dev/package.json
··· 13 13 "@angular/common": "19.2.17", 14 14 "@angular/core": "19.2.17", 15 15 "@hey-api/codegen-core": "workspace:*", 16 + "@hey-api/openapi-python": "workspace:*", 16 17 "@hey-api/openapi-ts": "workspace:*", 17 18 "@opencode-ai/sdk": "1.0.221", 18 19 "@pinia/colada": "0.19.1",
+1 -1
docs/openapi-ts/configuration.md
··· 242 242 243 243 ## API 244 244 245 - You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/types/config.d.ts) interface. 245 + You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/config/types.d.ts) interface. 246 246 247 247 <!--@include: ../partials/examples.md--> 248 248 <!--@include: ../partials/sponsors.md-->
+2 -8
docs/openapi-ts/get-started.md
··· 70 70 71 71 ### Versioning 72 72 73 - This package does NOT follow the [semantic versioning](https://semver.org/) strategy. Please pin an exact version so you can safely upgrade when you're ready. 74 - 75 - Due to the nature of the package, we use the following versioning strategy. 76 - 77 - - `1.x.x`: significant breaking changes, reserved for v1 release 78 - - `x.1.x`: breaking changes 79 - - `x.x.1`: new features, bug fixes, and non-breaking changes 73 + This package is in [initial development](https://semver.org/#spec-item-4). Please pin an exact version so you can safely upgrade when you're ready. 80 74 81 - We publish [migration notes](/openapi-ts/migrating) for every breaking release. You might not be impacted by a breaking release if you don't use the affected plugin(s). 75 + We publish [migration notes](/openapi-ts/migrating) for every breaking release. You might not be impacted by a breaking change if you don't use the affected features. 82 76 83 77 ## Usage 84 78
+22
packages/codegen-core/CHANGELOG.md
··· 1 1 # @hey-api/codegen-core 2 2 3 + ## 0.5.5 4 + 5 + ### Patch Changes 6 + 7 + - **config**: export `loadConfigFile` function (moved from `@hey-api/openapi-ts`) ([#3244](https://github.com/hey-api/openapi-ts/pull/3244)) ([`4f52bce`](https://github.com/hey-api/openapi-ts/commit/4f52bce79d8ac6573472f32a05b7f70913b90605)) by [@mrlubos](https://github.com/mrlubos) 8 + 9 + ### Updated Dependencies: 10 + 11 + - @hey-api/types@0.1.2 12 + 13 + ## 0.5.4 14 + 15 + ### Patch Changes 16 + 17 + - **internal**: move logger to codegen-core ([#3235](https://github.com/hey-api/openapi-ts/pull/3235)) ([`88532f0`](https://github.com/hey-api/openapi-ts/commit/88532f01200e1111818e82a4af7bd5cf8cf26e3b)) by [@mrlubos](https://github.com/mrlubos) 18 + 19 + ## 0.5.3 20 + 21 + ### Patch Changes 22 + 23 + - **deps**: move @hey-api/types to dependencies to fix broken types ([#3232](https://github.com/hey-api/openapi-ts/pull/3232)) ([`edfce48`](https://github.com/hey-api/openapi-ts/commit/edfce4875cb9279d3ab8bfd438ff177e8ba7845c)) by [@mrlubos](https://github.com/mrlubos) 24 + 3 25 ## 0.5.2 4 26 5 27 ### Patch Changes
+2 -1
packages/codegen-core/package.json
··· 1 1 { 2 2 "name": "@hey-api/codegen-core", 3 - "version": "0.5.2", 3 + "version": "0.5.5", 4 4 "description": "🧱 TypeScript framework for generating files.", 5 5 "homepage": "https://heyapi.dev/", 6 6 "repository": { ··· 63 63 "dependencies": { 64 64 "@hey-api/types": "workspace:*", 65 65 "ansi-colors": "4.1.3", 66 + "c12": "3.3.3", 66 67 "color-support": "1.1.3" 67 68 }, 68 69 "peerDependencies": {
+4
packages/codegen-core/src/__tests__/exports.test.ts
··· 8 8 'File', 9 9 'fromRef', 10 10 'fromRefs', 11 + 'detectInteractiveSession', 11 12 'isNode', 12 13 'isNodeRef', 13 14 'isRef', 14 15 'isSymbol', 15 16 'isSymbolRef', 17 + 'loadConfigFile', 16 18 'log', 19 + 'Logger', 20 + 'mergeConfigs', 17 21 'nodeBrand', 18 22 'Project', 19 23 'ref',
+14
packages/codegen-core/src/config/interactive.ts
··· 1 + /** 2 + * Detect if the current session is interactive based on TTY status and environment variables. 3 + * This is used as a fallback when the user doesn't explicitly set the interactive option. 4 + * @internal 5 + */ 6 + export function detectInteractiveSession(): boolean { 7 + return Boolean( 8 + process.stdin.isTTY && 9 + process.stdout.isTTY && 10 + !process.env.CI && 11 + !process.env.NO_INTERACTIVE && 12 + !process.env.NO_INTERACTION, 13 + ); 14 + }
+42
packages/codegen-core/src/config/load.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + import type { AnyObject, MaybeArray } from '@hey-api/types'; 3 + 4 + import { mergeConfigs } from './merge'; 5 + 6 + export async function loadConfigFile<T extends AnyObject>({ 7 + configFile, 8 + logger, 9 + name, 10 + userConfig, 11 + }: { 12 + configFile: string | undefined; 13 + logger: Logger; 14 + name: string; 15 + userConfig: T; 16 + }): Promise<{ 17 + configFile: string | undefined; 18 + configs: ReadonlyArray<T>; 19 + foundConfig: boolean; 20 + }> { 21 + const eventC12 = logger.timeEvent('c12'); 22 + // c12 is ESM-only since v3 23 + const { loadConfig } = await import('c12'); 24 + 25 + const { config: fileConfig, configFile: loadedConfigFile } = await loadConfig< 26 + MaybeArray<T> 27 + >({ 28 + configFile, 29 + name, 30 + }); 31 + eventC12.timeEnd(); 32 + 33 + const fileConfigs = fileConfig instanceof Array ? fileConfig : [fileConfig]; 34 + const mergedConfigs = fileConfigs.map((config) => 35 + mergeConfigs<T>(config, userConfig), 36 + ); 37 + const foundConfig = fileConfigs.some( 38 + (config) => Object.keys(config).length > 0, 39 + ); 40 + 41 + return { configFile: loadedConfigFile, configs: mergedConfigs, foundConfig }; 42 + }
+28
packages/codegen-core/src/config/merge.ts
··· 1 + import type { AnyObject } from '@hey-api/types'; 2 + 3 + function isPlainObject(value: unknown): value is AnyObject { 4 + return typeof value === 'object' && value !== null && !Array.isArray(value); 5 + } 6 + 7 + export function mergeConfigs<T extends AnyObject>( 8 + configA: T | undefined, 9 + configB: T | undefined, 10 + ): T { 11 + const a = (configA || {}) as AnyObject; 12 + const b = (configB || {}) as AnyObject; 13 + 14 + const result: AnyObject = { ...a }; 15 + 16 + for (const key of Object.keys(b)) { 17 + const valueA = a[key]; 18 + const valueB = b[key]; 19 + 20 + if (isPlainObject(valueA) && isPlainObject(valueB)) { 21 + result[key] = mergeConfigs(valueA, valueB); 22 + } else { 23 + result[key] = valueB; 24 + } 25 + } 26 + 27 + return result as T; 28 + }
+4
packages/codegen-core/src/index.ts
··· 5 5 ImportModule, 6 6 } from './bindings'; 7 7 export { nodeBrand, symbolBrand } from './brands'; 8 + export { detectInteractiveSession } from './config/interactive'; 9 + export { loadConfigFile } from './config/load'; 10 + export { mergeConfigs } from './config/merge'; 8 11 export type { 9 12 IProjectRenderMeta as ProjectRenderMeta, 10 13 ISymbolMeta as SymbolMeta, ··· 20 23 NameConflictResolvers, 21 24 } from './languages/types'; 22 25 export { log } from './log'; 26 + export { Logger } from './logger'; 23 27 export type { 24 28 INode as Node, 25 29 NodeName,
+10
packages/openapi-python/CHANGELOG.md
··· 1 + # @hey-api/openapi-python 2 + 3 + ## 0.0.2 4 + 5 + ### Patch Changes 6 + 7 + ### Updated Dependencies: 8 + 9 + - @hey-api/types@0.1.2 10 + - @hey-api/codegen-core@0.5.5
+2 -3
packages/openapi-python/package.json
··· 1 1 { 2 2 "name": "@hey-api/openapi-python", 3 - "version": "0.0.1", 3 + "version": "0.0.2", 4 4 "private": true, 5 5 "description": "🐍 OpenAPI to Python codegen.", 6 6 "homepage": "https://heyapi.dev/", ··· 73 73 "node": ">=20.19.0" 74 74 }, 75 75 "dependencies": { 76 - "@hey-api/codegen-core": "workspace:^0.5.2", 76 + "@hey-api/codegen-core": "workspace:^0.5.5", 77 77 "@hey-api/json-schema-ref-parser": "1.2.2", 78 78 "@hey-api/types": "workspace:*", 79 79 "ansi-colors": "4.1.3", 80 - "c12": "3.3.3", 81 80 "color-support": "1.1.3", 82 81 "commander": "14.0.2", 83 82 "open": "11.0.0",
-120
packages/openapi-python/src/cli.ts
··· 1 - import type { OptionValues } from 'commander'; 2 - import { Command } from 'commander'; 3 - 4 - // import { createClient } from '~/index'; 5 - import pkg from '../package.json' assert { type: 'json' }; 6 - 7 - const stringToBoolean = ( 8 - value: string | undefined, 9 - ): boolean | string | undefined => { 10 - if (value === 'true') return true; 11 - if (value === 'false') return false; 12 - return value; 13 - }; 14 - 15 - const processParams = ( 16 - obj: OptionValues, 17 - booleanKeys: ReadonlyArray<string>, 18 - ): OptionValues => { 19 - for (const key of booleanKeys) { 20 - const value = obj[key]; 21 - if (typeof value === 'string') { 22 - const parsedValue = stringToBoolean(value); 23 - delete obj[key]; 24 - obj[key] = parsedValue; 25 - } 26 - } 27 - return obj; 28 - }; 29 - 30 - export const runCli = async (): Promise<void> => { 31 - const params = new Command() 32 - .name(Object.keys(pkg.bin)[0]!) 33 - .usage('[options]') 34 - .version(pkg.version) 35 - .option('-c, --client <value>', 'HTTP client to generate') 36 - .option('-d, --debug', 'Set log level to debug') 37 - .option('--dry-run [value]', 'Skip writing files to disk?') 38 - .option('-f, --file [value]', 'Path to the config file') 39 - .option( 40 - '-i, --input <value>', 41 - 'OpenAPI specification (path, url, or string content)', 42 - ) 43 - .option('-l, --logs [value]', 'Logs folder') 44 - .option('-o, --output <value>', 'Output folder') 45 - .option('-p, --plugins [value...]', "List of plugins you'd like to use") 46 - .option('-s, --silent', 'Set log level to silent') 47 - .option( 48 - '--no-log-file', 49 - 'Disable writing a log file. Works like --silent but without suppressing console output', 50 - ) 51 - .option( 52 - '-w, --watch [value]', 53 - 'Regenerate the client when the input file changes?', 54 - ) 55 - .parse(process.argv) 56 - .opts(); 57 - 58 - let userConfig: Record<string, unknown>; 59 - 60 - try { 61 - userConfig = processParams(params, ['dryRun', 'logFile']); 62 - 63 - if (userConfig.file) { 64 - userConfig.configFile = userConfig.file; 65 - delete userConfig.file; 66 - } 67 - 68 - if (params.plugins === true) { 69 - userConfig.plugins = []; 70 - } else if (params.plugins) { 71 - userConfig.plugins = params.plugins; 72 - } else if (userConfig.client) { 73 - userConfig.plugins = ['@hey-api/sdk']; 74 - } 75 - 76 - if (userConfig.client) { 77 - (userConfig.plugins as Array<string>).push(userConfig.client as string); 78 - delete userConfig.client; 79 - } 80 - 81 - userConfig.logs = userConfig.logs 82 - ? { 83 - path: userConfig.logs, 84 - } 85 - : {}; 86 - 87 - if (userConfig.debug) { 88 - (userConfig.logs as Record<string, unknown>).level = 'debug'; 89 - delete userConfig.debug; 90 - } else if (userConfig.silent) { 91 - (userConfig.logs as Record<string, unknown>).level = 'silent'; 92 - delete userConfig.silent; 93 - } 94 - 95 - (userConfig.logs as Record<string, unknown>).file = userConfig.logFile; 96 - delete userConfig.logFile; 97 - 98 - if (typeof params.watch === 'string') { 99 - userConfig.watch = Number.parseInt(params.watch, 10); 100 - } 101 - 102 - if (!Object.keys(userConfig.logs as Record<string, unknown>).length) { 103 - delete userConfig.logs; 104 - } 105 - 106 - // const context = await createClient( 107 - // userConfig as unknown as Required<Parameters<typeof createClient>>[0], 108 - // ); 109 - // if ( 110 - // !context[0]?.config.input.some( 111 - // (input) => input.watch && input.watch.enabled, 112 - // ) 113 - // ) { 114 - // process.exit(0); 115 - // } 116 - process.exit(0); 117 - } catch { 118 - process.exit(1); 119 - } 120 - };
+40
packages/openapi-python/src/cli/adapter.ts
··· 1 + // import type { ToArray } from '@hey-api/types'; 2 + 3 + import type { UserConfig } from '~/config/types'; 4 + 5 + import type { CliOptions } from './schema'; 6 + 7 + export const cliToConfig = (cli: CliOptions): Partial<UserConfig> => { 8 + const config: Partial<UserConfig> = {}; 9 + 10 + if (cli.input) config.input = cli.input; 11 + if (cli.output) config.output = cli.output; 12 + if (cli.file) config.configFile = cli.file; 13 + if (cli.dryRun !== undefined) config.dryRun = cli.dryRun; 14 + 15 + // const plugins: ToArray<UserConfig['plugins']> = []; 16 + // if (cli.plugins instanceof Array && cli.plugins.length > 0) { 17 + // plugins.push(...cli.plugins); 18 + // } 19 + // if (cli.client) plugins.push(cli.client); 20 + // if (plugins.length > 0) config.plugins = plugins; 21 + 22 + if (cli.debug || cli.silent || cli.logs || cli.logFile !== undefined) { 23 + config.logs = { 24 + ...(cli.logs && { path: cli.logs }), 25 + ...(cli.debug && { level: 'debug' as const }), 26 + ...(cli.silent && { level: 'silent' as const }), 27 + ...(cli.logFile !== undefined && { file: cli.logFile }), 28 + }; 29 + } 30 + 31 + if (cli.watch !== undefined) { 32 + if (typeof cli.watch === 'string') { 33 + config.watch = Number.parseInt(cli.watch, 10); 34 + } else { 35 + config.watch = cli.watch; 36 + } 37 + } 38 + 39 + return config; 40 + };
+67
packages/openapi-python/src/cli/index.ts
··· 1 + import { Command, CommanderError } from 'commander'; 2 + 3 + import { createClient } from '~/index'; 4 + 5 + import pkg from '../../package.json' assert { type: 'json' }; 6 + import { cliToConfig } from './adapter'; 7 + 8 + const binName = Object.keys(pkg.bin)[0]!; 9 + 10 + const program = new Command() 11 + .name(binName) 12 + .description('Generate Python code from OpenAPI specifications') 13 + .version(pkg.version); 14 + 15 + program 16 + .option( 17 + '-i, --input <path...>', 18 + 'OpenAPI specification (path, URL, or string)', 19 + ) 20 + .option('-o, --output <path...>', 'Output folder(s)') 21 + .option('-c, --client <name>', 'HTTP client to generate') 22 + .option('-p, --plugins [names...]', 'Plugins to use') 23 + .option('-f, --file <path>', 'Path to config file') 24 + .option('-d, --debug', 'Enable debug logging') 25 + .option('-s, --silent', 'Suppress all output') 26 + .option('-l, --logs <path>', 'Logs folder path') 27 + .option('--no-log-file', 'Disable log file output') 28 + .option('--dry-run', 'Skip writing files') 29 + .option('-w, --watch [interval]', 'Watch for changes') 30 + .action(async (options) => { 31 + const config = cliToConfig(options); 32 + 33 + const context = await createClient( 34 + config as Parameters<typeof createClient>[0], 35 + ); 36 + 37 + const hasActiveWatch = context[0]?.config.input.some( 38 + (input) => input.watch?.enabled, 39 + ); 40 + 41 + if (!hasActiveWatch) { 42 + process.exit(0); 43 + } 44 + }); 45 + 46 + export async function runCli(): Promise<void> { 47 + try { 48 + await program.parseAsync(process.argv); 49 + } catch (error) { 50 + if (error instanceof CommanderError && 'code' in error) { 51 + if (error.code === 'commander.optionMissingArgument') { 52 + console.error( 53 + `\nMissing required argument. Run '${binName} --help' for usage.\n`, 54 + ); 55 + } else if (error.code === 'commander.unknownOption') { 56 + console.error( 57 + `\nUnknown option. Run '${binName} --help' for available options.\n`, 58 + ); 59 + } 60 + 61 + process.exit(error.exitCode); 62 + } 63 + 64 + console.error('Unexpected error:', error); 65 + process.exit(1); 66 + } 67 + }
+17
packages/openapi-python/src/cli/schema.ts
··· 1 + import type { MaybeArray } from '@hey-api/types'; 2 + 3 + // import type { PluginClientNames, PluginNames } from "~/plugins/types"; 4 + 5 + export interface CliOptions { 6 + // client?: PluginClientNames; 7 + debug?: boolean; 8 + dryRun?: boolean; 9 + file?: string; 10 + input?: MaybeArray<string>; 11 + logFile?: boolean; 12 + logs?: string; 13 + output?: MaybeArray<string>; 14 + // plugins?: ReadonlyArray<PluginNames>; 15 + silent?: boolean; 16 + watch?: boolean | string; 17 + }
+12 -1
packages/openapi-python/src/config/types.d.ts
··· 1 - // import { MaybeArray } from "@hey-api/types"; 1 + import type { MaybeArray } from '@hey-api/types'; 2 2 3 3 export interface UserConfig { 4 4 /** ··· 28 28 * generate multiple outputs, one for each input. 29 29 */ 30 30 // input: MaybeArray<UserInput | Required<UserInput>['path']>; 31 + input: MaybeArray<string>; 31 32 /** 32 33 * Show an interactive error reporting tool when the program crashes? You 33 34 * generally want to keep this disabled (default). ··· 41 42 * @default process.cwd() 42 43 */ 43 44 // logs?: string | Logs; 45 + logs?: 46 + | string 47 + | { 48 + level?: 'debug' | 'info' | 'warn' | 'error' | 'silent'; 49 + }; 44 50 /** 45 51 * Path to the output folder. 46 52 * ··· 48 54 * generate multiple outputs, one for each input. 49 55 */ 50 56 // output: MaybeArray<string | UserOutput>; 57 + output: MaybeArray<string>; 51 58 /** 52 59 * Customize how the input is parsed and transformed before it's passed to 53 60 * plugins. ··· 68 75 // }; 69 76 // }[PluginNames] 70 77 // >; 78 + /** 79 + * @deprecated use `input.watch` instead 80 + */ 81 + watch?: boolean | number; 71 82 }
+135
packages/openapi-python/src/generate.ts
··· 1 + import { Logger } from '@hey-api/codegen-core'; 2 + import type { LazyOrAsync, MaybeArray } from '@hey-api/types'; 3 + 4 + // import { checkNodeVersion } from '~/config/engine'; 5 + // import type { Configs } from '~/config/init'; 6 + // import { initConfigs } from '~/config/init'; 7 + // import { getLogs } from '~/config/logs'; 8 + import type { UserConfig } from '~/config/types'; 9 + // import { createClient as pCreateClient } from '~/createClient'; 10 + // import { 11 + // ConfigValidationError, 12 + // JobError, 13 + // logCrashReport, 14 + // openGitHubIssueWithCrashReport, 15 + // printCrashReport, 16 + // shouldReportCrash, 17 + // } from '~/error'; 18 + // import type { Context } from '~/ir/context'; 19 + // import { printCliIntro } from '~/utils/cli'; 20 + 21 + type Context = { 22 + config: { 23 + input: any[]; 24 + }; 25 + }; 26 + 27 + /** 28 + * Generate a client from the provided configuration. 29 + * 30 + * @param userConfig User provided {@link UserConfig} configuration(s). 31 + */ 32 + export const createClient = async ( 33 + userConfig?: LazyOrAsync<MaybeArray<UserConfig>>, 34 + logger = new Logger(), 35 + ): Promise<ReadonlyArray<Context>> => { 36 + const resolvedConfig = 37 + typeof userConfig === 'function' ? await userConfig() : userConfig; 38 + const userConfigs = resolvedConfig 39 + ? resolvedConfig instanceof Array 40 + ? resolvedConfig 41 + : [resolvedConfig] 42 + : []; 43 + 44 + console.log(userConfigs, logger); 45 + return []; 46 + // let rawLogs = userConfigs.find( 47 + // (config) => getLogs(config).level !== 'silent', 48 + // )?.logs; 49 + // if (typeof rawLogs === 'string') { 50 + // rawLogs = getLogs({ logs: rawLogs }); 51 + // } 52 + 53 + // let configs: Configs | undefined; 54 + 55 + // try { 56 + // checkNodeVersion(); 57 + 58 + // const eventCreateClient = logger.timeEvent('createClient'); 59 + 60 + // const eventConfig = logger.timeEvent('config'); 61 + // configs = await initConfigs({ logger, userConfigs }); 62 + // const printIntro = configs.results.some( 63 + // (result) => result.config.logs.level !== 'silent', 64 + // ); 65 + // if (printIntro) { 66 + // printCliIntro(); 67 + // } 68 + // eventConfig.timeEnd(); 69 + 70 + // const allConfigErrors = configs.results.flatMap((result) => 71 + // result.errors.map((error) => ({ error, jobIndex: result.jobIndex })), 72 + // ); 73 + // if (allConfigErrors.length) { 74 + // throw new ConfigValidationError(allConfigErrors); 75 + // } 76 + 77 + // const clients = await Promise.all( 78 + // configs.results.map(async (result) => { 79 + // try { 80 + // return await pCreateClient({ 81 + // config: result.config, 82 + // dependencies: configs!.dependencies, 83 + // jobIndex: result.jobIndex, 84 + // logger, 85 + // }); 86 + // } catch (error) { 87 + // throw new JobError('', { 88 + // error, 89 + // jobIndex: result.jobIndex, 90 + // }); 91 + // } 92 + // }), 93 + // ); 94 + // const result = clients.filter((client) => 95 + // Boolean(client), 96 + // ) as ReadonlyArray<Context>; 97 + 98 + // eventCreateClient.timeEnd(); 99 + 100 + // const printLogs = configs.results.some( 101 + // (result) => result.config.logs.level === 'debug', 102 + // ); 103 + // logger.report(printLogs); 104 + 105 + // return result; 106 + // } catch (error) { 107 + // const results = configs?.results ?? []; 108 + 109 + // const logs = 110 + // results.find((result) => result.config.logs.level !== 'silent')?.config 111 + // .logs ?? 112 + // results[0]?.config.logs ?? 113 + // rawLogs; 114 + // const dryRun = 115 + // results.some((result) => result.config.dryRun) ?? 116 + // userConfigs.some((config) => config.dryRun) ?? 117 + // false; 118 + // const logPath = 119 + // logs?.file && !dryRun 120 + // ? logCrashReport(error, logs.path ?? '') 121 + // : undefined; 122 + // if (!logs || logs.level !== 'silent') { 123 + // printCrashReport({ error, logPath }); 124 + // const isInteractive = 125 + // results.some((result) => result.config.interactive) ?? 126 + // userConfigs.some((config) => config.interactive) ?? 127 + // false; 128 + // if (await shouldReportCrash({ error, isInteractive })) { 129 + // await openGitHubIssueWithCrashReport(error); 130 + // } 131 + // } 132 + 133 + // throw error; 134 + // } 135 + };
+8 -13
packages/openapi-python/src/index.ts
··· 76 76 77 77 colors.enabled = colorSupport().hasBasic; 78 78 79 - // export { createClient } from '~/generate'; 79 + export { createClient } from '~/generate'; 80 80 81 81 /** 82 - * Type helper for openapi-ts.config.ts, returns {@link MaybeArray<UserConfig>} object(s) 82 + * Type helper for configuration object, returns {@link MaybeArray<UserConfig>} object(s) 83 83 */ 84 - export const defineConfig = async <T extends MaybeArray<UserConfig>>( 84 + export async function defineConfig<T extends MaybeArray<UserConfig>>( 85 85 config: LazyOrAsync<T>, 86 - ): Promise<T> => (typeof config === 'function' ? await config() : config); 86 + ): Promise<T> { 87 + return typeof config === 'function' ? await config() : config; 88 + } 87 89 90 + export { Logger } from '@hey-api/codegen-core'; 88 91 // export { defaultPaginationKeywords } from '~/config/parser'; 89 92 // export { defaultPlugins } from '~/config/plugins'; 93 + export type { UserConfig } from '~/config/types'; 90 94 // export type { IR } from '~/ir/types'; 91 95 // export { OperationPath, OperationStrategy } from '~/openApi/shared/locations'; 92 96 // export type { ··· 99 103 // OpenApiSchemaObject, 100 104 // } from '~/openApi/types'; 101 105 // export type { DefinePlugin, Plugin } from '~/plugins'; 102 - // export type { AngularClient } from '~/plugins/@hey-api/client-angular'; 103 - // export type { AxiosClient } from '~/plugins/@hey-api/client-axios'; 104 106 // export { 105 107 // clientDefaultConfig, 106 108 // clientDefaultMeta, ··· 108 110 // export { clientPluginHandler } from '~/plugins/@hey-api/client-core/plugin'; 109 111 // export type { Client } from '~/plugins/@hey-api/client-core/types'; 110 112 // export type { FetchClient } from '~/plugins/@hey-api/client-fetch'; 111 - // export type { NextClient } from '~/plugins/@hey-api/client-next'; 112 - // export type { NuxtClient } from '~/plugins/@hey-api/client-nuxt'; 113 - // export type { OfetchClient } from '~/plugins/@hey-api/client-ofetch'; 114 - // export type { ExpressionTransformer } from '~/plugins/@hey-api/transformers/expressions'; 115 - // export type { TypeTransformer } from '~/plugins/@hey-api/transformers/types'; 116 113 // export { definePluginConfig } from '~/plugins/shared/utils/config'; 117 114 // export * from '~/ts-dsl'; 118 - // export type { UserConfig } from '~/types/config'; 119 115 // export { utils } from '~/utils/exports'; 120 - // export { Logger } from '~/utils/logger';
-1
packages/openapi-ts-tests/main/test/cli.test.ts
··· 15 15 '--output', 16 16 path.resolve(__dirname, '.gen'), 17 17 '--dry-run', 18 - 'true', 19 18 ]); 20 19 expect(result.error).toBeFalsy(); 21 20 expect(result.status).toBe(0);
+39
packages/openapi-ts/CHANGELOG.md
··· 1 1 # @hey-api/openapi-ts 2 2 3 + ## 0.90.9 4 + 5 + ### Patch Changes 6 + 7 + - **ts-dsl**: allow removing object properties by passing `null` ([#3247](https://github.com/hey-api/openapi-ts/pull/3247)) ([`7be1561`](https://github.com/hey-api/openapi-ts/commit/7be1561a862d2bb217703003d5e6cbc4a6a09586)) by [@mrlubos](https://github.com/mrlubos) 8 + 9 + - **cli**: clean up interface ([#3244](https://github.com/hey-api/openapi-ts/pull/3244)) ([`4f52bce`](https://github.com/hey-api/openapi-ts/commit/4f52bce79d8ac6573472f32a05b7f70913b90605)) by [@mrlubos](https://github.com/mrlubos) 10 + 11 + - **ts-dsl**: override object properties when called multiple times with the same name ([#3247](https://github.com/hey-api/openapi-ts/pull/3247)) ([`7be1561`](https://github.com/hey-api/openapi-ts/commit/7be1561a862d2bb217703003d5e6cbc4a6a09586)) by [@mrlubos](https://github.com/mrlubos) 12 + 13 + - **config**: move `loadConfigFile` function to `@hey-api/codegen-core` ([#3244](https://github.com/hey-api/openapi-ts/pull/3244)) ([`4f52bce`](https://github.com/hey-api/openapi-ts/commit/4f52bce79d8ac6573472f32a05b7f70913b90605)) by [@mrlubos](https://github.com/mrlubos) 14 + 15 + ### Updated Dependencies: 16 + 17 + - @hey-api/types@0.1.2 18 + - @hey-api/codegen-core@0.5.5 19 + 20 + ## 0.90.8 21 + 22 + ### Patch Changes 23 + 24 + - **internal**: move logger to codegen-core ([#3235](https://github.com/hey-api/openapi-ts/pull/3235)) ([`88532f0`](https://github.com/hey-api/openapi-ts/commit/88532f01200e1111818e82a4af7bd5cf8cf26e3b)) by [@mrlubos](https://github.com/mrlubos) 25 + 26 + - **cli**: do not show ascii logo on generate command ([#3238](https://github.com/hey-api/openapi-ts/pull/3238)) ([`72e17f3`](https://github.com/hey-api/openapi-ts/commit/72e17f39d6e2d00eff24671e7f2b88801443489a)) by [@mrlubos](https://github.com/mrlubos) 27 + 28 + ### Updated Dependencies: 29 + 30 + - @hey-api/codegen-core@0.5.4 31 + 32 + ## 0.90.7 33 + 34 + ### Patch Changes 35 + 36 + - **deps**: move @hey-api/types to dependencies to fix broken types ([#3232](https://github.com/hey-api/openapi-ts/pull/3232)) ([`edfce48`](https://github.com/hey-api/openapi-ts/commit/edfce4875cb9279d3ab8bfd438ff177e8ba7845c)) by [@mrlubos](https://github.com/mrlubos) 37 + 38 + ### Updated Dependencies: 39 + 40 + - @hey-api/codegen-core@0.5.3 41 + 3 42 ## 0.90.6 4 43 5 44 ### Patch Changes
+2 -8
packages/openapi-ts/README.md
··· 214 214 215 215 ### Versioning 216 216 217 - This package does NOT follow the [semantic versioning](https://semver.org/) strategy. Please pin an exact version so you can safely upgrade when you're ready. 218 - 219 - Due to the nature of the package, we use the following versioning strategy. 220 - 221 - - `1.x.x`: significant breaking changes, reserved for v1 release 222 - - `x.1.x`: breaking changes 223 - - `x.x.1`: new features, bug fixes, and non-breaking changes 217 + This package is in [initial development](https://semver.org/#spec-item-4). Please pin an exact version so you can safely upgrade when you're ready. 224 218 225 - We publish [migration notes](https://heyapi.dev/openapi-ts/migrating) for every breaking release. You might not be impacted by a breaking release if you don't use the affected plugin(s). 219 + We publish [migration notes](https://heyapi.dev/openapi-ts/migrating) for every breaking release. You might not be impacted by a breaking change if you don't use the affected features. 226 220 227 221 ## Usage 228 222
+2 -3
packages/openapi-ts/package.json
··· 1 1 { 2 2 "name": "@hey-api/openapi-ts", 3 - "version": "0.90.6", 3 + "version": "0.90.9", 4 4 "description": "🌀 OpenAPI to TypeScript codegen. Production-ready SDKs, Zod schemas, TanStack Query hooks, and 20+ plugins. Used by Vercel, OpenCode, and PayPal.", 5 5 "homepage": "https://heyapi.dev/", 6 6 "repository": { ··· 89 89 "node": ">=20.19.0" 90 90 }, 91 91 "dependencies": { 92 - "@hey-api/codegen-core": "workspace:^0.5.2", 92 + "@hey-api/codegen-core": "workspace:^0.5.5", 93 93 "@hey-api/json-schema-ref-parser": "1.2.2", 94 94 "@hey-api/types": "workspace:*", 95 95 "ansi-colors": "4.1.3", 96 - "c12": "3.3.3", 97 96 "color-support": "1.1.3", 98 97 "commander": "14.0.2", 99 98 "open": "11.0.0",
+6 -7
packages/openapi-ts/src/__tests__/cli.test.ts
··· 53 53 process.argv = originalArgv; 54 54 } 55 55 expect(spy).toHaveBeenCalledWith({ 56 - input: 'foo.json', 56 + input: ['foo.json'], 57 57 logs: { 58 58 file: true, 59 59 }, 60 - output: 'bar', 60 + output: ['bar'], 61 61 }); 62 62 }); 63 63 ··· 77 77 logs: { 78 78 file: true, 79 79 }, 80 - plugins: [], 81 80 }); 82 81 }); 83 82 ··· 102 101 }); 103 102 }); 104 103 105 - it('with default plugins', async () => { 104 + it('with client plugin', async () => { 106 105 const originalArgv = process.argv.slice(); 107 106 try { 108 107 process.argv = [ ··· 119 118 logs: { 120 119 file: true, 121 120 }, 122 - plugins: ['@hey-api/typescript', '@hey-api/sdk', 'foo'], 121 + plugins: ['foo'], 123 122 }); 124 123 }); 125 124 ··· 214 213 expect(spy).toHaveBeenCalledWith({ 215 214 configFile: 'bar', 216 215 dryRun: true, 217 - input: 'baz', 216 + input: ['baz'], 218 217 logs: { 219 218 file: true, 220 219 path: 'qux', 221 220 }, 222 - output: 'quux', 221 + output: ['quux'], 223 222 plugins: ['foo'], 224 223 watch: true, 225 224 });
+16 -10
packages/openapi-ts/src/__tests__/interactive.test.ts
··· 1 + import { 2 + detectInteractiveSession, 3 + Logger, 4 + mergeConfigs, 5 + } from '@hey-api/codegen-core'; 1 6 import { afterEach, describe, expect, it } from 'vitest'; 2 7 3 - import { detectInteractiveSession, initConfigs } from '~/config/init'; 4 - import { mergeConfigs } from '~/config/merge'; 5 - import { Logger } from '~/utils/logger'; 8 + import { resolveJobs } from '~/config/init'; 6 9 7 10 describe('interactive config', () => { 8 11 it('should use detectInteractiveSession when not provided', async () => { 9 - const result = await initConfigs({ 12 + const result = await resolveJobs({ 10 13 logger: new Logger(), 11 14 userConfigs: [ 12 15 { ··· 17 20 }); 18 21 19 22 // In test environment, TTY is typically not available, so it should be false 20 - expect(result.results[0]?.config.interactive).toBe(false); 23 + expect(result.jobs[0]?.config.interactive).toBe(false); 21 24 }); 22 25 23 26 it('should respect user config when set to true', async () => { 24 - const result = await initConfigs({ 27 + const result = await resolveJobs({ 25 28 logger: new Logger(), 26 29 userConfigs: [ 27 30 { ··· 32 35 ], 33 36 }); 34 37 35 - expect(result.results[0]?.config.interactive).toBe(true); 38 + expect(result.jobs[0]?.config.interactive).toBe(true); 36 39 }); 37 40 38 41 it('should respect user config when set to false', async () => { 39 - const result = await initConfigs({ 42 + const result = await resolveJobs({ 40 43 logger: new Logger(), 41 44 userConfigs: [ 42 45 { ··· 47 50 ], 48 51 }); 49 52 50 - expect(result.results[0]?.config.interactive).toBe(false); 53 + expect(result.jobs[0]?.config.interactive).toBe(false); 51 54 }); 52 55 53 56 it('should allow file config to set interactive when CLI does not provide it', () => { ··· 76 79 }; 77 80 78 81 // After fix: file config's interactive should be preserved 79 - const mergedCorrect = mergeConfigs(fileConfig, cliConfigWithoutInteractive); 82 + const mergedCorrect = mergeConfigs<Partial<typeof fileConfig>>( 83 + fileConfig, 84 + cliConfigWithoutInteractive, 85 + ); 80 86 expect(mergedCorrect.interactive).toBe(false); 81 87 82 88 // Before fix: CLI's auto-detected interactive would override file config
-120
packages/openapi-ts/src/cli.ts
··· 1 - import type { OptionValues } from 'commander'; 2 - import { Command } from 'commander'; 3 - 4 - import { createClient } from '~/index'; 5 - 6 - import pkg from '../package.json' assert { type: 'json' }; 7 - 8 - const stringToBoolean = ( 9 - value: string | undefined, 10 - ): boolean | string | undefined => { 11 - if (value === 'true') return true; 12 - if (value === 'false') return false; 13 - return value; 14 - }; 15 - 16 - const processParams = ( 17 - obj: OptionValues, 18 - booleanKeys: ReadonlyArray<string>, 19 - ): OptionValues => { 20 - for (const key of booleanKeys) { 21 - const value = obj[key]; 22 - if (typeof value === 'string') { 23 - const parsedValue = stringToBoolean(value); 24 - delete obj[key]; 25 - obj[key] = parsedValue; 26 - } 27 - } 28 - return obj; 29 - }; 30 - 31 - export const runCli = async (): Promise<void> => { 32 - const params = new Command() 33 - .name(Object.keys(pkg.bin)[0]!) 34 - .usage('[options]') 35 - .version(pkg.version) 36 - .option('-c, --client <value>', 'HTTP client to generate') 37 - .option('-d, --debug', 'Set log level to debug') 38 - .option('--dry-run [value]', 'Skip writing files to disk?') 39 - .option('-f, --file [value]', 'Path to the config file') 40 - .option( 41 - '-i, --input <value>', 42 - 'OpenAPI specification (path, url, or string content)', 43 - ) 44 - .option('-l, --logs [value]', 'Logs folder') 45 - .option('-o, --output <value>', 'Output folder') 46 - .option('-p, --plugins [value...]', "List of plugins you'd like to use") 47 - .option('-s, --silent', 'Set log level to silent') 48 - .option( 49 - '--no-log-file', 50 - 'Disable writing a log file. Works like --silent but without suppressing console output', 51 - ) 52 - .option( 53 - '-w, --watch [value]', 54 - 'Regenerate the client when the input file changes?', 55 - ) 56 - .parse(process.argv) 57 - .opts(); 58 - 59 - let userConfig: Record<string, unknown>; 60 - 61 - try { 62 - userConfig = processParams(params, ['dryRun', 'logFile']); 63 - 64 - if (userConfig.file) { 65 - userConfig.configFile = userConfig.file; 66 - delete userConfig.file; 67 - } 68 - 69 - if (params.plugins === true) { 70 - userConfig.plugins = []; 71 - } else if (params.plugins) { 72 - userConfig.plugins = params.plugins; 73 - } else if (userConfig.client) { 74 - userConfig.plugins = ['@hey-api/typescript', '@hey-api/sdk']; 75 - } 76 - 77 - if (userConfig.client) { 78 - (userConfig.plugins as Array<string>).push(userConfig.client as string); 79 - delete userConfig.client; 80 - } 81 - 82 - userConfig.logs = userConfig.logs 83 - ? { 84 - path: userConfig.logs, 85 - } 86 - : {}; 87 - 88 - if (userConfig.debug) { 89 - (userConfig.logs as Record<string, unknown>).level = 'debug'; 90 - delete userConfig.debug; 91 - } else if (userConfig.silent) { 92 - (userConfig.logs as Record<string, unknown>).level = 'silent'; 93 - delete userConfig.silent; 94 - } 95 - 96 - (userConfig.logs as Record<string, unknown>).file = userConfig.logFile; 97 - delete userConfig.logFile; 98 - 99 - if (typeof params.watch === 'string') { 100 - userConfig.watch = Number.parseInt(params.watch, 10); 101 - } 102 - 103 - if (!Object.keys(userConfig.logs as Record<string, unknown>).length) { 104 - delete userConfig.logs; 105 - } 106 - 107 - const context = await createClient( 108 - userConfig as unknown as Required<Parameters<typeof createClient>>[0], 109 - ); 110 - if ( 111 - !context[0]?.config.input.some( 112 - (input) => input.watch && input.watch.enabled, 113 - ) 114 - ) { 115 - process.exit(0); 116 - } 117 - } catch { 118 - process.exit(1); 119 - } 120 - };
+40
packages/openapi-ts/src/cli/adapter.ts
··· 1 + import type { ToArray } from '@hey-api/types'; 2 + 3 + import type { UserConfig } from '~/config/types'; 4 + 5 + import type { CliOptions } from './schema'; 6 + 7 + export const cliToConfig = (cli: CliOptions): Partial<UserConfig> => { 8 + const config: Partial<UserConfig> = {}; 9 + 10 + if (cli.input) config.input = cli.input; 11 + if (cli.output) config.output = cli.output; 12 + if (cli.file) config.configFile = cli.file; 13 + if (cli.dryRun !== undefined) config.dryRun = cli.dryRun; 14 + 15 + const plugins: ToArray<UserConfig['plugins']> = []; 16 + if (cli.plugins instanceof Array && cli.plugins.length > 0) { 17 + plugins.push(...cli.plugins); 18 + } 19 + if (cli.client) plugins.push(cli.client); 20 + if (plugins.length > 0) config.plugins = plugins; 21 + 22 + if (cli.debug || cli.silent || cli.logs || cli.logFile !== undefined) { 23 + config.logs = { 24 + ...(cli.logs && { path: cli.logs }), 25 + ...(cli.debug && { level: 'debug' as const }), 26 + ...(cli.silent && { level: 'silent' as const }), 27 + ...(cli.logFile !== undefined && { file: cli.logFile }), 28 + }; 29 + } 30 + 31 + if (cli.watch !== undefined) { 32 + if (typeof cli.watch === 'string') { 33 + config.watch = Number.parseInt(cli.watch, 10); 34 + } else { 35 + config.watch = cli.watch; 36 + } 37 + } 38 + 39 + return config; 40 + };
+67
packages/openapi-ts/src/cli/index.ts
··· 1 + import { Command, CommanderError } from 'commander'; 2 + 3 + import { createClient } from '~/index'; 4 + 5 + import pkg from '../../package.json' assert { type: 'json' }; 6 + import { cliToConfig } from './adapter'; 7 + 8 + const binName = Object.keys(pkg.bin)[0]!; 9 + 10 + const program = new Command() 11 + .name(binName) 12 + .description('Generate TypeScript code from OpenAPI specifications') 13 + .version(pkg.version); 14 + 15 + program 16 + .option( 17 + '-i, --input <path...>', 18 + 'OpenAPI specification (path, URL, or string)', 19 + ) 20 + .option('-o, --output <path...>', 'Output folder(s)') 21 + .option('-c, --client <name>', 'HTTP client to generate') 22 + .option('-p, --plugins [names...]', 'Plugins to use') 23 + .option('-f, --file <path>', 'Path to config file') 24 + .option('-d, --debug', 'Enable debug logging') 25 + .option('-s, --silent', 'Suppress all output') 26 + .option('-l, --logs <path>', 'Logs folder path') 27 + .option('--no-log-file', 'Disable log file output') 28 + .option('--dry-run', 'Skip writing files') 29 + .option('-w, --watch [interval]', 'Watch for changes') 30 + .action(async (options) => { 31 + const config = cliToConfig(options); 32 + 33 + const context = await createClient( 34 + config as Parameters<typeof createClient>[0], 35 + ); 36 + 37 + const hasActiveWatch = context[0]?.config.input.some( 38 + (input) => input.watch?.enabled, 39 + ); 40 + 41 + if (!hasActiveWatch) { 42 + process.exit(0); 43 + } 44 + }); 45 + 46 + export async function runCli(): Promise<void> { 47 + try { 48 + await program.parseAsync(process.argv); 49 + } catch (error) { 50 + if (error instanceof CommanderError && 'code' in error) { 51 + if (error.code === 'commander.optionMissingArgument') { 52 + console.error( 53 + `\nMissing required argument. Run '${binName} --help' for usage.\n`, 54 + ); 55 + } else if (error.code === 'commander.unknownOption') { 56 + console.error( 57 + `\nUnknown option. Run '${binName} --help' for available options.\n`, 58 + ); 59 + } 60 + 61 + process.exit(error.exitCode); 62 + } 63 + 64 + console.error('Unexpected error:', error); 65 + process.exit(1); 66 + } 67 + }
+17
packages/openapi-ts/src/cli/schema.ts
··· 1 + import type { MaybeArray } from '@hey-api/types'; 2 + 3 + import type { PluginClientNames, PluginNames } from '~/plugins/types'; 4 + 5 + export interface CliOptions { 6 + client?: PluginClientNames; 7 + debug?: boolean; 8 + dryRun?: boolean; 9 + file?: string; 10 + input?: MaybeArray<string>; 11 + logFile?: boolean; 12 + logs?: string; 13 + output?: MaybeArray<string>; 14 + plugins?: ReadonlyArray<PluginNames>; 15 + silent?: boolean; 16 + watch?: boolean | string; 17 + }
+1 -2
packages/openapi-ts/src/config/__tests__/input.test.ts
··· 1 1 import { describe, expect, it } from 'vitest'; 2 2 3 - import type { UserConfig } from '~/types/config'; 4 - 5 3 import { getInput } from '../input'; 4 + import type { UserConfig } from '../types'; 6 5 7 6 describe('input config', () => { 8 7 describe('getInput', () => {
+54
packages/openapi-ts/src/config/expand.ts
··· 1 + import colors from 'ansi-colors'; 2 + 3 + import { getInput } from './input'; 4 + import type { UserConfig } from './types'; 5 + 6 + export interface Job { 7 + config: UserConfig; 8 + index: number; 9 + } 10 + 11 + export function expandToJobs( 12 + configs: ReadonlyArray<UserConfig>, 13 + ): ReadonlyArray<Job> { 14 + const jobs: Array<Job> = []; 15 + let jobIndex = 0; 16 + 17 + for (const config of configs) { 18 + const inputs = getInput(config); 19 + const outputs = 20 + config.output instanceof Array ? config.output : [config.output]; 21 + 22 + if (outputs.length === 1) { 23 + jobs.push({ 24 + config: { 25 + ...config, 26 + input: inputs, 27 + output: outputs[0]!, // output array with single item 28 + }, 29 + index: jobIndex++, 30 + }); 31 + } else if (outputs.length > 1 && inputs.length !== outputs.length) { 32 + // Warn and create job per output (all with same inputs) 33 + console.warn( 34 + `⚙️ ${colors.yellow('Warning:')} You provided ${colors.cyan(String(inputs.length))} ${colors.cyan(inputs.length === 1 ? 'input' : 'inputs')} and ${colors.yellow(String(outputs.length))} ${colors.yellow('outputs')}. This will produce identical output in multiple locations. You likely want to provide a single output or the same number of outputs as inputs.`, 35 + ); 36 + for (const output of outputs) { 37 + jobs.push({ 38 + config: { ...config, input: inputs, output }, 39 + index: jobIndex++, 40 + }); 41 + } 42 + } else if (outputs.length > 1) { 43 + // Pair inputs with outputs by index 44 + outputs.forEach((output, index) => { 45 + jobs.push({ 46 + config: { ...config, input: inputs[index]!, output }, 47 + index: jobIndex++, 48 + }); 49 + }); 50 + } 51 + } 52 + 53 + return jobs; 54 + }
+35 -148
packages/openapi-ts/src/config/init.ts
··· 1 - import path from 'node:path'; 2 - 3 - import type { ArrayOnly } from '@hey-api/types'; 4 - import colors from 'ansi-colors'; 5 - 6 - import { ConfigError } from '~/error'; 7 - import type { Config, UserConfig } from '~/types/config'; 8 - import type { Logger } from '~/utils/logger'; 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + import { loadConfigFile } from '@hey-api/codegen-core'; 9 3 10 - import { getInput } from './input'; 11 - import { getLogs } from './logs'; 12 - import { mergeConfigs } from './merge'; 13 - import { getOutput } from './output'; 4 + import { expandToJobs } from './expand'; 14 5 import { getProjectDependencies } from './packages'; 15 - import { getParser } from './parser'; 16 - import { getPlugins } from './plugins'; 17 - 18 - type ConfigResult = { 19 - config: Config; 20 - errors: ReadonlyArray<Error>; 21 - jobIndex: number; 22 - }; 6 + import type { ResolvedJob } from './resolve'; 7 + import { resolveConfig } from './resolve'; 8 + import type { UserConfig } from './types'; 9 + import { validateJobs } from './validate'; 23 10 24 11 export type Configs = { 25 12 dependencies: Record<string, string>; 26 - results: ReadonlyArray<ConfigResult>; 13 + jobs: ReadonlyArray<ResolvedJob>; 14 + /** 15 + * @deprecated Use `jobs` instead. 16 + */ 17 + results: ReadonlyArray<ResolvedJob>; 27 18 }; 28 19 29 20 /** 30 - * Detect if the current session is interactive based on TTY status and environment variables. 31 - * This is used as a fallback when the user doesn't explicitly set the interactive option. 32 21 * @internal 33 22 */ 34 - export const detectInteractiveSession = (): boolean => 35 - Boolean( 36 - process.stdin.isTTY && 37 - process.stdout.isTTY && 38 - !process.env.CI && 39 - !process.env.NO_INTERACTIVE && 40 - !process.env.NO_INTERACTION, 41 - ); 42 - 43 - /** 44 - * @internal 45 - */ 46 - export const initConfigs = async ({ 23 + export async function resolveJobs({ 47 24 logger, 48 25 userConfigs, 49 26 }: { 50 27 logger: Logger; 51 28 userConfigs: ReadonlyArray<UserConfig>; 52 - }): Promise<Configs> => { 29 + }): Promise<Configs> { 53 30 const configs: Array<UserConfig> = []; 54 31 let dependencies: Record<string, string> = {}; 55 32 56 33 const eventLoad = logger.timeEvent('load'); 57 34 for (const userConfig of userConfigs) { 58 - let configurationFile: string | undefined = undefined; 59 - if (userConfig?.configFile) { 35 + let configFile: string | undefined; 36 + if (userConfig.configFile) { 60 37 const parts = userConfig.configFile.split('.'); 61 - configurationFile = parts.slice(0, parts.length - 1).join('.'); 38 + configFile = parts.slice(0, parts.length - 1).join('.'); 62 39 } 63 40 64 - const eventC12 = logger.timeEvent('c12'); 65 - // c12 is ESM-only since v3 66 - const { loadConfig } = await import('c12'); 67 - const { config: configFromFile, configFile: loadedConfigFile } = 68 - await loadConfig<UserConfig>({ 69 - configFile: configurationFile, 70 - name: 'openapi-ts', 71 - }); 72 - eventC12.timeEnd(); 41 + const loaded = await loadConfigFile<UserConfig>({ 42 + configFile, 43 + logger, 44 + name: 'openapi-ts', 45 + userConfig, 46 + }); 73 47 74 48 if (!Object.keys(dependencies).length) { 75 49 // TODO: handle dependencies for multiple configs properly? 76 50 dependencies = getProjectDependencies( 77 - Object.keys(configFromFile).length ? loadedConfigFile : undefined, 51 + loaded.foundConfig ? loaded.configFile : undefined, 78 52 ); 79 53 } 80 54 81 - const mergedConfigs = 82 - configFromFile instanceof Array 83 - ? configFromFile.map((config) => mergeConfigs(config, userConfig)) 84 - : [mergeConfigs(configFromFile, userConfig)]; 85 - 86 - for (const mergedConfig of mergedConfigs) { 87 - const input = getInput(mergedConfig); 88 - 89 - if (mergedConfig.output instanceof Array) { 90 - const countInputs = input.length; 91 - const countOutputs = mergedConfig.output.length; 92 - if (countOutputs > 1) { 93 - if (countInputs !== countOutputs) { 94 - console.warn( 95 - `⚙️ ${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.`, 96 - ); 97 - for (const output of mergedConfig.output) { 98 - configs.push({ ...mergedConfig, input, output }); 99 - } 100 - } else { 101 - mergedConfig.output.forEach((output, index) => { 102 - configs.push({ ...mergedConfig, input: input[index]!, output }); 103 - }); 104 - } 105 - } else { 106 - configs.push({ 107 - ...mergedConfig, 108 - input, 109 - output: mergedConfig.output[0] ?? '', 110 - }); 111 - } 112 - } else { 113 - configs.push({ ...mergedConfig, input }); 114 - } 115 - } 55 + configs.push(...loaded.configs); 116 56 } 117 57 eventLoad.timeEnd(); 118 58 119 - const results: Array<ArrayOnly<ConfigResult>> = []; 120 - 121 59 const eventBuild = logger.timeEvent('build'); 122 - for (const userConfig of configs) { 123 - const logs = getLogs(userConfig); 124 - const input = getInput(userConfig); 125 - const output = getOutput(userConfig); 126 - const parser = getParser(userConfig); 127 - 128 - const errors: Array<Error> = []; 129 - 130 - if (!input.length) { 131 - errors.push( 132 - new ConfigError( 133 - 'missing input - which OpenAPI specification should we use to generate your output?', 134 - ), 135 - ); 136 - } 137 - 138 - if (!output.path) { 139 - errors.push( 140 - new ConfigError( 141 - 'missing output - where should we generate your output?', 142 - ), 143 - ); 144 - } 145 - 146 - output.path = path.resolve(process.cwd(), output.path); 147 - 148 - let plugins: Pick<Config, 'plugins' | 'pluginOrder'>; 149 - 150 - try { 151 - plugins = getPlugins({ dependencies, userConfig }); 152 - } catch (error) { 153 - errors.push(error); 154 - plugins = { 155 - pluginOrder: [], 156 - plugins: {}, 157 - }; 158 - } 159 - 160 - const config: Config = { 161 - configFile: userConfig.configFile ?? '', 162 - dryRun: userConfig.dryRun ?? false, 163 - input, 164 - interactive: userConfig.interactive ?? detectInteractiveSession(), 165 - logs, 166 - output, 167 - parser, 168 - pluginOrder: plugins.pluginOrder, 169 - plugins: plugins.plugins, 170 - }; 171 - 172 - const jobIndex = results.length; 173 - 174 - if (logs.level === 'debug') { 175 - const jobPrefix = colors.gray(`[Job ${jobIndex + 1}] `); 176 - console.warn(`${jobPrefix}${colors.cyan('config:')}`, config); 177 - } 178 - 179 - results.push({ config, errors, jobIndex }); 180 - } 60 + const jobs = validateJobs(expandToJobs(configs)); 61 + const resolvedJobs = jobs.map((validated) => 62 + resolveConfig(validated, dependencies), 63 + ); 181 64 eventBuild.timeEnd(); 182 65 183 - return { dependencies, results }; 184 - }; 66 + return { 67 + dependencies, 68 + jobs: resolvedJobs, 69 + results: resolvedJobs, 70 + }; 71 + }
+2 -1
packages/openapi-ts/src/config/input.ts
··· 1 - import type { Config, UserConfig } from '~/types/config'; 2 1 import type { Input, Watch } from '~/types/input'; 3 2 import { inputToApiRegistry } from '~/utils/input'; 4 3 import { heyApiRegistryBaseUrl } from '~/utils/input/heyApi'; 4 + 5 + import type { Config, UserConfig } from './types'; 5 6 6 7 const defaultWatch: Watch = { 7 8 enabled: false,
+1 -1
packages/openapi-ts/src/config/logs.ts
··· 1 - import type { Config, UserConfig } from '~/types/config'; 1 + import type { Config, UserConfig } from './types'; 2 2 3 3 export const getLogs = ( 4 4 userConfig: Pick<UserConfig, 'logs'> | undefined,
-32
packages/openapi-ts/src/config/merge.ts
··· 1 - import type { UserConfig } from '~/types/config'; 2 - 3 - const mergeObjects = ( 4 - objA: Record<string, unknown> | undefined, 5 - objB: Record<string, unknown> | undefined, 6 - ): Record<string, unknown> => { 7 - const a = objA || {}; 8 - const b = objB || {}; 9 - return { 10 - ...a, 11 - ...b, 12 - }; 13 - }; 14 - 15 - export const mergeConfigs = ( 16 - configA: UserConfig | undefined, 17 - configB: UserConfig | undefined, 18 - ): UserConfig => { 19 - const a: Partial<UserConfig> = configA || {}; 20 - const b: Partial<UserConfig> = configB || {}; 21 - const merged: UserConfig = { 22 - ...(a as UserConfig), 23 - ...(b as UserConfig), 24 - }; 25 - if (typeof merged.logs === 'object') { 26 - merged.logs = mergeObjects( 27 - a.logs as Record<string, unknown>, 28 - b.logs as Record<string, unknown>, 29 - ); 30 - } 31 - return merged; 32 - };
+1 -1
packages/openapi-ts/src/config/output/config.ts
··· 2 2 import ts from 'typescript'; 3 3 4 4 import { findTsConfigPath, loadTsConfig } from '~/generate/tsConfig'; 5 - import type { Config, UserConfig } from '~/types/config'; 6 5 6 + import type { Config, UserConfig } from '../types'; 7 7 import { valueToObject } from '../utils/config'; 8 8 import type { PostProcessor, UserPostProcessor } from './postprocess'; 9 9 import { postProcessors } from './postprocess';
+1 -2
packages/openapi-ts/src/config/parser.ts
··· 1 - import type { Config, UserConfig } from '~/types/config'; 2 - 1 + import type { Config, UserConfig } from './types'; 3 2 import { valueToObject } from './utils/config'; 4 3 5 4 export const defaultPaginationKeywords = [
+1 -1
packages/openapi-ts/src/config/plugins.ts
··· 4 4 PluginContext, 5 5 PluginNames, 6 6 } from '~/plugins/types'; 7 - import type { Config, UserConfig } from '~/types/config'; 8 7 8 + import type { Config, UserConfig } from './types'; 9 9 import { valueToObject } from './utils/config'; 10 10 import { packageFactory } from './utils/package'; 11 11
+65
packages/openapi-ts/src/config/resolve.ts
··· 1 + import path from 'node:path'; 2 + 3 + import { detectInteractiveSession } from '@hey-api/codegen-core'; 4 + import colors from 'ansi-colors'; 5 + 6 + import { getInput } from './input'; 7 + import { getLogs } from './logs'; 8 + import { getOutput } from './output'; 9 + import { getParser } from './parser'; 10 + import { getPlugins } from './plugins'; 11 + import type { Config } from './types'; 12 + import type { ValidationResult } from './validate'; 13 + 14 + export type ResolvedJob = { 15 + config: Config; 16 + errors: Array<Error>; 17 + index: number; 18 + }; 19 + 20 + export const resolveConfig = ( 21 + validated: ValidationResult, 22 + dependencies: Record<string, string>, 23 + ): ResolvedJob => { 24 + const logs = getLogs(validated.job.config); 25 + const input = getInput(validated.job.config); 26 + const output = getOutput(validated.job.config); 27 + const parser = getParser(validated.job.config); 28 + 29 + output.path = path.resolve(process.cwd(), output.path); 30 + 31 + let plugins: Pick<Config, 'plugins' | 'pluginOrder'>; 32 + 33 + try { 34 + plugins = getPlugins({ dependencies, userConfig: validated.job.config }); 35 + } catch (error) { 36 + validated.errors.push(error); 37 + plugins = { 38 + pluginOrder: [], 39 + plugins: {}, 40 + }; 41 + } 42 + 43 + const config: Config = { 44 + configFile: validated.job.config.configFile ?? '', 45 + dryRun: validated.job.config.dryRun ?? false, 46 + input, 47 + interactive: validated.job.config.interactive ?? detectInteractiveSession(), 48 + logs, 49 + output, 50 + parser, 51 + pluginOrder: plugins.pluginOrder, 52 + plugins: plugins.plugins, 53 + }; 54 + 55 + if (logs.level === 'debug') { 56 + const jobPrefix = colors.gray(`[Job ${validated.job.index}] `); 57 + console.warn(`${jobPrefix}${colors.cyan('config:')}`, config); 58 + } 59 + 60 + return { 61 + config, 62 + errors: validated.errors, 63 + index: validated.job.index, 64 + }; 65 + };
+39
packages/openapi-ts/src/config/validate.ts
··· 1 + import { ConfigError } from '~/error'; 2 + 3 + import type { Job } from './expand'; 4 + import { getInput } from './input'; 5 + import { getOutput } from './output'; 6 + 7 + export interface ValidationResult { 8 + errors: Array<ConfigError>; 9 + job: Job; 10 + } 11 + 12 + export function validateJobs( 13 + jobs: ReadonlyArray<Job>, 14 + ): ReadonlyArray<ValidationResult> { 15 + return jobs.map((job) => { 16 + const errors: Array<ConfigError> = []; 17 + const { config } = job; 18 + 19 + const inputs = getInput(config); 20 + if (!inputs.length) { 21 + errors.push( 22 + new ConfigError( 23 + 'missing input - which OpenAPI specification should we use to generate your output?', 24 + ), 25 + ); 26 + } 27 + 28 + const output = getOutput(config); 29 + if (!output.path) { 30 + errors.push( 31 + new ConfigError( 32 + 'missing output - where should we generate your output?', 33 + ), 34 + ); 35 + } 36 + 37 + return { errors, job }; 38 + }); 39 + }
+2 -2
packages/openapi-ts/src/createClient.ts
··· 1 1 import path from 'node:path'; 2 2 3 + import type { Logger } from '@hey-api/codegen-core'; 3 4 import { $RefParser } from '@hey-api/json-schema-ref-parser'; 4 5 import colors from 'ansi-colors'; 5 6 6 7 import { postprocessOutput } from '~/config/output'; 8 + import type { Config } from '~/config/types'; 7 9 import { generateOutput } from '~/generate/output'; 8 10 import { getSpec } from '~/getSpec'; 9 11 import type { Context } from '~/ir/context'; 10 12 import { parseOpenApiSpec } from '~/openApi'; 11 13 import { buildGraph } from '~/openApi/shared/utils/graph'; 12 14 import { patchOpenApiSpec } from '~/openApi/shared/utils/patch'; 13 - import type { Config } from '~/types/config'; 14 15 import type { Input } from '~/types/input'; 15 16 import type { WatchValues } from '~/types/types'; 16 - import type { Logger } from '~/utils/logger'; 17 17 18 18 export const compileInputPath = (input: Omit<Input, 'watch'>) => { 19 19 const result: Pick<
+29 -39
packages/openapi-ts/src/generate.ts
··· 1 + import { Logger } from '@hey-api/codegen-core'; 1 2 import type { LazyOrAsync, MaybeArray } from '@hey-api/types'; 2 3 3 4 import { checkNodeVersion } from '~/config/engine'; 4 5 import type { Configs } from '~/config/init'; 5 - import { initConfigs } from '~/config/init'; 6 + import { resolveJobs } from '~/config/init'; 6 7 import { getLogs } from '~/config/logs'; 8 + import type { UserConfig } from '~/config/types'; 7 9 import { createClient as pCreateClient } from '~/createClient'; 8 10 import { 9 11 ConfigValidationError, ··· 14 16 shouldReportCrash, 15 17 } from '~/error'; 16 18 import type { Context } from '~/ir/context'; 17 - import type { UserConfig } from '~/types/config'; 18 19 import { printCliIntro } from '~/utils/cli'; 19 - import { Logger } from '~/utils/logger'; 20 20 21 21 /** 22 22 * Generate a client from the provided configuration. 23 23 * 24 24 * @param userConfig User provided {@link UserConfig} configuration(s). 25 25 */ 26 - export const createClient = async ( 26 + export async function createClient( 27 27 userConfig?: LazyOrAsync<MaybeArray<UserConfig>>, 28 28 logger = new Logger(), 29 - ): Promise<ReadonlyArray<Context>> => { 29 + ): Promise<ReadonlyArray<Context>> { 30 30 const resolvedConfig = 31 31 typeof userConfig === 'function' ? await userConfig() : userConfig; 32 32 const userConfigs = resolvedConfig ··· 42 42 rawLogs = getLogs({ logs: rawLogs }); 43 43 } 44 44 45 - let configs: Configs | undefined; 45 + let jobs: Configs['jobs'] = []; 46 46 47 47 try { 48 48 checkNodeVersion(); ··· 50 50 const eventCreateClient = logger.timeEvent('createClient'); 51 51 52 52 const eventConfig = logger.timeEvent('config'); 53 - configs = await initConfigs({ logger, userConfigs }); 54 - const printIntro = configs.results.some( 55 - (result) => result.config.logs.level !== 'silent', 56 - ); 57 - if (printIntro) { 58 - printCliIntro(); 59 - } 53 + const resolved = await resolveJobs({ logger, userConfigs }); 54 + const dependencies = resolved.dependencies; 55 + jobs = resolved.jobs; 56 + const printIntro = jobs.some((job) => job.config.logs.level !== 'silent'); 57 + if (printIntro) printCliIntro(); 60 58 eventConfig.timeEnd(); 61 59 62 - const allConfigErrors = configs.results.flatMap((result) => 63 - result.errors.map((error) => ({ error, jobIndex: result.jobIndex })), 60 + const configErrors = jobs.flatMap((job) => 61 + job.errors.map((error) => ({ error, jobIndex: job.index })), 64 62 ); 65 - if (allConfigErrors.length) { 66 - throw new ConfigValidationError(allConfigErrors); 63 + if (configErrors.length > 0) { 64 + throw new ConfigValidationError(configErrors); 67 65 } 68 66 69 - const clients = await Promise.all( 70 - configs.results.map(async (result) => { 67 + const outputs = await Promise.all( 68 + jobs.map(async (job) => { 71 69 try { 72 70 return await pCreateClient({ 73 - config: result.config, 74 - dependencies: configs!.dependencies, 75 - jobIndex: result.jobIndex, 71 + config: job.config, 72 + dependencies, 73 + jobIndex: job.index, 76 74 logger, 77 75 }); 78 76 } catch (error) { 79 77 throw new JobError('', { 80 78 error, 81 - jobIndex: result.jobIndex, 79 + jobIndex: job.index, 82 80 }); 83 81 } 84 82 }), 85 83 ); 86 - const result = clients.filter((client) => 87 - Boolean(client), 88 - ) as ReadonlyArray<Context>; 84 + const contexts = outputs.filter((ctx): ctx is Context => ctx !== undefined); 89 85 90 86 eventCreateClient.timeEnd(); 91 87 92 - const printLogs = configs.results.some( 93 - (result) => result.config.logs.level === 'debug', 94 - ); 95 - logger.report(printLogs); 88 + logger.report(jobs.some((job) => job.config.logs.level === 'debug')); 96 89 97 - return result; 90 + return contexts; 98 91 } catch (error) { 99 - const results = configs?.results ?? []; 100 - 101 92 const logs = 102 - results.find((result) => result.config.logs.level !== 'silent')?.config 103 - .logs ?? 104 - results[0]?.config.logs ?? 93 + jobs.find((job) => job.config.logs.level !== 'silent')?.config.logs ?? 94 + jobs[0]?.config.logs ?? 105 95 rawLogs; 106 96 const dryRun = 107 - results.some((result) => result.config.dryRun) ?? 97 + jobs.some((job) => job.config.dryRun) ?? 108 98 userConfigs.some((config) => config.dryRun) ?? 109 99 false; 110 100 const logPath = ··· 114 104 if (!logs || logs.level !== 'silent') { 115 105 printCrashReport({ error, logPath }); 116 106 const isInteractive = 117 - results.some((result) => result.config.interactive) ?? 107 + jobs.some((job) => job.config.interactive) ?? 118 108 userConfigs.some((config) => config.interactive) ?? 119 109 false; 120 110 if (await shouldReportCrash({ error, isInteractive })) { ··· 124 114 125 115 throw error; 126 116 } 127 - }; 117 + }
+1 -1
packages/openapi-ts/src/generate/client.ts
··· 4 4 5 5 import type { IProject, ProjectRenderMeta } from '@hey-api/codegen-core'; 6 6 7 + import type { Config } from '~/config/types'; 7 8 import type { DefinePlugin } from '~/plugins'; 8 9 import type { Client } from '~/plugins/@hey-api/client-core/types'; 9 10 import { getClientPlugin } from '~/plugins/@hey-api/client-core/utils'; 10 - import type { Config } from '~/types/config'; 11 11 12 12 import { ensureDirSync } from './utils'; 13 13
+8 -6
packages/openapi-ts/src/index.ts
··· 72 72 // @ts-expect-error 73 73 import colorSupport from 'color-support'; 74 74 75 - import type { UserConfig } from '~/types/config'; 75 + import type { UserConfig } from '~/config/types'; 76 76 77 77 colors.enabled = colorSupport().hasBasic; 78 78 79 79 export { createClient } from '~/generate'; 80 80 81 81 /** 82 - * Type helper for openapi-ts.config.ts, returns {@link MaybeArray<UserConfig>} object(s) 82 + * Type helper for configuration object, returns {@link MaybeArray<UserConfig>} object(s) 83 83 */ 84 - export const defineConfig = async <T extends MaybeArray<UserConfig>>( 84 + export async function defineConfig<T extends MaybeArray<UserConfig>>( 85 85 config: LazyOrAsync<T>, 86 - ): Promise<T> => (typeof config === 'function' ? await config() : config); 86 + ): Promise<T> { 87 + return typeof config === 'function' ? await config() : config; 88 + } 87 89 90 + export { Logger } from '@hey-api/codegen-core'; 88 91 export { defaultPaginationKeywords } from '~/config/parser'; 89 92 export { defaultPlugins } from '~/config/plugins'; 93 + export type { UserConfig } from '~/config/types'; 90 94 export type { IR } from '~/ir/types'; 91 95 export { OperationPath, OperationStrategy } from '~/openApi/shared/locations'; 92 96 export type { ··· 115 119 export type { TypeTransformer } from '~/plugins/@hey-api/transformers/types'; 116 120 export { definePluginConfig } from '~/plugins/shared/utils/config'; 117 121 export * from '~/ts-dsl'; 118 - export type { UserConfig } from '~/types/config'; 119 122 export { utils } from '~/utils/exports'; 120 - export { Logger } from '~/utils/logger';
+1 -1
packages/openapi-ts/src/internal.ts
··· 1 - export { initConfigs } from './config/init'; 1 + export { resolveJobs as initConfigs } from './config/init'; 2 2 export { getSpec } from './getSpec'; 3 3 export { parseOpenApiSpec } from './openApi';
+1 -1
packages/openapi-ts/src/ir/__tests__/pagination.test.ts
··· 1 1 import { describe, expect, it, vi } from 'vitest'; 2 2 3 3 import { defaultPaginationKeywords } from '~/config/parser'; 4 - import type { Config } from '~/types/config'; 4 + import type { Config } from '~/config/types'; 5 5 6 6 import type { Context } from '../context'; 7 7 import { operationPagination } from '../operation';
+2 -2
packages/openapi-ts/src/ir/context.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 1 2 import { Project } from '@hey-api/codegen-core'; 2 3 4 + import type { Config } from '~/config/types'; 3 5 import type { Package } from '~/config/utils/package'; 4 6 import { packageFactory } from '~/config/utils/package'; 5 7 import type { Graph } from '~/graph'; ··· 7 9 import { PluginInstance } from '~/plugins/shared/utils/instance'; 8 10 import type { PluginNames } from '~/plugins/types'; 9 11 import { TypeScriptRenderer } from '~/ts-dsl'; 10 - import type { Config } from '~/types/config'; 11 - import type { Logger } from '~/utils/logger'; 12 12 import { applyNaming } from '~/utils/naming'; 13 13 import { resolveRef } from '~/utils/ref'; 14 14
+1 -1
packages/openapi-ts/src/ir/pagination.ts
··· 1 - import type { Config } from '~/types/config'; 1 + import type { Config } from '~/config/types'; 2 2 3 3 import type { IR } from './types'; 4 4
+1 -1
packages/openapi-ts/src/openApi/2.0.x/parser/__tests__/validate.test.ts
··· 1 1 import path from 'node:path'; 2 2 3 + import { Logger } from '@hey-api/codegen-core'; 3 4 import { describe, expect, it } from 'vitest'; 4 5 5 6 import { getSpecsPath, specFileToJson } from '~/openApi/__tests__/utils'; 6 7 import type { ValidatorResult } from '~/openApi/shared/utils/validator'; 7 8 8 - import { Logger } from '../../../../utils/logger'; 9 9 import { validateOpenApiSpec } from '../validate'; 10 10 11 11 const specsFolder = path.join(getSpecsPath(), '2.0.x', 'invalid');
+2 -1
packages/openapi-ts/src/openApi/2.0.x/parser/filter.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 1 3 import { createOperationKey } from '~/ir/operation'; 2 4 import { addNamespace, removeNamespace } from '~/openApi/shared/utils/filter'; 3 5 import { httpMethods } from '~/openApi/shared/utils/operation'; 4 - import type { Logger } from '~/utils/logger'; 5 6 6 7 import type { 7 8 OpenApiV2_0_X,
+2 -1
packages/openapi-ts/src/openApi/2.0.x/parser/validate.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 1 3 import { createOperationKey } from '~/ir/operation'; 2 4 import { httpMethods } from '~/openApi/shared/utils/operation'; 3 5 import type { 4 6 ValidatorIssue, 5 7 ValidatorResult, 6 8 } from '~/openApi/shared/utils/validator'; 7 - import type { Logger } from '~/utils/logger'; 8 9 9 10 import type { OpenApiV2_0_X, PathItemObject, PathsObject } from '../types/spec'; 10 11
+1 -1
packages/openapi-ts/src/openApi/3.0.x/parser/__tests__/validate.test.ts
··· 1 1 import path from 'node:path'; 2 2 3 + import { Logger } from '@hey-api/codegen-core'; 3 4 import { describe, expect, it } from 'vitest'; 4 5 5 6 import { getSpecsPath, specFileToJson } from '~/openApi/__tests__/utils'; 6 7 import type { ValidatorResult } from '~/openApi/shared/utils/validator'; 7 8 8 - import { Logger } from '../../../../utils/logger'; 9 9 import { validateOpenApiSpec } from '../validate'; 10 10 11 11 const specsFolder = path.join(getSpecsPath(), '3.0.x', 'invalid');
+2 -1
packages/openapi-ts/src/openApi/3.0.x/parser/filter.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 1 3 import { createOperationKey } from '~/ir/operation'; 2 4 import { addNamespace, removeNamespace } from '~/openApi/shared/utils/filter'; 3 5 import { httpMethods } from '~/openApi/shared/utils/operation'; 4 - import type { Logger } from '~/utils/logger'; 5 6 6 7 import type { OpenApiV3_0_X, PathItemObject, PathsObject } from '../types/spec'; 7 8
+2 -1
packages/openapi-ts/src/openApi/3.0.x/parser/validate.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 1 3 import { createOperationKey } from '~/ir/operation'; 2 4 import { httpMethods } from '~/openApi/shared/utils/operation'; 3 5 import type { 4 6 ValidatorIssue, 5 7 ValidatorResult, 6 8 } from '~/openApi/shared/utils/validator'; 7 - import type { Logger } from '~/utils/logger'; 8 9 9 10 import type { OpenApiV3_0_X, PathItemObject, PathsObject } from '../types/spec'; 10 11
+1 -1
packages/openapi-ts/src/openApi/3.1.x/parser/__tests__/validate.test.ts
··· 1 1 import path from 'node:path'; 2 2 3 + import { Logger } from '@hey-api/codegen-core'; 3 4 import { describe, expect, it } from 'vitest'; 4 5 5 6 import { getSpecsPath, specFileToJson } from '~/openApi/__tests__/utils'; 6 7 import type { ValidatorResult } from '~/openApi/shared/utils/validator'; 7 8 8 - import { Logger } from '../../../../utils/logger'; 9 9 import { validateOpenApiSpec } from '../validate'; 10 10 11 11 const specsFolder = path.join(getSpecsPath(), '3.1.x', 'invalid');
+2 -1
packages/openapi-ts/src/openApi/3.1.x/parser/filter.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 1 3 import { createOperationKey } from '~/ir/operation'; 2 4 import { addNamespace, removeNamespace } from '~/openApi/shared/utils/filter'; 3 5 import { httpMethods } from '~/openApi/shared/utils/operation'; 4 - import type { Logger } from '~/utils/logger'; 5 6 6 7 import type { OpenApiV3_1_X, PathItemObject, PathsObject } from '../types/spec'; 7 8
+2 -1
packages/openapi-ts/src/openApi/3.1.x/parser/validate.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 1 3 import { createOperationKey } from '~/ir/operation'; 2 4 import { httpMethods } from '~/openApi/shared/utils/operation'; 3 5 import type { 4 6 ValidatorIssue, 5 7 ValidatorResult, 6 8 } from '~/openApi/shared/utils/validator'; 7 - import type { Logger } from '~/utils/logger'; 8 9 9 10 import type { OpenApiV3_1_X, PathItemObject, PathsObject } from '../types/spec'; 10 11
+1 -1
packages/openapi-ts/src/openApi/__tests__/index.test.ts
··· 1 1 import { afterEach, describe, expect, it, vi } from 'vitest'; 2 2 3 + import type { Config } from '~/config/types'; 3 4 import type { OpenApiV3_0_X } from '~/openApi/3.0.x'; 4 5 import { parseV3_0_X } from '~/openApi/3.0.x'; 5 6 import type { OpenApiV3_1_X } from '~/openApi/3.1.x'; 6 7 import { parseV3_1_X } from '~/openApi/3.1.x'; 7 8 8 - import type { Config } from '../../types/config'; 9 9 import { parseOpenApiSpec } from '..'; 10 10 11 11 vi.mock('../3.0.x', () => ({
+3 -2
packages/openapi-ts/src/openApi/index.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 3 + import type { Config } from '~/config/types'; 1 4 import { satisfies } from '~/config/utils/package'; 2 5 import { Context } from '~/ir/context'; 3 6 import { parseV2_0_X } from '~/openApi/2.0.x'; 4 7 import { parseV3_0_X } from '~/openApi/3.0.x'; 5 8 import { parseV3_1_X } from '~/openApi/3.1.x'; 6 9 import type { OpenApi } from '~/openApi/types'; 7 - import type { Config } from '~/types/config'; 8 - import type { Logger } from '~/utils/logger'; 9 10 10 11 /** 11 12 * @internal
+2 -1
packages/openapi-ts/src/openApi/shared/graph/meta.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 1 3 import type { Graph } from '~/graph'; 2 4 import { createOperationKey } from '~/ir/operation'; 3 - import type { Logger } from '~/utils/logger'; 4 5 import { jsonPointerToPath } from '~/utils/ref'; 5 6 6 7 import { addNamespace, stringToNamespace } from '../utils/filter';
+1 -1
packages/openapi-ts/src/openApi/shared/transforms/enums.ts
··· 1 + import type { Config } from '~/config/types'; 1 2 import { applyNaming } from '~/utils/naming'; 2 3 import { jsonPointerToPath } from '~/utils/ref'; 3 4 4 - import type { Config } from '../../../types/config'; 5 5 import { deepClone } from '../utils/schema'; 6 6 import { childSchemaRelationships } from '../utils/schemaChildRelationships'; 7 7 import { getSchemasObject } from '../utils/transforms';
+3 -2
packages/openapi-ts/src/openApi/shared/transforms/readWrite.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 3 + import type { Config } from '~/config/types'; 1 4 import type { Graph } from '~/graph'; 2 - import type { Logger } from '~/utils/logger'; 3 5 import { applyNaming } from '~/utils/naming'; 4 6 import { jsonPointerToPath } from '~/utils/ref'; 5 7 6 - import type { Config } from '../../../types/config'; 7 8 import deepEqual from '../utils/deepEqual'; 8 9 import { buildGraph, type Scope } from '../utils/graph'; 9 10 import { deepClone } from '../utils/schema';
+3 -2
packages/openapi-ts/src/openApi/shared/utils/filter.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 3 + import type { Config } from '~/config/types'; 1 4 import { createOperationKey } from '~/ir/operation'; 2 5 import type { PathItemObject, PathsObject } from '~/openApi/3.1.x/types/spec'; 3 6 import type { OpenApi } from '~/openApi/types'; 4 - import type { Logger } from '~/utils/logger'; 5 7 6 - import type { Config } from '../../../types/config'; 7 8 import type { ResourceMetadata } from '../graph/meta'; 8 9 import { httpMethods } from './operation'; 9 10
+2 -1
packages/openapi-ts/src/openApi/shared/utils/graph.ts
··· 1 + import type { Logger } from '@hey-api/codegen-core'; 2 + 1 3 import type { Graph, NodeInfo } from '~/graph'; 2 - import type { Logger } from '~/utils/logger'; 3 4 import { normalizeJsonPointer, pathToJsonPointer } from '~/utils/ref'; 4 5 5 6 import { childSchemaRelationships } from './schemaChildRelationships';
+1 -1
packages/openapi-ts/src/plugins/@hey-api/client-core/utils.ts
··· 1 + import type { Config } from '~/config/types'; 1 2 import type { PluginClientNames } from '~/plugins/types'; 2 - import type { Config } from '~/types/config'; 3 3 4 4 export const getClientBaseUrlKey = (config: Config) => { 5 5 const client = getClientPlugin(config);
+9
packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts
··· 58 58 const mutationOptionsFn = 'mutationOptions'; 59 59 const symbolMutationOptions = plugin.symbol( 60 60 applyNaming(operation.id, plugin.config.mutationOptions), 61 + { 62 + meta: { 63 + category: 'hook', 64 + resource: 'operation', 65 + resourceId: operation.id, 66 + role: 'mutationOptions', 67 + tool: plugin.name, 68 + }, 69 + }, 61 70 ); 62 71 const statement = $.const(symbolMutationOptions) 63 72 .export()
+61 -22
packages/openapi-ts/src/ts-dsl/expr/object.ts
··· 21 21 export class ObjectTsDsl extends Mixed { 22 22 readonly '~dsl' = 'ObjectTsDsl'; 23 23 24 - protected _props: Array<ObjectPropTsDsl> = []; 24 + protected _props = new Map<string, ObjectPropTsDsl>(); 25 + protected _spreadCounter = 0; 25 26 26 27 constructor(...props: Array<ObjectPropTsDsl>) { 27 28 super(); ··· 30 31 31 32 override analyze(ctx: AnalysisContext): void { 32 33 super.analyze(ctx); 33 - for (const prop of this._props) { 34 + for (const prop of this._props.values()) { 34 35 ctx.analyze(prop); 35 36 } 36 37 } 37 38 38 - /** Adds a computed property (e.g. `{ [expr]: value }`). */ 39 - computed(name: string, expr: ExprFn): this { 40 - this._props.push( 41 - new ObjectPropTsDsl({ kind: 'computed', name }).value(expr), 42 - ); 39 + /** Returns composite key for the property. */ 40 + private _propKey(prop: ObjectPropTsDsl): string { 41 + if (prop.kind === 'spread') { 42 + return `spread:${this._spreadCounter++}`; 43 + } 44 + return `${prop.kind}:${prop.propName}`; 45 + } 46 + 47 + /** Adds a computed property (e.g. `{ [expr]: value }`), or removes if null. */ 48 + computed(name: string, expr: ExprFn | null): this { 49 + if (expr === null) { 50 + this._props.delete(`computed:${name}`); 51 + } else { 52 + this._props.set( 53 + `computed:${name}`, 54 + new ObjectPropTsDsl({ kind: 'computed', name }).value(expr), 55 + ); 56 + } 43 57 return this; 44 58 } 45 59 46 - /** Adds a getter property (e.g. `{ get foo() { ... } }`). */ 47 - getter(name: string, stmt: StmtFn): this { 48 - this._props.push(new ObjectPropTsDsl({ kind: 'getter', name }).value(stmt)); 60 + /** Adds a getter property (e.g. `{ get foo() { ... } }`), or removes if null. */ 61 + getter(name: string, stmt: StmtFn | null): this { 62 + if (stmt === null) { 63 + this._props.delete(`getter:${name}`); 64 + } else { 65 + this._props.set( 66 + `getter:${name}`, 67 + new ObjectPropTsDsl({ kind: 'getter', name }).value(stmt), 68 + ); 69 + } 49 70 return this; 50 71 } 51 72 52 73 /** Returns true if object has at least one property or spread. */ 53 74 hasProps(): boolean { 54 - return this._props.length > 0; 75 + return this._props.size > 0; 55 76 } 56 77 57 78 /** Returns true if object has no properties or spreads. */ 58 79 get isEmpty(): boolean { 59 - return this._props.length === 0; 80 + return this._props.size === 0; 60 81 } 61 82 62 - /** Adds a property assignment. */ 63 - prop(name: string, expr: ExprFn): this { 64 - this._props.push(new ObjectPropTsDsl({ kind: 'prop', name }).value(expr)); 83 + /** Adds a property assignment, or removes if null. */ 84 + prop(name: string, expr: ExprFn | null): this { 85 + if (expr === null) { 86 + this._props.delete(`prop:${name}`); 87 + } else { 88 + this._props.set( 89 + `prop:${name}`, 90 + new ObjectPropTsDsl({ kind: 'prop', name }).value(expr), 91 + ); 92 + } 65 93 return this; 66 94 } 67 95 68 96 /** Adds multiple properties. */ 69 97 props(...props: ReadonlyArray<ObjectPropTsDsl>): this { 70 - this._props.push(...props); 98 + for (const prop of props) { 99 + this._props.set(this._propKey(prop), prop); 100 + } 71 101 return this; 72 102 } 73 103 74 - /** Adds a setter property (e.g. `{ set foo(v) { ... } }`). */ 75 - setter(name: string, stmt: StmtFn): this { 76 - this._props.push(new ObjectPropTsDsl({ kind: 'setter', name }).value(stmt)); 104 + /** Adds a setter property (e.g. `{ set foo(v) { ... } }`), or removes if null. */ 105 + setter(name: string, stmt: StmtFn | null): this { 106 + if (stmt === null) { 107 + this._props.delete(`setter:${name}`); 108 + } else { 109 + this._props.set( 110 + `setter:${name}`, 111 + new ObjectPropTsDsl({ kind: 'setter', name }).value(stmt), 112 + ); 113 + } 77 114 return this; 78 115 } 79 116 80 117 /** Adds a spread property (e.g. `{ ...options }`). */ 81 118 spread(expr: ExprFn): this { 82 - this._props.push(new ObjectPropTsDsl({ kind: 'spread' }).value(expr)); 119 + const key = `spread:${this._spreadCounter++}`; 120 + this._props.set(key, new ObjectPropTsDsl({ kind: 'spread' }).value(expr)); 83 121 return this; 84 122 } 85 123 86 124 override toAst() { 125 + const props = [...this._props.values()]; 87 126 const node = ts.factory.createObjectLiteralExpression( 88 - this.$node(this._props), 89 - this.$multiline(this._props.length), 127 + this.$node(props), 128 + this.$multiline(props.length), 90 129 ); 91 130 return this.$hint(node); 92 131 }
+29 -16
packages/openapi-ts/src/ts-dsl/expr/prop.ts
··· 12 12 13 13 type Expr = NodeName | MaybeTsDsl<ts.Expression>; 14 14 type Stmt = NodeName | MaybeTsDsl<ts.Statement>; 15 - type Kind = 'computed' | 'getter' | 'prop' | 'setter' | 'spread'; 15 + 16 + export type ObjectPropKind = 17 + | 'computed' 18 + | 'getter' 19 + | 'prop' 20 + | 'setter' 21 + | 'spread'; 16 22 17 23 type Meta = 18 24 | { kind: 'computed'; name: string } ··· 27 33 readonly '~dsl' = 'ObjectPropTsDsl'; 28 34 29 35 protected _value?: Ref<Expr | Stmt>; 30 - protected meta: Meta; 36 + protected _meta: Meta; 31 37 32 38 constructor(meta: Meta) { 33 39 super(); 34 - this.meta = meta; 40 + this._meta = meta; 41 + } 42 + 43 + get kind(): ObjectPropKind { 44 + return this._meta.kind; 45 + } 46 + 47 + get propName(): string | undefined { 48 + return this._meta.name; 35 49 } 36 50 37 51 override analyze(ctx: AnalysisContext): void { ··· 39 53 ctx.analyze(this._value); 40 54 } 41 55 42 - /** Returns true when all required builder calls are present. */ 43 56 get isValid(): boolean { 44 57 return this.missingRequiredCalls().length === 0; 45 58 } ··· 56 69 override toAst() { 57 70 this.$validate(); 58 71 const node = this.$node(this._value); 59 - if (this.meta.kind === 'spread') { 72 + if (this._meta.kind === 'spread') { 60 73 if (ts.isStatement(node)) { 61 74 throw new Error( 62 75 'Invalid spread: object spread must be an expression, not a statement.', ··· 65 78 const result = ts.factory.createSpreadAssignment(node); 66 79 return this.$docs(result); 67 80 } 68 - if (this.meta.kind === 'getter') { 69 - const getter = new GetterTsDsl(this.meta.name).do(node); 81 + if (this._meta.kind === 'getter') { 82 + const getter = new GetterTsDsl(this._meta.name).do(node); 70 83 const result = this.$node(getter); 71 84 return this.$docs(result); 72 85 } 73 - if (this.meta.kind === 'setter') { 74 - const setter = new SetterTsDsl(this.meta.name).do(node); 86 + if (this._meta.kind === 'setter') { 87 + const setter = new SetterTsDsl(this._meta.name).do(node); 75 88 const result = this.$node(setter); 76 89 return this.$docs(result); 77 90 } 78 - if (ts.isIdentifier(node) && node.text === this.meta.name) { 91 + if (ts.isIdentifier(node) && node.text === this._meta.name) { 79 92 const result = ts.factory.createShorthandPropertyAssignment( 80 - this.meta.name, 93 + this._meta.name, 81 94 ); 82 95 return this.$docs(result); 83 96 } ··· 87 100 ); 88 101 } 89 102 const result = ts.factory.createPropertyAssignment( 90 - this.meta.kind === 'computed' 103 + this._meta.kind === 'computed' 91 104 ? ts.factory.createComputedPropertyName( 92 - this.$node(new IdTsDsl(this.meta.name)), 105 + this.$node(new IdTsDsl(this._meta.name)), 93 106 ) 94 - : this.$node(safePropName(this.meta.name)), 107 + : this.$node(safePropName(this._meta.name)), 95 108 node, 96 109 ); 97 110 return this.$docs(result); ··· 99 112 100 113 $validate(): asserts this is this & { 101 114 _value: Expr | Stmt; 102 - kind: Kind; 115 + kind: ObjectPropKind; 103 116 } { 104 117 const missing = this.missingRequiredCalls(); 105 118 if (missing.length === 0) return; 106 119 throw new Error( 107 - `Object property${this.meta.name ? ` "${this.meta.name}"` : ''} missing ${missing.join(' and ')}`, 120 + `Object property${this._meta.name ? ` "${this._meta.name}"` : ''} missing ${missing.join(' and ')}`, 108 121 ); 109 122 } 110 123
+11
packages/openapi-ts/src/ts-dsl/type/idx-sig.ts
··· 11 11 import { ReadonlyMixin } from '../mixins/modifiers'; 12 12 13 13 export type TypeIdxSigType = string | MaybeTsDsl<ts.TypeNode>; 14 + export type TypeIdxSigKind = 'idxSig'; 14 15 15 16 const Mixed = DocMixin(ReadonlyMixin(TsDsl<ts.IndexSignatureDeclaration>)); 16 17 ··· 25 26 super(); 26 27 this.name.set(name); 27 28 fn?.(this); 29 + } 30 + 31 + /** Element kind. */ 32 + get kind(): TypeIdxSigKind { 33 + return 'idxSig'; 34 + } 35 + 36 + /** Index signature parameter name. */ 37 + get propName(): string { 38 + return this.name.toString(); 28 39 } 29 40 30 41 override analyze(ctx: AnalysisContext): void {
+33 -15
packages/openapi-ts/src/ts-dsl/type/object.ts
··· 11 11 readonly '~dsl' = 'TypeObjectTsDsl'; 12 12 override scope: NodeScope = 'type'; 13 13 14 - protected props: Array<TypePropTsDsl | TypeIdxSigTsDsl> = []; 14 + protected _props = new Map<string, TypePropTsDsl | TypeIdxSigTsDsl>(); 15 15 16 16 override analyze(ctx: AnalysisContext): void { 17 17 super.analyze(ctx); 18 - for (const prop of this.props) { 18 + for (const prop of this._props.values()) { 19 19 ctx.analyze(prop); 20 20 } 21 21 } 22 22 23 - /** Returns true if object has at least one property or spread. */ 23 + /** Returns true if object has at least one property or index signature. */ 24 24 hasProps(): boolean { 25 - return this.props.length > 0; 25 + return this._props.size > 0; 26 26 } 27 27 28 - /** Adds an index signature to the object type. */ 29 - idxSig(name: string, fn: (i: TypeIdxSigTsDsl) => void): this { 30 - const idx = new TypeIdxSigTsDsl(name, fn); 31 - this.props.push(idx); 28 + /** Adds an index signature to the object type, or removes if fn is null. */ 29 + idxSig(name: string, fn: ((i: TypeIdxSigTsDsl) => void) | null): this { 30 + const key = `idxSig:${name}`; 31 + if (fn === null) { 32 + this._props.delete(key); 33 + } else { 34 + this._props.set(key, new TypeIdxSigTsDsl(name, fn)); 35 + } 32 36 return this; 33 37 } 34 38 35 - /** Returns true if object has no properties or spreads. */ 39 + /** Returns true if object has no properties or index signatures. */ 36 40 get isEmpty(): boolean { 37 - return !this.props.length; 41 + return this._props.size === 0; 38 42 } 39 43 40 - /** Adds a property signature (returns property builder). */ 41 - prop(name: string, fn: (p: TypePropTsDsl) => void): this { 42 - const prop = new TypePropTsDsl(name, fn); 43 - this.props.push(prop); 44 + /** Adds a property signature, or removes if fn is null. */ 45 + prop(name: string, fn: ((p: TypePropTsDsl) => void) | null): this { 46 + const key = `prop:${name}`; 47 + if (fn === null) { 48 + this._props.delete(key); 49 + } else { 50 + this._props.set(key, new TypePropTsDsl(name, fn)); 51 + } 52 + return this; 53 + } 54 + 55 + /** Adds multiple properties/index signatures. */ 56 + props(...members: ReadonlyArray<TypePropTsDsl | TypeIdxSigTsDsl>): this { 57 + for (const member of members) { 58 + this._props.set(`${member.kind}:${member.propName}`, member); 59 + } 44 60 return this; 45 61 } 46 62 47 63 override toAst() { 48 - return ts.factory.createTypeLiteralNode(this.$node(this.props)); 64 + return ts.factory.createTypeLiteralNode( 65 + this.$node([...this._props.values()]), 66 + ); 49 67 } 50 68 }
+11
packages/openapi-ts/src/ts-dsl/type/prop.ts
··· 16 16 import { safePropName } from '../utils/name'; 17 17 18 18 export type TypePropType = NodeName | MaybeTsDsl<ts.TypeNode>; 19 + export type TypePropKind = 'prop'; 19 20 20 21 const Mixed = DocMixin(OptionalMixin(ReadonlyMixin(TsDsl<ts.TypeElement>))); 21 22 ··· 29 30 super(); 30 31 this.name.set(name); 31 32 fn(this); 33 + } 34 + 35 + /** Element kind. */ 36 + get kind(): TypePropKind { 37 + return 'prop'; 38 + } 39 + 40 + /** Property name. */ 41 + get propName(): string { 42 + return this.name.toString(); 32 43 } 33 44 34 45 override analyze(ctx: AnalysisContext): void {
+6 -6
packages/openapi-ts/src/types/config.d.ts packages/openapi-ts/src/config/types.d.ts
··· 1 1 import type { MaybeArray } from '@hey-api/types'; 2 2 3 - import type { Output, UserOutput } from '~/config/output'; 4 3 import type { Plugin } from '~/plugins'; 5 4 import type { PluginConfigMap } from '~/plugins/config'; 6 5 import type { PluginNames } from '~/plugins/types'; 6 + import type { Input, UserInput, Watch } from '~/types/input'; 7 + import type { Logs } from '~/types/logs'; 8 + import type { Parser, UserParser } from '~/types/parser'; 7 9 8 - import type { Input, UserInput, Watch } from './input'; 9 - import type { Logs } from './logs'; 10 - import type { Parser, UserParser } from './parser'; 10 + import type { Output, UserOutput } from './output'; 11 11 12 - export interface UserConfig { 12 + export type UserConfig = { 13 13 /** 14 14 * Path to the config file. Set this value if you don't use the default 15 15 * config file name, or it's not located in the project root. ··· 81 81 * @deprecated use `input.watch` instead 82 82 */ 83 83 watch?: boolean | number | Watch; 84 - } 84 + }; 85 85 86 86 export type Config = Omit< 87 87 Required<UserConfig>,
packages/openapi-ts/src/utils/__tests__/logger.test.ts packages/codegen-core/src/__tests__/logger.test.ts
+7 -4
packages/openapi-ts/src/utils/cli.ts
··· 40 40 return { lines, maxLineLength }; 41 41 }; 42 42 43 - export function printCliIntro() { 43 + // TODO: show ascii logo only in `--help` and `--version` commands 44 + export function printCliIntro(showLogo: boolean = false): void { 44 45 const packageJson = loadPackageJson(); 45 - const text = asciiToLines(textAscii, { padding: 1 }); 46 - for (const line of text.lines) { 47 - console.log(colors.cyan(line)); 46 + if (showLogo) { 47 + const text = asciiToLines(textAscii, { padding: 1 }); 48 + for (const line of text.lines) { 49 + console.log(colors.cyan(line)); 50 + } 48 51 } 49 52 console.log(colors.gray(`${packageJson.name} v${packageJson.version}`)); 50 53 console.log('');
packages/openapi-ts/src/utils/logger.ts packages/codegen-core/src/logger.ts
+6
packages/types/CHANGELOG.md
··· 1 1 # @hey-api/types 2 2 3 + ## 0.1.2 4 + 5 + ### Patch Changes 6 + 7 + - feat: add `ToArray`, `ToReadonlyArray`, and `AnyObject` types ([#3244](https://github.com/hey-api/openapi-ts/pull/3244)) ([`4f52bce`](https://github.com/hey-api/openapi-ts/commit/4f52bce79d8ac6573472f32a05b7f70913b90605)) by [@mrlubos](https://github.com/mrlubos) 8 + 3 9 ## 0.1.1 4 10 5 11 ### Patch Changes
+1 -1
packages/types/package.json
··· 1 1 { 2 2 "name": "@hey-api/types", 3 - "version": "0.1.1", 3 + "version": "0.1.2", 4 4 "description": "Shared utility types.", 5 5 "type": "module", 6 6 "types": "./src/index.ts",
+18 -2
packages/types/src/index.ts
··· 1 + /** 2 + * An object with string keys and unknown values. 3 + */ 4 + export type AnyObject = Record<string, unknown>; 5 + 1 6 /** 2 7 * Converts all top-level ReadonlyArray properties to Array (shallow). 3 8 */ 4 9 export type ArrayOnly<T> = { 5 - [K in keyof T]: T[K] extends ReadonlyArray<infer U> ? Array<U> : T[K]; 10 + [K in keyof T]: ToArray<T[K]>; 6 11 }; 7 12 8 13 /** ··· 42 47 * Converts all top-level Array properties to ReadonlyArray (shallow). 43 48 */ 44 49 export type ReadonlyArrayOnly<T> = { 45 - [K in keyof T]: T[K] extends Array<infer U> ? ReadonlyArray<U> : T[K]; 50 + [K in keyof T]: ToReadonlyArray<T[K]>; 46 51 }; 52 + 53 + /** 54 + * Converts ReadonlyArray<T> to Array<T>, preserving unions. 55 + */ 56 + export type ToArray<T> = T extends ReadonlyArray<infer U> ? Array<U> : T; 57 + 58 + /** 59 + * Converts Array<T> to ReadonlyArray<T>, preserving unions. 60 + */ 61 + export type ToReadonlyArray<T> = 62 + T extends ReadonlyArray<infer U> ? ReadonlyArray<U> : T;
+16 -41
pnpm-lock.yaml
··· 121 121 '@hey-api/codegen-core': 122 122 specifier: workspace:* 123 123 version: link:../packages/codegen-core 124 + '@hey-api/openapi-python': 125 + specifier: workspace:* 126 + version: link:../packages/openapi-python 124 127 '@hey-api/openapi-ts': 125 128 specifier: workspace:* 126 129 version: link:../packages/openapi-ts ··· 1250 1253 ansi-colors: 1251 1254 specifier: 4.1.3 1252 1255 version: 4.1.3 1256 + c12: 1257 + specifier: 3.3.3 1258 + version: 3.3.3(magicast@0.3.5) 1253 1259 color-support: 1254 1260 specifier: 1.1.3 1255 1261 version: 1.1.3 ··· 1332 1338 packages/openapi-python: 1333 1339 dependencies: 1334 1340 '@hey-api/codegen-core': 1335 - specifier: workspace:^0.5.2 1341 + specifier: workspace:^0.5.5 1336 1342 version: link:../codegen-core 1337 1343 '@hey-api/json-schema-ref-parser': 1338 1344 specifier: 1.2.2 ··· 1343 1349 ansi-colors: 1344 1350 specifier: 4.1.3 1345 1351 version: 4.1.3 1346 - c12: 1347 - specifier: 3.3.3 1348 - version: 3.3.3(magicast@0.3.5) 1349 1352 color-support: 1350 1353 specifier: 1.1.3 1351 1354 version: 1.1.3 ··· 1387 1390 packages/openapi-ts: 1388 1391 dependencies: 1389 1392 '@hey-api/codegen-core': 1390 - specifier: workspace:^0.5.2 1393 + specifier: workspace:^0.5.5 1391 1394 version: link:../codegen-core 1392 1395 '@hey-api/json-schema-ref-parser': 1393 1396 specifier: 1.2.2 ··· 1398 1401 ansi-colors: 1399 1402 specifier: 4.1.3 1400 1403 version: 4.1.3 1401 - c12: 1402 - specifier: 3.3.3 1403 - version: 3.3.3(magicast@0.3.5) 1404 1404 color-support: 1405 1405 specifier: 1.1.3 1406 1406 version: 1.1.3 ··· 7901 7901 resolution: {integrity: sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==} 7902 7902 peerDependencies: 7903 7903 magicast: ^0.3.5 7904 - peerDependenciesMeta: 7905 - magicast: 7906 - optional: true 7907 - 7908 - c12@3.3.2: 7909 - resolution: {integrity: sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A==} 7910 - peerDependencies: 7911 - magicast: '*' 7912 7904 peerDependenciesMeta: 7913 7905 magicast: 7914 7906 optional: true ··· 14690 14682 '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.2.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.2)) 14691 14683 ansi-colors: 4.1.3 14692 14684 autoprefixer: 10.4.20(postcss@8.5.2) 14693 - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) 14685 + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) 14694 14686 browserslist: 4.25.4 14695 14687 copy-webpack-plugin: 12.0.2(webpack@5.98.0) 14696 14688 css-loader: 7.1.2(webpack@5.98.0) ··· 14710 14702 picomatch: 4.0.2 14711 14703 piscina: 4.8.0 14712 14704 postcss: 8.5.2 14713 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) 14705 + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) 14714 14706 resolve-url-loader: 5.0.0 14715 14707 rxjs: 7.8.1 14716 14708 sass: 1.85.0 ··· 14778 14770 '@vitejs/plugin-basic-ssl': 1.2.0(vite@7.2.2(@types/node@22.10.5)(jiti@2.6.1)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)(yaml@2.8.2)) 14779 14771 ansi-colors: 4.1.3 14780 14772 autoprefixer: 10.4.20(postcss@8.5.2) 14781 - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) 14773 + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) 14782 14774 browserslist: 4.25.4 14783 14775 copy-webpack-plugin: 12.0.2(webpack@5.98.0) 14784 14776 css-loader: 7.1.2(webpack@5.98.0) ··· 14798 14790 picomatch: 4.0.2 14799 14791 piscina: 4.8.0 14800 14792 postcss: 8.5.2 14801 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) 14793 + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) 14802 14794 resolve-url-loader: 5.0.0 14803 14795 rxjs: 7.8.1 14804 14796 sass: 1.85.0 ··· 14886 14878 picomatch: 4.0.2 14887 14879 piscina: 4.8.0 14888 14880 postcss: 8.5.2 14889 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) 14881 + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) 14890 14882 resolve-url-loader: 5.0.0 14891 14883 rxjs: 7.8.1 14892 14884 sass: 1.85.0 ··· 19163 19155 '@nuxt/test-utils@3.21.0(@vue/test-utils@2.4.6)(jsdom@23.0.0)(magicast@0.3.5)(typescript@5.9.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.10.5)(jiti@2.6.1)(jsdom@23.0.0)(less@4.2.2)(sass@1.85.0)(terser@5.43.1)(yaml@2.8.2))': 19164 19156 dependencies: 19165 19157 '@nuxt/kit': 3.20.2(magicast@0.3.5) 19166 - c12: 3.3.2(magicast@0.3.5) 19158 + c12: 3.3.3(magicast@0.3.5) 19167 19159 consola: 3.4.2 19168 19160 defu: 6.1.4 19169 19161 destr: 2.0.5 ··· 22376 22368 schema-utils: 4.3.2 22377 22369 webpack: 5.98.0(esbuild@0.25.0) 22378 22370 22379 - babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)): 22371 + babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0): 22380 22372 dependencies: 22381 22373 '@babel/core': 7.26.9 22382 22374 find-cache-dir: 4.0.0 ··· 22593 22585 optionalDependencies: 22594 22586 magicast: 0.3.5 22595 22587 22596 - c12@3.3.2(magicast@0.3.5): 22597 - dependencies: 22598 - chokidar: 4.0.3 22599 - confbox: 0.2.2 22600 - defu: 6.1.4 22601 - dotenv: 17.2.3 22602 - exsolve: 1.0.8 22603 - giget: 2.0.0 22604 - jiti: 2.6.1 22605 - ohash: 2.0.11 22606 - pathe: 2.0.3 22607 - perfect-debounce: 2.0.0 22608 - pkg-types: 2.3.0 22609 - rc9: 2.1.2 22610 - optionalDependencies: 22611 - magicast: 0.3.5 22612 - 22613 22588 c12@3.3.3(magicast@0.3.5): 22614 22589 dependencies: 22615 22590 chokidar: 5.0.0 ··· 27866 27841 ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.9.3) 27867 27842 optional: true 27868 27843 27869 - postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)): 27844 + postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0): 27870 27845 dependencies: 27871 27846 cosmiconfig: 9.0.0(typescript@5.8.3) 27872 27847 jiti: 1.21.7