UNPKG

t-comm

Version:

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

986 lines (979 loc) 42.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _toConsumableArray = require('@babel/runtime/helpers/toConsumableArray'); var _defineProperty = require('@babel/runtime/helpers/defineProperty'); var _slicedToArray = require('@babel/runtime/helpers/slicedToArray'); var _regeneratorRuntime = require('@babel/runtime/regenerator'); var tslib_es6 = require('../tslib.es6-0d92ef81.js'); var fs_fs = require('../fs/fs.js'); var node_nodeCommand = require('../node/node-command.js'); var nodejs_fs = require('../nodejs/fs.js'); var nodejs_path = require('../nodejs/path.js'); var tgit_helper = require('../tgit/helper.js'); var tgit_mr = require('../tgit/mr.js'); var time_time = require('../time/time.js'); var wecomRobot_batchSend = require('../wecom-robot/batch-send.js'); var lint_config = require('./config.js'); var lint_git = require('./git.js'); var lint_helper = require('./helper.js'); var lint_message = require('./message.js'); require('../nodejs/crypto.js'); require('../nodejs/os.js'); require('@babel/runtime/helpers/typeof'); require('../nodejs/child_process.js'); require('axios'); require('../tgit/user.js'); require('../wecom-robot/base.js'); require('../wecom-robot/helper.js'); require('../wecom-robot/send-img.js'); require('../node-img/img.js'); require('../nodejs/request.js'); require('../git/submodule.js'); require('../git/git.js'); require('../validate/validate.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var _toConsumableArray__default = /*#__PURE__*/_interopDefaultLegacy(_toConsumableArray); var _defineProperty__default = /*#__PURE__*/_interopDefaultLegacy(_defineProperty); var _slicedToArray__default = /*#__PURE__*/_interopDefaultLegacy(_slicedToArray); var _regeneratorRuntime__default = /*#__PURE__*/_interopDefaultLegacy(_regeneratorRuntime); function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } var ESLINT_CONFIG_FILE = '.eslintrc.js'; /** * 临时移除 .eslintrc.js 中的 parserOptions.project 配置。 * * 背景:lint vue 文件时,如果 tsconfig.json 的 include 不包含 .vue, * @typescript-eslint/parser 会抛 parsing error 导致整个 lint 流程中断; * 但对于 ts/tsx 文件,parserOptions.project 是开启类型感知规则的关键配置, * 不能永久删除。因此本函数返回一个 restore 函数,调用方应在 lint 完成后 * (建议放在 finally 中)调用它来还原配置文件。 * * 同时注册了进程退出钩子,作为异常崩溃时的兜底恢复。 * * @returns restore 函数,用于恢复 .eslintrc.js 原始内容;若未发生修改则为 no-op */ function removeParserOptionsProject(workspace) { var noop = function noop() {}; var configFile = nodejs_path.getPath().resolve(workspace, ESLINT_CONFIG_FILE); if (!nodejs_fs.getFs().existsSync(configFile)) { console.log('不存在配置文件'); return noop; } var content = fs_fs.readFileSync(configFile); var reg = /\s+project:\s+'[^']+json',/; var newContent = content.replace(reg, ''); if (!newContent || newContent === content) { return noop; } console.log('已删除 parserOptions.project'); fs_fs.writeFileSync(configFile, newContent); // 进程异常退出兜底:避免 lint 崩溃后 .eslintrc.js 永久残缺 var restored = false; var restore = function restore() { if (restored) return; restored = true; try { fs_fs.writeFileSync(configFile, content); console.log('已恢复 parserOptions.project'); } catch (err) { console.log('[restoreParserOptionsProject] error: ', err); } finally { process.off('exit', onProcessExit); process.off('SIGINT', onSignal); process.off('SIGTERM', onSignal); process.off('uncaughtException', onSignal); } }; var onProcessExit = function onProcessExit() { return restore(); }; var onSignal = function onSignal() { return restore(); }; process.on('exit', onProcessExit); process.on('SIGINT', onSignal); process.on('SIGTERM', onSignal); process.on('uncaughtException', onSignal); return restore; } function parseScssFiles(scssErrorFiles, workspace) { if (!(scssErrorFiles === null || scssErrorFiles === void 0 ? void 0 : scssErrorFiles.length)) { return []; } var parsedFiles = scssErrorFiles.reduce(function (acc, item) { var _a; (_a = item.warnings) === null || _a === void 0 ? void 0 : _a.forEach(function (messageItem) { acc.push(Object.assign(Object.assign({ filePath: item.source, parsedFilePath: nodejs_path.getPath().relative(workspace, item.source) }, messageItem), { message: messageItem.text, ruleId: messageItem.rule })); }); return acc; }, []); return parsedFiles; } function parseJSFiles(jsErrorFiles, workspace) { if (!(jsErrorFiles === null || jsErrorFiles === void 0 ? void 0 : jsErrorFiles.length)) { return []; } var parsedFiles = jsErrorFiles.reduce(function (acc, item) { var _a; (_a = item.messages) === null || _a === void 0 ? void 0 : _a.forEach(function (messageItem) { acc.push(Object.assign({ filePath: item.filePath, parsedFilePath: nodejs_path.getPath().relative(workspace, item.filePath) }, messageItem)); }); return acc; }, []); return parsedFiles; } function tryCreateMRNote(_ref) { var privateToken = _ref.privateToken, _ref$gitApiPrefix = _ref.gitApiPrefix, gitApiPrefix = _ref$gitApiPrefix === void 0 ? tgit_helper.T_GIT_BASE_URL : _ref$gitApiPrefix, errorFiles = _ref.errorFiles, projectName = _ref.projectName, mrId = _ref.mrId, workspace = _ref.workspace, isScss = _ref.isScss; return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee() { var parsedFiles, _iterator, _step, file, _t; return _regeneratorRuntime__default["default"].wrap(function (_context) { while (1) switch (_context.prev = _context.next) { case 0: parsedFiles = isScss ? parseScssFiles(errorFiles, workspace) : parseJSFiles(errorFiles, workspace); console.log('[isScss]', isScss); console.log('[parsedFiles]', parsedFiles); if (parsedFiles === null || parsedFiles === void 0 ? void 0 : parsedFiles.length) { _context.next = 1; break; } return _context.abrupt("return"); case 1: _iterator = _createForOfIteratorHelper(parsedFiles); _context.prev = 2; _iterator.s(); case 3: if ((_step = _iterator.n()).done) { _context.next = 5; break; } file = _step.value; _context.next = 4; return lint_git.createMRNote({ projectName: projectName, mrId: mrId, body: "".concat(file.message, "\n\n [").concat(file.ruleId, "]") || '格式错误', line: file.line, path: file.parsedFilePath, privateToken: privateToken, gitApiPrefix: gitApiPrefix }); case 4: _context.next = 3; break; case 5: _context.next = 7; break; case 6: _context.prev = 6; _t = _context["catch"](2); _iterator.e(_t); case 7: _context.prev = 7; _iterator.f(); return _context.finish(7); case 8: case "end": return _context.stop(); } }, _callee, null, [[2, 6, 7, 8]]); })); } /** * 生成修复分支名:feature/fix-eslint-{yyyyMMddhhmmss} 或 feature/fix-stylelint-{yyyyMMddhhmmss} * @example * ```ts * genFixBranchName('eslint', new Date('2025-01-02 03:04:05').getTime()); * // 'feature/fix-eslint-20250102030405' * * genFixBranchName('stylelint'); * // 'feature/fix-stylelint-{当前时间}' * ``` */ function genFixBranchName() { var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'eslint'; var timestamp = arguments.length > 1 ? arguments[1] : undefined; var ts = timestamp || Date.now(); var timeStr = time_time.timeStampFormat(ts, 'yyyyMMddhhmmss'); return "feature/fix-".concat(type, "-").concat(timeStr); } /** * 应在 git add 时排除的文件模式: * - lint 产出的 JSON 报告文件 * - ignoreSubmodule 可能修改的 ignore 文件 */ var GIT_ADD_EXCLUDE_PATTERNS = ['lint-*.json', 'lint-merged-*.json', '.eslintignore', '.stylelintignore']; /** * 在 fix 分支上执行 git add,排除 lint 产出文件和 ignore 文件变更 */ function gitAddExcludingLintOutputs(workspace) { var excludeArgs = GIT_ADD_EXCLUDE_PATTERNS.map(function (p) { return "':!".concat(p, "'"); }).join(' '); node_nodeCommand.execCommand("git add -A -- . ".concat(excludeArgs), workspace, 'inherit'); } /** * 创建一个 fix 分支、提交并推送,然后创建 MR * @returns MR 信息,失败时返回 undefined */ function commitFixAndCreateMR(_ref2) { var workspace = _ref2.workspace, fixBranch = _ref2.fixBranch, fixTargetBranch = _ref2.fixTargetBranch, commitMsg = _ref2.commitMsg, repo = _ref2.repo, privateToken = _ref2.privateToken, _ref2$gitApiPrefix = _ref2.gitApiPrefix, gitApiPrefix = _ref2$gitApiPrefix === void 0 ? tgit_helper.T_GIT_BASE_URL : _ref2$gitApiPrefix, mrTitlePrefix = _ref2.mrTitlePrefix; return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee2() { var diffOutput, mrData, _t2; return _regeneratorRuntime__default["default"].wrap(function (_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: gitAddExcludingLintOutputs(workspace); // 检查是否有实际变更(fix 可能没改任何文件) diffOutput = node_nodeCommand.execCommand('git diff --cached --name-only', workspace).trim(); if (diffOutput) { _context2.next = 1; break; } console.log("[autoFixMR] ".concat(fixBranch, " \u65E0\u5B9E\u9645\u53D8\u66F4\uFF0C\u8DF3\u8FC7\u521B\u5EFA MR")); return _context2.abrupt("return", undefined); case 1: _context2.prev = 1; node_nodeCommand.execCommand("git commit --no-verify -m \"".concat(commitMsg, "\""), workspace, 'inherit'); node_nodeCommand.execCommand("git push origin ".concat(fixBranch), workspace, 'inherit'); _context2.next = 2; return tgit_mr.createMR({ projectName: repo, privateToken: privateToken, sourceBranch: fixBranch, targetBranch: fixTargetBranch, baseUrl: gitApiPrefix, titlePrefix: mrTitlePrefix }); case 2: mrData = _context2.sent; console.log("[autoFixMR] \u5DF2\u521B\u5EFA\u4FEE\u590D MR: ".concat(fixBranch, " => ").concat(fixTargetBranch)); return _context2.abrupt("return", { fixBranch: fixBranch, fixTargetBranch: fixTargetBranch, mrData: mrData }); case 3: _context2.prev = 3; _t2 = _context2["catch"](1); console.log("[autoFixMR] \u63D0\u4EA4\u6216\u521B\u5EFA MR \u5931\u8D25 (".concat(fixBranch, "): "), _t2); return _context2.abrupt("return", undefined); case 4: case "end": return _context2.stop(); } }, _callee2, null, [[1, 3]]); })); } /** * 自动执行 --fix 并创建修复 MR * eslint 和 stylelint 分别创建独立 MR,便于 review * @returns 创建的修复 MR 信息列表,失败时返回 undefined * @example * ```ts * const fixMRList = await autoFixAndCreateMR({ * fileMap, * workspace: '/path/to/repo', * sourceBranch: 'feature/foo', * targetBranch: 'master', * repo: 'group/project', * privateToken: 'xxx', * mrTitlePrefix: '[WIP]', * }); * ``` */ function autoFixAndCreateMR(_ref3) { var fileMap = _ref3.fileMap, workspace = _ref3.workspace, sourceBranch = _ref3.sourceBranch, targetBranch = _ref3.targetBranch, repo = _ref3.repo, privateToken = _ref3.privateToken, _ref3$gitApiPrefix = _ref3.gitApiPrefix, gitApiPrefix = _ref3$gitApiPrefix === void 0 ? tgit_helper.T_GIT_BASE_URL : _ref3$gitApiPrefix, mrTitlePrefix = _ref3.mrTitlePrefix; return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee3() { var currentBranch, fixTargetBranch, eslintEntries, stylelintEntries, results, eslintFixBranch, _iterator2, _step2, _step2$value, key, info, eslintMR, stylelintFixBranch, _iterator3, _step3, _step3$value, _key, _info, stylelintMR, _t3; return _regeneratorRuntime__default["default"].wrap(function (_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: _context3.prev = 0; currentBranch = sourceBranch || node_nodeCommand.execCommand('git symbolic-ref --short -q HEAD', workspace).trim(); fixTargetBranch = currentBranch || targetBranch || 'master'; // 按 eslint / stylelint 分组有错误的文件类型 eslintEntries = []; stylelintEntries = []; Object.keys(fileMap).forEach(function (key) { var info = fileMap[key]; if (!info.total || !info.lintKeyword) return; if (info.isStyle) { stylelintEntries.push([key, info]); } else { eslintEntries.push([key, info]); } }); results = []; // ---- ESLint fix MR ---- if (!(eslintEntries.length > 0)) { _context3.next = 2; break; } eslintFixBranch = genFixBranchName(); node_nodeCommand.execCommand("git checkout -b ".concat(eslintFixBranch), workspace, 'inherit'); _iterator2 = _createForOfIteratorHelper(eslintEntries); try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { _step2$value = _slicedToArray__default["default"](_step2.value, 2), key = _step2$value[0], info = _step2$value[1]; try { node_nodeCommand.execCommand("npx eslint ".concat(info.lintKeyword, " --fix || true"), workspace, 'pipe'); console.log("[ESLint --fix] ".concat(key, " done")); } catch (err) { console.log("[ESLint --fix] ".concat(key, " error: "), err); } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } _context3.next = 1; return commitFixAndCreateMR({ workspace: workspace, fixBranch: eslintFixBranch, fixTargetBranch: fixTargetBranch, commitMsg: 'fix: auto fix eslint errors by checkLint', repo: repo, privateToken: privateToken, gitApiPrefix: gitApiPrefix, mrTitlePrefix: mrTitlePrefix }); case 1: eslintMR = _context3.sent; if (eslintMR) results.push(eslintMR); // 切回原分支,为下一轮 stylelint fix 做准备 try { node_nodeCommand.execCommand("git checkout ".concat(currentBranch), workspace, 'inherit'); } catch (err) { console.log('[autoFixMR] 切回原分支失败: ', err); } case 2: if (!(stylelintEntries.length > 0)) { _context3.next = 4; break; } stylelintFixBranch = genFixBranchName('stylelint', Date.now() + 1); node_nodeCommand.execCommand("git checkout -b ".concat(stylelintFixBranch), workspace, 'inherit'); _iterator3 = _createForOfIteratorHelper(stylelintEntries); try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { _step3$value = _slicedToArray__default["default"](_step3.value, 2), _key = _step3$value[0], _info = _step3$value[1]; try { node_nodeCommand.execCommand("npx stylelint ".concat(_info.lintKeyword, " --fix || true"), workspace, 'pipe'); console.log("[StyleLint --fix] ".concat(_key, " done")); } catch (err) { console.log("[StyleLint --fix] ".concat(_key, " error: "), err); } } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } _context3.next = 3; return commitFixAndCreateMR({ workspace: workspace, fixBranch: stylelintFixBranch, fixTargetBranch: fixTargetBranch, commitMsg: 'fix: auto fix stylelint errors by checkLint', repo: repo, privateToken: privateToken, gitApiPrefix: gitApiPrefix, mrTitlePrefix: mrTitlePrefix }); case 3: stylelintMR = _context3.sent; if (stylelintMR) results.push(stylelintMR); // 切回原分支 try { node_nodeCommand.execCommand("git checkout ".concat(currentBranch), workspace, 'inherit'); } catch (err) { console.log('[autoFixMR] 切回原分支失败: ', err); } case 4: return _context3.abrupt("return", results.length > 0 ? results : undefined); case 5: _context3.prev = 5; _t3 = _context3["catch"](0); console.log('[autoFixMR] error: ', _t3); return _context3.abrupt("return", undefined); case 6: case "end": return _context3.stop(); } }, _callee3, null, [[0, 5]]); })); } /** * 执行代码 lint 检查(支持 ESLint 和 StyleLint),并将结果通知到企业微信群、MR 评论等。 * * 支持增量模式(仅检查 sourceBranch 与 targetBranch 之间的 diff 文件)和全量模式(checkAll=true)。 * 检查完成后可自动执行 --fix 并创建修复 MR(autoFixMR=true)。 * * @param options - 配置参数 * @param options.privateToken - Git API 私有令牌,用于操作 MR * @param options.gitApiPrefix - Git API 地址前缀 * @param options.workspace - 项目工作目录绝对路径 * @param options.mrUrl - MR 页面链接,用于消息通知中展示 * @param options.mrId - MR ID,传入后会在 MR 中添加评论和逐行批注 * @param options.buildUrl - 流水线构建链接,用于消息通知中展示 * @param options.repo - 仓库名称(如 group/project) * @param options.repoUrl - 仓库页面链接 * @param options.sourceBranch - 源分支名(增量模式必填) * @param options.targetBranch - 目标分支名(增量模式必填) * @param options.docLink - 说明文档链接,用于消息通知中展示 * @param options.webhookUrl - 企业微信机器人 Webhook 地址 * @param options.chatId - 企业微信群聊 ID 列表,默认 ['ALL'] * @param options.checkAll - 是否全量检查,默认 false(增量模式) * @param options.mentionList - 需要 @ 的企业微信用户列表 * @param options.lintFiles - 需要检查的文件类型列表,默认检查所有配置的类型 * @param options.throwError - 检查不通过时是否抛出异常,默认 true * @param options.ignoreSubmodules - 是否在 lint 时忽略 git submodule,默认 true * @param options.autoFixMR - 是否自动执行 --fix 并创建修复 MR,默认 false * @param options.mrTitlePrefix - autoFixMR 创建 MR 时的标题前缀,如 '[WIP]' 可防止被自动合入 * @param options.onReport - 上报回调,lint 完成后调用,传入错误数量等关键信息 * @returns 返回 fileMap,包含各文件类型的 lint 结果 * @throws 当 throwError 为 true 且存在 lint 错误时抛出异常 * * @example * ```ts * import { checkLint } from 't-comm'; * * await checkLint({ * privateToken: 'your-token', * gitApiPrefix: 'https://git.woa.com/api/v3', * workspace: '/path/to/project', * buildUrl: 'https://ci.example.com/build/123', * repo: 'group/project', * sourceBranch: 'feature/xxx', * targetBranch: 'master', * docLink: 'https://doc.example.com/lint', * webhookUrl: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx', * }); * ``` */ function checkLint(_ref4) { var privateToken = _ref4.privateToken, _ref4$gitApiPrefix = _ref4.gitApiPrefix, gitApiPrefix = _ref4$gitApiPrefix === void 0 ? tgit_helper.T_GIT_BASE_URL : _ref4$gitApiPrefix, workspace = _ref4.workspace, mrUrl = _ref4.mrUrl, mrId = _ref4.mrId, buildUrl = _ref4.buildUrl, repo = _ref4.repo, repoUrl = _ref4.repoUrl, sourceBranch = _ref4.sourceBranch, targetBranch = _ref4.targetBranch, docLink = _ref4.docLink, webhookUrl = _ref4.webhookUrl, _ref4$chatId = _ref4.chatId, chatId = _ref4$chatId === void 0 ? ['ALL'] : _ref4$chatId, checkAll = _ref4.checkAll, _ref4$mentionList = _ref4.mentionList, mentionList = _ref4$mentionList === void 0 ? [] : _ref4$mentionList, _ref4$lintFiles = _ref4.lintFiles, lintFiles = _ref4$lintFiles === void 0 ? Object.keys(lint_config.FILE_TYPE_MAP) : _ref4$lintFiles, _ref4$throwError = _ref4.throwError, throwError = _ref4$throwError === void 0 ? true : _ref4$throwError, _ref4$ignoreSubmodule = _ref4.ignoreSubmodules, ignoreSubmodules = _ref4$ignoreSubmodule === void 0 ? true : _ref4$ignoreSubmodule, _ref4$autoFixMR = _ref4.autoFixMR, autoFixMR = _ref4$autoFixMR === void 0 ? false : _ref4$autoFixMR, mrTitlePrefix = _ref4.mrTitlePrefix, onReport = _ref4.onReport; return tslib_es6.__awaiter(this, void 0, void 0, /*#__PURE__*/_regeneratorRuntime__default["default"].mark(function _callee4() { var startTime, fileMap, _getDiffFile, diffFilesMap, innerExecCommand, keys, eslintKeys, stylelintKeys, restoreParserOptions, mergedExts, mergedOutputFile, lintResult, _iterator4, _step4, k, _iterator5, _step5, key, outputFile, _parseResult, errorMap, duration, hasError, fixMRList, errorDetails, totalErrors, commentSuccess, message, getPostFix, robotMessage, _i, _Object$keys, _key2, _t4, _t6; return _regeneratorRuntime__default["default"].wrap(function (_context4) { while (1) switch (_context4.prev = _context4.next) { case 0: if (!(!checkAll && (!sourceBranch || !targetBranch))) { _context4.next = 1; break; } throw new Error('增量模式,必须提供 sourceBranch 和 targetBranch!'); case 1: startTime = Date.now(); if (ignoreSubmodules) { lint_helper.ignoreSubmoduleInESLint(workspace); lint_helper.ignoreSubmoduleInStyleLint(workspace); } fileMap = Object.keys(lint_config.FILE_TYPE_MAP).filter(function (item) { return lintFiles.includes(item); }).reduce(function (acc, key) { return Object.assign(Object.assign({}, acc), _defineProperty__default["default"]({}, key, lint_config.FILE_TYPE_MAP[key])); }, {}); if (!checkAll) { _getDiffFile = getDiffFile({ sourceBranch: sourceBranch, targetBranch: targetBranch, workspace: workspace }), diffFilesMap = _getDiffFile.diffFilesMap; Object.keys(fileMap).forEach(function (key) { fileMap[key].lintKeyword = diffFilesMap[key]; }); } innerExecCommand = function innerExecCommand(key, info) { console.log("\u6B63\u5728\u6267\u884C lint ".concat(key, " ...")); // 增量模式下,如果 diff 中没有该类型的文件,lintKeyword 为空字符串,应跳过执行 // 否则 stylelint 会因为没有传入文件参数而对整个 cwd 做全量扫描 if (!checkAll && !info.lintKeyword) { console.log("[Skip] ".concat(key, " \u589E\u91CF\u6A21\u5F0F\u65E0\u53D8\u66F4\u6587\u4EF6\uFF0C\u8DF3\u8FC7")); return ''; } var restoreParserOptions = info.isVue ? removeParserOptionsProject(workspace) : function () {}; var outputFile = nodejs_path.getPath().resolve(workspace, info.outputFileName); try { if (info.isStyle) { try { // inherit 无法真正捕获错误 var lintResult = node_nodeCommand.execCommand("npx stylelint ".concat(info.lintKeyword, " --quiet -o ").concat(outputFile, " --formatter json || true"), workspace, 'pipe'); console.log('[StyleLint] result: \n', lintResult); } catch (err) { console.log('[StyleLint] error: ', err); } } else { try { var _lintResult = node_nodeCommand.execCommand("npx eslint ".concat(info.lintKeyword, " --quiet -o ").concat(outputFile, " --format json || true"), workspace, 'pipe'); console.log('[ESLint] result: \n', _lintResult); } catch (err) { console.log('[ESLint] error: ', err); } } } finally { restoreParserOptions(); } return outputFile; }; // 🚀 性能优化:将同引擎的文件类型合并执行,减少 npx 调用次数 // 对于增量模式,每种类型有不同的文件列表,无法合并,仍逐个执行 // 对于全量模式,同引擎可合并为一次调用(ESLint 支持 --ext .js,.ts,.tsx 等) keys = Object.keys(fileMap); if (checkAll && keys.length > 1) { // 按 eslint / stylelint 分组 eslintKeys = keys.filter(function (k) { return !fileMap[k].isStyle; }); stylelintKeys = keys.filter(function (k) { return fileMap[k].isStyle; }); // ESLint:合并所有非 style 文件类型,一次调用 if (eslintKeys.length > 0) { // vue 需要先移除 parserOptions.project;执行完后必须恢复,避免影响 ts 类型感知规则 restoreParserOptions = eslintKeys.some(function (k) { return fileMap[k].isVue; }) ? removeParserOptionsProject(workspace) : function () {}; mergedExts = eslintKeys.map(function (k) { return ".".concat(k); }).join(','); mergedOutputFile = nodejs_path.getPath().resolve(workspace, 'lint-merged-eslint.json'); try { console.log("\u6B63\u5728\u6267\u884C ESLint\uFF08\u5408\u5E76: ".concat(eslintKeys.join(', '), "\uFF09...")); lintResult = node_nodeCommand.execCommand("npx eslint --ext ".concat(mergedExts, " . --quiet -o ").concat(mergedOutputFile, " --format json || true"), workspace, 'pipe'); console.log('[ESLint merged] result: \n', lintResult); } catch (err) { console.log('[ESLint merged] error: ', err); } finally { restoreParserOptions(); } // 将合并结果文件赋给每个子类型,后续 parseResult 从同一文件读取并按 ext 过滤 _iterator4 = _createForOfIteratorHelper(eslintKeys); try { for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) { k = _step4.value; fileMap[k].outputFile = mergedOutputFile; fileMap[k].isMerged = true; } } catch (err) { _iterator4.e(err); } finally { _iterator4.f(); } } // StyleLint:各类型单独执行(stylelint 不支持多 glob 合并输出) _iterator5 = _createForOfIteratorHelper(stylelintKeys); try { for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) { key = _step5.value; outputFile = innerExecCommand(key, fileMap[key]); fileMap[key].outputFile = outputFile; } } catch (err) { _iterator5.e(err); } finally { _iterator5.f(); } } else { // 增量模式或只有一种类型,逐个执行 keys.forEach(function (key) { var outputFile = innerExecCommand(key, fileMap[key]); fileMap[key].outputFile = outputFile; }); } _parseResult = parseResult({ fileMap: fileMap }), errorMap = _parseResult.errorMap; Object.keys(fileMap).forEach(function (key) { fileMap[key].errorFiles = errorMap[key].errorFiles; fileMap[key].total = errorMap[key].total; }); duration = Date.now() - startTime; hasError = !!Object.values(fileMap).find(function (item) { return item.total; }); // autoFix 需要在 onReport 之前执行,确保 fixMR 信息可传给 onReport if (!(autoFixMR && hasError)) { _context4.next = 3; break; } _context4.next = 2; return autoFixAndCreateMR({ fileMap: fileMap, workspace: workspace, sourceBranch: sourceBranch, targetBranch: targetBranch, repo: repo, privateToken: privateToken, gitApiPrefix: gitApiPrefix, mrTitlePrefix: mrTitlePrefix }); case 2: fixMRList = _context4.sent; case 3: if (!onReport) { _context4.next = 7; break; } errorDetails = Object.keys(fileMap).map(function (key) { var _a, _b, _c; return { fileType: key, errorCount: (_a = fileMap[key].total) !== null && _a !== void 0 ? _a : 0, fileCount: (_c = (_b = fileMap[key].errorFiles) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0 }; }); totalErrors = errorDetails.reduce(function (acc, item) { return acc + item.errorCount; }, 0); _context4.prev = 4; _context4.next = 5; return onReport({ totalErrors: totalErrors, errorDetails: errorDetails, checkAll: !!checkAll, mrId: mrId, repo: repo, sourceBranch: sourceBranch, targetBranch: targetBranch, passed: totalErrors === 0, duration: duration, lintFiles: Object.keys(fileMap), fileMap: fileMap, fixMRList: fixMRList }); case 5: _context4.next = 7; break; case 6: _context4.prev = 6; _t4 = _context4["catch"](4); console.log('[onReport] error: ', _t4); case 7: commentSuccess = false; if (!mrId) { _context4.next = 12; break; } message = genRobotMessage({ fileMap: fileMap, checkAll: checkAll, mrUrl: mrUrl, sourceBranch: sourceBranch, targetBranch: targetBranch, buildUrl: buildUrl, docLink: docLink, repo: repo, repoUrl: repoUrl, mentionList: mentionList, workspace: workspace }); _context4.prev = 8; _context4.next = 9; return lint_git.createMRComment({ projectName: repo, mrId: mrId, data: message, privateToken: privateToken, gitApiPrefix: gitApiPrefix }); case 9: commentSuccess = _context4.sent; _context4.next = 11; break; case 10: _context4.prev = 10; _context4["catch"](8); case 11: console.log('[commentSuccess]', commentSuccess); case 12: getPostFix = function getPostFix() { if (!mrId) return ''; return commentSuccess ? '评论成功' : '评论失败'; }; robotMessage = genRobotMessage({ fileMap: fileMap, postFix: getPostFix(), mrUrl: mrUrl, sourceBranch: sourceBranch, targetBranch: targetBranch, buildUrl: buildUrl, docLink: docLink, checkAll: checkAll, repo: repo, repoUrl: repoUrl, mentionList: mentionList, workspace: workspace }); console.log('[robotMessage]: \n', robotMessage); _context4.prev = 13; _context4.next = 14; return wecomRobot_batchSend.batchSendWxRobotMarkdown({ content: robotMessage, chatId: chatId, webhookUrl: webhookUrl }); case 14: _context4.next = 16; break; case 15: _context4.prev = 15; _t6 = _context4["catch"](13); console.log('[batchSendWxRobotMarkdown] err', _t6); case 16: if (!mrId) { _context4.next = 19; break; } _i = 0, _Object$keys = Object.keys(fileMap); case 17: if (!(_i < _Object$keys.length)) { _context4.next = 19; break; } _key2 = _Object$keys[_i]; _context4.next = 18; return tryCreateMRNote({ projectName: repo, mrId: mrId, errorFiles: fileMap[_key2].errorFiles || [], workspace: workspace, privateToken: privateToken, gitApiPrefix: gitApiPrefix, isScss: fileMap[_key2].isStyle }); case 18: _i++; _context4.next = 17; break; case 19: if (!(throwError && hasError)) { _context4.next = 20; break; } throw new Error('Lint 检查不通过!'); case 20: return _context4.abrupt("return", fileMap); case 21: case "end": return _context4.stop(); } }, _callee4, null, [[4, 6], [8, 10], [13, 15]]); })); } function getErrorInfo(result) { var errorFiles = result.filter(function (item) { return item.errorCount; }); var total = errorFiles.reduce(function (acc, file) { acc += file.errorCount || 0; return acc; }, 0); return { total: total, errorFiles: errorFiles }; } var readyFile = function readyFile(file) { if (!nodejs_fs.getFs().existsSync(file)) { fs_fs.writeFileSync(file, [], true); return []; } return fs_fs.readFileSync(file, true); }; var parseErrorResult = function parseErrorResult(key, info) { if (!info.outputFile || !nodejs_fs.getFs().existsSync(info.outputFile)) { return { total: 0, errorFiles: [] }; } var content = readyFile(info.outputFile); // 全量模式下多个文件类型共用同一个合并输出文件,需要按扩展名过滤,避免数据重复 if (info.isMerged && !info.isStyle) { var ext = ".".concat(key); content = content.filter(function (item) { var fp = item.filePath || ''; return fp.endsWith(ext); }); } console.log("[result] ".concat(key, ": \n"), JSON.stringify(content, null, 2)); var resultTotal = 0; var resultErrorFiles = []; if (info.isStyle) { var parsed = content.filter(function (item) { var _a; return (_a = item.warnings) === null || _a === void 0 ? void 0 : _a.length; }).map(function (item) { return Object.assign(Object.assign({}, item), { errorCount: item.warnings.filter(function (warn) { return warn.severity === 'error'; }).length }); }); var _getErrorInfo = getErrorInfo(parsed), total = _getErrorInfo.total, errorFiles = _getErrorInfo.errorFiles; resultTotal = total; resultErrorFiles = errorFiles; } else { var _getErrorInfo2 = getErrorInfo(content), _total = _getErrorInfo2.total, _errorFiles = _getErrorInfo2.errorFiles; resultTotal = _total; resultErrorFiles = _errorFiles; } return { total: resultTotal, errorFiles: resultErrorFiles }; }; function parseResult(_ref5) { var fileMap = _ref5.fileMap; var errorMap = {}; Object.keys(fileMap).forEach(function (key) { errorMap[key] = parseErrorResult(key, fileMap[key]); }); return { errorMap: errorMap }; } function genRobotMessage(_ref6) { var fileMap = _ref6.fileMap, mrUrl = _ref6.mrUrl, sourceBranch = _ref6.sourceBranch, targetBranch = _ref6.targetBranch, buildUrl = _ref6.buildUrl, docLink = _ref6.docLink, repo = _ref6.repo, repoUrl = _ref6.repoUrl, postFix = _ref6.postFix, _ref6$checkAll = _ref6.checkAll, checkAll = _ref6$checkAll === void 0 ? false : _ref6$checkAll, _ref6$mentionList = _ref6.mentionList, mentionList = _ref6$mentionList === void 0 ? [] : _ref6$mentionList, workspace = _ref6.workspace; var postFixList = postFix ? [postFix] : []; var repoAndMrInfo = lint_message.genReportInfo({ workspace: workspace, mrUrl: mrUrl, sourceBranch: sourceBranch, targetBranch: targetBranch, repo: repo, repoUrl: repoUrl, checkAll: checkAll }); var allTotal = Object.values(fileMap).reduce(function (acc, item) { var _a; return acc + ((_a = item.total) !== null && _a !== void 0 ? _a : 0); }, 0); if (!allTotal) { return [lint_message.genTitle('✅', checkAll)].concat(_toConsumableArray__default["default"](repoAndMrInfo), ['未发现代码规范异常'], postFixList).join(','); } var lintErrorMessageList = Object.keys(fileMap).filter(function (item) { return !!fileMap[item].total; }).map(function (key) { var info = fileMap[key]; var total = info.total, errorFiles = info.errorFiles; return "- **".concat(key.toUpperCase(), " \u9519\u8BEF**\uFF1A").concat(total && (errorFiles === null || errorFiles === void 0 ? void 0 : errorFiles.length) ? "".concat(errorFiles === null || errorFiles === void 0 ? void 0 : errorFiles.length, "\u4E2A\u6587\u4EF6").concat(total, "\u4E2A\u9519\u8BEF") : '无'); }); return [[lint_message.genTitle('⚠️', checkAll)].concat(_toConsumableArray__default["default"](repoAndMrInfo), ["\u53EF\u5728[\u6D41\u6C34\u7EBF](".concat(buildUrl, ")\u4E2D\u67E5\u770B\u8BE6\u60C5\uFF0C\u6216\u672C\u5730\u8FD0\u884C `npx eslint --fix file` \u7B49\u547D\u4EE4"), docLink ? "[\u8BF4\u660E\u6587\u6863](".concat(docLink, ")") : '', mentionList.filter(function (mention) { return mention; }).map(function (mention) { return "<@".concat(mention, ">"); }).join('')], postFixList).filter(function (item) { return item; }).join(',')].concat(_toConsumableArray__default["default"](lintErrorMessageList)).join('\n'); } function getDiffFile(_ref7) { var sourceBranch = _ref7.sourceBranch, targetBranch = _ref7.targetBranch, workspace = _ref7.workspace; node_nodeCommand.execCommand("git clean -df && git reset --hard HEAD && git checkout ".concat(targetBranch, " && git pull && git submodule update --init"), workspace, 'inherit'); node_nodeCommand.execCommand("git checkout ".concat(sourceBranch, " && git reset --hard \"origin/").concat(sourceBranch, "\" && git pull && git submodule update --init"), workspace, 'inherit'); var list = node_nodeCommand.execCommand("git diff --name-only ".concat(sourceBranch, " ").concat(targetBranch), workspace, { stdio: 'pipe', line: -1 }).split('\n').map(function (item) { return item.trim(); }).filter(function (item) { return item; }).filter(function (item) { return nodejs_fs.getFs().existsSync(nodejs_path.getPath().resolve(workspace, item)); }); console.log('diff list: ', JSON.stringify(list, null, 2)); var diffFilesMap = {}; Object.keys(lint_config.FILE_TYPE_MAP).forEach(function (key) { var reg = lint_config.FILE_TYPE_MAP[key].reg; var files = list.filter(function (item) { return reg.test(item); }); diffFilesMap[key] = files.join(' '); }); return { diffFilesMap: diffFilesMap }; } exports.autoFixAndCreateMR = autoFixAndCreateMR; exports.checkLint = checkLint; exports.genFixBranchName = genFixBranchName; exports.getErrorInfo = getErrorInfo; exports.parseJSFiles = parseJSFiles; exports.parseScssFiles = parseScssFiles;