yapi-to-typescript
Version:
根据 YApi 的接口定义生成 TypeScript/JavaScript 的接口类型及其请求函数代码。
766 lines (617 loc) • 22.9 kB
JavaScript
;
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);
}