Panduan Lengkap Membuat Action Kustom di GitHub Actions
Pendahuluan
Bayangkan kamu sedang membangun sistem deployment otomatis seperti yang digunakan platform e-commerce besar — setiap kali ada kode baru masuk, sistem langsung memvalidasi, menguji, dan mendeploy tanpa campur tangan manusia. Di sinilah action kustom GitHub Actions menjadi senjata andalan.
Action bawaan dari GitHub Marketplace memang banyak, tapi ada kalanya tidak ada satu pun yang pas dengan kebutuhan proyekmu. Mungkin kamu perlu memvalidasi format file Markdown sebelum publish, atau mengirim notifikasi ke sistem internal tim. Itulah saatnya kamu membuat action sendiri.
Artikel ini membahas cara membuat action kustom dari nol: memahami strukturnya, memilih tipe yang tepat, hingga contoh kasus nyata yang langsung bisa kamu adaptasi. Jika kamu belum familiar dengan konsep dasar workflow, pastikan kamu sudah membaca artikel tentang komponen dasar GitHub Actions terlebih dahulu.
Memahami Anatomi Action Kustom: action.yml
Setiap action kustom wajib memiliki file action.yml di root repositori. File ini adalah “kontrak” yang mendeklarasikan apa yang bisa dilakukan action-mu, input apa yang diterima, dan output apa yang dihasilkan.
# action.yml
name: "Validasi Markdown"
description: "Memvalidasi format dan struktur file Markdown"
author: "KamusNgoding"
inputs:
target-directory:
description: "Direktori yang berisi file Markdown"
required: true
default: "."
strict-mode:
description: "Aktifkan validasi ketat (gagal jika ada warning)"
required: false
default: "false"
outputs:
total-files:
description: "Jumlah file Markdown yang divalidasi"
error-count:
description: "Jumlah error yang ditemukan"
runs:
using: "node20"
main: "dist/index.js"
Beberapa bagian penting yang perlu dipahami:
inputs: Parameter yang bisa diisi pengguna action-mu saat memakainya di workflow. Setiap input bisa punya nilaidefault.outputs: Data yang bisa diakses step berikutnya setelah action selesai berjalan.runs: Menentukan tipe action dan entry point-nya.
Tipe-Tipe Action Kustom: JavaScript, Docker, dan Composite
GitHub Actions mendukung tiga tipe action kustom, masing-masing dengan karakteristik berbeda:
1. JavaScript Action
Action berbasis Node.js yang berjalan langsung di runner. Ini adalah pilihan tercepat karena tidak perlu build container.
runs:
using: "node20"
main: "dist/index.js"
pre: "dist/setup.js" # opsional: dijalankan sebelum main
post: "dist/cleanup.js" # opsional: dijalankan setelah main
Cocok untuk: Manipulasi file, panggilan API, logika sederhana hingga menengah.
2. Docker Action
Menjalankan action di dalam container Docker. Lebih lambat tapi memberikan kontrol penuh atas environment — cocok jika action-mu butuh tool sistem seperti pandoc, imagemagick, atau runtime selain Node.js.
# action.yml untuk Docker Action
name: "Konversi Dokumen"
description: "Konversi Markdown ke PDF menggunakan Pandoc"
author: "KamusNgoding"
inputs:
input-file:
description: "Path ke file Markdown"
required: true
output-file:
description: "Nama file PDF output"
required: false
default: "output.pdf"
runs:
using: "docker"
image: "Dockerfile"
args:
- ${{ inputs.input-file }}
- ${{ inputs.output-file }}
# Dockerfile
FROM pandoc/core:3.1
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/bash
# entrypoint.sh
set -e
INPUT_FILE=$1
OUTPUT_FILE=$2
echo "Mengkonversi $INPUT_FILE ke $OUTPUT_FILE..."
pandoc "$INPUT_FILE" -o "$OUTPUT_FILE" --pdf-engine=xelatex
echo "Konversi selesai: $OUTPUT_FILE"
Cocok untuk: Action yang butuh dependensi sistem spesifik, atau bahasa selain JavaScript.
3. Composite Action
Menggabungkan beberapa step workflow menjadi satu action yang bisa dipakai ulang — tanpa menulis kode.
runs:
using: "composite"
steps:
- name: Install dependencies
shell: bash
run: npm ci
- name: Run linter
shell: bash
run: npm run lint
Cocok untuk: Mengemas urutan langkah yang sering diulang di banyak workflow.
Untuk artikel ini, kita akan fokus pada JavaScript Action karena paling umum digunakan dan performanya optimal.
Panduan Langkah-demi-Langkah Membuat Action JavaScript
Langkah 1: Siapkan Struktur Repositori
my-custom-action/
├── action.yml
├── package.json
├── src/
│ └── index.js
└── dist/
└── index.js (dibuat oleh bundler)
Langkah 2: Inisialisasi Project Node.js
npm init -y
npm install @actions/core @actions/github
npm install --save-dev @vercel/ncc
Package @actions/core menyediakan fungsi penting seperti getInput(), setOutput(), dan setFailed(). Jika kamu terbiasa dengan Pengenalan TypeScript: Superset JavaScript yang Lebih Aman, kamu bisa menggunakan TypeScript untuk action-mu dengan menambahkan @actions/core type definitions.
Langkah 3: Tulis Logika Action
// src/index.js
const core = require("@actions/core");
const fs = require("fs");
const path = require("path");
async function run() {
try {
// Ambil input dari action.yml
const targetDir = core.getInput("target-directory", { required: true });
const strictMode = core.getInput("strict-mode") === "true";
core.info(`Memulai validasi di direktori: ${targetDir}`);
// Cari semua file Markdown
const files = findMarkdownFiles(targetDir);
let errorCount = 0;
for (const file of files) {
const content = fs.readFileSync(file, "utf-8");
const errors = validateMarkdown(content, file);
if (errors.length > 0) {
errors.forEach((err) => core.error(err, { file }));
errorCount += errors.length;
}
}
// Set output untuk step berikutnya
core.setOutput("total-files", files.length.toString());
core.setOutput("error-count", errorCount.toString());
// Gagalkan workflow jika ada error
if (errorCount > 0 || (strictMode && errorCount > 0)) {
core.setFailed(`Validasi gagal: ditemukan ${errorCount} error`);
} else {
core.info(`✅ Semua ${files.length} file valid!`);
}
} catch (error) {
core.setFailed(`Action gagal: ${error.message}`);
}
}
function findMarkdownFiles(dir) {
const results = [];
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory() && item.name !== "node_modules") {
results.push(...findMarkdownFiles(fullPath));
} else if (item.isFile() && item.name.endsWith(".md")) {
results.push(fullPath);
}
}
return results;
}
function validateMarkdown(content, filePath) {
const errors = [];
// Cek apakah ada heading H1
if (!content.match(/^# .+/m)) {
errors.push(`${filePath}: Tidak ada heading H1`);
}
// Cek code block tanpa bahasa
const codeBlocksWithoutLang = content.match(/```\n/g);
if (codeBlocksWithoutLang) {
errors.push(
`${filePath}: Ada ${codeBlocksWithoutLang.length} code block tanpa bahasa`
);
}
return errors;
}
run();
Langkah 4: Bundle Action dengan ncc
GitHub Actions membutuhkan semua dependensi dalam satu file. Gunakan ncc untuk bundling:
// package.json
{
"scripts": {
"build": "ncc build src/index.js -o dist --license licenses.txt",
"prepare": "npm run build"
}
}
npm run build
Folder dist/ yang dihasilkan harus di-commit ke repositori, karena runner GitHub akan langsung mengeksekusi file tersebut.
Langkah 5: Gunakan Action di Workflow
# .github/workflows/validate.yml
name: Validasi Konten
on:
pull_request:
paths:
- "src/content/**/*.md"
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Jalankan validasi Markdown
id: validator
uses: username/my-custom-action@v1
with:
target-directory: "src/content"
strict-mode: "true"
- name: Tampilkan hasil
run: |
echo "Total file: ${{ steps.validator.outputs.total-files }}"
echo "Total error: ${{ steps.validator.outputs.error-count }}"
Contoh Kasus Nyata: Otomatisasi Validasi Markdown
Berikut workflow lengkap yang mengintegrasikan action kustom di atas dengan konteks nyata — misalnya untuk platform dokumentasi seperti KamusNgoding:
# .github/workflows/content-pipeline.yml
name: Content Quality Gate
on:
pull_request:
branches: [main]
jobs:
quality-check:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Validasi struktur Markdown
id: md-check
uses: username/my-custom-action@v1
with:
target-directory: "src/content"
strict-mode: "true"
- name: Komentar hasil di PR
if: always()
uses: actions/github-script@v7
with:
script: |
const totalFiles = '${{ steps.md-check.outputs.total-files }}';
const errorCount = '${{ steps.md-check.outputs.error-count }}';
const status = errorCount === '0' ? '✅ Lulus' : '❌ Gagal';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Hasil Validasi Konten\n\n${status}\n- File diperiksa: ${totalFiles}\n- Error ditemukan: ${errorCount}`
});
Pola ini mirip dengan konsep Mastering Factory Method Pattern: Membuat Sistem Plugin yang Fleksibel — kamu mendefinisikan antarmuka standar (action.yml), lalu implementasinya bisa diganti sesuai kebutuhan tanpa mengubah workflow yang memanggilnya.
Troubleshooting: Error yang Sering Muncul
Error: Cannot find module '@actions/core'
Penyebab: File dist/index.js tidak di-bundle dengan benar, atau folder dist/ tidak ikut di-commit ke repositori.
Solusi:
# Pastikan ncc sudah terinstall dan jalankan build
npm install --save-dev @vercel/ncc
npm run build
# Commit folder dist/
git add dist/
git commit -m "chore: bundle action dependencies"
git push
Error: Input required and not supplied: target-directory
Penyebab: Pengguna action tidak menyertakan input yang wajib (required: true) saat memanggil action di workflow mereka.
Solusi:
# Pastikan semua input required tersedia di workflow
- uses: username/my-custom-action@v1
with:
target-directory: "src/content" # ← wajib diisi
Atau ubah required: false dengan nilai default di action.yml jika input memang opsional.
Error: Error: ENOENT: no such file or directory
Penyebab: Action mencoba membaca direktori yang tidak ada, biasanya karena actions/checkout belum dipanggil sebelum action kustom dijalankan.
Solusi:
steps:
- uses: actions/checkout@v4 # ← WAJIB sebelum action yang akses file
- uses: username/my-custom-action@v1
with:
target-directory: "src/content"
Warning: Node.js 16 actions are deprecated
Penyebab: action.yml masih menggunakan using: "node16" yang sudah deprecated oleh GitHub.
Solusi:
# action.yml — ubah ke node20
runs:
using: "node20" # ← ganti dari node16
main: "dist/index.js"
Pertanyaan yang Sering Diajukan
Apa perbedaan action kustom dengan reusable workflow?
Action kustom adalah unit logika yang dikemas dalam repositori tersendiri dan dipanggil dengan uses: — fokusnya pada satu tugas spesifik. Reusable workflow adalah workflow lengkap (dengan jobs dan steps) yang bisa dipanggil dari workflow lain. Gunakan action kustom untuk logika yang sering dipakai ulang di banyak proyek, dan reusable workflow untuk urutan proses CI/CD yang standar di seluruh tim.
Bagaimana cara mempublish action ke GitHub Marketplace?
Repositori action-mu harus bersifat public, dan file action.yml harus ada di root repositori. Setelah itu, buka tab “Releases” di GitHub, buat release baru, dan centang opsi “Publish this Action to the GitHub Marketplace”. Pastikan nama dan deskripsi action jelas agar mudah ditemukan developer lain.
Apakah action kustom bisa digunakan di repositori private?
Ya. Untuk repositori private, kamu bisa merujuk action dari repositori lain di organisasi yang sama menggunakan format uses: org/repo-action@v1. Pastikan pengaturan akses “Actions” di organization settings mengizinkan akses lintas repositori.
Mengapa saya harus meng-commit folder dist/?
Berbeda dengan project Node.js biasa, GitHub Actions tidak menjalankan npm install sebelum mengeksekusi action. Runner langsung membaca file yang ada di repositori. Karena itu, semua dependensi harus sudah di-bundle ke dalam dist/index.js dan file tersebut harus ada di repositori.
Bagaimana cara menguji action kustom secara lokal sebelum di-push?
Gunakan package act (GitHub Actions local runner) atau buat file pengujian sederhana yang menyimulasikan environment GitHub Actions:
# Simulasi environment GitHub Actions secara lokal
INPUT_TARGET_DIRECTORY="./src/content" \
INPUT_STRICT_MODE="false" \
node dist/index.js
Kesimpulan
Membuat action kustom di GitHub Actions membuka peluang otomatisasi yang jauh lebih luas dari sekadar mengandalkan action pihak ketiga. Dengan memahami struktur action.yml, memilih tipe action yang tepat (JavaScript untuk kecepatan, Docker untuk fleksibilitas, Composite untuk penyederhanaan), dan mengikuti alur bundling yang benar, kamu bisa membangun blok otomatisasi yang dapat dipakai ulang di seluruh proyek timmu.
Kunci utamanya: selalu bundle dependensi dengan ncc, commit folder dist/, dan uji action-mu di workflow nyata sebelum mempublishnya. Selamat bereksperimen dan terus kembangkan pipeline otomatisasimu — jika ada pertanyaan atau ingin berbagi action kustom yang sudah kamu buat, jangan ragu untuk eksplorasi artikel-artikel lainnya di KamusNgoding!