openapi-ts-request
Version:
Swagger2/OpenAPI3/Apifox to TypeScript/JavaScript, request client(support any client), request mock service, enum and enum translation, react-query/vue-query, type field label, JSON Schemas
966 lines • 56.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const fs_1 = require("fs");
const glob_1 = require("glob");
const lodash_1 = require("lodash");
const minimatch_1 = require("minimatch");
const nunjucks_1 = tslib_1.__importDefault(require("nunjucks"));
const path_1 = require("path");
const rimraf_1 = require("rimraf");
const config_1 = require("../config");
const log_1 = tslib_1.__importDefault(require("../log"));
const util_1 = require("../util");
const config_2 = require("./config");
const file_1 = require("./file");
const merge_1 = require("./merge");
const patchSchema_1 = require("./patchSchema");
const util_2 = require("./util");
class ServiceGenerator {
constructor(config, openAPIData) {
var _a, _b, _c, _d, _e;
this.apiData = {};
this.classNameList = [];
this.schemaList = [];
this.interfaceTPConfigs = [];
this.config = Object.assign({ templatesFolder: (0, path_1.join)(__dirname, '../../', 'templates') }, config);
this.generateInfoLog();
const includeTags = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.includeTags) || [];
const includePaths = ((_b = this.config) === null || _b === void 0 ? void 0 : _b.includePaths) || [];
const excludeTags = ((_c = this.config) === null || _c === void 0 ? void 0 : _c.excludeTags) || [];
const excludePaths = ((_d = this.config) === null || _d === void 0 ? void 0 : _d.excludePaths) || [];
const priorityRule = config_1.PriorityRule[config.priorityRule];
if ((_e = this.config.hook) === null || _e === void 0 ? void 0 : _e.afterOpenApiDataInited) {
this.openAPIData =
this.config.hook.afterOpenApiDataInited(openAPIData) || openAPIData;
}
else {
this.openAPIData = openAPIData;
}
// 用 tag 分组 paths, { [tag]: [pathMap, pathMap] }
outerLoop: for (const pathKey in this.openAPIData.paths) {
// 这里判断paths
switch (priorityRule) {
case config_1.PriorityRule.include: {
// includePaths and includeTags is empty, 直接跳过
if ((0, lodash_1.isEmpty)(includeTags) && (0, lodash_1.isEmpty)(includePaths)) {
this.log('priorityRule include need includeTags or includePaths');
break outerLoop;
}
if (!(0, lodash_1.isEmpty)(includePaths) &&
!this.validateRegexp(pathKey, includePaths)) {
continue;
}
break;
}
case config_1.PriorityRule.exclude: {
if (this.validateRegexp(pathKey, excludePaths)) {
continue;
}
break;
}
case config_1.PriorityRule.both: {
// includePaths and includeTags is empty,直接跳过
if ((0, lodash_1.isEmpty)(includeTags) && (0, lodash_1.isEmpty)(includePaths)) {
this.log('priorityRule both need includeTags or includePaths');
break outerLoop;
}
const outIncludePaths = !(0, lodash_1.isEmpty)(includePaths) &&
!this.validateRegexp(pathKey, includePaths);
const inExcludePaths = !(0, lodash_1.isEmpty)(excludePaths) &&
this.validateRegexp(pathKey, excludePaths);
if (outIncludePaths || inExcludePaths) {
continue;
}
break;
}
default:
throw new Error('priorityRule must be "include" or "exclude" or "include"');
}
const pathItem = this.openAPIData.paths[pathKey];
(0, lodash_1.forEach)(config_2.methods, (method) => {
var _a;
const operationObject = pathItem[method];
if (!operationObject) {
return;
}
const hookCustomFileNames = ((_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customFileNames) || util_2.getDefaultFileTag;
const tags = hookCustomFileNames(operationObject, pathKey, method);
// 这里判断tags
tags.forEach((tag) => {
if (!tag) {
return;
}
if (priorityRule === config_1.PriorityRule.include) {
// includeTags 为空,不会匹配任何path,故跳过
if ((0, lodash_1.isEmpty)(includeTags)) {
this.log('priorityRule include need includeTags or includePaths');
return;
}
if (!this.validateRegexp(tag, includeTags)) {
return;
}
}
if (priorityRule === config_1.PriorityRule.exclude) {
if (this.validateRegexp(tag, excludeTags)) {
return;
}
}
if (priorityRule === config_1.PriorityRule.both) {
// includeTags is empty 没有配置, 直接跳过
if ((0, lodash_1.isEmpty)(includeTags)) {
this.log('priorityRule both need includeTags or includePaths');
return;
}
const outIncludeTags = !(0, lodash_1.isEmpty)(includeTags) && !this.validateRegexp(tag, includeTags);
const inExcludeTags = !(0, lodash_1.isEmpty)(excludeTags) && this.validateRegexp(tag, excludeTags);
if (outIncludeTags || inExcludeTags) {
return;
}
}
const tagTypeName = (0, util_2.resolveTypeName)(tag);
const tagKey = this.config.isCamelCase
? (0, util_1.camelCase)(tagTypeName)
: (0, lodash_1.lowerFirst)(tagTypeName);
if (!this.apiData[tagKey]) {
this.apiData[tagKey] = [];
}
this.apiData[tagKey].push(Object.assign({ path: pathKey, method }, operationObject));
});
});
}
}
genFile() {
var _a, _b, _c, _d;
if (this.config.full) {
try {
(0, glob_1.globSync)(`${this.config.serversPath}/**/*`)
.filter((item) => !item.includes('_deperated'))
.forEach((item) => {
(0, rimraf_1.rimrafSync)(item);
});
}
catch (error) {
(0, log_1.default)(`🚥 api 生成失败: ${error}`);
}
}
const isOnlyGenTypeScriptType = this.config.isOnlyGenTypeScriptType;
const isGenJavaScript = this.config.isGenJavaScript;
const reactQueryMode = this.config.reactQueryMode;
const reactQueryFileName = (0, config_2.displayReactQueryFileName)(reactQueryMode);
if (!isOnlyGenTypeScriptType) {
const prettierError = [];
// 生成 service controller 文件
this.getServiceTPConfigs().forEach((tp) => {
var _a, _b;
const { list } = tp, restTp = tslib_1.__rest(tp, ["list"]);
const payload = Object.assign({ namespace: this.config.namespace, requestOptionsType: this.config.requestOptionsType, requestImportStatement: this.config.requestImportStatement, interfaceFileName: config_2.interfaceFileName, list }, restTp);
const hookCustomTemplateService = (_b = (_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customTemplates) === null || _b === void 0 ? void 0 : _b[config_2.TypescriptFileType.serviceController];
if (hookCustomTemplateService) {
payload.list = list.map((item) => {
return {
customTemplate: true,
data: hookCustomTemplateService(item, payload),
};
});
}
const hasError = this.genFileFromTemplate(isGenJavaScript
? (0, util_2.getFinalFileName)(`${tp.className}.js`)
: (0, util_2.getFinalFileName)(`${tp.className}.ts`), config_2.TypescriptFileType.serviceController, payload);
prettierError.push(hasError);
if (this.config.isGenReactQuery) {
this.genFileFromTemplate(isGenJavaScript
? (0, util_2.getFinalFileName)(`${tp.className}.${reactQueryFileName}.js`)
: (0, util_2.getFinalFileName)(`${tp.className}.${reactQueryFileName}.ts`), config_2.TypescriptFileType.reactQuery, Object.assign({ namespace: this.config.namespace, requestOptionsType: this.config.requestOptionsType, requestImportStatement: this.config.requestImportStatement, interfaceFileName: config_2.interfaceFileName, reactQueryModePackageName: (0, config_1.displayReactQueryMode)(reactQueryMode) }, tp));
}
});
if (prettierError.includes(true)) {
(0, log_1.default)('🚥 格式化失败,请检查 service controller 文件内可能存在的语法错误');
}
}
// 处理重复的 typeName
this.interfaceTPConfigs = this.getInterfaceTPConfigs();
(0, util_2.handleDuplicateTypeNames)(this.interfaceTPConfigs);
// 生成 ts 类型声明
if (!isGenJavaScript) {
this.genFileFromTemplate(`${config_2.interfaceFileName}.ts`, config_2.TypescriptFileType.interface, {
nullable: this.config.nullable,
list: this.interfaceTPConfigs,
});
}
// 生成枚举翻译
const enums = (0, lodash_1.filter)(this.interfaceTPConfigs, (item) => item.isEnum);
if (!isGenJavaScript && !isOnlyGenTypeScriptType && !(0, lodash_1.isEmpty)(enums)) {
const hookCustomTemplateService = (_b = (_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customTemplates) === null || _b === void 0 ? void 0 : _b[config_2.TypescriptFileType.displayEnumLabel];
this.genFileFromTemplate(`${config_2.displayEnumLabelFileName}.ts`, config_2.TypescriptFileType.displayEnumLabel, {
customTemplate: !!hookCustomTemplateService,
list: hookCustomTemplateService
? hookCustomTemplateService(enums, this.config)
: enums,
namespace: this.config.namespace,
interfaceFileName: config_2.interfaceFileName,
});
}
const displayTypeLabels = (0, lodash_1.filter)(this.interfaceTPConfigs, (item) => !item.isEnum);
// 生成 type 翻译
if (!isGenJavaScript &&
!isOnlyGenTypeScriptType &&
this.config.isDisplayTypeLabel &&
!(0, lodash_1.isEmpty)(displayTypeLabels)) {
const hookCustomTemplateService = (_d = (_c = this.config.hook) === null || _c === void 0 ? void 0 : _c.customTemplates) === null || _d === void 0 ? void 0 : _d[config_2.TypescriptFileType.displayTypeLabel];
this.genFileFromTemplate(`${config_2.displayTypeLabelFileName}.ts`, config_2.TypescriptFileType.displayTypeLabel, {
customTemplate: !!hookCustomTemplateService,
list: hookCustomTemplateService
? hookCustomTemplateService(enums, this.config)
: displayTypeLabels,
namespace: this.config.namespace,
interfaceFileName: config_2.interfaceFileName,
});
}
if (!isOnlyGenTypeScriptType &&
this.config.isGenJsonSchemas &&
!(0, lodash_1.isEmpty)(this.schemaList)) {
// 处理重复的 schemaName
(0, util_2.handleDuplicateTypeNames)(this.schemaList);
// 生成 schema 文件
this.genFileFromTemplate(isGenJavaScript ? `${config_2.schemaFileName}.js` : `${config_2.schemaFileName}.ts`, config_2.TypescriptFileType.schema, {
list: this.schemaList,
});
}
// 生成 service index 文件
this.genFileFromTemplate(isGenJavaScript
? `${config_2.serviceEntryFileName}.js`
: `${config_2.serviceEntryFileName}.ts`, config_2.TypescriptFileType.serviceIndex, {
list: this.classNameList,
namespace: this.config.namespace,
interfaceFileName: config_2.interfaceFileName,
genType: isGenJavaScript ? config_2.LangType.js : config_2.LangType.ts,
isGenJsonSchemas: !isOnlyGenTypeScriptType &&
this.config.isGenJsonSchemas &&
!(0, lodash_1.isEmpty)(this.schemaList),
schemaFileName: config_2.schemaFileName,
isDisplayEnumLabel: !isOnlyGenTypeScriptType && !(0, lodash_1.isEmpty)(enums),
displayEnumLabelFileName: config_2.displayEnumLabelFileName,
isGenReactQuery: this.config.isGenReactQuery,
reactQueryFileName,
isDisplayTypeLabel: !isOnlyGenTypeScriptType &&
this.config.isDisplayTypeLabel &&
!(0, lodash_1.isEmpty)(displayTypeLabels),
displayTypeLabelFileName: config_2.displayTypeLabelFileName,
});
// 打印日志
(0, log_1.default)('✅ 成功生成 api 文件目录-> ', this.config.serversPath);
}
getInterfaceTPConfigs() {
var _a, _b, _c;
const schemas = (_a = this.openAPIData.components) === null || _a === void 0 ? void 0 : _a.schemas;
const lastTypes = this.interfaceTPConfigs;
const includeTags = ((_b = this.config) === null || _b === void 0 ? void 0 : _b.includeTags) || [];
const includePaths = ((_c = this.config) === null || _c === void 0 ? void 0 : _c.includePaths) || [];
// 强行替换掉请求参数params的类型,生成方法对应的 xxxxParams 类型
(0, lodash_1.keys)(this.openAPIData.paths).forEach((pathKey) => {
const pathItem = this.openAPIData.paths[pathKey];
(0, lodash_1.forEach)(config_2.methods, (method) => {
var _a, _b, _c, _d;
const operationObject = pathItem[method];
const hookCustomFileNames = ((_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customFileNames) || util_2.getDefaultFileTag;
if (!operationObject) {
return;
}
const tags = hookCustomFileNames(operationObject, pathKey, method);
if ((0, lodash_1.isEmpty)(includeTags) ||
(!(0, lodash_1.isEmpty)(includeTags) && (0, lodash_1.isEmpty)(tags)) ||
(0, lodash_1.isEmpty)(includePaths)) {
return;
}
const flag = this.validateRegexp((0, lodash_1.filter)(tags, (tag) => !!tag), includeTags);
const pathFlag = this.validateRegexp(pathKey, includePaths);
if (!flag || !pathFlag) {
return;
}
// 筛选出 pathItem 包含的 $ref 对应的schema
(0, util_2.markAllowedSchema)(JSON.stringify(pathItem), this.openAPIData);
operationObject.parameters = (_b = operationObject.parameters) === null || _b === void 0 ? void 0 : _b.filter((item) => {
const parameter = this.resolveParameterRef(item);
return (parameter === null || parameter === void 0 ? void 0 : parameter.in) !== `${config_2.parametersInsEnum.header}`;
});
const props = [];
(_c = operationObject.parameters) === null || _c === void 0 ? void 0 : _c.forEach((param) => {
var _a;
const parameter = this.resolveParameterRef(param);
if (parameter) {
props.push({
name: parameter.name,
desc: ((_a = parameter.description) !== null && _a !== void 0 ? _a : '').replace(config_2.lineBreakReg, ''),
required: parameter.required || false,
type: this.getType(parameter.schema),
});
}
});
// parameters may be in path
(_d = pathItem.parameters) === null || _d === void 0 ? void 0 : _d.forEach((param) => {
var _a;
const parameter = this.resolveParameterRef(param);
if (parameter) {
props.push({
name: parameter.name,
desc: ((_a = parameter.description) !== null && _a !== void 0 ? _a : '').replace(config_2.lineBreakReg, ''),
required: parameter.required,
type: this.getType(parameter.schema),
});
}
});
const typeName = this.getFunctionParamsTypeName(Object.assign(Object.assign({}, operationObject), { method, path: pathKey }));
if (props.length > 0 && typeName) {
lastTypes.push({
typeName,
type: 'Record<string, unknown>',
props: [props],
isEnum: false,
});
}
});
});
(0, lodash_1.keys)(schemas).forEach((schemaKey) => {
var _a;
const schema = schemas[schemaKey];
// 判断哪些 schema 需要添加进 type, schemas 渲染数组
if (!(schema === null || schema === void 0 ? void 0 : schema.isAllowed)) {
return;
}
const result = this.resolveObject(schema);
const getDefinesType = () => {
if (result === null || result === void 0 ? void 0 : result.type) {
return schema.type === 'object'
? config_1.SchemaObjectType.object
: config_2.numberEnum.includes(result.type)
? config_1.SchemaObjectType.number
: result.type;
}
return 'Record<string, unknown>';
};
// 解析 props 属性中的枚举
if ((0, lodash_1.isArray)(result.props) && result.props.length > 0) {
(0, lodash_1.forEach)(result.props[0], (item) => {
if (item.enum) {
const enumObj = this.resolveEnumObject(item);
lastTypes.push({
typeName: `${(0, lodash_1.upperFirst)(item.name)}Enum`,
type: enumObj.type,
props: [],
isEnum: enumObj.isEnum,
displayLabelFuncName: (0, util_1.camelCase)(`display-${item.name}-Enum`),
enumLabelType: enumObj.enumLabelType,
description: enumObj.description,
});
}
});
}
const isEnum = result.isEnum;
const typeName = (0, util_2.resolveTypeName)(schemaKey);
if (typeName) {
lastTypes.push({
typeName,
type: getDefinesType(),
props: (result.props || []),
isEnum,
displayLabelFuncName: isEnum
? (0, util_1.camelCase)(`display-${typeName}-Enum`)
: '',
enumLabelType: isEnum ? result.enumLabelType : '',
description: result.description,
});
}
if (this.config.isGenJsonSchemas) {
this.schemaList.push({
typeName: `$${(0, lodash_1.lowerFirst)((0, util_2.resolveTypeName)(schemaKey))}`,
type: JSON.stringify((0, patchSchema_1.patchSchema)(schema, (_a = this.openAPIData.components) === null || _a === void 0 ? void 0 : _a.schemas)),
});
}
});
return lastTypes === null || lastTypes === void 0 ? void 0 : lastTypes.sort((a, b) => a.typeName.localeCompare(b.typeName)); // typeName排序
}
getServiceTPConfigs() {
return (0, lodash_1.keys)(this.apiData)
.map((tag, index) => {
var _a, _b;
// functionName tag 级别防重
const tmpFunctionRD = {};
const genParams = this.apiData[tag]
.filter((api) =>
// 暂不支持变量, path 需要普通前缀请使用例如: apiPrefix: "`api`", path 需要变量前缀请使用例如: apiPrefix: "api"
!api.path.includes('${'))
.map((api) => {
var _a, _b, _c, _d, _e, _f;
const newApi = api;
try {
const params = this.getParamsTP(newApi.parameters, newApi.path) || {};
const body = this.getBodyTP(newApi.requestBody, this.config.namespace);
const bodyWithoutNamespace = this.getBodyTP(newApi.requestBody);
const response = this.getResponseTP(newApi.responses);
const file = this.getFileTP(newApi.requestBody);
let formData = false;
if (((_a = body === null || body === void 0 ? void 0 : body.mediaType) === null || _a === void 0 ? void 0 : _a.includes('form-data')) || file) {
formData = true;
}
let functionName = this.getFunctionName(newApi);
if (functionName && tmpFunctionRD[functionName]) {
functionName = `${functionName}_${(tmpFunctionRD[functionName] += 1)}`;
}
else if (functionName) {
tmpFunctionRD[functionName] = 1;
}
if (body === null || body === void 0 ? void 0 : body.isAnonymous) {
const bodyName = (0, lodash_1.upperFirst)(`${functionName}Body`);
this.interfaceTPConfigs.push({
typeName: bodyName,
type: bodyWithoutNamespace === null || bodyWithoutNamespace === void 0 ? void 0 : bodyWithoutNamespace.type,
isEnum: false,
props: [],
});
body.type = `${this.config.namespace}.${bodyName}`;
}
if (response === null || response === void 0 ? void 0 : response.isAnonymous) {
const responseName = (0, lodash_1.upperFirst)(`${functionName}Response`);
this.interfaceTPConfigs.push({
typeName: responseName,
type: response === null || response === void 0 ? void 0 : response.type,
isEnum: false,
props: [],
});
response.type = `${this.config.namespace}.${responseName}`;
}
const responsesType = this.getResponsesType(newApi.responses, functionName);
// 如果有多个响应类型,生成对应的类型定义
if (responsesType) {
this.interfaceTPConfigs.push({
typeName: (0, lodash_1.upperFirst)(`${functionName}Responses`),
type: responsesType,
isEnum: false,
props: [],
});
}
let formattedPath = newApi.path.replace(/:([^/]*)|{([^}]*)}/gi, (_, str, str2) => `$\{${str || str2}}`);
// 为 path 中的 params 添加 alias
const escapedPathParams = (0, lodash_1.map)(params.path, (item, index) => (Object.assign(Object.assign({}, item), { alias: `param${index}` })));
if (escapedPathParams.length) {
escapedPathParams.forEach((param) => {
formattedPath = formattedPath.replace(`$\{${param.name}}`, `$\{${param.alias}}`);
});
}
const finalParams = escapedPathParams && escapedPathParams.length
? Object.assign(Object.assign({}, params), { path: escapedPathParams }) : params;
// 处理 query 中的复杂对象
if (finalParams === null || finalParams === void 0 ? void 0 : finalParams.query) {
finalParams.query = finalParams.query.map((item) => (Object.assign(Object.assign({}, item), { isComplexType: item.isObject })));
}
// 处理 api path 前缀
const getPrefixPath = () => {
if (!this.config.apiPrefix) {
return formattedPath;
}
// 静态 apiPrefix
const prefix = (0, lodash_1.isFunction)(this.config.apiPrefix)
? `${this.config.apiPrefix({
path: formattedPath,
method: newApi.method,
namespace: tag,
functionName,
})}`.trim()
: this.config.apiPrefix.trim();
if (!prefix) {
return formattedPath;
}
if (prefix.startsWith("'") ||
prefix.startsWith('"') ||
prefix.startsWith('`')) {
const finalPrefix = prefix.slice(1, prefix.length - 1);
const firstPath = formattedPath.split('/')[1];
if (firstPath === finalPrefix ||
`/${firstPath}` === finalPrefix) {
return formattedPath;
}
return `${finalPrefix}${formattedPath}`;
}
// prefix 变量
return `$\{${prefix}}${formattedPath}`;
};
return Object.assign(Object.assign({}, newApi), { functionName: this.config.isCamelCase
? (0, util_1.camelCase)(functionName)
: functionName, typeName: this.getFunctionParamsTypeName(newApi), path: getPrefixPath(), pathInComment: formattedPath.replace(/\*/g, '*'), apifoxRunLink: newApi === null || newApi === void 0 ? void 0 : newApi['x-run-in-apifox'], hasPathVariables: formattedPath.includes('{'), hasApiPrefix: !!this.config.apiPrefix, method: newApi.method,
// 如果 functionName 和 summary 相同,则不显示 summary
desc: functionName === newApi.summary
? (newApi.description || '').replace(config_2.lineBreakReg, '')
: [
newApi.summary,
newApi.description,
((_c = (_b = newApi.responses) === null || _b === void 0 ? void 0 : _b.default) === null || _c === void 0 ? void 0 : _c.description)
? `返回值: ${((_d = newApi.responses) === null || _d === void 0 ? void 0 : _d.default).description}`
: '',
]
.filter((s) => s)
.join(' ')
.replace(config_2.lineBreakReg, ''), hasHeader: !!(params === null || params === void 0 ? void 0 : params.header) || !!(body === null || body === void 0 ? void 0 : body.mediaType), params: finalParams, hasParams: Boolean((0, lodash_1.keys)(finalParams).length), options: ((_f = (_e = this.config.hook) === null || _e === void 0 ? void 0 : _e.customOptionsDefaultValue) === null || _f === void 0 ? void 0 : _f.call(_e, newApi)) || {}, body,
file, hasFormData: formData, response });
}
catch (error) {
console.error('[GenSDK] gen service param error:', error);
throw error;
}
})
// 排序下,防止git乱
.sort((a, b) => a.path.localeCompare(b.path));
const fileName = (0, util_2.replaceDot)(tag) || `api${index}`;
let className = fileName;
if ((_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customClassName) {
className = this.config.hook.customClassName(tag);
}
if (genParams.length) {
this.classNameList.push({
fileName: className,
controllerName: className,
});
}
return {
genType: this.config.isGenJavaScript ? config_2.LangType.js : config_2.LangType.ts,
className,
instanceName: `${(_b = fileName[0]) === null || _b === void 0 ? void 0 : _b.toLowerCase()}${fileName.slice(1)}`,
list: genParams,
};
})
.filter((item) => { var _a; return !!((_a = item === null || item === void 0 ? void 0 : item.list) === null || _a === void 0 ? void 0 : _a.length); });
}
genFileFromTemplate(fileName, type, params) {
try {
const template = this.getTemplate(type);
// 设置输出不转义
const env = nunjucks_1.default.configure({
autoescape: false,
});
env.addFilter('capitalizeFirst', util_2.capitalizeFirstLetter);
const destPath = (0, path_1.join)(this.config.serversPath, fileName);
const destCode = nunjucks_1.default.renderString(template, Object.assign({ disableTypeCheck: false }, params));
let mergerProps = {};
if ((0, fs_1.existsSync)(destPath)) {
mergerProps = {
srcPath: destPath,
};
}
else {
mergerProps = {
source: '',
};
}
if (this.config.full) {
return (0, file_1.writeFile)(this.config.serversPath, fileName, destCode);
}
const merger = new merge_1.Merger(mergerProps);
return (0, file_1.writeFile)(this.config.serversPath, fileName, merger.merge({
source: destCode,
}));
}
catch (error) {
console.error('[GenSDK] file gen fail:', fileName, 'type:', type);
throw error;
}
}
getTemplate(type) {
return (0, fs_1.readFileSync)((0, path_1.join)(this.config.templatesFolder, `${type}.njk`), 'utf8');
}
// 生成方法名 functionName
getFunctionName(data) {
// 获取路径相同部分
const pathBasePrefix = (0, util_2.getBasePrefix)((0, lodash_1.keys)(this.openAPIData.paths));
return this.config.hook && this.config.hook.customFunctionName
? this.config.hook.customFunctionName(data, pathBasePrefix)
: (0, util_1.camelCase)(`${(0, util_2.genDefaultFunctionName)(data.path, pathBasePrefix)}-using-${data.method}`);
// return this.config.hook && this.config.hook.customFunctionName
// ? this.config.hook.customFunctionName(data)
// : data.operationId
// ? resolveFunctionName(stripDot(data.operationId), data.method)
// : data.method + genDefaultFunctionName(data.path, pathBasePrefix);
}
getType(schemaObject, namespace) {
var _a, _b;
const customTypeHookFunc = (_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customType;
const schemas = (_b = this.openAPIData.components) === null || _b === void 0 ? void 0 : _b.schemas;
if (customTypeHookFunc) {
const type = customTypeHookFunc({
schemaObject,
namespace,
schemas,
originGetType: util_2.getDefaultType,
});
if (typeof type === 'string') {
return type;
}
}
return (0, util_2.getDefaultType)(schemaObject, namespace, schemas);
}
getFunctionParamsTypeName(data) {
var _a, _b, _c;
const namespace = this.config.namespace ? `${this.config.namespace}.` : '';
const typeName = ((_c = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.hook) === null || _b === void 0 ? void 0 : _b.customTypeName) === null || _c === void 0 ? void 0 : _c.call(_b, data)) || this.getFunctionName(data);
return (0, lodash_1.upperFirst)((0, util_2.resolveTypeName)(`${namespace}${typeName !== null && typeName !== void 0 ? typeName : data.operationId}Params`));
}
getBodyTP(requestBody, namespace) {
var _a;
const reqBody = this.resolveRefObject(requestBody);
if ((0, lodash_1.isEmpty)(reqBody)) {
return null;
}
const reqContent = reqBody.content;
if (!(0, lodash_1.isObject)(reqContent)) {
return null;
}
let mediaType = (0, lodash_1.keys)(reqContent)[0];
const schema = ((_a = reqContent[mediaType]) === null || _a === void 0 ? void 0 : _a.schema) || config_2.DEFAULT_SCHEMA;
if (mediaType === '*/*') {
mediaType = '';
}
// 如果 requestBody 有 required 属性,则正常展示;如果没有,默认非必填
const required = typeof (requestBody === null || requestBody === void 0 ? void 0 : requestBody.required) === 'boolean' ? requestBody.required : false;
const bodySchema = {
mediaType,
required,
type: this.getType(schema, namespace),
isAnonymous: false,
};
// 匿名 body 场景
if (!(0, util_2.isReferenceObject)(schema)) {
bodySchema.isAnonymous = true;
}
return bodySchema;
}
getFileTP(requestBody) {
var _a;
const reqBody = this.resolveRefObject(requestBody);
if ((_a = reqBody === null || reqBody === void 0 ? void 0 : reqBody.content) === null || _a === void 0 ? void 0 : _a['multipart/form-data']) {
const ret = this.resolveFileTP(reqBody.content['multipart/form-data'].schema);
return ret.length > 0 ? ret : null;
}
return null;
}
resolveFileTP(obj) {
var _a;
let ret = [];
const resolved = this.resolveObject(obj);
const props = (((_a = resolved.props) === null || _a === void 0 ? void 0 : _a.length) > 0 &&
resolved.props[0].filter((p) => p.format === 'binary' ||
p.format === 'base64' ||
(0, util_2.isBinaryArraySchemaObject)(p))) ||
[];
if (props.length > 0) {
ret = props.map((p) => {
// 这里 p.type 是自定义type, 注意别混淆
return {
title: p.name,
multiple: p.type === `${config_1.SchemaObjectType.array}` ||
p.type === `${config_1.SchemaObjectType.stringArray}`,
};
});
}
if (resolved.type) {
ret = [...ret, ...this.resolveFileTP(resolved.type)];
}
return ret;
}
getResponseTP(responses = {}) {
var _a;
const { components } = this.openAPIData;
const response = responses &&
this.resolveRefObject(responses['200'] || responses['201'] || responses.default);
const defaultResponse = {
mediaType: '*/*',
type: 'unknown',
isAnonymous: false,
};
if (!response) {
return defaultResponse;
}
const resContent = response.content;
const resContentMediaTypes = (0, lodash_1.keys)(resContent);
const mediaType = resContentMediaTypes.includes('application/json')
? 'application/json'
: resContentMediaTypes[0]; // 优先使用 application/json
if (!(0, lodash_1.isObject)(resContent) || !mediaType) {
return defaultResponse;
}
let schema = (resContent[mediaType].schema ||
config_2.DEFAULT_SCHEMA);
const responseSchema = {
mediaType,
type: 'unknown',
isAnonymous: false,
};
if ((0, util_2.isReferenceObject)(schema)) {
const refName = (0, util_2.getLastRefName)(schema.$ref);
const childrenSchema = components.schemas[refName];
if ((0, util_2.isNonArraySchemaObject)(childrenSchema) && this.config.dataFields) {
schema = (((_a = this.config.dataFields
.map((field) => childrenSchema.properties[field])
.filter(Boolean)) === null || _a === void 0 ? void 0 : _a[0]) ||
resContent[mediaType].schema ||
config_2.DEFAULT_SCHEMA);
}
responseSchema.type = this.getType(schema, this.config.namespace);
return responseSchema;
}
if ((0, util_2.isSchemaObject)(schema)) {
(0, lodash_1.keys)(schema.properties).map((fieldName) => {
var _a, _b;
schema.properties[fieldName]['required'] =
(_b = (_a = schema.required) === null || _a === void 0 ? void 0 : _a.includes(fieldName)) !== null && _b !== void 0 ? _b : false;
});
responseSchema.isAnonymous = true;
}
responseSchema.type = this.getType(schema, this.config.namespace);
return responseSchema;
}
/**
* 生成多状态码响应类型定义
* 将 OpenAPI 的 responses 对象转换为 TypeScript 类型定义
* 例如:{ 200: ResponseType, 400: unknown, 404: unknown }
*
* @param responses OpenAPI 响应对象
* @param functionName 函数名称,用于生成主响应类型名称
* @returns 多状态码响应类型定义字符串,如果没有响应则返回 null
*/
getResponsesType(responses = {}, functionName) {
if ((0, lodash_1.isEmpty)(responses) ||
~(0, lodash_1.findIndex)(this.interfaceTPConfigs, (item) => item.typeName === (0, lodash_1.upperFirst)(`${functionName}Responses`))) {
return null;
}
const { components } = this.openAPIData;
// 生成主响应类型名称
const mainResponseTypeName = (0, lodash_1.upperFirst)(`${functionName}Response`);
const responseEntries = this.parseResponseEntries(responses, components);
const responseTypes = responseEntries.map(({ statusCode, type, description = '' }) => {
var _a;
// 检查是否已存在对应的主响应类型,如果存在则复用,避免重复定义
const existType = this.interfaceTPConfigs.find((item) => item.typeName === mainResponseTypeName);
const lastType = existType ? mainResponseTypeName : type;
// 格式化描述文本,让描述支持换行
const formattedDescription = config_2.lineBreakReg.test(description)
? (_a = description.split('\n')) === null || _a === void 0 ? void 0 : _a.join('\n * ')
: description;
// 生成带注释的类型定义
return formattedDescription
? ` /**\n * ${formattedDescription}\n */\n ${statusCode}: ${lastType};`
: ` ${statusCode}: ${lastType};`;
});
// 返回完整的对象类型定义
return `{\n${responseTypes.join('\n')}\n}`;
}
/**
* 解析响应条目,提取每个状态码对应的类型和描述信息
*
* @param responses OpenAPI 响应对象
* @param components OpenAPI 组件对象,用于解析引用类型
* @returns 响应条目数组,包含状态码、类型和描述
*/
parseResponseEntries(responses, components) {
return (0, lodash_1.keys)(responses).map((statusCode) => {
const response = this.resolveRefObject(responses[statusCode]);
if (!response) {
return { statusCode, type: 'unknown', description: '' };
}
const responseType = this.getResponseTypeFromContent(response, components);
const description = response.description || '';
return { statusCode, type: responseType, description };
});
}
/**
* 从响应内容中提取 TypeScript 类型
* 处理不同的媒体类型和 schema 类型
*
* @param response 响应对象
* @param components OpenAPI 组件对象
* @returns TypeScript 类型字符串
*/
getResponseTypeFromContent(response, components) {
var _a;
if (!response.content) {
return 'unknown';
}
const resContent = response.content;
const resContentMediaTypes = (0, lodash_1.keys)(resContent);
const mediaType = resContentMediaTypes.includes('application/json')
? 'application/json'
: resContentMediaTypes[0];
if (!(0, lodash_1.isObject)(resContent) || !mediaType) {
return 'unknown';
}
let schema = (resContent[mediaType].schema ||
config_2.DEFAULT_SCHEMA);
if ((0, util_2.isReferenceObject)(schema)) {
const refName = (0, util_2.getLastRefName)(schema.$ref);
const childrenSchema = components.schemas[refName];
// 如果配置了 dataFields,尝试从指定字段提取类型
if ((0, util_2.isNonArraySchemaObject)(childrenSchema) && this.config.dataFields) {
schema = (((_a = this.config.dataFields
.map((field) => childrenSchema.properties[field])
.filter(Boolean)) === null || _a === void 0 ? void 0 : _a[0]) ||
resContent[mediaType].schema ||
config_2.DEFAULT_SCHEMA);
}
return this.getType(schema);
}
else if ((0, util_2.isSchemaObject)(schema)) {
// 设置属性的 required 状态
(0, lodash_1.keys)(schema.properties).map((fieldName) => {
var _a, _b;
schema.properties[fieldName]['required'] =
(_b = (_a = schema.required) === null || _a === void 0 ? void 0 : _a.includes(fieldName)) !== null && _b !== void 0 ? _b : false;
});
return this.getType(schema);
}
else {
return this.getType(schema);
}
}
getParamsTP(parameters = [], path = null) {
const templateParams = {};
if (parameters === null || parameters === void 0 ? void 0 : parameters.length) {
(0, lodash_1.forEach)(config_2.parametersIn, (source) => {
const params = parameters
.map((p) => this.resolveRefObject(p))
.filter((p) => p.in === source)
.map((p) => {
var _a, _b, _c, _d, _e;
const isDirectObject = (((_a = p.schema) === null || _a === void 0 ? void 0 : _a.type) === 'object' ||
p.type) === 'object';
const refName = (0, util_2.getLastRefName)(((_b = p.schema) === null || _b === void 0 ? void 0 : _b.$ref) ||
p.$ref);
const deRefObj = (0, lodash_1.entries)((_c = this.openAPIData.components) === null || _c === void 0 ? void 0 : _c.schemas).find(([k]) => k === refName) || [];
const isRefObject = ((_d = deRefObj[1]) === null || _d === void 0 ? void 0 : _d.type) === 'object' &&
!(0, lodash_1.isEmpty)((_e = deRefObj[1]) === null || _e === void 0 ? void 0 : _e.properties);
return Object.assign(Object.assign({}, p), { isObject: isDirectObject || isRefObject, type: this.getType(p.schema || config_2.DEFAULT_SCHEMA, this.config.namespace) });
});
if (params.length) {
templateParams[source] = params;
}
});
}
if (path && path.length > 0) {
const regex = /\{(\w+)\}/g;
templateParams.path = templateParams.path || [];
let match = null;
while ((match = regex.exec(path))) {
if (!templateParams.path.some((p) => p.name === match[1])) {
templateParams.path.push(Object.assign(Object.assign({}, config_2.DEFAULT_PATH_PARAM), { name: match[1] }));
}
}
// 如果 path 没有内容,则将删除 path 参数,避免影响后续的 hasParams 判断
if (!templateParams.path.length)
delete templateParams.path;
}
return templateParams;
}
resolveObject(schemaObject) {
// 不使用 schemaObject: ISchemaObject = {}
schemaObject = schemaObject !== null && schemaObject !== void 0 ? schemaObject : {};
// 引用类型
if ((0, util_2.isReferenceObject)(schemaObject)) {
return this.resolveRefObject(schemaObject);
}
// 枚举类型
if (schemaObject.enum) {
return this.resolveEnumObject(schemaObject);
}
// 继承类型
if (schemaObject.allOf && schemaObject.allOf.length) {
return this.resolveAllOfObject(schemaObject);
}
// 对象类型
if (schemaObject.properties) {
return this.resolveProperties(schemaObject);
}
// 数组类型
if ((0, util_2.isArraySchemaObject)(schemaObject)) {
return this.resolveArray(schemaObject);
}
return schemaObject;
}
resolveArray(schemaObject) {
var _a;
if ((0, util_2.isReferenceObject)(schemaObject.items)) {
const refName = (0, util_2.getRefName)(schemaObject.items);
return {
type: `${refName}[]`,
};
}
else if ((_a = schemaObject.items) === null || _a === void 0 ? void 0 : _a.enum) {
return {
type: this.getType(schemaObject, this.config.namespace),
};
}
// 这里需要解析出具体属性,但由于 parser 层还不确定,所以暂时先返回 unknown[]
return { type: 'unknown[]' };
}
resolveProperties(schemaObject) {
return {
props: [this.getProps(schemaObject)],
};
}
resolveEnumObject(schemaObject) {
var _a;
const enumArray = schemaObject.enum;
let enumStr = '';
let enumLabelTypeStr = '';
if (config_2.numberEnum.includes(schemaObject.type) || (0, util_2.isAllNumber)(enumArray)) {
if (this.config.isSupportParseEnumDesc && schemaObject.description) {
const enumMap = (0, util_2.parseDescriptionEnum)(schemaObject.description);
enumStr = `{${(0, lodash_1.map)(enumArray, (value) => {
const enumLabel = enumMap.get(Number(value));
return `${enumLabel}=${Number(value)}`;
}).join(',')}}`;
}
else {
enumStr = `{${(0, lodash_1.map)(enumArray, (value) => `"NUMBER_${value}"=${Number(value)}`).join(',')}}`;
}
}
else if ((0, util_2.isAllNumeric)(enumArray)) {
enumStr = `{${(0, lodash_1.map)(enumArray, (value) => `"STRING_NUMBER_${value}"="${value}"`).join(',')}}`;
}
else {
enumStr = `{${(0, lodash_1.map)(enumArray, (value) => `"${value}"="${value}"`).join(',')}}`;
}
// 翻译枚举
if (schemaObject['x-enum-varnames'] && schemaObject['x-enum-comments']) {
enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value, index) => {
const enumKey = schemaObject['x-enum-varnames'][index];
return `${value}:"${schemaObject['x-enum-comments'][enumKey]}"`;
}).join(',')}}`;
}
else if ((_a = schemaObject === null || schemaObject === void 0 ? void 0 : schemaObject['x-apifox']) === null || _a === void 0 ? void 0 : _a['enumDescriptions']) {
enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => {
const enumLabel = schemaObject['x-apifox']['enumDescriptions'][value];
return `${value}:"${enumLabel}"`;
}).join(',')}}`;
}
else if (schemaObject === null || schemaObject === void 0 ? void 0 : schemaObject['x-apifox-enum']) {
enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => {
var _a;
const enumLabel = (_a = (0, lodash_1.find)(schemaObject['x-apifox-enum'], (item) => item.value === value)) === null || _a === void 0 ? void 0 : _a.description;
return `${value}:"${enumLabel}"`;
}).join(',')}}`;
}
else {
if (config_2.numberEnum.includes(schemaObject.type) || (0, util_2.isAllNumber)(enumArray)) {
if (this.config.isSupportParseEnumDesc && schemaObject.description) {
const enumMap = (0, util_2.parseDescriptionEnum)(schemaObject.description);
enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => {
const enumLabel = enumMap.get(Number(value));
return `${Number(value)}:"${enumLabel}"`;
}).join(',')}}`;
}
else {
enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => `${Number(value)}:"NUMBER_${value}"`).join(',')}}`;
}
}
else if ((0, util_2.isAllNumeric)(enumArray)) {
enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => `"${value}":"STRING_NUMBER_${value}"`).join(',')}}`;
}
else {
enumLabelTypeStr = `{${(0, lodash_1.map)(enumArray, (value) => `"${value}":"${value}"`).join(','