UNPKG

@magicbe/api-generator

Version:

api 生成器

528 lines (527 loc) 27.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var path_1 = __importDefault(require("path")); var fs_1 = require("fs"); var lodash_1 = __importDefault(require("lodash")); var prettier_1 = __importDefault(require("prettier")); var GeneratorYapiV3 = /** @class */ (function () { function GeneratorYapiV3(options) { this.options = options; } GeneratorYapiV3.prototype.gen = function () { return __awaiter(this, void 0, void 0, function () { var _a, service, token, project, list_menu, res, res, _loop_1, this_1, _i, _b, menu; var _c; return __generator(this, function (_d) { switch (_d.label) { case 0: _a = this.options, service = _a.service, token = _a.token; return [4 /*yield*/, fetch("".concat(service, "/api/project/get?token=").concat(token))]; case 1: res = _d.sent(); return [4 /*yield*/, res.json()]; case 2: project = _d.sent(); return [4 /*yield*/, fetch("".concat(service, "/api/interface/list_menu?token=").concat(token, "&project_id=").concat(project.data._id))]; case 3: res = _d.sent(); return [4 /*yield*/, res.json()]; case 4: list_menu = _d.sent(); _loop_1 = function (menu) { var file_name, text, _e, _f, item, details, res, parser, prettier_options, dir; return __generator(this, function (_g) { switch (_g.label) { case 0: if (!!((_c = this_1.options.cats) === null || _c === void 0 ? void 0 : _c.length)) { if (!lodash_1.default.includes(this_1.options.cats, menu._id)) { return [2 /*return*/, "continue"]; } } file_name = "gen_api_".concat(menu._id); if (this_1.options.target === 'typescript') { file_name = file_name.concat('.ts'); } else { file_name = file_name.concat('.js'); } text = ''; // 生成文件头部 text += "import request from \"@/request\";\n\n"; text += "/**\n"; text += " * \u6587\u4EF6\u7531 gen-api \u81EA\u52A8\u751F\u6210\uFF0C\u5982\u6709\u7591\u95EE\uFF0C\u8BF7\u8054\u7CFB teacher-wang \n"; text += " * \u63A5\u53E3 [".concat(menu.name, "] ").concat(service, "/project/").concat(project.data._id, "/interface/api/cat_").concat(menu._id, "\n"); text += " */\n\n"; _e = 0, _f = menu.list; _g.label = 1; case 1: if (!(_e < _f.length)) return [3 /*break*/, 5]; item = _f[_e]; details = void 0; return [4 /*yield*/, fetch("".concat(service, "/api/interface/get?token=").concat(token, "&id=").concat(item._id))]; case 2: res = _g.sent(); return [4 /*yield*/, res.json()]; case 3: details = _g.sent(); if (this_1.options.target === 'typescript') { text += this_1.genTypescript(project.data, menu, item, details.data); } else { text += this_1.genJavascript(project.data, menu, item, details.data); } _g.label = 4; case 4: _e++; return [3 /*break*/, 1]; case 5: parser = 'babel'; if (this_1.options.target === 'typescript') parser = 'babel-ts'; prettier_options = { parser: parser, singleQuote: true, trailingComma: 'es5', tabWidth: 4, semi: true, printWidth: 2000, }; return [4 /*yield*/, prettier_1.default.format(text, prettier_options)]; case 6: text = _g.sent(); return [4 /*yield*/, GeneratorYapiV3.mkDir(this_1.options.output)]; case 7: dir = _g.sent(); (0, fs_1.writeFile)(path_1.default.join(dir, file_name), text, function (err) { if (!err) console.log("".concat(dir, "/").concat(file_name, " \u751F\u6210\u6210\u529F!")); else console.error("".concat(dir, "/").concat(file_name, " \u751F\u6210\u5931\u8D25!")); }); return [2 /*return*/]; } }); }; this_1 = this; _i = 0, _b = list_menu.data; _d.label = 5; case 5: if (!(_i < _b.length)) return [3 /*break*/, 8]; menu = _b[_i]; return [5 /*yield**/, _loop_1(menu)]; case 6: _d.sent(); _d.label = 7; case 7: _i++; return [3 /*break*/, 5]; case 8: return [2 /*return*/]; } }); }); }; GeneratorYapiV3.prototype.genJavascript = function (project, menu, item, detail) { var content = ''; // 生成函数名称 var functionName = this.getFunctionName(detail.title, detail.method, detail.path); // 解析请求参数 var queryParams = this.parseQueryParams(detail.req_query); var pathParams = this.parsePathParams(detail.req_params); var bodyParamsResult = this.parseBodyParams(detail.req_body_other, detail.req_body_type, functionName); var bodyParams = bodyParamsResult.params; // 生成 JSDoc 注释 content += "/** \n"; content += " * ".concat(detail.title, " \n"); content += " * ".concat(this.options.service, "/project/").concat(project._id, "/interface/api/").concat(item._id, " \n"); content += " */\n"; // 生成函数 content += "export const ".concat(functionName, " = ("); var functionParams = []; if (pathParams.length > 0) { functionParams.push('params'); } if (queryParams.length > 0) { functionParams.push('query'); } if (bodyParams.length > 0) { functionParams.push('body'); } content += functionParams.join(', '); content += ") => request.".concat(detail.method.toLowerCase(), "("); // 构建 URL var url = this.buildUrl(detail.path, pathParams); content += "`".concat(url, "`"); if (bodyParams.length > 0) { content += ", body"; } if (queryParams.length > 0) { content += ", { params: query }"; } content += ");\n\n"; return content; }; GeneratorYapiV3.prototype.genTypescript = function (project, menu, item, detail) { var content = ''; var allInterfaces = []; // 收集所有嵌套接口 // 生成函数名称和基础接口名称 var functionName = this.getFunctionName(detail.title, detail.method, detail.path); var baseInterfaceName = functionName.charAt(0).toUpperCase() + functionName.slice(1); // 解析请求参数 var queryParams = this.parseQueryParams(detail.req_query); var pathParams = this.parsePathParams(detail.req_params); var bodyParamsResult = this.parseBodyParams(detail.req_body_other, detail.req_body_type, baseInterfaceName); var bodyParams = bodyParamsResult.params; var bodyInterfaces = bodyParamsResult.interfaces; // 收集所有嵌套接口 allInterfaces.push.apply(allInterfaces, bodyInterfaces); // 生成请求参数接口 if (queryParams.length > 0) { var queryInterfaceName = this.getInterfaceName(baseInterfaceName, 'Query'); content += "/** ".concat(detail.title, " \u8BF7\u6C42\u53C2\u6570 */\n"); content += "export interface ".concat(queryInterfaceName, " {\n"); queryParams.forEach(function (param) { content += "\t".concat(param.name).concat(param.required ? '' : '?', ": ").concat(param.type, ";\n"); }); content += "}\n\n"; } if (pathParams.length > 0) { var paramsInterfaceName = this.getInterfaceName(baseInterfaceName, 'Params'); content += "/** ".concat(detail.title, " \u8BF7\u6C42\u8DEF\u5F84\u53C2\u6570 */\n"); content += "export interface ".concat(paramsInterfaceName, " {\n"); pathParams.forEach(function (param) { content += "\t/** ".concat(param.desc, " */\n"); content += "\t".concat(param.name, ": ").concat(param.type, ";\n"); }); content += "}\n\n"; } // 解析响应体 var responseInterface = this.parseResponseBody(detail.res_body, detail.res_body_type, baseInterfaceName); if (responseInterface) { // 收集响应体的嵌套接口 allInterfaces.push.apply(allInterfaces, responseInterface.interfaces); } // 首先生成所有嵌套接口 if (allInterfaces.length > 0) { content += allInterfaces.join(''); } // 然后生成主接口 if (bodyParams.length > 0) { var bodyInterfaceName = this.getInterfaceName(baseInterfaceName, 'Body'); content += "/** ".concat(detail.title, " \u8BF7\u6C42\u4E3B\u4F53 */\n"); content += "export interface ".concat(bodyInterfaceName, " {\n"); bodyParams.forEach(function (param) { if (param.desc) { content += "\t/** ".concat(param.desc, " */\n"); } content += "\t".concat(param.name).concat(param.required ? '' : '?', ": ").concat(param.type, ";\n"); }); content += "}\n\n"; } if (responseInterface) { content += "/** ".concat(detail.title, " \u54CD\u5E94\u4E3B\u4F53 */\n"); content += "export interface ".concat(responseInterface.name, " {\n"); responseInterface.properties.forEach(function (prop) { content += "\t".concat(prop.name).concat(prop.required ? '' : '?', ": ").concat(prop.type, ";\n"); }); content += "}\n\n"; } // 生成请求函数 var url = this.buildUrl(detail.path, pathParams); content += "/** \n"; content += " * ".concat(detail.title, " \n"); content += " * ".concat(this.options.service, "/project/").concat(project._id, "/interface/api/").concat(item._id, " \n"); content += " */\n"; content += "export const ".concat(functionName, " = ("); var functionParams = []; if (pathParams.length > 0) { functionParams.push("params: ".concat(this.getInterfaceName(baseInterfaceName, 'Params'))); } if (queryParams.length > 0) { functionParams.push("query: ".concat(this.getInterfaceName(baseInterfaceName, 'Query'))); } if (bodyParams.length > 0) { functionParams.push("body: ".concat(this.getInterfaceName(baseInterfaceName, 'Body'))); } content += functionParams.join(', '); content += ") => request.".concat(detail.method.toLowerCase(), "<").concat((responseInterface === null || responseInterface === void 0 ? void 0 : responseInterface.name) || 'any', ">(`").concat(url, "`"); if (bodyParams.length > 0) { content += ", body"; } if (queryParams.length > 0) { content += ", { params: query }"; } content += ");\n\n"; return content; }; GeneratorYapiV3.prototype.parseQueryParams = function (reqQuery) { return reqQuery.map(function (param) { return ({ name: param.name, type: 'string', // 默认类型,可以根据实际需要调整 required: param.required === '1', desc: param.desc }); }); }; GeneratorYapiV3.prototype.parsePathParams = function (reqParams) { return reqParams.map(function (param) { return ({ name: param.name, type: 'string', // 默认类型,可以根据实际需要调整 required: param.required === '1', desc: param.desc }); }); }; GeneratorYapiV3.prototype.parseBodyParams = function (reqBodyOther, reqBodyType, functionName) { var _this = this; if (!reqBodyOther || reqBodyType !== 'json') { return { params: [], interfaces: [] }; } try { var bodySchema_1 = JSON.parse(reqBodyOther); var interfaces_1 = []; if (bodySchema_1.properties) { // 生成请求主体接口名称 var bodyInterfaceName_1 = this.getInterfaceName(functionName, 'Body'); var params = Object.entries(bodySchema_1.properties).map(function (_a) { var _b; var name = _a[0], prop = _a[1]; // 使用请求主体接口名称作为父级接口名称 var typeInfo = _this.getTypeFromSchema(prop, bodyInterfaceName_1, interfaces_1, name); return { name: name, type: typeInfo.type, required: ((_b = bodySchema_1.required) === null || _b === void 0 ? void 0 : _b.includes(name)) || false, desc: prop.description || '' }; }); return { params: params, interfaces: interfaces_1 }; } } catch (e) { // 解析失败,返回空数组 } return { params: [], interfaces: [] }; }; GeneratorYapiV3.prototype.parseResponseBody = function (resBody, resBodyType, functionName) { var _this = this; if (!resBody || resBodyType !== 'json') { return null; } try { var responseSchema_1 = JSON.parse(resBody); var interfaces_2 = []; if (responseSchema_1.properties) { // 先确定响应接口的名称 var responseInterfaceName_1 = this.getInterfaceName(functionName, 'Response'); var properties = Object.entries(responseSchema_1.properties).map(function (_a) { var _b; var name = _a[0], prop = _a[1]; // 对于响应体的第一层属性,使用响应接口名称作为父级接口名称 var typeInfo = _this.getTypeFromSchema(prop, responseInterfaceName_1, interfaces_2, name); return { name: name, type: typeInfo.type, required: ((_b = responseSchema_1.required) === null || _b === void 0 ? void 0 : _b.includes(name)) || false }; }); return { name: responseInterfaceName_1, properties: properties, interfaces: interfaces_2 }; } } catch (e) { // 解析失败,返回 null console.error(e); } return null; }; GeneratorYapiV3.prototype.getTypeFromSchema = function (prop, parentInterfaceName, interfaces, keyName) { if (prop.type === 'string') return { type: 'string', interfaces: [] }; if (prop.type === 'number') return { type: 'number', interfaces: [] }; if (prop.type === 'boolean') return { type: 'boolean', interfaces: [] }; if (prop.type === 'integer') return { type: 'number', interfaces: [] }; if (prop.type === 'array') { if (prop.items) { var itemTypeInfo = this.getTypeFromSchema(prop.items, parentInterfaceName, interfaces, keyName); return { type: "".concat(itemTypeInfo.type, "[]"), interfaces: __spreadArray(__spreadArray([], interfaces, true), itemTypeInfo.interfaces, true) }; } return { type: 'any[]', interfaces: [] }; } if (prop.type === 'object') { if (prop.properties) { // 生成嵌套接口,优先使用key名称 var interfaceName = this.generateNestedInterfaceName(parentInterfaceName, prop, keyName); // 生成嵌套接口时,使用新生成的接口名称作为父接口名称,用于进一步的嵌套 var nestedInterfaces = this.generateNestedInterface(interfaceName, prop, interfaceName); // 将嵌套接口添加到传入的interfaces数组中 interfaces.push.apply(interfaces, nestedInterfaces); return { type: interfaceName, interfaces: interfaces }; } return { type: 'any', interfaces: [] }; } return { type: 'any', interfaces: [] }; }; GeneratorYapiV3.prototype.generateNestedInterfaceName = function (parentInterfaceName, prop, keyName) { // 优先使用key名称,如果没有则使用父级接口名称+后缀 if (keyName) { // 将key名称转换为驼峰命名(处理下划线、连字符等) var camelCaseKeyName = keyName.replace(/[-_.]/g, ' ') .split(' ') .filter(function (word) { return word.length > 0; }) .map(function (word) { return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); }) .join(''); // 使用父级接口名称 + 驼峰命名后的key名称 return "".concat(parentInterfaceName).concat(camelCaseKeyName); } // 如果没有key名称,使用父级接口名称+后缀 var timestamp = Date.now().toString().slice(-6); return "".concat(parentInterfaceName, "Nested").concat(timestamp); }; GeneratorYapiV3.prototype.generateNestedInterface = function (interfaceName, prop, parentInterfaceName) { var _this = this; var interfaces = []; var interfaceContent = "export interface ".concat(interfaceName, " {\n"); if (prop.properties) { Object.entries(prop.properties).forEach(function (_a) { var _b; var name = _a[0], nestedProp = _a[1]; var typeInfo = _this.getTypeFromSchema(nestedProp, parentInterfaceName, interfaces, name); var required = ((_b = prop.required) === null || _b === void 0 ? void 0 : _b.includes(name)) || false; var desc = nestedProp.description || ''; // 处理属性名,如果以数字开头,则使用引号包裹 var validPropertyName = /^[0-9]/.test(name) ? "\"".concat(name, "\"") : name; if (desc) { interfaceContent += "\t/** ".concat(desc, " */\n"); } interfaceContent += "\t".concat(validPropertyName).concat(required ? '' : '?', ": ").concat(typeInfo.type, ";\n"); }); } interfaceContent += "}\n\n"; interfaces.push(interfaceContent); return interfaces; }; GeneratorYapiV3.prototype.getInterfaceName = function (parentInterfaceName, suffix) { // 基于父级接口名称生成子接口名称 return "".concat(parentInterfaceName).concat(suffix); }; GeneratorYapiV3.prototype.getFunctionName = function (title, method, path) { var methodPrefix = method.toLowerCase(); // 将路径转换为驼峰命名,处理空格、连字符、下划线、点号等 var pathParts = path.split('/').filter(function (part) { return part && !part.startsWith('{'); }); var pathCamelCase = pathParts.map(function (part) { // 替换连字符、下划线、点号为空格,然后转换为驼峰命名 return part.replace(/[-_.]/g, ' ') .split(' ') .filter(function (word) { return word.length > 0; }) .map(function (word) { return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); }) .join(''); }).join(''); // 提取路径参数名并转换为驼峰命名 var pathParams = path.split('/') .filter(function (part) { return part.startsWith('{') && part.endsWith('}'); }) .map(function (part) { var paramName = part.slice(1, -1); // 移除 { 和 } // 将参数名转换为驼峰命名(处理下划线等) return paramName.replace(/[-_.]/g, ' ') .split(' ') .filter(function (word) { return word.length > 0; }) .map(function (word) { return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); }) .join(''); }); // 将路径参数名添加到函数名末尾 var pathParamSuffix = pathParams.join(''); return "".concat(methodPrefix).concat(pathCamelCase).concat(pathParamSuffix); }; GeneratorYapiV3.prototype.buildUrl = function (path, pathParams) { var url = path; pathParams.forEach(function (param) { url = url.replace("{".concat(param.name, "}"), "${params.".concat(param.name, "}")); }); return url; }; /** * 创建目录 */ GeneratorYapiV3.mkDir = function (output, _path_) { return new Promise(function (resolve, reject) { var dir = path_1.default.join.apply(path_1.default, lodash_1.default.compact([output, _path_])); (0, fs_1.access)(dir, fs_1.constants.F_OK, function (err) { if (err) { (0, fs_1.mkdir)(dir, { recursive: true }, function (err) { if (err) { reject(err); console.error(err); } else { resolve(dir); console.log("".concat(output, " \u521B\u5EFA\u76EE\u5F55\u6210\u529F")); } }); } else { resolve(dir); } }); }); }; return GeneratorYapiV3; }()); exports.default = GeneratorYapiV3;