@ygyg/yg-cli
Version:
A simple CLI for front-end engineering automation construction tool.
398 lines (363 loc) • 12.2 kB
JavaScript
const { exec } = require('child_process');
const { ESLint } = require('eslint');
// const { readFileSync } = require('fs');
const { relative } = require('path');
const stripAnsi = require('strip-ansi');
const stylelint = require('stylelint');
const utils = require('./utils');
const fs = require('fs');
const path = require('path');
const fglob = require('fast-glob');
const ignore = require('ignore');
// const esResult = require('../docs/EslintResults');
const robotMessage = require('./robotMessage');
const _ = require('lodash');
const rootPath = process.cwd();
const { get, sortBy, sum, isString } = _;
// 初始化一个进度条长度为 50 的 ProgressBar 实例 ,对eslint 异步和同步效果不明显
const lintConfig = {
esLintType: 0, // lint类型 与chaos 的 table对齐
esLintPath: '',
styleLintType: 1,
styleLintPath: '',
};
const stats = {
js: { linting: 0, warningCount: 0, errorCount: 0, fileCount: 0 },
style: { linting: 0, warningCount: 0, errorCount: 0, fileCount: 0 },
};
exports.checkGroup = async function() {
// const myGroup = await utils.getLintGroup();
// Object.keys(myGroup).map(async function (group_key) {
// // utils.logStep(`group_key=${group_key}`);
// const lintPath = await utils.getLintPath({module_key: group_key});
// await lintCheck(lintPath, `group_key=${group_key}`); // 'app/web/src/'
// // const results = esResult.EslintResults();
// // utils.logStep(JSON.stringify(results));
// // formatEslintResults(esResult);
// return await myGroup[group_key];
// });
};
exports.checkList = async function(type) {
if (type === 'all') {
const lintPath = await utils.getLintPath();
lintCheck(lintPath);
} else if (type === 'group') {
this.checkGroup();
} else {
const GITDIFF = 'git diff --cached --diff-filter=ACMR --name-only';
// 执行 git 的命令
exec(GITDIFF, (error, stdout) => {
if (error) {
console.error(`exec error: ${error}`);
}
// 对返回结果进行处理,拿到要检查的文件列表
const diffFileArray = stdout
.split('\n')
.filter((diffFile) => /(\.js|\.jsx|\.ts|\.tsx)(\n|$)/gi.test(diffFile));
utils.logStep('待检查的文件:', diffFileArray);
lintCheck(diffFileArray);
});
}
};
async function lintCheck(diffFileArray, group_key = '') {
utils.logStep(diffFileArray);
if (diffFileArray.length > 0) {
// await downloading();
const [esResult, styleResult] = await Promise.all([
esLint_Check(diffFileArray),
styleLint_Check(diffFileArray),
]);
utils.logStep('Checking complete...');
outputLint([
{
fileCount: esResult.fileCount,
errorCount: esResult.errorCount,
warningCount: esResult.warningCount,
lintType: lintConfig.esLintType,
},
{
fileCount: styleResult.fileCount,
errorCount: styleResult.errorCount,
warningCount: styleResult.warningCount,
lintType: lintConfig.styleLintType,
},
]);
}
}
async function esLint_Check(diffFileArray) {
utils.logStep('Now Checking Eslint...');
// utils.logStep(`lintPath=${JSON.stringify(diffFileArray)}`);
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
const esFiles = await getLintFiles(diffFileArray, '.eslintignore', extensions);
const linter = new ESLint({
extensions: extensions,
errorOnUnmatchedPattern: true,
});
// --no-error-on-unmatched-pattern
// console.log(process.cwd(), diffFileArray);
// 2. Lint files.
const eslintResults = await linter.lintFiles(diffFileArray);
// 3. Format the results.
const formatter = await linter.loadFormatter('stylish');
if (!Array.isArray(eslintResults)) {
utils.logStep('无错误和警告');
return;
}
utils.logStep(formatter.format(eslintResults));
const warningCount = eslintResults.reduce(
(prev, cur) => prev + cur.warningCount,
0,
);
const errorCount = eslintResults.reduce(
(prev, cur) => prev + cur.errorCount,
0,
);
stats.js = {
linting: warningCount + errorCount,
warningCount,
errorCount,
fileCount: esFiles.length,
};
// const esResult = await esLint_Check(lintPath);
// console.log(eslintResults);
// // // utils.logStep(11, results);
// utils.logStep('all文件分组');
// formatEslintResults(eslintResults); // 格式化 排行榜
return { warningCount, errorCount, fileCount: esFiles.length };
}
async function getLintFiles(srcPath, ignorefile, extensions) {
const ig = ignore();
const distignoreFile = path.join(process.cwd(), ignorefile);
if (fs.existsSync(distignoreFile)) {
ig.add(fs.readFileSync(distignoreFile).toString());
}
const files = await fglob(['**', '!**/node_modules/**'], {
dot: false,
cwd: srcPath,
absolute: false,
});
const filterFiles = ig.filter(files).filter((item) => {
const ext = path.extname(item);
if (extensions.includes(ext)) {return item;}
});
return filterFiles;
}
async function styleLint_Check(diffFileArray) {
utils.logStep('Now Checking StyleLint...');
const extensions = ['.css', '.less', '.sass'];
let styleFiles = await getLintFiles(diffFileArray, '.stylelintignore', extensions);
const res = await stylelint.lint({
files: styleFiles, // app/web/src
reportNeedlessDisables: true,
});
// eslint-disable-next-line no-unused-vars
for (const result of res.results) {
if (result.warnings.length > 0) {
// utils.logStep(result.warnings);
const warnings = result.warnings;
const warningCount = warnings.reduce((prev, cur) => {
const currNum = cur.severity === 'warning' ? 1 : 0;
return prev + currNum;
}, 0);
const errorCount = warnings.reduce((prev, cur) => {
const currNum = cur.severity === 'error' ? 1 : 0;
return prev + currNum;
}, 0);
stats.style.warningCount += warningCount;
stats.style.errorCount += errorCount;
}
// else{
// utils.logStep('nonnonoon')
// }
}
stats.style.linting = stats.style.warningCount + stats.style.errorCount;
stats.style.fileCount = styleFiles.length;
const stylelintOutput = require('stylelint/lib/formatters/stringFormatter')(
res.results,
);
utils.logStep(stylelintOutput.substr(0, stylelintOutput.length - 1));
utils.logStep(JSON.stringify(stats));
return {
warningCount: stats.style.warningCount,
errorCount: stats.style.errorCount,
fileCount: stats.style.fileCount,
};
}
async function outputLint(opt) {
// utils.logStep(process.argv);
const [
node,
js,
gitUserId,
projectId = -1,
pipelineId = -1,
isDev = process.env.ISDEV || false,
] = process.argv;
utils.logStep(node, js);
const commits = await utils.getCommits();
// console.log(commits);
// if (gitUserId === undefined) {
// return;
// }
let insightParams = null;
if (Array.isArray(opt)) {
insightParams = opt.map((option) => {
const { errorCount, warningCount, fileCount, lintType } = option;
return {
gitUserId: gitUserId === undefined || typeof (gitUserId) === 'string' ? commits.email : parseInt(gitUserId, 10),
projectId: projectId === -1 ? commits.projectName : parseInt(projectId, 10),
pipelineId: parseInt(pipelineId, 10),
shortSha: commits.commit,
branch: commits.branch,
fileCount,
warningCount,
errorCount,
lintType,
};
});
} else {
utils.logStep('not array');
const { errorCount, warningCount, fileCount, lintType } = opt;
insightParams = {
gitUserId: parseInt(gitUserId, 10),
projectId: parseInt(projectId, 10),
pipelineId: parseInt(pipelineId, 10),
fileCount,
warningCount,
errorCount,
lintType,
};
}
utils.logStep(JSON.stringify(insightParams));
robotMessage.sendLintInsight(insightParams);
}
// checkList('all');
/**
* filePath => 相对路径
* source => 删掉
* output => 删掉
*/
const formatEslintResults = (results = []) => {
results.forEach((v) => {
const { filePath } = v;
v.filePath = relative(rootPath, filePath);
v.messages = v.messages.map((v2) => ({
...v2,
message: stripAnsi(v2.message),
}));
delete v.source;
delete v.output;
});
// const rank = getRankMessages(results);
// utils.logStep('all文件分组');
// utils.logStep(JSON.stringify(rank));
// utils.logStep('从模块文件分组');
// const sorted = groupByFolder(results, function (item) {
// const currFolder = item.filePath.substr(0, item.filePath.lastIndexOf('/'));
// return [currFolder];
// });
// console.log(sorted);
// const modules_group = [
// { name: '交易', path: 'app/web/src/Order' },
// { name: '金融', path: 'src/components/replenishment' },
// ];
// for (let i = 0; i < sorted.length; i += 1) {
// const mySorted = sorted[i];
// // const sorted = results.filter((item) =>
// // item.filePath.includes(modules_group[i].path),
// // );
// const currFolder = mySorted[0].filePath.substr(0, mySorted[0].filePath.lastIndexOf('/'));
// utils.logStep(`--------start [${i}]------按模块统计分组排行结果`);
// utils.logStep(`--------${currFolder}`);
// utils.logStep(JSON.stringify(mySorted));
// // const rank = getRankMessages(mySorted, mySorted[i].path);
// // const groupCount = groupBy(mySorted);
// // utils.logStep(mySorted[i].path);
// // utils.logStep(JSON.stringify(groupCount));
// // utils.logStep(JSON.stringify(rank));
// utils.logStep(`--------end [${i}]------分组排行结束`);
// }
};
function groupByFolder(array, f) {
// debugger;
const groups = {};
array.forEach(function(o) {
const group = JSON.stringify(f(o));
groups[group] = groups[group] || [];
groups[group].push(o);
});
return Object.keys(groups).map(function(group) {
return groups[group];
});
}
const getMeta = (ruleId = '', key = '') => {
const RulesMeta = [];
const url = get(RulesMeta[ruleId], key, '');
// sonar
if (!url && isString(ruleId) && ruleId.startsWith('sonar')) {
const sonarRuleId = ruleId.split('/')[1];
return `https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/${sonarRuleId}.md`;
}
return url;
};
function groupBy(eslintResults) {
const warningCount = eslintResults.reduce(
(prev, cur) => prev + cur.warningCount,
0,
);
const errorCount = eslintResults.reduce(
(prev, cur) => prev + cur.errorCount,
0,
);
return { warningCount, errorCount };
}
function localFlat(arr) {
return arr.reduce((acc, val) => acc.concat(val), []);
}
const getRankMessages = (EslintResults) => {
// utils.logStep(...EslintResults);
// EslintResults 按模块过滤
// TODO: 此处 应该按模块过滤,然后拍平,吐出rank
const Messages = localFlat([...EslintResults].map((v) => v.messages));
// utils.logStep(Messages, 33);
const ruleIdList = [...new Set(Messages.map((v) => v.ruleId))];
const RankMessages = ruleIdList.map((v) => {
const sameList = Messages.filter((v2) => v2.ruleId === v);
const { severity } = sameList[0];
const { url, description } = getMeta(v, 'docs');
const fixable = getMeta(v, 'fixable');
const severityText = {
1: 'warning',
2: 'error',
}[severity];
const count = sameList.length;
return {
ruleId: v,
url,
fixable,
description,
severityText,
count,
};
});
const SortRankMessages = sortBy(RankMessages, ['count']).reverse();
return SortRankMessages;
};
const analysis = (EslintResults) => {
const errorCount = sum(EslintResults.map((v) => v.errorCount));
const warningCount = sum(EslintResults.map((v) => v.warningCount));
const fixableCount = sum(
EslintResults.map((v) => v.fixableErrorCount + v.fixableWarningCount),
);
const fileCount = EslintResults.length;
const successFileCount = EslintResults.filter(
(v) => v.errorCount + v.warningCount === 0,
).length;
return {
errorCount,
warningCount,
fixableCount,
fileCount,
successFileCount,
};
};