t-comm
Version:
专业、稳定、纯粹的工具库
557 lines (550 loc) • 22.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _regeneratorRuntime = require('@babel/runtime/regenerator');
var tslib_es6 = require('../tslib.es6-0d92ef81.js');
var git_git = require('../git/git.js');
var node_nodeCommand = require('../node/node-command.js');
var tgit_branch = require('../tgit/branch.js');
var tgit_project = require('../tgit/project.js');
var wecomRobot_batchSend = require('../wecom-robot/batch-send.js');
var dailyMerge_helper = require('./helper.js');
require('../nodejs/fs.js');
require('../nodejs/path.js');
require('../validate/validate.js');
require('@babel/runtime/helpers/typeof');
require('../nodejs/child_process.js');
require('../tgit/helper.js');
require('axios');
require('../wecom-robot/base.js');
require('../wecom-robot/helper.js');
require('../wecom-robot/send-img.js');
require('../node-img/img.js');
require('../nodejs/crypto.js');
require('../nodejs/request.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var _regeneratorRuntime__default = /*#__PURE__*/_interopDefaultLegacy(_regeneratorRuntime);
var CHAT_ID = ['ALL'];
var DOC_URL = 'https://docs.qq.com/doc/DSGN2Uk1hTllydkRh';
function getStartTime() {
var date = new Date();
date.setHours(0, 0, 0, 0);
var startTime = parseInt("".concat(date.getTime()), 10) - 24 * 60 * 60 * 1000;
return startTime;
}
function getBranches(_ref) {
var baseUrl = _ref.baseUrl,
repoName = _ref.repoName,
privateToken = _ref.privateToken,
whiteBranchReg = _ref.whiteBranchReg;
return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee() {
var res, list, branches;
return _regeneratorRuntime__default["default"].wrap(function (_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 1;
return tgit_branch.getBranchesByProjectName({
projectName: repoName,
privateToken: privateToken,
baseUrl: baseUrl
})["catch"](function (err) {
console.log('[getBranches] err', err);
});
case 1:
res = _context.sent;
list = (res || []).filter(function (item) {
var commit = item.commit;
var date = commit === null || commit === void 0 ? void 0 : commit.committed_date;
var time = new Date(date).getTime();
if (time > getStartTime()) return true;
return false;
});
console.log('[Origin Branches]', list.map(function (item) {
return item === null || item === void 0 ? void 0 : item.name;
}));
branches = list.filter(function (branch) {
return dailyMerge_helper.shouldInclude(branch.name, whiteBranchReg);
});
console.log('[Filter Branches]', branches.map(function (item) {
return item === null || item === void 0 ? void 0 : item.name;
}));
return _context.abrupt("return", branches);
case 2:
case "end":
return _context.stop();
}
}, _callee);
}));
}
function mergeMainBranch(_ref2) {
var _ref2$branch = _ref2.branch,
branch = _ref2$branch === void 0 ? {} : _ref2$branch,
_ref2$results = _ref2.results,
results = _ref2$results === void 0 ? [] : _ref2$results,
_ref2$devRoot = _ref2.devRoot,
devRoot = _ref2$devRoot === void 0 ? '' : _ref2$devRoot,
_ref2$mainBranch = _ref2.mainBranch,
mainBranch = _ref2$mainBranch === void 0 ? 'develop' : _ref2$mainBranch,
_ref2$isDryRun = _ref2.isDryRun,
isDryRun = _ref2$isDryRun === void 0 ? false : _ref2$isDryRun;
var _a, _b;
var branchName = branch.name;
var author = ((_a = branch === null || branch === void 0 ? void 0 : branch.commit) === null || _a === void 0 ? void 0 : _a.author_name) || ((_b = branch === null || branch === void 0 ? void 0 : branch.author) === null || _b === void 0 ? void 0 : _b.username) || '';
if (!devRoot) {
results.push({
name: branchName,
author: author,
status: 'skipped',
reason: '未提供 devRoot'
});
return;
}
function tExecCommand(command) {
var stdio = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'inherit';
console.log('[Exec] ', command);
node_nodeCommand.execCommand(command, devRoot, stdio);
}
console.log('[Merging] ', branchName);
tExecCommand('git clean -df');
tExecCommand('git reset --hard HEAD');
tExecCommand("git checkout ".concat(mainBranch, " && git pull && git submodule update --init"), 'inherit');
tExecCommand("git checkout \"".concat(branchName, "\" && git reset --hard \"origin/").concat(branchName, "\" && git pull"));
var originCommitInfo = git_git.getGitCommitInfo(devRoot, true, false, false);
console.log('[originCommitInfo]', originCommitInfo);
try {
var out = tExecCommand("git merge ".concat(mainBranch, " -m \"Merge branch '").concat(mainBranch, "' into '").concat(branchName, "' by robot\""), 'inherit');
console.log('[out]', out);
} catch (err) {
console.log('[Merge Error]', err);
results.push({
name: branchName,
author: author,
status: 'conflict',
reason: (err === null || err === void 0 ? void 0 : err.message) ? String(err.message).slice(0, 500) : 'merge 冲突',
originCommit: originCommitInfo === null || originCommitInfo === void 0 ? void 0 : originCommitInfo.hash
});
return;
}
var nowCommitInfo = git_git.getGitCommitInfo(devRoot, true, false, false);
console.log('[nowCommitInfo]', nowCommitInfo);
if (nowCommitInfo.hash !== originCommitInfo.hash) {
try {
if (isDryRun) {
results.push({
name: branchName,
author: author,
status: 'success',
reason: 'dryRun 不实际推送',
originCommit: originCommitInfo === null || originCommitInfo === void 0 ? void 0 : originCommitInfo.hash,
nowCommit: nowCommitInfo === null || nowCommitInfo === void 0 ? void 0 : nowCommitInfo.hash
});
} else {
tExecCommand('git push');
results.push({
name: branchName,
author: author,
status: 'success',
originCommit: originCommitInfo === null || originCommitInfo === void 0 ? void 0 : originCommitInfo.hash,
nowCommit: nowCommitInfo === null || nowCommitInfo === void 0 ? void 0 : nowCommitInfo.hash
});
}
} catch (err) {
console.log('[Push Error]', err);
results.push({
name: branchName,
author: author,
status: 'conflict',
reason: "push \u5931\u8D25\uFF1A".concat((err === null || err === void 0 ? void 0 : err.message) ? String(err.message).slice(0, 500) : err),
originCommit: originCommitInfo === null || originCommitInfo === void 0 ? void 0 : originCommitInfo.hash,
nowCommit: nowCommitInfo === null || nowCommitInfo === void 0 ? void 0 : nowCommitInfo.hash
});
}
} else {
results.push({
name: branchName,
author: author,
status: 'noMerge',
reason: 'merge 后无新提交',
originCommit: originCommitInfo === null || originCommitInfo === void 0 ? void 0 : originCommitInfo.hash
});
}
}
function getMessageContent(_ref3) {
var appName = _ref3.appName,
projectId = _ref3.projectId,
baseUrl = _ref3.baseUrl,
quickLink = _ref3.quickLink,
_ref3$successBranches = _ref3.successBranches,
successBranches = _ref3$successBranches === void 0 ? [] : _ref3$successBranches,
_ref3$conflictBranche = _ref3.conflictBranches,
conflictBranches = _ref3$conflictBranche === void 0 ? [] : _ref3$conflictBranche,
_ref3$noMergeBranches = _ref3.noMergeBranches,
noMergeBranches = _ref3$noMergeBranches === void 0 ? [] : _ref3$noMergeBranches,
_ref3$toConfirmBranch = _ref3.toConfirmBranches,
toConfirmBranches = _ref3$toConfirmBranch === void 0 ? [] : _ref3$toConfirmBranch;
var link = quickLink ? quickLink : "".concat(baseUrl, "/search?tab=11&search=by%20robot&order=time&es_sort=desc&project_id=").concat(projectId || '');
var list = [">\u3010".concat(appName || '', "\u81EA\u52A8\u5408\u5E76\u3011[\u67E5\u770B](").concat(link, ")"), "- \u6210\u529F\u5408\u5E76\uFF1A".concat(successBranches.join('、')), "- \u5B58\u5728\u51B2\u7A81\uFF1A".concat(conflictBranches.join('、')), "- \u65E0\u9700\u5408\u5E76\uFF1A".concat(noMergeBranches.join('、')), "- [\u8BF4\u660E\u6587\u6863](".concat(DOC_URL, ")<@novlan1>")];
if (toConfirmBranches.length) {
list.push("- \u9700\u8981\u786E\u8BA4\uFF1A".concat(toConfirmBranches.join('、')));
}
var content = list.join('\n');
return content;
}
function sendSendMsg(content, webhookUrl) {
return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee2() {
return _regeneratorRuntime__default["default"].wrap(function (_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 1;
return wecomRobot_batchSend.batchSendWxRobotMarkdown({
content: content,
chatId: CHAT_ID,
webhookUrl: webhookUrl
});
case 1:
case "end":
return _context2.stop();
}
}, _callee2);
}));
}
/**
* 每日合并
* 1. 获取昨天有活跃的分支
* 2. 对于每个分支,进行合并并推送
* - 清理 Git 环境
* - 切到主分支,并拉最新代码
* - 切到当前分支,拉最新代码
* - 尝试执行 git merge
* - 对比 merge 前后的 commit 信息是否相同,作为判断 merge 是否成功的依据
* 3. 发送机器人消息
*
*
* @export
* @async
* @param {object} param0 参数
* @param {string} param0.webhookUrl 机器人地址
* @param {string} param0.appName 项目名称
* @param {string} param0.devRoot 项目根路径
* @param {string} param0.baseUrl 基础请求 url
* @param {string} param0.repoName 仓库名称
* @param {string} param0.privateToken 密钥
* @param {boolean} [param0.isDryRun=false] 是否演练
* @param {string} [param0.mainBranch='develop'] 主分支
* @param {Regexp} [param0.whiteBranchReg=/^release\|develop\|hotfix\\/.+$/] 不处理的分支正则
* @param {function} [param0.onReport] 执行完成后的结构化结果回调(可用于上报到自建后台)
* @returns {Promise<DailyMergeReportInfo>}
* @example
*
* ```ts
* dailyMerge({
* webhookUrl: 'xx',
* appName: 'xx',
* devRoot: 'xx',
*
* baseUrl: 'xx',
* repoName: 'xx',
* privateToken: 'xx',
*
* isDryRun: false,
* onReport: async (info) => { await axios.post('/api/daily-merge', info); },
* })
* ```
*/
function dailyMerge(_ref4) {
var webhookUrl = _ref4.webhookUrl,
appName = _ref4.appName,
devRoot = _ref4.devRoot,
baseUrl = _ref4.baseUrl,
quickLink = _ref4.quickLink,
repoName = _ref4.repoName,
privateToken = _ref4.privateToken,
_ref4$isDryRun = _ref4.isDryRun,
isDryRun = _ref4$isDryRun === void 0 ? false : _ref4$isDryRun,
_ref4$mainBranch = _ref4.mainBranch,
mainBranch = _ref4$mainBranch === void 0 ? 'develop' : _ref4$mainBranch,
_ref4$whiteBranchReg = _ref4.whiteBranchReg,
whiteBranchReg = _ref4$whiteBranchReg === void 0 ? dailyMerge_helper.DEFAULT_WHITE_REG : _ref4$whiteBranchReg,
onReport = _ref4.onReport;
return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee4() {
var _this = this;
var startTime, branchResults, buildInfo, safeReport, _info, _info2, branches, _info3, emptyContent, _robotNotified, _info4, successBranches, conflictBranches, noMergeBranches, toConfirmBranches, projectId, repoUrl, projectDetail, content, robotNotified, info, _t2, _t3, _t4, _t5;
return _regeneratorRuntime__default["default"].wrap(function (_context4) {
while (1) switch (_context4.prev = _context4.next) {
case 0:
console.log('\nSTART Daily Merge =====>');
startTime = Date.now(); // 用于收集本次执行各分支结果的容器(即使中途异常也能上报)
branchResults = [];
/** 构造最终上报的 info 对象 */
buildInfo = function buildInfo() {
var extra = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var endTime = Date.now();
var success = branchResults.filter(function (b) {
return b.status === 'success';
}).length;
var conflict = branchResults.filter(function (b) {
return b.status === 'conflict';
}).length;
var noMerge = branchResults.filter(function (b) {
return b.status === 'noMerge';
}).length;
var skipped = branchResults.filter(function (b) {
return b.status === 'skipped';
}).length;
var total = branchResults.length;
var status = 'success';
if (total === 0) status = 'empty';else if (conflict === 0) status = 'success';else if (success === 0 && noMerge === 0) status = 'failed';else status = 'partial';
return Object.assign({
repoName: repoName,
appName: appName,
mainBranch: mainBranch,
isDryRun: isDryRun,
startTime: startTime,
endTime: endTime,
duration: endTime - startTime,
branches: branchResults,
summary: {
total: total,
success: success,
conflict: conflict,
noMerge: noMerge,
skipped: skipped
},
status: status
}, extra);
};
/** 调用 onReport,吞掉错误避免影响主流程 */
safeReport = function safeReport(info, err) {
return tslib_es6.__awaiter(_this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee3() {
var _t;
return _regeneratorRuntime__default["default"].wrap(function (_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
if (!(typeof onReport !== 'function')) {
_context3.next = 1;
break;
}
return _context3.abrupt("return");
case 1:
_context3.prev = 1;
_context3.next = 2;
return onReport(info, err);
case 2:
_context3.next = 4;
break;
case 3:
_context3.prev = 3;
_t = _context3["catch"](1);
console.log('[onReport Error]', _t);
case 4:
case "end":
return _context3.stop();
}
}, _callee3, null, [[1, 3]]);
}));
};
if (baseUrl) {
_context4.next = 2;
break;
}
console.error('Please Input Base Url');
_info = buildInfo({
status: 'failed'
});
_context4.next = 1;
return safeReport(_info, {
message: 'baseUrl 未提供'
});
case 1:
return _context4.abrupt("return", _info);
case 2:
if (repoName) {
_context4.next = 4;
break;
}
console.error('Please Input Repo Name');
_info2 = buildInfo({
status: 'failed'
});
_context4.next = 3;
return safeReport(_info2, {
message: 'repoName 未提供'
});
case 3:
return _context4.abrupt("return", _info2);
case 4:
branches = [];
_context4.prev = 5;
_context4.next = 6;
return getBranches({
baseUrl: baseUrl,
repoName: repoName,
privateToken: privateToken,
whiteBranchReg: whiteBranchReg
});
case 6:
branches = _context4.sent;
_context4.next = 9;
break;
case 7:
_context4.prev = 7;
_t2 = _context4["catch"](5);
_info3 = buildInfo({
status: 'failed'
});
_context4.next = 8;
return safeReport(_info3, {
message: "\u83B7\u53D6\u5206\u652F\u5217\u8868\u5931\u8D25\uFF1A".concat((_t2 === null || _t2 === void 0 ? void 0 : _t2.message) || _t2),
stack: _t2 === null || _t2 === void 0 ? void 0 : _t2.stack
});
case 8:
throw _t2;
case 9:
if (branches === null || branches === void 0 ? void 0 : branches.length) {
_context4.next = 15;
break;
}
console.log('[no branch to merge]');
emptyContent = ">\u3010".concat(appName || '', "\u81EA\u52A8\u5408\u5E76\u3011\u672A\u627E\u5230\u9700\u8981\u5408\u5E76\u7684\u5206\u652F");
_robotNotified = false;
if (!webhookUrl) {
_context4.next = 13;
break;
}
_context4.prev = 10;
_context4.next = 11;
return sendSendMsg(emptyContent, webhookUrl);
case 11:
_robotNotified = true;
_context4.next = 13;
break;
case 12:
_context4.prev = 12;
_t3 = _context4["catch"](10);
console.log('[sendSendMsg Error]', _t3);
case 13:
_info4 = buildInfo({
status: 'empty',
robotMessage: emptyContent,
robotNotified: _robotNotified
});
_context4.next = 14;
return safeReport(_info4);
case 14:
return _context4.abrupt("return", _info4);
case 15:
branches.forEach(function (branch) {
mergeMainBranch({
branch: branch,
results: branchResults,
mainBranch: mainBranch,
devRoot: devRoot,
isDryRun: isDryRun
});
});
// 处理可能的"未被处理"分支(防御兜底)
branches.forEach(function (branch) {
if (!branchResults.find(function (r) {
return r.name === branch.name;
})) {
branchResults.push({
name: branch.name,
status: 'pending',
reason: '未被处理'
});
}
});
// 拼装机器人消息所需的字段
successBranches = branchResults.filter(function (b) {
return b.status === 'success';
}).map(function (b) {
return b.author ? "".concat(b.name, "<@").concat(b.author, ">") : b.name;
});
conflictBranches = branchResults.filter(function (b) {
return b.status === 'conflict';
}).map(function (b) {
return b.author ? "".concat(b.name, "<@").concat(b.author, ">") : b.name;
});
noMergeBranches = branchResults.filter(function (b) {
return b.status === 'noMerge';
}).map(function (b) {
return b.name;
});
toConfirmBranches = branchResults.filter(function (b) {
return b.status === 'pending' || b.status === 'skipped';
}).map(function (b) {
return b.name;
});
projectId = '';
_context4.prev = 16;
_context4.next = 17;
return tgit_project.getOneProjectDetail({
baseUrl: baseUrl,
projectName: repoName,
privateToken: privateToken
});
case 17:
projectDetail = _context4.sent;
projectId = (projectDetail === null || projectDetail === void 0 ? void 0 : projectDetail.id) || '';
repoUrl = (projectDetail === null || projectDetail === void 0 ? void 0 : projectDetail.web_url) || (projectDetail === null || projectDetail === void 0 ? void 0 : projectDetail.http_url_to_repo);
console.log('[projectDetail]', projectDetail);
_context4.next = 19;
break;
case 18:
_context4.prev = 18;
_t4 = _context4["catch"](16);
console.log('[getOneProjectDetail Error]', _t4);
case 19:
content = getMessageContent({
appName: appName,
projectId: projectId,
baseUrl: baseUrl,
quickLink: quickLink,
successBranches: successBranches,
conflictBranches: conflictBranches,
noMergeBranches: noMergeBranches,
toConfirmBranches: toConfirmBranches
});
console.log('[successBranches]', JSON.stringify(successBranches));
console.log('[conflictBranches]', JSON.stringify(conflictBranches));
console.log('[noMergeBranches]', JSON.stringify(noMergeBranches));
console.log('[robot message]', content);
robotNotified = false;
if (webhookUrl) {
_context4.next = 20;
break;
}
console.log('[No webhookUrl]');
_context4.next = 23;
break;
case 20:
_context4.prev = 20;
_context4.next = 21;
return sendSendMsg(content, webhookUrl);
case 21:
robotNotified = true;
_context4.next = 23;
break;
case 22:
_context4.prev = 22;
_t5 = _context4["catch"](20);
console.log('[sendSendMsg Error]', _t5);
case 23:
console.log('<===== END ');
info = buildInfo({
repoUrl: repoUrl,
projectId: projectId,
robotMessage: content,
robotNotified: robotNotified
});
_context4.next = 24;
return safeReport(info);
case 24:
return _context4.abrupt("return", info);
case 25:
case "end":
return _context4.stop();
}
}, _callee4, null, [[5, 7], [10, 12], [16, 18], [20, 22]]);
}));
}
exports.dailyMerge = dailyMerge;