t-comm
Version:
专业、稳定、纯粹的工具库
986 lines (979 loc) • 42.1 kB
JavaScript
;
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;