Jika tim Anda menjalankan writing challenge internal atau menerima kontribusi konten teknis dari banyak penulis, masalah utamanya biasanya bukan ide artikel, melainkan konsistensi submission. Front matter sering tidak lengkap, slug bentrok, Markdown tidak rapi, link rusak lolos review, dan reviewer harus mengulang pemeriksaan yang sama di setiap pull request.

Template GitHub Actions untuk writing challenge tim yang konsisten membantu memindahkan pemeriksaan berulang tersebut ke pipeline otomatis. Inspirasi budaya challenge komunitas seperti pengumuman pemenang Google I/O 2026 Writing Challenge di dev.to menunjukkan bahwa format challenge bisa mendorong partisipasi; namun agar skalanya nyaman untuk tim engineering, pengalaman kontributor harus ditopang oleh otomasi yang jelas, dapat diprediksi, dan mudah di-debug.

Mengapa workflow writing challenge perlu diotomasi

Untuk repository konten teknis, CI tidak hanya berfungsi sebagai penjaga kualitas kode. Ia juga menjadi kontrak editorial yang dapat diverifikasi. Setiap PR sebaiknya menjawab pertanyaan berikut sebelum reviewer membaca substansi artikel:

  • Apakah metadata artikel valid dan lengkap?
  • Apakah slug unik sehingga URL tidak bentrok?
  • Apakah format Markdown cukup konsisten?
  • Apakah link eksternal dan internal masih aktif?
  • Apakah hasil build preview dapat dirender?
  • Apakah PR sudah diberi label yang memudahkan triase?

Dengan pendekatan ini, reviewer manusia fokus pada akurasi teknis, struktur argumen, dan kualitas penjelasan, bukan hal-hal mekanis yang mudah diotomasi.

Struktur repository yang disarankan

Struktur berikut cukup umum untuk tim yang menyimpan artikel Markdown dan membangun situs dokumentasi atau blog statis:

repo-content/
├─ .github/
│  ├─ workflows/
│  │  ├─ content-ci.yml
│  │  ├─ preview-build.yml
│  │  └─ auto-label.yml
│  └─ pull_request_template.md
├─ content/
│  ├─ 2026-06-01-template-github-actions-writing-challenge.md
│  └─ 2026-06-02-observability-basics.md
├─ scripts/
│  ├─ validate-frontmatter.js
│  ├─ check-duplicate-slug.js
│  └─ changed-content-files.js
├─ site/
│  └─ ...
├─ package.json
└─ .markdownlint.json

Prinsipnya sederhana:

  • content/ menyimpan artikel sumber.
  • scripts/ berisi logika validasi yang spesifik terhadap aturan editorial tim Anda.
  • .github/workflows/ memecah workflow berdasarkan tanggung jawab, agar mudah dirawat.

Memisahkan validasi ke script kecil lebih baik daripada menulis seluruh logika dalam shell satu baris di YAML. Alasannya: lebih mudah diuji, lebih jelas pesan error-nya, dan lebih mudah diubah saat aturan editorial berkembang.

Konvensi artikel dan front matter

Sebelum menulis workflow, tentukan kontrak yang akan divalidasi. Misalnya setiap file di content/ harus memiliki front matter YAML seperti ini:

---
title: "Template GitHub Actions untuk Writing Challenge Tim yang Konsisten"
description: "Panduan membuat workflow CI untuk validasi konten teknis tim."
slug: "template-github-actions-writing-challenge"
authors:
  - "nama-penulis"
tags:
  - "github-actions"
  - "markdown"
status: "draft"
---

Field yang umum divalidasi:

  • title tidak kosong
  • description ada dan panjangnya wajar
  • slug hanya berisi huruf kecil, angka, dan tanda hubung
  • authors berbentuk array
  • tags berbentuk array dan tidak kosong
  • status termasuk nilai yang diizinkan, misalnya draft, review, atau ready

Jangan terlalu agresif di awal. Aturan yang terlalu kaku akan membuat kontributor frustrasi. Mulailah dari validasi yang benar-benar mencegah masalah produksi, lalu tambahkan aturan editorial secara bertahap.

Workflow utama: validasi konten di pull request

Berikut contoh workflow inti untuk menjalankan validasi front matter, cek slug duplikat, lint Markdown, dan pemeriksaan link. Contoh ini menggunakan Node.js karena praktis untuk mem-parsing YAML front matter dan mengolah file yang berubah, tetapi idenya tetap sama jika tim Anda lebih nyaman dengan Python atau shell script.

