Dua chapter sebelumnya memperkenalkan dua alat dengan wilayah kerja yang bersih: ESLint menjaga kualitas logika kode, Prettier menyeragamkan tampilannya. Pada proyek sungguhan keduanya tidak pernah berdiri sendiri — test runner dari Section Testing juga menunggu di jalur yang sama untuk memeriksa perilaku program. Persoalannya bukan memilih alat mana yang dipakai, melainkan bagaimana ketiganya ditegakkan secara seragam pada setiap anggota tim, setiap hari, tanpa bergantung pada ingatan masing-masing. Jawabannya adalah satu alur kerja yang hidup di dalam repo: konfigurasi yang ikut tersimpan, satu pintu masuk lewat npm scripts, dan rantai pemeriksaan yang berhenti pada gerbang pertama yang gagal.

Tiga Gerbang dalam Satu Rangkaian #

Bayangkan setiap perubahan kode harus melewati tiga pertanyaan berurutan sebelum dianggap siap. Gerbang pertama, lint, bertanya: apakah ada pola kode yang berisiko menumbuhkan bug? Gerbang kedua, format, bertanya: apakah tampilan kode seragam dengan seluruh proyek? Gerbang ketiga, uji, bertanya: apakah perilaku program masih sesuai harapan? Ketiganya menjawab pertanyaan yang berbeda dengan alat yang berbeda, namun bekerja di bawah kontrak yang sama: lolos berarti exit code 0, gagal berarti exit code 1.

GerbangAlatPertanyaan yang dijawabPintu masuk
LintESLintAdakah pola berisiko seperti var dan ==?npm run lint
FormatPrettier --checkSeragamkah kutip, indentasi, lebar baris?npm run format:check
Ujinode --testMasihkah perilaku program sesuai?npm test

Urutan ketiganya tidak kebetulan. Lint adalah pemeriksaan termurah karena hanya membaca berkas tanpa mengeksekusi apa pun, sehingga ditempatkan paling depan; kegagalan di gerbang awal menghentikan rangkaian sebelum pekerjaan yang lebih mahal dijalankan. Format menyusul karena sifatnya sama — memeriksa teks, bukan menjalankan program. Test berada paling akhir karena satu-satunya gerbang yang benar-benar mengeksekusi kode.

Konfigurasi yang Ikut Pindah Bersama Repo #

Konsistensi dimulai dari satu aturan sederhana: konfigurasi alat hidup di dalam repo, bukan di kepala orang. ESLint mencari eslint.config.mjs mulai dari folder berkas yang diperiksa lalu naik ke atas; Prettier melakukan hal yang sama pada .prettierrc.json. Karena keduanya tersimpan di repo, aturan yang berlaku bagi kita hari ini otomatis berlaku pula bagi rekan tim yang meng-clone repo besok, dan bagi mesin pipeline yang menjalankan pemeriksaan minggu depan. Inilah kedua berkas konfigurasi yang dipakai sepanjang chapter ini:

js
export default [
  {
    files: ['**/*.mjs'],
    rules: {
      'no-var': 'error',
      'no-unused-vars': 'error',
      eqeqeq: 'error',
    },
  },
];
json
{
  "printWidth": 80,
  "singleQuote": true,
  "semi": true
}

Tidak ada satu aturan pun yang baru — ketiga aturan ESLint dan ketiga opsi Prettier persis seperti dua chapter sebelumnya. Meniru keputusan yang sudah ada, bukan menciptakan yang baru setiap kali, justru merupakan inti konsistensi: pembaca yang sudah memahami konfigurasi tersebut tidak perlu mempelajari apa pun yang baru di sini.

Satu Pintu Masuk Bernama npm Scripts #

Perintah lengkap dengan npx dan nomor versi panjang rawan salah bila diketik ulang dari ingatan — dan mudah berbeda antar anggota tim. npm scripts memindahkan seluruh perintah itu ke dalam package.json sehingga setiap orang cukup mengingat pintu masuk yang sama. Berikut seluruh isi package.json proyek mini chapter ini:

json
{
  "name": "contoh-alur-mutu",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --test",
    "lint": "npx --yes [email protected] .",
    "format": "npx --yes [email protected] --write *.mjs",
    "format:check": "npx --yes [email protected] --check *.mjs",
    "ci": "npm run lint && npm run format:check && npm test"
  }
}

