Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | import fs from 'node:fs'; import { formatFiles, getProjects, type ExecutorContext, type ProjectConfiguration } from '@nx/devkit'; import { type Schema as EslintExecutorSchema } from '@nx/eslint/src/executors/lint/schema'; import { isEmpty, last, omit } from 'lodash-es'; import { type TargetConfiguration } from 'nx/src/config/workspace-json-project-json'; import { flushChanges, FsTree, printChanges } from 'nx/src/generators/tree'; import shell from 'shelljs'; import { dayjs } from '@amalia/ext/dayjs'; import { assert } from '@amalia/ext/typescript'; import { updateProjectConfiguration } from '../../generators/helpers/project-configuration'; import { debug } from './debug'; import { type LintMaxWarningsUpdaterExecutorSchema } from './schema.d'; const PATH_TO_FORMATTER = './libs/tools/plugin/src/executors/lint-max-warnings-updater/eslint-formatter.js'; const PATH_TO_HISTORY = './resources/codebase/warnings_history.json'; type WarningsHistory = Record<string, number>; type WarningsHistorySummary = { total: WarningsHistory; projects: Record<string, WarningsHistory>; }; const warningsHistorySummary = JSON.parse(fs.readFileSync(PATH_TO_HISTORY).toString()) as WarningsHistorySummary; const removeDuplicateValues = (historyRecord: WarningsHistory) => Object.entries(historyRecord).reduce((acc, [date, value]) => { const lastValue = last(Object.values(acc)); if (value !== lastValue) { acc[date] = value; } return acc; }, {} as WarningsHistory); const lintProjects = async (projectNames: string[]) => { const output: { projectName: string; warnings: number }[] = []; // First, lint projects, so it's in parallel. It'll fill the cache if needed. await new Promise<string>((resolve, reject) => { shell.exec( `nx run-many -t lint -p ${projectNames.join(' ')} --format=${PATH_TO_FORMATTER} --parallel=3`, {}, (code, stdout, stderr) => { if (code !== 0) { reject(stderr); return; } resolve(stdout); }, ); }); for (const projectName of projectNames) { // Then re-run the lint per project. I didn't find a clean way to get discriminated outputs of the run-many // because it'll only prefix the logs if we didn't hit the cache (lol). const cmdResult = shell.exec(`nx lint ${projectName} --format=${PATH_TO_FORMATTER}`, { async: false }); const matches = /Errors: \d+, Warnings: (?<warnings>\d+)/giu.exec(cmdResult); const warnings = matches?.groups?.['warnings'] || 0; output.push({ warnings: +warnings, projectName }); } return output.toSorted((a, b) => a.projectName.localeCompare(b.projectName)); }; const addLintMaxWarnings = (project: ProjectConfiguration, currentWarningsCount: number): ProjectConfiguration => { const lintTarget = project.targets?.['lint'] as TargetConfiguration<EslintExecutorSchema>; assert(lintTarget, `No lint target found for project ${project.name}`); return { ...project, targets: { ...project.targets, lint: { ...lintTarget, options: // Omit options if there are no options and no warnings (0 is the default value in nx.json). currentWarningsCount || !isEmpty(omit(lintTarget.options, 'maxWarnings')) ? { ...lintTarget.options, maxWarnings: currentWarningsCount || undefined, } : undefined, }, }, }; }; const executor = async ( { force = false }: LintMaxWarningsUpdaterExecutorSchema, { isVerbose = false, ...context }: ExecutorContext, ) => { debug.enabled ||= isVerbose; const tree = new FsTree(context.cwd, false); const allProjects = getProjects(tree); const currentDate = dayjs().format('YYYY-MM-DD'); const projectsToAnalyze = Array.from(allProjects.values()).filter( (project) => project.targets?.['lint']?.executor === '@nx/eslint:lint', ); if (force) { projectsToAnalyze.forEach((projectConfiguration) => { assert(projectConfiguration.name, `Project name missing in ${projectConfiguration.root}`); const newProjectConfiguration = addLintMaxWarnings(projectConfiguration, -1); updateProjectConfiguration(tree, projectConfiguration.name, newProjectConfiguration); }); flushChanges(context.cwd, tree.listChanges()); } const lintResults = await lintProjects(projectsToAnalyze.map((p) => p.name).filter(Boolean)); projectsToAnalyze.forEach((projectConfiguration) => { assert(projectConfiguration.name, `Project name missing in ${projectConfiguration.root}`); const lintResult = lintResults.find(({ projectName }) => projectConfiguration.name === projectName); assert(lintResult, `No lint result found for project ${projectConfiguration.name}`); const newProjectConfiguration = addLintMaxWarnings(projectConfiguration, lintResult.warnings); updateProjectConfiguration(tree, projectConfiguration.name, newProjectConfiguration); }); printChanges(tree.listChanges()); flushChanges(context.cwd, tree.listChanges()); lintResults .toSorted((a, b) => a.warnings - b.warnings) .forEach(({ projectName, warnings }) => { // eslint-disable-next-line no-console console.log(`${projectName}: ${warnings}`); }); const total = lintResults.reduce((acc, curr) => acc + (curr.warnings || 0), 0); // eslint-disable-next-line no-console console.info(`Total warnings: ${total}`); const computedWarningsHistorySummary: WarningsHistorySummary = { total: {}, projects: {}, }; // Writing that in the history file. lintResults.forEach(({ projectName, warnings }) => { computedWarningsHistorySummary.projects[projectName] = removeDuplicateValues({ ...warningsHistorySummary.projects[projectName], [currentDate]: warnings, }); }); computedWarningsHistorySummary.total = removeDuplicateValues({ ...warningsHistorySummary.total, [currentDate]: total, }); // Sort projects. computedWarningsHistorySummary.projects = Object.entries(computedWarningsHistorySummary.projects) .toSorted(([projectNameA], [projectNameB]) => projectNameA.localeCompare(projectNameB)) .reduce( (acc, [projectName, value]) => { acc[projectName] = value; return acc; }, {} as WarningsHistorySummary['projects'], ); fs.writeFileSync(PATH_TO_HISTORY, JSON.stringify(computedWarningsHistorySummary, undefined, 2)); await formatFiles(tree); return { success: true, }; }; export default executor; |