centralized-eslint-prettier

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Centralized ESLint # Centralized ESLint & Prettier Configuration Prettier Configuration

集中式ESLint & Prettier配置

This is a reference pattern. Learn from the approach, adapt to your context — don't copy verbatim.
Problem: Multi-workspace TypeScript projects (frontend, backend, infrastructure) need consistent linting and formatting without duplication, with support for pre-commit hooks and full-repo formatting.
Solution: Single root-level
eslint.config.mjs
with workspace-specific rules via file patterns, unified Prettier config, and centralized npm scripts with Husky integration.

这是一个参考模式:请学习这种思路,适配你自己的场景,不要直接逐字复制。
问题:多工作区TypeScript项目(前端、后端、基础设施)需要一致的代码检查和格式化能力,同时避免配置重复,还要支持pre-commit钩子和全仓库格式化。
解决方案:根目录下唯一的
eslint.config.mjs
文件,通过文件模式配置工作区专属规则,统一的Prettier配置,以及集成Husky的集中式npm脚本。

Why This Pattern?

为什么选择这个模式?

Benefits:
  • Single Source of Truth: One config file, no duplication
  • Workspace Flexibility: Different rules per workspace via file patterns
  • Simplified Maintenance: Update rules in one place
  • Consistent Pre-commit: Same hooks across all workspaces
  • Easy CI/CD: Single command lints entire codebase
  • Project Agnostic: Works for any TypeScript monorepo structure
Use Cases:
  • Multi-workspace TypeScript projects (Next.js + CDK, Nuxt + Node.js, etc.)
  • Projects with different linting needs per workspace (frontend vs CLI vs Lambda)
  • Teams wanting consistent code style without per-workspace configuration
  • Projects needing both pre-commit hooks and full-repo formatting

优势
  • 唯一可信源:一份配置文件,无重复
  • 工作区灵活性:通过文件模式为不同工作区配置不同规则
  • 简化维护:只需在一处更新规则
  • 一致的Pre-commit能力:所有工作区使用相同的钩子
  • 简单的CI/CD集成:单个命令即可检查整个代码库
  • 项目无关性:适用于任何TypeScript monorepo结构
适用场景
  • 多工作区TypeScript项目(Next.js + CDK、Nuxt + Node.js等)
  • 不同工作区有不同代码检查需求的项目(前端、CLI、Lambda之间的差异)
  • 希望无需为每个工作区单独配置就能实现统一代码风格的团队
  • 同时需要pre-commit钩子和全仓库格式化能力的项目

Pattern

模式结构

Architecture:
project-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
Key Components:
  • Root ESLint Config: Flat config (ESLint 9+) with file pattern-based rules
  • Workspace-Specific Rules: Different rules for frontend/backend/CLI via glob patterns
  • Prettier Integration: Single
    .prettierrc
    for all workspaces
  • Husky + lint-staged: Pre-commit formatting on changed files
  • Centralized Scripts: All lint/format commands in root
    package.json

架构
project-root/
├── eslint.config.mjs          # 唯一可信源
├── .prettierrc                 # 统一格式化规则
├── package.json                # 仅根目录存放脚本
├── .husky/pre-commit          # Git钩子
├── pnpm-workspace.yaml
├── frontend/
│   ├── package.json           # 不存放检查相关脚本
│   └── tsconfig.json
└── infrastructure/
    ├── package.json           # 不存放检查相关脚本
    └── tsconfig.json
核心组件
  • 根目录ESLint配置:采用ESLint 9+的扁平配置,基于文件模式配置规则
  • 工作区专属规则:通过glob模式为前端/后端/CLI配置不同规则
  • Prettier集成:所有工作区共用唯一的
    .prettierrc
    配置
  • Husky + lint-staged:pre-commit阶段自动格式化变更文件
  • 集中式脚本:所有检查/格式化命令都存放在根目录
    package.json

Implementation

实现步骤

1. Root ESLint Config (
eslint.config.mjs
)

1. 根目录ESLint配置(
eslint.config.mjs

javascript
import 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' }
  }
];
javascript
import 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 [
  // 全局忽略规则
  { ignores: IGNORE_PATTERNS },

  // 前端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'
    }
  },

  // 基础设施层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脚本 - 允许控制台输出
  {
    files: [
      'infrastructure/lib/cli/**/*.ts',
      'scripts/**/*.ts'
    ],
    rules: { 'no-console': 'off' }
  }
];

2. Prettier Config (
.prettierrc
)

