Skip to content

Commit

Permalink
Merge pull request #19 from developer-once/future/openApi
Browse files Browse the repository at this point in the history
「Future」add openapi create API services file
  • Loading branch information
MriLiuJY authored Jun 5, 2023
2 parents 48b2be5 + 6da1dc8 commit fcce4bc
Show file tree
Hide file tree
Showing 17 changed files with 2,332 additions and 20 deletions.
34 changes: 30 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "@developer-once/onecli",
"version": "1.4.4-beta.0",
"version": "1.5.0-beta.0",
"description": "developer-once onecli cli",
"main": "/lib/bin/index.js",
"files": [
"lib",
"templates",
"README.md"
],
"scripts": {
Expand All @@ -20,12 +21,21 @@
"license": "MIT",
"devDependencies": {
"@types/conventional-changelog": "^3.1.1",
"@types/express": "^4.17.11",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^8.2.0",
"@types/lodash": "^4.14.195",
"@types/memoizee": "^0.4.8",
"@types/mockjs": "^1.0.7",
"@types/node": "^18.13.0",
"@types/npmlog": "^4.1.4",
"@types/nunjucks": "^3.2.2",
"@types/ora": "^3.2.0",
"@types/prettier": "^2.2.1",
"@types/reserved-words": "^0.1.0",
"@types/semver": "^7.5.0",
"@types/shelljs": "^0.8.11",
"@types/swagger2openapi": "^7.0.0",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"@umijs/fabric": "^3.0.0",
Expand All @@ -34,7 +44,7 @@
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.2.0",
"rimraf": "^3.0.2",
"np": "^7.2.0",
"tslib": "^2.3.1",
"typescript": "^4.5.5"
},
Expand All @@ -45,20 +55,36 @@
"@commitlint/config-conventional": "^17.4.4",
"@commitlint/lint": "^17.4.4",
"@commitlint/load": "^17.5.0",
"axios": "^1.3.3",
"@umijs/fabric": "^2.5.6",
"axios": "^1.4.0",
"chalk": "^4.1.2",
"commander": "^9.0.0",
"commitizen": "^4.2.5",
"conventional-changelog": "^3.1.25",
"cz-conventional-changelog": "^3.3.0",
"dayjs": "^1.10.3",
"download-git-repo": "^3.0.2",
"eslint": "^7.18.0",
"execa": "^5.1.1",
"fs-extra": "^10.0.0",
"glob": "^7.1.6",
"inquirer": "^8.2.0",
"lodash": "^4.17.21",
"memoizee": "^0.4.15",
"minimist": "^1.2.6",
"mock.js": "^0.2.0",
"mockjs": "^1.1.0",
"node-fetch": "^2.6.11",
"npmlog": "^7.0.1",
"nunjucks": "^3.2.4",
"openapi3-ts": "^2.0.1",
"ora": "^5.4.1",
"prettier": "^2.8.8",
"reserved-words": "^0.1.2",
"semver": "^7.3.8",
"simple-git": "^3.16.1"
"simple-git": "^3.16.1",
"swagger2openapi": "^7.0.8",
"tiny-pinyin": "^1.3.2"
},
"repository": {
"type": "git",
Expand Down
11 changes: 10 additions & 1 deletion src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Command } from 'commander';
import create from '../commands/create/index';
import cz from '../commands/cz';
import publish from '../commands/publish';
// import init from '../commands/init';
import openAPI from '../commands/openapi/index';
import { checkLogin, checkVersion, log, PREFIX } from '../utils/index';

const program = new Command();
Expand Down Expand Up @@ -47,6 +47,15 @@ program
await publish();
});

// ---------- openApi ----------
program
.command('openapi')
.description('openapi 自动生成 service')
.action(() => {
openAPI();
});


// ---------- 这里设置的是全局配置 ----------
program
.name(Object.keys(pkg.bin)[0])
Expand Down
46 changes: 46 additions & 0 deletions src/commands/openapi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// @ts-nocheck
// --- 入口文件 ---
// --- 生成 Services ---
import inquirer from 'inquirer';
import { generateService } from './openApi';
import { log } from '../../utils/index';


async function inputSchemaPath() {
return inquirer
.prompt([
{
type: 'input',
name: 'schemaPath',
message: `请输入 OpenAPI 3.0 的地址, 默认为 https://petstore.swagger.io/v2/swagger.json`,
},
])
.then((answer) => answer.schemaPath);
}

async function inputServersPath() {
return inquirer
.prompt([
{
type: 'input',
name: 'serversPath',
message: `请输入生成的 Services 的路径, 默认为 ./src/services`,
},
])
.then((answer) => answer.serversPath);
}

const openAPI = async () => {
const schemaPath = await inputSchemaPath() || "https://petstore.swagger.io/v2/swagger.json";
log.verbose('schemaPath', schemaPath);

const serversPath = await inputServersPath() || "./src/services";
log.verbose('serversPath', serversPath);

generateService({
schemaPath,
serversPath,
});
};

export default openAPI;
7 changes: 7 additions & 0 deletions src/commands/openapi/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @ts-nocheck
import chalk from 'chalk';

// eslint-disable-next-line no-console
const Log = (...rest) => console.log(`${chalk.blue('[openAPI]')}: ${rest.join('\n')}`);

export default Log;
224 changes: 224 additions & 0 deletions src/commands/openapi/mockGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// @ts-nocheck
import Mock from 'mockjs';
import fs from 'fs';
import pinyin from "tiny-pinyin";
import {dirname, join} from 'path';

