Loading...
Loading...
Single source of truth for linting/formatting across workspaces. Apply when setting up or modifying ESLint/Prettier configuration in multi-workspace projects.
npx skill4agent add loxosceles/ai-dev centralized-eslint-prettiereslint.config.mjsproject-root/
├── eslint.config.mjs # Single source of truth
├── .prettierrc # Unified formatting rules
├── package.json # Root scripts only
├── .husky/pre-commit # Git hooks
├── pnpm-workspace.yaml
├── frontend/
│ ├── package.json # NO lint scripts
│ └── tsconfig.json
└── infrastructure/
├── package.json # NO lint scripts
└── tsconfig.json.prettierrcpackage.jsoneslint.config.mjsimport js from '@eslint/js';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import globals from 'globals';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const IGNORE_PATTERNS = [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/.next/**',
'**/cdk.out/**',
'**/*.d.ts',
'**/*.config.js'
];
const SHARED_RULES = {
'eol-last': ['error', 'always'],
'no-console': ['warn', { allow: ['error', 'warn'] }],
'no-unused-vars': 'off'
};
export default [
// Global ignores
{ ignores: IGNORE_PATTERNS },
// Frontend TypeScript
{
files: ['frontend/**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
project: path.join(__dirname, 'frontend/tsconfig.json'),
ecmaVersion: 'latest',
sourceType: 'module'
}
},
plugins: { '@typescript-eslint': typescriptEslint },
rules: {
...SHARED_RULES,
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-explicit-any': 'error'
}
},
// Infrastructure TypeScript
{
files: ['infrastructure/**/*.ts'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
project: path.join(__dirname, 'infrastructure/tsconfig.json'),
ecmaVersion: 'latest',
sourceType: 'module'
}
},
plugins: { '@typescript-eslint': typescriptEslint },
rules: {
...SHARED_RULES,
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-explicit-any': 'error'
}
},
// CLI scripts - allow console output
{
files: [
'infrastructure/lib/cli/**/*.ts',
'scripts/**/*.ts'
],
rules: { 'no-console': 'off' }
}
];.prettierrc{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"endOfLine": "auto"
}{
"scripts": {
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
"prepare": "husky"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write --end-of-line auto"
],
"*.{json,md,yml}": [
"prettier --write --end-of-line auto"
]
}
}package.json# .husky/pre-commit
pnpm exec lint-stagedpnpm add -D -w eslint \
@typescript-eslint/parser \
@typescript-eslint/eslint-plugin \
typescript-eslint \
@eslint/js \
globals \
prettier \
eslint-config-prettier \
husky \
lint-stagedimport nextPlugin from '@next/eslint-plugin-next';
{
files: ['frontend/**/*.{ts,tsx}'],
plugins: {
'@next/next': nextPlugin,
'@typescript-eslint': typescriptEslint
},
settings: {
next: { rootDir: path.join(__dirname, 'frontend') }
}
}.nuxt/eslint.config.mjs// frontend/eslint.config.mjs
import withNuxt from './.nuxt/eslint.config.mjs';
import { SHARED_RULES } from '../eslint.shared.mjs';
export default withNuxt({
rules: { ...SHARED_RULES }
});eslint.shared.mjs{
files: ['infrastructure/lib/lambdas/**/*.ts'],
rules: {
'no-console': 'off', // CloudWatch logs
'@typescript-eslint/no-explicit-any': 'error'
}
}{
files: ['scripts/**/*.ts', 'tools/**/*.ts'],
rules: { 'no-console': 'off' }
}// Tier 1: CLI Binaries
{
files: ['infrastructure/lib/cli/bin/**/*.ts'],
rules: { 'no-console': 'off' }
},
// Tier 2: Commands
{
files: ['infrastructure/lib/cli/commands/**/*.ts'],
rules: { 'no-console': 'off' }
},
// Tier 3: Domain Logic
{
files: ['infrastructure/core/**/*.ts'],
rules: { 'no-console': 'warn' }
}git add .
git commit -m "feat: add feature"
# Automatically runs lint-staged on changed files# Check formatting
pnpm format:check
# Fix all files
pnpm format
# Lint entire codebase
pnpm lint
# Auto-fix linting issues
pnpm lint:fix# GitHub Actions
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm format:check.mjs.eslintrc.js// ✅ Correct order
{ files: ['**/*.ts'], rules: {...} },
{ files: ['frontend/**/*.ts'], rules: {...} },
{ files: ['frontend/lib/cli/**/*.ts'], rules: {...} }
// ❌ Wrong order - specific rules won't apply
{ files: ['frontend/lib/cli/**/*.ts'], rules: {...} },
{ files: ['**/*.ts'], rules: {...} }frontend/eslint.config.mjseslint.shared.mjs# 1. Full codebase linting works
pnpm lint
# 2. Auto-fix works
pnpm lint:fix
# 3. Formatting works
pnpm format
# 4. Pre-commit hooks work
git add . && git commit -m "test"
# 5. No duplicate scripts in workspaces
grep -r '"lint":' */package.json
# Should ONLY show root package.json