From 386c289140d775b6c31b21baa71df00b7830e03a Mon Sep 17 00:00:00 2001 From: m5r Date: Thu, 20 Aug 2026 22:40:08 +0200 Subject: [PATCH] justfile for repetitive workflows, release cut included --- justfile | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 justfile diff --git a/justfile b/justfile new file mode 100644 index 0000000..66ea24e --- /dev/null +++ b/justfile @@ -0,0 +1,86 @@ +# Repetitive workflows. Anything a release or a review runs twice belongs here, +# so a step cannot be forgotten by hand (v0.0.7 first failed on an unbumped +# build.zig.zon that only verify-dist caught, one CI round too late). + +repo := "mokhtar/nxdns" +forge := "https://git.mial.net" + +# every recipe, described +default: + @just --list + +# unit suite +test: + zig build test + +# unit + integration suite +itest: + zig build test -Dintegration + +# admin: typecheck, tests, lint, formatting +admin-check: + cd admin && npx tsc -b && npx vitest run && npx oxlint && npx prettier --check . + +# regenerate the contract goldens from live responses +goldens: + zig build test -Dintegration -Dcontract-samples-out="{{justfile_directory()}}/admin/src/lib/contractSamples.gen.ts" + +# the binary with the real admin UI embedded (the default build embeds a placeholder) +build: + cd admin && npm run build + zig build -Dadmin-dist=admin/dist + +# everything CI gates on, locally +verify: test itest admin-check + zig fmt --check src/ tools/ + +# cut a release: preflight, bump, push, wait for CI, signed tag, watch the run. +# Requires: clean tree, master, CHANGELOG section for the version already written. +release version: + #!/usr/bin/env sh + set -eu + v="{{version}}" + [ -z "$(git status --porcelain)" ] || { echo "release: tree is dirty"; exit 1; } + [ "$(git branch --show-current)" = "master" ] || { echo "release: not on master"; exit 1; } + grep -q "^## \[$v\]" CHANGELOG.md || { echo "release: CHANGELOG.md has no ## [$v] section"; exit 1; } + git ls-remote --exit-code --tags origin "v$v" >/dev/null 2>&1 && { echo "release: tag v$v already on origin"; exit 1; } + if ! grep -q "\.version = \"$v\"" build.zig.zon; then + sed -i "s/\.version = \"[^\"]*\"/.version = \"$v\"/" build.zig.zon + grep -q "\.version = \"$v\"" build.zig.zon || { echo "release: version bump failed"; exit 1; } + git add build.zig.zon + git commit -S -m "build: bump version to $v" + fi + git push origin master + sha=$(git rev-parse HEAD) + echo "release: waiting for CI on $sha" + while :; do + state=$(curl -sf "{{forge}}/api/v1/repos/{{repo}}/commits/$sha/status" | python3 -c "import json,sys; print(json.load(sys.stdin).get('state') or 'pending')") || state=pending + case "$state" in + success) break ;; + pending) sleep 30 ;; + *) echo "release: CI is $state on $sha — not tagging"; exit 1 ;; + esac + done + git tag -s "v$v" -m "v$v" "$sha" + git push origin "v$v" + echo "release: tag v$v pushed, watching the release run" + while :; do + line=$(curl -sf "{{forge}}/api/v1/repos/{{repo}}/actions/runs?limit=10" | python3 -c " + import json,sys + for r in json.load(sys.stdin).get('workflow_runs', []): + if r.get('path','').startswith('release.yml') and r.get('head_sha') == '$sha': + print(r.get('status',''), r.get('conclusion','') or ''); break + ") || line="" + set -- $line "" + if [ "${1:-}" = "completed" ]; then + [ "$2" = "success" ] || { echo "release: workflow concluded $2 — read the publish job log"; exit 1; } + break + fi + sleep 60 + done + curl -sf "{{forge}}/api/v1/repos/{{repo}}/releases/tags/v$v" | python3 -c " + import json,sys + d = json.load(sys.stdin) + print('release: published', d['tag_name'], 'with', len(d.get('assets', [])), 'assets') + for a in d.get('assets', []): print(' ', a['name']) + "