import {prettierFile, writeFile} from '../../utils';
import OpenAPIParserMock from './openAPIParserMock/index';
import Log from './log';

Mock.Random.extend({
country() {
const data = [
'阿根廷',
'澳大利亚',
'巴西',
'加拿大',
'中国',
'法国',
'德国',
'印度',
'印度尼西亚',
'意大利',
'日本',
'韩国',
'墨西哥',
'俄罗斯',
'沙特阿拉伯',
'南非',
'土耳其',
'英国',
'美国',
];
const id = (Math.random() * data.length).toFixed();
return data[id];
},
phone() {
const phonepreFix = ['111', '112', '114']; // 自己写前缀哈
return this.pick(phonepreFix) + Mock.mock(/\d{8}/); // Number()
},
status() {
const status: string[] = ['success', 'error', 'default', 'processing', 'warning'];
const random: number = +(Math.random() * 4).toFixed(0);
return status[random];
},
authority() {
const status = ['admin', 'user', 'guest'];
const random = +(Math.random() * status.length).toFixed(0);
return status[random];
},
avatar() {
const avatar = [
'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
'https://avatars0.githubusercontent.com/u/507615?s=40&v=4',
'https://avatars1.githubusercontent.com/u/8186664?s=40&v=4',
];
const id = (Math.random() * avatar.length).toFixed();
return avatar[id];
},
group() {
const data = ['体验技术部', '创新科技组', '前端 6 组', '区块链平台部', '服务技术部'];
const id = (Math.random() * data.length).toFixed();
return data[id];
},
label() {
const label = [
'很有想法的',
'小清新',
'傻白甜',
'阳光少年',
'大咖',
'健身达人',
'程序员',
'算法工程师',
'川妹子',
'名望程序员',
'大长腿',
'海纳百川',
'专注设计',
'爱好广泛',
'IT 互联网',
];
const id = (Math.random() * label.length).toFixed();
return label[id];
},
href() {
const href = [
'https://preview.pro.ant.design/dashboard/analysis',
'https://ant.design',
'https://procomponents.ant.design/',
'https://umijs.org/',
'https://github.com/umijs/dumi',
];
const id = (Math.random() * href.length).toFixed();
return href[id];
},
});

const genMockData = (example: string) => {
if (!example) {
return {};
}

if (typeof example === 'string') {
return Mock.mock(example);
}

if (Array.isArray(example)) {
return Mock.mock(example);
}

return Object.keys(example)
.map((name) => {
return {
[name]: Mock.mock(example[name]),
};
})
.reduce((pre, next) => {
return {
...pre,
...next,
};
}, {});
};

const genByTemp = ( {
method,
path,
parameters,
status,
data,
}: {
method: string;
path: string;
parameters: { name: string, in: string, description: string, required: boolean, schema: {type: string}, example: string}[];
status: string;
data: string;
}) => {
if (!['get', 'put', 'post', 'delete', 'patch'].includes(method.toLocaleLowerCase())) {
return '';
}

let securityPath = path
parameters?.forEach(item => {
if (item.in === "path"){
securityPath = securityPath.replace(`{${item.name}}`, `:${item.name}`)
}
})

return `'${method.toUpperCase()} ${securityPath}': (req: Request, res: Response) => {
res.status(${status}).send(${data});
}`;
};

const genMockFiles = (mockFunction: string[]) => {
return prettierFile(`
// @ts-ignore
import { Request, Response } from 'express';
export default {
${mockFunction.join('\n,')}
}`)[0];
};
export type genMockDataServerConfig = { openAPI: any; mockFolder: string };

const mockGenerator = async ({ openAPI, mockFolder }: genMockDataServerConfig) => {
const openAPParse = new OpenAPIParserMock(openAPI);
const docs = openAPParse.parser();
const pathList = Object.keys(docs.paths);
const { paths } = docs;
const mockActionsObj = {};
pathList.forEach((path) => {
const pathConfig = paths[path];
Object.keys(pathConfig).forEach((method) => {
const methodConfig = pathConfig[method];
if (methodConfig) {
let conte = (
methodConfig.operationId ||
methodConfig?.tags?.join('/') ||
path.replace('/', '').split('/')[1]
)?.replace(/[^\w^\s^\u4e00-\u9fa5]/gi, '');
if (/[\u3220-\uFA29]/.test(conte)) {
conte = pinyin.convertToPinyin(conte, '', true)
}
if (!conte) {
return;
}
const data = genMockData(methodConfig.responses?.['200']?.example);
if (!mockActionsObj[conte]) {
mockActionsObj[conte] = [];
}
const tempFile = genByTemp({
method,
path,
parameters: methodConfig.parameters,
status: '200',
data: JSON.stringify(data),
});
if (tempFile) {
mockActionsObj[conte].push(tempFile);
}
}
});
});
Object.keys(mockActionsObj).forEach((file) => {
if (!file || file === 'undefined') {
return;
}
if (file.includes('/')) {
const dirName = dirname(join(mockFolder, `${file}.mock.ts`));
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName);
}
}
writeFile(mockFolder, `${file}.mock.ts`, genMockFiles(mockActionsObj[file]));
});
Log('✅ 生成 mock 文件成功');
};

export { mockGenerator };
Loading

0 comments on commit fcce4bc

Please sign in to comment.