@api-helper/cli
Version:
根据 Swagger 的接口定义生成 TypeScript/JavaScript 的接口类型及其请求函数代码。
991 lines (981 loc) • 48.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ora_1 = __importDefault(require("ora"));
const fast_glob_1 = __importDefault(require("fast-glob"));
// import os from 'node:os';
const pinyin_pro_1 = require("pinyin-pro");
// import { Worker } from 'node:worker_threads';
const path_1 = require("path");
const fs_extra_1 = __importStar(require("fs-extra"));
const template_1 = require("@api-helper/template");
const micromatch_1 = __importDefault(require("micromatch"));
const lodash_1 = require("lodash");
const util_1 = require("@api-helper/core/lib/utils/util");
const render_all_api_1 = require("@api-helper/template/lib/render-all-api");
const util_2 = require("../tools/util");
// import './worker-thread';
const locales_1 = __importDefault(require("../../lib/locales"));
const logger_1 = __importDefault(require("../../lib/tools/logger"));
const lib_1 = require("../../lib");
const const_1 = require("../../lib/service/const");
const parser_yapi_plugin_1 = __importDefault(require("./parser-plugins/parser-yapi-plugin"));
const parser_swagger_plugin_1 = __importDefault(require("./parser-plugins/parser-swagger-plugin"));
const prompts = require('prompts');
// const cpus = os.cpus().length;
// const CHUNK_NUM = 30;
let outputDiscardWarn = false;
class Service {
constructor(options = {}, isTestEnv = false) {
this.startDate = 0;
this.parserPlugins = [
new parser_yapi_plugin_1.default(),
new parser_swagger_plugin_1.default(),
];
this.apiHelperCLIRunningData = {
selectedDocumentEtag: []
};
this.selectedDocumentEtagTemp = [];
this.hasApiHelperCLIRunningData = false;
this.tempFolder = (0, path_1.join)(__dirname, './.cache.server');
this.isTestEnv = isTestEnv;
this.configFilePath = options.config;
this.constructorOptions = options;
this.locales = new locales_1.default();
}
run() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
yield this.locales.init();
this.startDate = Date.now();
const configList = yield this.getConfigFile();
const len = configList.length;
// 添加解析插件
this.injectParserPlugins(configList);
let hasGen = false;
for (let i = 0; i < configList.length; i++) {
try {
const config = configList[i];
if (len > 1) {
logger_1.default.info(`———————————————————— \x1B[34m${this.locales.$t('正在处理').replace('%0', String(i))}\x1B[0m ————————————————————`);
}
// 缺少输出路径跳过该项
if (!config.outputPath && !config.outputFilePath) {
logger_1.default.error(this.locales.$t('Config[%0]: 缺少 outputPath 配置').replace('%0', String(i)));
continue;
}
// 缺少文档配置,跳过该项
if (!((_a = config.documentServers) === null || _a === void 0 ? void 0 : _a.length)) {
logger_1.default.error(this.locales.$t('Config[%0]: 缺少 documentServers 配置').replace('%0', String(i)));
continue;
}
const parserPluginRunResult = yield this.parserDocument(config.documentServers, config);
const chooseDocumentList = yield this.chooseDocument(parserPluginRunResult, config, i);
const codes = yield this.genCode(config, chooseDocumentList);
yield this.output(config, codes);
hasGen = true;
}
catch (_b) { }
}
hasGen && this.setApiHelperCLIRunningData();
yield this.clear();
});
}
clear() {
return __awaiter(this, void 0, void 0, function* () {
(0, util_2.removeFolder)(this.tempFolder);
});
}
injectParserPlugins(configList) {
var _a;
for (const config of configList) {
const parserPlugins = (_a = config === null || config === void 0 ? void 0 : config.parserPlugins) !== null && _a !== void 0 ? _a : [];
for (const parserPlugin of parserPlugins) {
const parserPluginMap = this.getParserPluginMap();
if (parserPluginMap.has(parserPlugin.name)) {
logger_1.default.warn(this.locales.$t('插件已经注册').replace('%0', parserPlugin.name));
continue;
}
this.parserPlugins.push(parserPlugin);
}
}
}
getParserPluginMap() {
const map = new Map();
for (const parserPlugin of this.parserPlugins) {
map.set(parserPlugin.name, parserPlugin);
}
return map;
}
getApiHelperCLIRunningData(configFilePath) {
var _a, _b;
try {
const content = fs_extra_1.default.readFileSync(configFilePath).toString();
const apiHelperCLIRunningData = (_b = (_a = content.match(/\/\/\s==ApiHelperCLIRunningData==([\s\S]*)\/\/\s==\/ApiHelperCLIRunningData==/im)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : '';
if (!apiHelperCLIRunningData) {
return;
}
this.hasApiHelperCLIRunningData = true;
const getAllValue = (str, objectFields = []) => {
var _a;
const result = {};
/*// @字段名称 值 */
const fields = (_a = str.match(/\/\/\s+@(.*)/g)) !== null && _a !== void 0 ? _a : [];
fields.forEach((f) => {
var _a, _b, _c;
const key = (_a = f.match(/\/\/\s+@(.*?)(\s+|$)/)) === null || _a === void 0 ? void 0 : _a[1];
const value = (_c = (_b = f.match(/\/\/\s+@.*?\s+(.*)/)) === null || _b === void 0 ? void 0 : _b[1]) !== null && _c !== void 0 ? _c : '';
if (objectFields.includes(key) && value) {
let temp = [];
try {
temp = JSON.parse(value);
temp = Array.isArray(temp) ? temp : [];
}
catch (_d) { }
result[key] = temp;
}
else {
result[key] = value;
}
});
return result;
};
this.apiHelperCLIRunningData = Object.assign(Object.assign({}, this.apiHelperCLIRunningData), getAllValue(apiHelperCLIRunningData, ['selectedDocumentEtag']));
}
catch (e) {
logger_1.default.warn(e === null || e === void 0 ? void 0 : e.message);
}
}
setApiHelperCLIRunningData() {
try {
if (!this.configFileAbsolutePath) {
return;
}
const toComment = (obj) => {
const temp = [];
for (const [key, value] of Object.entries(obj)) {
let val = value;
if (typeof value === 'object' && value !== null) {
val = JSON.stringify(value);
}
else if (value === undefined) {
val = '';
}
temp.push(`// @${key} ${val}`);
}
return `// ==ApiHelperCLIRunningData==
${temp.join('\n')}
// ==/ApiHelperCLIRunningData==`;
};
let notChange = (0, lodash_1.isEqual)(this.selectedDocumentEtagTemp, this.apiHelperCLIRunningData.selectedDocumentEtag);
const removeBreakLine = (str) => {
str = str !== null && str !== void 0 ? str : '';
while (str.endsWith('\n') || str.endsWith('\r')) {
str = str.replace(/[\n\r]$/, '');
}
return str;
};
if (notChange) {
if (this.apiHelperCLIRunningData.selectedDocumentEtag.length === 0) {
let content = fs_extra_1.default.readFileSync(this.configFileAbsolutePath).toString();
const apiHelperCLIRunningData = content.match(/\/\/\s==ApiHelperCLIRunningData==([\s\S]*)\/\/\s==\/ApiHelperCLIRunningData==/im);
if (apiHelperCLIRunningData) {
content = content.replace(/\/\/\s==ApiHelperCLIRunningData==([\s\S]*)\/\/\s==\/ApiHelperCLIRunningData==/im, '');
content = removeBreakLine(content);
content += '\n';
fs_extra_1.default.writeFileSync(this.configFileAbsolutePath, content, { encoding: 'utf-8' });
}
}
return;
}
let content = fs_extra_1.default.readFileSync(this.configFileAbsolutePath).toString();
const val = Object.assign(Object.assign({}, this.apiHelperCLIRunningData), { selectedDocumentEtag: this.selectedDocumentEtagTemp });
let temp = toComment(Object.assign(Object.assign({}, this.apiHelperCLIRunningData), { selectedDocumentEtag: this.selectedDocumentEtagTemp }));
// 取消生成内容
if (!val.selectedDocumentEtag.length) {
temp = '';
}
const apiHelperCLIRunningData = content.match(/\/\/\s==ApiHelperCLIRunningData==([\s\S]*)\/\/\s==\/ApiHelperCLIRunningData==/im);
if (apiHelperCLIRunningData) {
content = content.replace(/\/\/\s==ApiHelperCLIRunningData==([\s\S]*)\/\/\s==\/ApiHelperCLIRunningData==/im, temp);
content = removeBreakLine(content);
content += '\n';
}
else {
content = removeBreakLine(content);
content = content + (temp ? '\n\n' + `${temp}\n` : '\n');
}
fs_extra_1.default.writeFileSync(this.configFileAbsolutePath, content, { encoding: 'utf-8' });
}
catch (e) {
logger_1.default.warn(e === null || e === void 0 ? void 0 : e.message);
}
}
// 1. 获取配置文件
getConfigFile() {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const constructorOptions = this.constructorOptions;
// 如果基于CLI执行,不需要在进行查找文件
// npx apih -url https://xxxx.com/swagger-ui.html
if (constructorOptions.url) {
const outputPath = (_a = constructorOptions === null || constructorOptions === void 0 ? void 0 : constructorOptions.outputPath) !== null && _a !== void 0 ? _a : '';
const isJS = outputPath.endsWith('.js') || constructorOptions.target === 'javascript';
const target = ((_b = constructorOptions.target) !== null && _b !== void 0 ? _b : isJS) ? 'javascript' : 'typescript';
const suffixName = `.${isJS ? 'js' : 'ts'}`;
return [{
target,
outputPath: outputPath || `src/api/index${suffixName}`,
requestFunctionFilePath: `src/api/request${suffixName}`,
documentServers: [
{
url: constructorOptions.url,
type: (_c = constructorOptions.type) !== null && _c !== void 0 ? _c : 'swagger',
authToken: constructorOptions.authToken,
},
],
}];
}
const { configFilePath } = this;
const spinner = (0, ora_1.default)(this.locales.$t('读取配置文件')).start();
// 有配置文件
if (configFilePath) {
const c = (0, util_2.getAbsolutePath)(configFilePath);
if (!(0, fs_extra_1.pathExistsSync)(c)) {
spinner.fail();
logger_1.default.error(this.locales.$t('配置文件不存在,程序退出'));
return process.exit(1);
}
const configList = (0, lodash_1.castArray)(require(c).default);
if (!configList.length) {
spinner.fail();
logger_1.default.error(this.locales.$t('配置为空,程序退出'));
return process.exit(1);
}
spinner.succeed();
logger_1.default.info(this.locales.$t('配置已加载:') + (0, util_2.toUnixPath)(configFilePath));
this.configFileAbsolutePath = c;
this.getApiHelperCLIRunningData(c);
return configList;
}
// 没有从根目录寻找
const files = yield (0, fast_glob_1.default)(['apih.config.(ts|js|cjs|mjs)'], { cwd: process.cwd(), absolute: true });
let configList = [];
let configPath = '';
let hasConfigFile = false;
for (const file of files) {
const c = (0, util_2.getAbsolutePath)(file);
if ((0, fs_extra_1.pathExistsSync)(c)) {
hasConfigFile = true;
const temp = (0, lodash_1.castArray)(require(c).default);
if (temp && temp.length) {
configPath = file;
configList = temp;
}
}
}
if (!hasConfigFile) {
spinner.fail();
logger_1.default.error(this.locales.$t('配置文件不存在,程序退出'));
process.exit(1);
}
if (!configList.length) {
spinner.fail();
logger_1.default.error(this.locales.$t('配置为空,程序退出'));
return process.exit(1);
}
spinner.succeed();
logger_1.default.info(this.locales.$t('配置已加载:') + (0, util_2.toUnixPath)(configPath));
this.configFileAbsolutePath = configPath;
this.getApiHelperCLIRunningData(configPath);
return configList;
});
}
// 2. 文档获取与解析
parserDocument(documentServers, config) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield (0, util_2.documentServersRunParserPlugins)(documentServers, this.parserPlugins, config);
return result.parserPluginRunResult;
});
}
// 3. 选择项目文档
chooseDocument(parserPluginRunResult, config, index) {
return __awaiter(this, void 0, void 0, function* () {
const choicesDocumentListOptions = [];
const genEtg = (url) => (0, util_2.md5)(index + url, { outputLength: 16 });
parserPluginRunResult.forEach((d, idx) => {
d.parsedDocumentList.forEach((item) => {
const choice = {
title: item.title,
description: item.documentServerUrl,
value: item.id,
selected: true,
};
if (this.hasApiHelperCLIRunningData && this.apiHelperCLIRunningData.selectedDocumentEtag.length > 0) {
choice.selected = this.apiHelperCLIRunningData.selectedDocumentEtag.includes(genEtg(item.documentServerUrl));
}
if (!choice.title) {
choice.title = `[${idx}]${d.documentServer.url}`;
delete choice.description;
}
choicesDocumentListOptions.push(choice);
});
});
if (choicesDocumentListOptions.length > 1) {
const answers = yield prompts([{
type: 'multiselect',
name: 'documentList',
message: '配置中存在多个项目,请选择项目文档(默认全部)',
choices: choicesDocumentListOptions,
}]);
if (answers.documentList.length === 0) {
const failText = this.locales.$t('未选择接口文档');
logger_1.default.error(failText);
return Promise.reject(failText);
}
parserPluginRunResult = parserPluginRunResult.filter((d) => {
d.parsedDocumentList = d.parsedDocumentList.filter((item) => answers.documentList.includes(item.id));
d.parsedDocumentList.forEach((item) => {
this.selectedDocumentEtagTemp.push(genEtg(item.documentServerUrl));
});
return d.parsedDocumentList.length > 0;
});
}
if (parserPluginRunResult.length === 0) {
const failText = this.locales.$t('未选择接口文档');
logger_1.default.error(failText);
return Promise.reject(failText);
}
return parserPluginRunResult;
});
}
// 4. 生成代码
genCode(config, parserPluginRunResult) {
return __awaiter(this, void 0, void 0, function* () {
const result = [];
const outputFilePath = yield getOutputPath(config);
const isTS = yield checkOutputTS(config);
// 生成代码
const spinner = (0, ora_1.default)(this.locales.$t('代码生成,这可能需要等待一段时间...')).start();
const _genCode = (documentList, params) => {
params = Object.assign({}, params);
documentList = filterCategory(documentList, params);
let code = (0, template_1.renderAllApi)(documentList, params) || '';
let codeDeclare = '';
if (!code.endsWith('\n')) {
code += '\n';
}
if (!isTS) {
params.isDeclare = true;
params.codeType = 'typescript';
codeDeclare = (0, template_1.renderAllApi)(documentList, params);
if (!codeDeclare.endsWith('\n')) {
codeDeclare += '\n';
}
}
return [code, codeDeclare];
};
const formatResultCode = (codes = [], codeDeclares = []) => __awaiter(this, void 0, void 0, function* () {
const result = [];
result.push(yield (0, lib_1.formatCode)({
sourceCode: codes.filter(Boolean).join('\n'),
formatCodeExtension: isTS ? '.ts' : '.js',
}));
if (!isTS) {
result.push(yield (0, lib_1.formatCode)({
sourceCode: codeDeclares.filter(Boolean).join('\n'),
formatCodeExtension: '.ts',
}));
}
return result;
});
for (const item of parserPluginRunResult) {
const { documentServer, parsedDocumentList } = item;
const { dataKey } = documentServer;
const serverName = documentServer.name ? (0, pinyin_pro_1.pinyin)(documentServer.name, { toneType: 'none', type: 'array' }).join('') : '';
yield Promise.all(parsedDocumentList.map((d) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const mgConfig = mergeConfig(config, documentServer);
const eventTemp = mgConfig.events;
delete mgConfig.events;
Object.assign(mgConfig, eventTemp);
const param = Object.assign(Object.assign({}, mgConfig), { codeType: isTS ? 'typescript' : 'javascript', dataKey: dataKey, isDeclare: false });
// let workerStartError = false;
// const categoryListLength = d.categoryList.length;
// const enableParallel = config.parallel !== false && cpus > 1 && categoryListLength > CHUNK_NUM;
// const enableParallel = config.parallel !== false && cpus > 1 && categoryListLength > CHUNK_NUM;
// // 使用多线程生成
// if (enableParallel) {
// const categoryWrap: Array<APIHelper.CategoryList> = [];
// const chunkSize = Math.abs(categoryListLength / cpus);
// let temp: APIHelper.CategoryList = [];
// for (let i = 0; i < d.categoryList.length; i++) {
// temp.push(d.categoryList[i]);
// if (temp.length >= chunkSize || (i === categoryListLength - 1 && temp.length > 0)) {
// categoryWrap.push([...temp]);
// temp = [];
// }
// }
// const parallelResult: Array<{code: string; codeDeclare: string;}> = [];
// await Promise.all(categoryWrap.map((categoryList, index) => new Promise((resolve) => {
// try{
// const worker = new Worker(processTSFile(join(__filename, '../worker-thread.ts')), {
// workerData: {
// isTS,
// param,
// categoryList,
// }
// });
// worker.on('message', (res) => {
// parallelResult[index] = res;
// resolve(1);
// });
// worker.on('error', (e) => {
// console.error(e);
// resolve(1);
// });
// worker.on('exit', (code) => {
// resolve(1);
// });
// } catch (e) {
// resolve(1);
// workerStartError = true;
// }
// })));
// for (const item of parallelResult) {
// if (item && 'code' in item) {
// codes.push(item.code);
// if (!isTS) {
// codeDeclares.push(item.codeDeclare);
// }
// }
// }
// if (!workerStartError) {
// return;
// }
// }
// 普通模式
// @ts-ignore
if (config.outputByCategory || ('group' in config && config.group)) {
const fileNameRecord = {};
const codes = [];
const codeDeclares = [];
const fileNameMap = {};
yield Promise.all(d.categoryList.map((category) => __awaiter(this, void 0, void 0, function* () {
var _c;
try {
const [code, codeDeclare] = _genCode([category], param);
const lastName = (_c = category.name.split('/').filter(Boolean).pop()) === null || _c === void 0 ? void 0 : _c.replace(/[.\s]/gim, '');
const fileNameBase = (0, pinyin_pro_1.pinyin)(lastName, { toneType: 'none', type: 'array' }).join('');
fileNameRecord[fileNameBase] = fileNameRecord[fileNameBase] ? fileNameRecord[fileNameBase] + 1 : 1;
const fileName = (fileNameRecord[fileNameBase] > 1 ? `${fileNameBase}${fileNameRecord[fileNameBase]}` : fileNameBase) + `${isTS ? '.ts' : '.js'}`;
const currentOutputFilePath = (0, path_1.join)(outputFilePath, serverName ? ((0, path_1.join)(serverName, fileName)) : fileName);
const tag = (0, util_1.uuid)().replace(/-/gim, '');
fileNameMap[tag] = currentOutputFilePath;
codes.push(`\n//模块分组-开始:${tag}\n${code} \n//模块分组-结束:${tag}\n`);
codeDeclares.push(`\n//模块分组-开始:${tag}\n${codeDeclare} \n//模块分组-结束:${tag}\n`);
}
catch (e) {
logger_1.default.error(e);
}
})));
try {
const [code2 = '', codeDeclare2 = ''] = yield formatResultCode(codes, codeDeclares);
yield Promise.all(Object.entries(fileNameMap).map(([item, currentOutputFilePath], index) => __awaiter(this, void 0, void 0, function* () {
var _d, _e, _f, _g;
const rex = new RegExp(`\/\/模块分组-开始:${item}([\\s\\S]*)\/\/模块分组-结束:${item}`, 'gim');
const code = ((_e = (_d = rex === null || rex === void 0 ? void 0 : rex.exec(code2)) === null || _d === void 0 ? void 0 : _d[1]) !== null && _e !== void 0 ? _e : '').trim();
rex.lastIndex = 0;
const codeDeclare = ((_g = (_f = rex === null || rex === void 0 ? void 0 : rex.exec(codeDeclare2)) === null || _f === void 0 ? void 0 : _f[1]) !== null && _g !== void 0 ? _g : '').trim();
const requestFilePath = removeExtensionName((0, util_2.getNormalizedRelativePath)(currentOutputFilePath, yield getRequestFunctionFilePath(config)), const_1.EXTENSIONS);
const renderHeaderParams = {
isTS,
config,
requestFilePath,
onlyTyping: config.onlyTyping && isTS
};
const [codeHead, codeHeadDeclare] = renderHeader(this.isTestEnv, renderHeaderParams, Object.assign(Object.assign({}, renderHeaderParams), { isTS: true, onlyTyping: config.onlyTyping }), this.locales);
result.push({
outputFilePath: currentOutputFilePath,
code: code ? `${codeHead}${code}` : '',
codeDeclare: codeDeclare ? `${codeHeadDeclare}${codeDeclare}` : '',
});
})));
}
catch (e) {
logger_1.default.error(e);
}
return;
}
try {
const [code, codeDeclare] = _genCode(d, param);
let currentOutputFilePath = outputFilePath;
if (serverName) {
const outputFilePathList = (0, util_2.toUnixPath)(outputFilePath).split('/');
const lastPath = outputFilePathList[outputFilePathList.length - 1];
if (lastPath === null || lastPath === void 0 ? void 0 : lastPath.includes('.')) {
outputFilePathList.pop();
const lastPathSplit = lastPath.split('.');
outputFilePathList.push([...lastPathSplit].slice(0, lastPathSplit.length - 1).join('.'));
outputFilePathList.push(serverName + '.' + [...lastPathSplit].pop());
}
else {
outputFilePathList.push(serverName);
}
currentOutputFilePath = outputFilePathList.join('/');
}
const outputFilePathList = (0, util_2.toUnixPath)(currentOutputFilePath).split('/');
if (((_b = (_a = outputFilePathList.pop()) === null || _a === void 0 ? void 0 : _a.includes) === null || _b === void 0 ? void 0 : _b.call(_a, '.')) === false) {
currentOutputFilePath += isTS ? '.ts' : '.js';
}
const requestFilePath = removeExtensionName((0, util_2.getNormalizedRelativePath)(currentOutputFilePath, yield getRequestFunctionFilePath(config)), const_1.EXTENSIONS);
const renderHeaderParams = {
isTS,
config,
requestFilePath,
onlyTyping: config.onlyTyping && isTS
};
const [codeHead, codeHeadDeclare] = renderHeader(this.isTestEnv, renderHeaderParams, Object.assign(Object.assign({}, renderHeaderParams), { isTS: true, onlyTyping: config.onlyTyping }), this.locales);
const [code2, codeDeclare2] = yield formatResultCode([codeHead, code], [codeHeadDeclare, codeDeclare]);
result.push({
outputFilePath: currentOutputFilePath,
code: code2,
codeDeclare: codeDeclare2,
});
}
catch (e) {
logger_1.default.error(e);
}
})));
}
spinner.succeed();
return result;
});
}
// 5. 输出
output(config, genCodes) {
return __awaiter(this, void 0, void 0, function* () {
const isTS = yield checkOutputTS(config);
const spinner = (0, ora_1.default)(this.locales.$t('文件输出')).start();
try {
for (const genCode of genCodes) {
const outputFilePath = genCode.outputFilePath;
yield (0, fs_extra_1.outputFile)(outputFilePath, genCode.code);
if (!isTS) {
const declareFilePath = filterDeclareFilename(outputFilePath);
yield (0, fs_extra_1.outputFile)(declareFilePath, genCode.codeDeclare);
}
}
spinner.succeed();
// 耗时:${((Date.now() - this.startDate) / 1000).toFixed(2)}秒
logger_1.default.info(this.locales.$t('Done. 代码生成成功.'));
}
catch (error) {
spinner.fail();
logger_1.default.error(error);
return Promise.reject(error);
}
});
}
}
Service.init = function (options = {}) {
return __awaiter(this, void 0, void 0, function* () {
const locales = yield new locales_1.default().init();
let configFile;
if (options === null || options === void 0 ? void 0 : options.config) {
if ((0, path_1.isAbsolute)(options.config)) {
configFile = options.config;
}
else {
configFile = (0, path_1.join)(process.cwd(), options.config);
}
}
else {
const answers = yield prompts([{
type: 'select',
name: 'codeType',
message: locales.$t('请选择配置文件类型?'),
choices: [
{ title: 'JavaScript', description: 'apih.config.js', value: 'apih.config.js' },
{ title: 'TypeScript', description: 'apih.config.ts', value: 'apih.config.ts' },
],
}]);
if (!answers.codeType) {
return;
}
configFile = (0, path_1.join)(process.cwd(), answers.codeType);
}
// 检测文件是否已经存在
try {
yield (0, fs_extra_1.stat)(configFile);
const overrideAnswer = yield prompts([{
type: 'confirm',
name: 'override',
message: locales.$t('检测到已经存在配置文件,是否覆盖已有配置文件?')
}]);
if (!overrideAnswer.override) {
return;
}
}
catch (_a) { }
const code = `import { defineConfig } from '@api-helper/cli';
// ${locales.$t('更多完整配置,参考文档:')}https://github.com/ztz2/api-helper
export default defineConfig({
// ${locales.$t('target')}
target: 'typescript',
// ${locales.$t('outputPath')}
outputPath: 'src/api/index.ts',
// ${locales.$t('requestFunctionFilePath')}
requestFunctionFilePath: 'src/api/request.ts',
// ${locales.$t('documentServers')}
documentServers: [{
// ${locales.$t('url')}
url: '${locales.$t('urlValue')}',
// ${locales.$t('type')}
type: 'swagger',
// ${locales.$t('dataKey')}
dataKey: '',
}],
});
`;
try {
yield (0, fs_extra_1.outputFile)(configFile, code);
yield getRequestFunctionFilePath({ outputFilePath: 'src/api/index.ts' });
logger_1.default.info(locales.$t('已生成配置文件.'));
}
catch (e) {
return logger_1.default.error(locales.$t('配置文件生成失败.'));
}
});
};
function renderHeader(isTestEnv = false, options1 = {}, options2 = {}, locales) {
var _a, _b, _c, _d, _e, _f;
const genTimeStr = isTestEnv ? '' : (0, util_1.formatDate)(Date.now());
const codeHeadTpl = `《if onlyTyping !== true》《if isTS》/* tslint:disable */
/* eslint-disable */
/* prettier-ignore-start */
/* ${locales.$t('代码生成时间:')}${genTimeStr} */
/* ${locales.$t('提示:该文件由 API Helper CLI 自动生成,请勿直接修改。')} */
/* ${locales.$t('文档参考:')}https://github.com/ztz2/api-helper */
// @ts-ignore
// prettier-ignore
import {
RequestFunctionRestArgsType,《if isTS && !onlyTyping》
processRequestFunctionConfig,《/if》
} from '@api-helper/cli/lib/helpers';
// @ts-ignore
// prettier-ignore
import request from '《requestFilePath》';《if isTS && !onlyTyping》
// @ts-ignore
// prettier-ignore
type CurrentRequestFunctionRestArgsType = RequestFunctionRestArgsType<typeof request>;《/if》
《else》
/* eslint-disable */
/* prettier-ignore-start */
/* ${locales.$t('代码生成时间:')}${genTimeStr} */
/* ${locales.$t('提示:该文件由 API Helper CLI 自动生成,请勿直接修改。')} */
/* ${locales.$t('文档参考:')}https://github.com/ztz2/api-helper */
// prettier-ignore
import { processRequestFunctionConfig } from '@api-helper/cli/lib/helpers';
// prettier-ignore
import request from '《requestFilePath》';
《/if》
《/if》
`;
let codeHead = (_c = (_b = (_a = template_1.artTemplate.render(codeHeadTpl, options1)) === null || _a === void 0 ? void 0 : _a.trim) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : '';
let codeHeadDeclare = (_f = (_e = (_d = template_1.artTemplate.render(codeHeadTpl, options2)) === null || _d === void 0 ? void 0 : _d.trim) === null || _e === void 0 ? void 0 : _e.call(_d)) !== null && _f !== void 0 ? _f : '';
codeHead = codeHead ? codeHead + '\n\n' : codeHead;
codeHeadDeclare = codeHeadDeclare ? codeHeadDeclare + '\n\n' : codeHeadDeclare;
return [codeHead, codeHeadDeclare];
}
function filterMatchParams(param) {
return param.map((item) => {
if (typeof item === 'string') {
return [item, '*'];
}
if ((item === null || item === void 0 ? void 0 : item.length) === 1) {
return [item[0], '*'];
}
return item;
});
}
function filterCategory(apiDocument, params) {
const isDocument = (0, render_all_api_1.checkDocument)(apiDocument);
let categoryList = isDocument ? apiDocument.categoryList : apiDocument;
if (typeof params.excludeCategory === 'function' || (Array.isArray(params.excludeCategory) && params.excludeCategory.length > 0) ||
typeof params.includeCategory === 'function' || (Array.isArray(params.includeCategory) && params.includeCategory.length > 0)) {
categoryList = categoryList.filter((item, index) => {
let isRemove = false;
if (typeof params.excludeCategory === 'function') {
isRemove = params.excludeCategory(item);
}
else if (Array.isArray(params.excludeCategory) && params.excludeCategory.length > 0) {
isRemove = micromatch_1.default.isMatch(item.name, params.excludeCategory);
}
else if (typeof params.includeCategory === 'function') {
isRemove = !params.includeCategory(item);
}
else if (Array.isArray(params.includeCategory) && params.includeCategory.length > 0) {
isRemove = !micromatch_1.default.isMatch(item.name, params.includeCategory);
}
return !isRemove;
});
}
if (typeof params.includeAPI === 'function' || (Array.isArray(params.includeAPI) && params.includeAPI.length > 0) ||
typeof params.excludeAPI === 'function' || (Array.isArray(params.excludeAPI) && params.excludeAPI.length > 0)) {
categoryList.forEach((item) => {
item.apiList = item.apiList.filter((api) => {
if (typeof params.excludeAPI === 'function') {
return !params.excludeAPI(api);
}
if ((Array.isArray(params.excludeAPI) && params.excludeAPI.length > 0)) {
const temp = filterMatchParams(params.excludeAPI);
return !temp.some(([u, m = '*']) => {
return micromatch_1.default.isMatch(api.path, u) && micromatch_1.default.isMatch(api.method.toLowerCase(), m.toLocaleString());
});
}
if (typeof params.includeAPI === 'function') {
return params.includeAPI(api);
}
if ((Array.isArray(params.includeAPI) && params.includeAPI.length > 0)) {
const temp = filterMatchParams(params.includeAPI);
return temp.some(([u, m = '*']) => {
return micromatch_1.default.isMatch(api.path, u) && micromatch_1.default.isMatch(api.method.toLowerCase(), m.toLocaleString());
});
}
});
});
}
if (isDocument && 'categoryList' in apiDocument) {
apiDocument.categoryList = categoryList;
return apiDocument;
}
return categoryList;
}
function filterDeclareFilename(filename) {
const types = ['.ts', '.js', '.tsx', '.jsx', '.mjs', '.ejs'];
const type = types.find((t) => filename.endsWith(t));
if (type) {
return `${filename.slice(0, filename.length - type.length)}.d.ts`;
}
return `${filename}.d.ts`;
}
function checkOutputTS(config) {
return __awaiter(this, void 0, void 0, function* () {
const outputFilePath = yield getOutputPath(config);
if (config.target != null) {
return config.target !== 'javascript';
}
return !(outputFilePath.endsWith('.js') || outputFilePath.endsWith('.jsx'));
});
}
function getOutputPath(config, showDiscardWarn = false) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const locales = yield new locales_1.default().init();
const outputPath = (_a = config.outputPath) !== null && _a !== void 0 ? _a : config.outputFilePath;
// 兼容旧版的配置路径
if (config.outputFilePath) {
if (showDiscardWarn) {
if (!outputDiscardWarn) {
outputDiscardWarn = true;
logger_1.default.warn(locales.$t('documentServers.outputFilePath 属性已经废弃,请使用 documentServers.outputPath'));
}
}
}
if (config.output) {
// 使用旧版配置,警告提示该配置已经废弃
if (showDiscardWarn) {
if (!outputDiscardWarn) {
outputDiscardWarn = true;
logger_1.default.warn(locales.$t('documentServers.output 属性已经废弃,请使用 documentServers.outputPath'));
}
}
if ((0, path_1.isAbsolute)(config.output.filename)) {
return config.output.filename;
}
if ((0, path_1.isAbsolute)(config.output.path)) {
(0, path_1.join)(config.output.path, config.output.filename);
}
return (0, path_1.join)((0, util_2.resolve)(config.output.path), config.output.filename);
}
if (outputPath && (0, path_1.isAbsolute)(outputPath)) {
return outputPath;
}
return (0, util_2.resolve)(outputPath);
});
}
function removeExtensionName(filepath = '', extensions = []) {
for (const extension of const_1.EXTENSIONS) {
if (filepath.endsWith(extension)) {
filepath = filepath.slice(0, filepath.length - extension.length);
break;
}
}
return filepath;
}
function getRequestFunctionFilePath(config) {
return __awaiter(this, void 0, void 0, function* () {
const locales = yield new locales_1.default().init();
const outputFilename = yield getOutputPath(config);
const extensionName = (0, util_2.getExtensionName)(outputFilename);
let isTS = yield checkOutputTS(config);
config.requestFunctionFilePath = config.requestFunctionFilePath ? config.requestFunctionFilePath : `src/api/request.${isTS ? 'ts' : 'js'}`;
let requestFunctionFilePath = config.requestFunctionFilePath;
// 兼容旧版配置
if (['src/utils/request', 'src/tools/request'].includes(requestFunctionFilePath)) {
requestFunctionFilePath += extensionName;
}
requestFunctionFilePath = (0, util_2.resolve)(requestFunctionFilePath);
isTS = requestFunctionFilePath.endsWith('.ts') || requestFunctionFilePath.endsWith('.tsx');
if (config.target) {
isTS = config.target === 'typescript';
}
const requestDeclareFunctionFilePath = filterDeclareFilename(requestFunctionFilePath);
// 创建request文件类型申明文件
if (!isTS &&
!requestFunctionFilePath.endsWith('.ts') &&
!requestFunctionFilePath.endsWith('.tsx')) {
try {
yield (0, fs_extra_1.stat)((0, util_2.resolve)(requestDeclareFunctionFilePath));
}
catch (_a) {
try {
yield (0, fs_extra_1.outputFile)(requestDeclareFunctionFilePath, `import { RequestFunctionConfig } from '@api-helper/cli/lib/helpers';
// ${locales.$t('自定义配置')}
export type RequestOptions = {
//
};
export default function request<ResponseData>(config: RequestFunctionConfig, options?: RequestOptions): Promise<ResponseData>;
`);
}
catch (_b) { }
}
}
if (config.onlyTyping && isTS) {
return requestFunctionFilePath;
}
try { // 路径可以访问,文件已经创建,直接返回
yield (0, fs_extra_1.stat)((0, util_2.resolve)(requestFunctionFilePath));
return requestFunctionFilePath;
}
catch (_c) { }
try { // 不可访问,重新创建文件
yield (0, fs_extra_1.outputFile)(requestFunctionFilePath, isTS ? `import { RequestFunctionConfig } from '@api-helper/cli/lib/helpers';
// ${locales.$t('自定义配置')}
export type RequestOptions = {
//
};
export default async function request<ResponseData>(config: RequestFunctionConfig, options?: RequestOptions): Promise<ResponseData> {
return new Promise((resolve, reject) => {
// ${locales.$t('以axios为例的请求配置')}
const requestConfig = {
url: config.path,
method: config.method,
data: config.data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
};
// ${locales.$t('处理表单数据请求头')}
if (config.hasFormData) {
requestConfig.headers['Content-Type'] = 'multipart/form-data';
}
console.log('${locales.$t('请求配置:')}', requestConfig);
// ${locales.$t('TODO 待实现具体request请求逻辑...')}
/**
// axios example
axios(requestConfig).then((res) => {
resolve(res as unknown as ResponseData);
}).catch(reject);
*/
// ${locales.$t('先用异步模拟request请求逻辑')}
setTimeout(() => {
resolve({} as unknown as ResponseData);
}, 1500);
});
}
` : `export default async function request(config, options) {
return new Promise((resolve, reject) => {
// ${locales.$t('以axios为例的请求配置')}
const requestConfig = {
url: config.path,
method: config.method,
data: config.data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
};
// ${locales.$t('处理表单数据请求头')}
if (config.hasFormData) {
requestConfig.headers['Content-Type'] = 'multipart/form-data';
}
console.log('${locales.$t('请求配置:')}', requestConfig);
// ${locales.$t('TODO 待实现具体request请求逻辑...')}
/**
// axios example
axios(requestConfig).then((res) => {
resolve(res as unknown as ResponseData);
}).catch(reject);
*/
// ${locales.$t('先用异步模拟request请求逻辑')}
setTimeout(() => {
resolve({});
}, 1500);
});
}
`);
}
catch (_d) {
logger_1.default.error(`${locales.$t('请求文件创建失败:')}${requestFunctionFilePath}`);
process.exit(1);
}
return requestFunctionFilePath;
});
}
function mergeConfig(rootConfig, serverConfig) {
return Object.assign({}, (0, lodash_1.merge)(serverConfig, (0, lodash_1.pick)(rootConfig, [
'genHeaders',
'genCookies',
'genResponseContentType',
'requestFunctionFilePath',
'requiredRequestField',
'requiredResponseField',
'events',
'includeCategory',
'excludeCategory',
'includeAPI',
'excludeAPI',
])));
}
exports.default = Service;