name: Content CI

on:
  pull_request:
    paths:
      - 'content/**/*.md'
      - 'scripts/**'
      - '.github/workflows/content-ci.yml'
      - '.markdownlint.json'

jobs:
  validate-content:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Get changed content files
        id: changed
        run: |
          node scripts/changed-content-files.js > changed-files.txt
          echo "count=$(wc -l < changed-files.txt | tr -d ' ')" >> $GITHUB_OUTPUT

      - name: Validate front matter
        if: steps.changed.outputs.count != '0'
        run: xargs -a changed-files.txt node scripts/validate-frontmatter.js

      - name: Check duplicate slugs
        if: steps.changed.outputs.count != '0'
        run: node scripts/check-duplicate-slug.js

      - name: Lint Markdown
        if: steps.changed.outputs.count != '0'
        run: npx markdownlint-cli "content/**/*.md"

      - name: Check links
        if: steps.changed.outputs.count != '0'
        run: npx lychee --no-progress --include-fragments content/**/*.md

Kenapa workflow ini bekerja dengan baik:

  • Trigger dibatasi ke path yang relevan, sehingga CI tidak berjalan saat file non-konten berubah.
  • fetch-depth: 0 berguna bila script perlu membandingkan branch atau membaca riwayat penuh.
  • Perubahan file dihitung dulu, sehingga job bisa dilewati jika tidak ada artikel yang berubah.
  • Setiap tahap fokus pada satu jenis validasi, sehingga pesan gagal lebih mudah dipahami kontributor.

Contoh validasi front matter

Script berikut hanya contoh minimal. Tujuannya menunjukkan pola validasi, bukan memaksakan library tertentu.

#!/usr/bin/env node
const fs = require('fs');
const matter = require('gray-matter');

const files = process.argv.slice(2);
let failed = false;

for (const file of files) {
  const raw = fs.readFileSync(file, 'utf8');
  const { data } = matter(raw);
  const errors = [];

  if (!data.title || typeof data.title !== 'string') {
    errors.push('title wajib diisi');
  }

  if (!data.description || typeof data.description !== 'string') {
    errors.push('description wajib diisi');
  }

  if (!data.slug || !/^[a-z0-9-]+$/.test(data.slug)) {
    errors.push('slug wajib lowercase-kebab-case');
  }

  if (!Array.isArray(data.authors) || data.authors.length === 0) {
    errors.push('authors wajib berupa array dan tidak kosong');
  }

  if (!Array.isArray(data.tags) || data.tags.length === 0) {
    errors.push('tags wajib berupa array dan tidak kosong');
  }

  if (data.status && !['draft', 'review', 'ready'].includes(data.status)) {
    errors.push('status harus draft, review, atau ready');
  }

  if (errors.length > 0) {
    failed = true;
    console.error(`\n${file}`);
    for (const err of errors) {
      console.error(`- ${err}`);
    }
  }
}

if (failed) process.exit(1);

Hal penting di sini adalah pesan error yang spesifik. CI yang gagal tanpa menjelaskan field mana yang salah akan memperlambat kontribusi lebih dari tidak adanya CI sama sekali.

Cek slug duplikat

Slug duplikat adalah masalah klasik saat banyak orang menulis topik yang mirip atau membuat draft paralel. Validasi ini sebaiknya membaca seluruh direktori content/, bukan hanya file yang berubah, karena bentrok bisa terjadi dengan artikel lama.

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const matter = require('gray-matter');

const dir = path.join(process.cwd(), 'content');
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
const seen = new Map();
let failed = false;

for (const file of files) {
  const fullPath = path.join(dir, file);
  const raw = fs.readFileSync(fullPath, 'utf8');
  const { data } = matter(raw);
  const slug = data.slug;

  if (!slug) continue;

  if (seen.has(slug)) {
    failed = true;
    console.error(`Slug duplikat: "${slug}" ditemukan pada ${seen.get(slug)} dan ${file}`);
  } else {
    seen.set(slug, file);
  }
}

if (failed) process.exit(1);

Jika repository Anda menggunakan folder bersarang atau nama file tidak unik, ubah pembacaan file menjadi rekursif. Jangan mengandalkan nama file sebagai pengganti slug kecuali memang itu kontrak arsitektur konten Anda.

Lint Markdown dan pemeriksaan link rusak

