import fs from "fs"; import {dirname, relative, resolve} from "path"; import {ESLint} from "eslint"; const projectRoot = process.cwd(); // Eslint mode: "Experimental" | "Legacy" const LINT_MODE = "Experimental"; const removeIgnoredFilesNew = async (files) => { try { const eslint = new ESLint(); const cwd = process.cwd(); const relativePaths = files.map((file) => relative(cwd, file)); const isIgnored = await Promise.all(relativePaths.map((file) => eslint.isPathIgnored(file))); return files.filter((_, i) => !isIgnored[i]).join(" "); } catch (error) { // eslint-disable-next-line no-console console.error(error); process.exit(1); } }; function findPackageRoot(file) { let dir = dirname(file); while (dir !== "/") { if (fs.existsSync(`${dir}/package.json`)) return dir; const parentDir = dirname(dir); if (parentDir === dir) break; dir = parentDir; } return projectRoot; } async function removeIgnoredFilesLegacy(files, eslint) { const isIgnored = await Promise.all(files.map((file) => eslint.isPathIgnored(file))); return files.filter((_, i) => !isIgnored[i]); } const lintStaged = { "**/*.{cjs,mjs,js,ts,jsx,tsx}": async (files) => { if (LINT_MODE === "Experimental") { // use ESLint with experimental configuration file resolution const filesToLint = await removeIgnoredFilesNew(files); return [ `eslint --flag v10_config_lookup_from_file --max-warnings=0 --no-warn-ignored --fix ${filesToLint}`, ]; } if (LINT_MODE === "Legacy") { // Legacy with group files by package root and resolve ESLint config const filesByPackage = new Map(); for (const file of files) { const pkgRoot = findPackageRoot(file); if (!filesByPackage.has(pkgRoot)) filesByPackage.set(pkgRoot, []); filesByPackage.get(pkgRoot).push(file); } const commands = []; for (const [pkgRoot, pkgFiles] of filesByPackage.entries()) { // Check if the package has its own eslint.config.mjs const configPath = fs.existsSync(resolve(pkgRoot, "eslint.config.mjs")) ? resolve(pkgRoot, "eslint.config.mjs") : resolve(projectRoot, "eslint.config.mjs"); const eslint = new ESLint({cwd: pkgRoot, overrideConfigFile: configPath}); const filesToLint = await removeIgnoredFilesLegacy(pkgFiles, eslint); if (filesToLint.length) { commands.push( `eslint --config ${configPath} --max-warnings=0 --fix ${filesToLint.join(" ")}`, ); } } return commands; } return []; }, "**/*.{html,css,scss,json,jsonc,md,mdx}": async (files) => { return [`prettier --ignore-path .prettierignore --write ${files.join(" ")}`]; }, }; export default lintStaged;