From aa24f41e7f3ac5929eb316c39b1040643ca9780a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:20:40 +0000 Subject: [PATCH 01/14] ci: release --- .changeset/cold-peas-speak.md | 6 ------ packages/codegen-core/CHANGELOG.md | 6 ++++++ packages/codegen-core/package.json | 2 +- packages/openapi-ts/CHANGELOG.md | 10 ++++++++++ packages/openapi-ts/package.json | 4 ++-- 5 files changed, 19 insertions(+), 9 deletions(-) delete mode 100644 .changeset/cold-peas-speak.md diff --git a/.changeset/cold-peas-speak.md b/.changeset/cold-peas-speak.md deleted file mode 100644 index a6a2aa41f..000000000 --- a/.changeset/cold-peas-speak.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hey-api/codegen-core': patch -'@hey-api/openapi-ts': patch ---- - -**deps**: move @hey-api/types to dependencies to fix broken types diff --git a/packages/codegen-core/CHANGELOG.md b/packages/codegen-core/CHANGELOG.md index d75c1cd8f..af0da88bd 100644 --- a/packages/codegen-core/CHANGELOG.md +++ b/packages/codegen-core/CHANGELOG.md @@ -1,5 +1,11 @@ # @hey-api/codegen-core +## 0.5.3 + +### Patch Changes + +- **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) + ## 0.5.2 ### Patch Changes diff --git a/packages/codegen-core/package.json b/packages/codegen-core/package.json index c8169646f..f2a596932 100644 --- a/packages/codegen-core/package.json +++ b/packages/codegen-core/package.json @@ -1,6 +1,6 @@ { "name": "@hey-api/codegen-core", - "version": "0.5.2", + "version": "0.5.3", "description": "๐Ÿงฑ TypeScript framework for generating files.", "homepage": "https://heyapi.dev/", "repository": { diff --git a/packages/openapi-ts/CHANGELOG.md b/packages/openapi-ts/CHANGELOG.md index 5ed15ddd8..78becbe66 100644 --- a/packages/openapi-ts/CHANGELOG.md +++ b/packages/openapi-ts/CHANGELOG.md @@ -1,5 +1,15 @@ # @hey-api/openapi-ts +## 0.90.7 + +### Patch Changes + +- **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) + +### Updated Dependencies: + +- @hey-api/codegen-core@0.5.3 + ## 0.90.6 ### Patch Changes diff --git a/packages/openapi-ts/package.json b/packages/openapi-ts/package.json index 1063dd65a..6ed772a19 100644 --- a/packages/openapi-ts/package.json +++ b/packages/openapi-ts/package.json @@ -1,6 +1,6 @@ { "name": "@hey-api/openapi-ts", - "version": "0.90.6", + "version": "0.90.7", "description": "๐ŸŒ€ OpenAPI to TypeScript codegen. Production-ready SDKs, Zod schemas, TanStack Query hooks, and 20+ plugins. Used by Vercel, OpenCode, and PayPal.", "homepage": "https://heyapi.dev/", "repository": { @@ -89,7 +89,7 @@ "node": ">=20.19.0" }, "dependencies": { - "@hey-api/codegen-core": "workspace:^0.5.2", + "@hey-api/codegen-core": "workspace:^0.5.3", "@hey-api/json-schema-ref-parser": "1.2.2", "@hey-api/types": "workspace:*", "ansi-colors": "4.1.3", -- 2.51.2 From f9cc5bc6c4da2fb216bb6a9ed72401116adc8f64 Mon Sep 17 00:00:00 2001 From: mrlubos <12529395+mrlubos@users.noreply.github.com> Date: Wed, 21 Jan 2026 16:22:04 +0000 Subject: [PATCH 02/14] chore: update lock file --- pnpm-lock.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index acb0fb6aa..bdbd9981c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1387,7 +1387,7 @@ importers: packages/openapi-ts: dependencies: '@hey-api/codegen-core': - specifier: workspace:^0.5.2 + specifier: workspace:^0.5.3 version: link:../codegen-core '@hey-api/json-schema-ref-parser': specifier: 1.2.2 @@ -14690,7 +14690,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14710,7 +14710,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14778,7 +14778,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14798,7 +14798,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14886,7 +14886,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -22376,7 +22376,7 @@ snapshots: schema-utils: 4.3.2 webpack: 5.98.0(esbuild@0.25.0) - babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)): + babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0): dependencies: '@babel/core': 7.26.9 find-cache-dir: 4.0.0 @@ -27866,7 +27866,7 @@ snapshots: ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.9.3) optional: true - postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)): + postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0): dependencies: cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.7 -- 2.51.2 From 88532f01200e1111818e82a4af7bd5cf8cf26e3b Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 22 Jan 2026 02:20:31 +0800 Subject: [PATCH 03/14] refactor: move logger to codegen-core --- .changeset/fuzzy-cobras-beam.md | 6 + .vscode/extensions.json | 2 +- .vscode/launch.json | 12 + dev/openapi-python.config.ts | 732 ++++++++++++++++++ dev/openapi-ts.config.ts | 2 - dev/package.json | 1 + docs/openapi-ts/configuration.md | 2 +- .../src/__tests__/exports.test.ts | 1 + .../src}/__tests__/logger.test.ts | 0 packages/codegen-core/src/index.ts | 1 + .../src/utils => codegen-core/src}/logger.ts | 0 packages/openapi-python/src/cli.ts | 24 +- packages/openapi-python/src/generate.ts | 135 ++++ packages/openapi-python/src/index.ts | 13 +- .../src/__tests__/interactive.test.ts | 2 +- .../src/config/__tests__/input.test.ts | 3 +- packages/openapi-ts/src/config/init.ts | 4 +- packages/openapi-ts/src/config/input.ts | 3 +- packages/openapi-ts/src/config/logs.ts | 2 +- packages/openapi-ts/src/config/merge.ts | 2 +- .../openapi-ts/src/config/output/config.ts | 2 +- packages/openapi-ts/src/config/parser.ts | 3 +- packages/openapi-ts/src/config/plugins.ts | 2 +- .../{types/config.d.ts => config/types.d.ts} | 8 +- packages/openapi-ts/src/createClient.ts | 4 +- packages/openapi-ts/src/generate.ts | 4 +- packages/openapi-ts/src/generate/client.ts | 2 +- packages/openapi-ts/src/index.ts | 6 +- .../src/ir/__tests__/pagination.test.ts | 2 +- packages/openapi-ts/src/ir/context.ts | 4 +- packages/openapi-ts/src/ir/pagination.ts | 2 +- .../2.0.x/parser/__tests__/validate.test.ts | 2 +- .../src/openApi/2.0.x/parser/filter.ts | 3 +- .../src/openApi/2.0.x/parser/validate.ts | 3 +- .../3.0.x/parser/__tests__/validate.test.ts | 2 +- .../src/openApi/3.0.x/parser/filter.ts | 3 +- .../src/openApi/3.0.x/parser/validate.ts | 3 +- .../3.1.x/parser/__tests__/validate.test.ts | 2 +- .../src/openApi/3.1.x/parser/filter.ts | 3 +- .../src/openApi/3.1.x/parser/validate.ts | 3 +- .../src/openApi/__tests__/index.test.ts | 2 +- packages/openapi-ts/src/openApi/index.ts | 5 +- .../src/openApi/shared/graph/meta.ts | 3 +- .../src/openApi/shared/transforms/enums.ts | 2 +- .../openApi/shared/transforms/readWrite.ts | 5 +- .../src/openApi/shared/utils/filter.ts | 5 +- .../src/openApi/shared/utils/graph.ts | 3 +- .../src/plugins/@hey-api/client-core/utils.ts | 2 +- pnpm-lock.yaml | 17 +- 49 files changed, 973 insertions(+), 81 deletions(-) create mode 100644 .changeset/fuzzy-cobras-beam.md create mode 100644 dev/openapi-python.config.ts rename packages/{openapi-ts/src/utils => codegen-core/src}/__tests__/logger.test.ts (100%) rename packages/{openapi-ts/src/utils => codegen-core/src}/logger.ts (100%) create mode 100644 packages/openapi-python/src/generate.ts rename packages/openapi-ts/src/{types/config.d.ts => config/types.d.ts} (93%) diff --git a/.changeset/fuzzy-cobras-beam.md b/.changeset/fuzzy-cobras-beam.md new file mode 100644 index 000000000..3ff6fd0a6 --- /dev/null +++ b/.changeset/fuzzy-cobras-beam.md @@ -0,0 +1,6 @@ +--- +'@hey-api/codegen-core': patch +'@hey-api/openapi-ts': patch +--- + +**internal**: move logger to codegen-core diff --git a/.vscode/extensions.json b/.vscode/extensions.json index eab4e3e33..10c86f442 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,9 +2,9 @@ "recommendations": [ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", + "esbenp.prettier-vscode", "hilleer.yaml-plus-json", "oxc.oxc-vscode", - "prettier.prettier-vscode", "streetsidesoftware.code-spell-checker", "usernamehw.errorlens" ] diff --git a/.vscode/launch.json b/.vscode/launch.json index e83d654b7..aa4048fe8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,6 +19,18 @@ "env": { "DEBUG": "false" } + }, + { + "type": "node", + "request": "launch", + "name": "openapi-python", + "skipFiles": ["/**"], + "cwd": "${workspaceFolder}/dev", + "runtimeExecutable": "node", + "program": "${workspaceFolder}/packages/openapi-python/dist/run.mjs", + "env": { + "DEBUG": "false" + } } ] } diff --git a/dev/openapi-python.config.ts b/dev/openapi-python.config.ts new file mode 100644 index 000000000..7914e5680 --- /dev/null +++ b/dev/openapi-python.config.ts @@ -0,0 +1,732 @@ +/* eslint-disable @typescript-eslint/no-unused-vars, arrow-body-style */ +// @ts-ignore +import path from 'node:path'; + +// @ts-ignore +import { customClientPlugin } from '@hey-api/custom-client/plugin'; +import { + // @ts-ignore + defineConfig, +} from '@hey-api/openapi-python'; +import { + // @ts-ignore + OperationPath, + // @ts-ignore + OperationStrategy, + // @ts-ignore + reserved, + // @ts-ignore + utils, +} from '@hey-api/openapi-ts'; + +// @ts-ignore +import { myClientPlugin } from '../packages/openapi-ts-tests/main/test/custom/client/plugin'; +// @ts-ignore +import { getSpecsPath } from '../packages/openapi-ts-tests/utils'; + +reserved.runtime.set((list) => [...list, 'Agent']); +reserved.type.set((list) => [...list, 'Agent']); + +console.log('yo'); + +// @ts-ignore +export default defineConfig(() => { + // ... + return [ + { + input: [ + { + // fetch: { + // headers: { + // 'x-foo': 'bar', + // }, + // }, + // path: { + // components: {}, + // info: { + // version: '1.0.0', + // }, + // openapi: '3.1.0', + // paths: {}, + // }, + path: path.resolve( + getSpecsPath(), + // '2.0.x', + // '3.0.x', + '3.1.x', + // 'circular.yaml', + // 'dutchie.json', + // 'enum-names-values.yaml', + // 'full.yaml', + // 'integer-formats.yaml', + // 'invalid', + // 'object-property-names.yaml', + // 'openai.yaml', + // 'opencode.yaml', + // 'pagination-ref.yaml', + // 'schema-const.yaml', + // 'sdk-instance.yaml', + // 'sdk-method-class-conflict.yaml', + // 'sdk-nested-classes.yaml', + // 'sdk-nested-conflict.yaml', + 'security-api-key.yaml', + // 'string-with-format.yaml', + // 'transformers.json', + // 'transformers-recursive.json', + // 'type-format.yaml', + // 'validators.yaml', + // 'validators-circular-ref.json', + // 'validators-circular-ref-2.yaml', + // 'zoom-video-sdk.json', + ), + // path: 'https://get.heyapi.dev/hey-api/backend?branch=main&version=1.0.0', + // path: 'http://localhost:4000/', + // path: 'http://localhost:8000/openapi.json', + // path: 'https://mongodb-mms-prod-build-server.s3.amazonaws.com/openapi/2caffd88277a4e27c95dcefc7e3b6a63a3b03297-v2-2023-11-15.json', + // path: 'https://raw.githubusercontent.com/swagger-api/swagger-petstore/master/src/main/resources/openapi.yaml', + // watch: { + // enabled: true, + // interval: 500, + // timeout: 30_000, + // }, + }, + // path.resolve(getSpecsPath(), '3.1.x', 'full.yaml'), + // { + // branch: 'main', + // organization: 'hey-api', + // path: 'hey-api/backend', + // project: 'backend', + // project: 'upload-openapi-spec', + // version: '1.0.0', + // }, + // 'hey-api/backend?branch=main&version=1.0.0', + // 'scalar:@scalar/access-service', + // 'readme:@developers/v2.0#nysezql0wwo236', + // 'readme:nysezql0wwo236', + // 'https://dash.readme.com/api/v1/api-registry/nysezql0wwo236', + // 'https://somefakedomain.com/openapi.yaml', + ], + logs: { + // level: 'debug', + path: './logs', + }, + output: [ + { + // case: 'snake_case', + // clean: true, + // fileName: { + // // case: 'snake_case', + // // name: '{{name}}.renamed', + // suffix: '.meh', + // }, + // header: null, + header: [ + '/* eslint-disable */', + '// This file is auto-generated by @hey-api/openapi-ts', + ], + // importFileExtension: '.js', + // indexFile: false, + // nameConflictResolver({ attempt, baseName }) { + // // console.log('resolving conflict for:', { attempt, baseName }); + // return attempt === 0 ? baseName : `${baseName}_N${attempt + 1}`; + // }, + path: path.resolve(__dirname, '.gen'), + postProcess: ['eslint'], + // preferExportAll: true, + resolveModuleName: (moduleName) => { + if (moduleName === 'valibot') { + return 'valibot'; + } + return; + }, + source: { + // callback(source) { + // console.log('Source generated, length:', source.length); + // }, + enabled: false, + // extension: 'yaml', + // fileName: 'spec', + // path: null, + // serialize(input) { + // return JSON.stringify(input, null, 0); + // }, + }, + // tsConfigPath: path.resolve( + // __dirname, + // 'tsconfig', + // 'tsconfig.nodenext.json', + // ), + }, + // '.gen', + ], + parser: { + filters: { + // deprecated: false, + operations: { + include: [ + // 'GET /event', + // '/^[A-Z]+ /v1//', + ], + }, + // orphans: true, + // preserveOrder: true, + // schemas: { + // include: ['Foo'], + // }, + // tags: { + // exclude: ['bar'], + // }, + }, + hooks: { + events: { + // 'node:set:after': ({ node, plugin }) => { + // if (node) { + // console.log(`(${plugin.name}) set node:`, node.symbol); + // } + // }, + // 'node:set:before': ({ node, plugin }) => { + // console.log(`(${plugin.name}) setting node:`, node?.symbol?.id); + // }, + // 'plugin:handler:after': ({ plugin }) => { + // console.log(`(${plugin.name}): handler finished`); + // }, + // 'plugin:handler:before': ({ plugin }) => { + // console.log(`(${plugin.name}): handler starting`); + // }, + // 'symbol:register:after': ({ plugin, symbol }) => { + // console.log(`(global, ${plugin.name}) registered:`, symbol.id); + // }, + 'symbol:register:before': ({ plugin, symbol }) => { + // if (!symbol.external && !symbol.meta?.resourceType) { + // console.log(`[${plugin.name}]:`, symbol.name); + // } + if (plugin && !symbol.external && !symbol.meta?.path) { + // console.log(`[${plugin.name}]:`, symbol.name, symbol.meta); + } + // if (symbol.meta?.tags && symbol.meta?.tags.size > 0) { + // console.log( + // `[${plugin.name}]:`, + // symbol.name, + // symbol.meta.path, + // symbol.meta.tags, + // ); + // } + }, + }, + operations: { + getKind() { + // noop + }, + isMutation() { + // noop + }, + isQuery: (op) => { + if (op.method === 'post' && op.path === '/search') { + return true; + } + return; + }, + }, + symbols: { + // getFilePath: (symbol) => { + // if (symbol.name) { + // return symbol.name[0]?.toLowerCase(); + // } + // return; + // }, + }, + }, + pagination: { + // keywords: ['aa'], + }, + patch: { + // operations: { + // 'GET /foo': (operation: any) => { + // operation.responses['200'].description = 'foo'; + // }, + // }, + // version: () => '3.1.1', + }, + transforms: { + // enums: { + // enabled: false, + // mode: 'root', + // // name: '{{name}}', + // }, + // propertiesRequiredByDefault: true, + // readWrite: { + // // enabled: false, + // requests: '{{name}}Writable', + // responses: '{{name}}', + // }, + }, + // validate_EXPERIMENTAL: true, + }, + plugins: [ + // customClientPlugin({ + // baseUrl: false, + // }), + // myClientPlugin(), + { + // baseUrl: false, + // exportFromIndex: true, + // name: '@hey-api/client-angular', + // runtimeConfigPath: path.resolve(__dirname, 'hey-api.ts'), + // runtimeConfigPath: './src/hey-api.ts', + // strictBaseUrl: true, + throwOnError: true, + }, + { + // case: 'snake_case', + // definitions: 'ไฝ _snake_{{name}}', + enums: { + // case: 'PascalCase', + // constantsIgnoreNull: true, + // enabled: false, + // mode: 'typescript', + }, + // errors: { + // error: 'ไป–ๅ€‘_error_{{name}}', + // name: 'ไฝ ๅ€‘_errors_{{name}}', + // }, + // exportFromIndex: false, + // name: '@hey-api/typescript', + // requests: 'ๆˆ‘ๅ€‘_data_{{name}}', + // responses: { + // name: 'ๆˆ‘_responses_{{name}}', + // response: 'ไป–_response_{{name}}', + // }, + // topType: 'any', + // tree: true, + // webhooks: { + // name: 'Webby{{name}}Hook', + // payload: '{{name}}WebhookEvent', + // }, + }, + { + // auth: false, + // client: false, + examples: { + // enabled: false, + importKind: 'default', + importName: 'CatStore', + importSetup: ({ $, node }) => + $.new( + node.name, + $.object().pretty().prop('apiKey', $.literal('YOUR_API_KEY')), + ), + // language: 'TypeScript', + moduleName: '@petstore/client', + payload(operation, ctx) { + const { $ } = ctx; + if ( + operation.path === '/pet/{petId}' || + operation.path === '/pet' + ) { + return $.object().pretty().prop('petId', $.literal(1234)); + } + return; + }, + setupName: 'client', + // transform: (example) => example.replace(/\({}\)/, '({\n ...\n})'), + }, + // getSignature: ({ fields, signature, operation }) => { + // // ... + // fields.unwrap('path') + // }, + // include... + name: '@hey-api/sdk', + operations: { + // container: 'object', + // containerName: { + // // casing: 'snake_case', + // name: 'OpencodeClient', + // }, + containerName: 'PetStore', + // nesting(operation) { + // if (operation.path === '/pet/{petId}' || operation.path === '/pet') { + // return ['pet', operation.operationId?.replace(/Pet/, '') || operation.method.toLocaleLowerCase()]; + // } + // return OperationPath.fromOperationId()(operation); + // }, + // methodName: { + // // casing: 'SCREAMING_SNAKE_CASE', + // name: '{{name}}Yummy', + // }, + // methods: 'static', + // nesting: 'id', + // segmentName: '{{name}}Seggy', + strategy: 'single', + // strategy(operation) { + // const locations = OperationStrategy.byTags({ + // fallback: 'default', + // path: OperationPath.fromOperationId({ + // delimiters: /[./]/, + // }), + // })(operation) + // return locations.map((loc) => { + // if (loc[0] && loc[1] && loc[0] === loc[1]) { + // return loc.slice(1); + // } + // return loc; + // }); + // }, + // strategyDefaultTag: 'DaxLikedThis', + }, + paramsStructure: 'flat', + // responseStyle: 'data', + // signature: 'auto', + // signature: 'client', + // signature: 'object', + // transformer: '@hey-api/transformers', + // transformer: true, + // validator: 'valibot', + // validator: { + // request: 'zod', + // response: 'zod', + // }, + '~hooks': { + symbols: { + // getFilePath: (symbol) => { + // if (symbol.name) { + // return utils.toCase(symbol.name, 'camelCase'); + // } + // return; + // }, + }, + }, + }, + { + // bigInt: true, + dates: true, + // name: '@hey-api/transformers', + }, + { + // name: 'fastify', + }, + { + // name: 'swr', + }, + { + // case: 'SCREAMING_SNAKE_CASE', + // comments: false, + exportFromIndex: true, + infiniteQueryKeys: { + // name: '{{name}}IQK', + // name: 'options', + }, + infiniteQueryOptions: { + meta() { + return { + custom: 'value', + }; + }, + // name: '{{name}}IQO', + // name: 'options', + }, + mutationOptions: { + meta() { + return { + custom: 'value', + }; + }, + // name: '{{name}}MO', + // name: 'options', + }, + // name: '@tanstack/react-query', + queryKeys: { + // name: '{{name}}QK', + // name: 'options', + tags: true, + }, + // queryOptions: false, + queryOptions: { + // meta() { + // return { + // custom: 'value', + // } + // }, + // name: '{{name}}QO', + // name: 'options', + }, + useQuery: true, + '~hooks': { + operations: { + getKind: (op) => { + if (op.method === 'post' && op.path === '/search') { + return ['query']; + } + return; + }, + isMutation() { + // noop + }, + isQuery: () => { + // noop + }, + }, + }, + }, + { + // name: 'arktype', + types: { + infer: true, + }, + }, + { + // case: 'SCREAMING_SNAKE_CASE', + // comments: false, + // definitions: 'z{{name}}', + exportFromIndex: true, + // metadata: true, + // name: 'valibot', + // requests: { + // case: 'PascalCase', + // name: '{{name}}Data', + // }, + // responses: { + // // case: 'snake_case', + // name: 'z{{name}}TestResponse', + // }, + // webhooks: { + // name: 'q{{name}}CoolWebhook', + // }, + '~hooks': { + events: { + // 'symbol:register:after': ({ plugin, symbol }) => { + // console.log(`(${plugin.name}) registered:`, symbol.id); + // }, + // 'symbol:register:before': ({ plugin, symbol }) => { + // console.log(`(${plugin.name}):`, symbol.name); + // }, + }, + symbols: { + // getFilePath: (symbol) => { + // if (symbol.name) { + // return utils.toCase(symbol.name, 'camelCase'); + // } + // return; + // }, + }, + }, + '~resolvers': { + // number(ctx) { + // const { $, plugin, symbols } = ctx; + // const { v } = symbols; + // // ctx.nodes.base = () => { + // // // implement custom base number resolver + // // } + // const big = plugin.symbolOnce('Big', { + // external: 'big.js', + // importKind: 'default', + // }); + // return $(v).attr('instance').call(big); + // }, + object(ctx) { + const { $, symbols } = ctx; + const { v } = symbols; + const additional = ctx.nodes.additionalProperties(ctx); + if (additional === undefined) { + const shape = ctx.nodes.shape(ctx); + ctx.nodes.base = () => $(v).attr('looseObject').call(shape); + } + }, + // string(ctx) { + // const { $, schema, symbols } = ctx; + // const { v } = symbols; + // if (schema.format === 'date' || schema.format === 'date-time') { + // ctx.nodes.format = () => $(v).attr('isoDateTime').call(); + // } + // }, + // validator(ctx) { + // const { $, plugin, symbols } = ctx; + // const { schema, v } = symbols; + // const vShadow = plugin.symbol('v'); + // const test = plugin.symbol('test'); + // const e = plugin.symbol('err'); + // return [ + // $.const(vShadow).assign($.literal('hi')), + // $('console').attr('log').call(vShadow), + // $.try( + // $.const(test).assign($.literal('test')), + // $('console').attr('log').call($.literal('hi'), test), + // ).catchArg(e), + // $.const('parsed').assign( + // $(v).attr('safeParseAsync').call(schema, 'data').await(), + // ), + // $('parsed').return(), + // ]; + // }, + }, + }, + { + // case: 'snake_case', + // comments: false, + compatibilityVersion: 'mini', + dates: { + // local: true, + // offset: true, + }, + definitions: { + // name: 'z{{name}}Definition', + // types: { + // infer: 'D{{name}}ZodType', + // }, + }, + exportFromIndex: true, + metadata: true, + // name: 'zod', + // requests: { + // // case: 'SCREAMING_SNAKE_CASE', + // // name: 'z{{name}}TestData', + // types: { + // infer: 'E{{name}}DataZodType', + // }, + // }, + responses: { + // case: 'snake_case', + // name: (name) => { + // if (name === 'complexTypes') { + // return 'z'; + // } + // return 'z{{name}}Response'; + // }, + // types: { + // infer: 'F{{name}}ResponseZodType', + // }, + }, + types: { + // infer: { + // case: 'snake_case', + // }, + }, + '~hooks': { + symbols: { + // getFilePath: (symbol) => { + // if (symbol.name === 'z') { + // return 'complexService'; + // } + // return; + // }, + }, + }, + '~resolvers': { + // number(ctx) { + // const { $, plugin, symbols } = ctx; + // const { z } = symbols; + // // ctx.nodes.base = () => { + // // // implement custom base number resolver + // // } + // const big = plugin.symbolOnce('Big', { + // external: 'big.js', + // importKind: 'default', + // }); + // return $(z).attr('instanceof').call(big); + // }, + // object(ctx) { + // const { $, symbols } = ctx; + // const { z } = symbols; + // const additional = ctx.nodes.additionalProperties(ctx); + // if (additional === undefined) { + // const shape = ctx.nodes.shape(ctx); + // // return $('z').attr('object').call(shape).attr('passthrough').call() + // ctx.nodes.base = () => + // $(z).attr('object').call(shape).attr('strict').call(); + // } + // }, + // string(ctx) { + // const { $, schema, symbols } = ctx; + // const { z } = symbols; + // if (schema.format === 'date' || schema.format === 'date-time') { + // ctx.nodes.format = () => $(z).attr('date').call(); + // } + // if (schema.format === 'int64') { + // ctx.nodes.format = () => + // $(z) + // .attr('string') + // .call() + // .attr('refine') + // .call( + // $.func() + // .param('val') + // .do( + // $.try( + // $(z) + // .attr('int64') + // .call() + // .attr('parse') + // .call($('BigInt').call('val')), + // $.return($.literal(true)), + // ).catch($.return($.literal(false))), + // ), + // $.object().prop( + // 'message', + // $.literal('Must be a valid int64 string'), + // ), + // ); + // } + // }, + // validator({ $, schema }) { + // return [ + // $.const('parsed').assign( + // $(schema).attr('safeParseAsync').call('data').await(), + // ), + // $('parsed').return(), + // ]; + // }, + }, + }, + { + exportFromIndex: true, + // name: '@hey-api/schemas', + // type: 'json', + }, + { + exportFromIndex: true, + httpRequests: { + // enabled: false, + // containerName: 'AngularTest', + // segmentName: '{{name}}Seggy', + // strategy: 'byTags', + }, + httpResources: { + // enabled: false, + // segmentName: '{{name}}Seggy', + strategy: 'byTags', + }, + // name: '@angular/common', + }, + { + exportFromIndex: true, + // mutationOptions: '{{name}}Mutationssss', + // name: '@pinia/colada', + // queryOptions: { + // name: '{{name}}Queryyyyy', + // }, + queryKeys: { + tags: true, + }, + '~hooks': { + operations: { + getKind: (op) => { + if (op.method === 'post' && op.path === '/search') { + return ['query']; + } + return; + }, + }, + }, + }, + ], + // watch: 3_000, + }, + // { + // input: 'scalar:@scalar/access-service', + // logs: { + // // level: 'debug', + // path: './logs', + // }, + // output: '.gen', + // }, + ]; +}); diff --git a/dev/openapi-ts.config.ts b/dev/openapi-ts.config.ts index f7f75e19b..521880cdc 100644 --- a/dev/openapi-ts.config.ts +++ b/dev/openapi-ts.config.ts @@ -30,7 +30,6 @@ export default defineConfig(() => { // ... return [ { - // experimentalParser: false, input: [ { // fetch: { @@ -107,7 +106,6 @@ export default defineConfig(() => { // level: 'debug', path: './logs', }, - // name: 'foo', output: [ { // case: 'snake_case', diff --git a/dev/package.json b/dev/package.json index 7ef608e67..bc238c5c9 100644 --- a/dev/package.json +++ b/dev/package.json @@ -13,6 +13,7 @@ "@angular/common": "19.2.17", "@angular/core": "19.2.17", "@hey-api/codegen-core": "workspace:*", + "@hey-api/openapi-python": "workspace:*", "@hey-api/openapi-ts": "workspace:*", "@opencode-ai/sdk": "1.0.221", "@pinia/colada": "0.19.1", diff --git a/docs/openapi-ts/configuration.md b/docs/openapi-ts/configuration.md index b44ec1d9d..b9198c2bf 100644 --- a/docs/openapi-ts/configuration.md +++ b/docs/openapi-ts/configuration.md @@ -242,7 +242,7 @@ src/ ## API -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. +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. diff --git a/packages/codegen-core/src/__tests__/exports.test.ts b/packages/codegen-core/src/__tests__/exports.test.ts index 0e4b19392..ca2c3cc5a 100644 --- a/packages/codegen-core/src/__tests__/exports.test.ts +++ b/packages/codegen-core/src/__tests__/exports.test.ts @@ -14,6 +14,7 @@ const constExports = [ 'isSymbol', 'isSymbolRef', 'log', + 'Logger', 'nodeBrand', 'Project', 'ref', diff --git a/packages/openapi-ts/src/utils/__tests__/logger.test.ts b/packages/codegen-core/src/__tests__/logger.test.ts similarity index 100% rename from packages/openapi-ts/src/utils/__tests__/logger.test.ts rename to packages/codegen-core/src/__tests__/logger.test.ts diff --git a/packages/codegen-core/src/index.ts b/packages/codegen-core/src/index.ts index e3a89641d..924115f4a 100644 --- a/packages/codegen-core/src/index.ts +++ b/packages/codegen-core/src/index.ts @@ -20,6 +20,7 @@ export type { NameConflictResolvers, } from './languages/types'; export { log } from './log'; +export { Logger } from './logger'; export type { INode as Node, NodeName, diff --git a/packages/openapi-ts/src/utils/logger.ts b/packages/codegen-core/src/logger.ts similarity index 100% rename from packages/openapi-ts/src/utils/logger.ts rename to packages/codegen-core/src/logger.ts diff --git a/packages/openapi-python/src/cli.ts b/packages/openapi-python/src/cli.ts index dec41bd98..675e2e9e3 100644 --- a/packages/openapi-python/src/cli.ts +++ b/packages/openapi-python/src/cli.ts @@ -1,7 +1,8 @@ import type { OptionValues } from 'commander'; import { Command } from 'commander'; -// import { createClient } from '~/index'; +import { createClient } from '~/index'; + import pkg from '../package.json' assert { type: 'json' }; const stringToBoolean = ( @@ -103,17 +104,16 @@ export const runCli = async (): Promise => { delete userConfig.logs; } - // const context = await createClient( - // userConfig as unknown as Required>[0], - // ); - // if ( - // !context[0]?.config.input.some( - // (input) => input.watch && input.watch.enabled, - // ) - // ) { - // process.exit(0); - // } - process.exit(0); + const context = await createClient( + userConfig as unknown as Required>[0], + ); + if ( + !context[0]?.config.input.some( + (input) => input.watch && input.watch.enabled, + ) + ) { + process.exit(0); + } } catch { process.exit(1); } diff --git a/packages/openapi-python/src/generate.ts b/packages/openapi-python/src/generate.ts new file mode 100644 index 000000000..6951430d9 --- /dev/null +++ b/packages/openapi-python/src/generate.ts @@ -0,0 +1,135 @@ +import { Logger } from '@hey-api/codegen-core'; +import type { LazyOrAsync, MaybeArray } from '@hey-api/types'; + +// import { checkNodeVersion } from '~/config/engine'; +// import type { Configs } from '~/config/init'; +// import { initConfigs } from '~/config/init'; +// import { getLogs } from '~/config/logs'; +import type { UserConfig } from '~/config/types'; +// import { createClient as pCreateClient } from '~/createClient'; +// import { +// ConfigValidationError, +// JobError, +// logCrashReport, +// openGitHubIssueWithCrashReport, +// printCrashReport, +// shouldReportCrash, +// } from '~/error'; +// import type { Context } from '~/ir/context'; +// import { printCliIntro } from '~/utils/cli'; + +type Context = { + config: { + input: any[]; + }; +}; + +/** + * Generate a client from the provided configuration. + * + * @param userConfig User provided {@link UserConfig} configuration(s). + */ +export const createClient = async ( + userConfig?: LazyOrAsync>, + logger = new Logger(), +): Promise> => { + const resolvedConfig = + typeof userConfig === 'function' ? await userConfig() : userConfig; + const userConfigs = resolvedConfig + ? resolvedConfig instanceof Array + ? resolvedConfig + : [resolvedConfig] + : []; + + console.log(userConfigs, logger); + return []; + // let rawLogs = userConfigs.find( + // (config) => getLogs(config).level !== 'silent', + // )?.logs; + // if (typeof rawLogs === 'string') { + // rawLogs = getLogs({ logs: rawLogs }); + // } + + // let configs: Configs | undefined; + + // try { + // checkNodeVersion(); + + // const eventCreateClient = logger.timeEvent('createClient'); + + // const eventConfig = logger.timeEvent('config'); + // configs = await initConfigs({ logger, userConfigs }); + // const printIntro = configs.results.some( + // (result) => result.config.logs.level !== 'silent', + // ); + // if (printIntro) { + // printCliIntro(); + // } + // eventConfig.timeEnd(); + + // const allConfigErrors = configs.results.flatMap((result) => + // result.errors.map((error) => ({ error, jobIndex: result.jobIndex })), + // ); + // if (allConfigErrors.length) { + // throw new ConfigValidationError(allConfigErrors); + // } + + // const clients = await Promise.all( + // configs.results.map(async (result) => { + // try { + // return await pCreateClient({ + // config: result.config, + // dependencies: configs!.dependencies, + // jobIndex: result.jobIndex, + // logger, + // }); + // } catch (error) { + // throw new JobError('', { + // error, + // jobIndex: result.jobIndex, + // }); + // } + // }), + // ); + // const result = clients.filter((client) => + // Boolean(client), + // ) as ReadonlyArray; + + // eventCreateClient.timeEnd(); + + // const printLogs = configs.results.some( + // (result) => result.config.logs.level === 'debug', + // ); + // logger.report(printLogs); + + // return result; + // } catch (error) { + // const results = configs?.results ?? []; + + // const logs = + // results.find((result) => result.config.logs.level !== 'silent')?.config + // .logs ?? + // results[0]?.config.logs ?? + // rawLogs; + // const dryRun = + // results.some((result) => result.config.dryRun) ?? + // userConfigs.some((config) => config.dryRun) ?? + // false; + // const logPath = + // logs?.file && !dryRun + // ? logCrashReport(error, logs.path ?? '') + // : undefined; + // if (!logs || logs.level !== 'silent') { + // printCrashReport({ error, logPath }); + // const isInteractive = + // results.some((result) => result.config.interactive) ?? + // userConfigs.some((config) => config.interactive) ?? + // false; + // if (await shouldReportCrash({ error, isInteractive })) { + // await openGitHubIssueWithCrashReport(error); + // } + // } + + // throw error; + // } +}; diff --git a/packages/openapi-python/src/index.ts b/packages/openapi-python/src/index.ts index 70ca11977..f2bc208d1 100644 --- a/packages/openapi-python/src/index.ts +++ b/packages/openapi-python/src/index.ts @@ -76,7 +76,7 @@ import type { UserConfig } from '~/config/types'; colors.enabled = colorSupport().hasBasic; -// export { createClient } from '~/generate'; +export { createClient } from '~/generate'; /** * Type helper for openapi-ts.config.ts, returns {@link MaybeArray} object(s) @@ -85,8 +85,10 @@ export const defineConfig = async >( config: LazyOrAsync, ): Promise => (typeof config === 'function' ? await config() : config); +export { Logger } from '@hey-api/codegen-core'; // export { defaultPaginationKeywords } from '~/config/parser'; // export { defaultPlugins } from '~/config/plugins'; +export type { UserConfig } from '~/config/types'; // export type { IR } from '~/ir/types'; // export { OperationPath, OperationStrategy } from '~/openApi/shared/locations'; // export type { @@ -99,8 +101,6 @@ export const defineConfig = async >( // OpenApiSchemaObject, // } from '~/openApi/types'; // export type { DefinePlugin, Plugin } from '~/plugins'; -// export type { AngularClient } from '~/plugins/@hey-api/client-angular'; -// export type { AxiosClient } from '~/plugins/@hey-api/client-axios'; // export { // clientDefaultConfig, // clientDefaultMeta, @@ -108,13 +108,6 @@ export const defineConfig = async >( // export { clientPluginHandler } from '~/plugins/@hey-api/client-core/plugin'; // export type { Client } from '~/plugins/@hey-api/client-core/types'; // export type { FetchClient } from '~/plugins/@hey-api/client-fetch'; -// export type { NextClient } from '~/plugins/@hey-api/client-next'; -// export type { NuxtClient } from '~/plugins/@hey-api/client-nuxt'; -// export type { OfetchClient } from '~/plugins/@hey-api/client-ofetch'; -// export type { ExpressionTransformer } from '~/plugins/@hey-api/transformers/expressions'; -// export type { TypeTransformer } from '~/plugins/@hey-api/transformers/types'; // export { definePluginConfig } from '~/plugins/shared/utils/config'; // export * from '~/ts-dsl'; -// export type { UserConfig } from '~/types/config'; // export { utils } from '~/utils/exports'; -// export { Logger } from '~/utils/logger'; diff --git a/packages/openapi-ts/src/__tests__/interactive.test.ts b/packages/openapi-ts/src/__tests__/interactive.test.ts index 7812c9546..766ca3e6a 100644 --- a/packages/openapi-ts/src/__tests__/interactive.test.ts +++ b/packages/openapi-ts/src/__tests__/interactive.test.ts @@ -1,8 +1,8 @@ +import { Logger } from '@hey-api/codegen-core'; import { afterEach, describe, expect, it } from 'vitest'; import { detectInteractiveSession, initConfigs } from '~/config/init'; import { mergeConfigs } from '~/config/merge'; -import { Logger } from '~/utils/logger'; describe('interactive config', () => { it('should use detectInteractiveSession when not provided', async () => { diff --git a/packages/openapi-ts/src/config/__tests__/input.test.ts b/packages/openapi-ts/src/config/__tests__/input.test.ts index c71da149f..c9f27d548 100644 --- a/packages/openapi-ts/src/config/__tests__/input.test.ts +++ b/packages/openapi-ts/src/config/__tests__/input.test.ts @@ -1,8 +1,7 @@ import { describe, expect, it } from 'vitest'; -import type { UserConfig } from '~/types/config'; - import { getInput } from '../input'; +import type { UserConfig } from '../types'; describe('input config', () => { describe('getInput', () => { diff --git a/packages/openapi-ts/src/config/init.ts b/packages/openapi-ts/src/config/init.ts index 22ad45d8e..7068e3368 100644 --- a/packages/openapi-ts/src/config/init.ts +++ b/packages/openapi-ts/src/config/init.ts @@ -1,11 +1,10 @@ import path from 'node:path'; +import type { Logger } from '@hey-api/codegen-core'; import type { ArrayOnly } from '@hey-api/types'; import colors from 'ansi-colors'; import { ConfigError } from '~/error'; -import type { Config, UserConfig } from '~/types/config'; -import type { Logger } from '~/utils/logger'; import { getInput } from './input'; import { getLogs } from './logs'; @@ -14,6 +13,7 @@ import { getOutput } from './output'; import { getProjectDependencies } from './packages'; import { getParser } from './parser'; import { getPlugins } from './plugins'; +import type { Config, UserConfig } from './types'; type ConfigResult = { config: Config; diff --git a/packages/openapi-ts/src/config/input.ts b/packages/openapi-ts/src/config/input.ts index 2aea3667d..8bd629683 100644 --- a/packages/openapi-ts/src/config/input.ts +++ b/packages/openapi-ts/src/config/input.ts @@ -1,8 +1,9 @@ -import type { Config, UserConfig } from '~/types/config'; import type { Input, Watch } from '~/types/input'; import { inputToApiRegistry } from '~/utils/input'; import { heyApiRegistryBaseUrl } from '~/utils/input/heyApi'; +import type { Config, UserConfig } from './types'; + const defaultWatch: Watch = { enabled: false, interval: 1_000, diff --git a/packages/openapi-ts/src/config/logs.ts b/packages/openapi-ts/src/config/logs.ts index 91d5742c5..c05448aed 100644 --- a/packages/openapi-ts/src/config/logs.ts +++ b/packages/openapi-ts/src/config/logs.ts @@ -1,4 +1,4 @@ -import type { Config, UserConfig } from '~/types/config'; +import type { Config, UserConfig } from './types'; export const getLogs = ( userConfig: Pick | undefined, diff --git a/packages/openapi-ts/src/config/merge.ts b/packages/openapi-ts/src/config/merge.ts index 5dc7b3a86..a4ce11067 100644 --- a/packages/openapi-ts/src/config/merge.ts +++ b/packages/openapi-ts/src/config/merge.ts @@ -1,4 +1,4 @@ -import type { UserConfig } from '~/types/config'; +import type { UserConfig } from './types'; const mergeObjects = ( objA: Record | undefined, diff --git a/packages/openapi-ts/src/config/output/config.ts b/packages/openapi-ts/src/config/output/config.ts index 1002bf550..3a3ea949b 100644 --- a/packages/openapi-ts/src/config/output/config.ts +++ b/packages/openapi-ts/src/config/output/config.ts @@ -2,8 +2,8 @@ import { log } from '@hey-api/codegen-core'; import ts from 'typescript'; import { findTsConfigPath, loadTsConfig } from '~/generate/tsConfig'; -import type { Config, UserConfig } from '~/types/config'; +import type { Config, UserConfig } from '../types'; import { valueToObject } from '../utils/config'; import type { PostProcessor, UserPostProcessor } from './postprocess'; import { postProcessors } from './postprocess'; diff --git a/packages/openapi-ts/src/config/parser.ts b/packages/openapi-ts/src/config/parser.ts index 82aeb2f39..55166bab7 100644 --- a/packages/openapi-ts/src/config/parser.ts +++ b/packages/openapi-ts/src/config/parser.ts @@ -1,5 +1,4 @@ -import type { Config, UserConfig } from '~/types/config'; - +import type { Config, UserConfig } from './types'; import { valueToObject } from './utils/config'; export const defaultPaginationKeywords = [ diff --git a/packages/openapi-ts/src/config/plugins.ts b/packages/openapi-ts/src/config/plugins.ts index 17a8444a3..dea32587a 100644 --- a/packages/openapi-ts/src/config/plugins.ts +++ b/packages/openapi-ts/src/config/plugins.ts @@ -4,8 +4,8 @@ import type { PluginContext, PluginNames, } from '~/plugins/types'; -import type { Config, UserConfig } from '~/types/config'; +import type { Config, UserConfig } from './types'; import { valueToObject } from './utils/config'; import { packageFactory } from './utils/package'; diff --git a/packages/openapi-ts/src/types/config.d.ts b/packages/openapi-ts/src/config/types.d.ts similarity index 93% rename from packages/openapi-ts/src/types/config.d.ts rename to packages/openapi-ts/src/config/types.d.ts index 791c13141..940423b18 100644 --- a/packages/openapi-ts/src/types/config.d.ts +++ b/packages/openapi-ts/src/config/types.d.ts @@ -1,13 +1,13 @@ import type { MaybeArray } from '@hey-api/types'; -import type { Output, UserOutput } from '~/config/output'; import type { Plugin } from '~/plugins'; import type { PluginConfigMap } from '~/plugins/config'; import type { PluginNames } from '~/plugins/types'; +import type { Input, UserInput, Watch } from '~/types/input'; +import type { Logs } from '~/types/logs'; +import type { Parser, UserParser } from '~/types/parser'; -import type { Input, UserInput, Watch } from './input'; -import type { Logs } from './logs'; -import type { Parser, UserParser } from './parser'; +import type { Output, UserOutput } from './output'; export interface UserConfig { /** diff --git a/packages/openapi-ts/src/createClient.ts b/packages/openapi-ts/src/createClient.ts index ea4ca80bc..0bfa3511b 100644 --- a/packages/openapi-ts/src/createClient.ts +++ b/packages/openapi-ts/src/createClient.ts @@ -1,19 +1,19 @@ import path from 'node:path'; +import type { Logger } from '@hey-api/codegen-core'; import { $RefParser } from '@hey-api/json-schema-ref-parser'; import colors from 'ansi-colors'; import { postprocessOutput } from '~/config/output'; +import type { Config } from '~/config/types'; import { generateOutput } from '~/generate/output'; import { getSpec } from '~/getSpec'; import type { Context } from '~/ir/context'; import { parseOpenApiSpec } from '~/openApi'; import { buildGraph } from '~/openApi/shared/utils/graph'; import { patchOpenApiSpec } from '~/openApi/shared/utils/patch'; -import type { Config } from '~/types/config'; import type { Input } from '~/types/input'; import type { WatchValues } from '~/types/types'; -import type { Logger } from '~/utils/logger'; export const compileInputPath = (input: Omit) => { const result: Pick< diff --git a/packages/openapi-ts/src/generate.ts b/packages/openapi-ts/src/generate.ts index 0f39ba1fc..6aba3be51 100644 --- a/packages/openapi-ts/src/generate.ts +++ b/packages/openapi-ts/src/generate.ts @@ -1,9 +1,11 @@ +import { Logger } from '@hey-api/codegen-core'; import type { LazyOrAsync, MaybeArray } from '@hey-api/types'; import { checkNodeVersion } from '~/config/engine'; import type { Configs } from '~/config/init'; import { initConfigs } from '~/config/init'; import { getLogs } from '~/config/logs'; +import type { UserConfig } from '~/config/types'; import { createClient as pCreateClient } from '~/createClient'; import { ConfigValidationError, @@ -14,9 +16,7 @@ import { shouldReportCrash, } from '~/error'; import type { Context } from '~/ir/context'; -import type { UserConfig } from '~/types/config'; import { printCliIntro } from '~/utils/cli'; -import { Logger } from '~/utils/logger'; /** * Generate a client from the provided configuration. diff --git a/packages/openapi-ts/src/generate/client.ts b/packages/openapi-ts/src/generate/client.ts index e1c46cfeb..3a09553fa 100644 --- a/packages/openapi-ts/src/generate/client.ts +++ b/packages/openapi-ts/src/generate/client.ts @@ -4,10 +4,10 @@ import { fileURLToPath } from 'node:url'; import type { IProject, ProjectRenderMeta } from '@hey-api/codegen-core'; +import type { Config } from '~/config/types'; import type { DefinePlugin } from '~/plugins'; import type { Client } from '~/plugins/@hey-api/client-core/types'; import { getClientPlugin } from '~/plugins/@hey-api/client-core/utils'; -import type { Config } from '~/types/config'; import { ensureDirSync } from './utils'; diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index 1e571e126..63ea62111 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -72,7 +72,7 @@ import colors from 'ansi-colors'; // @ts-expect-error import colorSupport from 'color-support'; -import type { UserConfig } from '~/types/config'; +import type { UserConfig } from '~/config/types'; colors.enabled = colorSupport().hasBasic; @@ -85,8 +85,10 @@ export const defineConfig = async >( config: LazyOrAsync, ): Promise => (typeof config === 'function' ? await config() : config); +export { Logger } from '@hey-api/codegen-core'; export { defaultPaginationKeywords } from '~/config/parser'; export { defaultPlugins } from '~/config/plugins'; +export type { UserConfig } from '~/config/types'; export type { IR } from '~/ir/types'; export { OperationPath, OperationStrategy } from '~/openApi/shared/locations'; export type { @@ -115,6 +117,4 @@ export type { ExpressionTransformer } from '~/plugins/@hey-api/transformers/expr export type { TypeTransformer } from '~/plugins/@hey-api/transformers/types'; export { definePluginConfig } from '~/plugins/shared/utils/config'; export * from '~/ts-dsl'; -export type { UserConfig } from '~/types/config'; export { utils } from '~/utils/exports'; -export { Logger } from '~/utils/logger'; diff --git a/packages/openapi-ts/src/ir/__tests__/pagination.test.ts b/packages/openapi-ts/src/ir/__tests__/pagination.test.ts index be3cbb457..ff38d1440 100644 --- a/packages/openapi-ts/src/ir/__tests__/pagination.test.ts +++ b/packages/openapi-ts/src/ir/__tests__/pagination.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; import { defaultPaginationKeywords } from '~/config/parser'; -import type { Config } from '~/types/config'; +import type { Config } from '~/config/types'; import type { Context } from '../context'; import { operationPagination } from '../operation'; diff --git a/packages/openapi-ts/src/ir/context.ts b/packages/openapi-ts/src/ir/context.ts index 5fdd88a53..44921188e 100644 --- a/packages/openapi-ts/src/ir/context.ts +++ b/packages/openapi-ts/src/ir/context.ts @@ -1,5 +1,7 @@ +import type { Logger } from '@hey-api/codegen-core'; import { Project } from '@hey-api/codegen-core'; +import type { Config } from '~/config/types'; import type { Package } from '~/config/utils/package'; import { packageFactory } from '~/config/utils/package'; import type { Graph } from '~/graph'; @@ -7,8 +9,6 @@ import type { PluginConfigMap } from '~/plugins/config'; import { PluginInstance } from '~/plugins/shared/utils/instance'; import type { PluginNames } from '~/plugins/types'; import { TypeScriptRenderer } from '~/ts-dsl'; -import type { Config } from '~/types/config'; -import type { Logger } from '~/utils/logger'; import { applyNaming } from '~/utils/naming'; import { resolveRef } from '~/utils/ref'; diff --git a/packages/openapi-ts/src/ir/pagination.ts b/packages/openapi-ts/src/ir/pagination.ts index b7f29126c..965062ce0 100644 --- a/packages/openapi-ts/src/ir/pagination.ts +++ b/packages/openapi-ts/src/ir/pagination.ts @@ -1,4 +1,4 @@ -import type { Config } from '~/types/config'; +import type { Config } from '~/config/types'; import type { IR } from './types'; diff --git a/packages/openapi-ts/src/openApi/2.0.x/parser/__tests__/validate.test.ts b/packages/openapi-ts/src/openApi/2.0.x/parser/__tests__/validate.test.ts index 676c2ddbc..432812a06 100644 --- a/packages/openapi-ts/src/openApi/2.0.x/parser/__tests__/validate.test.ts +++ b/packages/openapi-ts/src/openApi/2.0.x/parser/__tests__/validate.test.ts @@ -1,11 +1,11 @@ import path from 'node:path'; +import { Logger } from '@hey-api/codegen-core'; import { describe, expect, it } from 'vitest'; import { getSpecsPath, specFileToJson } from '~/openApi/__tests__/utils'; import type { ValidatorResult } from '~/openApi/shared/utils/validator'; -import { Logger } from '../../../../utils/logger'; import { validateOpenApiSpec } from '../validate'; const specsFolder = path.join(getSpecsPath(), '2.0.x', 'invalid'); diff --git a/packages/openapi-ts/src/openApi/2.0.x/parser/filter.ts b/packages/openapi-ts/src/openApi/2.0.x/parser/filter.ts index 0894abfc2..6b53d10df 100644 --- a/packages/openapi-ts/src/openApi/2.0.x/parser/filter.ts +++ b/packages/openapi-ts/src/openApi/2.0.x/parser/filter.ts @@ -1,7 +1,8 @@ +import type { Logger } from '@hey-api/codegen-core'; + import { createOperationKey } from '~/ir/operation'; import { addNamespace, removeNamespace } from '~/openApi/shared/utils/filter'; import { httpMethods } from '~/openApi/shared/utils/operation'; -import type { Logger } from '~/utils/logger'; import type { OpenApiV2_0_X, diff --git a/packages/openapi-ts/src/openApi/2.0.x/parser/validate.ts b/packages/openapi-ts/src/openApi/2.0.x/parser/validate.ts index a340487ce..c0d0d4f07 100644 --- a/packages/openapi-ts/src/openApi/2.0.x/parser/validate.ts +++ b/packages/openapi-ts/src/openApi/2.0.x/parser/validate.ts @@ -1,10 +1,11 @@ +import type { Logger } from '@hey-api/codegen-core'; + import { createOperationKey } from '~/ir/operation'; import { httpMethods } from '~/openApi/shared/utils/operation'; import type { ValidatorIssue, ValidatorResult, } from '~/openApi/shared/utils/validator'; -import type { Logger } from '~/utils/logger'; import type { OpenApiV2_0_X, PathItemObject, PathsObject } from '../types/spec'; diff --git a/packages/openapi-ts/src/openApi/3.0.x/parser/__tests__/validate.test.ts b/packages/openapi-ts/src/openApi/3.0.x/parser/__tests__/validate.test.ts index 9af472378..dd33d1abc 100644 --- a/packages/openapi-ts/src/openApi/3.0.x/parser/__tests__/validate.test.ts +++ b/packages/openapi-ts/src/openApi/3.0.x/parser/__tests__/validate.test.ts @@ -1,11 +1,11 @@ import path from 'node:path'; +import { Logger } from '@hey-api/codegen-core'; import { describe, expect, it } from 'vitest'; import { getSpecsPath, specFileToJson } from '~/openApi/__tests__/utils'; import type { ValidatorResult } from '~/openApi/shared/utils/validator'; -import { Logger } from '../../../../utils/logger'; import { validateOpenApiSpec } from '../validate'; const specsFolder = path.join(getSpecsPath(), '3.0.x', 'invalid'); diff --git a/packages/openapi-ts/src/openApi/3.0.x/parser/filter.ts b/packages/openapi-ts/src/openApi/3.0.x/parser/filter.ts index 0bec360ac..455ade9fc 100644 --- a/packages/openapi-ts/src/openApi/3.0.x/parser/filter.ts +++ b/packages/openapi-ts/src/openApi/3.0.x/parser/filter.ts @@ -1,7 +1,8 @@ +import type { Logger } from '@hey-api/codegen-core'; + import { createOperationKey } from '~/ir/operation'; import { addNamespace, removeNamespace } from '~/openApi/shared/utils/filter'; import { httpMethods } from '~/openApi/shared/utils/operation'; -import type { Logger } from '~/utils/logger'; import type { OpenApiV3_0_X, PathItemObject, PathsObject } from '../types/spec'; diff --git a/packages/openapi-ts/src/openApi/3.0.x/parser/validate.ts b/packages/openapi-ts/src/openApi/3.0.x/parser/validate.ts index e44ae495c..a604aa22c 100644 --- a/packages/openapi-ts/src/openApi/3.0.x/parser/validate.ts +++ b/packages/openapi-ts/src/openApi/3.0.x/parser/validate.ts @@ -1,10 +1,11 @@ +import type { Logger } from '@hey-api/codegen-core'; + import { createOperationKey } from '~/ir/operation'; import { httpMethods } from '~/openApi/shared/utils/operation'; import type { ValidatorIssue, ValidatorResult, } from '~/openApi/shared/utils/validator'; -import type { Logger } from '~/utils/logger'; import type { OpenApiV3_0_X, PathItemObject, PathsObject } from '../types/spec'; diff --git a/packages/openapi-ts/src/openApi/3.1.x/parser/__tests__/validate.test.ts b/packages/openapi-ts/src/openApi/3.1.x/parser/__tests__/validate.test.ts index 878e77990..108c1b93b 100644 --- a/packages/openapi-ts/src/openApi/3.1.x/parser/__tests__/validate.test.ts +++ b/packages/openapi-ts/src/openApi/3.1.x/parser/__tests__/validate.test.ts @@ -1,11 +1,11 @@ import path from 'node:path'; +import { Logger } from '@hey-api/codegen-core'; import { describe, expect, it } from 'vitest'; import { getSpecsPath, specFileToJson } from '~/openApi/__tests__/utils'; import type { ValidatorResult } from '~/openApi/shared/utils/validator'; -import { Logger } from '../../../../utils/logger'; import { validateOpenApiSpec } from '../validate'; const specsFolder = path.join(getSpecsPath(), '3.1.x', 'invalid'); diff --git a/packages/openapi-ts/src/openApi/3.1.x/parser/filter.ts b/packages/openapi-ts/src/openApi/3.1.x/parser/filter.ts index 577e00820..c69aacad0 100644 --- a/packages/openapi-ts/src/openApi/3.1.x/parser/filter.ts +++ b/packages/openapi-ts/src/openApi/3.1.x/parser/filter.ts @@ -1,7 +1,8 @@ +import type { Logger } from '@hey-api/codegen-core'; + import { createOperationKey } from '~/ir/operation'; import { addNamespace, removeNamespace } from '~/openApi/shared/utils/filter'; import { httpMethods } from '~/openApi/shared/utils/operation'; -import type { Logger } from '~/utils/logger'; import type { OpenApiV3_1_X, PathItemObject, PathsObject } from '../types/spec'; diff --git a/packages/openapi-ts/src/openApi/3.1.x/parser/validate.ts b/packages/openapi-ts/src/openApi/3.1.x/parser/validate.ts index fcdb8e62e..56a2d7d6c 100644 --- a/packages/openapi-ts/src/openApi/3.1.x/parser/validate.ts +++ b/packages/openapi-ts/src/openApi/3.1.x/parser/validate.ts @@ -1,10 +1,11 @@ +import type { Logger } from '@hey-api/codegen-core'; + import { createOperationKey } from '~/ir/operation'; import { httpMethods } from '~/openApi/shared/utils/operation'; import type { ValidatorIssue, ValidatorResult, } from '~/openApi/shared/utils/validator'; -import type { Logger } from '~/utils/logger'; import type { OpenApiV3_1_X, PathItemObject, PathsObject } from '../types/spec'; diff --git a/packages/openapi-ts/src/openApi/__tests__/index.test.ts b/packages/openapi-ts/src/openApi/__tests__/index.test.ts index 89e685e6a..cb9915cd6 100644 --- a/packages/openapi-ts/src/openApi/__tests__/index.test.ts +++ b/packages/openapi-ts/src/openApi/__tests__/index.test.ts @@ -1,11 +1,11 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; +import type { Config } from '~/config/types'; import type { OpenApiV3_0_X } from '~/openApi/3.0.x'; import { parseV3_0_X } from '~/openApi/3.0.x'; import type { OpenApiV3_1_X } from '~/openApi/3.1.x'; import { parseV3_1_X } from '~/openApi/3.1.x'; -import type { Config } from '../../types/config'; import { parseOpenApiSpec } from '..'; vi.mock('../3.0.x', () => ({ diff --git a/packages/openapi-ts/src/openApi/index.ts b/packages/openapi-ts/src/openApi/index.ts index 2ccd20c97..20b8869f4 100644 --- a/packages/openapi-ts/src/openApi/index.ts +++ b/packages/openapi-ts/src/openApi/index.ts @@ -1,11 +1,12 @@ +import type { Logger } from '@hey-api/codegen-core'; + +import type { Config } from '~/config/types'; import { satisfies } from '~/config/utils/package'; import { Context } from '~/ir/context'; import { parseV2_0_X } from '~/openApi/2.0.x'; import { parseV3_0_X } from '~/openApi/3.0.x'; import { parseV3_1_X } from '~/openApi/3.1.x'; import type { OpenApi } from '~/openApi/types'; -import type { Config } from '~/types/config'; -import type { Logger } from '~/utils/logger'; /** * @internal diff --git a/packages/openapi-ts/src/openApi/shared/graph/meta.ts b/packages/openapi-ts/src/openApi/shared/graph/meta.ts index 8c0dd6eb0..b63fa1223 100644 --- a/packages/openapi-ts/src/openApi/shared/graph/meta.ts +++ b/packages/openapi-ts/src/openApi/shared/graph/meta.ts @@ -1,6 +1,7 @@ +import type { Logger } from '@hey-api/codegen-core'; + import type { Graph } from '~/graph'; import { createOperationKey } from '~/ir/operation'; -import type { Logger } from '~/utils/logger'; import { jsonPointerToPath } from '~/utils/ref'; import { addNamespace, stringToNamespace } from '../utils/filter'; diff --git a/packages/openapi-ts/src/openApi/shared/transforms/enums.ts b/packages/openapi-ts/src/openApi/shared/transforms/enums.ts index 600af1e07..13e0f2429 100644 --- a/packages/openapi-ts/src/openApi/shared/transforms/enums.ts +++ b/packages/openapi-ts/src/openApi/shared/transforms/enums.ts @@ -1,7 +1,7 @@ +import type { Config } from '~/config/types'; import { applyNaming } from '~/utils/naming'; import { jsonPointerToPath } from '~/utils/ref'; -import type { Config } from '../../../types/config'; import { deepClone } from '../utils/schema'; import { childSchemaRelationships } from '../utils/schemaChildRelationships'; import { getSchemasObject } from '../utils/transforms'; diff --git a/packages/openapi-ts/src/openApi/shared/transforms/readWrite.ts b/packages/openapi-ts/src/openApi/shared/transforms/readWrite.ts index 080151b13..72ff87db1 100644 --- a/packages/openapi-ts/src/openApi/shared/transforms/readWrite.ts +++ b/packages/openapi-ts/src/openApi/shared/transforms/readWrite.ts @@ -1,9 +1,10 @@ +import type { Logger } from '@hey-api/codegen-core'; + +import type { Config } from '~/config/types'; import type { Graph } from '~/graph'; -import type { Logger } from '~/utils/logger'; import { applyNaming } from '~/utils/naming'; import { jsonPointerToPath } from '~/utils/ref'; -import type { Config } from '../../../types/config'; import deepEqual from '../utils/deepEqual'; import { buildGraph, type Scope } from '../utils/graph'; import { deepClone } from '../utils/schema'; diff --git a/packages/openapi-ts/src/openApi/shared/utils/filter.ts b/packages/openapi-ts/src/openApi/shared/utils/filter.ts index 8c8f66d51..4a07e2803 100644 --- a/packages/openapi-ts/src/openApi/shared/utils/filter.ts +++ b/packages/openapi-ts/src/openApi/shared/utils/filter.ts @@ -1,9 +1,10 @@ +import type { Logger } from '@hey-api/codegen-core'; + +import type { Config } from '~/config/types'; import { createOperationKey } from '~/ir/operation'; import type { PathItemObject, PathsObject } from '~/openApi/3.1.x/types/spec'; import type { OpenApi } from '~/openApi/types'; -import type { Logger } from '~/utils/logger'; -import type { Config } from '../../../types/config'; import type { ResourceMetadata } from '../graph/meta'; import { httpMethods } from './operation'; diff --git a/packages/openapi-ts/src/openApi/shared/utils/graph.ts b/packages/openapi-ts/src/openApi/shared/utils/graph.ts index d82efbf2b..9bf3cc737 100644 --- a/packages/openapi-ts/src/openApi/shared/utils/graph.ts +++ b/packages/openapi-ts/src/openApi/shared/utils/graph.ts @@ -1,5 +1,6 @@ +import type { Logger } from '@hey-api/codegen-core'; + import type { Graph, NodeInfo } from '~/graph'; -import type { Logger } from '~/utils/logger'; import { normalizeJsonPointer, pathToJsonPointer } from '~/utils/ref'; import { childSchemaRelationships } from './schemaChildRelationships'; diff --git a/packages/openapi-ts/src/plugins/@hey-api/client-core/utils.ts b/packages/openapi-ts/src/plugins/@hey-api/client-core/utils.ts index 05a0cbbf0..e5600545c 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/client-core/utils.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/client-core/utils.ts @@ -1,5 +1,5 @@ +import type { Config } from '~/config/types'; import type { PluginClientNames } from '~/plugins/types'; -import type { Config } from '~/types/config'; export const getClientBaseUrlKey = (config: Config) => { const client = getClientPlugin(config); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdbd9981c..43a73da3b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,9 @@ importers: '@hey-api/codegen-core': specifier: workspace:* version: link:../packages/codegen-core + '@hey-api/openapi-python': + specifier: workspace:* + version: link:../packages/openapi-python '@hey-api/openapi-ts': specifier: workspace:* version: link:../packages/openapi-ts @@ -14690,7 +14693,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14710,7 +14713,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14778,7 +14781,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14798,7 +14801,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14886,7 +14889,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -22376,7 +22379,7 @@ snapshots: schema-utils: 4.3.2 webpack: 5.98.0(esbuild@0.25.0) - babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0): + babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)): dependencies: '@babel/core': 7.26.9 find-cache-dir: 4.0.0 @@ -27866,7 +27869,7 @@ snapshots: ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.9.3) optional: true - postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0): + postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)): dependencies: cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.7 -- 2.51.2 From 72e17f39d6e2d00eff24671e7f2b88801443489a Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 22 Jan 2026 04:12:29 +0800 Subject: [PATCH 04/14] fix: do not show ascii logo on generate command --- .changeset/warm-spoons-move.md | 5 +++++ packages/openapi-ts/src/utils/cli.ts | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .changeset/warm-spoons-move.md diff --git a/.changeset/warm-spoons-move.md b/.changeset/warm-spoons-move.md new file mode 100644 index 000000000..7c1e9fef2 --- /dev/null +++ b/.changeset/warm-spoons-move.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +**cli**: do not show ascii logo on generate command diff --git a/packages/openapi-ts/src/utils/cli.ts b/packages/openapi-ts/src/utils/cli.ts index d65a5b82d..32552f7ab 100644 --- a/packages/openapi-ts/src/utils/cli.ts +++ b/packages/openapi-ts/src/utils/cli.ts @@ -40,11 +40,14 @@ const asciiToLines = ( return { lines, maxLineLength }; }; -export function printCliIntro() { +// TODO: show ascii logo only in `--help` and `--version` commands +export function printCliIntro(showLogo: boolean = false): void { const packageJson = loadPackageJson(); - const text = asciiToLines(textAscii, { padding: 1 }); - for (const line of text.lines) { - console.log(colors.cyan(line)); + if (showLogo) { + const text = asciiToLines(textAscii, { padding: 1 }); + for (const line of text.lines) { + console.log(colors.cyan(line)); + } } console.log(colors.gray(`${packageJson.name} v${packageJson.version}`)); console.log(''); -- 2.51.2 From d6c7e8a740e70314f409a47650edf39288be9551 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 20:20:52 +0000 Subject: [PATCH 05/14] ci: release --- .changeset/fuzzy-cobras-beam.md | 6 ------ .changeset/warm-spoons-move.md | 5 ----- packages/codegen-core/CHANGELOG.md | 6 ++++++ packages/codegen-core/package.json | 2 +- packages/openapi-ts/CHANGELOG.md | 12 ++++++++++++ packages/openapi-ts/package.json | 4 ++-- 6 files changed, 21 insertions(+), 14 deletions(-) delete mode 100644 .changeset/fuzzy-cobras-beam.md delete mode 100644 .changeset/warm-spoons-move.md diff --git a/.changeset/fuzzy-cobras-beam.md b/.changeset/fuzzy-cobras-beam.md deleted file mode 100644 index 3ff6fd0a6..000000000 --- a/.changeset/fuzzy-cobras-beam.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@hey-api/codegen-core': patch -'@hey-api/openapi-ts': patch ---- - -**internal**: move logger to codegen-core diff --git a/.changeset/warm-spoons-move.md b/.changeset/warm-spoons-move.md deleted file mode 100644 index 7c1e9fef2..000000000 --- a/.changeset/warm-spoons-move.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hey-api/openapi-ts': patch ---- - -**cli**: do not show ascii logo on generate command diff --git a/packages/codegen-core/CHANGELOG.md b/packages/codegen-core/CHANGELOG.md index af0da88bd..dd0bac3fe 100644 --- a/packages/codegen-core/CHANGELOG.md +++ b/packages/codegen-core/CHANGELOG.md @@ -1,5 +1,11 @@ # @hey-api/codegen-core +## 0.5.4 + +### Patch Changes + +- **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) + ## 0.5.3 ### Patch Changes diff --git a/packages/codegen-core/package.json b/packages/codegen-core/package.json index f2a596932..92bed14ca 100644 --- a/packages/codegen-core/package.json +++ b/packages/codegen-core/package.json @@ -1,6 +1,6 @@ { "name": "@hey-api/codegen-core", - "version": "0.5.3", + "version": "0.5.4", "description": "๐Ÿงฑ TypeScript framework for generating files.", "homepage": "https://heyapi.dev/", "repository": { diff --git a/packages/openapi-ts/CHANGELOG.md b/packages/openapi-ts/CHANGELOG.md index 78becbe66..a0e2fa0d9 100644 --- a/packages/openapi-ts/CHANGELOG.md +++ b/packages/openapi-ts/CHANGELOG.md @@ -1,5 +1,17 @@ # @hey-api/openapi-ts +## 0.90.8 + +### Patch Changes + +- **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) + +- **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) + +### Updated Dependencies: + +- @hey-api/codegen-core@0.5.4 + ## 0.90.7 ### Patch Changes diff --git a/packages/openapi-ts/package.json b/packages/openapi-ts/package.json index 6ed772a19..840e8dfbf 100644 --- a/packages/openapi-ts/package.json +++ b/packages/openapi-ts/package.json @@ -1,6 +1,6 @@ { "name": "@hey-api/openapi-ts", - "version": "0.90.7", + "version": "0.90.8", "description": "๐ŸŒ€ OpenAPI to TypeScript codegen. Production-ready SDKs, Zod schemas, TanStack Query hooks, and 20+ plugins. Used by Vercel, OpenCode, and PayPal.", "homepage": "https://heyapi.dev/", "repository": { @@ -89,7 +89,7 @@ "node": ">=20.19.0" }, "dependencies": { - "@hey-api/codegen-core": "workspace:^0.5.3", + "@hey-api/codegen-core": "workspace:^0.5.4", "@hey-api/json-schema-ref-parser": "1.2.2", "@hey-api/types": "workspace:*", "ansi-colors": "4.1.3", -- 2.51.2 From 4b449b86c8d8aa1b9171c8bae57c031e56b150a6 Mon Sep 17 00:00:00 2001 From: mrlubos <12529395+mrlubos@users.noreply.github.com> Date: Wed, 21 Jan 2026 20:22:07 +0000 Subject: [PATCH 06/14] chore: update lock file --- pnpm-lock.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43a73da3b..04b8bcda9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1390,7 +1390,7 @@ importers: packages/openapi-ts: dependencies: '@hey-api/codegen-core': - specifier: workspace:^0.5.3 + specifier: workspace:^0.5.4 version: link:../codegen-core '@hey-api/json-schema-ref-parser': specifier: 1.2.2 @@ -14693,7 +14693,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14713,7 +14713,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14781,7 +14781,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14801,7 +14801,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14889,7 +14889,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -22379,7 +22379,7 @@ snapshots: schema-utils: 4.3.2 webpack: 5.98.0(esbuild@0.25.0) - babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)): + babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0): dependencies: '@babel/core': 7.26.9 find-cache-dir: 4.0.0 @@ -27869,7 +27869,7 @@ snapshots: ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.9.3) optional: true - postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)): + postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0): dependencies: cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.7 -- 2.51.2 From 4f52bce79d8ac6573472f32a05b7f70913b90605 Mon Sep 17 00:00:00 2001 From: Lubos Date: Thu, 22 Jan 2026 19:49:27 +0800 Subject: [PATCH 07/14] refactor: cli interface --- .changeset/bright-falcons-chew.md | 5 + .changeset/clever-toys-dress.md | 5 + .changeset/fluffy-pens-admire.md | 5 + .changeset/stupid-news-wash.md | 5 + .vscode/launch.json | 2 + packages/codegen-core/package.json | 1 + .../src/__tests__/exports.test.ts | 3 + .../codegen-core/src/config/interactive.ts | 14 ++ packages/codegen-core/src/config/load.ts | 42 ++++ packages/codegen-core/src/config/merge.ts | 28 +++ packages/codegen-core/src/index.ts | 3 + packages/openapi-python/package.json | 1 - packages/openapi-python/src/cli.ts | 120 ------------ packages/openapi-python/src/cli/adapter.ts | 40 ++++ packages/openapi-python/src/cli/index.ts | 67 +++++++ packages/openapi-python/src/cli/schema.ts | 17 ++ packages/openapi-python/src/config/types.d.ts | 13 +- packages/openapi-python/src/index.ts | 8 +- .../openapi-ts-tests/main/test/cli.test.ts | 1 - packages/openapi-ts/package.json | 1 - packages/openapi-ts/src/__tests__/cli.test.ts | 13 +- .../src/__tests__/interactive.test.ts | 26 ++- packages/openapi-ts/src/cli.ts | 120 ------------ packages/openapi-ts/src/cli/adapter.ts | 40 ++++ packages/openapi-ts/src/cli/index.ts | 67 +++++++ packages/openapi-ts/src/cli/schema.ts | 17 ++ packages/openapi-ts/src/config/expand.ts | 54 ++++++ packages/openapi-ts/src/config/init.ts | 181 ++++-------------- packages/openapi-ts/src/config/merge.ts | 32 ---- packages/openapi-ts/src/config/resolve.ts | 65 +++++++ packages/openapi-ts/src/config/types.d.ts | 4 +- packages/openapi-ts/src/config/validate.ts | 39 ++++ packages/openapi-ts/src/generate.ts | 64 +++---- packages/openapi-ts/src/index.ts | 8 +- packages/openapi-ts/src/internal.ts | 2 +- packages/types/src/index.ts | 20 +- pnpm-lock.yaml | 50 ++--- 37 files changed, 656 insertions(+), 527 deletions(-) create mode 100644 .changeset/bright-falcons-chew.md create mode 100644 .changeset/clever-toys-dress.md create mode 100644 .changeset/fluffy-pens-admire.md create mode 100644 .changeset/stupid-news-wash.md create mode 100644 packages/codegen-core/src/config/interactive.ts create mode 100644 packages/codegen-core/src/config/load.ts create mode 100644 packages/codegen-core/src/config/merge.ts delete mode 100644 packages/openapi-python/src/cli.ts create mode 100644 packages/openapi-python/src/cli/adapter.ts create mode 100644 packages/openapi-python/src/cli/index.ts create mode 100644 packages/openapi-python/src/cli/schema.ts delete mode 100644 packages/openapi-ts/src/cli.ts create mode 100644 packages/openapi-ts/src/cli/adapter.ts create mode 100644 packages/openapi-ts/src/cli/index.ts create mode 100644 packages/openapi-ts/src/cli/schema.ts create mode 100644 packages/openapi-ts/src/config/expand.ts delete mode 100644 packages/openapi-ts/src/config/merge.ts create mode 100644 packages/openapi-ts/src/config/resolve.ts create mode 100644 packages/openapi-ts/src/config/validate.ts diff --git a/.changeset/bright-falcons-chew.md b/.changeset/bright-falcons-chew.md new file mode 100644 index 000000000..1c3e9a490 --- /dev/null +++ b/.changeset/bright-falcons-chew.md @@ -0,0 +1,5 @@ +--- +'@hey-api/types': patch +--- + +feat: add `ToArray`, `ToReadonlyArray`, and `AnyObject` types diff --git a/.changeset/clever-toys-dress.md b/.changeset/clever-toys-dress.md new file mode 100644 index 000000000..ad10ced8c --- /dev/null +++ b/.changeset/clever-toys-dress.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +**cli**: clean up interface diff --git a/.changeset/fluffy-pens-admire.md b/.changeset/fluffy-pens-admire.md new file mode 100644 index 000000000..8f94228b9 --- /dev/null +++ b/.changeset/fluffy-pens-admire.md @@ -0,0 +1,5 @@ +--- +'@hey-api/codegen-core': patch +--- + +**config**: export `loadConfigFile` function (moved from `@hey-api/openapi-ts`) diff --git a/.changeset/stupid-news-wash.md b/.changeset/stupid-news-wash.md new file mode 100644 index 000000000..0e89c1920 --- /dev/null +++ b/.changeset/stupid-news-wash.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +**config**: move `loadConfigFile` function to `@hey-api/codegen-core` diff --git a/.vscode/launch.json b/.vscode/launch.json index aa4048fe8..eaea5c38c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,6 +16,7 @@ "cwd": "${workspaceFolder}/dev", "runtimeExecutable": "node", "program": "${workspaceFolder}/packages/openapi-ts/dist/run.mjs", + "args": [], "env": { "DEBUG": "false" } @@ -28,6 +29,7 @@ "cwd": "${workspaceFolder}/dev", "runtimeExecutable": "node", "program": "${workspaceFolder}/packages/openapi-python/dist/run.mjs", + "args": [], "env": { "DEBUG": "false" } diff --git a/packages/codegen-core/package.json b/packages/codegen-core/package.json index 92bed14ca..253a45b9f 100644 --- a/packages/codegen-core/package.json +++ b/packages/codegen-core/package.json @@ -63,6 +63,7 @@ "dependencies": { "@hey-api/types": "workspace:*", "ansi-colors": "4.1.3", + "c12": "3.3.3", "color-support": "1.1.3" }, "peerDependencies": { diff --git a/packages/codegen-core/src/__tests__/exports.test.ts b/packages/codegen-core/src/__tests__/exports.test.ts index ca2c3cc5a..e6e2d7067 100644 --- a/packages/codegen-core/src/__tests__/exports.test.ts +++ b/packages/codegen-core/src/__tests__/exports.test.ts @@ -8,13 +8,16 @@ const constExports = [ 'File', 'fromRef', 'fromRefs', + 'detectInteractiveSession', 'isNode', 'isNodeRef', 'isRef', 'isSymbol', 'isSymbolRef', + 'loadConfigFile', 'log', 'Logger', + 'mergeConfigs', 'nodeBrand', 'Project', 'ref', diff --git a/packages/codegen-core/src/config/interactive.ts b/packages/codegen-core/src/config/interactive.ts new file mode 100644 index 000000000..baf0570b0 --- /dev/null +++ b/packages/codegen-core/src/config/interactive.ts @@ -0,0 +1,14 @@ +/** + * Detect if the current session is interactive based on TTY status and environment variables. + * This is used as a fallback when the user doesn't explicitly set the interactive option. + * @internal + */ +export function detectInteractiveSession(): boolean { + return Boolean( + process.stdin.isTTY && + process.stdout.isTTY && + !process.env.CI && + !process.env.NO_INTERACTIVE && + !process.env.NO_INTERACTION, + ); +} diff --git a/packages/codegen-core/src/config/load.ts b/packages/codegen-core/src/config/load.ts new file mode 100644 index 000000000..00fb5b4cd --- /dev/null +++ b/packages/codegen-core/src/config/load.ts @@ -0,0 +1,42 @@ +import type { Logger } from '@hey-api/codegen-core'; +import type { AnyObject, MaybeArray } from '@hey-api/types'; + +import { mergeConfigs } from './merge'; + +export async function loadConfigFile({ + configFile, + logger, + name, + userConfig, +}: { + configFile: string | undefined; + logger: Logger; + name: string; + userConfig: T; +}): Promise<{ + configFile: string | undefined; + configs: ReadonlyArray; + foundConfig: boolean; +}> { + const eventC12 = logger.timeEvent('c12'); + // c12 is ESM-only since v3 + const { loadConfig } = await import('c12'); + + const { config: fileConfig, configFile: loadedConfigFile } = await loadConfig< + MaybeArray + >({ + configFile, + name, + }); + eventC12.timeEnd(); + + const fileConfigs = fileConfig instanceof Array ? fileConfig : [fileConfig]; + const mergedConfigs = fileConfigs.map((config) => + mergeConfigs(config, userConfig), + ); + const foundConfig = fileConfigs.some( + (config) => Object.keys(config).length > 0, + ); + + return { configFile: loadedConfigFile, configs: mergedConfigs, foundConfig }; +} diff --git a/packages/codegen-core/src/config/merge.ts b/packages/codegen-core/src/config/merge.ts new file mode 100644 index 000000000..2dbff0bc2 --- /dev/null +++ b/packages/codegen-core/src/config/merge.ts @@ -0,0 +1,28 @@ +import type { AnyObject } from '@hey-api/types'; + +function isPlainObject(value: unknown): value is AnyObject { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +export function mergeConfigs( + configA: T | undefined, + configB: T | undefined, +): T { + const a = (configA || {}) as AnyObject; + const b = (configB || {}) as AnyObject; + + const result: AnyObject = { ...a }; + + for (const key of Object.keys(b)) { + const valueA = a[key]; + const valueB = b[key]; + + if (isPlainObject(valueA) && isPlainObject(valueB)) { + result[key] = mergeConfigs(valueA, valueB); + } else { + result[key] = valueB; + } + } + + return result as T; +} diff --git a/packages/codegen-core/src/index.ts b/packages/codegen-core/src/index.ts index 924115f4a..87b1eaa4d 100644 --- a/packages/codegen-core/src/index.ts +++ b/packages/codegen-core/src/index.ts @@ -5,6 +5,9 @@ export type { ImportModule, } from './bindings'; export { nodeBrand, symbolBrand } from './brands'; +export { detectInteractiveSession } from './config/interactive'; +export { loadConfigFile } from './config/load'; +export { mergeConfigs } from './config/merge'; export type { IProjectRenderMeta as ProjectRenderMeta, ISymbolMeta as SymbolMeta, diff --git a/packages/openapi-python/package.json b/packages/openapi-python/package.json index df1af4f19..6dd1bf09d 100644 --- a/packages/openapi-python/package.json +++ b/packages/openapi-python/package.json @@ -77,7 +77,6 @@ "@hey-api/json-schema-ref-parser": "1.2.2", "@hey-api/types": "workspace:*", "ansi-colors": "4.1.3", - "c12": "3.3.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", diff --git a/packages/openapi-python/src/cli.ts b/packages/openapi-python/src/cli.ts deleted file mode 100644 index 675e2e9e3..000000000 --- a/packages/openapi-python/src/cli.ts +++ /dev/null @@ -1,120 +0,0 @@ -import type { OptionValues } from 'commander'; -import { Command } from 'commander'; - -import { createClient } from '~/index'; - -import pkg from '../package.json' assert { type: 'json' }; - -const stringToBoolean = ( - value: string | undefined, -): boolean | string | undefined => { - if (value === 'true') return true; - if (value === 'false') return false; - return value; -}; - -const processParams = ( - obj: OptionValues, - booleanKeys: ReadonlyArray, -): OptionValues => { - for (const key of booleanKeys) { - const value = obj[key]; - if (typeof value === 'string') { - const parsedValue = stringToBoolean(value); - delete obj[key]; - obj[key] = parsedValue; - } - } - return obj; -}; - -export const runCli = async (): Promise => { - const params = new Command() - .name(Object.keys(pkg.bin)[0]!) - .usage('[options]') - .version(pkg.version) - .option('-c, --client ', 'HTTP client to generate') - .option('-d, --debug', 'Set log level to debug') - .option('--dry-run [value]', 'Skip writing files to disk?') - .option('-f, --file [value]', 'Path to the config file') - .option( - '-i, --input ', - 'OpenAPI specification (path, url, or string content)', - ) - .option('-l, --logs [value]', 'Logs folder') - .option('-o, --output ', 'Output folder') - .option('-p, --plugins [value...]', "List of plugins you'd like to use") - .option('-s, --silent', 'Set log level to silent') - .option( - '--no-log-file', - 'Disable writing a log file. Works like --silent but without suppressing console output', - ) - .option( - '-w, --watch [value]', - 'Regenerate the client when the input file changes?', - ) - .parse(process.argv) - .opts(); - - let userConfig: Record; - - try { - userConfig = processParams(params, ['dryRun', 'logFile']); - - if (userConfig.file) { - userConfig.configFile = userConfig.file; - delete userConfig.file; - } - - if (params.plugins === true) { - userConfig.plugins = []; - } else if (params.plugins) { - userConfig.plugins = params.plugins; - } else if (userConfig.client) { - userConfig.plugins = ['@hey-api/sdk']; - } - - if (userConfig.client) { - (userConfig.plugins as Array).push(userConfig.client as string); - delete userConfig.client; - } - - userConfig.logs = userConfig.logs - ? { - path: userConfig.logs, - } - : {}; - - if (userConfig.debug) { - (userConfig.logs as Record).level = 'debug'; - delete userConfig.debug; - } else if (userConfig.silent) { - (userConfig.logs as Record).level = 'silent'; - delete userConfig.silent; - } - - (userConfig.logs as Record).file = userConfig.logFile; - delete userConfig.logFile; - - if (typeof params.watch === 'string') { - userConfig.watch = Number.parseInt(params.watch, 10); - } - - if (!Object.keys(userConfig.logs as Record).length) { - delete userConfig.logs; - } - - const context = await createClient( - userConfig as unknown as Required>[0], - ); - if ( - !context[0]?.config.input.some( - (input) => input.watch && input.watch.enabled, - ) - ) { - process.exit(0); - } - } catch { - process.exit(1); - } -}; diff --git a/packages/openapi-python/src/cli/adapter.ts b/packages/openapi-python/src/cli/adapter.ts new file mode 100644 index 000000000..64666ce28 --- /dev/null +++ b/packages/openapi-python/src/cli/adapter.ts @@ -0,0 +1,40 @@ +// import type { ToArray } from '@hey-api/types'; + +import type { UserConfig } from '~/config/types'; + +import type { CliOptions } from './schema'; + +export const cliToConfig = (cli: CliOptions): Partial => { + const config: Partial = {}; + + if (cli.input) config.input = cli.input; + if (cli.output) config.output = cli.output; + if (cli.file) config.configFile = cli.file; + if (cli.dryRun !== undefined) config.dryRun = cli.dryRun; + + // const plugins: ToArray = []; + // if (cli.plugins instanceof Array && cli.plugins.length > 0) { + // plugins.push(...cli.plugins); + // } + // if (cli.client) plugins.push(cli.client); + // if (plugins.length > 0) config.plugins = plugins; + + if (cli.debug || cli.silent || cli.logs || cli.logFile !== undefined) { + config.logs = { + ...(cli.logs && { path: cli.logs }), + ...(cli.debug && { level: 'debug' as const }), + ...(cli.silent && { level: 'silent' as const }), + ...(cli.logFile !== undefined && { file: cli.logFile }), + }; + } + + if (cli.watch !== undefined) { + if (typeof cli.watch === 'string') { + config.watch = Number.parseInt(cli.watch, 10); + } else { + config.watch = cli.watch; + } + } + + return config; +}; diff --git a/packages/openapi-python/src/cli/index.ts b/packages/openapi-python/src/cli/index.ts new file mode 100644 index 000000000..d900f7bf9 --- /dev/null +++ b/packages/openapi-python/src/cli/index.ts @@ -0,0 +1,67 @@ +import { Command, CommanderError } from 'commander'; + +import { createClient } from '~/index'; + +import pkg from '../../package.json' assert { type: 'json' }; +import { cliToConfig } from './adapter'; + +const binName = Object.keys(pkg.bin)[0]!; + +const program = new Command() + .name(binName) + .description('Generate Python code from OpenAPI specifications') + .version(pkg.version); + +program + .option( + '-i, --input ', + 'OpenAPI specification (path, URL, or string)', + ) + .option('-o, --output ', 'Output folder(s)') + .option('-c, --client ', 'HTTP client to generate') + .option('-p, --plugins [names...]', 'Plugins to use') + .option('-f, --file ', 'Path to config file') + .option('-d, --debug', 'Enable debug logging') + .option('-s, --silent', 'Suppress all output') + .option('-l, --logs ', 'Logs folder path') + .option('--no-log-file', 'Disable log file output') + .option('--dry-run', 'Skip writing files') + .option('-w, --watch [interval]', 'Watch for changes') + .action(async (options) => { + const config = cliToConfig(options); + + const context = await createClient( + config as Parameters[0], + ); + + const hasActiveWatch = context[0]?.config.input.some( + (input) => input.watch?.enabled, + ); + + if (!hasActiveWatch) { + process.exit(0); + } + }); + +export async function runCli(): Promise { + try { + await program.parseAsync(process.argv); + } catch (error) { + if (error instanceof CommanderError && 'code' in error) { + if (error.code === 'commander.optionMissingArgument') { + console.error( + `\nMissing required argument. Run '${binName} --help' for usage.\n`, + ); + } else if (error.code === 'commander.unknownOption') { + console.error( + `\nUnknown option. Run '${binName} --help' for available options.\n`, + ); + } + + process.exit(error.exitCode); + } + + console.error('Unexpected error:', error); + process.exit(1); + } +} diff --git a/packages/openapi-python/src/cli/schema.ts b/packages/openapi-python/src/cli/schema.ts new file mode 100644 index 000000000..f49160f37 --- /dev/null +++ b/packages/openapi-python/src/cli/schema.ts @@ -0,0 +1,17 @@ +import type { MaybeArray } from '@hey-api/types'; + +// import type { PluginClientNames, PluginNames } from "~/plugins/types"; + +export interface CliOptions { + // client?: PluginClientNames; + debug?: boolean; + dryRun?: boolean; + file?: string; + input?: MaybeArray; + logFile?: boolean; + logs?: string; + output?: MaybeArray; + // plugins?: ReadonlyArray; + silent?: boolean; + watch?: boolean | string; +} diff --git a/packages/openapi-python/src/config/types.d.ts b/packages/openapi-python/src/config/types.d.ts index 6692ab0b2..220745b6b 100644 --- a/packages/openapi-python/src/config/types.d.ts +++ b/packages/openapi-python/src/config/types.d.ts @@ -1,4 +1,4 @@ -// import { MaybeArray } from "@hey-api/types"; +import type { MaybeArray } from '@hey-api/types'; export interface UserConfig { /** @@ -28,6 +28,7 @@ export interface UserConfig { * generate multiple outputs, one for each input. */ // input: MaybeArray['path']>; + input: MaybeArray; /** * Show an interactive error reporting tool when the program crashes? You * generally want to keep this disabled (default). @@ -41,6 +42,11 @@ export interface UserConfig { * @default process.cwd() */ // logs?: string | Logs; + logs?: + | string + | { + level?: 'debug' | 'info' | 'warn' | 'error' | 'silent'; + }; /** * Path to the output folder. * @@ -48,6 +54,7 @@ export interface UserConfig { * generate multiple outputs, one for each input. */ // output: MaybeArray; + output: MaybeArray; /** * Customize how the input is parsed and transformed before it's passed to * plugins. @@ -68,4 +75,8 @@ export interface UserConfig { // }; // }[PluginNames] // >; + /** + * @deprecated use `input.watch` instead + */ + watch?: boolean | number; } diff --git a/packages/openapi-python/src/index.ts b/packages/openapi-python/src/index.ts index f2bc208d1..e7b9c61fc 100644 --- a/packages/openapi-python/src/index.ts +++ b/packages/openapi-python/src/index.ts @@ -79,11 +79,13 @@ colors.enabled = colorSupport().hasBasic; export { createClient } from '~/generate'; /** - * Type helper for openapi-ts.config.ts, returns {@link MaybeArray} object(s) + * Type helper for configuration object, returns {@link MaybeArray} object(s) */ -export const defineConfig = async >( +export async function defineConfig>( config: LazyOrAsync, -): Promise => (typeof config === 'function' ? await config() : config); +): Promise { + return typeof config === 'function' ? await config() : config; +} export { Logger } from '@hey-api/codegen-core'; // export { defaultPaginationKeywords } from '~/config/parser'; diff --git a/packages/openapi-ts-tests/main/test/cli.test.ts b/packages/openapi-ts-tests/main/test/cli.test.ts index 9bb58351e..5143df63e 100755 --- a/packages/openapi-ts-tests/main/test/cli.test.ts +++ b/packages/openapi-ts-tests/main/test/cli.test.ts @@ -15,7 +15,6 @@ describe('bin', () => { '--output', path.resolve(__dirname, '.gen'), '--dry-run', - 'true', ]); expect(result.error).toBeFalsy(); expect(result.status).toBe(0); diff --git a/packages/openapi-ts/package.json b/packages/openapi-ts/package.json index 840e8dfbf..fbf5a8227 100644 --- a/packages/openapi-ts/package.json +++ b/packages/openapi-ts/package.json @@ -93,7 +93,6 @@ "@hey-api/json-schema-ref-parser": "1.2.2", "@hey-api/types": "workspace:*", "ansi-colors": "4.1.3", - "c12": "3.3.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", diff --git a/packages/openapi-ts/src/__tests__/cli.test.ts b/packages/openapi-ts/src/__tests__/cli.test.ts index e5db904ee..29fb83a2c 100755 --- a/packages/openapi-ts/src/__tests__/cli.test.ts +++ b/packages/openapi-ts/src/__tests__/cli.test.ts @@ -53,11 +53,11 @@ describe('cli', () => { process.argv = originalArgv; } expect(spy).toHaveBeenCalledWith({ - input: 'foo.json', + input: ['foo.json'], logs: { file: true, }, - output: 'bar', + output: ['bar'], }); }); @@ -77,7 +77,6 @@ describe('cli', () => { logs: { file: true, }, - plugins: [], }); }); @@ -102,7 +101,7 @@ describe('cli', () => { }); }); - it('with default plugins', async () => { + it('with client plugin', async () => { const originalArgv = process.argv.slice(); try { process.argv = [ @@ -119,7 +118,7 @@ describe('cli', () => { logs: { file: true, }, - plugins: ['@hey-api/typescript', '@hey-api/sdk', 'foo'], + plugins: ['foo'], }); }); @@ -214,12 +213,12 @@ describe('cli', () => { expect(spy).toHaveBeenCalledWith({ configFile: 'bar', dryRun: true, - input: 'baz', + input: ['baz'], logs: { file: true, path: 'qux', }, - output: 'quux', + output: ['quux'], plugins: ['foo'], watch: true, }); diff --git a/packages/openapi-ts/src/__tests__/interactive.test.ts b/packages/openapi-ts/src/__tests__/interactive.test.ts index 766ca3e6a..3f525cf7b 100644 --- a/packages/openapi-ts/src/__tests__/interactive.test.ts +++ b/packages/openapi-ts/src/__tests__/interactive.test.ts @@ -1,12 +1,15 @@ -import { Logger } from '@hey-api/codegen-core'; +import { + detectInteractiveSession, + Logger, + mergeConfigs, +} from '@hey-api/codegen-core'; import { afterEach, describe, expect, it } from 'vitest'; -import { detectInteractiveSession, initConfigs } from '~/config/init'; -import { mergeConfigs } from '~/config/merge'; +import { resolveJobs } from '~/config/init'; describe('interactive config', () => { it('should use detectInteractiveSession when not provided', async () => { - const result = await initConfigs({ + const result = await resolveJobs({ logger: new Logger(), userConfigs: [ { @@ -17,11 +20,11 @@ describe('interactive config', () => { }); // In test environment, TTY is typically not available, so it should be false - expect(result.results[0]?.config.interactive).toBe(false); + expect(result.jobs[0]?.config.interactive).toBe(false); }); it('should respect user config when set to true', async () => { - const result = await initConfigs({ + const result = await resolveJobs({ logger: new Logger(), userConfigs: [ { @@ -32,11 +35,11 @@ describe('interactive config', () => { ], }); - expect(result.results[0]?.config.interactive).toBe(true); + expect(result.jobs[0]?.config.interactive).toBe(true); }); it('should respect user config when set to false', async () => { - const result = await initConfigs({ + const result = await resolveJobs({ logger: new Logger(), userConfigs: [ { @@ -47,7 +50,7 @@ describe('interactive config', () => { ], }); - expect(result.results[0]?.config.interactive).toBe(false); + expect(result.jobs[0]?.config.interactive).toBe(false); }); it('should allow file config to set interactive when CLI does not provide it', () => { @@ -76,7 +79,10 @@ describe('interactive config', () => { }; // After fix: file config's interactive should be preserved - const mergedCorrect = mergeConfigs(fileConfig, cliConfigWithoutInteractive); + const mergedCorrect = mergeConfigs>( + fileConfig, + cliConfigWithoutInteractive, + ); expect(mergedCorrect.interactive).toBe(false); // Before fix: CLI's auto-detected interactive would override file config diff --git a/packages/openapi-ts/src/cli.ts b/packages/openapi-ts/src/cli.ts deleted file mode 100644 index 75f528a46..000000000 --- a/packages/openapi-ts/src/cli.ts +++ /dev/null @@ -1,120 +0,0 @@ -import type { OptionValues } from 'commander'; -import { Command } from 'commander'; - -import { createClient } from '~/index'; - -import pkg from '../package.json' assert { type: 'json' }; - -const stringToBoolean = ( - value: string | undefined, -): boolean | string | undefined => { - if (value === 'true') return true; - if (value === 'false') return false; - return value; -}; - -const processParams = ( - obj: OptionValues, - booleanKeys: ReadonlyArray, -): OptionValues => { - for (const key of booleanKeys) { - const value = obj[key]; - if (typeof value === 'string') { - const parsedValue = stringToBoolean(value); - delete obj[key]; - obj[key] = parsedValue; - } - } - return obj; -}; - -export const runCli = async (): Promise => { - const params = new Command() - .name(Object.keys(pkg.bin)[0]!) - .usage('[options]') - .version(pkg.version) - .option('-c, --client ', 'HTTP client to generate') - .option('-d, --debug', 'Set log level to debug') - .option('--dry-run [value]', 'Skip writing files to disk?') - .option('-f, --file [value]', 'Path to the config file') - .option( - '-i, --input ', - 'OpenAPI specification (path, url, or string content)', - ) - .option('-l, --logs [value]', 'Logs folder') - .option('-o, --output ', 'Output folder') - .option('-p, --plugins [value...]', "List of plugins you'd like to use") - .option('-s, --silent', 'Set log level to silent') - .option( - '--no-log-file', - 'Disable writing a log file. Works like --silent but without suppressing console output', - ) - .option( - '-w, --watch [value]', - 'Regenerate the client when the input file changes?', - ) - .parse(process.argv) - .opts(); - - let userConfig: Record; - - try { - userConfig = processParams(params, ['dryRun', 'logFile']); - - if (userConfig.file) { - userConfig.configFile = userConfig.file; - delete userConfig.file; - } - - if (params.plugins === true) { - userConfig.plugins = []; - } else if (params.plugins) { - userConfig.plugins = params.plugins; - } else if (userConfig.client) { - userConfig.plugins = ['@hey-api/typescript', '@hey-api/sdk']; - } - - if (userConfig.client) { - (userConfig.plugins as Array).push(userConfig.client as string); - delete userConfig.client; - } - - userConfig.logs = userConfig.logs - ? { - path: userConfig.logs, - } - : {}; - - if (userConfig.debug) { - (userConfig.logs as Record).level = 'debug'; - delete userConfig.debug; - } else if (userConfig.silent) { - (userConfig.logs as Record).level = 'silent'; - delete userConfig.silent; - } - - (userConfig.logs as Record).file = userConfig.logFile; - delete userConfig.logFile; - - if (typeof params.watch === 'string') { - userConfig.watch = Number.parseInt(params.watch, 10); - } - - if (!Object.keys(userConfig.logs as Record).length) { - delete userConfig.logs; - } - - const context = await createClient( - userConfig as unknown as Required>[0], - ); - if ( - !context[0]?.config.input.some( - (input) => input.watch && input.watch.enabled, - ) - ) { - process.exit(0); - } - } catch { - process.exit(1); - } -}; diff --git a/packages/openapi-ts/src/cli/adapter.ts b/packages/openapi-ts/src/cli/adapter.ts new file mode 100644 index 000000000..222a088fa --- /dev/null +++ b/packages/openapi-ts/src/cli/adapter.ts @@ -0,0 +1,40 @@ +import type { ToArray } from '@hey-api/types'; + +import type { UserConfig } from '~/config/types'; + +import type { CliOptions } from './schema'; + +export const cliToConfig = (cli: CliOptions): Partial => { + const config: Partial = {}; + + if (cli.input) config.input = cli.input; + if (cli.output) config.output = cli.output; + if (cli.file) config.configFile = cli.file; + if (cli.dryRun !== undefined) config.dryRun = cli.dryRun; + + const plugins: ToArray = []; + if (cli.plugins instanceof Array && cli.plugins.length > 0) { + plugins.push(...cli.plugins); + } + if (cli.client) plugins.push(cli.client); + if (plugins.length > 0) config.plugins = plugins; + + if (cli.debug || cli.silent || cli.logs || cli.logFile !== undefined) { + config.logs = { + ...(cli.logs && { path: cli.logs }), + ...(cli.debug && { level: 'debug' as const }), + ...(cli.silent && { level: 'silent' as const }), + ...(cli.logFile !== undefined && { file: cli.logFile }), + }; + } + + if (cli.watch !== undefined) { + if (typeof cli.watch === 'string') { + config.watch = Number.parseInt(cli.watch, 10); + } else { + config.watch = cli.watch; + } + } + + return config; +}; diff --git a/packages/openapi-ts/src/cli/index.ts b/packages/openapi-ts/src/cli/index.ts new file mode 100644 index 000000000..7c02bd893 --- /dev/null +++ b/packages/openapi-ts/src/cli/index.ts @@ -0,0 +1,67 @@ +import { Command, CommanderError } from 'commander'; + +import { createClient } from '~/index'; + +import pkg from '../../package.json' assert { type: 'json' }; +import { cliToConfig } from './adapter'; + +const binName = Object.keys(pkg.bin)[0]!; + +const program = new Command() + .name(binName) + .description('Generate TypeScript code from OpenAPI specifications') + .version(pkg.version); + +program + .option( + '-i, --input ', + 'OpenAPI specification (path, URL, or string)', + ) + .option('-o, --output ', 'Output folder(s)') + .option('-c, --client ', 'HTTP client to generate') + .option('-p, --plugins [names...]', 'Plugins to use') + .option('-f, --file ', 'Path to config file') + .option('-d, --debug', 'Enable debug logging') + .option('-s, --silent', 'Suppress all output') + .option('-l, --logs ', 'Logs folder path') + .option('--no-log-file', 'Disable log file output') + .option('--dry-run', 'Skip writing files') + .option('-w, --watch [interval]', 'Watch for changes') + .action(async (options) => { + const config = cliToConfig(options); + + const context = await createClient( + config as Parameters[0], + ); + + const hasActiveWatch = context[0]?.config.input.some( + (input) => input.watch?.enabled, + ); + + if (!hasActiveWatch) { + process.exit(0); + } + }); + +export async function runCli(): Promise { + try { + await program.parseAsync(process.argv); + } catch (error) { + if (error instanceof CommanderError && 'code' in error) { + if (error.code === 'commander.optionMissingArgument') { + console.error( + `\nMissing required argument. Run '${binName} --help' for usage.\n`, + ); + } else if (error.code === 'commander.unknownOption') { + console.error( + `\nUnknown option. Run '${binName} --help' for available options.\n`, + ); + } + + process.exit(error.exitCode); + } + + console.error('Unexpected error:', error); + process.exit(1); + } +} diff --git a/packages/openapi-ts/src/cli/schema.ts b/packages/openapi-ts/src/cli/schema.ts new file mode 100644 index 000000000..0cd428a83 --- /dev/null +++ b/packages/openapi-ts/src/cli/schema.ts @@ -0,0 +1,17 @@ +import type { MaybeArray } from '@hey-api/types'; + +import type { PluginClientNames, PluginNames } from '~/plugins/types'; + +export interface CliOptions { + client?: PluginClientNames; + debug?: boolean; + dryRun?: boolean; + file?: string; + input?: MaybeArray; + logFile?: boolean; + logs?: string; + output?: MaybeArray; + plugins?: ReadonlyArray; + silent?: boolean; + watch?: boolean | string; +} diff --git a/packages/openapi-ts/src/config/expand.ts b/packages/openapi-ts/src/config/expand.ts new file mode 100644 index 000000000..0c78e1d75 --- /dev/null +++ b/packages/openapi-ts/src/config/expand.ts @@ -0,0 +1,54 @@ +import colors from 'ansi-colors'; + +import { getInput } from './input'; +import type { UserConfig } from './types'; + +export interface Job { + config: UserConfig; + index: number; +} + +export function expandToJobs( + configs: ReadonlyArray, +): ReadonlyArray { + const jobs: Array = []; + let jobIndex = 0; + + for (const config of configs) { + const inputs = getInput(config); + const outputs = + config.output instanceof Array ? config.output : [config.output]; + + if (outputs.length === 1) { + jobs.push({ + config: { + ...config, + input: inputs, + output: outputs[0]!, // output array with single item + }, + index: jobIndex++, + }); + } else if (outputs.length > 1 && inputs.length !== outputs.length) { + // Warn and create job per output (all with same inputs) + console.warn( + `โš™๏ธ ${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.`, + ); + for (const output of outputs) { + jobs.push({ + config: { ...config, input: inputs, output }, + index: jobIndex++, + }); + } + } else if (outputs.length > 1) { + // Pair inputs with outputs by index + outputs.forEach((output, index) => { + jobs.push({ + config: { ...config, input: inputs[index]!, output }, + index: jobIndex++, + }); + }); + } + } + + return jobs; +} diff --git a/packages/openapi-ts/src/config/init.ts b/packages/openapi-ts/src/config/init.ts index 7068e3368..a668b35ed 100644 --- a/packages/openapi-ts/src/config/init.ts +++ b/packages/openapi-ts/src/config/init.ts @@ -1,184 +1,71 @@ -import path from 'node:path'; - import type { Logger } from '@hey-api/codegen-core'; -import type { ArrayOnly } from '@hey-api/types'; -import colors from 'ansi-colors'; - -import { ConfigError } from '~/error'; +import { loadConfigFile } from '@hey-api/codegen-core'; -import { getInput } from './input'; -import { getLogs } from './logs'; -import { mergeConfigs } from './merge'; -import { getOutput } from './output'; +import { expandToJobs } from './expand'; import { getProjectDependencies } from './packages'; -import { getParser } from './parser'; -import { getPlugins } from './plugins'; -import type { Config, UserConfig } from './types'; - -type ConfigResult = { - config: Config; - errors: ReadonlyArray; - jobIndex: number; -}; +import type { ResolvedJob } from './resolve'; +import { resolveConfig } from './resolve'; +import type { UserConfig } from './types'; +import { validateJobs } from './validate'; export type Configs = { dependencies: Record; - results: ReadonlyArray; + jobs: ReadonlyArray; + /** + * @deprecated Use `jobs` instead. + */ + results: ReadonlyArray; }; -/** - * Detect if the current session is interactive based on TTY status and environment variables. - * This is used as a fallback when the user doesn't explicitly set the interactive option. - * @internal - */ -export const detectInteractiveSession = (): boolean => - Boolean( - process.stdin.isTTY && - process.stdout.isTTY && - !process.env.CI && - !process.env.NO_INTERACTIVE && - !process.env.NO_INTERACTION, - ); - /** * @internal */ -export const initConfigs = async ({ +export async function resolveJobs({ logger, userConfigs, }: { logger: Logger; userConfigs: ReadonlyArray; -}): Promise => { +}): Promise { const configs: Array = []; let dependencies: Record = {}; const eventLoad = logger.timeEvent('load'); for (const userConfig of userConfigs) { - let configurationFile: string | undefined = undefined; - if (userConfig?.configFile) { + let configFile: string | undefined; + if (userConfig.configFile) { const parts = userConfig.configFile.split('.'); - configurationFile = parts.slice(0, parts.length - 1).join('.'); + configFile = parts.slice(0, parts.length - 1).join('.'); } - const eventC12 = logger.timeEvent('c12'); - // c12 is ESM-only since v3 - const { loadConfig } = await import('c12'); - const { config: configFromFile, configFile: loadedConfigFile } = - await loadConfig({ - configFile: configurationFile, - name: 'openapi-ts', - }); - eventC12.timeEnd(); + const loaded = await loadConfigFile({ + configFile, + logger, + name: 'openapi-ts', + userConfig, + }); if (!Object.keys(dependencies).length) { // TODO: handle dependencies for multiple configs properly? dependencies = getProjectDependencies( - Object.keys(configFromFile).length ? loadedConfigFile : undefined, + loaded.foundConfig ? loaded.configFile : undefined, ); } - const mergedConfigs = - configFromFile instanceof Array - ? configFromFile.map((config) => mergeConfigs(config, userConfig)) - : [mergeConfigs(configFromFile, userConfig)]; - - for (const mergedConfig of mergedConfigs) { - const input = getInput(mergedConfig); - - if (mergedConfig.output instanceof Array) { - const countInputs = input.length; - const countOutputs = mergedConfig.output.length; - if (countOutputs > 1) { - if (countInputs !== countOutputs) { - console.warn( - `โš™๏ธ ${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.`, - ); - for (const output of mergedConfig.output) { - configs.push({ ...mergedConfig, input, output }); - } - } else { - mergedConfig.output.forEach((output, index) => { - configs.push({ ...mergedConfig, input: input[index]!, output }); - }); - } - } else { - configs.push({ - ...mergedConfig, - input, - output: mergedConfig.output[0] ?? '', - }); - } - } else { - configs.push({ ...mergedConfig, input }); - } - } + configs.push(...loaded.configs); } eventLoad.timeEnd(); - const results: Array> = []; - const eventBuild = logger.timeEvent('build'); - for (const userConfig of configs) { - const logs = getLogs(userConfig); - const input = getInput(userConfig); - const output = getOutput(userConfig); - const parser = getParser(userConfig); - - const errors: Array = []; - - if (!input.length) { - errors.push( - new ConfigError( - 'missing input - which OpenAPI specification should we use to generate your output?', - ), - ); - } - - if (!output.path) { - errors.push( - new ConfigError( - 'missing output - where should we generate your output?', - ), - ); - } - - output.path = path.resolve(process.cwd(), output.path); - - let plugins: Pick; - - try { - plugins = getPlugins({ dependencies, userConfig }); - } catch (error) { - errors.push(error); - plugins = { - pluginOrder: [], - plugins: {}, - }; - } - - const config: Config = { - configFile: userConfig.configFile ?? '', - dryRun: userConfig.dryRun ?? false, - input, - interactive: userConfig.interactive ?? detectInteractiveSession(), - logs, - output, - parser, - pluginOrder: plugins.pluginOrder, - plugins: plugins.plugins, - }; - - const jobIndex = results.length; - - if (logs.level === 'debug') { - const jobPrefix = colors.gray(`[Job ${jobIndex + 1}] `); - console.warn(`${jobPrefix}${colors.cyan('config:')}`, config); - } - - results.push({ config, errors, jobIndex }); - } + const jobs = validateJobs(expandToJobs(configs)); + const resolvedJobs = jobs.map((validated) => + resolveConfig(validated, dependencies), + ); eventBuild.timeEnd(); - return { dependencies, results }; -}; + return { + dependencies, + jobs: resolvedJobs, + results: resolvedJobs, + }; +} diff --git a/packages/openapi-ts/src/config/merge.ts b/packages/openapi-ts/src/config/merge.ts deleted file mode 100644 index a4ce11067..000000000 --- a/packages/openapi-ts/src/config/merge.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { UserConfig } from './types'; - -const mergeObjects = ( - objA: Record | undefined, - objB: Record | undefined, -): Record => { - const a = objA || {}; - const b = objB || {}; - return { - ...a, - ...b, - }; -}; - -export const mergeConfigs = ( - configA: UserConfig | undefined, - configB: UserConfig | undefined, -): UserConfig => { - const a: Partial = configA || {}; - const b: Partial = configB || {}; - const merged: UserConfig = { - ...(a as UserConfig), - ...(b as UserConfig), - }; - if (typeof merged.logs === 'object') { - merged.logs = mergeObjects( - a.logs as Record, - b.logs as Record, - ); - } - return merged; -}; diff --git a/packages/openapi-ts/src/config/resolve.ts b/packages/openapi-ts/src/config/resolve.ts new file mode 100644 index 000000000..19352320e --- /dev/null +++ b/packages/openapi-ts/src/config/resolve.ts @@ -0,0 +1,65 @@ +import path from 'node:path'; + +import { detectInteractiveSession } from '@hey-api/codegen-core'; +import colors from 'ansi-colors'; + +import { getInput } from './input'; +import { getLogs } from './logs'; +import { getOutput } from './output'; +import { getParser } from './parser'; +import { getPlugins } from './plugins'; +import type { Config } from './types'; +import type { ValidationResult } from './validate'; + +export type ResolvedJob = { + config: Config; + errors: Array; + index: number; +}; + +export const resolveConfig = ( + validated: ValidationResult, + dependencies: Record, +): ResolvedJob => { + const logs = getLogs(validated.job.config); + const input = getInput(validated.job.config); + const output = getOutput(validated.job.config); + const parser = getParser(validated.job.config); + + output.path = path.resolve(process.cwd(), output.path); + + let plugins: Pick; + + try { + plugins = getPlugins({ dependencies, userConfig: validated.job.config }); + } catch (error) { + validated.errors.push(error); + plugins = { + pluginOrder: [], + plugins: {}, + }; + } + + const config: Config = { + configFile: validated.job.config.configFile ?? '', + dryRun: validated.job.config.dryRun ?? false, + input, + interactive: validated.job.config.interactive ?? detectInteractiveSession(), + logs, + output, + parser, + pluginOrder: plugins.pluginOrder, + plugins: plugins.plugins, + }; + + if (logs.level === 'debug') { + const jobPrefix = colors.gray(`[Job ${validated.job.index}] `); + console.warn(`${jobPrefix}${colors.cyan('config:')}`, config); + } + + return { + config, + errors: validated.errors, + index: validated.job.index, + }; +}; diff --git a/packages/openapi-ts/src/config/types.d.ts b/packages/openapi-ts/src/config/types.d.ts index 940423b18..c6568a935 100644 --- a/packages/openapi-ts/src/config/types.d.ts +++ b/packages/openapi-ts/src/config/types.d.ts @@ -9,7 +9,7 @@ import type { Parser, UserParser } from '~/types/parser'; import type { Output, UserOutput } from './output'; -export interface UserConfig { +export type UserConfig = { /** * Path to the config file. Set this value if you don't use the default * config file name, or it's not located in the project root. @@ -81,7 +81,7 @@ export interface UserConfig { * @deprecated use `input.watch` instead */ watch?: boolean | number | Watch; -} +}; export type Config = Omit< Required, diff --git a/packages/openapi-ts/src/config/validate.ts b/packages/openapi-ts/src/config/validate.ts new file mode 100644 index 000000000..2be89f161 --- /dev/null +++ b/packages/openapi-ts/src/config/validate.ts @@ -0,0 +1,39 @@ +import { ConfigError } from '~/error'; + +import type { Job } from './expand'; +import { getInput } from './input'; +import { getOutput } from './output'; + +export interface ValidationResult { + errors: Array; + job: Job; +} + +export function validateJobs( + jobs: ReadonlyArray, +): ReadonlyArray { + return jobs.map((job) => { + const errors: Array = []; + const { config } = job; + + const inputs = getInput(config); + if (!inputs.length) { + errors.push( + new ConfigError( + 'missing input - which OpenAPI specification should we use to generate your output?', + ), + ); + } + + const output = getOutput(config); + if (!output.path) { + errors.push( + new ConfigError( + 'missing output - where should we generate your output?', + ), + ); + } + + return { errors, job }; + }); +} diff --git a/packages/openapi-ts/src/generate.ts b/packages/openapi-ts/src/generate.ts index 6aba3be51..bdbb3907c 100644 --- a/packages/openapi-ts/src/generate.ts +++ b/packages/openapi-ts/src/generate.ts @@ -3,7 +3,7 @@ import type { LazyOrAsync, MaybeArray } from '@hey-api/types'; import { checkNodeVersion } from '~/config/engine'; import type { Configs } from '~/config/init'; -import { initConfigs } from '~/config/init'; +import { resolveJobs } from '~/config/init'; import { getLogs } from '~/config/logs'; import type { UserConfig } from '~/config/types'; import { createClient as pCreateClient } from '~/createClient'; @@ -23,10 +23,10 @@ import { printCliIntro } from '~/utils/cli'; * * @param userConfig User provided {@link UserConfig} configuration(s). */ -export const createClient = async ( +export async function createClient( userConfig?: LazyOrAsync>, logger = new Logger(), -): Promise> => { +): Promise> { const resolvedConfig = typeof userConfig === 'function' ? await userConfig() : userConfig; const userConfigs = resolvedConfig @@ -42,7 +42,7 @@ export const createClient = async ( rawLogs = getLogs({ logs: rawLogs }); } - let configs: Configs | undefined; + let jobs: Configs['jobs'] = []; try { checkNodeVersion(); @@ -50,61 +50,51 @@ export const createClient = async ( const eventCreateClient = logger.timeEvent('createClient'); const eventConfig = logger.timeEvent('config'); - configs = await initConfigs({ logger, userConfigs }); - const printIntro = configs.results.some( - (result) => result.config.logs.level !== 'silent', - ); - if (printIntro) { - printCliIntro(); - } + const resolved = await resolveJobs({ logger, userConfigs }); + const dependencies = resolved.dependencies; + jobs = resolved.jobs; + const printIntro = jobs.some((job) => job.config.logs.level !== 'silent'); + if (printIntro) printCliIntro(); eventConfig.timeEnd(); - const allConfigErrors = configs.results.flatMap((result) => - result.errors.map((error) => ({ error, jobIndex: result.jobIndex })), + const configErrors = jobs.flatMap((job) => + job.errors.map((error) => ({ error, jobIndex: job.index })), ); - if (allConfigErrors.length) { - throw new ConfigValidationError(allConfigErrors); + if (configErrors.length > 0) { + throw new ConfigValidationError(configErrors); } - const clients = await Promise.all( - configs.results.map(async (result) => { + const outputs = await Promise.all( + jobs.map(async (job) => { try { return await pCreateClient({ - config: result.config, - dependencies: configs!.dependencies, - jobIndex: result.jobIndex, + config: job.config, + dependencies, + jobIndex: job.index, logger, }); } catch (error) { throw new JobError('', { error, - jobIndex: result.jobIndex, + jobIndex: job.index, }); } }), ); - const result = clients.filter((client) => - Boolean(client), - ) as ReadonlyArray; + const contexts = outputs.filter((ctx): ctx is Context => ctx !== undefined); eventCreateClient.timeEnd(); - const printLogs = configs.results.some( - (result) => result.config.logs.level === 'debug', - ); - logger.report(printLogs); + logger.report(jobs.some((job) => job.config.logs.level === 'debug')); - return result; + return contexts; } catch (error) { - const results = configs?.results ?? []; - const logs = - results.find((result) => result.config.logs.level !== 'silent')?.config - .logs ?? - results[0]?.config.logs ?? + jobs.find((job) => job.config.logs.level !== 'silent')?.config.logs ?? + jobs[0]?.config.logs ?? rawLogs; const dryRun = - results.some((result) => result.config.dryRun) ?? + jobs.some((job) => job.config.dryRun) ?? userConfigs.some((config) => config.dryRun) ?? false; const logPath = @@ -114,7 +104,7 @@ export const createClient = async ( if (!logs || logs.level !== 'silent') { printCrashReport({ error, logPath }); const isInteractive = - results.some((result) => result.config.interactive) ?? + jobs.some((job) => job.config.interactive) ?? userConfigs.some((config) => config.interactive) ?? false; if (await shouldReportCrash({ error, isInteractive })) { @@ -124,4 +114,4 @@ export const createClient = async ( throw error; } -}; +} diff --git a/packages/openapi-ts/src/index.ts b/packages/openapi-ts/src/index.ts index 63ea62111..f2bf9a14e 100644 --- a/packages/openapi-ts/src/index.ts +++ b/packages/openapi-ts/src/index.ts @@ -79,11 +79,13 @@ colors.enabled = colorSupport().hasBasic; export { createClient } from '~/generate'; /** - * Type helper for openapi-ts.config.ts, returns {@link MaybeArray} object(s) + * Type helper for configuration object, returns {@link MaybeArray} object(s) */ -export const defineConfig = async >( +export async function defineConfig>( config: LazyOrAsync, -): Promise => (typeof config === 'function' ? await config() : config); +): Promise { + return typeof config === 'function' ? await config() : config; +} export { Logger } from '@hey-api/codegen-core'; export { defaultPaginationKeywords } from '~/config/parser'; diff --git a/packages/openapi-ts/src/internal.ts b/packages/openapi-ts/src/internal.ts index ec80d9485..5ed164152 100644 --- a/packages/openapi-ts/src/internal.ts +++ b/packages/openapi-ts/src/internal.ts @@ -1,3 +1,3 @@ -export { initConfigs } from './config/init'; +export { resolveJobs as initConfigs } from './config/init'; export { getSpec } from './getSpec'; export { parseOpenApiSpec } from './openApi'; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 01ab8a619..2a866c98d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,8 +1,13 @@ +/** + * An object with string keys and unknown values. + */ +export type AnyObject = Record; + /** * Converts all top-level ReadonlyArray properties to Array (shallow). */ export type ArrayOnly = { - [K in keyof T]: T[K] extends ReadonlyArray ? Array : T[K]; + [K in keyof T]: ToArray; }; /** @@ -42,5 +47,16 @@ export type MaybePromise = T | Promise; * Converts all top-level Array properties to ReadonlyArray (shallow). */ export type ReadonlyArrayOnly = { - [K in keyof T]: T[K] extends Array ? ReadonlyArray : T[K]; + [K in keyof T]: ToReadonlyArray; }; + +/** + * Converts ReadonlyArray to Array, preserving unions. + */ +export type ToArray = T extends ReadonlyArray ? Array : T; + +/** + * Converts Array to ReadonlyArray, preserving unions. + */ +export type ToReadonlyArray = + T extends ReadonlyArray ? ReadonlyArray : T; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04b8bcda9..083800e11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1253,6 +1253,9 @@ importers: ansi-colors: specifier: 4.1.3 version: 4.1.3 + c12: + specifier: 3.3.3 + version: 3.3.3(magicast@0.3.5) color-support: specifier: 1.1.3 version: 1.1.3 @@ -1346,9 +1349,6 @@ importers: ansi-colors: specifier: 4.1.3 version: 4.1.3 - c12: - specifier: 3.3.3 - version: 3.3.3(magicast@0.3.5) color-support: specifier: 1.1.3 version: 1.1.3 @@ -1401,9 +1401,6 @@ importers: ansi-colors: specifier: 4.1.3 version: 4.1.3 - c12: - specifier: 3.3.3 - version: 3.3.3(magicast@0.3.5) color-support: specifier: 1.1.3 version: 1.1.3 @@ -7908,14 +7905,6 @@ packages: magicast: optional: true - c12@3.3.2: - resolution: {integrity: sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A==} - peerDependencies: - magicast: '*' - peerDependenciesMeta: - magicast: - optional: true - c12@3.3.3: resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==} peerDependencies: @@ -14693,7 +14682,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14713,7 +14702,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14781,7 +14770,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14801,7 +14790,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14889,7 +14878,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -19166,7 +19155,7 @@ snapshots: '@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))': dependencies: '@nuxt/kit': 3.20.2(magicast@0.3.5) - c12: 3.3.2(magicast@0.3.5) + c12: 3.3.3(magicast@0.3.5) consola: 3.4.2 defu: 6.1.4 destr: 2.0.5 @@ -22379,7 +22368,7 @@ snapshots: schema-utils: 4.3.2 webpack: 5.98.0(esbuild@0.25.0) - babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0): + babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)): dependencies: '@babel/core': 7.26.9 find-cache-dir: 4.0.0 @@ -22596,23 +22585,6 @@ snapshots: optionalDependencies: magicast: 0.3.5 - c12@3.3.2(magicast@0.3.5): - dependencies: - chokidar: 4.0.3 - confbox: 0.2.2 - defu: 6.1.4 - dotenv: 17.2.3 - exsolve: 1.0.8 - giget: 2.0.0 - jiti: 2.6.1 - ohash: 2.0.11 - pathe: 2.0.3 - perfect-debounce: 2.0.0 - pkg-types: 2.3.0 - rc9: 2.1.2 - optionalDependencies: - magicast: 0.3.5 - c12@3.3.3(magicast@0.3.5): dependencies: chokidar: 5.0.0 @@ -27869,7 +27841,7 @@ snapshots: ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.9.3) optional: true - postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0): + postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)): dependencies: cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.7 -- 2.51.2 From 820c2022fe087a28e811ae55ce9381260ea2d5f4 Mon Sep 17 00:00:00 2001 From: Lubos Date: Fri, 23 Jan 2026 04:16:42 +0800 Subject: [PATCH 08/14] docs: update copy on versioning --- docs/openapi-ts/get-started.md | 10 ++-------- packages/openapi-ts/README.md | 10 ++-------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/docs/openapi-ts/get-started.md b/docs/openapi-ts/get-started.md index 6a6f52e59..9206020b8 100644 --- a/docs/openapi-ts/get-started.md +++ b/docs/openapi-ts/get-started.md @@ -70,15 +70,9 @@ bun add @hey-api/openapi-ts -D -E ### Versioning -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. +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. -Due to the nature of the package, we use the following versioning strategy. - -- `1.x.x`: significant breaking changes, reserved for v1 release -- `x.1.x`: breaking changes -- `x.x.1`: new features, bug fixes, and non-breaking changes - -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). +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. ## Usage diff --git a/packages/openapi-ts/README.md b/packages/openapi-ts/README.md index 3a5e6a18e..ebbe9f69f 100644 --- a/packages/openapi-ts/README.md +++ b/packages/openapi-ts/README.md @@ -214,15 +214,9 @@ bun add @hey-api/openapi-ts -D -E ### Versioning -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. +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. -Due to the nature of the package, we use the following versioning strategy. - -- `1.x.x`: significant breaking changes, reserved for v1 release -- `x.1.x`: breaking changes -- `x.x.1`: new features, bug fixes, and non-breaking changes - -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). +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. ## Usage -- 2.51.2 From 7be1561a862d2bb217703003d5e6cbc4a6a09586 Mon Sep 17 00:00:00 2001 From: Lubos Date: Fri, 23 Jan 2026 05:40:37 +0800 Subject: [PATCH 09/14] fix: object ts-dsl improvements, ability to remove --- .changeset/big-streets-behave.md | 5 ++ .changeset/ready-dots-beg.md | 5 ++ dev/openapi-ts.config.ts | 3 +- packages/openapi-ts/src/ts-dsl/expr/object.ts | 83 ++++++++++++++----- packages/openapi-ts/src/ts-dsl/expr/prop.ts | 45 ++++++---- .../openapi-ts/src/ts-dsl/type/idx-sig.ts | 11 +++ packages/openapi-ts/src/ts-dsl/type/object.ts | 48 +++++++---- packages/openapi-ts/src/ts-dsl/type/prop.ts | 11 +++ 8 files changed, 157 insertions(+), 54 deletions(-) create mode 100644 .changeset/big-streets-behave.md create mode 100644 .changeset/ready-dots-beg.md diff --git a/.changeset/big-streets-behave.md b/.changeset/big-streets-behave.md new file mode 100644 index 000000000..7a6a7affc --- /dev/null +++ b/.changeset/big-streets-behave.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +**ts-dsl**: allow removing object properties by passing `null` diff --git a/.changeset/ready-dots-beg.md b/.changeset/ready-dots-beg.md new file mode 100644 index 000000000..06dea506b --- /dev/null +++ b/.changeset/ready-dots-beg.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +**ts-dsl**: override object properties when called multiple times with the same name diff --git a/dev/openapi-ts.config.ts b/dev/openapi-ts.config.ts index 521880cdc..2e34aca1b 100644 --- a/dev/openapi-ts.config.ts +++ b/dev/openapi-ts.config.ts @@ -475,7 +475,7 @@ export default defineConfig(() => { // definitions: 'z{{name}}', exportFromIndex: true, // metadata: true, - // name: 'valibot', + name: 'valibot', // requests: { // case: 'PascalCase', // name: '{{name}}Data', @@ -524,6 +524,7 @@ export default defineConfig(() => { const additional = ctx.nodes.additionalProperties(ctx); if (additional === undefined) { const shape = ctx.nodes.shape(ctx); + shape.prop('body', $(v).attr('never').call()); ctx.nodes.base = () => $(v).attr('looseObject').call(shape); } }, diff --git a/packages/openapi-ts/src/ts-dsl/expr/object.ts b/packages/openapi-ts/src/ts-dsl/expr/object.ts index 3f4b771f8..9e0c6c50f 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/object.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/object.ts @@ -21,7 +21,8 @@ const Mixed = AsMixin( export class ObjectTsDsl extends Mixed { readonly '~dsl' = 'ObjectTsDsl'; - protected _props: Array = []; + protected _props = new Map(); + protected _spreadCounter = 0; constructor(...props: Array) { super(); @@ -30,63 +31,101 @@ export class ObjectTsDsl extends Mixed { override analyze(ctx: AnalysisContext): void { super.analyze(ctx); - for (const prop of this._props) { + for (const prop of this._props.values()) { ctx.analyze(prop); } } - /** Adds a computed property (e.g. `{ [expr]: value }`). */ - computed(name: string, expr: ExprFn): this { - this._props.push( - new ObjectPropTsDsl({ kind: 'computed', name }).value(expr), - ); + /** Returns composite key for the property. */ + private _propKey(prop: ObjectPropTsDsl): string { + if (prop.kind === 'spread') { + return `spread:${this._spreadCounter++}`; + } + return `${prop.kind}:${prop.propName}`; + } + + /** Adds a computed property (e.g. `{ [expr]: value }`), or removes if null. */ + computed(name: string, expr: ExprFn | null): this { + if (expr === null) { + this._props.delete(`computed:${name}`); + } else { + this._props.set( + `computed:${name}`, + new ObjectPropTsDsl({ kind: 'computed', name }).value(expr), + ); + } return this; } - /** Adds a getter property (e.g. `{ get foo() { ... } }`). */ - getter(name: string, stmt: StmtFn): this { - this._props.push(new ObjectPropTsDsl({ kind: 'getter', name }).value(stmt)); + /** Adds a getter property (e.g. `{ get foo() { ... } }`), or removes if null. */ + getter(name: string, stmt: StmtFn | null): this { + if (stmt === null) { + this._props.delete(`getter:${name}`); + } else { + this._props.set( + `getter:${name}`, + new ObjectPropTsDsl({ kind: 'getter', name }).value(stmt), + ); + } return this; } /** Returns true if object has at least one property or spread. */ hasProps(): boolean { - return this._props.length > 0; + return this._props.size > 0; } /** Returns true if object has no properties or spreads. */ get isEmpty(): boolean { - return this._props.length === 0; + return this._props.size === 0; } - /** Adds a property assignment. */ - prop(name: string, expr: ExprFn): this { - this._props.push(new ObjectPropTsDsl({ kind: 'prop', name }).value(expr)); + /** Adds a property assignment, or removes if null. */ + prop(name: string, expr: ExprFn | null): this { + if (expr === null) { + this._props.delete(`prop:${name}`); + } else { + this._props.set( + `prop:${name}`, + new ObjectPropTsDsl({ kind: 'prop', name }).value(expr), + ); + } return this; } /** Adds multiple properties. */ props(...props: ReadonlyArray): this { - this._props.push(...props); + for (const prop of props) { + this._props.set(this._propKey(prop), prop); + } return this; } - /** Adds a setter property (e.g. `{ set foo(v) { ... } }`). */ - setter(name: string, stmt: StmtFn): this { - this._props.push(new ObjectPropTsDsl({ kind: 'setter', name }).value(stmt)); + /** Adds a setter property (e.g. `{ set foo(v) { ... } }`), or removes if null. */ + setter(name: string, stmt: StmtFn | null): this { + if (stmt === null) { + this._props.delete(`setter:${name}`); + } else { + this._props.set( + `setter:${name}`, + new ObjectPropTsDsl({ kind: 'setter', name }).value(stmt), + ); + } return this; } /** Adds a spread property (e.g. `{ ...options }`). */ spread(expr: ExprFn): this { - this._props.push(new ObjectPropTsDsl({ kind: 'spread' }).value(expr)); + const key = `spread:${this._spreadCounter++}`; + this._props.set(key, new ObjectPropTsDsl({ kind: 'spread' }).value(expr)); return this; } override toAst() { + const props = [...this._props.values()]; const node = ts.factory.createObjectLiteralExpression( - this.$node(this._props), - this.$multiline(this._props.length), + this.$node(props), + this.$multiline(props.length), ); return this.$hint(node); } diff --git a/packages/openapi-ts/src/ts-dsl/expr/prop.ts b/packages/openapi-ts/src/ts-dsl/expr/prop.ts index 9a94b75fd..9a795604b 100644 --- a/packages/openapi-ts/src/ts-dsl/expr/prop.ts +++ b/packages/openapi-ts/src/ts-dsl/expr/prop.ts @@ -12,7 +12,13 @@ import { IdTsDsl } from './id'; type Expr = NodeName | MaybeTsDsl; type Stmt = NodeName | MaybeTsDsl; -type Kind = 'computed' | 'getter' | 'prop' | 'setter' | 'spread'; + +export type ObjectPropKind = + | 'computed' + | 'getter' + | 'prop' + | 'setter' + | 'spread'; type Meta = | { kind: 'computed'; name: string } @@ -27,11 +33,19 @@ export class ObjectPropTsDsl extends Mixed { readonly '~dsl' = 'ObjectPropTsDsl'; protected _value?: Ref; - protected meta: Meta; + protected _meta: Meta; constructor(meta: Meta) { super(); - this.meta = meta; + this._meta = meta; + } + + get kind(): ObjectPropKind { + return this._meta.kind; + } + + get propName(): string | undefined { + return this._meta.name; } override analyze(ctx: AnalysisContext): void { @@ -39,7 +53,6 @@ export class ObjectPropTsDsl extends Mixed { ctx.analyze(this._value); } - /** Returns true when all required builder calls are present. */ get isValid(): boolean { return this.missingRequiredCalls().length === 0; } @@ -56,7 +69,7 @@ export class ObjectPropTsDsl extends Mixed { override toAst() { this.$validate(); const node = this.$node(this._value); - if (this.meta.kind === 'spread') { + if (this._meta.kind === 'spread') { if (ts.isStatement(node)) { throw new Error( 'Invalid spread: object spread must be an expression, not a statement.', @@ -65,19 +78,19 @@ export class ObjectPropTsDsl extends Mixed { const result = ts.factory.createSpreadAssignment(node); return this.$docs(result); } - if (this.meta.kind === 'getter') { - const getter = new GetterTsDsl(this.meta.name).do(node); + if (this._meta.kind === 'getter') { + const getter = new GetterTsDsl(this._meta.name).do(node); const result = this.$node(getter); return this.$docs(result); } - if (this.meta.kind === 'setter') { - const setter = new SetterTsDsl(this.meta.name).do(node); + if (this._meta.kind === 'setter') { + const setter = new SetterTsDsl(this._meta.name).do(node); const result = this.$node(setter); return this.$docs(result); } - if (ts.isIdentifier(node) && node.text === this.meta.name) { + if (ts.isIdentifier(node) && node.text === this._meta.name) { const result = ts.factory.createShorthandPropertyAssignment( - this.meta.name, + this._meta.name, ); return this.$docs(result); } @@ -87,11 +100,11 @@ export class ObjectPropTsDsl extends Mixed { ); } const result = ts.factory.createPropertyAssignment( - this.meta.kind === 'computed' + this._meta.kind === 'computed' ? ts.factory.createComputedPropertyName( - this.$node(new IdTsDsl(this.meta.name)), + this.$node(new IdTsDsl(this._meta.name)), ) - : this.$node(safePropName(this.meta.name)), + : this.$node(safePropName(this._meta.name)), node, ); return this.$docs(result); @@ -99,12 +112,12 @@ export class ObjectPropTsDsl extends Mixed { $validate(): asserts this is this & { _value: Expr | Stmt; - kind: Kind; + kind: ObjectPropKind; } { const missing = this.missingRequiredCalls(); if (missing.length === 0) return; throw new Error( - `Object property${this.meta.name ? ` "${this.meta.name}"` : ''} missing ${missing.join(' and ')}`, + `Object property${this._meta.name ? ` "${this._meta.name}"` : ''} missing ${missing.join(' and ')}`, ); } diff --git a/packages/openapi-ts/src/ts-dsl/type/idx-sig.ts b/packages/openapi-ts/src/ts-dsl/type/idx-sig.ts index 1bddd6030..f30ac3a57 100644 --- a/packages/openapi-ts/src/ts-dsl/type/idx-sig.ts +++ b/packages/openapi-ts/src/ts-dsl/type/idx-sig.ts @@ -11,6 +11,7 @@ import { DocMixin } from '../mixins/doc'; import { ReadonlyMixin } from '../mixins/modifiers'; export type TypeIdxSigType = string | MaybeTsDsl; +export type TypeIdxSigKind = 'idxSig'; const Mixed = DocMixin(ReadonlyMixin(TsDsl)); @@ -27,6 +28,16 @@ export class TypeIdxSigTsDsl extends Mixed { fn?.(this); } + /** Element kind. */ + get kind(): TypeIdxSigKind { + return 'idxSig'; + } + + /** Index signature parameter name. */ + get propName(): string { + return this.name.toString(); + } + override analyze(ctx: AnalysisContext): void { super.analyze(ctx); ctx.analyze(this._key); diff --git a/packages/openapi-ts/src/ts-dsl/type/object.ts b/packages/openapi-ts/src/ts-dsl/type/object.ts index 24091060f..ecdd331db 100644 --- a/packages/openapi-ts/src/ts-dsl/type/object.ts +++ b/packages/openapi-ts/src/ts-dsl/type/object.ts @@ -11,40 +11,58 @@ export class TypeObjectTsDsl extends Mixed { readonly '~dsl' = 'TypeObjectTsDsl'; override scope: NodeScope = 'type'; - protected props: Array = []; + protected _props = new Map(); override analyze(ctx: AnalysisContext): void { super.analyze(ctx); - for (const prop of this.props) { + for (const prop of this._props.values()) { ctx.analyze(prop); } } - /** Returns true if object has at least one property or spread. */ + /** Returns true if object has at least one property or index signature. */ hasProps(): boolean { - return this.props.length > 0; + return this._props.size > 0; } - /** Adds an index signature to the object type. */ - idxSig(name: string, fn: (i: TypeIdxSigTsDsl) => void): this { - const idx = new TypeIdxSigTsDsl(name, fn); - this.props.push(idx); + /** Adds an index signature to the object type, or removes if fn is null. */ + idxSig(name: string, fn: ((i: TypeIdxSigTsDsl) => void) | null): this { + const key = `idxSig:${name}`; + if (fn === null) { + this._props.delete(key); + } else { + this._props.set(key, new TypeIdxSigTsDsl(name, fn)); + } return this; } - /** Returns true if object has no properties or spreads. */ + /** Returns true if object has no properties or index signatures. */ get isEmpty(): boolean { - return !this.props.length; + return this._props.size === 0; + } + + /** Adds a property signature, or removes if fn is null. */ + prop(name: string, fn: ((p: TypePropTsDsl) => void) | null): this { + const key = `prop:${name}`; + if (fn === null) { + this._props.delete(key); + } else { + this._props.set(key, new TypePropTsDsl(name, fn)); + } + return this; } - /** Adds a property signature (returns property builder). */ - prop(name: string, fn: (p: TypePropTsDsl) => void): this { - const prop = new TypePropTsDsl(name, fn); - this.props.push(prop); + /** Adds multiple properties/index signatures. */ + props(...members: ReadonlyArray): this { + for (const member of members) { + this._props.set(`${member.kind}:${member.propName}`, member); + } return this; } override toAst() { - return ts.factory.createTypeLiteralNode(this.$node(this.props)); + return ts.factory.createTypeLiteralNode( + this.$node([...this._props.values()]), + ); } } diff --git a/packages/openapi-ts/src/ts-dsl/type/prop.ts b/packages/openapi-ts/src/ts-dsl/type/prop.ts index ec19c68f8..5cf5d4cd3 100644 --- a/packages/openapi-ts/src/ts-dsl/type/prop.ts +++ b/packages/openapi-ts/src/ts-dsl/type/prop.ts @@ -16,6 +16,7 @@ import { TokenTsDsl } from '../token'; import { safePropName } from '../utils/name'; export type TypePropType = NodeName | MaybeTsDsl; +export type TypePropKind = 'prop'; const Mixed = DocMixin(OptionalMixin(ReadonlyMixin(TsDsl))); @@ -31,6 +32,16 @@ export class TypePropTsDsl extends Mixed { fn(this); } + /** Element kind. */ + get kind(): TypePropKind { + return 'prop'; + } + + /** Property name. */ + get propName(): string { + return this.name.toString(); + } + override analyze(ctx: AnalysisContext): void { super.analyze(ctx); ctx.analyze(this._type); -- 2.51.2 From 717a1746072d147c97c3dca878eb1ea204248633 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:50:47 +0000 Subject: [PATCH 10/14] ci: release --- .changeset/big-streets-behave.md | 5 ----- .changeset/bright-falcons-chew.md | 5 ----- .changeset/clever-toys-dress.md | 5 ----- .changeset/fluffy-pens-admire.md | 5 ----- .changeset/ready-dots-beg.md | 5 ----- .changeset/stupid-news-wash.md | 5 ----- packages/codegen-core/CHANGELOG.md | 10 ++++++++++ packages/codegen-core/package.json | 2 +- packages/openapi-python/CHANGELOG.md | 10 ++++++++++ packages/openapi-python/package.json | 4 ++-- packages/openapi-ts/CHANGELOG.md | 17 +++++++++++++++++ packages/openapi-ts/package.json | 4 ++-- packages/types/CHANGELOG.md | 6 ++++++ packages/types/package.json | 2 +- 14 files changed, 49 insertions(+), 36 deletions(-) delete mode 100644 .changeset/big-streets-behave.md delete mode 100644 .changeset/bright-falcons-chew.md delete mode 100644 .changeset/clever-toys-dress.md delete mode 100644 .changeset/fluffy-pens-admire.md delete mode 100644 .changeset/ready-dots-beg.md delete mode 100644 .changeset/stupid-news-wash.md create mode 100644 packages/openapi-python/CHANGELOG.md diff --git a/.changeset/big-streets-behave.md b/.changeset/big-streets-behave.md deleted file mode 100644 index 7a6a7affc..000000000 --- a/.changeset/big-streets-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hey-api/openapi-ts': patch ---- - -**ts-dsl**: allow removing object properties by passing `null` diff --git a/.changeset/bright-falcons-chew.md b/.changeset/bright-falcons-chew.md deleted file mode 100644 index 1c3e9a490..000000000 --- a/.changeset/bright-falcons-chew.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hey-api/types': patch ---- - -feat: add `ToArray`, `ToReadonlyArray`, and `AnyObject` types diff --git a/.changeset/clever-toys-dress.md b/.changeset/clever-toys-dress.md deleted file mode 100644 index ad10ced8c..000000000 --- a/.changeset/clever-toys-dress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hey-api/openapi-ts': patch ---- - -**cli**: clean up interface diff --git a/.changeset/fluffy-pens-admire.md b/.changeset/fluffy-pens-admire.md deleted file mode 100644 index 8f94228b9..000000000 --- a/.changeset/fluffy-pens-admire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hey-api/codegen-core': patch ---- - -**config**: export `loadConfigFile` function (moved from `@hey-api/openapi-ts`) diff --git a/.changeset/ready-dots-beg.md b/.changeset/ready-dots-beg.md deleted file mode 100644 index 06dea506b..000000000 --- a/.changeset/ready-dots-beg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hey-api/openapi-ts': patch ---- - -**ts-dsl**: override object properties when called multiple times with the same name diff --git a/.changeset/stupid-news-wash.md b/.changeset/stupid-news-wash.md deleted file mode 100644 index 0e89c1920..000000000 --- a/.changeset/stupid-news-wash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@hey-api/openapi-ts': patch ---- - -**config**: move `loadConfigFile` function to `@hey-api/codegen-core` diff --git a/packages/codegen-core/CHANGELOG.md b/packages/codegen-core/CHANGELOG.md index dd0bac3fe..0b657d5d0 100644 --- a/packages/codegen-core/CHANGELOG.md +++ b/packages/codegen-core/CHANGELOG.md @@ -1,5 +1,15 @@ # @hey-api/codegen-core +## 0.5.5 + +### Patch Changes + +- **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) + +### Updated Dependencies: + +- @hey-api/types@0.1.2 + ## 0.5.4 ### Patch Changes diff --git a/packages/codegen-core/package.json b/packages/codegen-core/package.json index 253a45b9f..bf6cd4dcc 100644 --- a/packages/codegen-core/package.json +++ b/packages/codegen-core/package.json @@ -1,6 +1,6 @@ { "name": "@hey-api/codegen-core", - "version": "0.5.4", + "version": "0.5.5", "description": "๐Ÿงฑ TypeScript framework for generating files.", "homepage": "https://heyapi.dev/", "repository": { diff --git a/packages/openapi-python/CHANGELOG.md b/packages/openapi-python/CHANGELOG.md new file mode 100644 index 000000000..61da4c735 --- /dev/null +++ b/packages/openapi-python/CHANGELOG.md @@ -0,0 +1,10 @@ +# @hey-api/openapi-python + +## 0.0.2 + +### Patch Changes + +### Updated Dependencies: + +- @hey-api/types@0.1.2 +- @hey-api/codegen-core@0.5.5 diff --git a/packages/openapi-python/package.json b/packages/openapi-python/package.json index 6dd1bf09d..05d57ae0c 100644 --- a/packages/openapi-python/package.json +++ b/packages/openapi-python/package.json @@ -1,6 +1,6 @@ { "name": "@hey-api/openapi-python", - "version": "0.0.1", + "version": "0.0.2", "private": true, "description": "๐Ÿ OpenAPI to Python codegen.", "homepage": "https://heyapi.dev/", @@ -73,7 +73,7 @@ "node": ">=20.19.0" }, "dependencies": { - "@hey-api/codegen-core": "workspace:^0.5.2", + "@hey-api/codegen-core": "workspace:^0.5.5", "@hey-api/json-schema-ref-parser": "1.2.2", "@hey-api/types": "workspace:*", "ansi-colors": "4.1.3", diff --git a/packages/openapi-ts/CHANGELOG.md b/packages/openapi-ts/CHANGELOG.md index a0e2fa0d9..5e244635b 100644 --- a/packages/openapi-ts/CHANGELOG.md +++ b/packages/openapi-ts/CHANGELOG.md @@ -1,5 +1,22 @@ # @hey-api/openapi-ts +## 0.90.9 + +### Patch Changes + +- **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) + +- **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) + +- **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) + +- **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) + +### Updated Dependencies: + +- @hey-api/types@0.1.2 +- @hey-api/codegen-core@0.5.5 + ## 0.90.8 ### Patch Changes diff --git a/packages/openapi-ts/package.json b/packages/openapi-ts/package.json index fbf5a8227..19f1f6e34 100644 --- a/packages/openapi-ts/package.json +++ b/packages/openapi-ts/package.json @@ -1,6 +1,6 @@ { "name": "@hey-api/openapi-ts", - "version": "0.90.8", + "version": "0.90.9", "description": "๐ŸŒ€ OpenAPI to TypeScript codegen. Production-ready SDKs, Zod schemas, TanStack Query hooks, and 20+ plugins. Used by Vercel, OpenCode, and PayPal.", "homepage": "https://heyapi.dev/", "repository": { @@ -89,7 +89,7 @@ "node": ">=20.19.0" }, "dependencies": { - "@hey-api/codegen-core": "workspace:^0.5.4", + "@hey-api/codegen-core": "workspace:^0.5.5", "@hey-api/json-schema-ref-parser": "1.2.2", "@hey-api/types": "workspace:*", "ansi-colors": "4.1.3", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index a00d8f78b..2ee449a48 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,11 @@ # @hey-api/types +## 0.1.2 + +### Patch Changes + +- 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) + ## 0.1.1 ### Patch Changes diff --git a/packages/types/package.json b/packages/types/package.json index ed2100ff6..49ebbb304 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@hey-api/types", - "version": "0.1.1", + "version": "0.1.2", "description": "Shared utility types.", "type": "module", "types": "./src/index.ts", -- 2.51.2 From 6507f47c6a4e93f7356891caab9982ee26fbcc5c Mon Sep 17 00:00:00 2001 From: mrlubos <12529395+mrlubos@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:52:10 +0000 Subject: [PATCH 11/14] chore: update lock file --- pnpm-lock.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 083800e11..4dfb593b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1338,7 +1338,7 @@ importers: packages/openapi-python: dependencies: '@hey-api/codegen-core': - specifier: workspace:^0.5.2 + specifier: workspace:^0.5.5 version: link:../codegen-core '@hey-api/json-schema-ref-parser': specifier: 1.2.2 @@ -1390,7 +1390,7 @@ importers: packages/openapi-ts: dependencies: '@hey-api/codegen-core': - specifier: workspace:^0.5.4 + specifier: workspace:^0.5.5 version: link:../codegen-core '@hey-api/json-schema-ref-parser': specifier: 1.2.2 @@ -14682,7 +14682,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14702,7 +14702,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14770,7 +14770,7 @@ snapshots: '@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)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) browserslist: 4.25.4 copy-webpack-plugin: 12.0.2(webpack@5.98.0) css-loader: 7.1.2(webpack@5.98.0) @@ -14790,7 +14790,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -14878,7 +14878,7 @@ snapshots: picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)) + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 @@ -22368,7 +22368,7 @@ snapshots: schema-utils: 4.3.2 webpack: 5.98.0(esbuild@0.25.0) - babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0(esbuild@0.25.0)): + babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0): dependencies: '@babel/core': 7.26.9 find-cache-dir: 4.0.0 @@ -27841,7 +27841,7 @@ snapshots: ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.9.3) optional: true - postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.0)): + postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0): dependencies: cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.7 -- 2.51.2 From 224613cc0eae6de87dc338a1077fbdb6664cc973 Mon Sep 17 00:00:00 2001 From: Juan Ibarra Date: Fri, 23 Jan 2026 14:35:42 -0400 Subject: [PATCH 12/14] feat(tanstack-query): add symbol metadata to mutationOptions --- .../plugins/@tanstack/query-core/v5/mutationOptions.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts index 9861f479d..94eff58ff 100644 --- a/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts +++ b/packages/openapi-ts/src/plugins/@tanstack/query-core/v5/mutationOptions.ts @@ -58,6 +58,15 @@ export const createMutationOptions = ({ const mutationOptionsFn = 'mutationOptions'; const symbolMutationOptions = plugin.symbol( applyNaming(operation.id, plugin.config.mutationOptions), + { + meta: { + category: 'hook', + resource: 'operation', + resourceId: operation.id, + role: 'mutationOptions', + tool: plugin.name, + }, + }, ); const statement = $.const(symbolMutationOptions) .export() -- 2.51.2 From 5977f56362351a9e4f1e9715c141a8e37130a4bb Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 24 Jan 2026 08:04:41 +0800 Subject: [PATCH 13/14] Add symbol metadata to mutationOptions for plugins Index mutation options symbol for various TanStack plugins. --- .changeset/rare-coins-heal.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/rare-coins-heal.md diff --git a/.changeset/rare-coins-heal.md b/.changeset/rare-coins-heal.md new file mode 100644 index 000000000..1cf1db447 --- /dev/null +++ b/.changeset/rare-coins-heal.md @@ -0,0 +1,9 @@ +--- +"@hey-api/openapi-ts": patch +--- + +**plugin(@tanstack/angular-query-experimental)**: index mutation options symbol +**plugin(@tanstack/react-query)**: index mutation options symbol +**plugin(@tanstack/solid-query)**: index mutation options symbol +**plugin(@tanstack/svelte-query)**: index mutation options symbol +**plugin(@tanstack/vue-query)**: index mutation options symbol -- 2.51.2 From c5f379ea5534272d63a423c671427ff1ef13197a Mon Sep 17 00:00:00 2001 From: Lubos Date: Sat, 24 Jan 2026 08:22:12 +0800 Subject: [PATCH 14/14] docs: split into multiple changesets --- .changeset/cold-ideas-eat-2.md | 5 +++++ .changeset/cold-ideas-eat-3.md | 5 +++++ .changeset/cold-ideas-eat-4.md | 5 +++++ .changeset/cold-ideas-eat.md | 5 +++++ 4 files changed, 20 insertions(+) create mode 100644 .changeset/cold-ideas-eat-2.md create mode 100644 .changeset/cold-ideas-eat-3.md create mode 100644 .changeset/cold-ideas-eat-4.md create mode 100644 .changeset/cold-ideas-eat.md diff --git a/.changeset/cold-ideas-eat-2.md b/.changeset/cold-ideas-eat-2.md new file mode 100644 index 000000000..6fd00b7c2 --- /dev/null +++ b/.changeset/cold-ideas-eat-2.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +**plugin(@tanstack/solid-query)**: index mutation options symbol diff --git a/.changeset/cold-ideas-eat-3.md b/.changeset/cold-ideas-eat-3.md new file mode 100644 index 000000000..19414524b --- /dev/null +++ b/.changeset/cold-ideas-eat-3.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +**plugin(@tanstack/svelte-query)**: index mutation options symbol diff --git a/.changeset/cold-ideas-eat-4.md b/.changeset/cold-ideas-eat-4.md new file mode 100644 index 000000000..b3a152212 --- /dev/null +++ b/.changeset/cold-ideas-eat-4.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +**plugin(@tanstack/vue-query)**: index mutation options symbol diff --git a/.changeset/cold-ideas-eat.md b/.changeset/cold-ideas-eat.md new file mode 100644 index 000000000..82308643a --- /dev/null +++ b/.changeset/cold-ideas-eat.md @@ -0,0 +1,5 @@ +--- +"@hey-api/openapi-ts": patch +--- + +**plugin(@tanstack/react-query)**: index mutation options symbol -- 2.51.2