Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
51f7a89
chore: add eslint formatter for multiple formats
BioPhoton Aug 27, 2025
d3e41aa
chore: stricter plugin usage
BioPhoton Aug 27, 2025
ee9aae1
chore: remove package
BioPhoton Aug 27, 2025
4166956
refactor(eslint-multi-formatter): fix lint
BioPhoton Aug 27, 2025
8208532
Update tools/eslint-multi-format/README.md
BioPhoton Aug 28, 2025
d37c4a3
Update tools/eslint-multi-format/src/lib/utils.ts
BioPhoton Aug 28, 2025
6e0c734
Merge branch 'main' into tools/custom-eslint-formatter
BioPhoton Aug 28, 2025
7b090ca
Merge remote-tracking branch 'origin/tools/custom-eslint-formatter' i…
BioPhoton Aug 28, 2025
1592f62
refactor: full refactor + add tests
BioPhoton Aug 29, 2025
56cf11e
refactor: use error helper
BioPhoton Aug 29, 2025
53c5796
refactor: fix lint
BioPhoton Aug 29, 2025
65c365e
Merge branch 'main' into tools/custom-eslint-formatter
BioPhoton Sep 1, 2025
4de3d33
refactor: fix lint formatter usage
BioPhoton Sep 1, 2025
d3351f0
refactor: fix lint formatter lint
BioPhoton Sep 1, 2025
a786407
refactor: adjust lint targets
BioPhoton Sep 1, 2025
e1554d6
refactor: remove packages
BioPhoton Sep 1, 2025
944b141
refactor: fix lint
BioPhoton Sep 1, 2025
28bc74d
refactor: revert env vars
BioPhoton Sep 2, 2025
4e154e1
refactor: adjust GH ci action
BioPhoton Sep 2, 2025
7fa3e53
refactor: adjust GH ci action 2
BioPhoton Sep 2, 2025
72b3f2c
refactor: adjust targets
BioPhoton Sep 2, 2025
6e0d998
refactor: adjust targets 3
BioPhoton Sep 2, 2025
689d671
refactor: adjust targets 4
BioPhoton Sep 2, 2025
26b80b8
refactor: adjust targets 5
BioPhoton Sep 2, 2025
6372d63
refactor: adjust targets 6
BioPhoton Sep 2, 2025
3d306da
chore: revert targets
BioPhoton Sep 2, 2025
914b146
Merge branch 'main' into tools/custom-eslint-formatter
BioPhoton Sep 2, 2025
17f8029
chore: fix lint setup
BioPhoton Sep 2, 2025
9a73ab9
refactor: fix async
BioPhoton Sep 2, 2025
37ea315
refactor: copy code
BioPhoton Sep 2, 2025
563c8ec
refactor: use copy code
BioPhoton Sep 2, 2025
fe10e39
refactor: wip
BioPhoton Sep 2, 2025
e0270c6
refactor: rename package from eslint-formatter-multiple-formats to es…
BioPhoton Sep 2, 2025
7981046
refactor: fix targets
BioPhoton Sep 2, 2025
6c499cc
refactor: fix lint
BioPhoton Sep 2, 2025
ccdb58d
refactor: fix lint target
BioPhoton Sep 2, 2025
16a3977
refactor: use plugin to set up targets
BioPhoton Sep 3, 2025
97e1e6c
chore: adjust lint target
BioPhoton Sep 3, 2025
44ddced
chore: fix pkg install
BioPhoton Sep 3, 2025
8eb075a
chore: fix lint
BioPhoton Sep 3, 2025
28b76f0
Merge branch 'main' into tools/custom-eslint-formatter
BioPhoton Sep 3, 2025
f56ba9e
Update tools/eslint-formatter-multi/package.json
BioPhoton Sep 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion code-pushup.preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,13 @@ export const eslintCoreConfigNx = async (
eslintrc: `packages/${projectName}/eslint.config.js`,
patterns: ['.'],
})
: await eslintPlugin(await eslintConfigFromAllNxProjects()),
: await eslintPlugin(await eslintConfigFromAllNxProjects(), {
artifacts: {
// We leverage Nx dependsOn to only run all lint targets before we run code-pushup
// generateArtifactsCommand: 'npx nx run-many -t lint',
artifactsPaths: ['packages/**/.eslint/eslint-report.json'],
},
}),
],
categories: eslintCategories,
});
Expand Down
64 changes: 31 additions & 33 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,40 @@
}
],
"sharedGlobals": [
{ "runtime": "node -e \"console.log(require('os').platform())\"" },
{ "runtime": "node -v" },
{ "runtime": "npm -v" }
{
"runtime": "node -e \"console.log(require('os').platform())\""
},
{
"runtime": "node -v"
},
{
"runtime": "npm -v"
}
]
},
"targetDefaults": {
"lint": {
"dependsOn": ["eslint-formatter-multi:build"],
"inputs": ["lint-eslint-inputs"],
"outputs": ["{projectRoot}/.eslint/**/*"],
"cache": true,
"executor": "nx:run-commands",
"options": {
"command": "eslint",
"args": [
"{projectRoot}/**/*.ts",
"{projectRoot}/package.json",
"--config={projectRoot}/eslint.config.js",
"--max-warnings=0",
"--no-warn-ignored",
"--error-on-unmatched-pattern=false",
"--format=./tools/eslint-formatter-multi/dist/src/index.js"
],
"env": {
"ESLINT_FORMATTER_CONFIG": "{\"outputDir\":\"{projectRoot}/.eslint\"}"
}
}
},
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
Expand Down Expand Up @@ -97,36 +125,6 @@
"inputs": ["default"],
"cache": true
},
"lint": {
"inputs": ["lint-eslint-inputs"],
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"cache": true,
"options": {
"errorOnUnmatchedPattern": false,
"maxWarnings": 0,
"lintFilePatterns": [
"{projectRoot}/**/*.ts",
"{projectRoot}/package.json"
]
}
},
"lint-report": {
"inputs": ["default", "{workspaceRoot}/eslint.config.?(c)js"],
"outputs": ["{projectRoot}/.eslint/eslint-report*.json"],
"cache": true,
"executor": "@nx/linter:eslint",
"options": {
"errorOnUnmatchedPattern": false,
"maxWarnings": 0,
"format": "json",
"outputFile": "{projectRoot}/.eslint/eslint-report.json",
"lintFilePatterns": [
"{projectRoot}/**/*.ts",
"{projectRoot}/package.json"
]
}
},
"nxv-pkg-install": {
"parallelism": false
},
Expand Down
8 changes: 7 additions & 1 deletion packages/plugin-eslint/src/lib/runner/lint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async function executeLint({
patterns,
}: ESLintTarget): Promise<ESLint.LintResult[]> {
// running as CLI because ESLint#lintFiles() runs out of memory
const { stdout } = await executeProcess({
const { stdout, stderr, code } = await executeProcess({
command: 'npx',
args: [
'eslint',
Expand All @@ -42,6 +42,12 @@ async function executeLint({
cwd: process.cwd(),
});

if (!stdout.trim()) {
throw new Error(
`ESLint produced empty output. Exit code: ${code}, STDERR: ${stderr}`,
);
}

return JSON.parse(stdout) as ESLint.LintResult[];
}

Expand Down
9 changes: 8 additions & 1 deletion project.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
}
]
},
"code-pushup-eslint": {},
"code-pushup-eslint": {
"dependsOn": [
{
"target": "lint",
"projects": "*"
}
]
},
"code-pushup-jsdocs": {},
"code-pushup-typescript": {},
"code-pushup": {
Expand Down
77 changes: 77 additions & 0 deletions tools/eslint-formatter-multi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# ESLint Multi Formatter

The ESLint plugin uses a custom formatter that supports multiple output formats and destinations simultaneously.

## Configuration

Use the `ESLINT_FORMATTER_CONFIG` environment variable to configure the formatter with JSON.

### Configuration Schema

```jsonc
{
"outputDir": "./reports", // Optional: Output directory (default: cwd/.eslint)
"filename": "eslint-report", // Optional: Base filename without extension (default: 'eslint-report')
"formats": ["json"], // Optional: Array of format names for file output (default: ['json'])
"terminal": "stylish", // Optional: Format for terminal output (default: 'stylish')
"verbose": true, // Optional: Enable verbose logging (default: false)
}
```

### Supported Formats

The following ESLint formatters are supported:

- `stylish` (default terminal output)
- `json` (default file output)
- Custom formatters (fallback to stylish formatting)

## Usage Examples

### Basic Usage

```bash
# Default behavior - JSON file output + stylish console output
npx eslint .

# Custom output directory and filename
ESLINT_FORMATTER_CONFIG='{"outputDir":"./ci-reports","filename":"lint-results"}' npx eslint .
# Creates: ci-reports/lint-results.json + terminal output
```

### Multiple Output Formats

```bash
# Generate JSON file
ESLINT_FORMATTER_CONFIG='{"formats":["json"],"terminal":"stylish"}' npx eslint .
# Creates: .eslint/eslint-report.json + terminal output

# Custom directory with JSON format
ESLINT_FORMATTER_CONFIG='{"outputDir":"./reports","filename":"eslint-results","formats":["json"]}' npx eslint .
# Creates: reports/eslint-results.json
```

### Terminal Output Only

```bash
# Only show terminal output, no files
ESLINT_FORMATTER_CONFIG='{"formats":[],"terminal":"stylish"}' npx eslint .

# Different terminal format
ESLINT_FORMATTER_CONFIG='{"formats":[],"terminal":"stylish"}' npx eslint .
```

## Default Behavior

When no `ESLINT_FORMATTER_CONFIG` is provided, the formatter uses these defaults:

- **outputDir**: `./.eslint` (relative to current working directory)
- **filename**: `eslint-report`
- **formats**: `["json"]`
- **terminal**: `stylish`
- **verbose**: `false`

This means by default you get:

- A JSON file at `./.eslint/eslint-report.json`
- Stylish terminal output
21 changes: 21 additions & 0 deletions tools/eslint-formatter-multi/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import tseslint from 'typescript-eslint';
import baseConfig from '../../eslint.config.js';

export default tseslint.config(
...baseConfig,
{
files: ['**/*.ts'],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
files: ['**/*.json'],
rules: {
'@nx/dependency-checks': ['error'],
},
},
);
41 changes: 41 additions & 0 deletions tools/eslint-formatter-multi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@code-pushup/eslint-formatter-multi",
"version": "0.0.1",
"private": false,
"type": "module",
"main": "./src/index.js",
"types": "./src/index.d.ts",
"engines": {
"node": ">=17.0.0"
},
"description": "ESLint formatter that supports multiple output formats and destinations simultaneously",
"author": "Michael Hladky",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/code-pushup/cli.git",
"directory": "tools/eslint-formatter-multi"
},
"keywords": [
"eslint",
"eslint-formatter",
"eslintformatter",
"multi-format",
"code-quality",
"linting"
],
"files": [
"src/*",
"README.md"
],
"publishConfig": {
"access": "public"
},
"dependencies": {
"ansis": "^3.3.0",
"tslib": "^2.8.1"
},
"peerDependencies": {
"eslint": "^8.0.0 || ^9.0.0"
}
}
12 changes: 12 additions & 0 deletions tools/eslint-formatter-multi/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "eslint-formatter-multi",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "tools/eslint-formatter-multi/src",
"projectType": "library",
"tags": ["scope:tooling", "type:util"],
"targets": {
"lint": {},
"unit-test": {},
"build": {}
}
}
3 changes: 3 additions & 0 deletions tools/eslint-formatter-multi/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default } from './lib/multiple-formats.js';
export * from './lib/multiple-formats.js';
export * from './lib/utils.js';
83 changes: 83 additions & 0 deletions tools/eslint-formatter-multi/src/lib/multiple-formats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { ESLint } from 'eslint';
import * as process from 'node:process';
import type { FormatterConfig } from './types.js';
import {
type EslintFormat,
formatTerminalOutput,
findConfigFromEnv as getConfigFromEnv,
persistEslintReports,
} from './utils.js';

