name: Build # Compile gate and accessibility audit for the GitHub mirror. builds.sr.ht is the # primary remote for this project but has no macOS images, so xcodebuild cannot # run there; this job builds and tests on a GitHub-hosted macOS runner instead. # # This runs `xcodebuild test`, which also compiles the app, the widget, and the # share extension (the DomainDig scheme's build action pulls both in as # dependencies). The test target is DomainDigUITests — an accessibility audit # suite; see DomainDigUITests/AccessibilityAuditHarness.swift. # # WHAT THIS JOB IS FOR, given the audit also runs locally: # a clean checkout of the merge result. A local hook runs against the working # tree and therefore cannot catch a file that was never committed — the failure # mode that matters most here, since DomainDig.xcodeproj is hand-edited and uses # file-system-synchronized groups where a whole missing folder still builds fine # locally. This job is the only place that check exists; sr.ht cannot run it. # # DELIBERATELY ONE JOB, NEWEST RUNTIME ONLY. Audit coverage is not nested across # OS versions, so the oldest supported OS genuinely needs its own run — but the # macos-26 image ships only iOS 26.x runtimes, so CI *cannot* provide it. Asking # for two jobs here bought two near-identical 26.x runs at double the macOS # minutes. Floor coverage lives in Scripts/audit-a11y.sh, run from a machine that # actually has an 18.x runtime installed, and is wired to the pre-push hook in # .githooks/. See Docs/ACCESSIBILITY.md for the split. # # The audit REPORTS but does not FAIL by default. It surfaces violations that # exist today, so gating on it would block every unrelated PR until the # accessibility pass in issue #21 completes. Findings land in the job log and in # the uploaded .xcresult bundle, tagged [report] or [FAIL]. Enforcement is a # committed constant: widen `AccessibilityAuditHarness.enforcedAuditTypes` as # each phase clears a category. (Env vars were tried first — neither a plain # xcodebuild env var nor a TEST_RUNNER_-prefixed build setting reaches the UI # test process.) # # pull_request only, plus manual dispatch. GitHub builds the merge result (PR # merged into main), so a green PR validates exactly what will land on main. # Note: this repo currently also pushes directly to main for releases, and those # pushes are NOT gated here — add a `push: { branches: [main] }` trigger below if # you want direct-to-main commits covered too. # # paths-ignore skips prose-only changes. Both globs are single-star, so they # match the repo root and Docs/ but nothing deeper — a .md that ever lands inside # a source directory still builds. on: pull_request: paths-ignore: ['*.md', 'Docs/*.md'] workflow_dispatch: # The job only reads code; drop the default read-write GITHUB_TOKEN scope. permissions: contents: read concurrency: group: build-${{ github.ref }} cancel-in-progress: true jobs: test: name: xcodebuild test # macos-latest still points at macOS 15, which lacks the iOS 26+ SDK this app # is built against. runs-on: macos-26 steps: - uses: actions/checkout@v7 - name: Show toolchain run: | xcodebuild -version swift --version - name: Select simulator id: sim run: | set -euo pipefail # Newest available iPhone runtime. No deployment-target filtering is # needed for "newest" — it is always at or above the floor. The # previous selector took the first iPhone from ANY runtime, which on a # machine with an older runtime installed could pick a simulator below # the deployment target, where the app cannot install. selected=$(xcrun simctl list devices available --json \ | jq -c ' [ .devices | to_entries[] | (.key | capture("SimRuntime\\.iOS-(?[0-9]+)-(?[0-9]+)$")) as $v | (($v.maj | tonumber) * 1000 + ($v.min | tonumber)) as $rank | .value[] | select(.name | startswith("iPhone")) | { rank: $rank, udid: .udid, name: .name, os: "\($v.maj).\($v.min)" } ] | sort_by(.rank, .name) | last ') if [ -z "$selected" ] || [ "$selected" = "null" ]; then echo "::error::No iPhone simulator available on this image" xcrun simctl list devices available >&2 exit 1 fi label=$(echo "$selected" | jq -r '"\(.name) (iOS \(.os))"') echo "Selected $label" echo "udid=$(echo "$selected" | jq -r .udid)" >> "$GITHUB_OUTPUT" echo "label=$label" >> "$GITHUB_OUTPUT" - name: Test on ${{ steps.sim.outputs.label }} run: | set -o pipefail xcodebuild test \ -project DomainDig.xcodeproj \ -scheme DomainDig \ -destination "id=${{ steps.sim.outputs.udid }}" \ -resultBundlePath TestResults.xcresult \ CODE_SIGNING_ALLOWED=NO - name: Upload results # Always upload: on success the bundle carries the accessibility burndown # list, which is the reason this suite exists. if: always() uses: actions/upload-artifact@v4 with: name: test-results path: TestResults.xcresult retention-days: 7