2. Prettier配置(
.prettierrc

json
{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "endOfLine": "auto"
}
json
{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "endOfLine": "auto"
}

3. Root Package.json Scripts

3. 根目录Package.json脚本

json
{
  "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"
    ]
  }
}
Critical: Workspace
package.json
files should NOT have lint/format scripts.
json
{
  "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
文件不应该包含任何检查/格式化相关脚本。

4. Husky Pre-commit Hook

4. Husky Pre-commit钩子

bash
undefined
bash
undefined

.husky/pre-commit

.husky/pre-commit

pnpm exec lint-staged
undefined
pnpm exec lint-staged
undefined

5. Dependencies

5. 依赖安装

bash
pnpm add -D -w eslint \
  @typescript-eslint/parser \
  @typescript-eslint/eslint-plugin \
  typescript-eslint \
  @eslint/js \
  globals \
  prettier \
  eslint-config-prettier \
  husky \
  lint-staged

bash
pnpm add -D -w eslint \
  @typescript-eslint/parser \
  @typescript-eslint/eslint-plugin \
  typescript-eslint \
  @eslint/js \
  globals \
  prettier \
  eslint-config-prettier \
  husky \
  lint-staged

Framework-Specific Variations

框架专属变体

Next.js Frontend

Next.js前端

javascript
import nextPlugin from '@next/eslint-plugin-next';

{
  files: ['frontend/**/*.{ts,tsx}'],
  plugins: {
    '@next/next': nextPlugin,
    '@typescript-eslint': typescriptEslint
  },
  settings: {
    next: { rootDir: path.join(__dirname, 'frontend') }
  }
}
javascript
import 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.js Frontend (Auto-generated Config)

Nuxt.js前端(自动生成配置)

Nuxt auto-generates
.nuxt/eslint.config.mjs
. Keep it and extend:
javascript
// frontend/eslint.config.mjs
import withNuxt from './.nuxt/eslint.config.mjs';
import { SHARED_RULES } from '../eslint.shared.mjs';

export default withNuxt({
  rules: { ...SHARED_RULES }
});
Note: For Nuxt, keep the workspace-level config due to auto-generation. Create
eslint.shared.mjs
at root to share rules.
Nuxt会自动生成
.nuxt/eslint.config.mjs
,保留该文件并扩展即可:
javascript
// frontend/eslint.config.mjs
import withNuxt from './.nuxt/eslint.config.mjs';
import { SHARED_RULES } from '../eslint.shared.mjs';

export default withNuxt({
  rules: { ...SHARED_RULES }
});
注意:对于Nuxt,由于配置是自动生成的,需要保留工作区级别的配置。可以在根目录创建
eslint.shared.mjs
来共享通用规则。

Lambda Functions

Lambda函数

javascript
{
  files: ['infrastructure/lib/lambdas/**/*.ts'],
  rules: {
    'no-console': 'off',  // CloudWatch logs
    '@typescript-eslint/no-explicit-any': 'error'
  }
}

javascript
{
  files: ['infrastructure/lib/lambdas/**/*.ts'],
  rules: {
    'no-console': 'off',  // 适配CloudWatch日志需求
    '@typescript-eslint/no-explicit-any': 'error'
  }
}

Workspace-Specific Patterns

工作区专属模式

Two-Tier CLI (Simple Projects)

两层CLI(简单项目)

javascript
{
  files: ['scripts/**/*.ts', 'tools/**/*.ts'],
  rules: { 'no-console': 'off' }
}
javascript
{
  files: ['scripts/**/*.ts', 'tools/**/*.ts'],
  rules: { 'no-console': 'off' }
}

Three-Tier CLI (Infrastructure Projects)

三层CLI(基础设施项目)

javascript
// 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' }
}

javascript
// 第一层:CLI二进制文件
{
  files: ['infrastructure/lib/cli/bin/**/*.ts'],
  rules: { 'no-console': 'off' }
},

// 第二层:命令逻辑
{
  files: ['infrastructure/lib/cli/commands/**/*.ts'],
  rules: { 'no-console': 'off' }
},

// 第三层:领域逻辑
{
  files: ['infrastructure/core/**/*.ts'],
  rules: { 'no-console': 'warn' }
}

Usage

使用方式

Pre-commit (Automatic)

Pre-commit(自动执行)

bash
git add .
git commit -m "feat: add feature"
bash
git add .
git commit -m "feat: add feature"

Automatically runs lint-staged on changed files

自动在变更文件上执行lint-staged

undefined
undefined

Full Repo Formatting

全仓库格式化

