UNPKG

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
"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, '&#42;') + '\n' : formattedPath.replace(/\*/g, '&#42;'); 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