diff --git a/.github/workflows/code-review.yml b/.github/workflows/code-review.yml index ee63d62..d158d9e 100644 --- a/.github/workflows/code-review.yml +++ b/.github/workflows/code-review.yml @@ -26,4 +26,8 @@ jobs: - uses: actions/checkout@v3 - uses: ./.github/actions/node-env - run: npm ci - - run: npm run test + - run: npm run test:coverage + - uses: codacy/codacy-coverage-reporter-action@v1 + with: + api-token: ${{ secrets.CODACY_API_TOKEN }} + coverage-reports: coverage/lcov.info diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 7743100..2c2d5cc 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v3 - uses: ./.github/actions/node-env - run: npm ci - - run: npm run test-coverage + - run: npm run test:coverage - uses: codacy/codacy-coverage-reporter-action@v1 with: api-token: ${{ secrets.CODACY_API_TOKEN }} diff --git a/README.md b/README.md index 7aad7fc..0a75a57 100644 --- a/README.md +++ b/README.md @@ -132,24 +132,22 @@ generate({ # 配置 -| 参数名 | 类型 | 可选性 | 描述 | 默认值 | -| -------------------- | --------------- | ------- | --------------------------------------------------------------------------------- | ----------------------------------------------- | -| `cwd` | `string` | `false` | 当前工作路径 | `process.cwd()` | -| `dest` | `string` | `false` | 目标目录 | `src/apis` | -| `axiosImport` | `string` | `false` | axios 导入内容 | 默认从官方 Axios 导入,可以使用自己实现的客户端 | -| `unwrapResponseData` | `boolean` | `false` | 是否取消对 axios response 的包裹(即直接返回 ResponseData,而不是 AxiosResponse) | `false` | -| `list` | `OpenApiSpec[]` | `false` | OpenAPI 规范声明列表 | `[]` | - -`OpenApiSpec` 签名: - -| 名称 | 类型 | 可选项 | 描述 | 默认值 | -| ------------- | -------- | ------- | -------------------------------------- | --------------- | -| `name` | `string` | `true` | 文件名,可以包含路径,相当于 dest 配置 | `process.cwd()` | -| `axiosImport` | `string` | `false` | axios 导入内容,优先级更高 | 无 | -| `url` | `string` | `false` | 远程 openApi 描述地址 | `undefined` | -| `spec` | `Spec` | `false` | 本地 OpenApi 描述文件 | `undefined` | - -备注:`url` 属性和 `spec` 属性,至少有一个必须。 +| 参数名 | 类型 | 可选性 | 描述 | 默认值 | +| -------------------- | ----------------- | ------- | --------------------------------------------------------------------------------- | ----------------------------------------------- | +| `cwd` | `string` | `false` | 当前工作路径 | `process.cwd()` | +| `dest` | `string` | `false` | 目标目录 | `src/apis` | +| `axiosImport` | `string` | `false` | axios 导入内容 | 默认从官方 Axios 导入,可以使用自己实现的客户端 | +| `unwrapResponseData` | `boolean` | `false` | 是否取消对 axios response 的包裹(即直接返回 ResponseData,而不是 AxiosResponse) | `false` | +| `apis` | `OpenapiConfig[]` | `false` | OpenAPI 列表

| `[]` | + +`OpenapiConfig` 签名: + +| 名称 | 类型 | 可选项 | 描述 | 默认值 | +| -------------------- | --------- | ------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------- | +| `name` | `string` | 必须 | openapi 的名称,将会生成 ${name}.ts 文件 | `undefined` | +| `axiosImport` | `string` | 可选 | axios 导入内容,优先级更高 | 无 | +| `unwrapResponseData` | `boolean` | 可选 | 是否取消对 axios response 的包裹,优先级更高(即直接返回 ResponseData,而不是

AxiosResponse)
| `false` | +| `schema` | `string | OpenApiSpec` | 必须 | openapi 的 schema,可以是一个链接地址,也可以是本地路径,也可以是一个对象 | `undefined` | # 鸣谢 diff --git a/package-lock.json b/package-lock.json index 1711dac..31bea2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "openapi-axios", - "version": "0.6.27", + "version": "0.7.0", "license": "MIT", "dependencies": { "chalk": "^4.1.2", @@ -1428,19 +1428,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.55.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-5.55.0.tgz", - "integrity": "sha512-OK+cIO1ZGhJYNCL//a3ROpsd83psf4dUJ4j7pdNVzd5DmIk+ffkuUIX2vcZQbEW/IR41DYsfJTB19tpCboxQuw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@typescript-eslint/type-utils": { "version": "5.56.0", "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", @@ -1524,53 +1511,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/types": { - "version": "5.55.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.55.0.tgz", - "integrity": "sha512-M4iRh4AG1ChrOL6Y+mETEKGeDnT7Sparn6fhZ5LtVJF1909D5O4uqK+C5NPbLmpfZ0XIIxCdwzKiijpZUOvOug==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.55.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.55.0.tgz", - "integrity": "sha512-I7X4A9ovA8gdpWMpr7b1BN9eEbvlEtWhQvpxp/yogt48fy9Lj3iE3ild/1H3jKBBIYj5YYJmS2+9ystVhC7eaQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.55.0", - "@typescript-eslint/visitor-keys": "5.55.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "5.56.0", "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.56.0.tgz", @@ -1688,19 +1628,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.55.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.55.0.tgz", - "integrity": "sha512-q2dlHHwWgirKh1D3acnuApXG+VNXpEY5/AwRxDVuEQpxWaB0jCDe0jFMVMALJ3ebSfuOVE8/rMS+9ZOYGg1GWw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.55.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/@vitest/coverage-c8": { "version": "0.29.7", "resolved": "https://registry.npmmirror.com/@vitest/coverage-c8/-/coverage-c8-0.29.7.tgz", diff --git a/package.json b/package.json index 75f5ae8..acd4ba6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "prepare": "husky install", "lint": "eslint src/**/*.ts && tsc --project tsconfig.json --noEmit", "test": "vitest run", - "test-coverage": "vitest run --coverage", + "test:coverage": "vitest run --coverage", "build:types": "rm -rf dist-dts && tsc --project tsconfig.types.json", "build:files": "rm -rf dist-cjs dist-mjs && rollup --config", "build": "npm run build:types && npm run build:files" diff --git a/src/commands.ts b/src/commands.ts index 7bb5ab2..71c4009 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -51,7 +51,7 @@ export async function start(startConfig?: StartConfig) { try { await generate(strictConfig, (generated, info) => { - const { oasItem } = generated; + const { openapi } = generated; const { index, length, done, start, end } = info; const width = Math.min(String(length).length, 2); const stepText = String(index + 1).padStart(width, '0'); @@ -61,14 +61,14 @@ export async function start(startConfig?: StartConfig) { console.log( chalk.cyanBright(`[${stepText}/${length}]`), 'generated ', - chalk.yellowBright(oasItem.name), + chalk.yellowBright(openapi.name), chalk.gray(`${past}ms`) ); } else { console.log( chalk.cyanBright(`[${stepText}/${length}]`), 'generating', - chalk.yellowBright(oasItem.name), + chalk.yellowBright(openapi.name), chalk.gray('...') ); } diff --git a/src/configure.ts b/src/configure.ts index eae09dd..c39ab5a 100644 --- a/src/configure.ts +++ b/src/configure.ts @@ -6,7 +6,7 @@ export const defaults: StrictConfig = { dest: 'src/apis', axiosImport: axiosImportDefault, unwrapResponseData: false, - list: [], + apis: [], onGenerated: () => 0, }; diff --git a/src/generator.ts b/src/generator.ts index 253446a..672f1fd 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -1,34 +1,52 @@ import fs from 'fs/promises'; +import { isBoolean, isString } from 'lodash-es'; import path from 'path'; -import { generateApi } from 'swagger-typescript-api'; +import { generateApi, GenerateApiParams } from 'swagger-typescript-api'; import { axiosImportDefault, helpersImport, templatesDir } from './const'; -import { - Generated, - GeneratedCallback, - OpenApiSpec, - OpenApiSpecAsLocal, - OpenApiSpecAsRemote, - StrictConfig, -} from './types'; +import { Generated, GeneratedCallback, OpenapiConfig, StrictConfig } from './types'; -export async function generateItem(oasItem: OpenApiSpec, config: StrictConfig): Promise { - const { name, axiosImport: axiosImportScope } = oasItem; - const { cwd, dest, axiosImport: axiosImportGlobal, unwrapResponseData } = config; - const axiosImport = axiosImportScope || axiosImportGlobal || axiosImportDefault; - const { files } = await generateApi({ +export function generateParams(openapiConfig: OpenapiConfig, config: StrictConfig): GenerateApiParams { + const { name, schema, unwrapResponseData: unwrapResponseDataScope } = openapiConfig; + const { unwrapResponseData: unwrapResponseDataGlobal } = config; + const unwrapResponseData = isBoolean(unwrapResponseDataScope) ? unwrapResponseDataScope : unwrapResponseDataGlobal; + const common: Omit = { name, - url: (oasItem as OpenApiSpecAsRemote).url, - spec: (oasItem as OpenApiSpecAsLocal).spec, output: false, httpClientType: 'axios', templates: templatesDir, silent: true, unwrapResponseData, - }); + }; + + if (isString(schema)) { + if (/^https:\/\//i.test(schema)) { + return { + ...common, + url: schema, + }; + } else { + return { + ...common, + input: schema, + }; + } + } else { + return { + ...common, + spec: schema, + }; + } +} +export async function generateItem(openapiConfig: OpenapiConfig, config: StrictConfig): Promise { + const { axiosImport: axiosImportScope, schema } = openapiConfig; + const { cwd, dest, axiosImport: axiosImportGlobal, unwrapResponseData } = config; + const axiosImport = axiosImportScope || axiosImportGlobal || axiosImportDefault; + const params = generateParams(openapiConfig, config); + const { files } = await generateApi(params); const generated: Generated = { files: [], - oasItem, + openapi: openapiConfig, config, }; @@ -47,17 +65,17 @@ export async function generateItem(oasItem: OpenApiSpec, config: StrictConfig): } export async function generate(config: StrictConfig, callback?: GeneratedCallback): Promise { - const { list, onGenerated } = config; + const { apis, onGenerated } = config; let index = 0; - const length = list.length; + const length = apis.length; const generatedList: Generated[] = []; - for (const oasItem of list) { + for (const oasItem of apis) { const start = Date.now(); callback?.( { files: [], - oasItem, + openapi: oasItem, config, }, { index, length, done: false, start, end: start } diff --git a/src/types.ts b/src/types.ts index b3dbef4..bc27c56 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,26 +1,31 @@ -interface OpenApiSpecBase { +export type OpenapiSpec = import('swagger-schema-official').Spec; + +export type OpenapiConfig = { + /** + * openapi 的名称,将会生成 ${name}.ts 文件 + */ name: string; /** - * 全局导入 axios 客户端,优先级低于每个 oas 配置,默认从 axios 官方导入,导入名称必须为 axios,例如 + * 导入 axios 客户端,默认从 axios 官方导入,导入名称必须为 axios,优先级高于全局配置,例如 * ``` * import { axios } from '@/utils/axios'; * ``` */ axiosImport?: string; -} -export interface OpenApiSpecAsRemote extends OpenApiSpecBase { - url: string; -} - -export type Spec = import('swagger-schema-official').Spec; - -export interface OpenApiSpecAsLocal extends OpenApiSpecBase { - spec: Spec; -} + /** + * 是否取消包装 data,默认 undefined,优先级高于全局配置 + * false = 返回值就是响应(response),response.data 才是实际值 + * true = 返回值就是数据 + */ + unwrapResponseData?: boolean; -export type OpenApiSpec = OpenApiSpecAsRemote | OpenApiSpecAsLocal; + /** + * openapi 的 schema,可以是一个链接地址,也可以是本地路径,也可以是一个对象 + */ + schema: string | OpenapiSpec; +}; export interface UserConfig { /** @@ -34,7 +39,7 @@ export interface UserConfig { dest?: string; /** - * 导入 axios 客户端,优先级高于全局配置,默认从 axios 官方导入,导入名称必须为 axios,例如 + * 默认从 axios 官方导入,导入名称必须为 axios,例如 * ``` * import { axios } from '@/utils/axios'; * ``` @@ -56,9 +61,9 @@ export interface UserConfig { onGenerated?: (generated: Generated) => any; /** - * OpenApiSpec 列表 + * OpenapiConfig 列表 */ - list: OpenApiSpec[]; + apis: OpenapiConfig[]; } export type StrictConfig = Required; @@ -73,7 +78,7 @@ export enum ContentKind { export interface Generated { files: string[]; - oasItem: OpenApiSpec; + openapi: OpenapiConfig; config: StrictConfig; } diff --git a/test/configure.test.ts b/test/configure.test.ts index d68313f..abd5edb 100644 --- a/test/configure.test.ts +++ b/test/configure.test.ts @@ -6,14 +6,14 @@ test('defaults', () => { const axios = new Axios();`); expect(defaults.dest).toBe('src/apis'); expect(defaults.cwd).toBe(process.cwd()); - expect(defaults.list).toHaveLength(0); + expect(defaults.apis).toHaveLength(0); expect(defaults.unwrapResponseData).toBe(false); expect(defaults.onGenerated).toBeTypeOf('function'); }); test('defineConfig', () => { const userConfig: UserConfig = { - list: [], + apis: [], }; const strictConfig = defineConfig(userConfig); diff --git a/test/generator.test.ts b/test/generator.test.ts index 34397dc..94ea7ca 100644 --- a/test/generator.test.ts +++ b/test/generator.test.ts @@ -2,13 +2,20 @@ import { random } from 'lodash-es'; import path from 'path'; import { cleanDir, isFile } from 'src/utils'; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'; -import { generate, Generated, GenerateInfo, generateItem, Spec } from '../src'; +import { generate, Generated, GenerateInfo, generateItem, OpenapiSpec, StrictConfig } from '../src'; import petstore3 from './petstore3.json'; describe('generate-item', () => { const cwd = process.cwd(); const dest = 'dist-test'; - const config = { axiosImport: '', cwd, dest: dest, list: [], unwrapResponseData: false, onGenerated: () => 0 }; + const config: StrictConfig = { + axiosImport: '', + cwd, + dest: dest, + apis: [], + unwrapResponseData: false, + onGenerated: () => 0, + }; afterEach(async () => { await cleanDir(path.join(cwd, dest)); @@ -23,7 +30,7 @@ describe('generate-item', () => { const generated = await generateItem( { name, - url: 'https://petstore3.swagger.io/api/v3/openapi.json', + schema: 'https://petstore3.swagger.io/api/v3/openapi.json', }, config ); @@ -42,7 +49,7 @@ describe('generate-item', () => { const generated = await generateItem( { name, - spec: petstore3 as unknown as Spec, + schema: petstore3 as unknown as OpenapiSpec, }, config ); @@ -56,18 +63,18 @@ describe('generate-item', () => { test('generate', async () => { const cwd = process.cwd(); const dest = 'dist-test'; - const config = { + const config: StrictConfig = { axiosImport: '', cwd, dest: dest, - list: [ + apis: [ { name: random(1, 1000).toString(), - spec: petstore3 as unknown as Spec, + schema: petstore3 as unknown as OpenapiSpec, }, { name: random(1, 1000).toString(), - spec: petstore3 as unknown as Spec, + schema: petstore3 as unknown as OpenapiSpec, }, ], unwrapResponseData: false,