Pengenalan: Menjawab kebutuhan linting yang terukur

Pipeline linting multi-stage di CI untuk monorepo TypeScript memastikan bahwa kode seluruh paket tervalidasi tanpa membuat CI berjalan terlalu lama. Dalam beberapa tahap, kita bisa melakukan pre-commit ringan di lokal, mengidentifikasi path lint yang berubah, serta menjalankan linting komprehensif di cloud dengan caching dan pencatatan hasil.

Pada artikel ini kita langsung menguraikan pendekatan konkret: bagaimana pipeline lint di GitHub Actions/GitLab CI bisa memisah tugas, menghemat waktu, dan tetap menjadi gating PR yang efektif.

Perbedaan pre-commit dan lint path spesifik

Pre-commit hook idealnya menjalankan quick lint terhadap file yang akan di-commit agar developer mendapatkan feedback instan. Misalnya, menjalankan ESLint hanya pada staged files dan memblokir commit jika ada error fatal. Namun, lint di CI harus menyertakan seluruh paket yang terpengaruh agar tidak ada konflik antar dependensi.

Untuk menghindari menjalankan linting di seluruh monorepo, gunakan pendekatan lint path spesifik: deteksi paket yang berubah dengan perintah seperti git diff --name-only origin/main...HEAD | cut -d/ -f1 | uniq dan jalankan lint hanya pada paket itu. Ini juga memungkinkan job lint CI cepat dan relevan.

Membangun multi-stage pipeline linting di CI

Langkah utama pada pipeline linting multi-stage adalah:

  1. Stage persiapan: checkout, caching node_modules, dan deteksi paket berubah.
  2. Stage lint cepat: lint path yang berubah untuk gating awal.
  3. Stage lint menyeluruh: lint semua paket yang penting (misalnya library-core) secara paralel.
  4. Stage agregasi hasil untuk laporan dan gating PR.

Contoh GitHub Actions

Snippet berikut menunjukkan multi-stage linting dengan caching dan paralelisasi.

name: Lint Monorepo TypeScript
on:
  pull_request:
    paths:
      - 'packages/**'
      - 'apps/**'
jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      changed-packages: ${{ steps.detect.outputs.packages }}
    steps:
      - uses: actions/checkout@v4
      - name: Restore node cache
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-cache-${{ hashFiles('**/package-lock.json') }}
      - name: Detect changed packages
        id: detect
        run: |
          echo "packages=$(git diff --name-only origin/main...HEAD | grep -o '^[^/]*' | sort -u | tr '\n' ' ')" >> $GITHUB_OUTPUT
  lint-changed:
    needs: prepare
    runs-on: ubuntu-latest
    if: steps.detect.outputs.packages != ''
    strategy:
      matrix:
        package: ${{ fromJson('[' + needs.prepare.outputs.changed-packages + ']') }}
    steps:
      - uses: actions/checkout@v4
      - name: Restore dependencies
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-cache-${{ matrix.package }}-${{ hashFiles(matrix.package + '/package-lock.json') }}
      - name: Lint ${matrix.package}
        run: npx eslint ${matrix.package}/src --max-warnings=0
  lint-core:
    needs: prepare
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Restore core cache
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-cache-core-${{ hashFiles('packages/library-core/package-lock.json') }}
      - name: Full lint core
        run: npx eslint packages/library-core/src --format json -o reports/core-lint.json
      - name: Upload report
        uses: actions/upload-artifact@v4
        with:
          name: lint-report-core
          path: reports/core-lint.json

Strategi ini membagi linting menjadi job kecil (per paket) sehingga berjalan paralel. Cache npm mengurangi waktu install, sementara job lint-changed diatur hanya jika ada paket yang berubah.

Stage lint di GitLab CI

Pada GitLab CI, gunakan stages dan rules untuk meniru behavior serupa.

stages:
  - prepare
  - lint
prepare:
  stage: prepare
  script:
    - npm ci
    - echo "changed=$(git diff --name-only $CI_MERGE_REQUEST_TARGET_BRANCH_NAME...$CI_COMMIT_SHA | cut -d/ -f1 | sort -u)" > changed.txt
  artifacts:
    files:
      - changed.txt
lint_changed:
  stage: lint
  needs: [prepare]
  rules:
    - exists: changed.txt
  script:
    - packages=$(cat changed.txt)
    - for pkg in $packages; do npx eslint "$pkg/src" --max-warnings=0; done
  parallel: 4
  cache:
    paths:
      - .npm

Pakai parallel untuk menjalankan lint lintas paket sekaligus; GitLab membagi script di atas menjadi 4 worker.

Teknik tambahan: caching, paralelisasi, skip folder tak berubah, agregasi laporan

Caching: Simpan direktori ~/.npm atau node_modules antara job lint. Pastikan key cache menyertakan digest package-lock.json agar cache valid saat dependensi berubah.

Paralelisasi: Jalankan lint per paket sebagai matrix job di GitHub atau gunakan parallel di GitLab. Batas grepping lint path dan sabar dengan concurrency level sesuai resources runner.

Skip folder tak berubah: Gunakan deteksi git diff atau tool graph dependency (misalnya nx affected:lint) untuk menjalankan lint hanya pada paket yang memodifikasi file. Ini mengurangi waktu CI saat PR cuma mengubah satu layanan.

Agregasi laporan: Output lint ke format JSON/Checkstyle, kemudian unggah sebagai artifact. Di GitHub Actions, job lint bisa menulis reports/lint.json dan job final menggabungkan artifact menjadi laporan yang bisa diunduh reviewer.

Strategi gating PR tanpa memperlambat developer

  • Lint path spesifik di pre-merge: Jalankan lint hanya pada paket yang berubah agar blocking singkat yet meaningful.
  • Lint menyeluruh di branch utama: Saat merge ke main, jalankan lint penuh untuk seluruh monorepo sebagai sanity check.
  • Fail fast dengan threshold: Gunakan eslint --max-warnings=0 agar sekali ada error langsung jatuh. Tambahkan juga lint per-package di pre-commit untuk catch error lebih awal.
  • Gunakan gate report: Ekspor lint report, kemudian menggunakan pull_request review untuk melihat detail error; PR hanya bisa merge setelah job lint sukses.

Tips troubleshooting umum

  • Lint job timeout: Pastikan paralelisasi tidak melebihi kuota runner. Kurangi jumlah matrix jika lint per-package memakan memori besar.
  • Error cache stale: Perbarui key cache dan tambahkan restore-keys partial. Jika lint menolak karena modul tidak ditemukan, bersihkan cache dan jalankan npm ci manual di runner.
  • Lint jalan tapi laporan kosong: Pastikan flag --format json dan path output valid, kemudian confirm job lint upload artifact dengan action yang sesuai.
  • Skip folder tapi lint tetap jalan: Periksa log job lint-changed untuk output paket; pastikan script deteksi tidak mengembalikan empty string dengan whitespace.

Dengan pipeline linting multi-stage yang tertata, monorepo TypeScript dapat mempertahankan kualitas tanpa membebani CI atau developer. Fokus pada lint path spesifik, cache efisien, paralelisasi, dan strategi gating PR untuk proses yang cepat sekaligus aman.