Mari kita bedah isi bagian scripts:

  1. "test": "node --test": menjalankan test runner bawaan Node; tanpa argumen, runner menemukan berkas *.test.mjs di folder proyek secara otomatis.
  2. "lint": ESLint memeriksa direktori saat ini (.) memakai aturan dari eslint.config.mjs.
  3. "format": Prettier menulis ulang seluruh berkas *.mjs — dipakai saat memperbaiki, bukan saat memeriksa.
  4. "format:check": pemeriksaan tanpa mengubah berkas; inilah bentuk yang tepat untuk sebuah rantai pemeriksaan.
  5. "ci": merangkai tiga gerbang dengan && — rantai ini kita jalankan beberapa saat lagi.

Sebelum merangkai, mari kita lihat bahan yang dijaga ketiga gerbang: satu fungsi kecil dan dua test untuknya.

js
export function hitungPajak(harga, tarifPersen) {
  if (tarifPersen < 0) {
    throw new Error('tarif tidak boleh negatif');
  }
  return Math.round(harga * (tarifPersen / 100));
}
js
import { test } from 'node:test';
import assert from 'node:assert';
import { hitungPajak } from './hitung-pajak.mjs';

test('menghitung pajak dari harga', () => {
  assert.strictEqual(hitungPajak(150000, 11), 16500);
});

test('tarif negatif ditolak', () => {
  assert.throws(() => hitungPajak(150000, -5), {
    message: 'tarif tidak boleh negatif',
  });
});

Fungsi hitungPajak menolak tarif negatif lalu membulatkan hasil pajak; kedua test mengimpor fungsi itu dan memeriksa perilakunya. Kode ini sengaja sudah rapi dan lolos lint — nanti kita akan saksikan sendiri apa yang terjadi bila satu gerbang dilanggar.

Gerbang Lint dalam Praktik #

Gerbang pertama dijalankan dengan satu perintah pendek:

bash
npm run lint
echo "kode keluar: $?"

Output yang Dihasilkan:

text

> [email protected] lint
> npx --yes [email protected] .

kode keluar: 0

Cara membaca keluarannya:

  1. Baris > [email protected] lint: banner npm yang menyatakan nama package dan nama script yang sedang berjalan.
  2. Baris > npx --yes [email protected] .: perintah sesungguhnya yang dijalankan oleh script — persis perintah panjang yang kini tidak perlu kita ketik lagi.
  3. Tidak ada laporan: ESLint memeriksa seluruh berkas .mjs dan tidak menemukan satu masalah pun.
  4. kode keluar: 0: gerbang lolos; sebuah echo kecil kita pakai untuk memperlihatkan exit code yang oleh rantai nanti dibaca otomatis.

Sekarang uji ketajamannya. Kita ciptakan satu pelanggaran kecil — berkas baru yang memakai var — lalu jalankan gerbang yang sama:

bash
printf 'var sisaStok = 5;\nconsole.log(sisaStok);\n' > salinan.mjs
npm run lint | sed 's|.*/||'
echo "kode keluar: ${PIPESTATUS[0]}"
rm salinan.mjs

Output yang Dihasilkan:

text

> [email protected] lint
> npx --yes [email protected] .

salinan.mjs
  1:1  error  Unexpected var, use let or const instead  no-var

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

kode keluar: 1

Laporan menunjuk salinan.mjs baris 1 kolom 1 dengan aturan no-var — konfigurasi yang sama dengan Chapter Linting dengan ESLint menangkap pelanggaran baru tanpa satu pengaturan tambahan pun. Pipe sed 's|.*/||' kembali dipakai untuk memangkas path absolut pada baris nama berkas, dan ${PIPESTATUS[0]} membaca exit code npm di ujung pertama pipe, persis konvensi chapter tersebut. Gerbang menutup dengan kode keluar: 1; perintah rm menghapus salinan agar demonstrasi bisa diulang dengan hasil identik.

Gerbang Format dan Gerbang Uji #

Gerbang kedua memeriksa tampilan seluruh berkas .mjs tanpa mengubah apa pun:

bash
npm run format:check
echo "kode keluar: $?"

Output yang Dihasilkan:

text

> [email protected] format:check
> npx --yes [email protected] --check *.mjs

Checking formatting...
All matched files use Prettier code style!
kode keluar: 0

Prettier membaca .prettierrc.json, membandingkan setiap berkas dengan hasil format yang seharusnya, dan melaporkan semuanya rapi — exit code 0. Gerbang ketiga menjalankan program sungguhan:

bash
npm test

Output yang Dihasilkan:

text

> [email protected] test
> node --test

✔ menghitung pajak dari harga
✔ tarif negatif ditolak
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

