Automated Quality Gates: My Development Pipeline

How I use automated quality gates to maintain code standards without slowing down development.

Automated Quality Gates: My Development Pipeline

Quality shouldn't be a manual process. After years of debugging issues that could have been caught earlier, I've built a comprehensive quality pipeline that runs with a single command: bun run quality.

This isn't just about catching bugs—it's about creating a development environment where quality is automatic, not accidental.

The Complete Quality Pipeline

Here's my actual package.json setup that powers the entire quality system:

[CODEBLOCK:json]{ "scripts": { "lint": "eslint", "knip": "knip", "loc": "tsx scripts/loc.ts", "loc:check": "tsx scripts/loc.ts --check", "quality": "bun run lint && bunx tsc --noEmit && bun run loc:check && bun run knip" }, "dependencies": { "knip": "^5.63.1", "tsx": "^4.20.5" }, "devDependencies": { "eslint": "^9", "eslint-config-next": "15.5.3", "typescript": "^5" } } [/CODEBLOCK]

The beauty of this setup is its simplicity: one command, four comprehensive checks. Let me break down each gate and why it matters:

Gate 1: ESLint - Code Style & Best Practices

Using ESLint 9 with Next.js configuration, this catches the subtle issues that compound over time:

[CODEBLOCK:javascript]// My actual eslint.config.mjs setup import { FlatCompat } from '@eslint/eslintrc'; const compat = new FlatCompat({ baseDirectory: import.meta.dirname, }); export default [ ...compat.extends('next/core-web-vitals', 'next/typescript'), { rules: { '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-explicit-any': 'error', 'prefer-const': 'error', 'no-var': 'error', // Next.js specific optimizations '@next/next/no-img-element': 'error', '@next/next/no-html-link-for-pages': 'error' } } ]; [/CODEBLOCK]

What it catches in practice:

  • Unused imports: Prevents bundle bloat from dead imports
  • TypeScript violations: Catches any usage that defeats type safety
  • Next.js anti-patterns: Image optimization violations, incorrect Link usage
  • Modern JavaScript: Enforces const/let over var

The key insight: ESLint isn't just about style—it's about preventing performance and maintainability issues before they reach production.

Gate 2: TypeScript Compiler - Type Safety

Running tsc --noEmit performs a full type check without generating JavaScript files:

[CODEBLOCK:bash]# The actual command from my quality pipeline bunx tsc --noEmit [/CODEBLOCK]

With TypeScript 5 and strict mode enabled, this catches issues that would otherwise become runtime errors:

[CODEBLOCK:typescript]// tsconfig.json (key settings) { "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true } } [/CODEBLOCK]

Real-world catches from my codebase:

  • API response mismatches: When backend changes break frontend assumptions
  • Component prop errors: Missing required props or incorrect types
  • Async/await issues: Unhandled promises and incorrect return types
  • Array access safety: Prevents array[0] crashes when array might be empty

The --noEmit flag is crucial—it runs faster than a full build and focuses purely on type checking.

Gate 3: Line Count Check - Complexity Control

This uses my custom tsx scripts/loc.ts --check command (detailed in my 200-line rule note) to enforce architectural discipline:

[CODEBLOCK:typescript]// From my actual scripts/loc.ts class LineCounter { async run(checkMode = false): Promise<void> { const results = await this.scanDirectory(this.rootDir); if (checkMode && results.oversizedFiles.length > 0) { this.printOversizedFiles(results); process.exit(1); // Fail the quality check } this.printResults(results); } } [/CODEBLOCK]

With my loc.config.json configuration:

[CODEBLOCK:json]{ "maxLines": 200, "include": { "extensions": [".ts", ".tsx", ".js", ".jsx"] }, "exclude": { "dirs": ["node_modules", ".next", "dist"], "patterns": [".*\\.d\\.ts$", ".*\\.config\\.(js|ts)$"] } } [/CODEBLOCK]

Why this matters:

  • AI collaboration: Smaller files work better with AI assistants
  • Code review efficiency: Easier to review focused, single-purpose files
  • Debugging speed: Less code to search through when issues arise
  • Refactoring safety: Smaller blast radius for changes

The --check flag makes it fail CI if any files exceed the limit, forcing architectural decisions rather than allowing technical debt.

Gate 4: Knip - Dead Code Detection

Knip 5.63.1 is the final gate, catching what the other tools miss—code that exists but serves no purpose:

