diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 409703b..c3f374e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,16 +18,43 @@ jobs: - name: Test run: rite test + lint: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - uses: jdx/mise-action@v2 + - name: Lint run: rite lint + # "Does it build": render the formula against a tarball of HEAD, brew + # install it, run the formula test block, then a real `macprefs backup` + # end-to-end on the runner. + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - uses: jdx/mise-action@v2 + + - name: Build, install, and smoke test the brew package + run: rite build + # Verifies the documented contributor workflow from a bare machine, # exactly as the README's "Getting started" section describes it: # curl-install mise -> mise install -> rite setup -> rite test lint. - # The `test` job above uses mise-action, which is faster but not the - # path a fresh contributor actually follows. + # The jobs above use mise-action, which is faster but not the path a + # fresh contributor actually follows. contrib-workflow: runs-on: macos-latest + # mise resolves github:clintmod/rite via the GitHub API; unauthenticated + # requests share the runner IP's 60/hr quota and 403 constantly. The + # other jobs get a token injected by mise-action; this one bootstraps + # bare, so pass the workflow token explicitly. (Locally, mise picks up + # your gh CLI auth instead.) + env: + GITHUB_TOKEN: ${{ github.token }} steps: - uses: actions/checkout@v4 diff --git a/Ritefile.yml b/Ritefile.yml index 9ad6895..a6bafae 100644 --- a/Ritefile.yml +++ b/Ritefile.yml @@ -48,6 +48,12 @@ tasks: cmds: - uv run pylint *.py + build: + desc: Build the brew package from committed HEAD, install, verify, uninstall + aliases: [b] + cmds: + - ./ci/scripts/brew-build-test.sh + clean: desc: Remove generated files aliases: [c] diff --git a/ci/scripts/brew-build-test.sh b/ci/scripts/brew-build-test.sh new file mode 100755 index 0000000..576d453 --- /dev/null +++ b/ci/scripts/brew-build-test.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Build the brew package from the committed tree (git archive HEAD), render +# the formula template against the local tarball, install it, verify the +# installed CLI end-to-end with a real backup, then uninstall. +# +# Refuses to run if macprefs is already installed via brew (it would clobber +# the real install) unless FORCE=1 is set. +set -euo pipefail + +VERSION=$(python3 -c "import version; print(version.__version__.lstrip('v'))") +TMP=$(mktemp -d) +trap 'rm -rf "$TMP"' EXIT + +if brew list macprefs &>/dev/null && [ "${FORCE:-0}" != "1" ]; then + echo "macprefs is already installed via brew - refusing to clobber it." >&2 + echo "Re-run with FORCE=1 to uninstall it and test anyway." >&2 + exit 1 +fi + +TARBALL="$TMP/macprefs-$VERSION.tar.gz" +git archive --prefix="macprefs-$VERSION/" -o "$TARBALL" HEAD +SHA=$(shasum -a 256 "$TARBALL" | cut -d' ' -f1) + +sed -e "s|url \".*\"|url \"file://$TARBALL\"|" \ + -e "s|###sha256###|$SHA|" \ + macprefs.template.rb > "$TMP/macprefs.rb" + +# Homebrew requires formulae to live in a tap; use a throwaway local one. +TAP="macprefs-ci/local" +brew untap -f "$TAP" >/dev/null 2>&1 || true +brew tap-new --no-git "$TAP" >/dev/null +trap 'brew uninstall --force macprefs >/dev/null 2>&1 || true; brew untap -f "$TAP" >/dev/null 2>&1 || true; rm -rf "$TMP"' EXIT +cp "$TMP/macprefs.rb" "$(brew --repository "$TAP")/Formula/macprefs.rb" + +brew install "$TAP/macprefs" + +# Formula's own test block (runs `macprefs --help`). +brew test "$TAP/macprefs" + +INSTALLED_VERSION=$(macprefs --version) +echo "installed: $INSTALLED_VERSION" +echo "$INSTALLED_VERSION" | grep -q "$VERSION" + +# End-to-end: a real backup into a throwaway dir. Backup only reads system +# state and writes to MACPREFS_BACKUP_DIR, so this is safe anywhere. +MACPREFS_BACKUP_DIR="$TMP/backup" macprefs backup +test -d "$TMP/backup/preferences" + +echo "brew package build + install + backup smoke test: OK"