feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
const {
excludeIgnoredFiles,
groupChangedFilesByProject,
getRushConfiguration,
} = require('./utils');
const micromatch = require('micromatch');
const path = require('path');
const fs = require('fs');
module.exports = {
'**/*.{ts,tsx,js,jsx,mjs}': async files => {
const match = micromatch.not(files, [
'**/common/_templates/!(_*)/**/(.)?*',
]);
const changedFileGroup = await groupChangedFilesByProject(match);
const eslintCmds = Object.entries(changedFileGroup)
.filter(([packageName]) =>
packageName !== '@coze-arch/arco-icon' && packageName !== '@coze-arch/arco-illustration')
.map(
([packageName, changedFiles]) => {
const rushConfiguration = getRushConfiguration();
const { projectFolder, packageName: name } =
rushConfiguration.getProjectByName(packageName);
const filesToCheck = changedFiles
.map(f => path.relative(projectFolder, f))
.join(' ');
// TSESTREE_SINGLE_RUN doc https://typescript-eslint.io/packages/parser/#allowautomaticsingleruninference
// 切换到项目文件夹,并运行 ESLint 命令
const cmd = [
`cd ${projectFolder}`,
`TSESTREE_SINGLE_RUN=true eslint --fix --cache ${filesToCheck} --no-error-on-unmatched-pattern`,
].join(' && ');
return {
name,
cmd,
};
},
);
if (!eslintCmds.length) return [];
if (eslintCmds.length > 16) {
console.log(
`For performance reason, skip ESlint detection due to ${eslintCmds.length} eslint commands.`,
);
return [];
}
return [
// 这里不能直接返回 eslintCmds 数组,因为 lint-staged 会依次串行执行每个命令
// 而 concurrently 会并行执行多个命令
`concurrently --max-process 8 --names ${eslintCmds
.map(r => `${r.name}`)
.join(',')} --kill-others-on-fail ${eslintCmds
.map(r => `"${r.cmd}"`)
.join(' ')}`,
];
},
'**/*.{less,scss,css}': files => {
// 暂时只修复,不报错卡点
return [`stylelint ${files.join(' ')} --fix || exit 0`];
},
'**/package.json': async files => {
const match = micromatch.not(files, [
'**/common/_templates/!(_*)/**/(.)?*',
]);
const filesToLint = await excludeIgnoredFiles(match);
if (!filesToLint) return [];
return [
// https://eslint.org/docs/latest/flags/#enable-feature-flags-with-the-cli
// eslint v9默认从cwd找配置这里需要使用 unstable_config_lookup_from_file 配置,否则会报错
`eslint --cache ${filesToLint} --flag unstable_config_lookup_from_file`,
`prettier ${filesToLint} --write`,
];
},
'**/!(package).json': 'prettier --write',
};

View File

@@ -0,0 +1,18 @@
{
"name": "rush-lint-staged",
"version": "1.0.0",
"private": true,
"author": "fanwenjie.fe@bytedance.com",
"resolutions": {},
"dependencies": {
"@microsoft/rush-lib": "5.147.1",
"concurrently": "^7.6.0",
"eslint": "~9.12.0",
"lint-staged": "^13.0.3",
"micromatch": "^4.0.5",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3",
"stylelint": "^16.7.0",
"typescript": "~5.8.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,106 @@
const path = require('path');
const { ESLint } = require('eslint');
const { RushConfiguration } = require('@microsoft/rush-lib');
const getRushConfiguration = (function () {
let rushConfiguration = null;
return function () {
// eslint-disable-next-line
return (rushConfiguration ||= RushConfiguration.loadFromDefaultLocation({
startingFolder: process.cwd(),
}));
};
})();
// 获取变更文件所在的项目路径
function withProjectFolder(changedFiles) {
const projectFolders = [];
try {
const rushConfiguration = getRushConfiguration();
const { rushJsonFolder } = rushConfiguration;
const lookup = rushConfiguration.getProjectLookupForRoot(rushJsonFolder);
for (const file of changedFiles) {
const project = lookup.findChildPath(path.relative(rushJsonFolder, file));
// 忽略不在 rush.json 内定义的项目
if (project) {
const projectFolder = project?.projectFolder ?? rushJsonFolder;
const packageName = project?.packageName;
projectFolders.push({
file,
projectFolder,
packageName,
});
}
}
} catch (e) {
console.error(e);
throw e;
}
return projectFolders;
}
async function excludeIgnoredFiles(changedFiles) {
try {
const eslintInstances = new Map();
const changedFilesWithIgnored = await Promise.all(
withProjectFolder(changedFiles).map(async ({ file, projectFolder }) => {
let eslint = eslintInstances.get(projectFolder);
if (!eslint) {
eslint = new ESLint({ cwd: projectFolder });
eslintInstances.set(projectFolder, eslint);
}
return {
file,
isIgnored: await eslint.isPathIgnored(file),
};
}),
);
return changedFilesWithIgnored
.filter(change => !change.isIgnored)
.map(change => change.file)
.join(' ');
} catch (e) {
console.error(e);
throw e;
}
}
// 获取发生变更的项目路径
function getChangedProjects(changedFiles) {
const changedProjectFolders = new Set();
const changedProjects = new Set();
withProjectFolder(changedFiles).forEach(({ projectFolder, packageName }) => {
if (!changedProjectFolders.has(projectFolder)) {
changedProjectFolders.add(projectFolder);
changedProjects.add({
packageName,
projectFolder,
});
}
});
return [...changedProjects];
}
const groupChangedFilesByProject = changedFiles => {
const changedFilesMap = withProjectFolder(changedFiles);
const result = changedFilesMap.reduce((pre, cur) => {
pre[cur.packageName] ||= [];
pre[cur.packageName].push(cur.file);
return pre;
}, {});
return result;
};
exports.excludeIgnoredFiles = excludeIgnoredFiles;
exports.getRushConfiguration = getRushConfiguration;
exports.getChangedProjects = getChangedProjects;
exports.groupChangedFilesByProject = groupChangedFilesByProject;