ci(publish): wire Scoop bucket + Homebrew tap jobs (D3 §3b/§3c)
ci / gate (push) Successful in 1m59s
ci / manifests (push) Successful in 4s

Add sibling publish.yaml jobs (scoop-bucket, homebrew-tap) that render a
manifest from the release .sha256 sidecars and idempotently push it to the
org-level lazyeval/scoop-bucket and lazyeval/homebrew-tap repos, using the
scoped lazyeval-ci bot token (LAZYEVAL_PKG_TOKEN).

Render logic lives in dependency-free bash (the CI image has no jq/ruby):
scripts/render-scoop-manifest.sh and scripts/render-homebrew-formula.sh.
scripts/test-package-renders.sh exercises both: it validates the Scoop JSON
with node and asserts fields on both manifests, and additionally runs
`ruby -c` on the formula where ruby is present (dev box), skipping it
gracefully otherwise.

A new ci.yaml `manifests` job runs that test on every push so a render
regression surfaces immediately, not at the next manual publish dispatch.
The CI image has no ruby, so in CI the gate covers the Scoop JSON (node) and
field assertions for both manifests; the formula's Ruby syntax is checked
dev-side only (the static heredoc's variable parts cannot introduce syntax
errors).

- Scoop: x64 (gnu) + arm64 (gnullvm); #/-rename fragment so the bin shim is
  version-stable; checkver, no autoupdate (the pipeline is the updater).
- Homebrew: on_macos/on_linux x arch bare-binary formula; no Windows.

Docs: ADR-0056 Amendment 2 (+ README index, requirements D3).

