├── .coveralls.yml
├── .prettierignore
├── example
├── src
│ └── page
│ │ └── index.js
├── plugin.js
├── plugin
│ └── test.js
├── package.json
├── .miladyrc.js
├── .umirc.js
└── data
│ └── swagger2.json
├── .yarnrc
├── .eslintignore
├── .prettierrc.js
├── .fatherrc.js
├── src
├── utils
│ ├── common.ts
│ ├── handelStr.test.ts
│ └── handelStr.ts
├── plugins
│ ├── serviceTs
│ │ ├── http.ts.tpl
│ │ ├── example.ts
│ │ ├── index.test.ts
│ │ ├── indexB.ts
│ │ └── index.ts
│ ├── serviceJs
│ │ └── index.ts
│ └── mock
│ │ └── index.ts
├── typings.d.ts
└── index.ts
├── .travis.yml
├── .gitignore
├── .editorconfig
├── .eslintrc.js
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── PULL_REQUEST_TEMPLATE.md
├── tsconfig.json
├── LICENSE
├── bin
└── milady.js
├── package.json
└── README.md
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /lib
3 | /out
4 |
--------------------------------------------------------------------------------
/example/src/page/index.js:
--------------------------------------------------------------------------------
1 | export default () =>
测试页面,运行用于查看mock数据
;
2 |
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | # Using the official mirror source
2 | registry "https://registry.yarnpkg.com"
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /scripts
2 | /config
3 | **/node_modules/**
4 | /lib
5 | /es
6 | /template
7 | /example
8 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | const fabric = require('@umijs/fabric');
2 |
3 | module.exports = {
4 | ...fabric.prettier,
5 | };
6 |
--------------------------------------------------------------------------------
/.fatherrc.js:
--------------------------------------------------------------------------------
1 | export default {
2 | target: 'node',
3 | nodeVersion: 8,
4 | cjs: { type: 'babel' },
5 | disableTypeCheck: true,
6 | };
7 |
--------------------------------------------------------------------------------
/src/utils/common.ts:
--------------------------------------------------------------------------------
1 | export function isUrl(params: string) {
2 | const routeReg = /^https?:\/\/[^/:]+(:\d*)?(\/#)?([^?]*)/;
3 | return routeReg.test(params);
4 | }
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '8'
4 | - '10'
5 | before_script: npm run eslint
6 | script: npm run build && npm run test
7 | after_script: yarn run coverage
8 |
--------------------------------------------------------------------------------
/example/plugin.js:
--------------------------------------------------------------------------------
1 | import milady from '../lib';
2 |
3 | export default function(api, opts = {}) {
4 | api.registerCommand('codegen', {}, args => {
5 | milady(opts);
6 | });
7 | }
8 |
--------------------------------------------------------------------------------
/example/plugin/test.js:
--------------------------------------------------------------------------------
1 | function handelData(params) {
2 | console.log(123);
3 | return [];
4 | }
5 |
6 | exports.default = {
7 | outPath: 'out', // 输出目录路径
8 | handelData,
9 | };
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /lib
4 | *.tgz
5 | *.log
6 | package-lock.json
7 | yarn.lock
8 | # 输出文件
9 | example/out
10 | example/mock
11 | example/*/services
12 | example/src/page/.umi
13 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "private": true,
4 | "scripts": {
5 | "start": "umi dev",
6 | "milady": "node --inspect-brk ../bin/milady.js",
7 | "codegen": "node --inspect-brk ./node_modules/.bin/umi codegen"
8 | },
9 | "devDependencies": {
10 | "umi": "^2.8.8"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/src/utils/handelStr.test.ts:
--------------------------------------------------------------------------------
1 | import { handelRef, strToKey } from './handelStr';
2 |
3 | test('handelRef', () => {
4 | expect(handelRef('#/definitions/aa')).toEqual('aa');
5 | });
6 | test('strToKey', () => {
7 | expect(strToKey('#/definitions/aa')).toEqual('definitionsaa');
8 | expect(strToKey('你会12aa')).toEqual('你会12aa');
9 | expect(strToKey('')).toEqual('');
10 | });
11 |
--------------------------------------------------------------------------------
/example/.miladyrc.js:
--------------------------------------------------------------------------------
1 | exports.default = {
2 | // dataSource: './data/swagger2.json', //推荐的,优先加载命令行的url,命令行没有再加载配置的url
3 | // plugins: [{ url: './plugin/test.js' }], //可选的,
4 | // defaultPlugins: {
5 | // mock: { enabled: false },
6 | // serviceJs: { enabled: false },
7 | // serviceTs: { enabled: false },
8 | // serviceT: { enabled: false },
9 | // },
10 | };
11 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const fabric = require('@umijs/fabric');
2 |
3 | module.exports = {
4 | ...fabric.default,
5 | rules: {
6 | ...fabric.default.rules,
7 | '@typescript-eslint/camelcase': 0,
8 | '@typescript-eslint/class-name-casing': 0,
9 | '@typescript-eslint/no-explicit-any':0,
10 | 'no-console':0
11 | },
12 | globals: {
13 | page: true,
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/src/plugins/serviceTs/http.ts.tpl:
--------------------------------------------------------------------------------
1 | <%= InterfaceDefinition %>
2 |
3 | /**
4 | * @tags <%= FunctionTags %>
5 | * @summary <%= FunctionSummary %>
6 | * @description <%= FunctionDescription %>
7 | * @param params <%= FunctionParams %>
8 | */
9 | export async function <%= FunctionName %>(params?: <%= FunctionParams %>): Promise<<%= FunctionPromise %>> {
10 | return request(<%= FunctionUrl %>, <%= FunctionParamsMethod %>);
11 | }
12 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # 贡献
2 |
3 | ## 安装
4 |
5 | 在 git 克隆 repo 后安装依赖,
6 |
7 | ```zsh
8 | yarn
9 | ```
10 |
11 | 打包
12 |
13 | ```zsh
14 | yarn run build
15 | ```
16 |
17 | 全局链接包(等同于全局安装包)
18 |
19 | ```zsh
20 | yarn link
21 | ```
22 |
23 | ## 命令行调试
24 |
25 | 切换目录到 example
26 |
27 | ```zsh
28 | cd exameple
29 | ```
30 |
31 | 使用命令调试 milady
32 |
33 | ```zsh
34 | yarn run milady
35 | ```
36 |
37 | ## 引入调试
38 |
39 | 安装包
40 |
41 | ```zsh
42 | yarn
43 | ```
44 |
45 | 使用命令调试 umi 插件(调试引用)
46 |
47 | ```zsh
48 | yarn run codegen
49 | ```
50 |
--------------------------------------------------------------------------------
/example/.umirc.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: [
3 | [
4 | './plugin.js',
5 | {
6 | swaggerUrl: 'https://petstore.swagger.io/v2/swagger.json', //推荐的,优先加载命令行的url,命令行没有再加载配置的url
7 | // plugins: [
8 | // {
9 | // outPath: 'mock', //输出目录路径
10 | // handelData: params => {
11 | // return [{ fileName: 'test.txt', fileStr: '1' }];
12 | // }, //传入swagger数据,返回集合fileName是生成的文件名,fileStr是生成的文件内容
13 | // },
14 | // ], //可选的
15 | },
16 | ],
17 | ],
18 | };
19 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.less';
3 | declare module '*.scss';
4 | declare module '*.sass';
5 | declare module '*.svg';
6 | declare module '*.png';
7 | declare module '*.jpg';
8 | declare module '*.jpeg';
9 | declare module '*.gif';
10 | declare module '*.bmp';
11 | declare module '*.tiff';
12 | declare module '*.json';
13 | declare module 'rc-animate';
14 | declare module 'omit.js';
15 | declare module 'react-copy-to-clipboard';
16 | declare module 'react-fittext';
17 | declare module '@antv/data-set';
18 | declare module 'nzh/cn';
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | ## Background(背景)
10 |
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 对问题的清晰而简明的描述。我总是很遗憾当…
12 |
13 | ## Proposal(建议)
14 |
15 | Describe the solution you'd like, better to provide some pseudo code. 描述您想要的解决方案,最好提供一些伪代码。
16 |
17 | ## Additional context(附加上下文)
18 |
19 | Add any other context or screenshots about the feature request here. 在此处添加有关功能请求的任何其他上下文或屏幕截图。
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "declaration": true,
6 | "moduleResolution": "node",
7 | "esModuleInterop": true,
8 | "jsx": "react",
9 | "experimentalDecorators": true,
10 | "noImplicitAny": true,
11 | "preserveConstEnums": true,
12 | "outDir": "lib/",
13 | "baseUrl": ".",
14 | "strict": true,
15 | "strictNullChecks": true,
16 | "paths": {
17 | "@/*": ["./src/*"]
18 | }
19 | },
20 | "include": ["src/**/*"],
21 | "exclude": ["node_modules", "**/*.spec.ts"]
22 | }
23 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## What kind of change does this PR introduce(这个 PR 引入了什么样的变化)?
2 |
3 | - [ ] Bugfix(修正错误)
4 | - [ ] Feature(新功能)
5 | - [ ] Refactor(重构)
6 | - [ ] Build-related changes(与构建相关的更改)
7 | - [ ] Other, please describe(其他,请描述):
8 |
9 | ## Does this PR introduce a breaking change(这次 PR 引入了一个重大变化吗)?
10 |
11 | - [ ] Yes(是)
12 | - [ ] No(否)
13 |
14 | If yes, please describe the impact and migration path for existing applications(如果是,请描述现有应用程序的影响和迁移路径):
15 |
16 | ## The PR fulfills these requirements(PR 符合以下要求)
17 |
18 | - [ ] All tests are passing(所有测试都通过)
19 |
20 | ## Other information(其他信息)
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | ## What happens(发生了什么)?
10 |
11 | A clear and concise description of what the bug is(对错误的清晰而简明的描述).
12 |
13 | ## Mini Showcase Repository(迷你展示库)
14 |
15 | > Provide a mini GitHub repository which can reproduce the issue(提供一个可以重现问题的小型 Github 存储库).
16 |
17 |
18 |
19 | ## How To Reproduce(如何重现)
20 |
21 | **Steps to reproduce the behavior(重现行为的步骤):** 1. 2.
22 |
23 | **Expected behavior(预期行为)** 1. 2.
24 |
25 | ## Context(上下文)
26 |
27 | - **milady Version**:
28 | - **Node Version**:
29 | - **Platform(操作系统平台)**:
30 |
--------------------------------------------------------------------------------
/src/plugins/serviceTs/example.ts:
--------------------------------------------------------------------------------
1 | /* 示例文件作为插件参考 */
2 | import request from 'umi-request';
3 |
4 | interface VisitData {
5 | /**
6 | * @description 横轴值
7 | */
8 | x: string;
9 | /**
10 | * @description 纵轴值
11 | */
12 | y: string;
13 | }
14 |
15 | interface ApiFormsQuery {
16 | /**
17 | * @description token
18 | */
19 | type: string;
20 | }
21 |
22 | /**
23 | * @tags 表单接口
24 | * @summary 表单接口
25 | * @description 表单:不知道是啥,这就是一个描述
26 | * @param params ApiFormsQuery
27 | * @returns {visitData:VisitData}
28 | */
29 | export async function apiForms(params?: ApiFormsQuery): Promise {
30 | return request('/api/forms', {
31 | method: 'POST',
32 | data: params,
33 | });
34 | }
35 |
36 | /**
37 | * @tags 表单接口
38 | * @summary 表单接口
39 | * @description 表单:不知道是啥,这就是一个描述
40 | * @param params ApiFormsQuery
41 | * @returns {visitData:VisitData}
42 | */
43 | export async function menu(params?: ApiFormsQuery): Promise {
44 | return request('/api/menu', { params });
45 | }
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 alita
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/utils/handelStr.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 把$ref字段处理成definitions里面的键
3 | * @param params
4 | */
5 | export function handelRef(params: string) {
6 | let str = params.replace('#/definitions/', '');
7 | str = strToKey(str);
8 | return str;
9 | }
10 |
11 | /**
12 | * @dec 将字符串转成js合法变量
13 | * @param params 需要转成合法变量的字符串
14 | */
15 | export function strToKey(params: string) {
16 | const reg = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/; // 匹配中文、字母、数字
17 | if (reg.test(params)) {
18 | return params;
19 | }
20 | if (params) {
21 | return params.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '');
22 | }
23 | console.log('strToKey执行异常,参数为空');
24 | return params;
25 | }
26 |
27 | export function apiToName(api: string, method?: string) {
28 | const name = strToKey(wordToHump(api, '/'));
29 | return method ? name + method.toLocaleUpperCase() : name;
30 | }
31 |
32 | export function wordToHump(params: string, delimiter: string) {
33 | return `${params
34 | .split(delimiter)
35 | .map((item, index) => {
36 | if (item) {
37 | if (index > 1) {
38 | const arr = item.split('');
39 | arr[0] = arr[0].toLocaleUpperCase();
40 | return arr.join('');
41 | }
42 | }
43 | return item;
44 | })
45 | .join('')}`;
46 | }
47 |
--------------------------------------------------------------------------------
/src/plugins/serviceJs/index.ts:
--------------------------------------------------------------------------------
1 | import { apiToName } from '../../utils/handelStr';
2 |
3 | function main(SwaggerData: { paths: any }) {
4 | const { paths } = SwaggerData;
5 | let str = "import { stringify } from 'qs';\nimport request from '@/utils/request';\n";
6 | Object.keys(paths).forEach(api => {
7 | Object.keys(paths[api]).forEach(method => {
8 | if (api !== '/') {
9 | const { description } = paths[api][method];
10 | str = str.concat(
11 | services(
12 | apiToName(api, method),
13 | api,
14 | description && description.replace(/\n/g, ''),
15 | method,
16 | ),
17 | );
18 | }
19 | });
20 | });
21 | const file = [{ fileName: 'api.js', fileStr: str }];
22 | return file;
23 | }
24 |
25 | function services(name: string, api: string, desc: string, method: string) {
26 | if (method === 'post') {
27 | return `\nexport async function ${name}(params) {\n return request('${api}', {\n method: 'POST',\n body: params,\n });\n} // ${desc}\n`;
28 | }
29 | return `\nexport async function ${name}(params) {\n return request(\`${api}?\${stringify(params)}\`);\n} // ${desc}\n`;
30 | }
31 |
32 | export default {
33 | outPath: 'src/services',
34 | handelData: main,
35 | };
36 |
--------------------------------------------------------------------------------
/bin/milady.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-var-requires
4 | const yParser = require('yargs-parser');
5 | // eslint-disable-next-line @typescript-eslint/no-var-requires
6 | const { join } = require('path');
7 | // eslint-disable-next-line @typescript-eslint/no-var-requires
8 | const { existsSync } = require('fs');
9 |
10 | function checkVersion() {
11 | const nodeVersion = process.versions.node;
12 | const versions = nodeVersion.split('.');
13 | const major = versions[0];
14 | const minor = versions[1];
15 | if (major * 10 + minor * 1 < 65) {
16 | // eslint-disable-next-line no-console
17 | console.log(`Node version must >= 6.5, but got ${major}.${minor}`);
18 | process.exit(1);
19 | }
20 | }
21 | function getArgv() {
22 | const argv = yParser(process.argv.slice(2));
23 | const [url] = argv._;
24 | return url;
25 | }
26 | function getParams() {
27 | let config = {
28 | dataSource: '',
29 | };
30 | const path = join(process.cwd(), '.miladyrc.js');
31 | if (existsSync(path)) {
32 | try {
33 | // eslint-disable-next-line global-require,import/no-dynamic-require
34 | config = require(path).default;
35 | } catch (error) {
36 | console.log(error);
37 | }
38 | }
39 | config.dataSource = getArgv() ? getArgv() : config.dataSource;
40 | return config;
41 | }
42 | checkVersion();
43 | const milady = require('../lib/index').default;
44 |
45 | milady(getParams());
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "milady",
3 | "version": "0.0.5",
4 | "description": "Generate the front-end code by parsing the interface document",
5 | "main": "lib/index.js",
6 | "bin": {
7 | "milady": "./bin/milady.js"
8 | },
9 | "scripts": {
10 | "build": "father-build",
11 | "publish": "npm publish --access public",
12 | "prettier": "prettier -c --write **/*",
13 | "eslint": "eslint --ext .js,.jsx,.ts,.tsx --format=pretty ./",
14 | "test": "umi-test",
15 | "lint-staged": "lint-staged",
16 | "coverage": "cat ./coverage/lcov.info | coveralls"
17 | },
18 | "husky": {
19 | "hooks": {
20 | "pre-commit": "npm run lint-staged"
21 | }
22 | },
23 | "lint-staged": {
24 | "**/*.{js,jsx,tsx,ts,less,md,json}": [
25 | "npm run prettier",
26 | "git add"
27 | ],
28 | "**/*.{js,jsx,ts,tsx}": "npm run eslint"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "git+https://github.com/alitajs/codegen.git"
33 | },
34 | "keywords": [
35 | "codegen",
36 | "umi",
37 | "alitajs"
38 | ],
39 | "files": [
40 | "lib",
41 | "template",
42 | "bin"
43 | ],
44 | "author": "xiaohuoni",
45 | "license": "MIT",
46 | "bugs": {
47 | "url": "https://github.com/alitajs/codegen/issues"
48 | },
49 | "homepage": "https://github.com/alitajs/codegen#readme",
50 | "dependencies": {
51 | "@types/node-fetch": "^2.3.7",
52 | "@types/signale": "^1.2.1",
53 | "fs": "^0.0.1-security",
54 | "fs-extra": "^8.0.1",
55 | "node-fetch": "^2.6.0",
56 | "path": "^0.12.7",
57 | "signale": "^1.4.0",
58 | "typescript": "^3.5.2",
59 | "umi-request": "^1.0.8",
60 | "yargs-parser": "^13.1.1"
61 | },
62 | "devDependencies": {
63 | "@types/fs-extra": "^7.0.0",
64 | "@types/jest": "^24.0.15",
65 | "@umijs/fabric": "^1.0.9",
66 | "father-build": "^1.3.2",
67 | "husky": "^2.4.1",
68 | "lint-staged": "^8.2.1",
69 | "prettier": "^1.18.2",
70 | "umi-test": "^1.6.1"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # milady
2 |
3 | Generate the front-end code by parsing the interface document
4 |
5 | ## what
6 |
7 | 这是一个通过 swagger 的 url 自动化生成代码的项目。
8 |
9 | ## why
10 |
11 | 因为使用 umi 途中需要写很多重复的 service 文件和 mock 文件,故建立此项目来自动化这些重复的工作。
12 |
13 | ## how
14 |
15 | ### 通过引用使用
16 |
17 | 安装
18 |
19 | ```js
20 | npm i milady
21 | ```
22 |
23 | 使用
24 |
25 | ```js
26 | import milady from 'milady';
27 | const config = {
28 | swaggerUrl: '', //必填,用于获取数据
29 | };
30 | milady(config); //传入配置参数,调用milady方法生成文件
31 | ```
32 |
33 | ### 通过命令行使用
34 |
35 | 安装
36 |
37 | ```js
38 | npm i milady -D
39 | ```
40 |
41 | 使用:
42 |
43 | - 执行命令`milady [swaggerUrl]`生成文件
44 | - 可选择设置配置`.miladyrc.js`文件进行高级设置,配置文件如下:
45 |
46 | ```js
47 | exports.default = {
48 | swaggerUrl: '', //推荐的,优先加载命令行的url,命令行没有再加载配置的url
49 | };
50 | ```
51 |
52 | ### 配置说明
53 |
54 | ### swaggerUrl
55 |
56 | 字段类型:string
57 |
58 | 说明:swagger 数据源的 url
59 |
60 | 例子:
61 |
62 | ```js
63 | exports.default = {
64 | swaggerUrl: '',
65 | };
66 | ```
67 |
68 | ### defaultPlugins
69 |
70 | 字段类型:object
71 |
72 | 说明:控制 milady 内置插件开启或关闭,目前支持 umi 的 mock 数据文件和 service 文件 js 版生成。enabled 为 false 则不会加载插件。
73 |
74 | 例子:
75 |
76 | ```js
77 | exports.default = {
78 | swaggerUrl: '',
79 | defaultPlugins: {
80 | serviceJs: { enabled: true },
81 | mock: { enabled: false },
82 | },
83 | };
84 | ```
85 |
86 | ### plugins
87 |
88 | 字段类型:array
89 |
90 | 说明:用于设置自己写的外部插件,url 为相对于根目录的插件目录。
91 |
92 | 例子:
93 |
94 | ```js
95 | exports.default = {
96 | swaggerUrl: '',
97 | plugins: [{ url: './plugin/detail.js' }],
98 | };
99 | ```
100 |
101 | ### 插件开发
102 |
103 | 说明:
104 |
105 | 1. 插件文件需要返回一个默认值对象,outPath 为输出到相对于根目录位置,handelData 为处理数据的方法。
106 | 2. handelData 有一个参数,是 swagger 数据源的值。handelData 必须返回一个固定格式集合,数组里面一个对象代表一个会生成的文件,fileName 是生成文件的名字(需要后缀),fileStr 是文件内容。
107 | 3. 你可以根据 swagger 数据处理出你喜欢的字符串来生成文件,也可以用这个方法作为生成文件的方法。
108 |
109 | 插件文件格式:
110 |
111 | ```js
112 | function handelData(params) {
113 | return [
114 | {
115 | fileName: 'check.txt',
116 | fileStr: '测试字符串',
117 | },
118 | ];
119 | }
120 | exports.default = {
121 | outPath: './out', // 输出目录路径
122 | handelData,
123 | };
124 | ```
125 |
--------------------------------------------------------------------------------
/src/plugins/serviceTs/index.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | changeApi,
3 | changeParam,
4 | changeText,
5 | generateHead,
6 | generateInterfaceName,
7 | generateName,
8 | generatePromise,
9 | generateType,
10 | toUpperCase,
11 | } from '.';
12 |
13 | test('changeText', () => {
14 | expect(changeText('a_b')).toEqual('aB');
15 | expect(changeText('a_b')).toEqual('aB');
16 | expect(changeText('a_Bc_d')).toEqual('aBcD');
17 | });
18 |
19 | test('changeApi', () => {
20 | expect(changeApi('/api/test')).toEqual("'/api/test'");
21 | // eslint-disable-next-line no-template-curly-in-string
22 | expect(changeApi('/api/test/{id}')).toEqual('`/api/test/${params.id}`');
23 | });
24 |
25 | test('changeParam', () => {
26 | expect(changeParam('/api/test'.split('/'))).toEqual(['', 'api', 'test']);
27 | expect(changeParam('/api/test/{id}'.split('/'))).toEqual(['', 'api', 'test', 'id']);
28 | expect(changeParam('/api/test/{id}/{bb}'.split('/'))).toEqual(['', 'api', 'test', 'id', 'bb']);
29 | });
30 |
31 | test('generateName', () => {
32 | expect(generateName('/api/test')).toEqual('apiTest');
33 | expect(generateName('/api/test/bcd')).toEqual('testBcd');
34 | expect(generateName('/api/test/{id}/{px}')).toEqual('idPx');
35 | });
36 | test('toUpperCase', () => {
37 | expect(toUpperCase('jnjsa')).toEqual('Jnjsa');
38 | expect(toUpperCase('cjsn')).toEqual('Cjsn');
39 | expect(toUpperCase('_as')).toEqual('_as');
40 | expect(toUpperCase('1dsa')).toEqual('1dsa');
41 | });
42 |
43 | test('generateType', () => {
44 | const data = {
45 | type: 'array',
46 | items: {
47 | $ref: '#/definitions/PlaystationGamePrice',
48 | },
49 | };
50 | expect(generateType(data)).toEqual('PlaystationGamePrice []');
51 | const data1 = {
52 | type: 'string',
53 | };
54 | expect(generateType(data1)).toEqual('string');
55 | const data2 = {
56 | $ref: '#/definitions/PlaystationGamePrice',
57 | };
58 | expect(generateType(data2)).toEqual('PlaystationGamePrice');
59 | const data3 = {
60 | type: 'integer',
61 | };
62 | expect(generateType(data3)).toEqual('number');
63 | });
64 |
65 | test('generateHead', () => {
66 | const data = {
67 | host: 'alitajs.com',
68 | basePath: '/v1',
69 | info: {
70 | version: '1.0.0',
71 | },
72 | };
73 | expect(generateHead(data)).toEqual(`
74 | /**
75 | * This file is automatically generated using Alitajs/codegen
76 | * Host: alitajs.com
77 | * BasePath: /v1
78 | * Version: 1.0.0
79 | * Description: 这个文件是使用脚本自动生成的,原则上来说,你不需要手动修改它
80 | * Others:
81 | **/
82 | import request from 'umi-request';\n
83 | `);
84 | });
85 |
86 | test('generatePromise', () => {
87 | expect(
88 | generatePromise({
89 | $ref: '#/definitions/ResultWrapper«ListResult«PlaystationGamePrice»»',
90 | }),
91 | ).toEqual('ResultWrapperListResultPlaystationGamePrice');
92 | expect(generatePromise({ $ref: '#/definitions/分页数据«优惠券对象»' })).toEqual(
93 | 'TemporaryVariable0',
94 | );
95 | expect(generatePromise({ type: 'string' })).toEqual('string');
96 | });
97 |
98 | test('generateInterfaceName', () => {
99 | expect(generateInterfaceName('分页数据«优惠券对象»')).toEqual('TemporaryVariable0');
100 | expect(generateInterfaceName('AnalysisData«List»')).toEqual('AnalysisDataList');
101 | });
102 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch';
2 | import { join } from 'path';
3 | import { outputFileSync, readdirSync } from 'fs-extra';
4 | import signale from 'signale';
5 | import { isUrl } from './utils/common';
6 |
7 | const loadPlugins: string[] = [];
8 | export interface DefaultPluginsConfig {
9 | enabled: boolean;
10 | }
11 | export interface DefaultPlugins {
12 | [index: string]: DefaultPluginsConfig;
13 | }
14 |
15 | // eslint-disable-next-line space-before-function-paren
16 | export default async function({
17 | dataSource,
18 | plugins = [],
19 | defaultPlugins = {
20 | mock: { enabled: true },
21 | serviceJs: { enabled: false },
22 | serviceTs: { enabled: true },
23 | serviceT: { enabled: false },
24 | },
25 | }: {
26 | dataSource: string;
27 | plugins: any[];
28 | defaultPlugins: DefaultPlugins;
29 | }) {
30 | signale.time('milady');
31 | if (!dataSource) {
32 | signale.log('必须携带URL地址,如milady https://xx.x.x/abc/v2/api-docs#/');
33 | return;
34 | }
35 | /* 获取数据 */
36 | let data: any;
37 | if (isUrl(dataSource)) {
38 | data = await getData(dataSource);
39 | } else {
40 | const str = join(process.cwd(), dataSource);
41 | // eslint-disable-next-line global-require,import/no-dynamic-require
42 | data = require(str);
43 | }
44 | /* 处理数据 */
45 | const files = handleData(data, defaultPlugins, plugins);
46 | /* 生成代码 */
47 | codeGen(files);
48 | signale.timeEnd('milady');
49 | }
50 | async function getData(dataSource: string) {
51 | const res = await fetch(dataSource);
52 | const data = await res.json();
53 | const error = await data.catch;
54 | if (error) {
55 | signale.error(error);
56 | }
57 | return data;
58 | }
59 | function handleData(
60 | SwaggerData: { tags?: never[] | undefined; paths: any; definitions: any },
61 | defaultPlugins: DefaultPlugins,
62 | plugins: any[],
63 | ) {
64 | const directory: any = [];
65 | /* 加载默认插件 */
66 | const defaultPluginsName = readdirSync(join(__dirname, '/plugins'));
67 | defaultPluginsName.forEach((item: any) => {
68 | const defaultPlugin = defaultPlugins[item];
69 | const str: string = join(__dirname, '/plugins', item);
70 | // eslint-disable-next-line global-require,import/no-dynamic-require
71 | const defaultPluginFile = require(str).default;
72 | if (defaultPlugin.enabled) {
73 | directory.push({
74 | outPath: defaultPluginFile.outPath,
75 | file: defaultPluginFile.handelData(SwaggerData),
76 | });
77 | loadPlugins.push(item);
78 | }
79 | });
80 | /* 加载配置插件 */
81 | if (Object.prototype.toString.call(plugins) === '[object Array]') {
82 | if (plugins.length !== 0) {
83 | plugins.forEach(element => {
84 | if (element.hasOwnProperty('url')) {
85 | const str: string = join(process.cwd(), element.url);
86 | // eslint-disable-next-line global-require,import/no-dynamic-require
87 | const pluginsFile = require(str).default;
88 | directory.push({
89 | outPath: pluginsFile.outPath,
90 | file: pluginsFile.handelData(SwaggerData),
91 | });
92 | loadPlugins.push(element.url.split('/').pop());
93 | } else {
94 | signale.error('插件字段类型有误!');
95 | }
96 | });
97 | }
98 | } else {
99 | signale.error('请检查插件格式是否为数组!');
100 | } // 检查插件类型
101 |
102 | return directory;
103 | }
104 | function generate(outPath: string, fileArr: { fileName: string; fileStr: string }[]) {
105 | fileArr.forEach(element => {
106 | let paths = outPath || 'out/';
107 | paths = join(paths, element.fileName);
108 | outputFileSync(paths, element.fileStr, 'utf-8');
109 | });
110 | }
111 | function codeGen(files: any[]) {
112 | files.forEach(element => {
113 | generate(element.outPath, element.file);
114 | });
115 | signale.complete('文件创建完成');
116 | signale.complete(`加载插件列表:${loadPlugins.join('、')}`);
117 | }
118 |
--------------------------------------------------------------------------------
/src/plugins/mock/index.ts:
--------------------------------------------------------------------------------
1 | import { handelRef, strToKey } from '../../utils/handelStr';
2 |
3 | function main(SwaggerData: { paths: any; definitions: any }) {
4 | const { paths, definitions } = SwaggerData;
5 | const indexStr = generateIndex(paths);
6 | const dataStr = generateData(definitions);
7 | const file = [
8 | { fileName: 'index.js', fileStr: indexStr },
9 | { fileName: 'data.js', fileStr: dataStr },
10 | ];
11 | return file;
12 | }
13 |
14 | /* 生成接口文件 */
15 | function generateIndex(paths: any) {
16 | let indexStr = "import data from './data';\n\nexport default {\n";
17 | Object.keys(paths).forEach(api => {
18 | Object.keys(paths[api]).forEach(method => {
19 | if (method === 'post' || method === 'get') {
20 | const { description, responses } = paths[api][method];
21 | const data = getIndexValue(responses);
22 |
23 | indexStr = indexStr.concat(
24 | ` '${method.toUpperCase()} ${api}': {\n code: 0,\n message: '成功',\n data: ${data},\n }, // ${description &&
25 | description.replace(/\n/g, '')}\n`,
26 | );
27 | }
28 | });
29 | });
30 | indexStr = indexStr.concat('};\n');
31 | return indexStr;
32 | } // 生成接口文件
33 | function getIndexValue(responses: any) {
34 | let res;
35 | if (responses['200'] && responses['200'].schema) {
36 | if (responses['200'].schema.$ref) {
37 | res = `data.${handelRef(responses['200'].schema.$ref)}`;
38 | } else if (responses['200'].schema.items && responses['200'].schema.items.$ref) {
39 | res = `[data.${handelRef(responses['200'].schema.items.$ref)}]`;
40 | }
41 | } else {
42 | res = null;
43 | }
44 | return res;
45 | }
46 |
47 | /* 生成数据文件 */
48 | let up = true; // 排序
49 | function generateData(definitions: any) {
50 | let dataStr = '';
51 | dataStr = dataStr.concat(generateObjectStr(Object.keys(definitions)));
52 | dataStr = dataStr.concat(generateBody(definitions));
53 | dataStr = dataStr.concat('export default obj;\n');
54 | return dataStr;
55 | } // 生成数据文件
56 | function generateBody(definitions: any) {
57 | let dataStr = '';
58 | Object.keys(definitions).forEach(dataName => {
59 | const dataContents = definitions[dataName].properties;
60 | up = true;
61 | const str = generateDataStr(dataName, dataContents);
62 | if (up) {
63 | dataStr = str.concat(dataStr);
64 | } else {
65 | dataStr = dataStr.concat(str);
66 | }
67 | });
68 | return dataStr;
69 | }
70 | function generateDataStr(dataName: any, dataContents: any) {
71 | if (!dataContents) {
72 | console.log(dataContents);
73 | return '';
74 | }
75 | let str = `obj.${strToKey(dataName)} = {\n`;
76 | Object.keys(dataContents).forEach(propertiesName => {
77 | str = str.concat(
78 | ` ${propertiesName}: ${generateValueStr(dataContents[propertiesName])}, // ${
79 | dataContents[propertiesName].description
80 | ? dataContents[propertiesName].description.replace(/\n/g, '').replace(/\s/g, '')
81 | : '无'
82 | }\n`,
83 | );
84 | });
85 | str = str.concat('};\n');
86 | return str;
87 | } // 生成数据
88 | function generateValueStr(params: any) {
89 | let dataStr;
90 | let data = null;
91 | if (params.items && params.items.$ref) {
92 | data = handelRef(params.items.$ref);
93 | }
94 | switch (params.type) {
95 | case 'array':
96 | dataStr = `[obj.${data}]`;
97 | up = false;
98 | break;
99 | case 'object':
100 | dataStr = `obj.${data}`;
101 | up = false;
102 | break;
103 | case 'string':
104 | dataStr = '1';
105 | break;
106 | case 'integer':
107 | dataStr = 1;
108 | break;
109 | case 'boolean':
110 | dataStr = true;
111 | break;
112 | case 'number':
113 | dataStr = 1;
114 | break;
115 | case 'undefined':
116 | dataStr = undefined;
117 | break;
118 | default:
119 | dataStr = "'没有type值'";
120 | break;
121 | }
122 | return dataStr;
123 | } // 生成数据值
124 | function generateObjectStr(params: any[]) {
125 | let str = 'const obj = {\n';
126 | params.forEach(element => {
127 | str = str.concat(` ${strToKey(element)}: null,\n`);
128 | });
129 | str = str.concat('};\n');
130 | return str;
131 | } // 生成定义导出数据对象
132 | export default {
133 | outPath: 'mock/',
134 | handelData: main,
135 | };
136 |
--------------------------------------------------------------------------------
/src/plugins/serviceTs/indexB.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'fs';
2 | import { join } from 'path';
3 | import { handelRef, strToKey } from '../../utils/handelStr';
4 |
5 | function main(SwaggerData: { paths: any; definitions: any }) {
6 | const { paths, definitions } = SwaggerData;
7 | const indexStr = generateIndex(paths);
8 | const dataStr = generateData(definitions);
9 | const str = indexStr + dataStr;
10 | const file = [{ fileName: 'api.ts', fileStr: str }];
11 | return file;
12 | }
13 |
14 | /* 生成接口文件 */
15 | function generateIndex(paths: any) {
16 | let indexStr = '';
17 | Object.keys(paths).forEach(api => {
18 | Object.keys(paths[api]).forEach(method => {
19 | if (method === 'post' || method === 'get') {
20 | const { description, responses, summary, tags, parameters } = paths[api][method];
21 | const interfaceGet = '';
22 | const requestInterfaceName = '';
23 | const responseInterfaceName = '';
24 | const requestBody = '';
25 | const data = getIndexValue(responses);
26 | const tpl = join(__dirname, './http.ts.tpl');
27 | let tplContent = readFileSync(tpl, 'utf-8');
28 | tplContent = tplContent
29 | .replace('<%= InterfaceGet %>', interfaceGet)
30 | .replace('<%= FunctionTags %>', JSON.stringify(tags))
31 | .replace('<%= FunctionSummary %>', summary)
32 | .replace('<%= FunctionDescription %>', description)
33 | .replace(new RegExp('<%= FunctionParams %>', 'g'), strToKey(requestInterfaceName))
34 | .replace('<%= FunctionName %>', strToKey(api))
35 | .replace('<%= FunctionPromise %>', strToKey(responseInterfaceName))
36 | .replace('<%= FunctionUrl %>', api)
37 | .replace('<%= FunctionParamsMethod %>', requestBody);
38 | indexStr = indexStr.concat(tplContent);
39 | }
40 | });
41 | });
42 | indexStr = indexStr.concat('};\n');
43 | return indexStr;
44 | } // 生成接口文件
45 | function getIndexValue(responses: any) {
46 | let res;
47 | if (responses['200'] && responses['200'].schema) {
48 | if (responses['200'].schema.$ref) {
49 | res = `data.${handelRef(responses['200'].schema.$ref)}`;
50 | } else if (responses['200'].schema.items && responses['200'].schema.items.$ref) {
51 | res = `[data.${handelRef(responses['200'].schema.items.$ref)}]`;
52 | }
53 | } else {
54 | res = null;
55 | }
56 | return res;
57 | }
58 |
59 | /* 生成请求文件 */
60 | let up = true; // 排序
61 | function generateData(definitions: any) {
62 | let dataStr = '';
63 | dataStr = dataStr.concat(generateObjectStr(Object.keys(definitions)));
64 | dataStr = dataStr.concat(generateBody(definitions));
65 | dataStr = dataStr.concat('export default obj;\n');
66 | return dataStr;
67 | } // 生成数据文件
68 | function generateBody(definitions: any) {
69 | let dataStr = '';
70 | Object.keys(definitions).forEach(dataName => {
71 | const dataContents = definitions[dataName].properties;
72 | up = true;
73 | const str = generateDataStr(dataName, dataContents);
74 | if (up) {
75 | dataStr = str.concat(dataStr);
76 | } else {
77 | dataStr = dataStr.concat(str);
78 | }
79 | });
80 | return dataStr;
81 | }
82 | function generateDataStr(dataName: any, dataContents: any) {
83 | if (!dataContents) {
84 | console.log(dataContents);
85 | return '';
86 | }
87 | let str = `obj.${strToKey(dataName)} = {\n`;
88 | Object.keys(dataContents).forEach(propertiesName => {
89 | str = str.concat(
90 | ` ${propertiesName}: ${generateValueStr(dataContents[propertiesName])}, // ${
91 | dataContents[propertiesName].description
92 | ? dataContents[propertiesName].description.replace(/\n/g, '').replace(/\s/g, '')
93 | : '无'
94 | }\n`,
95 | );
96 | });
97 | str = str.concat('};\n');
98 | return str;
99 | } // 生成数据
100 | function generateValueStr(params: any) {
101 | let dataStr;
102 | let data = null;
103 | if (params.items && params.items.$ref) {
104 | data = handelRef(params.items.$ref);
105 | }
106 | switch (params.type) {
107 | case 'array':
108 | dataStr = `[obj.${data}]`;
109 | up = false;
110 | break;
111 | case 'object':
112 | dataStr = `obj.${data}`;
113 | up = false;
114 | break;
115 | case 'string':
116 | dataStr = '1';
117 | break;
118 | case 'integer':
119 | dataStr = 1;
120 | break;
121 | case 'boolean':
122 | dataStr = true;
123 | break;
124 | case 'number':
125 | dataStr = 1;
126 | break;
127 | case 'undefined':
128 | dataStr = undefined;
129 | break;
130 | default:
131 | dataStr = "'没有type值'";
132 | break;
133 | }
134 | return dataStr;
135 | } // 生成数据值
136 | function generateObjectStr(params: any[]) {
137 | let str = 'const obj = {\n';
138 | params.forEach(element => {
139 | str = str.concat(` ${strToKey(element)}: null,\n`);
140 | });
141 | str = str.concat('};\n');
142 | return str;
143 | } // 生成定义导出数据对象
144 | export default {
145 | outPath: 'mock/',
146 | handelData: main,
147 | };
148 |
--------------------------------------------------------------------------------
/src/plugins/serviceTs/index.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 |
3 | import { readFileSync } from 'fs';
4 | import { strToKey, apiToName } from '../../utils/handelStr';
5 |
6 | const GetMethodString = '{\n params\n }';
7 | const PostMethodString = "{\n method: 'POST',\n data: params\n }";
8 |
9 | export function changeText(input: string) {
10 | if (input.includes('_')) {
11 | const texts = input.split('_');
12 | let str = texts.splice(0, 1)[0];
13 | texts.forEach(t => {
14 | str += toUpperCase(t);
15 | });
16 | return str;
17 | }
18 | return input;
19 | }
20 | let additionalParameters = {} as any;
21 | export function changeApi(input: string) {
22 | const hasParams = input.includes('{');
23 | const nameArr = input.split('/');
24 | const newArr = nameArr.map(i => {
25 | let iStr = i;
26 | if (i.includes('{')) {
27 | const param = i.replace(new RegExp('{', 'g'), '').replace(new RegExp('}', 'g'), '');
28 | additionalParameters[param] = param;
29 | iStr = `\${params.${param}}`;
30 | }
31 | return iStr;
32 | });
33 | return hasParams ? `\`${newArr.join('/')}\`` : `'${newArr.join('/')}'`;
34 | }
35 | export function changeParam(nameArr: any[]) {
36 | const newArr = nameArr.map(i => {
37 | let iStr = i;
38 | if (i.includes('{')) {
39 | iStr = i.replace(new RegExp('{', 'g'), '').replace(new RegExp('}', 'g'), '');
40 | }
41 | return iStr;
42 | });
43 | return newArr;
44 | }
45 | export function generateName(api: string) {
46 | const nameArr = changeParam(api.split('/'));
47 | let name = nameArr[nameArr.length - 2]
48 | ? nameArr[nameArr.length - 2] + toUpperCase(nameArr[nameArr.length - 1])
49 | : nameArr[nameArr.length - 1];
50 | name = name.replace(/-/g, '_');
51 | return changeText(name);
52 | }
53 | export function toUpperCase(str: string) {
54 | return str.replace(str[0], str[0].toUpperCase());
55 | }
56 | export function generateType({ type, items, $ref }: any) {
57 | if (type === 'integer') {
58 | return 'number';
59 | }
60 | if (type === 'array') {
61 | if (items.$ref) {
62 | return `${generateInterfaceName(items.$ref)} []`;
63 | }
64 | if (items.type) {
65 | return `${items.type} []`;
66 | }
67 | return '[]';
68 | }
69 | if ($ref) {
70 | return generateInterfaceName($ref);
71 | }
72 | return type;
73 | }
74 | export function generateHead(data: any) {
75 | return `
76 | /**
77 | * This file is automatically generated using Alitajs/codegen
78 | * Host: ${data.host}
79 | * BasePath: ${data.basePath}
80 | * Version: ${data.info.version}
81 | * Description: 这个文件是使用脚本自动生成的,原则上来说,你不需要手动修改它
82 | * Others:
83 | **/
84 | import request from 'umi-request';\n
85 | `;
86 | }
87 | const hasChineseArr = {} as any;
88 | let hasChineseCount = 0;
89 | export function generatePromise(resData: any) {
90 | let promise = '';
91 | if (!resData) {
92 | return 'any';
93 | }
94 | if (resData.$ref) {
95 | promise = resData.$ref
96 | .replace('#/definitions/', '')
97 | .replace(new RegExp('«', 'g'), '')
98 | .replace(new RegExp('»', 'g'), '');
99 | } else if (resData.item) {
100 | promise = resData.item.$ref
101 | .replace('#/definitions/', '')
102 | .replace(new RegExp('«', 'g'), '')
103 | .replace(new RegExp('»', 'g'), '');
104 | } else if (resData.type) {
105 | promise = resData.type;
106 | }
107 | const hasChinese = /[^\u4e00-\u9fa5]+/.test(promise);
108 | if (!hasChinese) {
109 | if (hasChineseArr[promise]) {
110 | promise = hasChineseArr[promise];
111 | } else {
112 | hasChineseArr[promise] = `TemporaryVariable${hasChineseCount}`;
113 | promise = `TemporaryVariable${hasChineseCount}`;
114 | hasChineseCount += 1;
115 | }
116 | }
117 | if (resData.item) {
118 | promise += '[]';
119 | }
120 | return promise;
121 | }
122 | export function generateInterfaceName(text: string) {
123 | let input = text;
124 | input = input
125 | .replace('#/definitions/', '')
126 | .replace(new RegExp('«', 'g'), '')
127 | .replace(new RegExp('»', 'g'), '');
128 | const hasChinese = /[^\u4e00-\u9fa5]+/.test(input);
129 | if (!hasChinese) {
130 | if (hasChineseArr[input]) {
131 | input = hasChineseArr[input];
132 | } else {
133 | hasChineseArr[input] = `TemporaryVariable${hasChineseCount}`;
134 | input = `TemporaryVariable${hasChineseCount}`;
135 | hasChineseCount += 1;
136 | }
137 | }
138 | return changeText(input);
139 | }
140 | export function generateServiceFiles(SwaggerData: {
141 | tags?: never[] | undefined;
142 | paths: any;
143 | definitions: any;
144 | }) {
145 | const { tags = [], paths, definitions } = SwaggerData;
146 | let outPutStr = generateHead(SwaggerData);
147 | /* 生成接口文件 */
148 | Object.keys(definitions).forEach(defItem => {
149 | outPutStr += `interface ${strToKey(generateInterfaceName(defItem))} {\n`;
150 | const properties = definitions[defItem].properties || {};
151 | Object.keys(properties).forEach(subDefItem => {
152 | let defItemStr = ' /**\n';
153 | defItemStr += ` * @description ${properties[subDefItem].description || ''}\n`;
154 | defItemStr += ' **/\n';
155 | defItemStr += ` ${subDefItem}: ${strToKey(generateType(properties[subDefItem]))};\n`;
156 | outPutStr += defItemStr;
157 | });
158 | outPutStr += '}\n';
159 | });
160 | /* 生成请求文件 */
161 | Object.keys(paths).forEach(item => {
162 | const itemData = paths[item];
163 | Object.keys(itemData).forEach(subItem => {
164 | additionalParameters = {};
165 | const subItemData = itemData[subItem];
166 | const { summary, description, tags: subTags, responses, parameters = [] } = subItemData;
167 | const resData = responses['200'] && responses['200'].schema;
168 | const url = changeApi(item);
169 | const name = apiToName(item, subItem);
170 | const params = `${toUpperCase(name)}Query`;
171 | const paramsMethod = subItem === 'get' ? GetMethodString : PostMethodString;
172 | let itemTargs = subTags || [];
173 | itemTargs = itemTargs.map((t: string) => {
174 | let tStr = t;
175 | tags.forEach((tag: { name: string; description: string }) => {
176 | if (tag.name === t) {
177 | tStr = tag.description;
178 | }
179 | });
180 | return tStr;
181 | });
182 | const promise = generatePromise(resData);
183 |
184 | let definition = `interface ${params} {\n`;
185 | parameters.map((p: { in: string; description: any; name: any; type: any }) => {
186 | if (p.in === 'query') {
187 | let pStr = ' /**\n';
188 | pStr += ` * @description ${p.description || ''}\n`;
189 | pStr += ' **/\n';
190 | pStr += ` ${p.name}: ${generateType(p)};\n`;
191 | definition += pStr;
192 | Object.keys(additionalParameters).map(ap => {
193 | let apStr = ' /**\n';
194 | apStr += ' * @description 请求地址中追加的参数\n';
195 | apStr += ' **/\n';
196 | apStr += ` ${ap}: string;\n`;
197 | definition += apStr;
198 | return ap;
199 | });
200 | }
201 | return p;
202 | });
203 | definition += '}\n';
204 | const tpl = join(__dirname, './http.ts.tpl');
205 | let tplContent = readFileSync(tpl, 'utf-8');
206 | tplContent = tplContent
207 | .replace('<%= InterfaceDefinition %>', definition)
208 | .replace('<%= FunctionTags %>', JSON.stringify(itemTargs))
209 | .replace('<%= FunctionSummary %>', summary)
210 | .replace('<%= FunctionDescription %>', description)
211 | .replace(new RegExp('<%= FunctionParams %>', 'g'), strToKey(params))
212 | .replace('<%= FunctionName %>', strToKey(name))
213 | .replace('<%= FunctionPromise %>', strToKey(promise))
214 | .replace('<%= FunctionUrl %>', url)
215 | .replace('<%= FunctionParamsMethod %>', paramsMethod);
216 | outPutStr += tplContent;
217 | });
218 | });
219 | const file = [{ fileName: 'api.ts', fileStr: outPutStr }];
220 | return file;
221 | }
222 | export default {
223 | outPath: 'src/services',
224 | handelData: generateServiceFiles,
225 | };
226 |
--------------------------------------------------------------------------------
/example/data/swagger2.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.",
5 | "version": "1.0.0",
6 | "title": "Swagger Petstore",
7 | "termsOfService": "http://swagger.io/terms/",
8 | "contact": { "email": "apiteam@swagger.io" },
9 | "license": { "name": "Apache 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }
10 | },
11 | "host": "petstore.swagger.io",
12 | "basePath": "/v2",
13 | "tags": [
14 | {
15 | "name": "pet",
16 | "description": "Everything about your Pets",
17 | "externalDocs": { "description": "Find out more", "url": "http://swagger.io" }
18 | },
19 | { "name": "store", "description": "Access to Petstore orders" },
20 | {
21 | "name": "user",
22 | "description": "Operations about user",
23 | "externalDocs": { "description": "Find out more about our store", "url": "http://swagger.io" }
24 | }
25 | ],
26 | "schemes": ["https", "http"],
27 | "paths": {
28 | "/pet": {
29 | "post": {
30 | "tags": ["pet"],
31 | "summary": "Add a new pet to the store",
32 | "description": "",
33 | "operationId": "addPet",
34 | "consumes": ["application/json", "application/xml"],
35 | "produces": ["application/xml", "application/json"],
36 | "parameters": [
37 | {
38 | "in": "body",
39 | "name": "body",
40 | "description": "Pet object that needs to be added to the store",
41 | "required": true,
42 | "schema": { "$ref": "#/definitions/Pet" }
43 | }
44 | ],
45 | "responses": { "405": { "description": "Invalid input" } },
46 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }]
47 | },
48 | "put": {
49 | "tags": ["pet"],
50 | "summary": "Update an existing pet",
51 | "description": "",
52 | "operationId": "updatePet",
53 | "consumes": ["application/json", "application/xml"],
54 | "produces": ["application/xml", "application/json"],
55 | "parameters": [
56 | {
57 | "in": "body",
58 | "name": "body",
59 | "description": "Pet object that needs to be added to the store",
60 | "required": true,
61 | "schema": { "$ref": "#/definitions/Pet" }
62 | }
63 | ],
64 | "responses": {
65 | "400": { "description": "Invalid ID supplied" },
66 | "404": { "description": "Pet not found" },
67 | "405": { "description": "Validation exception" }
68 | },
69 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }]
70 | }
71 | },
72 | "/pet/findByStatus": {
73 | "get": {
74 | "tags": ["pet"],
75 | "summary": "Finds Pets by status",
76 | "description": "Multiple status values can be provided with comma separated strings",
77 | "operationId": "findPetsByStatus",
78 | "produces": ["application/xml", "application/json"],
79 | "parameters": [
80 | {
81 | "name": "status",
82 | "in": "query",
83 | "description": "Status values that need to be considered for filter",
84 | "required": true,
85 | "type": "array",
86 | "items": {
87 | "type": "string",
88 | "enum": ["available", "pending", "sold"],
89 | "default": "available"
90 | },
91 | "collectionFormat": "multi"
92 | }
93 | ],
94 | "responses": {
95 | "200": {
96 | "description": "successful operation",
97 | "schema": { "type": "array", "items": { "$ref": "#/definitions/Pet" } }
98 | },
99 | "400": { "description": "Invalid status value" }
100 | },
101 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }]
102 | }
103 | },
104 | "/pet/findByTags": {
105 | "get": {
106 | "tags": ["pet"],
107 | "summary": "Finds Pets by tags",
108 | "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
109 | "operationId": "findPetsByTags",
110 | "produces": ["application/xml", "application/json"],
111 | "parameters": [
112 | {
113 | "name": "tags",
114 | "in": "query",
115 | "description": "Tags to filter by",
116 | "required": true,
117 | "type": "array",
118 | "items": { "type": "string" },
119 | "collectionFormat": "multi"
120 | }
121 | ],
122 | "responses": {
123 | "200": {
124 | "description": "successful operation",
125 | "schema": { "type": "array", "items": { "$ref": "#/definitions/Pet" } }
126 | },
127 | "400": { "description": "Invalid tag value" }
128 | },
129 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }],
130 | "deprecated": true
131 | }
132 | },
133 | "/pet/{petId}": {
134 | "get": {
135 | "tags": ["pet"],
136 | "summary": "Find pet by ID",
137 | "description": "Returns a single pet",
138 | "operationId": "getPetById",
139 | "produces": ["application/xml", "application/json"],
140 | "parameters": [
141 | {
142 | "name": "petId",
143 | "in": "path",
144 | "description": "ID of pet to return",
145 | "required": true,
146 | "type": "integer",
147 | "format": "int64"
148 | }
149 | ],
150 | "responses": {
151 | "200": {
152 | "description": "successful operation",
153 | "schema": { "$ref": "#/definitions/Pet" }
154 | },
155 | "400": { "description": "Invalid ID supplied" },
156 | "404": { "description": "Pet not found" }
157 | },
158 | "security": [{ "api_key": [] }]
159 | },
160 | "post": {
161 | "tags": ["pet"],
162 | "summary": "Updates a pet in the store with form data",
163 | "description": "",
164 | "operationId": "updatePetWithForm",
165 | "consumes": ["application/x-www-form-urlencoded"],
166 | "produces": ["application/xml", "application/json"],
167 | "parameters": [
168 | {
169 | "name": "petId",
170 | "in": "path",
171 | "description": "ID of pet that needs to be updated",
172 | "required": true,
173 | "type": "integer",
174 | "format": "int64"
175 | },
176 | {
177 | "name": "name",
178 | "in": "formData",
179 | "description": "Updated name of the pet",
180 | "required": false,
181 | "type": "string"
182 | },
183 | {
184 | "name": "status",
185 | "in": "formData",
186 | "description": "Updated status of the pet",
187 | "required": false,
188 | "type": "string"
189 | }
190 | ],
191 | "responses": { "405": { "description": "Invalid input" } },
192 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }]
193 | },
194 | "delete": {
195 | "tags": ["pet"],
196 | "summary": "Deletes a pet",
197 | "description": "",
198 | "operationId": "deletePet",
199 | "produces": ["application/xml", "application/json"],
200 | "parameters": [
201 | { "name": "api_key", "in": "header", "required": false, "type": "string" },
202 | {
203 | "name": "petId",
204 | "in": "path",
205 | "description": "Pet id to delete",
206 | "required": true,
207 | "type": "integer",
208 | "format": "int64"
209 | }
210 | ],
211 | "responses": {
212 | "400": { "description": "Invalid ID supplied" },
213 | "404": { "description": "Pet not found" }
214 | },
215 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }]
216 | }
217 | },
218 | "/pet/{petId}/uploadImage": {
219 | "post": {
220 | "tags": ["pet"],
221 | "summary": "uploads an image",
222 | "description": "",
223 | "operationId": "uploadFile",
224 | "consumes": ["multipart/form-data"],
225 | "produces": ["application/json"],
226 | "parameters": [
227 | {
228 | "name": "petId",
229 | "in": "path",
230 | "description": "ID of pet to update",
231 | "required": true,
232 | "type": "integer",
233 | "format": "int64"
234 | },
235 | {
236 | "name": "additionalMetadata",
237 | "in": "formData",
238 | "description": "Additional data to pass to server",
239 | "required": false,
240 | "type": "string"
241 | },
242 | {
243 | "name": "file",
244 | "in": "formData",
245 | "description": "file to upload",
246 | "required": false,
247 | "type": "file"
248 | }
249 | ],
250 | "responses": {
251 | "200": {
252 | "description": "successful operation",
253 | "schema": { "$ref": "#/definitions/ApiResponse" }
254 | }
255 | },
256 | "security": [{ "petstore_auth": ["write:pets", "read:pets"] }]
257 | }
258 | },
259 | "/store/inventory": {
260 | "get": {
261 | "tags": ["store"],
262 | "summary": "Returns pet inventories by status",
263 | "description": "Returns a map of status codes to quantities",
264 | "operationId": "getInventory",
265 | "produces": ["application/json"],
266 | "parameters": [],
267 | "responses": {
268 | "200": {
269 | "description": "successful operation",
270 | "schema": {
271 | "type": "object",
272 | "additionalProperties": { "type": "integer", "format": "int32" }
273 | }
274 | }
275 | },
276 | "security": [{ "api_key": [] }]
277 | }
278 | },
279 | "/store/order": {
280 | "post": {
281 | "tags": ["store"],
282 | "summary": "Place an order for a pet",
283 | "description": "",
284 | "operationId": "placeOrder",
285 | "produces": ["application/xml", "application/json"],
286 | "parameters": [
287 | {
288 | "in": "body",
289 | "name": "body",
290 | "description": "order placed for purchasing the pet",
291 | "required": true,
292 | "schema": { "$ref": "#/definitions/Order" }
293 | }
294 | ],
295 | "responses": {
296 | "200": {
297 | "description": "successful operation",
298 | "schema": { "$ref": "#/definitions/Order" }
299 | },
300 | "400": { "description": "Invalid Order" }
301 | }
302 | }
303 | },
304 | "/store/order/{orderId}": {
305 | "get": {
306 | "tags": ["store"],
307 | "summary": "Find purchase order by ID",
308 | "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
309 | "operationId": "getOrderById",
310 | "produces": ["application/xml", "application/json"],
311 | "parameters": [
312 | {
313 | "name": "orderId",
314 | "in": "path",
315 | "description": "ID of pet that needs to be fetched",
316 | "required": true,
317 | "type": "integer",
318 | "maximum": 10.0,
319 | "minimum": 1.0,
320 | "format": "int64"
321 | }
322 | ],
323 | "responses": {
324 | "200": {
325 | "description": "successful operation",
326 | "schema": { "$ref": "#/definitions/Order" }
327 | },
328 | "400": { "description": "Invalid ID supplied" },
329 | "404": { "description": "Order not found" }
330 | }
331 | },
332 | "delete": {
333 | "tags": ["store"],
334 | "summary": "Delete purchase order by ID",
335 | "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
336 | "operationId": "deleteOrder",
337 | "produces": ["application/xml", "application/json"],
338 | "parameters": [
339 | {
340 | "name": "orderId",
341 | "in": "path",
342 | "description": "ID of the order that needs to be deleted",
343 | "required": true,
344 | "type": "integer",
345 | "minimum": 1.0,
346 | "format": "int64"
347 | }
348 | ],
349 | "responses": {
350 | "400": { "description": "Invalid ID supplied" },
351 | "404": { "description": "Order not found" }
352 | }
353 | }
354 | },
355 | "/user": {
356 | "post": {
357 | "tags": ["user"],
358 | "summary": "Create user",
359 | "description": "This can only be done by the logged in user.",
360 | "operationId": "createUser",
361 | "produces": ["application/xml", "application/json"],
362 | "parameters": [
363 | {
364 | "in": "body",
365 | "name": "body",
366 | "description": "Created user object",
367 | "required": true,
368 | "schema": { "$ref": "#/definitions/User" }
369 | }
370 | ],
371 | "responses": { "default": { "description": "successful operation" } }
372 | }
373 | },
374 | "/user/createWithArray": {
375 | "post": {
376 | "tags": ["user"],
377 | "summary": "Creates list of users with given input array",
378 | "description": "",
379 | "operationId": "createUsersWithArrayInput",
380 | "produces": ["application/xml", "application/json"],
381 | "parameters": [
382 | {
383 | "in": "body",
384 | "name": "body",
385 | "description": "List of user object",
386 | "required": true,
387 | "schema": { "type": "array", "items": { "$ref": "#/definitions/User" } }
388 | }
389 | ],
390 | "responses": { "default": { "description": "successful operation" } }
391 | }
392 | },
393 | "/user/createWithList": {
394 | "post": {
395 | "tags": ["user"],
396 | "summary": "Creates list of users with given input array",
397 | "description": "",
398 | "operationId": "createUsersWithListInput",
399 | "produces": ["application/xml", "application/json"],
400 | "parameters": [
401 | {
402 | "in": "body",
403 | "name": "body",
404 | "description": "List of user object",
405 | "required": true,
406 | "schema": { "type": "array", "items": { "$ref": "#/definitions/User" } }
407 | }
408 | ],
409 | "responses": { "default": { "description": "successful operation" } }
410 | }
411 | },
412 | "/user/login": {
413 | "get": {
414 | "tags": ["user"],
415 | "summary": "Logs user into the system",
416 | "description": "",
417 | "operationId": "loginUser",
418 | "produces": ["application/xml", "application/json"],
419 | "parameters": [
420 | {
421 | "name": "username",
422 | "in": "query",
423 | "description": "The user name for login",
424 | "required": true,
425 | "type": "string"
426 | },
427 | {
428 | "name": "password",
429 | "in": "query",
430 | "description": "The password for login in clear text",
431 | "required": true,
432 | "type": "string"
433 | }
434 | ],
435 | "responses": {
436 | "200": {
437 | "description": "successful operation",
438 | "schema": { "type": "string" },
439 | "headers": {
440 | "X-Rate-Limit": {
441 | "type": "integer",
442 | "format": "int32",
443 | "description": "calls per hour allowed by the user"
444 | },
445 | "X-Expires-After": {
446 | "type": "string",
447 | "format": "date-time",
448 | "description": "date in UTC when token expires"
449 | }
450 | }
451 | },
452 | "400": { "description": "Invalid username/password supplied" }
453 | }
454 | }
455 | },
456 | "/user/logout": {
457 | "get": {
458 | "tags": ["user"],
459 | "summary": "Logs out current logged in user session",
460 | "description": "",
461 | "operationId": "logoutUser",
462 | "produces": ["application/xml", "application/json"],
463 | "parameters": [],
464 | "responses": { "default": { "description": "successful operation" } }
465 | }
466 | },
467 | "/user/{username}": {
468 | "get": {
469 | "tags": ["user"],
470 | "summary": "Get user by user name",
471 | "description": "",
472 | "operationId": "getUserByName",
473 | "produces": ["application/xml", "application/json"],
474 | "parameters": [
475 | {
476 | "name": "username",
477 | "in": "path",
478 | "description": "The name that needs to be fetched. Use user1 for testing. ",
479 | "required": true,
480 | "type": "string"
481 | }
482 | ],
483 | "responses": {
484 | "200": {
485 | "description": "successful operation",
486 | "schema": { "$ref": "#/definitions/User" }
487 | },
488 | "400": { "description": "Invalid username supplied" },
489 | "404": { "description": "User not found" }
490 | }
491 | },
492 | "put": {
493 | "tags": ["user"],
494 | "summary": "Updated user",
495 | "description": "This can only be done by the logged in user.",
496 | "operationId": "updateUser",
497 | "produces": ["application/xml", "application/json"],
498 | "parameters": [
499 | {
500 | "name": "username",
501 | "in": "path",
502 | "description": "name that need to be updated",
503 | "required": true,
504 | "type": "string"
505 | },
506 | {
507 | "in": "body",
508 | "name": "body",
509 | "description": "Updated user object",
510 | "required": true,
511 | "schema": { "$ref": "#/definitions/User" }
512 | }
513 | ],
514 | "responses": {
515 | "400": { "description": "Invalid user supplied" },
516 | "404": { "description": "User not found" }
517 | }
518 | },
519 | "delete": {
520 | "tags": ["user"],
521 | "summary": "Delete user",
522 | "description": "This can only be done by the logged in user.",
523 | "operationId": "deleteUser",
524 | "produces": ["application/xml", "application/json"],
525 | "parameters": [
526 | {
527 | "name": "username",
528 | "in": "path",
529 | "description": "The name that needs to be deleted",
530 | "required": true,
531 | "type": "string"
532 | }
533 | ],
534 | "responses": {
535 | "400": { "description": "Invalid username supplied" },
536 | "404": { "description": "User not found" }
537 | }
538 | }
539 | }
540 | },
541 | "securityDefinitions": {
542 | "petstore_auth": {
543 | "type": "oauth2",
544 | "authorizationUrl": "https://petstore.swagger.io/oauth/authorize",
545 | "flow": "implicit",
546 | "scopes": { "write:pets": "modify pets in your account", "read:pets": "read your pets" }
547 | },
548 | "api_key": { "type": "apiKey", "name": "api_key", "in": "header" }
549 | },
550 | "definitions": {
551 | "Order": {
552 | "type": "object",
553 | "properties": {
554 | "id": { "type": "integer", "format": "int64" },
555 | "petId": { "type": "integer", "format": "int64" },
556 | "quantity": { "type": "integer", "format": "int32" },
557 | "shipDate": { "type": "string", "format": "date-time" },
558 | "status": {
559 | "type": "string",
560 | "description": "Order Status",
561 | "enum": ["placed", "approved", "delivered"]
562 | },
563 | "complete": { "type": "boolean", "default": false }
564 | },
565 | "xml": { "name": "Order" }
566 | },
567 | "User": {
568 | "type": "object",
569 | "properties": {
570 | "id": { "type": "integer", "format": "int64" },
571 | "username": { "type": "string" },
572 | "firstName": { "type": "string" },
573 | "lastName": { "type": "string" },
574 | "email": { "type": "string" },
575 | "password": { "type": "string" },
576 | "phone": { "type": "string" },
577 | "userStatus": { "type": "integer", "format": "int32", "description": "User Status" }
578 | },
579 | "xml": { "name": "User" }
580 | },
581 | "Category": {
582 | "type": "object",
583 | "properties": {
584 | "id": { "type": "integer", "format": "int64" },
585 | "name": { "type": "string" }
586 | },
587 | "xml": { "name": "Category" }
588 | },
589 | "Tag": {
590 | "type": "object",
591 | "properties": {
592 | "id": { "type": "integer", "format": "int64" },
593 | "name": { "type": "string" }
594 | },
595 | "xml": { "name": "Tag" }
596 | },
597 | "Pet": {
598 | "type": "object",
599 | "required": ["name", "photoUrls"],
600 | "properties": {
601 | "id": { "type": "integer", "format": "int64" },
602 | "category": { "$ref": "#/definitions/Category" },
603 | "name": { "type": "string", "example": "doggie" },
604 | "photoUrls": {
605 | "type": "array",
606 | "xml": { "name": "photoUrl", "wrapped": true },
607 | "items": { "type": "string" }
608 | },
609 | "tags": {
610 | "type": "array",
611 | "xml": { "name": "tag", "wrapped": true },
612 | "items": { "$ref": "#/definitions/Tag" }
613 | },
614 | "status": {
615 | "type": "string",
616 | "description": "pet status in the store",
617 | "enum": ["available", "pending", "sold"]
618 | }
619 | },
620 | "xml": { "name": "Pet" }
621 | },
622 | "ApiResponse": {
623 | "type": "object",
624 | "properties": {
625 | "code": { "type": "integer", "format": "int32" },
626 | "type": { "type": "string" },
627 | "message": { "type": "string" }
628 | }
629 | }
630 | },
631 | "externalDocs": { "description": "Find out more about Swagger", "url": "http://swagger.io" }
632 | }
633 |
--------------------------------------------------------------------------------