UNPKG

yapi-to-typescript

Version:

根据 YApi 的接口定义生成 TypeScript/JavaScript 的接口类型及其请求函数代码。

766 lines (617 loc) 22.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; exports.__esModule = true; exports.getCachedPrettierOptions = void 0; exports.getNormalizedRelativePath = getNormalizedRelativePath; exports.getPrettier = getPrettier; exports.getPrettierOptions = getPrettierOptions; exports.getRequestDataJsonSchema = getRequestDataJsonSchema; exports.getResponseDataJsonSchema = getResponseDataJsonSchema; exports.httpGet = httpGet; exports.isGetLikeMethod = isGetLikeMethod; exports.isPostLikeMethod = isPostLikeMethod; exports.jsonSchemaStringToJsonSchema = jsonSchemaStringToJsonSchema; exports.jsonSchemaToJSTTJsonSchema = jsonSchemaToJSTTJsonSchema; exports.jsonSchemaToType = jsonSchemaToType; exports.jsonToJsonSchema = jsonToJsonSchema; exports.mockjsTemplateToJsonSchema = mockjsTemplateToJsonSchema; exports.processJsonSchema = processJsonSchema; exports.propDefinitionsToJsonSchema = propDefinitionsToJsonSchema; exports.reachJsonSchema = reachJsonSchema; exports.sortByWeights = sortByWeights; exports.throwError = throwError; exports.toUnixPath = toUnixPath; exports.traverseJsonSchema = traverseJsonSchema; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _createForOfIteratorHelperLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/createForOfIteratorHelperLoose")); var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _fsExtra = _interopRequireDefault(require("fs-extra")); var _json = _interopRequireDefault(require("json5")); var _nodeFetch = _interopRequireDefault(require("node-fetch")); var _path = _interopRequireDefault(require("path")); var _prettier = _interopRequireDefault(require("prettier")); var _proxyAgent = _interopRequireDefault(require("proxy-agent")); var _toJsonSchema = _interopRequireDefault(require("to-json-schema")); var _vtils = require("vtils"); var _jsonSchemaToTypescript = require("json-schema-to-typescript"); var _helpers = require("./helpers"); var _types = require("./types"); var _url2 = require("url"); /** * 抛出错误。 * * @param msg 错误信息 */ function throwError() { for (var _len = arguments.length, msg = new Array(_len), _key = 0; _key < _len; _key++) { msg[_key] = arguments[_key]; } /* istanbul ignore next */ throw new Error(msg.join('')); } /** * 将路径统一为 unix 风格的路径。 * * @param path 路径 * @returns unix 风格的路径 */ function toUnixPath(path) { return path.replace(/[/\\]+/g, '/'); } /** * 获得规范化的相对路径。 * * @param from 来源路径 * @param to 去向路径 * @returns 相对路径 */ function getNormalizedRelativePath(from, to) { return toUnixPath(_path.default.relative(_path.default.dirname(from), to)).replace(/^(?=[^.])/, './').replace(/\.(ts|js)x?$/i, ''); } /** * 原地遍历 JSONSchema。 */ function traverseJsonSchema(jsonSchema, cb, currentPath) { if (currentPath === void 0) { currentPath = []; } /* istanbul ignore if */ if (!(0, _vtils.isObject)(jsonSchema)) return jsonSchema; // Mock.toJSONSchema 产生的 properties 为数组,然而 JSONSchema4 的 properties 为对象 if ((0, _vtils.isArray)(jsonSchema.properties)) { jsonSchema.properties = jsonSchema.properties.reduce(function (props, js) { props[js.name] = js; return props; }, {}); } // 处理传入的 JSONSchema cb(jsonSchema, currentPath); // 继续处理对象的子元素 if (jsonSchema.properties) { (0, _vtils.forOwn)(jsonSchema.properties, function (item, key) { return traverseJsonSchema(item, cb, [].concat(currentPath, [key])); }); } // 继续处理数组的子元素 if (jsonSchema.items) { (0, _vtils.castArray)(jsonSchema.items).forEach(function (item, index) { return traverseJsonSchema(item, cb, [].concat(currentPath, [index])); }); } // 处理 oneOf if (jsonSchema.oneOf) { jsonSchema.oneOf.forEach(function (item) { return traverseJsonSchema(item, cb, currentPath); }); } // 处理 anyOf if (jsonSchema.anyOf) { jsonSchema.anyOf.forEach(function (item) { return traverseJsonSchema(item, cb, currentPath); }); } // 处理 allOf if (jsonSchema.allOf) { jsonSchema.allOf.forEach(function (item) { return traverseJsonSchema(item, cb, currentPath); }); } return jsonSchema; } /** * 原地处理 JSONSchema。 * * @param jsonSchema 待处理的 JSONSchema * @returns 处理后的 JSONSchema */ function processJsonSchema(jsonSchema, customTypeMapping) { return traverseJsonSchema(jsonSchema, function (jsonSchema) { // 删除通过 swagger 导入时未剔除的 ref delete jsonSchema.$ref; delete jsonSchema.$$ref; // 数组只取第一个判断类型 if (jsonSchema.type === 'array' && Array.isArray(jsonSchema.items) && jsonSchema.items.length) { jsonSchema.items = jsonSchema.items[0]; } // 处理类型名称为标准的 JSONSchema 类型名称 if (jsonSchema.type) { // 类型映射表,键都为小写 var typeMapping = (0, _extends2.default)({ byte: 'integer', short: 'integer', int: 'integer', long: 'integer', float: 'number', double: 'number', bigdecimal: 'number', char: 'string', void: 'null' }, (0, _vtils.mapKeys)(customTypeMapping, function (_, key) { return key.toLowerCase(); })); var isMultiple = Array.isArray(jsonSchema.type); var types = (0, _vtils.castArray)(jsonSchema.type).map(function (type) { // 所有类型转成小写,如:String -> string type = type.toLowerCase(); // 映射为标准的 JSONSchema 类型 type = typeMapping[type] || type; return type; }); jsonSchema.type = isMultiple ? types : types[0]; } // 移除字段名称首尾空格 if (jsonSchema.properties) { (0, _vtils.forOwn)(jsonSchema.properties, function (_, prop) { var propDef = jsonSchema.properties[prop]; delete jsonSchema.properties[prop]; jsonSchema.properties[prop.trim()] = propDef; }); if (Array.isArray(jsonSchema.required)) { jsonSchema.required = jsonSchema.required.map(function (prop) { return prop.trim(); }); } } return jsonSchema; }); } /** * 获取适用于 JSTT 的 JSONSchema。 * * @param jsonSchema 待处理的 JSONSchema * @returns 适用于 JSTT 的 JSONSchema */ function jsonSchemaToJSTTJsonSchema(jsonSchema, typeName) { if (jsonSchema) { // 去除最外层的 description 以防止 JSTT 提取它作为类型的注释 delete jsonSchema.description; } return traverseJsonSchema(jsonSchema, function (jsonSchema, currentPath) { // 支持类型引用 var refValue = // YApi 低版本不支持配置 title,可以在 description 里配置 jsonSchema.title == null ? jsonSchema.description : jsonSchema.title; if (refValue != null && refValue.startsWith('&')) { var typeRelativePath = refValue.substring(1); var typeAbsolutePath = toUnixPath(_path.default.resolve(_path.default.dirname(("/" + currentPath.join('/')).replace(/\/{2,}/g, '/')), typeRelativePath).replace(/^[a-z]+:/i, '')); var typeAbsolutePathArr = typeAbsolutePath.split('/').filter(Boolean); var tsTypeLeft = ''; var tsTypeRight = typeName; for (var _iterator = (0, _createForOfIteratorHelperLoose2.default)(typeAbsolutePathArr), _step; !(_step = _iterator()).done;) { var key = _step.value; tsTypeLeft += 'NonNullable<'; tsTypeRight += "[" + JSON.stringify(key) + "]>"; } var tsType = "" + tsTypeLeft + tsTypeRight; jsonSchema.tsType = tsType; } // 去除 title 和 id,防止 json-schema-to-typescript 提取它们作为接口名 delete jsonSchema.title; delete jsonSchema.id; // 忽略数组长度限制 delete jsonSchema.minItems; delete jsonSchema.maxItems; if (jsonSchema.type === 'object') { // 将 additionalProperties 设为 false jsonSchema.additionalProperties = false; } // 删除 default,防止 json-schema-to-typescript 根据它推测类型 delete jsonSchema.default; return jsonSchema; }); } /** * 将 JSONSchema 字符串转为 JSONSchema 对象。 * * @param str 要转换的 JSONSchema 字符串 * @returns 转换后的 JSONSchema 对象 */ function jsonSchemaStringToJsonSchema(str, customTypeMapping) { return processJsonSchema(JSON.parse(str), customTypeMapping); } /** * 获得 JSON 数据的 JSONSchema 对象。 * * @param json JSON 数据 * @returns JSONSchema 对象 */ function jsonToJsonSchema(json, customTypeMapping) { var schema = (0, _toJsonSchema.default)(json, { required: false, arrays: { mode: 'first' }, objects: { additionalProperties: false }, strings: { detectFormat: false }, postProcessFnc: function postProcessFnc(type, schema, value) { if (!schema.description && !!value && type !== 'object') { schema.description = JSON.stringify(value); } return schema; } }); delete schema.description; return processJsonSchema(schema, customTypeMapping); } /** * 获得 mockjs 模板的 JSONSchema 对象。 * * @param template mockjs 模板 * @returns JSONSchema 对象 */ function mockjsTemplateToJsonSchema(template, customTypeMapping) { var actions = []; // https://github.com/nuysoft/Mock/blob/refactoring/src/mock/constant.js#L27 var keyRe = /(.+)\|(?:\+(\d+)|([+-]?\d+-?[+-]?\d*)?(?:\.(\d+-?\d*))?)/; // https://github.com/nuysoft/Mock/wiki/Mock.Random var numberPatterns = ['natural', 'integer', 'float', 'range', 'increment']; var boolPatterns = ['boolean', 'bool']; var normalizeValue = function normalizeValue(value) { if (typeof value === 'string' && value.startsWith('@')) { var pattern = value.slice(1); if (numberPatterns.some(function (p) { return pattern.startsWith(p); })) { return 1; } if (boolPatterns.some(function (p) { return pattern.startsWith(p); })) { return true; } } return value; }; (0, _vtils.traverse)(template, function (value, key, parent) { if (typeof key === 'string') { actions.push(function () { delete parent[key]; parent[// https://github.com/nuysoft/Mock/blob/refactoring/src/mock/schema/schema.js#L16 key.replace(keyRe, '$1')] = normalizeValue(value); }); } }); actions.forEach(function (action) { return action(); }); return jsonToJsonSchema(template, customTypeMapping); } /** * 获得属性定义列表的 JSONSchema 对象。 * * @param propDefinitions 属性定义列表 * @returns JSONSchema 对象 */ function propDefinitionsToJsonSchema(propDefinitions, customTypeMapping) { return processJsonSchema({ type: 'object', required: propDefinitions.reduce(function (res, prop) { if (prop.required) { res.push(prop.name); } return res; }, []), properties: propDefinitions.reduce(function (res, prop) { res[prop.name] = (0, _extends2.default)({ type: prop.type, description: prop.comment }, prop.type === 'file' ? { tsType: _helpers.FileData.name } : {}); return res; }, {}) }, customTypeMapping); } var JSTTOptions = { bannerComment: '', style: { bracketSpacing: false, printWidth: 120, semi: true, singleQuote: true, tabWidth: 2, trailingComma: 'none', useTabs: false } }; /** * 根据 JSONSchema 对象生产 TypeScript 类型定义。 * * @param jsonSchema JSONSchema 对象 * @param typeName 类型名称 * @returns TypeScript 类型定义 */ function jsonSchemaToType(_x, _x2) { return _jsonSchemaToType.apply(this, arguments); } function _jsonSchemaToType() { _jsonSchemaToType = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(jsonSchema, typeName) { var fakeTypeName, code; return _regenerator.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (!(0, _vtils.isEmpty)(jsonSchema)) { _context.next = 2; break; } return _context.abrupt("return", "export interface " + typeName + " {}"); case 2: if (!jsonSchema.__is_any__) { _context.next = 5; break; } delete jsonSchema.__is_any__; return _context.abrupt("return", "export type " + typeName + " = any"); case 5: // JSTT 会转换 typeName,因此传入一个全大写的假 typeName,生成代码后再替换回真正的 typeName fakeTypeName = 'THISISAFAKETYPENAME'; _context.next = 8; return (0, _jsonSchemaToTypescript.compile)(jsonSchemaToJSTTJsonSchema((0, _vtils.cloneDeepFast)(jsonSchema), typeName), fakeTypeName, JSTTOptions); case 8: code = _context.sent; return _context.abrupt("return", code.replace(fakeTypeName, typeName).trim()); case 10: case "end": return _context.stop(); } } }, _callee); })); return _jsonSchemaToType.apply(this, arguments); } function getRequestDataJsonSchema(interfaceInfo, customTypeMapping) { var jsonSchema; // 处理表单数据(仅 POST 类接口) if (isPostLikeMethod(interfaceInfo.method)) { switch (interfaceInfo.req_body_type) { case _types.RequestBodyType.form: jsonSchema = propDefinitionsToJsonSchema(interfaceInfo.req_body_form.map(function (item) { return { name: item.name, required: item.required === _types.Required.true, type: item.type === _types.RequestFormItemType.file ? 'file' : 'string', comment: item.desc }; }), customTypeMapping); break; case _types.RequestBodyType.json: if (interfaceInfo.req_body_other) { jsonSchema = interfaceInfo.req_body_is_json_schema ? jsonSchemaStringToJsonSchema(interfaceInfo.req_body_other, customTypeMapping) : jsonToJsonSchema(_json.default.parse(interfaceInfo.req_body_other), customTypeMapping); } break; default: /* istanbul ignore next */ break; } } // 处理查询数据 if ((0, _vtils.isArray)(interfaceInfo.req_query) && interfaceInfo.req_query.length) { var queryJsonSchema = propDefinitionsToJsonSchema(interfaceInfo.req_query.map(function (item) { return { name: item.name, required: item.required === _types.Required.true, type: item.type || 'string', comment: item.desc }; }), customTypeMapping); /* istanbul ignore else */ if (jsonSchema) { jsonSchema.properties = (0, _extends2.default)({}, jsonSchema.properties, queryJsonSchema.properties); jsonSchema.required = [].concat(Array.isArray(jsonSchema.required) ? jsonSchema.required : [], Array.isArray(queryJsonSchema.required) ? queryJsonSchema.required : []); } else { jsonSchema = queryJsonSchema; } } // 处理路径参数 if ((0, _vtils.isArray)(interfaceInfo.req_params) && interfaceInfo.req_params.length) { var paramsJsonSchema = propDefinitionsToJsonSchema(interfaceInfo.req_params.map(function (item) { return { name: item.name, required: true, type: item.type || 'string', comment: item.desc }; }), customTypeMapping); /* istanbul ignore else */ if (jsonSchema) { jsonSchema.properties = (0, _extends2.default)({}, jsonSchema.properties, paramsJsonSchema.properties); jsonSchema.required = [].concat(Array.isArray(jsonSchema.required) ? jsonSchema.required : [], Array.isArray(paramsJsonSchema.required) ? paramsJsonSchema.required : []); } else { jsonSchema = paramsJsonSchema; } } return jsonSchema || {}; } function getResponseDataJsonSchema(interfaceInfo, customTypeMapping, dataKey) { var jsonSchema = {}; switch (interfaceInfo.res_body_type) { case _types.ResponseBodyType.json: if (interfaceInfo.res_body) { jsonSchema = interfaceInfo.res_body_is_json_schema ? jsonSchemaStringToJsonSchema(interfaceInfo.res_body, customTypeMapping) : mockjsTemplateToJsonSchema(_json.default.parse(interfaceInfo.res_body), customTypeMapping); } break; default: jsonSchema = { __is_any__: true }; break; } if (dataKey && jsonSchema) { jsonSchema = reachJsonSchema(jsonSchema, dataKey); } return jsonSchema; } function reachJsonSchema(jsonSchema, path) { var last = jsonSchema; for (var _iterator2 = (0, _createForOfIteratorHelperLoose2.default)((0, _vtils.castArray)(path)), _step2; !(_step2 = _iterator2()).done;) { var _last$properties; var segment = _step2.value; var _last = (_last$properties = last.properties) == null ? void 0 : _last$properties[segment]; if (!_last) { return jsonSchema; } last = _last; } return last; } function sortByWeights(list) { list.sort(function (a, b) { var _x$weights; var x = a.weights.length > b.weights.length ? b : a; var minLen = Math.min(a.weights.length, b.weights.length); var maxLen = Math.max(a.weights.length, b.weights.length); (_x$weights = x.weights).push.apply(_x$weights, new Array(maxLen - minLen).fill(0)); var w = a.weights.reduce(function (w, _, i) { if (w === 0) { w = a.weights[i] - b.weights[i]; } return w; }, 0); return w; }); return list; } function isGetLikeMethod(method) { return method === _types.Method.GET || method === _types.Method.OPTIONS || method === _types.Method.HEAD; } function isPostLikeMethod(method) { return !isGetLikeMethod(method); } function getPrettier(_x3) { return _getPrettier.apply(this, arguments); } function _getPrettier() { _getPrettier = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(cwd) { var projectPrettierPath; return _regenerator.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: projectPrettierPath = _path.default.join(cwd, 'node_modules/prettier'); _context2.next = 3; return _fsExtra.default.pathExists(projectPrettierPath); case 3: if (!_context2.sent) { _context2.next = 5; break; } return _context2.abrupt("return", require(projectPrettierPath)); case 5: return _context2.abrupt("return", require('prettier')); case 6: case "end": return _context2.stop(); } } }, _callee2); })); return _getPrettier.apply(this, arguments); } function getPrettierOptions() { return _getPrettierOptions.apply(this, arguments); } function _getPrettierOptions() { _getPrettierOptions = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3() { var prettierOptions, _yield$run, prettierConfigPathErr, prettierConfigPath, _yield$run2, prettierConfigErr, prettierConfig; return _regenerator.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: prettierOptions = { parser: 'typescript', printWidth: 120, tabWidth: 2, singleQuote: true, semi: false, trailingComma: 'all', bracketSpacing: false, endOfLine: 'lf' }; // 测试时跳过本地配置的解析 if (!process.env.JEST_WORKER_ID) { _context3.next = 3; break; } return _context3.abrupt("return", prettierOptions); case 3: _context3.next = 5; return (0, _vtils.run)(function () { return _prettier.default.resolveConfigFile(); }); case 5: _yield$run = _context3.sent; prettierConfigPathErr = _yield$run[0]; prettierConfigPath = _yield$run[1]; if (!(prettierConfigPathErr || !prettierConfigPath)) { _context3.next = 10; break; } return _context3.abrupt("return", prettierOptions); case 10: _context3.next = 12; return (0, _vtils.run)(function () { return _prettier.default.resolveConfig(prettierConfigPath); }); case 12: _yield$run2 = _context3.sent; prettierConfigErr = _yield$run2[0]; prettierConfig = _yield$run2[1]; if (!(prettierConfigErr || !prettierConfig)) { _context3.next = 17; break; } return _context3.abrupt("return", prettierOptions); case 17: return _context3.abrupt("return", (0, _extends2.default)({}, prettierOptions, prettierConfig, { parser: 'typescript' })); case 18: case "end": return _context3.stop(); } } }, _callee3); })); return _getPrettierOptions.apply(this, arguments); } var getCachedPrettierOptions = (0, _vtils.memoize)(getPrettierOptions); exports.getCachedPrettierOptions = getCachedPrettierOptions; function httpGet(_x4, _x5) { return _httpGet.apply(this, arguments); } function _httpGet() { _httpGet = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4(url, query) { var _url, res; return _regenerator.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: _url = new _url2.URL(url); if (query) { Object.keys(query).forEach(function (key) { _url.searchParams.set(key, query[key]); }); } url = _url.toString(); _context4.next = 5; return (0, _nodeFetch.default)(url, { method: 'GET', agent: new _proxyAgent.default() }); case 5: res = _context4.sent; return _context4.abrupt("return", res.json()); case 7: case "end": return _context4.stop(); } } }, _callee4); })); return _httpGet.apply(this, arguments); }