Unverified pending real use: scoop/brew install, the HEAD:main branch
assumption, macOS Gatekeeper-via-brew on the ad-hoc-signed binary.
This commit is contained in:
claude@clouddev1
2026-06-19 21:30:18 +00:00
parent c0531aa048
commit 6d54c1e96c
8 changed files with 474 additions and 12 deletions
+23
View File
@@ -6,6 +6,11 @@
# was enabled once the tree was reformatted on main (ADR-ci-002 Amendment 1 /
# issue #35). The release job (static binary for D2) and the platform matrix
# layer on later, step by step.
#
# A separate, lightweight `manifests` job logic-tests the package-manifest
# render scripts (Scoop/Homebrew) used by publish.yaml — bash + node only, no
# toolchain — so a render regression surfaces on the breaking push rather than
# weeks later at the next manual publish dispatch (ADR-0056 Amendment 2).
name: ci
on:
push:
@@ -46,3 +51,21 @@ jobs:
run: nix develop -c cargo clippy --all-targets -- -D warnings
- name: test
run: nix develop -c cargo test --no-fail-fast
# Logic test for the package-manifest render scripts. Renders with DUMMY
# inputs and validates the output — it never publishes or touches the lazyeval
# repos (that is publish.yaml's manual job). Runs on the same image but skips
# nix: it needs only bash + node, both in the base image.
#
# NOTE: the CI image has no ruby, so the script's `ruby -c` formula syntax
# check is skipped here (it degrades gracefully); the Scoop JSON is still
# validated with node and both manifests' fields are asserted. Full formula
# syntax is checked dev-side (ruby present) on every pre-commit local run.
manifests:
runs-on: ci-public
container:
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
steps:
- uses: actions/checkout@v4
- name: render-script tests (Scoop + Homebrew)
run: bash scripts/test-package-renders.sh
+130 -6
View File
@@ -71,9 +71,133 @@ jobs:
nix develop -c cargo publish --locked
echo "published rdbms-playground $VER to crates.io."
# Future manual publication targets go here as independent, idempotent jobs,
# e.g.:
# scoop-bucket: { ... update the lazyeval Scoop bucket manifest ... }
# homebrew-tap: { ... update the lazyeval Homebrew formula ... }
# winget: { ... komac submit, or a manual PR helper ... }
# No `needs:` between them — each checks the target and skips if already done.
# Update the lazyeval Scoop bucket (Windows). Renders the manifest from the
# release's .sha256 sidecars and commits it to lazyeval/scoop-bucket. Pushes
# with the lazyeval-ci bot token (LAZYEVAL_PKG_TOKEN), which is scoped — via
# the bot's org-team membership — to the lazyeval package repos only, so a
# leak cannot touch oli/rdbms-playground.
scoop-bucket:
runs-on: ci-public
container:
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
steps:
- uses: actions/checkout@v4 # default ref (main) — current render script
- name: update the lazyeval Scoop bucket (idempotent)
shell: bash
env:
TAG: ${{ inputs.tag }}
# Passed via env, never inlined into the script, so the value stays
# masked in logs; it only materialises in the clone URL at runtime.
PKG_TOKEN: ${{ secrets.LAZYEVAL_PKG_TOKEN }}
run: |
set -euo pipefail
VER="${TAG#v}"
echo "scoop: targeting rdbms-playground $VER ($TAG)"
base="https://git.lazyeval.net/oli/rdbms-playground/releases/download/$TAG"
fetch_hash() {
local asset="$1" line
echo "scoop: fetching $asset.sha256" >&2
line=$(curl -fsSL "$base/$asset.sha256") \
|| { echo "ERROR: cannot fetch $asset.sha256 — is $TAG released with assets?" >&2; exit 1; }
# First whitespace-delimited field is the hash. `read` is a bash
# builtin (no awk, which the slim CI image may lack).
local hash _
read -r hash _ <<<"$line"
printf '%s' "$hash"
}
h_x64=$(fetch_hash "rdbms-playground-$TAG-x86_64-pc-windows-gnu.exe")
h_arm=$(fetch_hash "rdbms-playground-$TAG-aarch64-pc-windows-gnullvm.exe")
echo "scoop: rendering manifest"
bash scripts/render-scoop-manifest.sh "$VER" "$h_x64" "$h_arm" > /tmp/rdbms-playground.json
node -e 'JSON.parse(require("fs").readFileSync("/tmp/rdbms-playground.json","utf8"))' \
|| { echo "ERROR: rendered Scoop manifest is not valid JSON" >&2; exit 1; }
work=$(mktemp -d)
echo "scoop: cloning lazyeval/scoop-bucket"
git clone --depth 1 "https://lazyeval-ci:${PKG_TOKEN}@git.lazyeval.net/lazyeval/scoop-bucket.git" "$work"
cp /tmp/rdbms-playground.json "$work/rdbms-playground.json"
cd "$work"
git config user.name "lazyeval-ci"
git config user.email "ci@lazyeval.net"
git add rdbms-playground.json
if git diff --cached --quiet; then
echo "scoop: manifest already at $VER — nothing to commit."
exit 0
fi
git commit -m "rdbms-playground $VER"
# Push to main explicitly: a freshly-created (empty) repo clone may put
# the first commit on a differently-named local branch. Assumes the
# bucket/tap default branch is `main` (Gitea's default for new repos).
git push origin HEAD:main
echo "scoop: bucket updated to rdbms-playground $VER."
# Update the lazyeval Homebrew tap (macOS + Linux). Same shape as scoop-bucket;
# writes Formula/rdbms-playground.rb into lazyeval/homebrew-tap.
homebrew-tap:
runs-on: ci-public
container:
image: git.lazyeval.net/oli/rdbms-playground-ci:latest
steps:
- uses: actions/checkout@v4
- name: update the lazyeval Homebrew tap (idempotent)
shell: bash
env:
TAG: ${{ inputs.tag }}
PKG_TOKEN: ${{ secrets.LAZYEVAL_PKG_TOKEN }}
run: |
set -euo pipefail
VER="${TAG#v}"
echo "homebrew: targeting rdbms-playground $VER ($TAG)"
base="https://git.lazyeval.net/oli/rdbms-playground/releases/download/$TAG"
fetch_hash() {
local asset="$1" line
echo "homebrew: fetching $asset.sha256" >&2
line=$(curl -fsSL "$base/$asset.sha256") \
|| { echo "ERROR: cannot fetch $asset.sha256 — is $TAG released with assets?" >&2; exit 1; }
# First whitespace-delimited field is the hash. `read` is a bash
# builtin (no awk, which the slim CI image may lack).
local hash _
read -r hash _ <<<"$line"
printf '%s' "$hash"
}
mac_arm=$(fetch_hash "rdbms-playground-$TAG-aarch64-apple-darwin")
mac_x64=$(fetch_hash "rdbms-playground-$TAG-x86_64-apple-darwin")
lin_arm=$(fetch_hash "rdbms-playground-$TAG-aarch64-unknown-linux-musl")
lin_x64=$(fetch_hash "rdbms-playground-$TAG-x86_64-unknown-linux-musl")
echo "homebrew: rendering formula"
bash scripts/render-homebrew-formula.sh "$VER" "$mac_arm" "$mac_x64" "$lin_arm" "$lin_x64" \
> /tmp/rdbms-playground.rb
grep -q '^class RdbmsPlayground < Formula$' /tmp/rdbms-playground.rb \
|| { echo "ERROR: rendered formula looks malformed" >&2; exit 1; }
work=$(mktemp -d)
echo "homebrew: cloning lazyeval/homebrew-tap"
git clone --depth 1 "https://lazyeval-ci:${PKG_TOKEN}@git.lazyeval.net/lazyeval/homebrew-tap.git" "$work"
mkdir -p "$work/Formula"
cp /tmp/rdbms-playground.rb "$work/Formula/rdbms-playground.rb"
cd "$work"
git config user.name "lazyeval-ci"
git config user.email "ci@lazyeval.net"
git add Formula/rdbms-playground.rb
if git diff --cached --quiet; then
echo "homebrew: formula already at $VER — nothing to commit."
exit 0
fi
git commit -m "rdbms-playground $VER"
# Push to main explicitly: a freshly-created (empty) repo clone may put
# the first commit on a differently-named local branch. Assumes the
# bucket/tap default branch is `main` (Gitea's default for new repos).
git push origin HEAD:main
echo "homebrew: tap updated to rdbms-playground $VER."
# winget remains a future sibling job here (komac on Linux CI, or a manual PR
# to microsoft/winget-pkgs). No `needs:` between jobs — each is independent and
# idempotent, so one failing or being added never breaks another.