From 0220faf382064e96b217fa862cc8edc2a035efba Mon Sep 17 00:00:00 2001 From: Yusuke Morishita Date: Tue, 7 Jul 2026 06:35:52 -0700 Subject: [PATCH 1/3] Improve the CI harness - Extract the duplicated simulator-selection script from the package and example jobs into a local composite action (.github/actions/select-simulator) with the same download-and-retry behavior. - Give every job an explicit timeout so a hung simulator or runner cannot burn the full six-hour default. - Collect code coverage in the package test job and publish a per-target line-coverage table to the job summary. - Restrict the test workflow's token to contents: read; it only checks out the repository. - Add a Dependabot config that keeps the GitHub Actions up to date with a weekly grouped PR. Job names are unchanged, so the required status checks on main are unaffected. --- .github/actions/select-simulator/action.yml | 30 +++++++++++++++++ .github/dependabot.yml | 12 +++++++ .github/workflows/claude-code-review.yml | 1 + .github/workflows/documentation.yml | 1 + .github/workflows/test.yml | 36 +++++++++++++-------- 5 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 .github/actions/select-simulator/action.yml create mode 100644 .github/dependabot.yml diff --git a/.github/actions/select-simulator/action.yml b/.github/actions/select-simulator/action.yml new file mode 100644 index 0000000..dcd3735 --- /dev/null +++ b/.github/actions/select-simulator/action.yml @@ -0,0 +1,30 @@ +name: Select iOS simulator +description: > + Finds the first available iPhone simulator, downloading the iOS platform + first if none is installed, and exposes its name as the `device` output. +outputs: + device: + description: The name of the selected iPhone simulator. + value: ${{ steps.select.outputs.device }} +runs: + using: composite + steps: + - id: select + shell: bash + run: | + set -euo pipefail + find_device() { + xcrun simctl list devices available --json \ + | jq -r '[.devices | to_entries[] | select(.key|test("iOS")) | .value[] | select(.isAvailable and (.name|startswith("iPhone")))][0].name // empty' + } + DEVICE=$(find_device) + if [ -z "$DEVICE" ]; then + sudo xcodebuild -downloadPlatform iOS + DEVICE=$(find_device) + fi + if [ -z "$DEVICE" ]; then + echo "::error::No available iPhone simulator found" >&2 + exit 1 + fi + echo "device=$DEVICE" >> "$GITHUB_OUTPUT" + echo "Using: $DEVICE" diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8c676c4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# Keep the GitHub Actions used by the workflows up to date. The library has no +# package dependencies, so actions are the only ecosystem to watch. +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + actions: + patterns: + - "*" diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 805ccc5..b242674 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -12,6 +12,7 @@ jobs: claude-review: if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest + timeout-minutes: 20 permissions: contents: read pull-requests: write diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 7453743..0a6b256 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -15,6 +15,7 @@ jobs: deploy: name: Deploy to GitHub Pages runs-on: macos-26 + timeout-minutes: 30 permissions: contents: read pages: write diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1e2988c..dd40c70 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,9 @@ on: pull_request: workflow_dispatch: +permissions: + contents: read + concurrency: group: test-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} @@ -14,6 +17,7 @@ jobs: package: name: Package tests (iOS Simulator) runs-on: macos-26 + timeout-minutes: 30 steps: - uses: actions/checkout@v7 - name: Select Xcode @@ -26,13 +30,7 @@ jobs: swift --version - name: Select simulator id: sim - run: | - DEVICE=$(xcrun simctl list devices available --json \ - | jq -r '[.devices | to_entries[] | select(.key|test("iOS")) | .value[] | select(.isAvailable and (.name|startswith("iPhone")))][0].name // empty') - if [ -z "$DEVICE" ]; then sudo xcodebuild -downloadPlatform iOS; DEVICE=$(xcrun simctl list devices available --json | jq -r '[.devices|to_entries[]|select(.key|test("iOS"))|.value[]|select(.isAvailable and (.name|startswith("iPhone")))][0].name // empty'); fi - if [ -z "$DEVICE" ]; then echo "::error::No available iPhone simulator found" >&2; exit 1; fi - echo "device=$DEVICE" >> "$GITHUB_OUTPUT" - echo "Using: $DEVICE" + uses: ./.github/actions/select-simulator - name: Test run: | set -o pipefail @@ -41,11 +39,26 @@ jobs: xcodebuild test \ -scheme SwipeMenuViewController \ -destination "platform=iOS Simulator,name=${{ steps.sim.outputs.device }}" \ + -enableCodeCoverage YES \ + -resultBundlePath TestResults.xcresult \ CODE_SIGNING_ALLOWED=NO + - name: Report code coverage + run: | + set -euo pipefail + { + echo "### Code coverage" + echo "" + echo "| Target | Line coverage |" + echo "|---|---|" + # List the library targets only; the test bundle's own coverage is noise. + xcrun xccov view --report --json TestResults.xcresult \ + | jq -r '.targets[] | select(((.name | endswith(".xctest")) or (.name | endswith("Tests"))) | not) | "| \(.name) | \((.lineCoverage * 10000 | round) / 100)% |"' + } >> "$GITHUB_STEP_SUMMARY" example: name: Example app build runs-on: macos-26 + timeout-minutes: 30 steps: - uses: actions/checkout@v7 - name: Select Xcode @@ -68,13 +81,7 @@ jobs: run: xcodegen generate --spec Example/project.yml - name: Select simulator id: sim - run: | - DEVICE=$(xcrun simctl list devices available --json \ - | jq -r '[.devices | to_entries[] | select(.key|test("iOS")) | .value[] | select(.isAvailable and (.name|startswith("iPhone")))][0].name // empty') - if [ -z "$DEVICE" ]; then sudo xcodebuild -downloadPlatform iOS; DEVICE=$(xcrun simctl list devices available --json | jq -r '[.devices|to_entries[]|select(.key|test("iOS"))|.value[]|select(.isAvailable and (.name|startswith("iPhone")))][0].name // empty'); fi - if [ -z "$DEVICE" ]; then echo "::error::No available iPhone simulator found" >&2; exit 1; fi - echo "device=$DEVICE" >> "$GITHUB_OUTPUT" - echo "Using: $DEVICE" + uses: ./.github/actions/select-simulator - name: Build and test run: | set -o pipefail @@ -88,6 +95,7 @@ jobs: docc: name: Documentation build runs-on: macos-26 + timeout-minutes: 20 steps: - uses: actions/checkout@v7 - name: Select Xcode From 5a386f65cec7e0163eb263d7539981fc71e2e55e Mon Sep 17 00:00:00 2001 From: Yusuke Morishita Date: Tue, 7 Jul 2026 06:41:54 -0700 Subject: [PATCH 2/3] Keep the review workflow untouched in this branch The review action validates that its workflow file matches the default branch and skips the review otherwise, so the timeout for that job moves to its own change. --- .github/workflows/claude-code-review.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b242674..805ccc5 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -12,7 +12,6 @@ jobs: claude-review: if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest - timeout-minutes: 20 permissions: contents: read pull-requests: write From 009a0546704f5c1b4b3cb948144d981055c38ecd Mon Sep 17 00:00:00 2001 From: Yusuke Morishita Date: Tue, 7 Jul 2026 06:46:41 -0700 Subject: [PATCH 3/3] Give the simulator jobs headroom for the platform-download fallback If no iPhone simulator is preinstalled, the selection step downloads the iOS platform first, which can take a large share of a 30-minute budget on a cold runner. 45 minutes keeps the guard against hung jobs while leaving room for that rare path. --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dd40c70..a8de3ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: package: name: Package tests (iOS Simulator) runs-on: macos-26 - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v7 - name: Select Xcode @@ -58,7 +58,7 @@ jobs: example: name: Example app build runs-on: macos-26 - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v7 - name: Select Xcode