Markdown lint berguna untuk konsistensi struktur heading, spasi, list, dan blok kode. Ia tidak menjamin kualitas tulisan, tetapi sangat efektif mengurangi perbedaan format antar penulis. Simpan aturan di .markdownlint.json agar mudah dinegosiasikan oleh tim.

{
  "default": true,
  "MD013": false,
  "MD022": true,
  "MD032": true,
  "MD041": false
}

Contoh di atas sengaja tidak terlalu ketat. Misalnya aturan panjang baris sering memicu friksi pada paragraf biasa dan URL panjang.

Untuk link checker, perlu dipahami trade-off berikut:

  • Link eksternal dapat gagal karena rate limiting, firewall, atau situs tujuan sedang lambat.
  • Link dengan anchor fragmen sering valid secara dokumen tetapi sulit diverifikasi jika halaman dirender dinamis.
  • Pemeriksaan semua link di setiap PR bisa membuat CI lambat.

Praktik yang cukup aman adalah memeriksa link pada file yang berubah untuk PR, lalu menjalankan pemeriksaan penuh terjadwal, misalnya sekali sehari atau mingguan.

name: Scheduled Link Check

on:
  schedule:
    - cron: '0 2 * * 1'
  workflow_dispatch:

jobs:
  links:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check all content links
        run: npx lychee --no-progress --include-fragments content/**/*.md

Jika Anda sering menerima false positive, tambahkan daftar pengecualian yang ditinjau secara berkala. Jangan langsung menonaktifkan pemeriksaan link seluruhnya.

Preview build agar reviewer melihat hasil akhir

Review artikel akan lebih cepat jika reviewer bisa membuka hasil render, bukan hanya membaca Markdown mentah. Workflow preview build biasanya melakukan tiga hal:

  1. Membangun situs atau halaman preview.
  2. Mengunggah artefak build atau menerbitkan preview environment.
  3. Menulis tautan preview ke pull request.

Contoh generik berikut hanya sampai tahap build dan upload artefak, karena mekanisme deployment preview berbeda-beda tergantung stack situs Anda.

name: Preview Build

on:
  pull_request:
    paths:
      - 'content/**/*.md'
      - 'site/**'
      - 'package.json'

jobs:
  build-preview:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - name: Build site
        run: npm run build
      - name: Upload preview artifact
        uses: actions/upload-artifact@v4
        with:
          name: site-preview
          path: dist/

Jika tim Anda menggunakan platform yang mendukung preview deployment otomatis, Anda bisa menambahkan komentar PR berisi URL preview. Nilai utamanya adalah reviewer dapat mengecek:

  • render heading dan daftar isi,
  • blok kode dan syntax highlighting,
  • gambar atau aset yang hilang,
  • broken internal routes,
  • perbedaan antara Markdown source dan tampilan final.

Preview build tidak menggantikan lint dan validasi metadata. Ia menutup kelas masalah yang berbeda: masalah render dan integrasi dengan generator situs.

Auto-label PR untuk triase yang lebih cepat

Pada writing challenge tim, label membantu editor atau maintainer memprioritaskan review. Misalnya:

  • content untuk semua PR artikel
  • needs-review saat validasi lulus
  • draft bila front matter status masih draft
  • docs-site jika PR juga mengubah generator situs

Cara paling sederhana adalah memakai workflow berbasis path:

name: Auto Label Content PR

on:
  pull_request_target:
    types: [opened, synchronize, reopened]

jobs:
  label:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: Label content PR
        uses: actions/github-script@v7
        with:
          script: |
            const pr = context.payload.pull_request;
            const files = await github.paginate(
              github.rest.pulls.listFiles,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: pr.number,
                per_page: 100
              }
            );

            const names = files.map(f => f.filename);
            const labels = new Set();

            if (names.some(name => name.startsWith('content/'))) labels.add('content');
            if (names.some(name => name.startsWith('site/'))) labels.add('docs-site');

            if (labels.size > 0) {
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: [...labels]
              });
            }

Catatan keamanan: pull_request_target memiliki konteks permission yang lebih tinggi dibanding pull_request. Karena itu, gunakan hanya untuk operasi aman seperti membaca metadata PR dan menambahkan label. Hindari mengeksekusi kode dari branch kontributor dalam workflow ini.

Strategi branch protection yang realistis

Workflow yang bagus akan kurang efektif jika branch utama tetap menerima merge tanpa syarat. Untuk repository konten, strategi branch protection yang praktis biasanya mencakup:

  • Require pull request before merging
  • Require status checks to pass untuk Content CI dan Preview Build
  • Require at least 1 review dari editor teknis atau maintainer
  • Dismiss stale reviews bila ada perubahan besar setelah review
  • Restrict direct pushes ke branch utama

