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
917 lines • 62.2 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 Helper = tslib_1.__importStar(require("./serviceGeneratorHelper"));
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.typeModuleMap = new Map();
// 从原始 schema key 到最终类型名称的映射(用于处理重名情况)
this.schemaKeyToTypeNameMap = new Map();
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);
// 先处理重复的 typeName,建立类型名称映射(在生成 service controller 之前)
this.interfaceTPConfigs = this.getInterfaceTPConfigs();
this.schemaKeyToTypeNameMap = (0, util_2.handleDuplicateTypeNames)(this.interfaceTPConfigs);
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 文件内可能存在的语法错误');
}
}
// 生成 ts 类型声明
if (!isGenJavaScript) {
if (this.config.isSplitTypesByModule) {
// 按模块拆分类型文件
const { moduleTypes, commonTypes, enumTypes } = this.groupTypesByModule();
// 生成枚举文件
if (enumTypes.length > 0) {
this.genFileFromTemplate(`${config_2.enumFileName}.ts`, config_2.TypescriptFileType.enum, {
list: enumTypes,
});
}
// 生成公共类型文件
if (commonTypes.length > 0) {
// 分析公共类型需要导入的枚举
const { enumImports } = this.getModuleImports(commonTypes, [], // 公共类型不依赖其他公共类型
enumTypes);
this.genFileFromTemplate(`${config_2.commonTypeFileName}.ts`, config_2.TypescriptFileType.moduleType, {
nullable: this.config.nullable,
list: commonTypes,
enumImports, // 公共类型需要导入的枚举
});
}
// 生成每个模块的类型文件
moduleTypes.forEach((types, moduleName) => {
if (types.length > 0) {
// 分析该模块需要导入哪些类型
const { commonImports, enumImports } = this.getModuleImports(types, commonTypes, enumTypes);
this.genFileFromTemplate(`${moduleName}.type.ts`, config_2.TypescriptFileType.moduleType, {
nullable: this.config.nullable,
list: types,
commonImports, // 需要导入的公共类型
enumImports, // 需要导入的枚举类型
});
}
});
// 生成 types.ts 作为统一导出入口
this.genFileFromTemplate(`${config_2.interfaceFileName}.ts`, config_2.TypescriptFileType.typeIndex, {
hasEnumTypes: enumTypes.length > 0,
hasCommonTypes: commonTypes.length > 0,
moduleList: Array.from(moduleTypes.keys()).filter((moduleName) => {
const types = moduleTypes.get(moduleName);
return types ? types.length > 0 : false;
}),
});
}
else {
// 在生成文件前,对 interfaceTPConfigs 进行排序(因为 getServiceTPConfigs 可能添加了新类型)
this.interfaceTPConfigs.sort((a, b) => a.typeName.localeCompare(b.typeName));
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) || [];
// 记录每个 schema 被哪些模块使用(用于拆分类型)
const schemaUsageMap = new Map();
// 强行替换掉请求参数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);
// 如果启用了类型拆分,记录每个 schema 被哪些模块使用
if (this.config.isSplitTypesByModule) {
const pathItemStr = JSON.stringify(pathItem);
const refRegex = /"\$ref":\s*"#\/components\/schemas\/([^"]+)"/g;
let match;
while ((match = refRegex.exec(pathItemStr)) !== null) {
const schemaName = match[1];
if (!schemaName)
continue;
tags.forEach((tag) => {
var _a;
if (tag) {
const tagTypeName = (0, util_2.resolveTypeName)(tag);
const tagKey = this.config.isCamelCase
? (0, util_1.camelCase)(tagTypeName)
: (0, lodash_1.lowerFirst)(tagTypeName);
const className = ((_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customClassName)
? this.config.hook.customClassName(tag)
: (0, util_2.replaceDot)(tagKey);
if (!schemaUsageMap.has(schemaName)) {
schemaUsageMap.set(schemaName, new Set());
}
const moduleSet = schemaUsageMap.get(schemaName);
if (moduleSet) {
moduleSet.add(className);
}
}
});
}
}
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,
});
// 记录 Params 类型归属(属于对应的 tag/module)
if (this.config.isSplitTypesByModule) {
tags.forEach((tag) => {
var _a;
if (tag) {
const tagTypeName = (0, util_2.resolveTypeName)(tag);
const tagKey = this.config.isCamelCase
? (0, util_1.camelCase)(tagTypeName)
: (0, lodash_1.lowerFirst)(tagTypeName);
const className = ((_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customClassName)
? this.config.hook.customClassName(tag)
: (0, util_2.replaceDot)(tagKey);
this.markTypeUsedByModule(typeName, className);
}
});
}
}
});
});
(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);
const enumTypeName = `${(0, lodash_1.upperFirst)(item.name)}Enum`;
lastTypes.push({
typeName: enumTypeName,
type: enumObj.type,
props: [],
isEnum: enumObj.isEnum,
displayLabelFuncName: (0, util_1.camelCase)(`display-${item.name}-Enum`),
enumLabelType: enumObj.enumLabelType,
description: enumObj.description,
});
// 记录枚举类型归属(继承父 schema 的归属)
if (this.config.isSplitTypesByModule &&
schemaUsageMap.has(schemaKey)) {
const moduleSet = schemaUsageMap.get(schemaKey);
if (moduleSet) {
moduleSet.forEach((className) => {
this.markTypeUsedByModule(enumTypeName, className);
});
}
}
}
});
}
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,
originalSchemaKey: schemaKey, // 保存原始 schema key,用于处理重名后的类型映射
});
// 记录 schema 类型归属
if (this.config.isSplitTypesByModule && schemaUsageMap.has(schemaKey)) {
const moduleSet = schemaUsageMap.get(schemaKey);
if (moduleSet) {
moduleSet.forEach((className) => {
this.markTypeUsedByModule(typeName, className);
});
}
}
}
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;
// return 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 = {};
// 获取当前模块的 className (用于记录类型归属)
const fileName = (0, util_2.replaceDot)(tag) || `api${index}`;
const className = ((_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customClassName)
? this.config.hook.customClassName(tag)
: fileName;
const genParams = this.apiData[tag]
.filter((api) =>
// 暂不支持变量, path 需要普通前缀请使用例如: apiPrefix: "`api`", path 需要变量前缀请使用例如: apiPrefix: "api"
!api.path.includes('${'))
.map((api) => {
var _a, _b, _c, _d, _e;
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: [],
});
// 记录类型归属
if (this.config.isSplitTypesByModule) {
this.markTypeUsedByModule(bodyName, className);
}
body.type = `${this.config.namespace}.${bodyName}`;
}
if (response === null || response === void 0 ? void 0 : response.isAnonymous) {
const responseName = (0, lodash_1.upperFirst)(`${functionName}Response`);
// 使用正则表达式移除 response?.type 中包含 this.config.namespace 的部分,isAnonymous模式不需要 this.config.namespace 前缀
const cleanType = ((_b = response === null || response === void 0 ? void 0 : response.type) === null || _b === void 0 ? void 0 : _b.includes(`${this.config.namespace}.`))
? (_c = response === null || response === void 0 ? void 0 : response.type) === null || _c === void 0 ? void 0 : _c.replace(new RegExp(`${this.config.namespace}\\.`, 'g'), '')
: (response === null || response === void 0 ? void 0 : response.type) || '';
this.interfaceTPConfigs.push({
typeName: responseName,
type: cleanType,
isEnum: false,
props: [],
});
// 记录类型归属
if (this.config.isSplitTypesByModule) {
this.markTypeUsedByModule(responseName, className);
}
response.type = `${this.config.namespace}.${responseName}`;
}
const responsesType = this.getResponsesType(newApi.responses, functionName);
// 如果有多个响应类型,生成对应的类型定义
if (responsesType) {
const responsesTypeName = (0, lodash_1.upperFirst)(`${functionName}Responses`);
this.interfaceTPConfigs.push({
typeName: responsesTypeName,
type: responsesType,
isEnum: false,
props: [],
});
// 记录类型归属
if (this.config.isSplitTypesByModule) {
this.markTypeUsedByModule(responsesTypeName, className);
}
}
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(Object.assign({}, (() => {
var _a, _b, _c;
const rawDesc = functionName === newApi.summary
? newApi.description || ''
: [
newApi.summary,
newApi.description,
((_b = (_a = newApi.responses) === null || _a === void 0 ? void 0 : _a.default) === null || _b === void 0 ? void 0 : _b.description)
? `返回值: ${((_c = newApi.responses) === null || _c === void 0 ? void 0 : _c.default).description}`
: '',
]
.filter((s) => s)
.join(' ');
const hasLineBreak = config_2.lineBreakReg.test(rawDesc);
// 格式化描述文本,让描述支持换行
const desc = hasLineBreak
? '\n * ' + rawDesc.split('\n').join('\n * ') + '\n *'
: rawDesc;
// 如果描述有换行,pathInComment 结尾加换行使 */ 单独一行
const pathInComment = hasLineBreak
? formattedPath.replace(/\*/g, '*') + '\n'
: formattedPath.replace(/\*/g, '*');
const originApifoxRunLink = newApi === null || newApi === void 0 ? void 0 : newApi['x-run-in-apifox'];
const apifoxRunLink = hasLineBreak && originApifoxRunLink
? ' * ' + originApifoxRunLink + '\n'
: originApifoxRunLink;
return { desc, pathInComment, apifoxRunLink };
})()), newApi), { functionName: this.config.isCamelCase
? (0, util_1.camelCase)(functionName)
: functionName, typeName: this.getFunctionParamsTypeName(newApi), path: getPrefixPath(), hasPathVariables: formattedPath.includes('{'), hasApiPrefix: !!this.config.apiPrefix, method: newApi.method, 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: ((_e = (_d = this.config.hook) === null || _d === void 0 ? void 0 : _d.customOptionsDefaultValue) === null || _e === void 0 ? void 0 : _e.call(_d, 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));
// fileName 和 className 已在方法开始时声明
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) {
var _a;
try {
const template = this.getTemplate(type);
// 应用 customRenderTemplateData hook (如果存在)
let processedParams = Object.assign({}, params);
const customListHooks = (_a = this.config.hook) === null || _a === void 0 ? void 0 : _a.customRenderTemplateData;
if (customListHooks && params.list) {
try {
const context = {
fileName,
params: processedParams,
};
let processedList = params.list;
// 根据不同的文件类型调用相应的 hook 函数
switch (type) {
case config_2.TypescriptFileType.serviceController:
if (customListHooks.serviceController) {
processedList = customListHooks.serviceController(params.list, context);
}
break;
case config_2.TypescriptFileType.reactQuery:
if (customListHooks.reactQuery) {
processedList = customListHooks.reactQuery(params.list, context);
}
break;
case config_2.TypescriptFileType.interface:
if (customListHooks.interface) {
processedList = customListHooks.interface(params.list, context);
}
break;
case config_2.TypescriptFileType.displayEnumLabel:
if (customListHooks.displayEnumLabel) {
processedList = customListHooks.displayEnumLabel(params.list, context);
}
break;
case config_2.TypescriptFileType.displayTypeLabel:
if (customListHooks.displayTypeLabel) {
processedList = customListHooks.displayTypeLabel(params.list, context);
}
break;
case config_2.TypescriptFileType.schema:
if (customListHooks.schema) {
processedList = customListHooks.schema(params.list, context);
}
break;
case config_2.TypescriptFileType.serviceIndex:
if (customListHooks.serviceIndex) {
processedList = customListHooks.serviceIndex(params.list, context);
}
break;
}
if (processedList !== params.list) {
processedParams = Object.assign(Object.assign({}, processedParams), { list: processedList });
this.log(`customRenderTemplateData hook applied for ${type}: ${fileName}`);
}
}
catch (error) {
console.error(`[GenSDK] customRenderTemplateData hook error for ${type}:`, error);
this.log(`customRenderTemplateData hook failed for ${type}, using original list`);
// 发生错误时使用原始参数继续执行
}
}
// 设置输出不转义
const env = nunjucks_1.default.configure({
autoescape: false,
});
env.addFilter('capitalizeFirst', util_2.capitalizeFirstLetter);
env.addFilter('escapeJs', util_2.escapeStringForJs);
const destPath = (0, path_1.join)(this.config.serversPath, fileName);
const destCode = nunjucks_1.default.renderString(template, Object.assign({ disableTypeCheck: false }, processedParams));
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;
const schemaKeyToTypeNameMap = this.schemaKeyToTypeNameMap;
if (customTypeHookFunc) {
// 为自定义 hook 提供支持映射的 originGetType
const originGetTypeWithMapping = (schema, ns, s) => (0, util_2.getDefaultType)(schema, ns, s, schemaKeyToTypeNameMap);
const type = customTypeHookFunc({
schemaObject,
namespace,
schemas,
originGetType: originGetTypeWithMapping,
});
if (typeof type === 'string') {
return type;
}
}
return (0, util_2.getDefaultType)(schemaObject, namespace, schemas, schemaKeyToTypeNameMap);
}
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) {
return Helper.resolveFileTP({
obj,
resolveObjectFunc: (schemaObject) => this.resolveObject(schemaObject),
});
}
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,
responseType: undefined,
};
if (!response) {
return defaultResponse;
}
const resContent = response.content;
const resContentMediaTypes = (0, lodash_1.keys)(resContent);
// 检测二进制流媒体类型
const binaryMediaTypes = (0, util_2.getBinaryMediaTypes)(this.config.binaryMediaTypes);
const binaryMediaType = resContentMediaTypes.find((mediaType) => (0, util_2.isBinaryMediaType)(mediaType, binaryMediaTypes));
const mediaType = resContentMediaTypes.includes('application/json')
? 'application/json'
: binaryMediaType || 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,
responseType: undefined,
};
// 如果是二进制媒体类型,直接返回二进制类型
if ((0, util_2.isBinaryMediaType)(mediaType, binaryMediaTypes)) {
const binaryType = (0, util_2.getBinaryResponseType)();
responseSchema.type = binaryType;
// 自动为二进制响应添加 responseType 配置
responseSchema.responseType = (0, util_2.getAxiosResponseType)(binaryType);
return responseSchema;
}
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_SCHE