name: ci on: push: branches: [main] pull_request: jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: # fetch-depth: 0, for the changelog-monotonic step below and only # for it. That check is about a DIFF — which release headings the # merge base had — so it needs the base branch's history present, # and the default depth-1 checkout has none of it. An explicit # `git fetch origin ` would be narrower, but it has to be # right on both event types and on fork PRs, and getting it subtly # wrong degrades to a SKIP (a guard that silently stops guarding — # the exact failure this repo keeps refusing). Full history on a # pure-bash tree costs a second; the STRICT flag below turns any # remaining skip red rather than green. fetch-depth: 0 - name: shellcheck # -x follows the `source=SCRIPTDIR/...` directives into commands/lib/. # globstar so a script in a new subdirectory is linted without anyone # remembering to edit this list; bin/* covers the extensionless entrypoints. # dotglob because globs skip dot-prefixed names: without it `**/` never # descends into `.github/`, so `.github/scripts/*.sh` — release-lib.sh # among them — was swept up by nothing (#70). It also makes `**` # descend into `.git/`, which holds no tracked `.sh` on a checkout. # The file list is printed so under-coverage shows up in the log, and # the comm below turns under-coverage into a failure rather than a # thing someone has to notice: every tracked `.sh` must be in the set. run: | shopt -s globstar dotglob files=(bin/* **/*.sh) printf 'shellcheck: %s\n' "${files[@]}" uncovered=$(comm -23 <(git ls-files '*.sh' | sort) <(printf '%s\n' "${files[@]}" | sort)) if [ -n "$uncovered" ]; then printf 'tracked .sh files the glob does not lint:\n%s\n' "$uncovered" >&2 exit 1 fi shellcheck -x "${files[@]}" - name: cli tests run: bash test/cli.sh # test/labels-reconcile.sh existed here since #87 but ran nowhere: the # label state machine gates every PR on this repo and its fixtures were # green only when someone remembered to run them by hand. Same step, same # place as heavy-duty/box. - name: labels state-machine tests run: bash test/labels-reconcile.sh - name: release-flow tests run: bash test/release.sh # No SHIPPED release heading was deleted (#98). Its own step rather than # a line inside test/release.sh: that suite drives the arming rule # against constructed VERSION + CHANGELOG.md trees that are not git # repos, and this assert needs a git history — folding it in would make # those cases skip or lie. It is also a DIFFERENT invariant: arming is a # fact about this tree, monotonicity is a fact about this tree versus # its merge base. Pull requests only: on a push to main the merge base # IS HEAD, so the assert is vacuous and would only add a green step that # proves nothing. STRICT=1 so a checkout that cannot reach the base ref # fails here instead of skipping quietly forever. - name: no shipped changelog heading was deleted if: github.event_name == 'pull_request' env: CHANGELOG_MONOTONIC_STRICT: '1' run: bash .github/scripts/changelog-monotonic.sh "origin/${{ github.base_ref }}" # Kept SEPARATE from `check` on purpose: this job pulls a Postgres image and # stands up throwaway containers, and a slow image pull must never delay the # fast shellcheck + cli.sh feedback above. ubuntu-latest ships Docker running # and passwordless sudo, so test/db-integration.sh EXECUTES here (it only # skips where Docker is absent). It is the automated proof that dump/restore # actually round-trips, not just that the args parse. db-integration: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: db dump/restore round-trip run: bash test/db-integration.sh