bash
undefined
bash
undefined

Check formatting

检查格式化合规性

pnpm format:check
pnpm format:check

Fix all files

修复所有文件的格式问题

pnpm format
pnpm format

Lint entire codebase

检查整个代码库的代码规范

pnpm lint
pnpm lint

Auto-fix linting issues

自动修复可解决的代码规范问题

pnpm lint:fix
undefined
pnpm lint:fix
undefined

CI/CD Integration

CI/CD集成

yaml
undefined
yaml
undefined

GitHub Actions

GitHub Actions

  • run: pnpm install --frozen-lockfile
  • run: pnpm lint
  • run: pnpm format:check

---
  • run: pnpm install --frozen-lockfile
  • run: pnpm lint
  • run: pnpm format:check

---

Tradeoffs

权衡说明

ESLint 9+ Flat Config Required

要求使用ESLint 9+扁平配置

Constraint: This pattern uses ESLint 9+ flat config format (
.mjs
file).
Why: Flat config is the future of ESLint and provides better TypeScript support.
Migration: Old
.eslintrc.js
configs need conversion. See ESLint migration guide.
约束:该模式使用ESLint 9+的扁平配置格式(
.mjs
文件)。
原因:扁平配置是ESLint的未来方向,对TypeScript的支持更好。
迁移方案:旧的
.eslintrc.js
配置需要转换,可参考ESLint迁移指南

File Pattern Ordering Matters

文件模式的顺序很重要

Constraint: More specific patterns must come after general ones.
Example:
javascript
// ✅ 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: {...} }
约束:更具体的模式必须放在通用模式之后。
示例
javascript
// ✅ 正确顺序
{ files: ['**/*.ts'], rules: {...} },
{ files: ['frontend/**/*.ts'], rules: {...} },
{ files: ['frontend/lib/cli/**/*.ts'], rules: {...} }

// ❌ 错误顺序 - 特定规则不会生效
{ files: ['frontend/lib/cli/**/*.ts'], rules: {...} },
{ files: ['**/*.ts'], rules: {...} }

Nuxt.js Exception

Nuxt.js例外情况

Constraint: Nuxt auto-generates ESLint config, requiring workspace-level config.
Solution: Keep
frontend/eslint.config.mjs
but import shared rules from root via
eslint.shared.mjs
.

约束:Nuxt会自动生成ESLint配置,需要保留工作区级别的配置。
解决方案:保留
frontend/eslint.config.mjs
,但通过
eslint.shared.mjs
从根目录导入共享规则。

When NOT to Use

不适用场景

  • Single-workspace projects: Simpler to use workspace-level config
  • Non-TypeScript projects: Pattern is TypeScript-focused (though adaptable)
  • Legacy ESLint versions: Requires ESLint 9+ for flat config
  • Highly divergent workspace needs: If workspaces need completely different tooling, separate configs may be clearer

  • 单工作区项目:直接使用工作区级别的配置更简单
  • 非TypeScript项目:该模式聚焦于TypeScript场景(不过也可适配)
  • 旧版本ESLint:需要ESLint 9+才能支持扁平配置
  • 工作区需求差异极大的项目:如果各工作区需要完全不同的工具链,使用独立配置会更清晰

Verification Checklist

验证检查清单

After setup:
bash
undefined
配置完成后:
bash
undefined

1. Full codebase linting works

1. 全代码库检查正常运行

pnpm lint
pnpm lint

2. Auto-fix works

2. 自动修复功能正常

pnpm lint:fix
pnpm lint:fix

3. Formatting works

3. 格式化功能正常

pnpm format
pnpm format

4. Pre-commit hooks work

4. Pre-commit钩子正常工作

git add . && git commit -m "test"
git add . && git commit -m "test"

5. No duplicate scripts in workspaces

5. 工作区无重复脚本

grep -r '"lint":' */package.json
grep -r '"lint":' */package.json

Should ONLY show root package.json

应该只在根目录package.json中匹配到结果


---

---

Related Patterns

相关模式

  • CLI Architecture - Understanding CLI tiers for console.log rules
  • Environment Validation - Fail-fast validation patterns

  • CLI架构 - 了解CLI分层逻辑,合理配置console.log规则
  • 环境校验 - 快速失败的校验模式

Progressive Improvement

持续优化

If the developer corrects a behavior that this skill should have prevented, suggest a specific amendment to this skill to prevent the same correction in the future.
如果开发者修正了本技能本应避免的问题,请针对本技能提出具体的修改建议,避免后续再出现同类问题。