auto-request
Version:
通过Yapi JSON Schema生成接口Axios或Taro接口
1,480 lines (1,464 loc) • 529 kB
JavaScript
'use strict';
var fs = require('fs');
var prettier = require('prettier');
var path = require('path');
var parser = require('@babel/parser');
var traverse = require('@babel/traverse');
var jsonSchemaToTypescript = require('json-schema-to-typescript');
var readline = require('readline');
var require$$1 = require('util');
var stream = require('stream');
var require$$3 = require('http');
var require$$4 = require('https');
var require$$0$1 = require('url');
var require$$8 = require('crypto');
var http2 = require('http2');
var require$$4$1 = require('assert');
var require$$0$2 = require('tty');
var zlib = require('zlib');
var events = require('events');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
var prettier__namespace = /*#__PURE__*/_interopNamespaceDefault(prettier);
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
var ErrorType;
(function (ErrorType) {
ErrorType["PathParams"] = "path-total-error";
ErrorType["SchemaError"] = "schema-error";
ErrorType["PathError"] = "path-error";
ErrorType["Debug"] = "debug";
})(ErrorType || (ErrorType = {}));
const ErrorTypeDesc = {
[ErrorType.PathParams]: '路径参数数量匹配不上文档',
[ErrorType.SchemaError]: '接口参数解析出问题',
[ErrorType.PathError]: '路径参数类型没写为path',
[ErrorType.Debug]: 'debug数据',
};
class Store {
constructor() {
this.errorUrl = [];
this.isTypeScript = true;
this.isJsDoc = false;
this.errorPath = '';
this.filename = '';
this.logFilename = '';
}
pushError(data) {
this.errorUrl.push(data);
}
print() {
const errTypeHash = this.errorUrl.reduce((pre, next) => {
if (!pre[next.errorType]) {
pre[next.errorType] = [];
}
pre[next.errorType].push({
...next,
errorType: ErrorTypeDesc[next.errorType],
});
return pre;
}, {});
return JSON.stringify(errTypeHash);
}
getFileType() {
if (this.isTypeScript) {
return '.ts';
}
return '.js';
}
}
const store = new Store();
var MethodsType;
(function (MethodsType) {
MethodsType["Get"] = "get";
MethodsType["Post"] = "post";
MethodsType["Delete"] = "delete";
MethodsType["Put"] = "put";
})(MethodsType || (MethodsType = {}));
const initArray = (arrs) => {
if (Array.isArray(arrs)) {
return arrs;
}
return [];
};
const camelcase = require('camelcase');
const pascalCase = (str) => {
return camelcase(str, { pascalCase: true });
};
const clone$1 = require('clone');
const pascalCaseReplaceString = (str, pascalCase = true) => {
// 移除路径中的特殊字符,包括冒号(用于路由命名空间如 session:encrypt)
return camelcase(str.replace(/[\/|\{|\}|\?|\$|:]/g, ''), { pascalCase });
};
const renderMethodArgs = (args) => {
return initArray(args)
.filter((item) => item)
.join(',');
};
/**
* 标准化并格式化 URL
* 1. 移除路径参数中的正则表达式 {userid:\d+} -> {userid}
* 2. 将路径参数转换为模板字符串格式 {userid} -> ${userid}
*/
const normalizeUrl = (parameters = [], url) => {
// 从 parameters 中提取所有可能的路径参数名
const pathParamNames = new Set();
// 优先从 in: "path" 的参数中提取
parameters.forEach(param => {
if (param.in === 'path' && param.name) {
pathParamNames.add(param.name);
}
});
// 容错处理:如果 parameters 中有参数名匹配 URL 中的 {xxx} 格式,也认为是路径参数
const urlParamPattern = /\{([^:}]+)(?::[^}]*)?\}/g;
let match;
const urlCopy = url; // 保存原始URL用于匹配
while ((match = urlParamPattern.exec(urlCopy)) !== null) {
const paramName = match[1];
// 检查 parameters 中是否有这个参数名(不限 in 类型)
const foundParam = parameters.find(p => p.name === paramName);
if (foundParam) {
pathParamNames.add(paramName);
}
}
// 替换 URL 中的复杂路径参数为简单格式
// {userid:\d+} -> {userid}
// {type:[\w\+=\-]+} -> {type}
let normalizedUrl = url;
if (pathParamNames.size > 0) {
normalizedUrl = url.replace(/\{([^:}]+)(?::[^}]*)?\}/g, (match, paramName) => {
// 如果这个参数名在我们的列表中,使用简化格式
if (pathParamNames.has(paramName)) {
return `{${paramName}}`;
}
// 否则保持原样(虽然这种情况应该很少)
return match;
});
}
else {
// 如果没有找到参数定义,也尝试简化(容错)
normalizedUrl = url.replace(/\{([^:}]+)(?::[^}]*)?\}/g, '{$1}');
}
return normalizedUrl;
};
const formatUrl = (parameters = [], url) => {
// 先标准化 URL
const normalized = normalizeUrl(parameters, url);
// 然后将路径参数转换为模板字符串格式
const pathParams = initArray(parameters).filter((paramsItem) => paramsItem.in === 'path');
return pathParams.reduce((pre, { name }) => {
return pre.replace(`{${name.trim()}}`, `$\{${name.trim()}}`);
}, normalized);
};
const createMethodsName = (url, method, parameters = []) => {
// 使用标准化的 URL 生成方法名
const normalized = normalizeUrl(parameters, url);
return pascalCaseReplaceString(`${normalized}${pascalCase(method)}`);
};
const formatParamtersItemType = (type) => {
if (type === 'integer') {
return 'number';
}
return 'string';
};
// 获取parameters中path的参数
const renderRenderParametersPaths = (parmeters, isTypeScript = true) => {
return initArray(parmeters)
.reduce((pre, next) => {
if (next.in === 'path') {
if (isTypeScript) {
pre.push(`${next.name}: ${formatParamtersItemType(next.type)}`);
return pre;
}
pre.push(`${next.name}`);
return pre;
}
return pre;
}, [])
.join(',');
};
const getParameterPath = (parmeters) => {
return initArray(parmeters).reduce((pre, next) => {
if (next.in === 'path') {
pre.push(next);
return pre;
}
return pre;
}, []);
};
const getParameterData = (parmeters) => {
return initArray(parmeters).reduce((pre, next) => {
if (next.in === 'body') {
pre.push(next);
return pre;
}
return pre;
}, []);
};
const getParameterParams = (parmeters) => {
return initArray(parmeters).reduce((pre, next) => {
if (next.in === 'query') {
pre.push(next);
return pre;
}
return pre;
}, []);
};
// 渲染模板头部
const getTemplatePrefix = () => {
if (store.isTypeScript) {
return `
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
\n
`;
}
return `import axios from 'axios'\n`;
};
// const deepCopy = (json: any) => {
// return clone(json);
// };
// const formatTsResponse = (name: string) => {
// return `${name}Response`;
// };
// const createTypeDefineObj = (title: string, properties: any, type: string, required?: any) => {
// return {
// key: title,
// propertiesKey: {
// $ref: `#/definitions/${title}`,
// },
// definitionsKey: {
// title: title,
// type: type,
// additionalProperties: false,
// properties: properties,
// required: required,
// },
// };
// };
const wapperDefineName = (methName, mode) => {
if (mode === 'params') {
return `${methName}RequsetParams`;
}
if (mode === 'data') {
return `${methName}RequsetData`;
}
return `${methName}Response`;
};
class Helper {
constructor(uri, method, params) {
this.getApiInteraces = [];
this.renderOptionsStr = {
params: '',
data: '',
};
this.uri = uri;
this.method = method;
this.sourceSchema = params;
this.description = params.description;
this.summary = params.summary;
}
getUrl() {
return formatUrl(this.sourceSchema.parameters, this.uri);
}
getMethodsName() {
return createMethodsName(this.uri, this.method, this.sourceSchema.parameters);
}
getJsDocTypes() {
return `{import("./${store.filename}.types.ts").${this.getMethodsName()}}`;
}
getMethodPrePath() {
const getPaths = renderRenderParametersPaths(this.sourceSchema.parameters, store.isTypeScript);
this.__vaildateGetMethodPrePath(getParameterPath(this.sourceSchema.parameters));
return getPaths ? `${getPaths}` : '';
}
getMethodPreParams() {
const getParams = getParameterParams(this.sourceSchema.parameters);
if (getParams.length === 0) {
return '';
}
this.renderOptionsStr.params = 'params';
if (store.isTypeScript) {
return `params: P`;
}
return 'params';
}
getJsDocMethodPreParams() {
const getParams = getParameterParams(this.sourceSchema.parameters);
if (getParams.length === 0) {
return '';
}
this.renderOptionsStr.params = 'params';
return `params: P`;
}
getMethodOption() {
if (store.isTypeScript) {
return `options: AxiosRequestConfig = {}`;
}
return `options = {}`;
}
getJsDocMethodOption() {
return `options: AxiosRequestConfig`;
}
// 获取params的ts定义名字
getMethodPreDefineParams() {
if (!this.getMethodPreParams()) {
return;
}
return wapperDefineName(this.getMethodsName(), 'params');
}
// 校验path参数是否有有问题
__vaildateGetMethodPrePath(getParams) {
const matchResult = this.uri.match(/\{\w+\}/g);
const getUrlParams = (matchResult ? matchResult : []);
const getUrlParamsTotal = getUrlParams.length;
const parameters = getParams.length;
if (getUrlParamsTotal === 0 && parameters === 0) {
return;
}
// 检查定义的path参数和url上的参数数量是否一致
if (getUrlParamsTotal !== parameters) {
store.pushError({
url: this.uri,
desc: this.description,
method: this.method,
errorType: ErrorType.PathParams,
});
}
// 检测路径参数类型是否匹配
const errMsg = [];
getUrlParams.forEach((key) => {
const target = getParams.find((item) => String(key).indexOf(item.name) > -1);
if (target && target.in !== 'path') {
errMsg.push(`${key}的in类型为${target.in}, 应该改为path`);
}
});
if (errMsg.length > 0) {
store.pushError({
url: this.uri,
desc: this.description,
method: this.method,
errMsg: errMsg.join(','),
errorType: ErrorType.PathError,
});
}
}
debugPrint(desc, errMsg = '') {
if (this.description === desc) {
store.pushError({
errorType: ErrorType.Debug,
url: this.uri,
method: this.method,
desc: this.description,
errMsg: errMsg,
});
}
}
// 查找方法中所有params参数
getMethodParams() { }
// 查找方法中所有data参数
getMethodData() {
return getParameterData(this.sourceSchema.parameters);
}
renderGetMethodData() {
if (this.getMethodData().length === 0) {
return '';
}
this.renderOptionsStr.data = 'data';
if (store.isTypeScript) {
return `data: D`;
}
return `data`;
}
renderJsDocGetMethodData() {
if (this.getMethodData().length === 0) {
return '';
}
this.renderOptionsStr.data = 'data';
return `data: D`;
}
// 获取data的ts定义名字
renderGetMethodDefineData() {
if (this.method === MethodsType.Get) {
return;
}
if (!this.renderGetMethodData()) {
return;
}
return wapperDefineName(this.getMethodsName(), 'data');
}
// 获取接口返回的ts定义名字
getMethodsDefineResponse() {
return wapperDefineName(this.getMethodsName(), 'response');
}
// 获取返回对象的ts定义名字
renderTsDefineResFeature() {
if (!this.sourceSchema.responses['200']) {
return [];
}
return [];
}
renderTsDefineReqFeature() {
const { parameters = [] } = this.sourceSchema;
const reqParams = initArray(parameters).filter((params) => params.in === 'body');
if (reqParams.length === 0) {
return [];
}
return [];
}
// 处理query data的函数
generatorDefineParams() {
const querys = initArray(this.sourceSchema.parameters).filter((parameterItem) => parameterItem.in === 'query');
const properties = querys.reduce((pre, next) => {
const { name, type, description } = next;
pre[name] = {
type,
description,
};
return pre;
}, {});
const required = initArray(querys)
.filter((item) => item.required)
.map((item) => item.name);
const key = this.getMethodPreDefineParams();
if (!key) {
return {
key,
propertiesKey: {},
definitionsKey: {},
};
}
return {
key,
propertiesKey: {
$ref: `#/definitions/${key}`,
},
definitionsKey: {
title: key,
type: 'object',
additionalProperties: false,
properties,
description: this.description,
required: required,
},
};
}
// 处理response的函数
formatProperties(title, itemSchema) {
try {
const deepItemSchema = clone$1(itemSchema);
// if (!deepItemSchema.properties && deepItemSchema['$ref']) {
// const innerTitle = `${title}${pascalCase('items')}`;
// return {
// [title]:`#/definitions/${title}`
// }
// }
const properties = Object.keys(deepItemSchema.properties).reduce((pre, next) => {
const data = deepItemSchema.properties[next];
const innerTitle = `${title}${pascalCase(next)}`;
if (['object'].includes(data.type)) {
pre[next] = {};
pre[next]['$ref'] = `#/definitions/${innerTitle}`;
return pre;
}
if (['array'].includes(data.type)) {
pre[next] = {};
pre[next]['items'] = {};
pre[next]['items']['$ref'] = `#/definitions/${innerTitle}`;
return pre;
}
pre[next] = {};
pre[next] = clone$1(data);
return pre;
}, {});
return properties;
}
catch (err) {
return {};
}
}
setTypeObjToInstances(preName, key, itemSchema) {
try {
if (!key) {
// 没有key就是root节点
// 先把这个接口的response根节点写入
if (itemSchema.type === 'object') {
const rootTitle = `${preName}`;
const rootProperties = this.formatProperties(rootTitle, itemSchema);
const apiInstance = {
key: rootTitle,
propertiesKey: {
$ref: `#/definitions/${rootTitle}`,
},
definitionsKey: {
title: rootTitle,
type: itemSchema.type,
properties: rootProperties,
additionalProperties: false,
required: initArray(itemSchema.required),
},
};
this.getApiInteraces.push(apiInstance);
return;
}
if (itemSchema.type === 'array') {
const rootTitle = `${preName}`;
const rootProperties = this.formatProperties(rootTitle, itemSchema.items);
const apiInstance = {
key: rootTitle,
propertiesKey: {
items: {
$ref: `#/definitions/${rootTitle}`,
},
},
definitionsKey: {
title: rootTitle,
type: itemSchema.type,
properties: rootProperties,
additionalProperties: false,
required: initArray(itemSchema.required),
},
};
this.getApiInteraces.push(apiInstance);
return;
}
}
// 剩下就是有key的
const rootTitle = `${preName}${pascalCase(key)}`;
if (key === 'children') {
}
if (itemSchema.type === 'array') {
const apiInstance = {
key: rootTitle,
propertiesKey: {
items: {
$ref: `#/definitions/${rootTitle}`,
},
},
definitionsKey: {
title: rootTitle,
type: itemSchema.items.type,
properties: this.formatProperties(rootTitle, itemSchema.items), // TODO,
additionalProperties: false,
required: initArray(itemSchema.required),
},
};
this.getApiInteraces.push(apiInstance);
return;
}
if (itemSchema.type === 'object') {
const apiInstance = {
key: rootTitle,
propertiesKey: {
$ref: `#/definitions/${rootTitle}`,
},
definitionsKey: {
title: rootTitle,
type: itemSchema.type,
properties: this.formatProperties(rootTitle, itemSchema), // TODO,
additionalProperties: false,
required: initArray(itemSchema.required),
},
};
this.getApiInteraces.push(apiInstance);
return;
}
(() => {
const apiInstance = {
key: rootTitle,
propertiesKey: {
$ref: `#/definitions/${rootTitle}`,
},
definitionsKey: {
title: rootTitle,
type: itemSchema.type,
additionalProperties: false,
required: initArray(itemSchema.required),
},
};
this.getApiInteraces.push(apiInstance);
})();
}
catch (err) {
const rootTitle = `${preName}${pascalCase(key)}`;
if (itemSchema.type === 'array') {
const apiInstance = {
key: rootTitle,
propertiesKey: {
items: {
$ref: `#/definitions/${rootTitle}`,
},
},
definitionsKey: {
title: rootTitle,
additionalProperties: true,
},
};
this.getApiInteraces.push(apiInstance);
}
store.pushError({
url: this.uri,
method: this.method,
errorType: ErrorType.SchemaError,
desc: this.description,
errMsg: 'setTypeObjToInstances ' + rootTitle + ' ' + err.toString(),
});
}
}
setTypeNotProperties(preName, key, itemSchema) {
const rootTitle = `${preName}${pascalCase(key)}`;
const apiInstance = {
key: rootTitle,
propertiesKey: {},
definitionsKey: {
title: rootTitle,
type: itemSchema.type,
additionalProperties: true,
required: [],
},
};
this.getApiInteraces.push(apiInstance);
}
setTypeNotProperToArrRef(preName, key, itemSchema) {
const rootTitle = `${preName}${pascalCase(key)}`;
const apiInstance = {
key: rootTitle,
propertiesKey: {
...itemSchema.items,
},
definitionsKey: {
title: rootTitle,
type: itemSchema.type,
additionalProperties: true,
required: [],
},
};
this.getApiInteraces.push(apiInstance);
}
generatorDeepDefine(preName, key, itemSchema) {
if (itemSchema.type === 'object') {
// 有key说明不是跟response
const rootTitle = `${preName}${pascalCase(key)}`;
if (key && !itemSchema.properties) {
this.setTypeNotProperties(preName, key, itemSchema);
return;
}
if (key && itemSchema.properties) {
this.setTypeObjToInstances(preName, key, itemSchema);
// TODO
}
// 深度递归找出里面需要另外设置ref的数据
Object.keys(itemSchema.properties).forEach((itemKey) => {
const data = itemSchema.properties[itemKey];
if (['object'].includes(data.type)) {
this.generatorDeepDefine(rootTitle, itemKey, clone$1(data));
}
else if (['array'].includes(data.type)) {
this.generatorDeepDefine(rootTitle, itemKey, clone$1(data));
}
});
}
if (itemSchema.type === 'array') {
this.setTypeObjToInstances(preName, key, itemSchema);
if (!itemSchema.items) {
return;
}
if (!key && !itemSchema.items.properties) {
this.setTypeNotProperToArrRef(preName, key, itemSchema);
return;
// 根节点数组的情况
}
if (!itemSchema.items.properties) {
this.setTypeNotProperties(preName, key, itemSchema);
return;
}
const rootTitle = `${preName}${pascalCase(key)}`;
if (itemSchema.items.type === 'object') {
Object.keys(clone$1(itemSchema.items.properties)).forEach((itemKey) => {
const data = itemSchema.items.properties[itemKey];
if (['object', 'array'].includes(data.type)) {
this.generatorDeepDefine(rootTitle, itemKey, clone$1(data));
}
});
}
else if (itemSchema.items.type === 'array') {
const data = itemSchema.items;
this.generatorDeepDefine(rootTitle, 'item', clone$1(data));
}
else ;
}
}
// 从 example 字符串中解析出数据结构
parseExampleToSchema(example) {
try {
const parsed = JSON.parse(example);
return this.inferSchemaFromValue(parsed);
}
catch (err) {
// 如果解析失败,返回一个空的 object schema
return {
type: 'object',
properties: {},
additionalProperties: true,
};
}
}
// 从实际值推断 schema 结构(所有字段都设为 any 类型,非必填)
inferSchemaFromValue(value) {
if (value === null || value === undefined) {
return { type: 'any' };
}
if (Array.isArray(value)) {
const itemSchema = value.length > 0
? this.inferSchemaFromValue(value[0])
: { type: 'any' };
return {
type: 'array',
items: itemSchema,
};
}
if (typeof value === 'object') {
const properties = {};
Object.keys(value).forEach(key => {
// 所有字段都设置为 any 类型
properties[key] = {
type: 'any',
description: '',
};
});
return {
type: 'object',
properties,
additionalProperties: true,
required: [], // 所有字段都是非必填
};
}
// 基础类型也返回 any
return { type: 'any' };
}
generatorDefineData() {
return {
key: '',
propertiesKey: {},
definitionsKey: {},
};
}
generatorDefineResponse() {
const key = this.getMethodsDefineResponse();
const response = this.sourceSchema.responses['200'];
if (!response) {
// 没有200响应,记录错误并生成一个 any 类型的默认定义
store.pushError({
url: this.uri,
method: this.method,
errorType: ErrorType.SchemaError,
desc: this.description,
errMsg: '缺少 response 200 定义',
});
const defaultAnyInfo = {
key,
propertiesKey: {
$ref: `#/definitions/${key}`,
},
definitionsKey: {
title: key,
type: 'object',
additionalProperties: true,
description: this.description,
},
};
this.getApiInteraces.push(defaultAnyInfo);
return this.getApiInteraces;
}
const resSchema = response.schema;
if (!resSchema) {
const emptyInfof = {
key,
propertiesKey: {
$ref: `#/definitions/${key}`,
},
definitionsKey: {
title: key,
type: 'object',
additionalProperties: true,
description: this.description,
},
};
this.getApiInteraces.push(emptyInfof);
return this.getApiInteraces;
}
// 检查是否只有 example 而没有 properties(边界情况)
const hasProperties = resSchema.properties || (resSchema.items && resSchema.items.properties);
const hasExample = resSchema.example;
if (!hasProperties && hasExample && typeof hasExample === 'string') {
// 只有 example,没有结构化的 properties
// 尝试从 example 中解析出结构
const parsedSchema = this.parseExampleToSchema(hasExample);
// 生成一个简单的定义,所有字段都是 any 类型
const exampleInfo = {
key,
propertiesKey: {
$ref: `#/definitions/${key}`,
},
definitionsKey: {
title: key,
type: parsedSchema.type || 'object',
properties: parsedSchema.properties || {},
additionalProperties: true,
required: [],
description: this.description,
},
};
this.getApiInteraces.push(exampleInfo);
return this.getApiInteraces;
}
// 先把根节点的response设置好
this.setTypeObjToInstances(key, '', resSchema);
// 编辑response对象
if (resSchema.type === 'array') {
this.generatorDeepDefine(key, '', resSchema.items);
}
else {
this.generatorDeepDefine(key, '', resSchema);
}
return this.getApiInteraces;
}
}
// import { MethodsType, GetSchema } from '@/define';
// 生成ts的schema
const handleRenderApiTsFileFeature = (apis) => {
const init = {
title: 'Api',
type: 'object',
properties: {},
definitions: {},
additionalProperties: false,
preDefine: '',
};
return apis.reduce((pre, next) => {
try {
[next.generatorDefineParams()].forEach((item) => {
const { propertiesKey, definitionsKey, key } = item;
if (!key) {
return;
}
pre['properties'][key] = propertiesKey;
pre['definitions'][key] = definitionsKey;
});
// get是没有data的 post的情况下 存在实例全局对象里面在下面的函数一起处理
next.generatorDefineData();
next.generatorDefineResponse().forEach((item) => {
const { propertiesKey, definitionsKey, key } = item;
if (!key) {
return;
}
pre['properties'][key] = propertiesKey;
pre['definitions'][key] = definitionsKey;
});
}
catch (err) {
next.debugPrint(next.description, '【handleRenderApiTsFileFeature】' + err.toString());
}
return pre;
}, init);
};
// 返回接口定义名字的import列表
const wrapperPreFixDefineName = (helpers) => {
return helpers.reduce((pre, next) => {
const params = next.getMethodPreDefineParams();
if (params) {
pre.push(params);
}
const data = next.renderGetMethodDefineData();
if (data) {
pre.push(data);
}
const response = next.getMethodsDefineResponse();
if (response) {
pre.push(response);
}
return pre;
}, []);
};
// 返回接口泛型参数
const wrapperMethodPreInterface = (helpers) => {
const p = [];
const params = helpers.getMethodPreDefineParams();
if (params) {
p.push(`P extends ${params}`);
}
const data = helpers.renderGetMethodDefineData();
if (data) {
p.push(`D extends ${data}`);
}
const response = helpers.getMethodsDefineResponse();
if (response) {
p.push(`S = AxiosResponse < ${response} > `);
}
return p;
};
const wrapperPreImportDefine = (defines) => {
return `
import { \n${defines}\n
} from './${store.filename}.define';
`;
};
/**
* 渲染 TypeScript 或 JavaScript 接口函数
*/
const renderMethodTemplate = (instance, includeData = false) => {
const pathArgs = instance.getMethodPrePath();
const paramsArgs = instance.getMethodPreParams();
const dataArgs = includeData ? instance.renderGetMethodData() : '';
const optionArgs = instance.getMethodOption();
const args = renderMethodArgs([pathArgs, paramsArgs, dataArgs, optionArgs]);
const requestArgs = renderMethodArgs([
`url: \`${instance.getUrl()}\``,
`method: \'${instance.method}\'`,
`${instance.renderOptionsStr.data}`,
`${instance.renderOptionsStr.params}`,
`...options`,
]);
const preInterface = renderMethodArgs(wrapperMethodPreInterface(instance));
const summary = instance.description === instance.summary ? '' : `\n * @summary ${instance.summary}`;
// TypeScript 模式
if (store.isTypeScript) {
return `
/***
* @description ${instance.description}${summary}
**/
export const ${instance.getMethodsName()} = <${preInterface}>(${args}):Promise<S> => {
return axios.request({${requestArgs}})
}\n
`;
}
// JsDoc 模式
if (store.isJsDoc) {
return `
/***
* @type {import("./${store.filename}.types.ts").${instance.getMethodsName()}}
* @description ${instance.description}${summary}
**/
export const ${instance.getMethodsName()} = (${args}) => {
return axios.request({${requestArgs}})
}\n
`;
}
// 纯 JavaScript 模式
return `
/***
* @description ${instance.description}${summary}
**/
export const ${instance.getMethodsName()} = (${args}) => {
return axios.request({${requestArgs}})
}\n
`;
};
/**
* 渲染 JsDoc 类型定义
*/
const renderJsDocTemplate = (instance, includeData = false) => {
const pathArgs = instance.getMethodPrePath();
const paramsArgs = instance.getJsDocMethodPreParams();
const dataArgs = includeData ? instance.renderJsDocGetMethodData() : '';
const optionArgs = instance.getJsDocMethodOption();
const args = renderMethodArgs([pathArgs, paramsArgs, dataArgs, optionArgs]);
const preInterface = renderMethodArgs(wrapperMethodPreInterface(instance));
const response = 'S';
return `
export type ${instance.getMethodsName()} = <${preInterface}>(${args})=> Promise<${response}>\n
`;
};
/**
* 创建方法渲染器
* @param uri - 接口路径
* @param method - 请求方法
* @param params - 接口参数定义
* @param includeData - 是否包含 data 参数(POST/PUT/DELETE 需要)
*/
const createMethodRenderer = (uri, method, params, includeData = false) => {
const instance = new Helper(uri, method, params);
// 渲染模板方法,可以通过传入自定义的方法覆盖
const renderTemplate = (renderCallback) => {
if (typeof renderCallback === 'function') {
return renderCallback.call(instance);
}
return renderMethodTemplate(instance, includeData);
};
// 渲染 JsDoc 模板
const renderJsDocMethod = (renderJsDocCallback) => {
if (typeof renderJsDocCallback === 'function') {
return renderJsDocCallback.call(instance);
}
return renderJsDocTemplate(instance, includeData);
};
return {
instance,
renderTemplate,
renderJsDocTemplate: renderJsDocMethod,
};
};
class GetHelper extends Helper {
// 待处理的数据
constructor(uri, method, params) {
super(uri, method, params);
}
// get是没有data的
generatorDefineData() {
return {
key: '',
propertiesKey: {},
definitionsKey: {},
};
}
}
const useGetMethdos = (uri, method, params, _response) => {
// GET 请求不包含 data 参数
return createMethodRenderer(uri, method, params, false);
};
class PostHelper extends Helper {
// 待处理的数据
constructor(uri, method, params) {
super(uri, method, params);
}
// post/put/delete 有 data 参数
generatorDefineData() {
const key = this.renderGetMethodDefineData();
if (!key) {
return {
key: '',
propertiesKey: {},
definitionsKey: {},
};
}
const bodySchema = this.sourceSchema.parameters.find((item) => item.in === 'body');
if (!bodySchema) {
return {
key: '',
propertiesKey: {},
definitionsKey: {},
};
}
// 边界情况1:bodySchema 没有 schema 定义
if (!bodySchema.schema) {
store.pushError({
url: this.uri,
method: this.method,
errorType: ErrorType.SchemaError,
desc: this.description,
errMsg: '缺少 body schema 定义',
});
const defaultAnyInfo = {
key,
propertiesKey: {
$ref: `#/definitions/${key}`,
},
definitionsKey: {
title: key,
type: 'object',
additionalProperties: true,
description: this.description,
},
};
this.getApiInteraces.push(defaultAnyInfo);
return {
key: '',
propertiesKey: {},
definitionsKey: {},
};
}
// 边界情况2:schema 只有 type,没有 properties(object 类型)
const schema = bodySchema.schema;
const hasProperties = schema.properties || (schema.items && schema.items.properties);
if (schema.type === 'object' && !hasProperties) {
store.pushError({
url: this.uri,
method: this.method,
errorType: ErrorType.SchemaError,
desc: this.description,
errMsg: 'body schema 缺少 properties 定义',
});
const defaultAnyInfo = {
key,
propertiesKey: {
$ref: `#/definitions/${key}`,
},
definitionsKey: {
title: key,
type: 'object',
additionalProperties: true,
description: this.description,
},
};
this.getApiInteraces.push(defaultAnyInfo);
return {
key: '',
propertiesKey: {},
definitionsKey: {},
};
}
// 正常处理流程
try {
// 先把根节点的response设置好
if (bodySchema.schema?.type !== 'array') {
this.setTypeObjToInstances(key, '', bodySchema.schema);
}
// 编辑response对象
this.generatorDeepDefine(key, '', bodySchema.schema);
}
catch (err) {
// 如果处理过程中出错,生成 any 类型定义
store.pushError({
url: this.uri,
method: this.method,
errorType: ErrorType.SchemaError,
desc: this.description,
errMsg: `body schema 处理失败: ${err.message}`,
});
const defaultAnyInfo = {
key,
propertiesKey: {
$ref: `#/definitions/${key}`,
},
definitionsKey: {
title: key,
type: 'object',
additionalProperties: true,
description: this.description,
},
};
this.getApiInteraces.push(defaultAnyInfo);
}
// post的情况会在处理response的时候顺带处理
return {
key: '',
propertiesKey: {},
definitionsKey: {},
};
}
}
const usePostMethdos = (uri, method, params) => {
// POST/PUT/DELETE 请求包含 data 参数
return createMethodRenderer(uri, method, params, true);
};
/**
* 路径处理器 - 优化重复的遍历逻辑
*/
const clone = require('clone');
// 全局忽略 URL 列表
let globalIgnoreUrls = [];
/**
* 设置全局忽略 URL 列表
*/
function setIgnoreUrls(ignoreUrls = []) {
globalIgnoreUrls = ignoreUrls;
}
/**
* 检查 URL 是否应该被忽略
*/
function shouldIgnoreUrl(url) {
return globalIgnoreUrls.includes(url);
}
/**
* 遍历所有路径和方法
* @param paths - Swagger 的 paths 对象
* @param callback - 处理每个路径方法的回调函数
*/
const forEachPath = (paths, callback) => {
Object.keys(paths).forEach((uri) => {
// 检查是否在忽略名单中
if (shouldIgnoreUrl(uri)) {
console.log(`⚠️ 跳过忽略的 URL: ${uri}`);
return;
}
const pathItem = paths[uri];
Object.keys(pathItem).forEach((method) => {
callback({ uri, method, schema: clone(pathItem[method]) });
});
});
};
/**
* 生成所有路径的 Helper 实例
* @param paths - Swagger 的 paths 对象
* @returns Helper 实例数组
*/
const generateHelpers = (paths) => {
const helpers = [];
forEachPath(paths, ({ uri, method, schema }) => {
if (method === MethodsType.Get) {
helpers.push(new GetHelper(uri, method, schema));
}
else {
helpers.push(new PostHelper(uri, method, schema));
}
});
return helpers;
};
/**
* 生成所有路径的模板渲染器
* @param paths - Swagger 的 paths 对象
* @param renderCallback - 自定义渲染回调函数
* @returns 渲染后的模板字符串数组
*/
const generateTemplates = (paths, renderCallback) => {
const templates = [];
forEachPath(paths, ({ uri, method, schema }) => {
const schemaData = schema;
const renderer = method === 'get'
? useGetMethdos(uri, method, schema, schemaData['responses'])
: usePostMethdos(uri, method, schema);
templates.push(renderer.renderTemplate(renderCallback));
});
return templates;
};
/**
* 生成所有路径的 JsDoc 模板
* @param paths - Swagger 的 paths 对象
* @param renderJsDocCallback - 自定义 JsDoc 渲染回调函数
* @returns 渲染后的 JsDoc 字符串数组
*/
const generateJsDocTemplates = (paths, renderJsDocCallback) => {
const templates = [];
forEachPath(paths, ({ uri, method, schema }) => {
const schemaData = schema;
const renderer = method === 'get'
? useGetMethdos(uri, method, schema, schemaData['responses'])
: usePostMethdos(uri, method, schema);
templates.push(renderer.renderJsDocTemplate(renderJsDocCallback));
});
return templates;
};
/**
* 生成路径定义对象
* @param paths - Swagger paths 对象
* @returns 定义对象
*/
const generatePathsDefine = (paths) => {
return handleRenderApiTsFileFeature(generateHelpers(paths));
};
/**
* 生成路径定义名称列表
* @param paths - Swagger paths 对象
* @returns 定义名称数组
*/
const generatePathsDefineNames = (paths) => {
const helpers = [];
forEachPath(paths, ({ uri, method, schema }) => {
helpers.push(new Helper(uri, method, schema));
});
return wrapperPreFixDefineName(helpers);
};
/**
* 从 JavaScript 代码字符串中提取导出的函数名及其注释
* @param code - JavaScript 代码字符串
* @returns 包含函数名和注释的对象数组
*/
const extractFunctionsFromCode = (code) => {
try {
// 解析为 AST(支持 ES6+ 语法)
const ast = parser.parse(code, {
sourceType: 'module',
plugins: ['jsx', 'typescript'], // 支持 JSX 和 TypeScript
attachComment: true, // 启用注释解析
});
const exportedFunctions = [];
// 遍历 AST
traverse(ast, {
ExportNamedDeclaration(path) {
// 提取函数上方的注释
const comments = path.node.leadingComments || [];
const description = extractCommentValue(comments, 'description');
const summary = extractCommentValue(comments, 'summary');
// 处理导出的函数声明
if (path.node.declaration?.type === 'FunctionDeclaration') {
const functionName = path.node.declaration.id?.name;
if (functionName) {
exportedFunctions.push({ name: functionName, description, summary });
}
}
// 处理导出的变量声明(函数表达式或箭头函数)
if (path.node.declaration?.type === 'VariableDeclaration') {
path.node.declaration.declarations.forEach((declaration) => {
if (declaration.init?.type === 'FunctionExpression' ||
declaration.init?.type === 'ArrowFunctionExpression') {
const functionName = declaration.id?.name;
if (functionName) {
exportedFunctions.push({ name: functionName, description, summary });
}
}
});
}
},
});
return exportedFunctions;
}
catch (err) {
console.error('解析代码失败:', err.message);
return [];
}
};
/**
* 从注释中提取特定标签的值
* @param comments - 注释节点数组
* @param tag - 标签名(如 'description' 或 'summary')
* @returns 标签对应的值
*/
const extractCommentValue = (comments, tag) => {
for (const comment of comments) {
if (comment.type === 'CommentBlock') {
const lines = comment.value.split('\n');
for (const line of lines) {
const match = line.match(new RegExp(`@${tag}\\s+(.*)`));
if (match) {
return match[1].trim();
}
}
}
}
return undefined;
};
/**
* 从文件中提取导出的函数名
* @param filePath - 文件路径
* @returns 导出的函数名数组
*/
const extractFunctionsFromFile = (filePath) => {
try {
const code = fs__namespace.readFileSync(filePath, 'utf-8');
return extractFunctionsFromCode(code);
}
catch (err) {
console.error(`读取文件失败: ${path__namespace.basename(filePath)}`, err.message);
return [];
}
};
/**
* 比较两个函数数组,输出新增和删除的函数
* @param oldFunctions - 旧的函数数组
* @param newFunctions - 新的函数数组
* @returns 包含新增和删除函数信息的对象
*/
const compareFunctions = (oldFunctions, newFunctions) => {
// 提取函数名数组
const oldFunctionNames = oldFunctions.map((func) => func.name);
const newFunctionNames = newFunctions.map((func) => func.name);
// 找出新增的函数
const added = newFunctions.filter((func) => !oldFunctionNames.includes(func.name));
// 找出删除的函数
const removed = oldFunctions.filter((func) => !newFunctionNames.includes(func.name));
return { added, removed };
};
/**
* 生成多列 Markdown 表格
* @param data - 表格数据(二维数组)
* @param headers - 表头数组
* @param title - 表格标题(可选)
* @returns Markdown 表格字符串
*/
const generateMultiColumnMarkdownTable = (data, headers, title) => {
if (data.length === 0) {
return title ? `**${title}**\n\n无数据` : '无数据';
}
// 生成表头
let table = `| ${headers.join(' | ')} |\n`;
table += `| ${headers.map(() => '---').join(' | ')} |\n`;
// 生成表格内容
data.forEach((row) => {
table += `| ${row.map((cell) => cell || '').join(' | ')} |\n`;
});
// 添加标题(如果提供)
if (title) {
table = `**${title}**\n\n${table}`;
}
return table;
};
class Diff {
diff(oldStr, newStr,
// Type below is not accurate/complete - see above for full possibilities - but it compiles
options = {}) {
let callback;
if (typeof options === 'function') {
callback = options;
options = {};
}
else if ('callback' in options) {
callback = options.callback;
}
// Allow subclasses to massage the input prior to running
const oldString = this.castInput(oldStr, options);
const newString = this.castInput(newStr, options);
const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
const newTokens = this.removeEmpty(this.tokenize(newString, options));
return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
}
diffWithOptionsObj(oldTokens, newTokens, options, callback) {
var _a;
const done = (value) => {
value = this.postProcess(value, options);
if (callback) {
setTimeout(function () { callback(value); }, 0);
return undefined;
}
else {
return value;
}
};
const newLen = newTokens.length, oldLen = oldTokens.length;
let editLength = 1;
let maxEditLength = newLen + oldLen;
if (options.maxEditLength != null) {
maxEditLength = Math.min(maxEditLength, options.maxEditLength);
}
const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
const abortAfterTimestamp = Date.now() + maxExecutionTime;
const bestPath = [{ oldPos: -1, lastComponent: undefined }];
// Seed editLength = 0, i.e. the content starts with the same values
let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
// Identity per the equality and tokenizer
return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
}
// Once we hit the right edge of the edit graph on some diagonal k, we can
// definitely reach the end of the edit graph in no more than k edits, so
// there's no point in considering any moves to diagonal k+1 any more (from
// which we're guaranteed to need at least k+1 more edits).
// Similarly, once we've reached the bottom of the edit graph, there's no
// point considering moves to lower diagonals.
// We record this fact by setting minDiagonalToConsider and
// maxDiagonalToConsider to some finite value once we've hit the edge of
// the edit graph.
// This optimization is not faithful to the original algorithm presented in
// Myers's paper, which instead pointlessly extends D-paths off the end of
// the edit graph - see page 7 of Myers's paper which notes this point
// explicitly and illustrates it with a diagram. This has major performance
// implications for some common scenarios. For instance, to compute a diff
// where the new text simply appends d characters on the end of the
// original text of length n, the true Myers algorithm will take O(n+d^2)
// time while this optimization needs only O(n+d) time.
let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
// Main worker method. checks all permutations of a given edit length for acceptance.
const execEditLength = () => {
for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
let basePath;
const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
if (removePath) {
// No one else is going to attempt to use this value, clear it
// @ts-expect-error - perf optimisation. This type-violating value will never be read.
bestPath[diagonalPath - 1] = undefined;
}
let canAdd = false;
if (addPath) {