diff --git a/.changeset/dry-tables-sniff.md b/.changeset/dry-tables-sniff.md new file mode 100644 index 0000000..c2b5c58 --- /dev/null +++ b/.changeset/dry-tables-sniff.md @@ -0,0 +1,5 @@ +--- +'openapi-ts-request': major +--- + +refactor: refactor client request params to adapt support react-query diff --git a/.changeset/lovely-laws-press.md b/.changeset/lovely-laws-press.md new file mode 100644 index 0000000..6af7fdf --- /dev/null +++ b/.changeset/lovely-laws-press.md @@ -0,0 +1,5 @@ +--- +'openapi-ts-request': patch +--- + +perf: perf translate multiple Chinese tag into English tag diff --git a/.changeset/popular-rabbits-repair.md b/.changeset/popular-rabbits-repair.md new file mode 100644 index 0000000..6c510d8 --- /dev/null +++ b/.changeset/popular-rabbits-repair.md @@ -0,0 +1,5 @@ +--- +'openapi-ts-request': minor +--- + +feat: support generate react-query diff --git a/.changeset/ten-eyes-marry.md b/.changeset/ten-eyes-marry.md new file mode 100644 index 0000000..d313caf --- /dev/null +++ b/.changeset/ten-eyes-marry.md @@ -0,0 +1,5 @@ +--- +'openapi-ts-request': minor +--- + +feat: support generate JavaScript diff --git a/.eslintrc.cjs b/.eslintrc.cjs index ab4c60a..f640e87 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,15 +5,7 @@ module.exports = { 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', ], - ignorePatterns: [ - 'dist', - 'node_modules', - 'test', - '.eslintrc.cjs', - 'commitlint.config.cjs', - 'lint-staged.config.cjs', - 'prettier.config.cjs', - ], + ignorePatterns: ['dist', 'node_modules', 'test', '*.cjs'], plugins: [], parserOptions: { ecmaVersion: 'latest', diff --git a/README-en_US.md b/README-en_US.md index c4f50d9..758b03f 100644 --- a/README-en_US.md +++ b/README-en_US.md @@ -6,10 +6,11 @@ English | open the log (default: false) - --priorityRule priority rule, include/exclude/both (default: "include") - --includeTags <(string|RegExp)[]> generate code from include tags - --includePaths <(string|RegExp)[]> generate code from include paths - --excludeTags <(string|RegExp)[]> generate code from exclude tags - --excludePaths <(string|RegExp)[]> generate code from exclude paths - --requestOptionsType custom request method options parameter type (default: "{ [key: - string]: unknown }") - --requestImportStatement custom request import statement, for example: "const request = - require('@/request')" - --apiPrefix custom the prefix of the api path, for example: "api"(variable), - "'api'"(string) - --isDisplayTypeLabel generate label matching type field (default: false) - --isGenJsonSchemas generate JSON Schemas (default: false) - --mockFolder mock file path, for example: './mocks' - --authorization docs authorization - --nullable null instead of optional (default: false) - --isTranslateToEnglishTag translate chinese tag name to english tag name (default: false) - --isOnlyGenTypeScriptType only generate typescript type (default: false) - --isCamelCase camelCase naming of controller files and request client (default: true) - -h, --help display help for command + -V, --version output the version number + -i, --input OpenAPI specification, can be a path, url (required) + -o, --output output directory (required) + --requestLibPath custom request lib path, for example: "@/request", "node-fetch" (default: "axios") + --enableLogging open the log (default: false) + --priorityRule priority rule, include/exclude/both (default: "include") + --includeTags <(string|RegExp)[]> generate code from include tags + --includePaths <(string|RegExp)[]> generate code from include paths + --excludeTags <(string|RegExp)[]> generate code from exclude tags + --excludePaths <(string|RegExp)[]> generate code from exclude paths + --requestOptionsType custom request method options parameter type (default: "{ [key: string]: unknown }") + --requestImportStatement custom request import statement, for example: "const request = require('@/request')" + --apiPrefix custom the prefix of the api path, for example: "api"(variable), "'api'"(string) + --isGenReactQuery generate react-query (default: false) + --isGenJavaScript generate JavaScript (default: false) + --isDisplayTypeLabel generate label matching type field (default: false) + --isGenJsonSchemas generate JSON Schemas (default: false) + --mockFolder mock file path, for example: './mocks' + --authorization docs authorization + --nullable null instead of optional (default: false) + --isTranslateToEnglishTag translate chinese tag name to english tag name (default: false) + --isOnlyGenTypeScriptType only generate typescript type (default: false) + --isCamelCase camelCase naming of controller files and request client (default: true) + -h, --help display help for command ``` run: @@ -226,6 +229,8 @@ openapi -i ./spec.json -o ./apis | requestOptionsType | no | string | '{ [key: string]: unknown }' | custom request method options parameter type | | requestImportStatement | no | string | - | custom request import statement, for example: "const request = require('@/request')" | | apiPrefix | no | string | - | custom the prefix of the api path, for example: 'api'(variable), "'api'"(string) | +| isGenReactQuery | no | boolean | false | generate react-query | +| isGenJavaScript | no | boolean | false | generate JavaScript | | isDisplayTypeLabel | no | boolean | false | generate label matching type field | | isGenJsonSchemas | no | boolean | false | generate JSON Schemas | | mockFolder | no | string | - | mock file path, for example: './mocks' | diff --git a/README.md b/README.md index 48d3b0e..b487098 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,11 @@ 根据 [Swagger2/OpenAPI3/Apifox](https://swagger.io/blog/news/whats-new-in-openapi-3-0/) 文档生成: -- TS 类型 -- 客户端请求函数 +- TypeScript/JavaScript +- 客户端请求函数(支持任意客户端) - 模拟请求响应服务 - 枚举和枚举翻译 +- react-query - 类型字段翻译 - JSON Schemas @@ -18,9 +19,9 @@ ## 功能 - 支持 Swagger2.0/OpenAPI/Apifox 3.0,3.1 定义 -- 生成 TS 类型, 请求客户端, 请求模拟服务, 枚举, 类型字段翻译, JSON Schemas +- 生成 TypeScript/JavaScript, 请求客户端(支持任意客户端), 请求模拟服务, 枚举和枚举翻译, react-query, 类型字段翻译, JSON Schemas - 支持通过 npx、CLI、Nodejs 的方式使用 -- 支持自定义请求工具函数, 支持 Fetch、Axios、[UniApp-Request](https://github.com/openapi-ui/openapi-ts-request/issues/46)、Node.js、XHR 客户端 +- 支持自定义请求工具函数, 支持 Fetch、Axios、[UniApp-Request](https://github.com/openapi-ui/openapi-ts-request/issues/46)、Taro-Request、Node.js、XHR 客户端 - 支持通过 tags 过滤生成结果 - 支持 JSON/YAML 定义文件 - 支持将中文 tag 名称翻译为英文 tag 名称 @@ -94,10 +95,13 @@ import request from 'axios'; import * as API from './types'; /** Update an existing pet PUT /pet */ -export async function updatePet( - body: API.Pet, - options?: { [key: string]: unknown } -) { +export async function updatePet({ + body, + options, +}: { + body: API.Pet; + options?: { [key: string]: unknown }; +}) { return request(`/pet`, { method: 'PUT', headers: { @@ -177,31 +181,30 @@ $ openapi --help Usage: openapi [options] Options: - -V, --version output the version number - -i, --input OpenAPI specification, can be a path, url (required) - -o, --output output directory (required) - --requestLibPath custom request lib path, for example: "@/request", "node-fetch" (default: "axios") - --enableLogging open the log (default: false) - --priorityRule priority rule, include/exclude/both (default: "include") - --includeTags <(string|RegExp)[]> generate code from include tags - --includePaths <(string|RegExp)[]> generate code from include paths - --excludeTags <(string|RegExp)[]> generate code from exclude tags - --excludePaths <(string|RegExp)[]> generate code from exclude paths - --requestOptionsType custom request method options parameter type (default: "{ [key: - string]: unknown }") - --requestImportStatement custom request import statement, for example: "const request = - require('@/request')" - --apiPrefix custom the prefix of the api path, for example: "api"(variable), - "'api'"(string) - --isDisplayTypeLabel generate label matching type field (default: false) - --isGenJsonSchemas generate JSON Schemas (default: false) - --mockFolder mock file path, for example: './mocks' - --authorization docs authorization - --nullable null instead of optional (default: false) - --isTranslateToEnglishTag translate chinese tag name to english tag name (default: false) - --isOnlyGenTypeScriptType only generate typescript type (default: false) - --isCamelCase camelCase naming of controller files and request client (default: true) - -h, --help display help for command + -V, --version output the version number + -i, --input OpenAPI specification, can be a path, url (required) + -o, --output output directory (required) + --requestLibPath custom request lib path, for example: "@/request", "node-fetch" (default: "axios") + --enableLogging open the log (default: false) + --priorityRule priority rule, include/exclude/both (default: "include") + --includeTags <(string|RegExp)[]> generate code from include tags + --includePaths <(string|RegExp)[]> generate code from include paths + --excludeTags <(string|RegExp)[]> generate code from exclude tags + --excludePaths <(string|RegExp)[]> generate code from exclude paths + --requestOptionsType custom request method options parameter type (default: "{ [key: string]: unknown }") + --requestImportStatement custom request import statement, for example: "const request = require('@/request')" + --apiPrefix custom the prefix of the api path, for example: "api"(variable), "'api'"(string) + --isGenReactQuery generate react-query (default: false) + --isGenJavaScript generate JavaScript (default: false) + --isDisplayTypeLabel generate label matching type field (default: false) + --isGenJsonSchemas generate JSON Schemas (default: false) + --mockFolder mock file path, for example: './mocks' + --authorization docs authorization + --nullable null instead of optional (default: false) + --isTranslateToEnglishTag translate chinese tag name to english tag name (default: false) + --isOnlyGenTypeScriptType only generate typescript type (default: false) + --isCamelCase camelCase naming of controller files and request client (default: true) + -h, --help display help for command ``` 运行: @@ -226,6 +229,8 @@ openapi --i ./spec.json --o ./apis | requestOptionsType | 否 | string | '{ [key: string]: unknown }' | 自定义请求方法 options 参数类型 | | requestImportStatement | 否 | string | - | 自定义请求方法表达式,例如:"const request = require('@/request')" | | apiPrefix | 否 | string | - | api path的前缀,例如:'api'(动态变量), "'api'"(字符串) | +| isGenReactQuery | 否 | boolean | false | 是否生成 react-query | +| isGenJavaScript | 否 | boolean | false | 是否生成 JavaScript | | isDisplayTypeLabel | 否 | boolean | false | 是否生成 type 对应的label | | isGenJsonSchemas | 否 | boolean | false | 是否生成 JSON Schemas | | mockFolder | 否 | string | - | mock文件路径,例如:'./mocks' | diff --git a/package.json b/package.json index 34eaaad..06862d7 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "openapi-ts-request", "version": "0.13.4", - "description": "Swagger2/OpenAPI3 to TypeScript, request client, request mock service, enum, type field label, JSON Schemas", - "packageManager": "pnpm@9.11.0", + "description": "Swagger2/OpenAPI3/Apifox to TypeScript/JavaScript, request client(support any client), request mock service, enum and enum translation, react-query, type field label, JSON Schemas", + "packageManager": "pnpm@9.15.0", "engines": { "node": ">=18.0.0", "pnpm": ">=9" @@ -62,6 +62,7 @@ "@changesets/cli": "^2.27.6", "@commitlint/cli": "^19.2.1", "@commitlint/config-conventional": "^19.2.2", + "@tanstack/react-query": "^5.62.10", "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.17.5", "@types/memoizee": "^0.4.11", @@ -74,20 +75,24 @@ "@typescript-eslint/parser": "^7.9.0", "eslint": "^8.57.1", "husky": "^9.0.11", - "lint-staged": "^15.2.5", + "lint-staged": "^15.3.0", "openapi-types": "^12.1.3", "ts-node": "^10.9.2", "typescript": "5.7.2" }, "keywords": [ "openapi", - "openapi3", "swagger", - "openapi to ts", - "openapi to request client", - "openapi to axios client", - "openapi to fetch client", - "openapi to uni request client", - "openapi to JSON Schemas" + "openapi-ts", + "swagger-ts", + "openapi-typescript", + "swagger-typescript", + "openapi-react-query", + "openapi-fetch", + "openapi-axios", + "openapi-uniapp", + "openapi-taro", + "openapi-node-fetch", + "openapi-JSON-Schemas" ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c401067..5a2df40 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,13 +77,16 @@ importers: version: 0.5.0 '@changesets/cli': specifier: ^2.27.6 - version: 2.27.10 + version: 2.27.11 '@commitlint/cli': specifier: ^19.2.1 version: 19.6.1(@types/node@20.16.9)(typescript@5.7.2) '@commitlint/config-conventional': specifier: ^19.2.2 version: 19.6.0 + '@tanstack/react-query': + specifier: ^5.62.10 + version: 5.62.11(react@19.0.0) '@types/js-yaml': specifier: ^4.0.9 version: 4.0.9 @@ -121,8 +124,8 @@ importers: specifier: ^9.0.11 version: 9.1.7 lint-staged: - specifier: ^15.2.5 - version: 15.2.11 + specifier: ^15.3.0 + version: 15.3.0 openapi-types: specifier: ^12.1.3 version: 12.1.3 @@ -200,8 +203,8 @@ packages: resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} - '@changesets/apply-release-plan@7.0.6': - resolution: {integrity: sha512-TKhVLtiwtQOgMAC0fCJfmv93faiViKSDqr8oMEqrnNs99gtSC1sZh/aEMS9a+dseU1ESZRCK+ofLgGY7o0fw/Q==} + '@changesets/apply-release-plan@7.0.7': + resolution: {integrity: sha512-qnPOcmmmnD0MfMg9DjU1/onORFyRpDXkMMl2IJg9mECY6RnxL3wN0TCCc92b2sXt1jt8DgjAUUsZYGUGTdYIXA==} '@changesets/assemble-release-plan@6.0.5': resolution: {integrity: sha512-IgvBWLNKZd6k4t72MBTBK3nkygi0j3t3zdC1zrfusYo0KpdsvnDjrMM9vPnTCLCMlfNs55jRL4gIMybxa64FCQ==} @@ -212,12 +215,12 @@ packages: '@changesets/changelog-github@0.5.0': resolution: {integrity: sha512-zoeq2LJJVcPJcIotHRJEEA2qCqX0AQIeFE+L21L8sRLPVqDhSXY8ZWAt2sohtBpFZkBwu+LUwMSKRr2lMy3LJA==} - '@changesets/cli@2.27.10': - resolution: {integrity: sha512-PfeXjvs9OfQJV8QSFFHjwHX3QnUL9elPEQ47SgkiwzLgtKGyuikWjrdM+lO9MXzOE22FO9jEGkcs4b+B6D6X0Q==} + '@changesets/cli@2.27.11': + resolution: {integrity: sha512-1QislpE+nvJgSZZo9+Lj3Lno5pKBgN46dAV8IVxKJy9wX8AOrs9nn5pYVZuDpoxWJJCALmbfOsHkyxujgetQSg==} hasBin: true - '@changesets/config@3.0.4': - resolution: {integrity: sha512-+DiIwtEBpvvv1z30f8bbOsUQGuccnZl9KRKMM/LxUHuDu5oEjmN+bJQ1RIBKNJjfYMQn8RZzoPiX0UgPaLQyXw==} + '@changesets/config@3.0.5': + resolution: {integrity: sha512-QyXLSSd10GquX7hY0Mt4yQFMEeqnO5z/XLpbIr4PAkNNoQNKwDyiSrx4yd749WddusH1v3OSiA0NRAYmH/APpQ==} '@changesets/errors@0.2.0': resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} @@ -228,8 +231,8 @@ packages: '@changesets/get-github-info@0.6.0': resolution: {integrity: sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==} - '@changesets/get-release-plan@4.0.5': - resolution: {integrity: sha512-E6wW7JoSMcctdVakut0UB76FrrN3KIeJSXvB+DHMFo99CnC3ZVnNYDCVNClMlqAhYGmLmAj77QfApaI3ca4Fkw==} + '@changesets/get-release-plan@4.0.6': + resolution: {integrity: sha512-FHRwBkY7Eili04Y5YMOZb0ezQzKikTka4wL753vfUA5COSebt7KThqiuCN9BewE4/qFGgF/5t3AuzXx1/UAY4w==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} @@ -424,6 +427,14 @@ packages: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} + '@tanstack/query-core@5.62.9': + resolution: {integrity: sha512-lwePd8hNYhyQ4nM/iRQ+Wz2cDtspGeZZHFZmCzHJ7mfKXt+9S301fULiY2IR2byJYY6Z03T427E5PoVfMexHjw==} + + '@tanstack/react-query@5.62.11': + resolution: {integrity: sha512-Xb1nw0cYMdtFmwkvH9+y5yYFhXvLRCnXoqlzSw7UkqtCVFq3cG8q+rHZ2Yz1XrC+/ysUaTqbLKJqk95mCgC1oQ==} + peerDependencies: + react: ^18 || ^19 + '@trivago/prettier-plugin-sort-imports@4.3.0': resolution: {integrity: sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==} peerDependencies: @@ -675,6 +686,10 @@ packages: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -1295,8 +1310,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@15.2.11: - resolution: {integrity: sha512-Ev6ivCTYRTGs9ychvpVw35m/bcNDuBN+mnTeObCL5h+boS5WzBEC6LHI4I9F/++sZm1m+J2LEiy0gxL/R9TBqQ==} + lint-staged@15.3.0: + resolution: {integrity: sha512-vHFahytLoF2enJklgtOtCtIjZrKD/LoxlaUusd5nh7dWv/dkKQJY74ndFSzxCdv7g0ueGg1ORgTSt4Y9LPZn9A==} engines: {node: '>=18.12.0'} hasBin: true @@ -1649,6 +1664,10 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -2066,9 +2085,9 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - '@changesets/apply-release-plan@7.0.6': + '@changesets/apply-release-plan@7.0.7': dependencies: - '@changesets/config': 3.0.4 + '@changesets/config': 3.0.5 '@changesets/get-version-range-type': 0.4.0 '@changesets/git': 3.0.2 '@changesets/should-skip-package': 0.1.1 @@ -2103,15 +2122,15 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/cli@2.27.10': + '@changesets/cli@2.27.11': dependencies: - '@changesets/apply-release-plan': 7.0.6 + '@changesets/apply-release-plan': 7.0.7 '@changesets/assemble-release-plan': 6.0.5 '@changesets/changelog-git': 0.2.0 - '@changesets/config': 3.0.4 + '@changesets/config': 3.0.5 '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.2 - '@changesets/get-release-plan': 4.0.5 + '@changesets/get-release-plan': 4.0.6 '@changesets/git': 3.0.2 '@changesets/logger': 0.1.1 '@changesets/pre': 2.0.1 @@ -2134,7 +2153,7 @@ snapshots: spawndamnit: 3.0.1 term-size: 2.2.1 - '@changesets/config@3.0.4': + '@changesets/config@3.0.5': dependencies: '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.2 @@ -2162,10 +2181,10 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/get-release-plan@4.0.5': + '@changesets/get-release-plan@4.0.6': dependencies: '@changesets/assemble-release-plan': 6.0.5 - '@changesets/config': 3.0.4 + '@changesets/config': 3.0.5 '@changesets/pre': 2.0.1 '@changesets/read': 0.6.2 '@changesets/types': 6.0.0 @@ -2347,7 +2366,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7 + debug: 4.4.0 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -2365,7 +2384,7 @@ snapshots: '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7 + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -2444,6 +2463,13 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@tanstack/query-core@5.62.9': {} + + '@tanstack/react-query@5.62.11(react@19.0.0)': + dependencies: + '@tanstack/query-core': 5.62.9 + react: 19.0.0 + '@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.4.2)': dependencies: '@babel/generator': 7.17.7 @@ -2548,7 +2574,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.7.2) '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.7.2) - debug: 4.3.7 + debug: 4.4.0 eslint: 8.57.1 ts-api-utils: 1.3.0(typescript@5.7.2) optionalDependencies: @@ -2562,7 +2588,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7 + debug: 4.4.0 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 @@ -2718,6 +2744,8 @@ snapshots: chalk@5.3.0: {} + chalk@5.4.1: {} + chardet@0.7.0: {} ci-info@3.9.0: {} @@ -3326,9 +3354,9 @@ snapshots: lines-and-columns@1.2.4: {} - lint-staged@15.2.11: + lint-staged@15.3.0: dependencies: - chalk: 5.3.0 + chalk: 5.4.1 commander: 12.1.0 debug: 4.4.0 execa: 8.0.1 @@ -3641,6 +3669,8 @@ snapshots: quick-lru@5.1.1: {} + react@19.0.0: {} + read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 diff --git a/src/bin/openapi.ts b/src/bin/openapi.ts index 34097f1..4c7abfb 100644 --- a/src/bin/openapi.ts +++ b/src/bin/openapi.ts @@ -51,6 +51,8 @@ const params = program '--apiPrefix ', `custom the prefix of the api path, for example: "api"(variable), "'api'"(string)` ) + .option('--isGenReactQuery ', 'generate react-query', false) + .option('--isGenJavaScript ', 'generate JavaScript', false) .option( '--isDisplayTypeLabel ', 'generate label matching type field', @@ -105,6 +107,8 @@ async function run() { excludePaths: params.excludePaths as string[], requestOptionsType: params.requestOptionsType as string, apiPrefix: params.apiPrefix as string, + isGenReactQuery: JSON.parse(params.isGenReactQuery as string) === true, + isGenJavaScript: JSON.parse(params.isGenJavaScript as string) === true, isDisplayTypeLabel: JSON.parse(params.isDisplayTypeLabel as string) === true, isGenJsonSchemas: JSON.parse(params.isGenJsonSchemas as string) === true, diff --git a/src/generator/config.ts b/src/generator/config.ts index a12a0c5..6d04142 100644 --- a/src/generator/config.ts +++ b/src/generator/config.ts @@ -12,6 +12,8 @@ export const displayTypeLabelFileName = 'displayTypeLabel'; export const schemaFileName = 'schema'; +export const reactQueryFileName = 'reactquery'; + export enum TypescriptFileType { interface = 'interface', serviceController = 'serviceController', @@ -19,6 +21,7 @@ export enum TypescriptFileType { displayEnumLabel = 'displayEnumLabel', displayTypeLabel = 'displayTypeLabel', schema = 'schema', + reactQuery = 'reactQuery', } export const DEFAULT_SCHEMA: SchemaObject = { @@ -69,3 +72,8 @@ export const numberEnum = [ // 匹配换行符的正则 export const lineBreakReg = /[\r\n]+/g; + +export enum LangType { + ts = 'ts', + js = 'js', +} diff --git a/src/generator/serviceGenarator.ts b/src/generator/serviceGenarator.ts index 2bf89f4..5c8d802 100644 --- a/src/generator/serviceGenarator.ts +++ b/src/generator/serviceGenarator.ts @@ -42,6 +42,7 @@ import { import { DEFAULT_PATH_PARAM, DEFAULT_SCHEMA, + LangType, TypescriptFileType, displayEnumLabelFileName, displayTypeLabelFileName, @@ -51,6 +52,7 @@ import { numberEnum, parametersIn, parametersInsEnum, + reactQueryFileName, schemaFileName, serviceEntryFileName, } from './config'; @@ -68,6 +70,7 @@ import { TagAPIDataType, } from './type'; import { + capitalizeFirstLetter, genDefaultFunctionName, getBasePrefix, getDefaultFileTag, @@ -94,13 +97,11 @@ import { export default class ServiceGenerator { protected apiData: TagAPIDataType = {}; protected classNameList: ControllerType[] = []; - protected finalPath: string; protected config: GenerateServiceProps; protected openAPIData: OpenAPIObject; protected schemaList: ISchemaItem[] = []; constructor(config: GenerateServiceProps, openAPIData: OpenAPIObject) { - this.finalPath = ''; this.config = { templatesFolder: join(__dirname, '../../', 'templates'), ...config, @@ -259,23 +260,28 @@ export default class ServiceGenerator { log(`🚥 api 生成失败: ${error}`); } + const isOnlyGenTypeScriptType = this.config.isOnlyGenTypeScriptType; + const isGenJavaScript = this.config.isGenJavaScript; + // 处理重复的 typeName const interfaceTPConfigs = this.getInterfaceTPConfigs(); handleDuplicateTypeNames(interfaceTPConfigs); // 生成 ts 类型声明 - this.genFileFromTemplate( - `${interfaceFileName}.ts`, - TypescriptFileType.interface, - { - nullable: this.config.nullable, - list: interfaceTPConfigs, - } - ); + if (!isGenJavaScript) { + this.genFileFromTemplate( + `${interfaceFileName}.ts`, + TypescriptFileType.interface, + { + nullable: this.config.nullable, + list: interfaceTPConfigs, + } + ); + } // 生成枚举翻译 const enums = filter(interfaceTPConfigs, (item) => item.isEnum); - if (!this.config.isOnlyGenTypeScriptType && !isEmpty(enums)) { + if (!isGenJavaScript && !isOnlyGenTypeScriptType && !isEmpty(enums)) { this.genFileFromTemplate( `${displayEnumLabelFileName}.ts`, TypescriptFileType.displayEnumLabel, @@ -293,7 +299,8 @@ export default class ServiceGenerator { ); // 生成 type 翻译 if ( - !this.config.isOnlyGenTypeScriptType && + !isGenJavaScript && + !isOnlyGenTypeScriptType && this.config.isDisplayTypeLabel && !isEmpty(displayTypeLabels) ) { @@ -308,13 +315,15 @@ export default class ServiceGenerator { ); } - if (!this.config.isOnlyGenTypeScriptType) { + if (!isOnlyGenTypeScriptType) { const prettierError = []; // 生成 service controller 文件 this.getServiceTPConfigs().forEach((tp) => { const hasError = this.genFileFromTemplate( - getFinalFileName(`${tp.className}.ts`), + isGenJavaScript + ? getFinalFileName(`${tp.className}.js`) + : getFinalFileName(`${tp.className}.ts`), TypescriptFileType.serviceController, { namespace: this.config.namespace, @@ -326,6 +335,22 @@ export default class ServiceGenerator { ); prettierError.push(hasError); + + if (this.config.isGenReactQuery) { + this.genFileFromTemplate( + isGenJavaScript + ? getFinalFileName(`${tp.className}.${reactQueryFileName}.js`) + : getFinalFileName(`${tp.className}.${reactQueryFileName}.ts`), + TypescriptFileType.reactQuery, + { + namespace: this.config.namespace, + requestOptionsType: this.config.requestOptionsType, + requestImportStatement: this.config.requestImportStatement, + interfaceFileName: interfaceFileName, + ...tp, + } + ); + } }); if (prettierError.includes(true)) { @@ -336,7 +361,7 @@ export default class ServiceGenerator { } if ( - !this.config.isOnlyGenTypeScriptType && + !isOnlyGenTypeScriptType && this.config.isGenJsonSchemas && !isEmpty(this.schemaList) ) { @@ -344,7 +369,7 @@ export default class ServiceGenerator { handleDuplicateTypeNames(this.schemaList); // 生成 schema 文件 this.genFileFromTemplate( - `${schemaFileName}.ts`, + isGenJavaScript ? `${schemaFileName}.js` : `${schemaFileName}.ts`, TypescriptFileType.schema, { list: this.schemaList, @@ -354,22 +379,26 @@ export default class ServiceGenerator { // 生成 service index 文件 this.genFileFromTemplate( - `${serviceEntryFileName}.ts`, + isGenJavaScript + ? `${serviceEntryFileName}.js` + : `${serviceEntryFileName}.ts`, TypescriptFileType.serviceIndex, { list: this.classNameList, namespace: this.config.namespace, interfaceFileName: interfaceFileName, + genType: isGenJavaScript ? LangType.js : LangType.ts, isGenJsonSchemas: - !this.config.isOnlyGenTypeScriptType && + !isOnlyGenTypeScriptType && this.config.isGenJsonSchemas && !isEmpty(this.schemaList), schemaFileName: schemaFileName, - isDisplayEnumLabel: - !this.config.isOnlyGenTypeScriptType && !isEmpty(enums), + isDisplayEnumLabel: !isOnlyGenTypeScriptType && !isEmpty(enums), displayEnumLabelFileName: displayEnumLabelFileName, + isGenReactQuery: this.config.isGenReactQuery, + reactQueryFileName: reactQueryFileName, isDisplayTypeLabel: - !this.config.isOnlyGenTypeScriptType && + !isOnlyGenTypeScriptType && this.config.isDisplayTypeLabel && !isEmpty(displayTypeLabels), displayTypeLabelFileName: displayTypeLabelFileName, @@ -405,7 +434,12 @@ export default class ServiceGenerator { } const flag = this.validateRegexp( - map(tags, (tag) => tag.toLowerCase()), + filter( + map(tags, (tag) => + tag?.toLowerCase ? tag.toLowerCase() : undefined + ), + (tag) => !!tag + ), includeTags ); @@ -488,6 +522,7 @@ export default class ServiceGenerator { const enumObj = this.resolveEnumObject( item as unknown as SchemaObject ); + lastTypes.push({ typeName: `${upperFirst(item.name)}Enum`, type: enumObj.type, @@ -708,7 +743,7 @@ export default class ServiceGenerator { } return { - genType: 'ts', + genType: this.config.isGenJavaScript ? LangType.js : LangType.ts, className, instanceName: `${fileName[0]?.toLowerCase()}${fileName.slice(1)}`, list: genParams, @@ -725,10 +760,12 @@ export default class ServiceGenerator { try { const template = this.getTemplate(type); // 设置输出不转义 - nunjucks.configure({ + const env = nunjucks.configure({ autoescape: false, }); + env.addFilter('capitalizeFirst', capitalizeFirstLetter); + return writeFile( this.config.serversPath, fileName, @@ -1241,6 +1278,7 @@ export default class ServiceGenerator { return dataArray.some((item) => { const result = regexp.some((reg) => this.matches(item, reg)); this.log(`"Item:", ${item}, "Matches:", ${result}`); + return result; }); } @@ -1256,8 +1294,10 @@ export default class ServiceGenerator { return minimatch(item, reg); } else if (reg instanceof RegExp) { reg.lastIndex = 0; // 重置正则表达式的 lastIndex 属性 + return reg.test(item); } + return false; // 对于其他类型,返回 false } } diff --git a/src/generator/util.ts b/src/generator/util.ts index 11af8cd..609a059 100644 --- a/src/generator/util.ts +++ b/src/generator/util.ts @@ -224,7 +224,8 @@ export function getDefaultType( const schemaKey = getLastRefName(item.$ref); if ((schemas?.[schemaKey] as SchemaObject)?.enum) { - return `I${getDefaultType(item, namespace)}`; + // return `I${getDefaultType(item, namespace)}`; + return getDefaultType(item, namespace); } } @@ -489,3 +490,7 @@ export function isAllNumeric(arr) { export function isAllNumber(arr) { return every(arr, (item) => isNumber(item)); } + +export function capitalizeFirstLetter(str: string) { + return str.replace(/^[a-z]/, (match) => match.toUpperCase()); +} diff --git a/src/index.ts b/src/index.ts index 65939fc..78c1670 100644 --- a/src/index.ts +++ b/src/index.ts @@ -80,6 +80,14 @@ export type GenerateServiceProps = { namespace: string; functionName: string; }) => string); + /** + * 是否生成 react-query 配置 + */ + isGenReactQuery?: boolean; + /** + * 是否生成 JavaScript, 不生成 TypeScript + */ + isGenJavaScript?: boolean; /** * 是否生成 type 对应的label, 默认: false */ @@ -247,11 +255,13 @@ export async function generateService({ : null, requestOptionsType: '{[key: string]: unknown}', namespace: 'API', - nullable: false, - isCamelCase: true, + isGenReactQuery: false, + isGenJavaScript: false, isDisplayTypeLabel: false, isGenJsonSchemas: false, + nullable: false, isOnlyGenTypeScriptType: false, + isCamelCase: true, ...rest, }, openAPI diff --git a/src/util.ts b/src/util.ts index 53fb67d..7f080a0 100644 --- a/src/util.ts +++ b/src/util.ts @@ -20,7 +20,7 @@ export const getImportStatement = (requestLibPath: string) => { return `import request from '${requestLibPath}';`; } - return `import request from 'axios';`; + return `import { request } from 'axios';`; }; async function getSchema(schemaPath: string, authorization?: string) { @@ -154,6 +154,7 @@ export async function translateChineseModuleNodeToEnglish( forEach(keys(openAPI.paths), (path) => { const pathItemObject = openAPI.paths[path]; + forEach(keys(pathItemObject), (method: string) => { if (pathItemObject[method]) { const operation = pathItemObject[method] as OperationObject; @@ -166,30 +167,32 @@ export async function translateChineseModuleNodeToEnglish( void Promise.all( map(uniq(tags), (tagName) => { return new Promise((resolve) => { - void translate(tagName, null, 'en') - .then((translateRes) => { - const text = camelCase(translateRes?.translation); - if (text) { - translateMap[tagName] = text; - resolve(text); - } - }) - .catch(() => { - resolve(tagName); - }); + if (tagName && /[\u3220-\uFA29]/.test(tagName)) { + void translate(tagName, null, 'en') + .then((translateRes) => { + const text = camelCase(translateRes?.translation); + + if (text) { + translateMap[tagName] = text; + resolve(text); + } + }) + .catch(() => { + resolve(tagName); + }); + } else { + resolve(tagName); + } }); }) ) .then(() => { - map(operations, (operation) => { - const tagName = operation.tags?.[0]; - - if (tagName && /[\u3220-\uFA29]/.test(tagName)) { - operation.tags = [ - translateMap[tagName], - ...operation.tags.slice(1), - ]; - } + forEach(operations, (operation) => { + forEach(operation.tags, (tagName, index) => { + if (translateMap[tagName]) { + operation.tags[index] = translateMap[tagName]; + } + }); }); resolve(true); }) diff --git a/templates/reactQuery.njk b/templates/reactQuery.njk new file mode 100644 index 0000000..8a5eb11 --- /dev/null +++ b/templates/reactQuery.njk @@ -0,0 +1,112 @@ +{% if genType === "ts" %} + /* eslint-disable */ + // @ts-ignore +{% endif -%} +import { queryOptions, useMutation } from '@tanstack/react-query'; +{%- if genType === "ts" %} + import type { DefaultError } from '@tanstack/react-query'; +{% endif %} + +import * as apis from './{{ className }}'; +{%- if genType === "ts" %} + import * as {{ namespace }} from './{{ interfaceFileName }}'; +{% endif %} + +{% for api in list %} + /** {{ api.desc if api.desc else '此处后端没有提供注释' }} {{ api.method | upper }} {{ api.pathInComment | safe }}{{ ' ' if api.apifoxRunLink else '' }}{{ api.apifoxRunLink }} */ + {%- if api.method | lower === "get" %} + export function {{ api.functionName }}QueryOptions(options + {%- if genType === "ts" -%} + : { + {%- if api.params and api.hasParams %} + // 叠加生成的Param类型 (非body参数openapi默认没有生成对象) + params + : {{ namespace }}.{{ api.typeName }} + {# header 入参 -#} + {% if api.params.header -%} + & { // header + {% for param in api.params.header -%} + {% if param.description -%} + /** {{ param.description }} */ + {% endif -%} + '{{ param.name }}' + {{- "?" if not param.required }} + {{- (": " + param.type + ";") | safe }} + {% endfor -%} + } + {%- endif -%} + {%- if api.hasParams -%} + {{ ";" if api.body or api.file}} + {%- endif -%} + {%- endif -%} + + {%- if api.body -%} + body: + {% if api.body.propertiesList %}{ + {%- for prop in api.body.propertiesList %} + {% if prop.schema.description -%} + /** {{ prop.schema.description }} */ + {% endif -%} + '{{ prop.key }}'{{ "?" if not prop.schema.required }}: {{ prop.schema.type }}, + {%- endfor %} + } + {%- else -%} + {{ api.body.type }} + {%- endif -%} + {{ ";" if api.file }} + {%- endif %} + + {%- if api.file -%} + {%- for file in api.file -%} + {{file.title | safe}} + {{- "?" if not api.file.required -}} + : File {{ "[]" if file.multiple }} + {{";" if not loop.last }} + {%- endfor -%} + {%- endif -%} + {{ ";" if api.body or api.hasParams or api.file }} + options?: {{ requestOptionsType }} + } + {%- endif -%} + ) { + return queryOptions({ + queryFn: async ({ queryKey }) => { + return apis.{{ api.functionName }}(queryKey[1]{{ " as typeof options" if genType === "ts" }}); + }, + queryKey: ['{{ api.functionName }}', options], + }); + } + {%- else %} + export function use{{ api.functionName | capitalizeFirst }}Mutation(options + {%- if genType === "ts" -%} + ?: { + onSuccess?: (value?: {{ api.response.type }}) => void; + onError?: (error?: DefaultError) => void; + } + {%- endif %} + ) { + const { onSuccess, onError } = options || {}; + + const response = useMutation({ + mutationFn: apis.{{ api.functionName }}, + onSuccess(data{{ (": " + api.response.type) | safe if genType === "ts" }}) { + {% if genType === "ts" %} + onSuccess?.(data); + {% else %} + onSuccess && onSuccess(data); + {% endif %} + }, + onError(error) { + {% if genType === "ts" %} + onError?.(error); + {% else %} + onError && onError(error); + {% endif %} + } + }) + + return response; + } + {%- endif %} + +{% endfor %} \ No newline at end of file diff --git a/templates/serviceController.njk b/templates/serviceController.njk index 3a352ba..0a92329 100644 --- a/templates/serviceController.njk +++ b/templates/serviceController.njk @@ -1,168 +1,173 @@ -/* eslint-disable */ -// @ts-ignore -import * as {{ namespace }} from './{{ interfaceFileName }}'; +{% if genType === "ts" %} + /* eslint-disable */ + // @ts-ignore +{% endif -%} {{ requestImportStatement }} +{% if genType === "ts" %} + import * as {{ namespace }} from './{{ interfaceFileName }}'; +{% endif %} {% for api in list -%} -/** {{ api.desc if api.desc else '此处后端没有提供注释' }} {{ api.method | upper }} {{ api.pathInComment | safe }}{{ ' ' if api.apifoxRunLink else '' }}{{ api.apifoxRunLink | safe }} */ -export async function {{ api.functionName }}( -{%- if api.params and api.hasParams %} - // 叠加生成的Param类型 (非body参数openapi默认没有生成对象) - params - {%- if genType === "ts" -%} - : {{namespace}}.{{api.typeName}} - {# header 入参 -#} - {% if api.params.header -%} - & { // header - {% for param in api.params.header -%} - {% if param.description -%} - /** {{ param.description }} */ - {% endif -%} - '{{ param.name }}' - {{- "?" if not param.required }} - {{- (": " + param.type + ";") | safe }} - {% endfor -%} - } + /** {{ api.desc if api.desc else '此处后端没有提供注释' }} {{ api.method | upper }} {{ api.pathInComment | safe }}{{ ' ' if api.apifoxRunLink else '' }}{{ api.apifoxRunLink }} */ + export async function {{ api.functionName }}({ + {%- if api.params and api.hasParams %} + params + {%- if api.hasParams -%} + {{ "," if api.body or api.file }} + {%- endif -%} {%- endif -%} - {%- endif -%} - {%- if api.hasParams -%} - {{ "," if api.body or api.file}} - {%- endif -%} -{%- endif -%} - - - -{%- if api.body -%} - body - {%- if genType === "ts" -%} - - : {% if api.body.propertiesList %}{ - {%- for prop in api.body.propertiesList %} - {% if prop.schema.description -%} - /** {{ prop.schema.description }} */ - {% endif -%} - '{{ prop.key }}'{{ "?" if not prop.schema.required }}: {{ prop.schema.type }}, - {%- endfor %} + {%- if api.body -%} + body + {{ "," if api.file }} + {%- endif %} + {%- if api.file -%} + {%- for file in api.file -%} + {{ file.title | safe }} + {{ "," if not loop.last }} + {%- endfor -%} + {%- endif -%} + {{ "," if api.body or api.hasParams or api.file }} + options } - {%- else -%} - {{ api.body.type }} - {%- endif -%} - - {%- endif -%} - {{ "," if api.file }} -{%- endif %} - - - -{%- if api.file -%} -{%- for file in api.file -%} -{{file.title | safe}} -{%- if genType === "ts" -%} -{{- "?" if not api.file.required -}} -: File {{ "[]" if file.multiple }} -{%- endif -%} -{{"," if not loop.last }} -{%- endfor -%} -{%- endif -%} - - - -{{ "," if api.body or api.hasParams or api.file }} - options {{ ("?: " + requestOptionsType) if genType === "ts" }} -) { - {% if api.params and api.params.path -%} - const { {% for param in api.params.path %}'{{ param.name }}': {{ param.alias }}, {% endfor %} - {% if api.params.path -%} - ...queryParams - {% endif -%} - } = params; - {% endif -%} - - - - {% if api.hasFormData -%} - const formData = new FormData(); + {%- if genType === "ts" -%} + : { + {%- if api.params and api.hasParams %} + // 叠加生成的Param类型 (非body参数openapi默认没有生成对象) + params: {{ namespace }}.{{ api.typeName }} + {# header 入参 -#} + {% if api.params.header -%} + & { // header + {% for param in api.params.header -%} + {% if param.description -%} + /** {{ param.description }} */ + {% endif -%} + '{{ param.name }}' + {{- "?" if not param.required }} + {{- (": " + param.type + ";") | safe }} + {% endfor -%} + } + {%- endif -%} + {%- if api.hasParams -%} + {{ ";" if api.body or api.file }} + {%- endif -%} + {%- endif -%} + + {%- if api.body -%} + body: + {% if api.body.propertiesList %} + { + {%- for prop in api.body.propertiesList %} + {% if prop.schema.description -%} + /** {{ prop.schema.description }} */ + {% endif -%} + '{{ prop.key }}'{{ "?" if not prop.schema.required }}: {{ prop.schema.type }}, + {%- endfor %} + } + {%- else -%} + {{ api.body.type }} + {%- endif -%} + {{ ";" if api.file }} + {%- endif %} - {% if api.file -%} - {% for file in api.file %} - if({{file.title | safe}}){ - {% if file.multiple %} - {{file.title | safe}}.forEach(f => formData.append('{{file.title | safe}}', f || '')); - {% else %} - formData.append('{{file.title | safe}}', {{file.title | safe}}) - {% endif %} - } - {% endfor %} + {%- if api.file -%} + {%- for file in api.file -%} + {{ file.title | safe }} + {{- "?" if not api.file.required -}} + : File {{ "[]" if file.multiple }} + {{ ";" if not loop.last }} + {%- endfor -%} + {%- endif -%} + {{ ";" if api.body or api.hasParams or api.file }} + options?: {{ requestOptionsType }} + } {%- endif -%} - - {% if api.body %} - Object.keys(body).forEach(ele => { - {% if genType === "ts" %} - const item = (body as { [key: string]: any })[ele]; - {% else %} - const item = body[ele]; + ) + { + {% if api.params and api.params.path %} + const { {% for param in api.params.path %}'{{ param.name }}': {{ param.alias }}, {% endfor %} + {% if api.params.path -%} + ...queryParams + {% endif -%} + } = params; {% endif %} - if (item !== undefined && item !== null) { - {% if genType === "ts" %} - if (typeof item === 'object' && !(item instanceof File)) { - if (item instanceof Array) { - item.forEach((f) => formData.append(ele, f || '')); - } else { - formData.append(ele, JSON.stringify(item)); - } - } else { - formData.append(ele, item); - } - {% else %} - formData.append(ele, typeof item === 'object' ? JSON.stringify(item) : item); + {%- if api.hasFormData -%} + const formData = new FormData(); + + {% if api.file -%} + {% for file in api.file %} + if({{ file.title | safe }}) { + {% if file.multiple %} + {{ file.title | safe }}.forEach(f => formData.append('{{ file.title | safe }}', f || '')); + {% else %} + formData.append('{{ file.title | safe }}', {{ file.title | safe }}) + {% endif %} + } + {% endfor %} + {%- endif -%} + + {% if api.body %} + Object.keys(body).forEach(ele => { + {% if genType === "ts" %} + const item = (body as { [key: string]: any })[ele]; + {% else %} + const item = body[ele]; + {% endif %} + if (item !== undefined && item !== null) { + {% if genType === "ts" %} + if (typeof item === 'object' && !(item instanceof File)) { + if (item instanceof Array) { + item.forEach((f) => formData.append(ele, f || '')); + } else { + formData.append(ele, JSON.stringify(item)); + } + } else { + formData.append(ele, item); + } + {% else %} + formData.append(ele, typeof item === 'object' ? JSON.stringify(item) : item); + {% endif %} + } + }); {% endif %} - } - }); - {% endif %} - {% endif -%} - - - - {% if api.hasPathVariables or api.hasApiPrefix -%} - return request{{ ("<" + api.response.type + ">") | safe if genType === "ts" }}(`{{ api.path | safe }}`, { - {% else -%} - return request{{ ("<" + api.response.type + ">") | safe if genType === "ts" }}('{{ api.path }}', { - {% endif -%} - method: '{{ api.method | upper }}', + {% endif %} - {%- if api.hasHeader and api.body.mediaType %} - headers: { - {%- if api.body.mediaType %} - 'Content-Type': '{{ api.body.mediaType | safe }}', + {% if api.hasPathVariables or api.hasApiPrefix -%} + return request{{ ("<" + api.response.type + ">") | safe if genType === "ts" }}(`{{ api.path | safe }}`, { + {% else -%} + return request{{ ("<" + api.response.type + ">") | safe if genType === "ts" }}('{{ api.path }}', { + {% endif -%} + method: '{{ api.method | upper }}', + {%- if api.hasHeader and api.body.mediaType %} + headers: { + {%- if api.body.mediaType %} + 'Content-Type': '{{ api.body.mediaType | safe }}', + {%- endif %} + }, {%- endif %} - }, - {%- endif %} - - {%- if api.params and api.hasParams %} - params: { - {%- for query in api.params.query %} - {% if query.schema.default -%} - // {{query.name | safe}} has a default value: {{ query.schema.default | safe }} - '{{query.name | safe}}': '{{query.schema.default | safe}}', - {%- endif -%} - {%- endfor -%} - ...{{ 'queryParams' if api.params and api.params.path else 'params' }}, - {%- for query in api.params.query %} - {%- if query.isComplexType %} - '{{query.name | safe}}': undefined, - ...{{ 'queryParams' if api.params and api.params.path else 'params' }}['{{query.name | safe}}'], - {%- endif %} - {%- endfor -%} - }, - {%- endif %} - - {%- if api.hasFormData %} - data: formData, - {%- elseif api.body %} - data: body, - {%- endif %} - ...(options || {{api.options | dump}}), - }); -} + {%- if api.params and api.hasParams %} + params: { + {%- for query in api.params.query %} + {% if query.schema.default -%} + // {{ query.name | safe }} has a default value: {{ query.schema.default | safe }} + '{{ query.name | safe }}': '{{query.schema.default | safe}}', + {%- endif -%} + {%- endfor -%} + ...{{ 'queryParams' if api.params and api.params.path else 'params' }}, + {%- for query in api.params.query %} + {%- if query.isComplexType %} + '{{ query.name | safe }}': undefined, + ...{{ 'queryParams' if api.params and api.params.path else 'params' }}['{{ query.name | safe }}'], + {%- endif %} + {%- endfor -%} + }, + {%- endif %} + {%- if api.hasFormData %} + data: formData, + {%- elseif api.body %} + data: body, + {%- endif %} + ...(options || {{ api.options | dump }}), + }); + } {% endfor -%} diff --git a/templates/serviceIndex.njk b/templates/serviceIndex.njk index 1fa3f9c..544929b 100644 --- a/templates/serviceIndex.njk +++ b/templates/serviceIndex.njk @@ -1,15 +1,21 @@ -/* eslint-disable */ -// @ts-ignore -export * from './{{ interfaceFileName }}'; -{%- if isDisplayEnumLabel %} -export * from './{{ displayEnumLabelFileName }}'; -{%- endif %} -{%- if isDisplayTypeLabel %} -export * from './{{ displayTypeLabelFileName }}'; -{%- endif %} +{% if genType === "ts" %} + /* eslint-disable */ + // @ts-ignore + export * from './{{ interfaceFileName }}'; + {%- if isDisplayEnumLabel %} + export * from './{{ displayEnumLabelFileName }}'; + {% endif -%} + {%- if isDisplayTypeLabel %} + export * from './{{ displayTypeLabelFileName }}'; + {% endif -%} +{% endif %} {%- if isGenJsonSchemas %} -export * from './{{ schemaFileName }}'; + export * from './{{ schemaFileName }}'; {%- endif %} + {% for api in list -%} -export * from './{{ api.fileName }}'; -{% endfor -%} + export * from './{{ api.fileName }}'; + {%- if isGenReactQuery -%} + export * from './{{ api.fileName }}.{{ reactQueryFileName }}'; + {%- endif -%} +{%- endfor -%} diff --git a/test/example-files/openapi-chinese-tag.json b/test/example-files/openapi-chinese-tag.json index 80ed252..4c884e6 100644 --- a/test/example-files/openapi-chinese-tag.json +++ b/test/example-files/openapi-chinese-tag.json @@ -47,7 +47,7 @@ "paths": { "/pet": { "put": { - "tags": ["宠物"], + "tags": ["宠物", "你好", "世界", "happy"], "summary": "Update an existing pet", "description": "Update an existing pet by Id", "operationId": "updatePet", diff --git a/test/genOpenapi.js b/test/genOpenapi.js index 2cdb3e6..3281ad8 100644 --- a/test/genOpenapi.js +++ b/test/genOpenapi.js @@ -1,7 +1,14 @@ const { generateService } = require('../dist/index'); generateService({ - schemaPath: `${__dirname}/example-files/openapi-test-allof-api.json`, - serversPath: './apis/allof', - isDisplayTypeLabel: true, + schemaPath: `${__dirname}/example-files/openapi.json`, + serversPath: './apis/js-client', + isGenJavaScript: true, + isGenReactQuery: true, +}); + +generateService({ + schemaPath: `${__dirname}/example-files/openapi.json`, + serversPath: './apis/react-query', + isGenReactQuery: true, }); \ No newline at end of file diff --git a/test/test.js b/test/test.js index edad872..faf0b4d 100644 --- a/test/test.js +++ b/test/test.js @@ -102,6 +102,7 @@ const gen = async () => { await openAPI.generateService({ schemaPath: `${__dirname}/example-files/swagger-file-convert.json`, serversPath: './apis/file', + isGenReactQuery: true, }); // 生成枚举翻译, 生成 type 翻译 @@ -191,6 +192,21 @@ const gen = async () => { serversPath: './apis/apifox-enum-label', }); + // 测试生成 react-query + await openAPI.generateService({ + schemaPath: `${__dirname}/example-files/openapi-display-enum-label.json`, + serversPath: './apis/react-query', + isGenReactQuery: true, + }); + + // 测试生成 JavaScript + await openAPI.generateService({ + schemaPath: `${__dirname}/example-files/openapi-display-enum-label.json`, + serversPath: './apis/javascript', + isGenReactQuery: true, + isGenJavaScript: true, + }); + // check 文件生成 const fileControllerStr = fs.readFileSync( path.join(__dirname, 'apis/file/fileController.ts'),