export const DEFAULT_OUTPUT_DIR = '.eslint';
export const DEFAULT_FILENAME = 'eslint-report';
export const DEFAULT_FORMATS = ['json'] as EslintFormat[];
export const DEFAULT_TERMINAL = 'stylish' as EslintFormat;

export const DEFAULT_CONFIG: Required<
Pick<
FormatterConfig,
'outputDir' | 'filename' | 'formats' | 'terminal' | 'verbose'
>
> = {
outputDir: DEFAULT_OUTPUT_DIR,
filename: DEFAULT_FILENAME,
formats: DEFAULT_FORMATS,
terminal: DEFAULT_TERMINAL,
verbose: false,
};

/**
* Format ESLint results using multiple configurable formatters
*
* @param results - The ESLint results
* @param args - The arguments passed to the formatter
* @returns The formatted results for terminal display
*
* @example
* // Basic usage:
* ESLINT_FORMATTER_CONFIG='{"filename":"lint-results","formats":["json"],"terminal":"stylish"}' npx eslint .
* // Creates: .eslint/eslint-results.json + terminal output
*
* // With custom output directory:
* ESLINT_FORMATTER_CONFIG='{"outputDir":"./ci-reports","filename":"eslint-report","formats":["json","html"],"terminal":"stylish"}' nx lint utils
* // Creates: ci-reports/eslint-report.json, ci-reports/eslint-report.html + terminal output
*
* Configuration schema:
* {
* "outputDir": "./reports", // Optional: Output directory (default: cwd/.eslint)
* "filename": "eslint-report", // Optional: Base filename without extension (default: 'eslint-report')
* "formats": ["json"], // Optional: Array of format names for file output (default: ['json'])
* "terminal": "stylish" // Optional: Format for terminal output (default: 'stylish')
* }
*/
export default async function multipleFormats(
results: ESLint.LintResult[],
_args?: unknown,
): Promise<string> {
const config = {
...DEFAULT_CONFIG,
...getConfigFromEnv(process.env),
} satisfies FormatterConfig;

const {
outputDir = DEFAULT_OUTPUT_DIR,
filename,
formats,
terminal,
verbose = false,
} = config;

try {
await persistEslintReports(formats, results, {
outputDir,
filename,
verbose,
});
} catch (error) {
if (verbose) {
console.error('Error writing ESLint reports:', error);
}
}

return formatTerminalOutput(terminal, results);
}
Loading
Loading