UNPKG

t-comm

Version:

专业、稳定、纯粹的工具库

557 lines (550 loc) 22.4 kB
'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;