[CODEBLOCK:typescript]// My actual knip.config.ts export default { entry: [ 'app/**/*.{ts,tsx}', 'components/**/*.{ts,tsx}', 'lib/**/*.{ts,tsx}', 'scripts/**/*.ts' ], project: ['**/*.{ts,tsx}'], ignore: [ 'docs/**', '**/*.d.ts', 'content/**/*.md' ], ignoreDependencies: [ '@types/*', 'eslint-config-*' ] }; [/CODEBLOCK]

Real cleanup wins from my codebase:

  • Unused dependencies: Removed 12 packages worth 2.3MB from bundle
  • Dead imports: Found components imported but never used
  • Orphaned files: Discovered old utility files no longer referenced
  • Type-only imports: Identified missing type keywords affecting bundle size

Knip runs last because it's the most comprehensive—it analyzes the entire dependency graph after other tools have ensured the code is valid.

Integration Strategy

Development Workflow Integration

I run bun run quality at three key points:

  1. Before commits: Catches issues early in development
  2. During code review: Ensures PR quality
  3. In CI/CD: Final safety net before deployment
[CODEBLOCK:bash]# My typical development flow git add . bun run quality # Run before committing git commit -m "feat: add new component" [/CODEBLOCK]

CI/CD Pipeline

[CODEBLOCK:yaml]# .github/workflows/quality.yml name: Quality Gates on: [push, pull_request] jobs: quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 with: version: 8 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'bun' - run: bun install --frozen-lockfile - run: bun run quality [/CODEBLOCK]

Performance Optimization

The pipeline runs in about 15-30 seconds on my codebase:

[CODEBLOCK:bash]# Timing breakdown (approximate) # ESLint: 3-8 seconds # TypeScript: 5-15 seconds # LOC check: 1-2 seconds # Knip: 3-8 seconds [/CODEBLOCK]

Key optimizations:

  • Sequential execution: Fail fast on first error
  • Cached dependencies: bun's efficient caching
  • Incremental TypeScript: Only checks changed files in watch mode

Real-World Impact

After 6 months of using this quality pipeline, the results speak for themselves:

Quantified Benefits

  • 90% reduction in style-related code review comments
  • Zero production TypeScript errors since implementation
  • 15% smaller bundle size from dead code elimination
  • 50% faster debugging with smaller, focused files

Unexpected Wins

  • Better AI collaboration: Smaller files work seamlessly with AI assistants
  • Faster onboarding: New developers can understand the codebase structure quickly
  • Reduced cognitive load: No mental energy spent on style decisions
  • Deployment confidence: Green pipeline means production-ready code

Implementation Guide for Your Team

Start Simple, Scale Gradually

[CODEBLOCK:json]// Week 1: Basic safety net { "scripts": { "quality:basic": "bun run lint && bunx tsc --noEmit" } } // Week 2: Add complexity control { "scripts": { "quality": "bun run lint && bunx tsc --noEmit && bun run loc:check" } } // Week 3: Full pipeline { "scripts": { "quality": "bun run lint && bunx tsc --noEmit && bun run loc:check && bun run knip" } } [/CODEBLOCK]

Essential Dependencies

[CODEBLOCK:bash]# Core quality tools bun add -D eslint typescript tsx knip # Framework-specific (for Next.js) bun add -D eslint-config-next @eslint/eslintrc [/CODEBLOCK]

Lessons Learned and Trade-offs

The Good

  • Consistency: Every developer follows the same standards
  • Early detection: Issues caught in development, not production
  • Automated enforcement: No need to remember to run checks
  • Confidence: Green pipeline = deployable code

The Challenges

  • Initial setup time: Getting all tools configured correctly
  • Learning curve: Team needs to understand each tool's purpose
  • Occasional false positives: Knip sometimes flags legitimate code
  • Pipeline time: Adds 15-30 seconds to development cycle

When to Break the Rules

Sometimes you need exceptions:

  • Generated files: Auto-generated code often exceeds line limits
  • Configuration files: Complex configs might need more flexibility
  • Legacy code: Gradual migration rather than big-bang refactoring

The key is being intentional about exceptions and documenting why they exist.

The Bigger Picture

This quality pipeline isn't just about catching bugs—it's about creating a development culture where quality is automatic, not accidental. When quality checks are fast, reliable, and integrated into the workflow, they become invisible infrastructure that just works.

The result? More time spent building features, less time debugging preventable issues.


Quality gates transform development from "hope it works" to "know it works." The initial investment in setup pays dividends in reduced debugging time and increased deployment confidence.