Dua tanda centang menandakan kedua test pada hitung-pajak.test.mjs lulus, disusul ringkasan tests 2, pass 2, fail 0. Sesuai konvensi Section Testing, keluaran disaring dari baris durasi milidetik agar stabil untuk dibandingkan antar eksekusi — flag --check dan --write beserta seluruh opsi Prettier lainnya terdokumentasi di dokumentasi CLI Prettier .

Rantai Lengkap: npm run ci #

Sekarang seluruh rangkaiannya. Script ci menjalankan ketiga gerbang berurutan:

bash
npm run ci

Output yang Dihasilkan:

text

> [email protected] ci
> npm run lint && npm run format:check && npm test

> [email protected] lint
> npx --yes [email protected] .

> [email protected] format:check
> npx --yes [email protected] --check *.mjs

Checking formatting...
All matched files use Prettier code style!

> [email protected] test
> node --test

✔ menghitung pajak dari harga
✔ tarif negatif ditolak
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

Perhatikan liputan keluarannya: banner ci diikuti lint yang sunyi, format:check yang melaporkan seluruh berkas rapi, lalu test dengan dua test lulus — tiga gerbang, satu perintah, exit code 0 di ujungnya. Operator && di antara perintah memegang peran kunci: perintah berikutnya hanya berjalan bila perintah sebelumnya berhasil. Untuk melihat akibatnya, ulangi rantai dengan pelanggaran yang sama:

bash
printf 'var sisaStok = 5;\nconsole.log(sisaStok);\n' > salinan.mjs
npm run ci | sed 's|.*/||'
echo "kode keluar: ${PIPESTATUS[0]}"
rm salinan.mjs

Output yang Dihasilkan:

text

> [email protected] ci
> npm run lint && npm run format:check && npm test

> [email protected] lint
> npx --yes [email protected] .

salinan.mjs
  1:1  error  Unexpected var, use let or const instead  no-var

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

kode keluar: 1

Bandingkan dengan keluaran sebelumnya: lint menemukan salinan.mjs dan gagal, sehingga banner format:check dan test tidak pernah muncul — kedua gerbang tersebut tidak dijalankan sama sekali. Rantai berhenti di kegagalan pertama dan keluar dengan kode 1. Perilaku ini yang membuat exit code menjadi bahasa penghubung antar alat: pipeline apa pun yang menjalankan npm run ci cukup memeriksa satu angka untuk tahu bahwa seluruh proyek sehat.

Konvensi Tim di Balik Alat #

Alat menegakkan apa yang bisa diotomasi; sisanya adalah keputusan yang perlu diambil tim lalu ditulis agar tidak diperdebatkan ulang setiap kali. Berkas .editorconfig adalah contoh termurah — satu berkas kecil yang memberi tahu editor apa pun tentang indentasi dan akhir baris, terlepas dari preferensi bawaan masing-masing editor:

ini .editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2

Untuk umpan balik yang lebih dekat dengan momen commit, ekosistem menyediakan husky — pembungkus praktis untuk Git hooks — dan lint-staged yang menjalankan format dan lint hanya pada berkas yang akan di-commit. Keduanya tidak terpasang pada contoh Course ini karena perannya sudah tertutup oleh rantai ci; pada proyek besar, kombinasinya memberi peringatan bahkan sebelum commit terkirim. Satu catatan penting: Git hooks bersifat lokal dan bisa dilewati, sehingga rantai di pipeline tetap dibutuhkan sebagai garis akhir. Spesifikasi format .editorconfig sendiri dipelihara terbuka di proyek EditorConfig .

Lapisan-lapisan tersebut merangkum menjadi satu peta kecil:

LapisanAlatDitegakkan kapan
Struktur editor.editorconfigSaat mengetik
Tampilan kodePrettierSaat menyimpan atau npm run format
Kualitas logikaESLintnpm run lint
Perilaku programnode --testnpm test
Seluruhnya sekaligusrantai ciSebelum penggabungan

Dengan ini Section Debugging dan Code Quality tertutup: kita bisa membedah program dengan debugger, membaca stack trace, mencatat kejadian lewat logging, dan kini menjaga kualitas secara otomatis melalui tiga gerbang yang dirangkai dalam satu perintah. Peralatan kita lengkap — pertanyaan berikutnya adalah bagaimana menyusun kode itu sendiri. Section Architecture dan Design membuka pembahasan itu dari pola arsitektur berlapis: memisahkan rute, logika, dan data agar proyek tetap terkelola saat terus tumbuh.