Releasing FormSpec
Prosedur manual untuk maintainer memproduksi dan mempublikasikan release CLI formspec (binary multi-OS). Panduan untuk mengunduh binary ada di install.md.
Prasyarat (di mesin release)
| Tool | Kegunaan |
|---|---|
| Go ≥ 1.26 | Compile binary formspec |
| Node.js + npm | Build SPA yang di-embed ke binary |
gh CLI (ter-auth) | Upload artifact ke GitHub Releases — opsional, bisa manual via web |
tar, zip, shasum/sha256sum | Packaging + checksums |
gh auth status # pastikan sudah login ke github.comCara cepat — script satu perintah
Ada dua jalur cepat: target Makefile (tanpa tag di awal) atau script (dari working tree bersih sampai draft release):
make set-version-auto && make release && make release-upload # bertahap
scripts/git-push-and-tag.sh v0.4.2 # test + tag + push + build + upload draft
scripts/git-push-and-tag.sh v0.4.2 --skip-tests # lewati go test (harus sudah dijalankan manual)Sebelum tagging, script juga otomatis menyinkronkan versi contoh yang hardcoded di site/src/components/Install.tsx (bagian Install di formspec.dev) dan docs/guides/install.md — versi lama di-replace ke versi release lalu di-commit, sehingga tag selalu berisi site/docs dengan versi yang benar. Installer (install.sh/install.ps1) sendiri tidak perlu diubah karena resolve versi terbaru via GitHub API saat runtime.
Setelah itu script menjalankan preflight generated artifacts: regenerate make generate-schema + make generate-kind-docs lalu fail-fast bila schemas/ atau docs/kind/ berubah (artefak basi — biasanya pkg/spec baru diubah tapi generator lupa dijalankan). Bila preflight gagal, hasil regenerate dibiarkan di tree — commit dulu, lalu jalankan ulang script.
Script yang sama menerapkan semua guard prosedur manual sebelum menyentuh apapun: VERSION harus semver, working tree harus bersih, tag belum dipakai (lokal & remote), gh ter-auth, dan sedang di branch main. Selain itu, VERSION harus lebih tinggi dari tag tertinggi yang sudah ada — versi tidak boleh mundur, karena GitHub menentukan release "latest" berdasarkan yang terakhir di-publish (bukan semver tertinggi); release semver lebih rendah akan membuat installer men-downgrade user. Output akhirnya adalah draft release — tetap harus di-review lalu Publish (langkah yang sama seperti §4).
Script memerlukan working tree bersih — commit semua perubahan dulu. Untuk kondisi khusus (mis. release dari commit tertentu, atau setelah gagal di tengah jalan), ikuti langkah manual §2–§4 di bawah yang lebih bisa diputus per langkah.
0. Cek versi — tidak perlu diingat-ingat
Versi yang pernah di-release tersimpan di 3 tempat; versi berikutnya selalu kelihatan dari sini, bukan dari catatan manual:
# Tag terakhir yang pernah dibuat (lokal)
git tag -l | sort -V | tail -1
# Tag yang SUDAH ter-push ke remote (ini yang menentukan: satu tag = satu release)
git ls-remote --tags origin | grep -o 'refs/tags/.*' | sort -V | tail -1
# Versi terbaru yang live di GitHub Releases (dipakai installer user)
curl -fsSL https://api.github.com/repos/primadi/formspec/releases/latest \
| grep tag_name
# Versi yang terpasang di mesin ini
formspec versionVersi rilis berikutnya (dan validasinya) bisa dilihat langsung:
make release-version # versi yang akan dipakai rilis
make release-version VERSION=v0.1.0 # validasi versi eksplisitCatatan: repo tidak menyimpan file VERSION — angka versi di-stamp saat build dari tag (-ldflags -X main.version=), jadi tidak ada angka versi yang bisa stale di source code. VERSION= opsional di target rilis:
make release-version/make releasetanpaVERSION=memakai tag semver yang menunjuk HEAD — yaitu versi yang memang sedang dibangun (satu tag = satu release ⟹ tag di HEAD adalah kandidat versi rilis). Kalau HEAD belum di-tag, baru patch-bump dari tag semver tertinggi (scripts/next-version.sh;v0.0.9→v0.0.10). Hitungannya terhadap tag lokal — jalankangit fetch --tagsdulu bila tag terbaru hanya ada di remote. Bump minor/major tetap eksplisit:make release VERSION=v0.1.0;make release-uploadmemakai versi yang sudah tertanam didist/release/, bukan di-derive ulang — kalau tidak, tag yang dibuat di antara dua perintah akan menggeser hasil auto-bump dan upload mencampur dua versi.VERSION=eksplisit harus sama dengan versi artifact tersebut;- guard-nya menolak bila tag sudah punya release published (satu tag = satu release; draft yang belum lengkap justru di-resume — lihat §4).
Versi rilis wajib semver murni (
v<major>.<minor>.<patch>). String git-describe sepertiv0.0.8-4-gceaaf2aditolak guard (scripts/check-semver.sh):formspec upgrademembacanya sebagai prerelease v0.0.8, sehingga rilis yang isinya justru lebih baru tampak sebagai rollback bagi user. Karena ituVERSIONuntuk target rilis tidak diambil darigit describe—git describehanya menjadi stamp build dev (make build/make build-formspec).Sebelum ada tag semver sama sekali, auto-bump gagal dan versi harus ditentukan eksplisit (rilis pertama dipilih sadar, mis.
v0.1.0).
1. Pastikan state siap rilis
git checkout main
git pull origin main
go test ./... # semua hijau2. Tentukan & push tag versi
Tag meng-embed ke URL download (.../download/<tag>/formspec-<os>-<arch>.tar.gz), sehingga satu tag = satu release dan tidak bisa dipakai ulang. Cara termudah — satu target Makefile yang menghitung versi, menjalankan guard, membuat tag, dan (opsional) mem-push-nya:
make set-version-auto # patch-bump tag tertinggi → tag lokal di HEAD
make set-version-auto PUSH=1 # + git push origin <tag>
make set-version-auto VERSION=v0.4.2 # bump minor/major eksplisitGuard-nya sama dengan scripts/git-push-and-tag.sh dan gagal sebelum menyentuh git: semver murni · working tree bersih · branch main · tag belum ada (lokal & remote) · versi tidak mundur dari tag tertinggi. git fetch --tags dijalankan dulu (--no-fetch untuk melewati). Tanpa VERSION=, target ini idempoten: jika HEAD sudah di-tag versi rilis, ia menolak dan menyuruh lanjut ke make release — bukan membuat nomor berikutnya di commit yang sama.
Atau lakukan manual, sama sahnya:
git tag v0.4.2
git push origin main --tagsmake release sendiri tidak membuat tag — ia hanya build artifact dan tidak menyentuh git ref (set-version-auto yang menyediakannya secara eksplisit). Tag dipilih sadar oleh maintainer (auto-bump versi ≠ otorisasi publish; lihat docs_internal/plan/release-version-auto.md §Keputusan). Karena itu urutan tag ↔ build tidak saling bergantung, dan dua urutan ini sama sahnya:
make set-version-auto && make release # tag dulu → build
make release && make set-version-auto # build dulu → tag menyusulSyaratnya cuma satu: tag harus menunjuk commit yang dibangun. Kalau HEAD bergerak setelah make release (mis. ada commit susulan), tag di commit baru tidak lagi mewakili artifact di dist/release/ — pindahkan tag ke commit yang dibangun atau ulangi make release dari commit yang di-tag.
Agar langkah ini tidak muncul mendadak sebagai error di §4, make release mencetak status tag — sebelum build SPA dan sekali lagi di ringkasan akhir — via scripts/release-tag-status.sh (info saja, tidak pernah menggagalkan build). Tag juga harus sudah di-push sebelum publish: URL download release meng-embed tag. Guard yang menegakkan itu hanya git rev-parse lokal (tag wajib ada di mesin ini); status tag di remote tidak diperiksa otomatis — karena itu set-version-auto menyarankan PUSH=1.
3. Build semua artifact
make release VERSION=v0.4.2 # tanpa VERSION= → patch-bump tag tertinggiYang dilakukan target ini:
- Build SPA (
build-spa) sekali — identik untuk semua target - Cross-compile
{linux,darwin,windows} × {amd64,arm64}denganCGO_ENABLED=0 -trimpathdan versi di-stamp via-ldflags "-X main.version=..." - Packaging:
tar.gz(linux/darwin),zip(windows), +SHA256SUMS.txt
Output: dist/release/. Verifikasi cepat sebelum upload:
cd dist/release
shasum -a 256 -c SHA256SUMS.txt
ls -lh # semua arsip harus besar (belasan–dua puluh MB); ukuran
# < 1 MB berarti binary di dalamnya kosong — jangan upload
tar -xzf formspec-darwin-arm64.tar.gz -C /tmp && /tmp/formspec version # → formspec v0.4.2Catatan:
SHA256SUMS.txtdi-generate dari file yang ada — checksum "OK" hanya membuktikan konsistensi, bukan bahwa artifact valid. Ukuran file dan cekformspec versionyang mendeteksi artifact rusak. Makefile guardreleasejuga fail-fast bila binary hasil build kosong.
Kontrak publik: nama artifact (
formspec-<os>-<arch>.tar.gz|.zip),SHA256SUMS.txt(satu baris per artifact), dan endpointreleases/latestadalah API yang dipakaiformspec upgradedan installer script. Jangan mengubahnya tanpa bump major.
4. Upload ke GitHub Releases
make release-upload VERSION=v0.4.2 # tanpa VERSION= → versi artifact dist/release/Membuat draft release dengan semua artifact + SHA256SUMS.txt + generated notes. Review di halaman Releases (urutan, notes, checksum), lalu klik Publish.
Target ini idempotent — boleh diulang. Kalau upload macet atau terputus di tengah jalan (mis. baru sebagian asset yang naik), jalankan perintah yang sama lagi; tidak ada yang perlu dihapus lebih dulu:
- Release belum ada → draft dibuat dulu (
--draft --generate-notes, tanpa asset), lalu asset di-upload menyusul. - Release draft sudah ada → upload dilanjutkan: asset yang ukurannya sudah cocok di GitHub dilewati, sisanya di-upload ulang dengan
gh release upload --clobber. - Release sudah published → ditolak ("satu tag = satu release"): artifact sudah bisa diunduh user lewat URL yang meng-embed tag, jadi isinya tidak boleh berubah lagi — lihat §Rollback rilis.
Upload dijalankan satu file per file (bukan paralel), jadi progresnya granular dan file yang gagal bisa diulang tanpa mengulang yang sudah naik. Bila satu asset gagal, target berhenti dengan pesan yang menyuruh mengulang perintah yang sama. Di akhir, jumlah asset di GitHub diverifikasi sama dengan jumlah file di dist/release/.
Versi yang di-upload adalah versi yang tertanam di dist/release/ (spa-<versi>.tar.gz). Kalau VERSION= diberikan tapi tidak cocok dengan artifact di sana, target berhenti dengan pesan yang menyuruh menjalankan ulang make release VERSION=… lebih dulu — supaya upload tidak mencampur artifact dari dua build.
Guard ini mengecek status release, bukan status tag (push tag di langkah 2 memang mendahului upload) — jadi tag yang sudah di-push tapi belum punya release tetap bisa di-upload.
--clobber baru ada di gh >= 2.18. Tanpa gh (atau gh terlalu tua): upload dist/release/* manual di https://github.com/primadi/formspec/releases/new — pilih tag di langkah 2, pilih draft, dan sertakan SHA256SUMS.txt.
5. Verifikasi pasca-publish (public sanity check)
curl -fsSL https://formspec.dev/install.sh | sh # default → latest
formspec version # → formspec v0.4.2Installer meresolve releases/latest, jadi publish release menentukan versi yang di-install user baru.
Rollback rilis
Tag published tidak bisa dipakai ulang — installer user bisa saja sudah mengunduh artifact dari tag tersebut (URL download meng-embed tag), sehingga retag membuat artifact yang sudah disebar tidak lagi cocok. Untuk memutar versi user kembali:
- Rilis patch baru
v0.4.3, atau - Minta user jalankan installer ulang dengan versi terdahulu:
FORMSPEC_VERSION=v0.4.1 sh -c "$(curl -fsSL https://formspec.dev/install.sh)".
Hapus tag pada release yang masih DRAFT
Draft yang assetnya belum lengkap tidak perlu dihapus lebih dulu — cukup jalankan ulang make release-upload VERSION=<tag> dan upload dilanjutkan dari asset yang belum naik (§4).
Menghapus draft diperlukan bila isi draft itu sendiri yang salah — mis. dist dist/release/ sudah dibangun ulang dari commit berbeda sehingga ukuran asset tidak lagi cocok dan upload ulang justru mencampur dua build. Berbeda dengan published, tag pada release yang masih draft boleh dihapus dan dipakai ulang — draft tidak bisa diunduh user, jadi tidak ada artifact yang pernah tersebar dengan stamp versi itu:
gh release delete v0.0.5 --cleanup-tag --yes # hapus draft release + tag remote
git tag -d v0.0.5 # hapus tag lokalSetelah itu tag bisa dibuat ulang menunjuk commit mana pun dan di-upload ulang (scripts/git-push-and-tag.sh / make release-upload). Guard "versi tidak boleh mundur" tetap berlaku terhadap tag published tertinggi — tag yang dihapus tidak dihitung lagi karena sudah tidak ada di remote.
Catatan: gh release delete tanpa --cleanup-tag hanya menghapus release-nya; tag git tetap ada dan harus dihapus terpisah (tag draft release tetap ter-push ke remote karena dibuat lewat git push --tags sebelum upload).
Kalau release yang dihapus sudah PUBLISHED
Jangan. Konsekuensinya:
- URL download tag itu 404 — user yang install dengan
FORMSPEC_VERSION=<tag>gagal. User yang sudah ter-install tidak terdampak (binary lokal tetap jalan, stamp versinya tetap). - Label "Latest" berpindah bila yang dihapus adalah latest — GitHub memilih release published yang terakhir di-publish (bukan semver tertinggi), jadi user baru bisa ter-downgrade.
- Nomor versi jadi tidak bisa dipercaya — bila tag ikut dihapus dan dipakai ulang, dua artifact berbeda pernah beredar dengan stamp versi sama; guard
release-uploadjuga lolos lagi sehingga upload ulang tidak terdeteksi. Anggap nomor versi yang pernah published "terbakar": jika artifact-nya bermasalah, hapus tag+release lalu rilis versi lebih tinggi — jangan pakai ulang nomornya.
Catatan versi di mesin user
- Binary user selalu bernama
formspec(tanpa versi) di slot yang sama — installer upgrade/rollback cukup menimpa. - Versi aktual dicek via
formspec version.
Belum otomatis (deferred)
GitHub Actions workflow yang trigger on tag push (build + release otomatis) sengaja ditunda — lihat docs_internal/plan/install-page-plan.md §Excluded. Target make release di Makefile adalah basis script yang siap diadaptasi.
Yang sengaja di luar script
Dua pipeline deploy berjalan dengan kadensi tersendiri dan tidak diikat ke release CLI:
- schemas.formspec.dev — JSON Schema untuk YAML editor. Generator (
make generate-schema) di-commit ke repo dan diverifikasi fresh oleh preflight script; pen-deployan-nya lewat jalur git-based terpisah (make publish-schemasmen-stageschemas/dist/<version>, commit, push → Cloudflare auto-build — lihatschemas/README.md). - docs-site (
docs/) — auto-build oleh Cloudflare saat main ter-push; tidak perlu langkah release khusus.