Kalau tim Anda kecil dan ritme publikasinya cepat, jangan terlalu banyak menambahkan status check wajib. Misalnya pemeriksaan link penuh mingguan tidak perlu dijadikan syarat merge PR harian. Pisahkan antara:

  • gating checks: wajib lulus untuk merge,
  • informational checks: memberi sinyal kualitas, tetapi tidak memblokir merge.

Pemisahan ini penting agar branch protection menjaga kualitas tanpa menciptakan antrean merge yang tidak perlu.

Checklist review untuk submission yang konsisten

CI membantu banyak, tetapi reviewer tetap memegang keputusan editorial dan teknis. Simpan checklist di .github/pull_request_template.md agar penulis dan reviewer memakai kriteria yang sama.

## Checklist penulis
- [ ] Front matter lengkap dan valid
- [ ] Slug unik dan sesuai format
- [ ] Artikel sudah di-lint tanpa error
- [ ] Link internal dan eksternal sudah dicek
- [ ] Preview build berhasil
- [ ] Contoh kode sudah diuji atau diverifikasi
- [ ] Istilah teknis konsisten

## Checklist reviewer
- [ ] Judul sesuai isi artikel
- [ ] Penjelasan teknis akurat
- [ ] Tidak ada klaim yang tidak didukung
- [ ] Struktur heading jelas
- [ ] Kode dan command realistis
- [ ] Layak dipublikasikan atau perlu revisi

Checklist seperti ini efektif karena mengurangi diskusi berulang. Penulis tahu definisi “siap direview”, dan reviewer punya standar yang konsisten antar artikel.

Kesalahan umum dan tips debugging

1. CI lambat karena memeriksa seluruh repository

Gunakan filter path dan, bila perlu, hanya proses file yang berubah pada PR. Validasi yang memang perlu melihat seluruh koleksi, seperti slug unik, tetap bisa berjalan global karena biayanya biasanya kecil.

2. Link checker sering gagal acak

Jangan langsung menyimpulkan semua link rusak. Cek apakah kegagalan berasal dari timeout, redirect, atau pembatasan akses. Pertimbangkan retry terbatas atau pengecualian untuk domain tertentu yang memang tidak stabil.

3. Workflow sulit dirawat karena semua logika ada di YAML

Pindahkan logika ke script terpisah. YAML sebaiknya menjadi orkestrator, bukan tempat utama logika bisnis editorial.

4. Pesan gagal tidak membantu kontributor

Pastikan setiap validasi menyebut file dan alasan gagal secara eksplisit. Semakin sedikit tebakan yang harus dilakukan penulis, semakin cepat PR diperbaiki.

5. Auto-label atau komentar PR tidak jalan pada fork

Periksa event yang digunakan dan permission workflow. Untuk operasi metadata, pull_request_target sering lebih tepat, tetapi harus dipakai dengan batasan keamanan yang ketat.

Template minimum yang layak dipakai tim

Jika Anda ingin memulai tanpa terlalu kompleks, kombinasi minimum berikut sudah cukup kuat:

  1. Content CI: validasi front matter, slug unik, Markdown lint
  2. Preview Build: build situs dan unggah artefak preview
  3. Auto Label: beri label berdasarkan path
  4. Branch protection: wajib lulus CI dan minimal satu review
  5. PR checklist: samakan ekspektasi penulis dan reviewer

Setelah stabil, baru tambahkan pemeriksaan link penuh, aturan editorial tambahan, atau automasi komentar preview.

Penutup

Template GitHub Actions untuk writing challenge tim yang konsisten bukan sekadar rangkaian job CI. Ia adalah cara memformalkan standar kontribusi konten teknis agar partisipasi tetap tinggi tanpa mengorbankan kualitas. Dengan validasi front matter, cek slug duplikat, lint Markdown, pemeriksaan link, preview build, dan auto-label PR, tim bisa mengurangi beban review manual dan mempercepat rilis artikel secara signifikan.

Mulailah dari aturan yang benar-benar menyelesaikan masalah harian tim Anda. Jika workflow mudah dipahami, pesan error jelas, dan branch protection disusun dengan realistis, writing challenge tidak hanya seru di permukaan, tetapi juga efisien secara operasional untuk maintainer dan kontributor.