clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

main
forge / .forge / ci
ci 8.1K executable
1#!/bin/sh2# CI for the forge itself: lint the CLI, type-check, then build the demo end to end.3set -e4sh -n bin/forge demo/make-demo.sh5npm ci --silent --no-audit --no-fund6npx tsc -p .7# The design rules (DESIGN.md): tokens only, no capitals, tracking, round8# corners or middle dots in the forge's CSS, templates and guide.9node lib/check-css.ts10# Scenarios: one behavior each, in a throwaway forge of its own (test/lib.sh).11sh test/run.sh12# And in the forge's container, as root, when FORGE_CI_CONTAINER=1 asks13# for it (test/container.sh): it builds the image, minutes the first time.14if [ "${FORGE_CI_CONTAINER:-}" = 1 ]; then15 docker build -q -t "${FORGE_TEST_IMAGE:-forge:test}" . >/dev/null16 sh test/container.sh17fi18# Strict templates: printing an undefined or null value fails the build.19FORGE_STRICT=1 sh demo/make-demo.sh >/dev/null20test -f demo/root/.forge/www/dami/greet/reviews/3/index.html21grep -q 'class="ci pass"' demo/root/.forge/www/dami/greet/reviews/3/index.html22# Highlighting in file pages, diffs (with a 4-digit gutter) and markdown fences; search indexes.23www=demo/root/.forge/www24grep -q 'class="hljs-title class_"' $www/dami/greet/blob/main/web/greet.js/index.html25grep -q 'style="--w:4".*class="hljs-' $www/dami/greet/reviews/7/index.html26grep -q '<code class="language-go">resp, err := http.Get(<span class="hljs-string">' $www/dami/greet/blob/main/docs/examples.md/index.html27# A mermaid diagram in the wiki, drawn at build time.28grep -q '<figure class="diagram"><svg xmlns="http://www.w3.org/2000/svg" class="pele pele-flowchart"' $www/dami/greet/wiki/Design/index.html29grep -q '"reviews":\[\[7,"Regional notes"' $www/dami/greet/search.json30grep -q '"dami/greet","say hello, with flags"' $www/repos.json31grep -q 'data-search="/dami/greet/search.json"' $www/dami/greet/index.html32# Insights: the demo runs forge lake before its build, which puts the exports next to33# the insights pages; the forge's leave out nothing public.34grep -q '"scope":"dami/greet".*"landed":1,"closed":1,' $www/dami/greet/insights/insights.json35grep -q '"ci":18,"ci_pass":17,' $www/dami/greet/insights/insights.json36# CI metrics, approval rules, an agent review and blame (demo/make-demo.sh).37grep -q '"metrics":\[\["script_bytes","B",' $www/dami/greet/insights/insights.json38grep -q '<span class="tag blocking">blocking</span>' $www/dami/greet/reviews/7/index.html39grep -q '<b>agent review</b><span class="has">: demo-rules, signed by ana, 2 findings, <b>1 blocking open</b>' $www/dami/greet/reviews/7/index.html40grep -q '"ranges":\[\[1,' $www/dami/greet/blob/main/greet.sh/blame.json41grep -q 'data-blame-src="/dami/greet/blob/main/greet.sh/blame.json"' $www/dami/greet/blob/main/greet.sh/index.html42grep -q '"repos":\[\["ana/dotfiles",' $www/insights/insights.json43for f in events builds reviews ci; do test "$(head -c 4 $www/insights/$f.parquet)" = PAR1; done44# Templates render the same without 11ty (lib/render-page.ts), with the45# demo build's settings; every asset a page links exists under its hashed46# name; link previews. The demo's clock (make-demo.sh), so a date says the47# same year it did when the demo was built, and an activity grid ends on48# the day its last build did: 2026-09-02, from midnight, as the demo's49# clock starts at midnight the day before.50tmp=$(mktemp -d)51demo_now=$(date -u -j -f '%Y-%m-%d %H:%M:%S' '2026-09-02 00:00:00' +%s 2>/dev/null || date -u -d '2026-09-02 00:00:00' +%s)52FORGE_ROOT=$PWD/demo/root FORGE_SSH=git@forge.local FORGE_CACHE=$tmp FORGE_STRICT=1 FORGE_NOW=$demo_now node lib/render-page.ts check $www53rm -rf "$tmp"54for a in $(grep -rhoE '(href|src)="/assets/[^"]+"' $www --include=index.html | sed -E 's/^[a-z-]+="//; s/"$//' | sort -u); do55 test -f "$www$a" || { echo "missing asset $a"; exit 1; }56done57grep -q '<meta property="og:title" content="#3 Usage and --help - dami/greet">' $www/dami/greet/reviews/3/index.html58# Review discussion: description, an outdated comment, a stale approval, a59# comment inline in the combined diff, approvals in the list, reviews.json.60rv=demo/root/.forge/www/dami/greet61grep -q 'class="panel review-desc"' $rv/reviews/3/index.html62grep -q 'class="comment outdated"' $rv/reviews/3/index.html63grep -q 'changed since' $rv/reviews/3/index.html64grep -q 'class="cmt" id="comment-3"' $rv/reviews/3/diff/index.html65grep -q 'class="appr"' $rv/reviews/index.html66node lib/review-list.ts $rv/reviews.json | grep -q '#2 .*approved by ana'67# Deleting a branch removes its previews; gc then frees their files.68w=demo/root/.forge/www69test ! -e "$w/previews/dami/greet/docs/dami~old-idea"70if grep -q old-idea "$w/dami/greet/sites/index.html"; then echo "sites page lists a deleted preview"; exit 1; fi71# The v0.1 release: its files served, the page listing them with a good signature.72dl=$w/dami/greet/releases/download/v0.173sh bin/forge release verify --signers demo/root/.forge/allowed_signers "$dl/greet.sh" "$dl/greet-0.1.tar.gz" >/dev/null74grep -q 'releases/download/v0.1/greet.sh" download>' "$w/dami/greet/releases/index.html"75grep -q '✓ signed by dami' "$w/dami/greet/releases/index.html"76# Push to create, and landing on the server: ana/notes was made by a push and77# its review landed by ssh land, as dami/greet's first was; the hooks logged78# the deleted branches, and main is where they were.79n=demo/root/ana/notes.git80test -x $n/hooks/pre-receive81grep -q '^deleted ' $n/forge/reviews/182test "$(git --git-dir=$n rev-parse main)" = "$(awk '$1 == "rev" { s = $3 } END { print s }' $n/forge/reviews/1)"83grep -q '^deleted ' demo/root/dami/greet.git/forge/reviews/184# Pinned revisions stay out of what clients see; the build wrote a commit-graph.85g=demo/root/dami/greet.git86test "$(git ls-remote $g | grep -c refs/forge/)" = 087test "$(git --git-dir=$g for-each-ref refs/forge/ | wc -l)" -gt 088test -f $g/objects/info/commit-graphs/commit-graph-chain89gc=$(FORGE_ROOT=$PWD/demo/root sh bin/forge gc --age 0)90printf '%s\n' "$gc" | grep -q '^removed store'91test -f "$w/sites/dami/greet/docs/index.html"92# After gc: a multi-pack index with a bitmap, packed refs, and old revisions93# still there for range-diffs (#4 was rebased between its last two).94test -f $g/objects/pack/multi-pack-index95ls $g/objects/pack/multi-pack-index-*.bitmap >/dev/null96grep -q ' refs/forge/keep/' $g/packed-refs97git --git-dir=$g range-diff --no-color $(awk '$1 == "rev" { r = p; p = $5 ".." $3 } END { print r, p }' $g/forge/reviews/4) | grep -q .98# The guide: there, deployed in the demo, and no broken internal links.99# Its fonts are the forge's, copied in when it is deployed (.forge/sites).100mkdir -p guide/fonts && cp site/assets/fonts/*.woff2 guide/fonts/101node lib/check-links.ts guide102rm -rf guide/fonts103test -f "$w/sites/dami/forge/guide/index.html"104# Backups: stage the demo forge the way forge backup does, restore that into105# an empty root, and check the forge comes back whole: hooks, keys, refs,106# review logs and comments, sites, pages. The restic round trip needs107# restic, which the container image has. The restore's build would start a108# forge lake in the background 10 seconds on, writing into $r while it is109# removed at the end; FORGE_LAKE=0 starts none.110r=$(mktemp -d "${TMPDIR:-/tmp}/forge-ci-restore.XXXXXX")111FORGE_ROOT=$PWD/demo/root sh bin/forge backup --dry-run 2>/dev/null112FORGE_LAKE=0 FORGE_ROOT=$r sh bin/forge restore --from demo/root/.forge/backup/stage >/dev/null 2>&1113test -x "$r/dami/greet.git/hooks/pre-receive"114grep -q 'shell ana"' "$r/.ssh/authorized_keys"115diff -r demo/root/dami/greet.git/forge "$r/dami/greet.git/forge"116for x in dami/greet ana/dotfiles; do117 git --git-dir="$r/$x.git" fsck --no-dangling --no-progress118 test "$(git --git-dir="demo/root/$x.git" for-each-ref | wc -l)" = "$(git --git-dir="$r/$x.git" for-each-ref | wc -l)"119done120test -f "$r/.forge/www/dami/greet/reviews/3/index.html"121test -f "$r/.forge/www/sites/dami/greet/docs/index.html"122cmp "$w/dami/greet/releases/download/v0.1/greet.sh" "$r/.forge/www/dami/greet/releases/download/v0.1/greet.sh"123if command -v restic >/dev/null 2>&1; then124 export FORGE_BACKUP_REPOSITORY="$r/restic" FORGE_BACKUP_PASSWORD=ci125 FORGE_ROOT=$PWD/demo/root sh bin/forge backup 2>/dev/null126 FORGE_ROOT=$PWD/demo/root sh bin/forge backups --status | grep -q 'restore drill: ok'127else128 echo "restic is not installed: skipped the restic round trip"129fi130rm -rf "$r"131echo ok