GitHub Actions ile Lint ve Format Kontrolü: Kod Kalitesini Otomatikleştirin
Kod kalitesi meselesi, bir projenin büyüklüğü ne olursa olsun her zaman öncelikli gündem maddesi olmalı. “Çalışıyor ya, ne olmuş?” diyebilirsiniz, ama ekip büyüdükçe, yeni geliştiriciler projeye dahil oldukça ve kod tabanı genişledikçe tutarsız formatlama ve lint hataları gerçek bir kaosa dönüşüyor. İşte tam bu noktada GitHub Actions devreye giriyor: her push veya pull request anında otomatik olarak lint ve format kontrolü yaparak sorunları insan gözü değmeden yakalamak mümkün oluyor.
Neden Lint ve Format Kontrolü Önemli?
Düşünün, beş kişilik bir ekipte çalışıyorsunuz. Biri tab kullanıyor, diğeri iki boşluk, bir başkası dört boşluk. Python dosyasında kullanılmayan import’lar var, JavaScript kodunda noktalı virgül tutarsızlıkları var. Code review sırasında asıl mantık hataları yerine bu tür şeylerle zaman kaybediyorsunuz.
Lint araçları kodu statik olarak analiz eder ve potansiyel hataları, kötü pratikleri veya stil sorunlarını işaretler. Format araçları ise kodu otomatik olarak belirli bir stile uygun hale getirir. Bu ikisini CI/CD pipeline’ına entegre ettiğinizde:
- Kod tutarlılığı otomatik olarak sağlanır
- Code review süreci sadece iş mantığına odaklanabilir
- Teknik borç birikmeden önlenir
- Yeni ekip üyeleri projeye daha kolay adapte olur
- Üretim ortamına potansiyel runtime hataları geçmez
Temel GitHub Actions Kavramları (Hızlı Hatırlatma)
Lint workflow’larına geçmeden önce temel yapıyı hatırlayalım. GitHub Actions, .github/workflows/ dizinindeki YAML dosyalarını okur. Her workflow bir veya daha fazla job içerir, her job ise step’lerden oluşur.
# Proje dizin yapısı
myproject/
├── .github/
│ └── workflows/
│ ├── lint.yml
│ └── format-check.yml
├── src/
├── tests/
└── package.json
Bir workflow’un temel anatomisi şöyle:
name: Code Quality Check
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
Bu basit yapı bile çok şey yapıyor: kodu checkout ediyor, Node.js kuruyor, bağımlılıkları yüklüyor ve ESLint çalıştırıyor.
Python Projeleri için Kapsamlı Lint Workflow’u
Python projelerinde genellikle birden fazla araç bir arada kullanılır. flake8 veya pylint lint için, black format için, isort import sıralaması için, mypy ise tip kontrolü için kullanılır.
name: Python Code Quality
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
python-quality:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 black isort mypy pylint
pip install -r requirements.txt
if [ -f requirements-dev.txt ]; then
pip install -r requirements-dev.txt
fi
- name: Run Black (format check)
run: black --check --diff src/ tests/
- name: Run isort (import sort check)
run: isort --check-only --diff src/ tests/
- name: Run flake8 (lint)
run: |
flake8 src/ tests/
--max-line-length=88
--extend-ignore=E203,W503
--count
--statistics
- name: Run mypy (type check)
run: mypy src/ --ignore-missing-imports
Burada dikkat çeken birkaç nokta var. matrix stratejisi ile birden fazla Python versiyonunda test ediyoruz. cache step’i ile pip paketlerini önbellekliyoruz, bu özellikle büyük projelerde ciddi zaman tasarrufu sağlıyor. black --check modu dosyaları değiştirmiyor, sadece format hatası olup olmadığını raporluyor.
JavaScript/TypeScript Projeleri için ESLint ve Prettier
Frontend veya Node.js projelerinde ESLint ve Prettier kombinasyonu neredeyse standart haline geldi.
name: JavaScript Code Quality
on:
push:
branches: [ main, develop, 'feature/**' ]
pull_request:
types: [ opened, synchronize, reopened ]
jobs:
eslint:
name: ESLint Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint with annotations
uses: reviewdog/action-eslint@v1
with:
reporter: github-pr-review
eslint_flags: 'src/**/*.{js,jsx,ts,tsx}'
- name: Run Prettier check
run: npx prettier --check "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}"
- name: TypeScript type check
run: npx tsc --noEmit
reviewdog/action-eslint kullanmak önemli bir detay burada. Bu action, lint hatalarını doğrudan pull request’in ilgili satırlarına yorum olarak ekliyor. Yani geliştiriciler hangi satırda ne sorun olduğunu PR üzerinde görebiliyor, ayrıca log dosyasını karıştırmaları gerekmiyor.
Çoklu Dil Desteği: Monorepo Senaryosu
Gerçek dünya projelerinde genellikle monorepo yapısı görüyoruz. Frontend React, backend Python veya Go, ve belki bir microservice daha. Bu durumda her servisi ayrı ayrı kontrol etmek gerekiyor.
name: Monorepo Code Quality
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.changes.outputs.frontend }}
backend: ${{ steps.changes.outputs.backend }}
services: ${{ steps.changes.outputs.services }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'
services:
- 'services/**'
frontend-lint:
needs: detect-changes
if: ${{ needs.detect-changes.outputs.frontend == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm run format:check
backend-lint:
needs: detect-changes
if: ${{ needs.detect-changes.outputs.backend == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install ruff black
- run: ruff check .
- run: black --check .
dorny/paths-filter action’ı burada kilit rol oynuyor. Sadece değişen dizinlere ait lint job’ları çalışıyor. 100 servisli bir monorepo’da tek bir dosyayı değiştirdiğinizde tüm servislerin lint’ini çalıştırmak hem zaman hem de kaynak israfı olur.
Pre-commit Hook ile GitHub Actions Entegrasyonu
Profesyonel bir yaklaşım olarak pre-commit framework’ünü hem yerel geliştirme ortamında hem de CI’da kullanabilirsiniz. Bu şekilde yerel ve uzak kontroller tamamen senkronize kalır.
Önce projeye .pre-commit-config.yaml ekleyin:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-merge-conflict
- id: detect-private-key
- repo: https://github.com/psf/black
rev: 23.12.0
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.8
hooks:
- id: ruff
args: [--fix]
Sonra bu config’i GitHub Actions’ta kullanın:
name: Pre-commit Checks
on: [push, pull_request]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Run pre-commit
uses: pre-commit/[email protected]
with:
extra_args: --all-files
Bu yaklaşımın güzelliği şu: geliştiriciler pre-commit install komutunu çalıştırdığında aynı kontroller her commit öncesi yerel olarak da çalışıyor. CI’a geçen hatalar ciddi ölçüde azalıyor.
Hata Raporlama ve Bildirimler
Lint hataları olduğunda sadece build’in kırmızıya dönmesi yeterli değil, geliştiricilere net bildirim göndermek gerekiyor. Özellikle büyük ekiplerde Slack veya email bildirimleri önemli.
name: Code Quality with Notifications
on:
push:
branches: [ main ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup and lint
id: lint_step
run: |
npm ci
npm run lint 2>&1 | tee lint-output.txt
echo "exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT
- name: Upload lint results
if: always()
uses: actions/upload-artifact@v4
with:
name: lint-results
path: lint-output.txt
retention-days: 7
- name: Notify Slack on failure
if: failure()
uses: slackapi/[email protected]
with:
channel-id: 'dev-alerts'
slack-message: |
*Lint Kontrolü Başarısız!*
Repo: ${{ github.repository }}
Branch: ${{ github.ref_name }}
Commit: ${{ github.sha }}
Gönderi: ${{ github.actor }}
Detaylar: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
- name: Add PR comment on lint failure
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## Lint Kontrolü BaşarısıznLütfen lint hatalarını düzeltin. [Workflow loglarına](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) bakın.'
})
PR üzerine otomatik yorum bırakmak harika bir pratik. Geliştirici PR’a bakıyor, hata nerede anında görüyor, log dosyasını aramak zorunda kalmıyor.
Performans Optimizasyonu: Cache Stratejileri
Büyük projelerde bağımlılık kurulumu ciddi zaman alabilir. Doğru cache stratejisi ile bu süreyi dramatik şekilde kısaltabilirsiniz.
name: Optimized Lint Pipeline
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js with aggressive caching
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Cache ESLint results
uses: actions/cache@v4
with:
path: .eslintcache
key: eslint-${{ runner.os }}-${{ hashFiles('**/.eslintrc*', '**/package-lock.json') }}-${{ github.sha }}
restore-keys: |
eslint-${{ runner.os }}-${{ hashFiles('**/.eslintrc*', '**/package-lock.json') }}-
eslint-${{ runner.os }}-
- name: Install dependencies
run: npm ci
- name: Run ESLint with cache
run: npx eslint src/ --cache --cache-location .eslintcache --ext .js,.jsx,.ts,.tsx
- name: Cache mypy results
uses: actions/cache@v4
with:
path: .mypy_cache
key: mypy-${{ runner.os }}-${{ hashFiles('**/*.py') }}
restore-keys: |
mypy-${{ runner.os }}-
- name: Run mypy with cache
run: mypy src/ --cache-dir .mypy_cache
ESLint’in --cache seçeneği sadece değişen dosyaları tekrar lint ediyor. Binlerce dosyalı bir projede bu fark çok büyük olabiliyor, 5 dakikadan 30 saniyeye düşebiliyor.
Gerçek Dünya Senaryosu: E-ticaret Projesi Pipeline’ı
Bir e-ticaret platformu düşünün: React frontend, Django backend, ve birkaç Python microservice. Bu proje için production-ready bir workflow nasıl görünmeli?
name: E-Commerce Platform Quality Gate
on:
pull_request:
branches: [ main, staging ]
push:
branches: [ main ]
env:
PYTHON_VERSION: '3.11'
NODE_VERSION: '20'
jobs:
# Tüm job'ların geçmesi gereken kalite kapısı
quality-gate:
runs-on: ubuntu-latest
needs: [frontend-quality, backend-quality, security-lint]
if: always()
steps:
- name: Check all quality jobs
run: |
if [[ "${{ needs.frontend-quality.result }}" != "success" ||
"${{ needs.backend-quality.result }}" != "success" ||
"${{ needs.security-lint.result }}" != "success" ]]; then
echo "Kalite kontrolleri başarısız. Merge engellendirebilir."
exit 1
fi
echo "Tüm kalite kontrolleri başarılı!"
frontend-quality:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run lint:strict
- run: npm run format:check
- run: npm run typecheck
backend-quality:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install quality tools
run: |
pip install ruff black isort mypy django-stubs
pip install -r requirements.txt
- run: ruff check . --select ALL --ignore ANN,D
- run: black --check .
- run: isort --check-only .
- run: mypy apps/ --strict
security-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Run Bandit security linter
run: |
pip install bandit
bandit -r backend/ -ll -ii
--exclude backend/tests/
-f json -o bandit-report.json || true
- name: Upload security report
uses: actions/upload-artifact@v4
with:
name: security-lint-report
path: bandit-report.json
Bu pipeline’da dikkat edin: quality-gate job’u diğer tüm job’ların sonucunu kontrol ediyor. Branch protection rule’larında bu job’u zorunlu hale getirirseniz, kalite kontrollerinden geçmeyen hiçbir PR merge edilemiyor.
Branch Protection ile Entegrasyon
Workflow’ları yazdınız, çalışıyor. Ama geliştiriciler bypass edip direkt push yapabiliyorsa ne anlamı var? GitHub branch protection rules ile bu workflow’ları zorunlu hale getirmek şart.
GitHub repository settings üzerinden yapılan branch protection ayarları için şu noktalar önemli:
- Require status checks to pass before merging seçeneğini aktif edin
- Zorunlu hale getireceğiniz job isimlerini “Required status checks” listesine ekleyin
- Require branches to be up to date before merging seçeneği ile her PR’ın main ile güncel olmasını zorunlu kılın
- Include administrators seçeneği ile admin kullanıcıların da bu kuralları atlatamamasını sağlayın
Unutmayın, bu kurallar olmadan en güzel workflow’ların bile pratik değeri oldukça sınırlı kalır.
Yaygın Sorunlar ve Çözümleri
Gerçek projelerde karşılaşılan bazı tipik sorunlar ve çözüm yolları:
Problem 1: Lint hataları çok fazla, nereden başlayacağız?
Mevcut projeye lint entegre ediyorsanız yüzlerce hata çıkabilir. Bunu aşmak için kademeli yaklaşım işe yarıyor. Önce sadece yeni dosyaları kontrol eden bir workflow yazın, eski dosyalar için --diff-filter=A gibi seçenekler kullanın.
Problem 2: Farklı işletim sistemlerinde farklı davranış
Windows geliştirici makinelerde CRLF satır sonları, Linux CI’da LF bekler. .gitattributes dosyasında * text=auto ayarı ve .editorconfig bu sorunu büyük ölçüde çözer.
Problem 3: Cache sorunları
Bazen cache bozuluyor ve workflow garip hatalar veriyor. Cache key’e tarih ekleyerek veya Actions sekmesinden cache’i manuel silerek bu sorunu aşabilirsiniz.
Problem 4: Çok yavaş çalışan lint
Paralel job’lar kullanın, lint ve format kontrolünü ayrı job’lara bölün, cache stratejinizi optimize edin. Büyük projelerde lint işlemini dosya bazında bölerek paralel çalıştırmak da mümkün.
Sonuç
GitHub Actions ile lint ve format kontrolü entegrasyonu, ilk kurulumda belki birkaç saatinizi alıyor ama uzun vadede kazandırdığı zaman ve kalite tartışılmaz. Ekipteki her geliştirici aynı standartlara uyuyor, code review süreçleri iş mantığına odaklanıyor, üretim ortamına giden kod çok daha temiz oluyor.
Başlangıç olarak en basit şeyle başlayın: tek bir lint tool, tek bir workflow. Sonra kademeli olarak genişletin. Mükemmel pipeline ilk günden çalışmaz, ama çalışan bir pipeline birkaç saatte kurulabilir.
Özellikle şunu vurgulamak istiyorum: bu araçlar geliştiriciyi kısıtlamak için değil, onu korumak için var. Gece 2’de aceleyle yazılan bir kod, sabah lint’ten geçmek zorunda kalınca biraz daha dikkatli yazılıyor. Bu da hem geliştirici hem de kullanıcı için kazanç.
Son olarak, hangi araçları seçeceğiniz projenize ve ekinize göre değişir ama süreci otomatikleştirmek her zaman manuel kontrolden daha güvenilir. CI/CD pipeline’ı insan hatalarını ortadan kaldırmaz, ama ciddi ölçüde azaltır ve bu bile başlı başına büyük bir değer.
