From 7c978aa86b5c0f62cf7b82ee1b34da5489e319bd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 11:59:45 +0000 Subject: [PATCH 1/5] ci: pin every version CI depends on CI was breaking on changes nobody made here: an upstream MMKVCore release, a new Xcode on the macOS image, a floating action tag. Pin the whole toolchain so a green run stays reproducible. Workflows: - runs-on macos-26 / ubuntu-24.04 instead of the *-latest aliases, which follow new OS releases and swap Xcode, simulator runtimes and CocoaPods underneath an unchanged build - third-party actions pinned to full commit SHAs instead of floating major tags, with the version in a trailing comment - move shared toolchain setup into .github/actions/setup so all pinned versions live in one file: Bun 1.3.14, Node 24.18.0 (macOS) / 22.23.2 (Ubuntu), Temurin 21.0.11+10, Xcode 26.6, CocoaPods 1.17.0, Maestro 2.8.0 - select Xcode explicitly rather than inheriting the image default, and fail with the list of installed versions if the pinned one is gone - force the pinned CocoaPods through a PATH shim; EAS resolves `pod` from PATH, so pinning the gem alone loses to a newer preinstalled version - fetch Maestro as an immutable release asset instead of piping get.maestro.mobile.dev, which installs whatever is newest - pin the iOS simulator runtime (26.5) as well as the device model Dependencies: - exact versions everywhere, no carets or tildes. bun ci is already frozen, but ranges still let a plain `bun install` drift, and they silently break patchedDependencies: react-native-mmkv floating to 3.3.4 would drop the app-group path patch with nothing failing until a widget misbehaved - turbo: latest -> 2.7.1 (the version the lockfile already resolved) - pin EAS CLI to 22.0.0 in the mobile scripts and eas.json - bun.lock workspace ranges updated to match; no resolutions changed Adds .github/README.md documenting where each pin lives, how to bump one, and what still can't be pinned. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QRXxEuKNzgWvK2RxJjQ8TC --- .github/README.md | 79 +++++++++++++++++++ .github/actions/setup/action.yml | 114 +++++++++++++++++++++++++++ .github/workflows/build-android.yml | 13 +-- .github/workflows/build-ios.yml | 8 +- .github/workflows/lint.yml | 11 +-- .github/workflows/test-android.yml | 13 +-- .github/workflows/test-ios.yml | 26 +++--- .github/workflows/test-web.yml | 12 +-- .github/workflows/typecheck.yml | 8 +- apps/mobile/eas.json | 2 +- apps/mobile/package.json | 96 +++++++++++----------- apps/web/package.json | 28 +++---- bun.lock | 118 ++++++++++++++-------------- package.json | 8 +- 14 files changed, 361 insertions(+), 175 deletions(-) create mode 100644 .github/README.md create mode 100644 .github/actions/setup/action.yml diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..7267d6b --- /dev/null +++ b/.github/README.md @@ -0,0 +1,79 @@ +# CI version pinning + +Everything CI touches is pinned to an exact version. The goal is blunt: a run +that is green today should still be green in a year with no changes to this +repo. If a build breaks, it should be because someone changed the code — not +because an upstream release happened. + +Nothing in CI may use `latest`, a floating major tag (`@v4`), a caret/tilde +range, or "whatever the runner image ships". + +## Where the pins live + +| What | Pinned to | File | +| --- | --- | --- | +| Runner image | `macos-26`, `ubuntu-24.04` | `.github/workflows/*.yml` | +| Third-party actions | full commit SHA (`# vX.Y.Z` comment) | `.github/workflows/*.yml` | +| Bun | `1.3.14` | `.github/actions/setup/action.yml`, `packageManager` in `package.json` | +| Node.js | `24.18.0` (macOS), `22.23.2` (Ubuntu) | `.github/actions/setup/action.yml` | +| Temurin JDK | `21.0.11+10` | `.github/actions/setup/action.yml` | +| Xcode | `26.6` | `.github/actions/setup/action.yml` | +| CocoaPods | `1.17.0` | `.github/actions/setup/action.yml` | +| Maestro | `2.8.0` | `.github/actions/setup/action.yml` | +| iOS simulator | `iPhone 17 Pro`, iOS `26.5` | `.github/workflows/test-ios.yml` | +| EAS CLI | `22.0.0` | `apps/mobile/package.json` scripts, `apps/mobile/eas.json` | +| npm dependencies | exact versions, no ranges | `package.json`, `apps/*/package.json` | +| Transitive npm deps | resolved tree | `bun.lock` (CI runs `bun ci`, which is frozen-lockfile) | +| Native iOS pods | `MMKVCore` / `MMKVAppExtension` `2.4.0` | `apps/mobile/plugins/withAddPodDepsToTargets.ts` | +| `react-native-mmkv` patch | `3.3.3` | `apps/mobile/patches/`, `patchedDependencies` | + +Most of the toolchain lives in one file, `.github/actions/setup/action.yml`, so +a bump is a one-line edit there and CI proves it. + +## Why exact npm versions, when `bun.lock` already exists + +`bun ci` is frozen, so CI installs exactly what the lockfile says. Ranges still +bite in two places: + +- Anyone running plain `bun install` locally silently drifts off the lockfile, + and then commits the drift. +- `patchedDependencies` keys are exact (`react-native-mmkv@3.3.3`). If the + range floats to `3.3.4`, the patch stops applying **silently** — the app-group + MMKV path fix just disappears, and nothing fails until a widget misbehaves. + +Exact versions make both impossible. + +## Why the CocoaPods shim + +`pod` is resolved from `PATH` by the EAS build, so `gem install cocoapods -v X` +is not enough — a newer CocoaPods preinstalled on a future runner image would +still win. The setup action writes a small `pod` shim that always calls the +pinned gem version explicitly. + +## Bumping something + +1. Change the version in the one place listed above. +2. Open a PR and let CI run. +3. If it's green, merge. If not, you found out on your terms rather than on a + random Tuesday. + +For npm dependencies, bump the exact version in the relevant `package.json`, +run `bun install` to refresh `bun.lock`, and commit both. + +## What still isn't fully pinnable + +Worth knowing about, because these can still break a build that never changed: + +- **Runner images are eventually retired.** `macos-26` and `ubuntu-24.04` will + be removed by GitHub in time. When that happens, the job fails immediately + and loudly with an unknown-label error rather than drifting — that's the + point. Move to the next image and re-pin Xcode, Node and CocoaPods to + whatever it ships. +- **GitHub retires action runtimes.** Actions declaring `node20` (such as + `futureware-tech/simulator-action`) will eventually be forced onto a newer + Node by the runner regardless of the SHA pin. +- **The Android SDK components** Gradle downloads during a build come from the + versions the generated `android/` project requests, which are set by the + pinned Expo/React Native versions. +- **EAS Build's server-side behaviour.** `eas build --local` still talks to + Expo's servers for credentials and project config. diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..029c3a0 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,114 @@ +name: Setup +description: Installs the pinned toolchain that every Scheduli CI job builds on. + +# --------------------------------------------------------------------------- +# This file is the single source of truth for CI tool versions. +# +# Everything here is pinned to an exact version on purpose: a green CI run +# today should still be green in a year without touching the repo. Nothing may +# resolve to "latest", a floating tag, or "whatever the runner image ships". +# +# The values below were captured from the GitHub-hosted `macos-26` and +# `ubuntu-24.04` images (image version 20260728.0273.1), so pinning them is a +# no-op today and a guarantee later. +# +# To upgrade a tool, change it here, in this file only, and let CI prove it. +# --------------------------------------------------------------------------- + +inputs: + java: + description: Install the pinned Temurin JDK (Android builds, and Maestro's runtime). + required: false + default: "false" + xcode: + description: Select the pinned Xcode and force the pinned CocoaPods (macOS only). + required: false + default: "false" + maestro: + description: Install the pinned Maestro CLI. + required: false + default: "false" + +runs: + using: composite + steps: + - name: Install Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.3.14 # keep in sync with "packageManager" in package.json + + # Node is pinned per-OS to the version each runner image ships today, so + # this freezes current behaviour instead of changing it. Both are already + # in the runner tool cache, so setup-node is a fast no-op. + - name: Install Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + with: + node-version: ${{ runner.os == 'macOS' && '24.18.0' || '22.23.2' }} + + - name: Install Java + if: inputs.java == 'true' + uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1 + with: + distribution: temurin + java-version: 21.0.11+10 + + - name: Select Xcode + if: inputs.xcode == 'true' + shell: bash + run: | + set -euo pipefail + xcode_app="/Applications/Xcode_${XCODE_VERSION}.app" + if [ ! -d "$xcode_app" ]; then + echo "::error::Xcode ${XCODE_VERSION} is not installed on this runner image." + echo "Available Xcode versions:" + ls -d /Applications/Xcode*.app + echo "Pick one of the above and update XCODE_VERSION in .github/actions/setup/action.yml." + exit 1 + fi + sudo xcode-select --switch "$xcode_app" + xcodebuild -version + env: + XCODE_VERSION: "26.6" + + # `pod` is resolved from PATH by the EAS build, so pinning the gem is not + # enough: a newer CocoaPods preinstalled on a future runner image would + # still win. A shim that always calls the exact gem version removes that. + - name: Pin CocoaPods + if: inputs.xcode == 'true' + shell: bash + run: | + set -euo pipefail + if ! gem list -i -v "$COCOAPODS_VERSION" cocoapods > /dev/null 2>&1; then + gem install cocoapods -v "$COCOAPODS_VERSION" --no-document \ + || sudo gem install cocoapods -v "$COCOAPODS_VERSION" --no-document + fi + real_pod="$(command -v pod)" + shim_dir="$RUNNER_TEMP/pinned-bin" + mkdir -p "$shim_dir" + printf '#!/usr/bin/env bash\nexec "%s" _%s_ "$@"\n' "$real_pod" "$COCOAPODS_VERSION" > "$shim_dir/pod" + chmod +x "$shim_dir/pod" + echo "$shim_dir" >> "$GITHUB_PATH" + "$shim_dir/pod" --version + env: + COCOAPODS_VERSION: "1.17.0" + + # Deliberately not `curl https://get.maestro.mobile.dev | bash`: that + # installs whatever the newest release happens to be, and pipes a script + # that can change under us. This fetches one immutable release asset. + - name: Install Maestro + if: inputs.maestro == 'true' + shell: bash + run: | + set -euo pipefail + download_dir="$RUNNER_TEMP/maestro-download" + mkdir -p "$download_dir" + curl -fsSL --retry 3 --retry-all-errors \ + -o "$download_dir/maestro.zip" \ + "https://github.com/mobile-dev-inc/maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip" + unzip -q "$download_dir/maestro.zip" -d "$download_dir" + mkdir -p "$HOME/.maestro" + cp -rf "$download_dir/maestro/." "$HOME/.maestro/" + echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" + "$HOME/.maestro/bin/maestro" --version + env: + MAESTRO_VERSION: "2.8.0" diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 2cbb658..ed0c240 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -8,18 +8,13 @@ on: jobs: build: - runs-on: macos-latest + runs-on: macos-26 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - uses: actions/setup-java@v4 + - uses: ./.github/actions/setup with: - distribution: "temurin" - java-version: "21" - - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest + java: true - name: Install dependencies run: bun ci diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 4b65aa0..fd232c7 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -8,13 +8,13 @@ on: jobs: build: - runs-on: macos-latest + runs-on: macos-26 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - uses: oven-sh/setup-bun@v2 + - uses: ./.github/actions/setup with: - bun-version: latest + xcode: true - name: Install dependencies run: bun ci diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d1f6177..fdc2a74 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,13 +6,14 @@ on: jobs: lint: - runs-on: ubuntu-latest + # Pinned image, not `ubuntu-latest`: the `latest` alias moves to a new + # Ubuntu release without warning. Tool versions come from + # .github/actions/setup. + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest + - uses: ./.github/actions/setup - name: Install dependencies run: bun ci diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index 7db2c32..a850c20 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -6,18 +6,13 @@ on: jobs: build: - runs-on: macos-latest + runs-on: macos-26 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - uses: actions/setup-java@v4 + - uses: ./.github/actions/setup with: - distribution: "temurin" - java-version: "21" - - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest + java: true - name: Install dependencies run: bun ci diff --git a/.github/workflows/test-ios.yml b/.github/workflows/test-ios.yml index d40b286..0918fdc 100644 --- a/.github/workflows/test-ios.yml +++ b/.github/workflows/test-ios.yml @@ -6,23 +6,23 @@ on: jobs: test: - runs-on: macos-latest + # Pinned image, not `macos-latest`: that alias follows each new macOS + # release, which swaps the default Xcode, the simulator runtimes and the + # preinstalled CocoaPods underneath a build that never changed. + runs-on: macos-26 timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - uses: oven-sh/setup-bun@v2 + - uses: ./.github/actions/setup with: - bun-version: latest + java: true # Maestro runs on the JVM + xcode: true + maestro: true - name: Install dependencies run: bun ci - - name: Install Maestro - run: | - curl -fsSL "https://get.maestro.mobile.dev" | bash - echo "$HOME/.maestro/bin" >> $GITHUB_PATH - - name: Build iOS Simulator App env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} @@ -30,9 +30,13 @@ jobs: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} run: bunx turbo run build:ios:simulator - - uses: futureware-tech/simulator-action@v4 + # `os` is pinned as well as `model`: without it the action boots whatever + # the newest installed runtime happens to be. + - uses: futureware-tech/simulator-action@dab10d813144ef59b48d401cd95da151222ef8cd # v4 with: model: "iPhone 17 Pro" + os: "iOS" + os_version: "26.5" wait_for_boot: true - name: Install iOS App @@ -54,7 +58,7 @@ jobs: - name: Upload Debug Information if: failure() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: debug-info path: ./debug-info/ diff --git a/.github/workflows/test-web.yml b/.github/workflows/test-web.yml index 72de45a..6a40979 100644 --- a/.github/workflows/test-web.yml +++ b/.github/workflows/test-web.yml @@ -6,25 +6,25 @@ on: jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest + - uses: ./.github/actions/setup - name: Install dependencies run: bun ci + # The browser build is chosen by the exact @playwright/test version in + # apps/web/package.json, so this is pinned by the lockfile. - name: Install Playwright Browsers run: bun run --filter @scheduli/web test:setup - name: Run Playwright tests run: bun run --filter @scheduli/web test - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: failure() with: name: playwright-report diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 6725922..fe2c483 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -6,13 +6,11 @@ on: jobs: typecheck: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest + - uses: ./.github/actions/setup - name: Install dependencies run: bun ci diff --git a/apps/mobile/eas.json b/apps/mobile/eas.json index faf00aa..675fbfd 100644 --- a/apps/mobile/eas.json +++ b/apps/mobile/eas.json @@ -1,6 +1,6 @@ { "cli": { - "version": ">= 5.2.0", + "version": "22.0.0", "promptToConfigurePushNotifications": false, "appVersionSource": "remote" }, diff --git a/apps/mobile/package.json b/apps/mobile/package.json index b6e511e..7ae56c0 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -4,77 +4,77 @@ "private": true, "main": "expo-router/entry", "scripts": { - "build:ios": "bunx eas-cli build --profile production --local --platform ios --non-interactive --output dist/app.ipa", - "submit:ios": "bunx eas-cli submit --platform ios --path dist/app.ipa --non-interactive", - "build:android": "bunx eas-cli build --profile production --local --platform android --non-interactive --output dist/app.aab", - "submit:android": "bunx eas-cli submit --platform android --path dist/app.aab --non-interactive", + "build:ios": "bunx eas-cli@22.0.0 build --profile production --local --platform ios --non-interactive --output dist/app.ipa", + "submit:ios": "bunx eas-cli@22.0.0 submit --platform ios --path dist/app.ipa --non-interactive", + "build:android": "bunx eas-cli@22.0.0 build --profile production --local --platform android --non-interactive --output dist/app.aab", + "submit:android": "bunx eas-cli@22.0.0 submit --platform android --path dist/app.aab --non-interactive", "clean": "git clean -xdf .cache .expo .turbo android ios node_modules dist", "dev": "expo start", "android": "expo run:android", "ios": "expo run:ios", "test": "./scripts/maestro-test.sh", - "env:dev": "bunx eas-cli env:pull --environment development", - "build:ios:simulator": "bunx eas-cli build --platform ios --profile e2e-test --local --non-interactive --output dist/app.tar.gz && tar -xvf dist/app.tar.gz -C dist/", - "build:android:simulator": "bunx eas-cli build --platform android --profile e2e-test --local --non-interactive --output dist/app.apk", + "env:dev": "bunx eas-cli@22.0.0 env:pull --environment development", + "build:ios:simulator": "bunx eas-cli@22.0.0 build --platform ios --profile e2e-test --local --non-interactive --output dist/app.tar.gz && tar -xvf dist/app.tar.gz -C dist/", + "build:android:simulator": "bunx eas-cli@22.0.0 build --platform android --profile e2e-test --local --non-interactive --output dist/app.apk", "install:ios:simulator": "xcrun simctl install booted dist/Scheduli.app", "typecheck": "tsc --noEmit" }, "dependencies": { - "@bacons/apple-targets": "^3.0.5", - "@expo-google-fonts/poppins": "^0.4.1", - "@expo/config-plugins": "^54.0.4", - "@expo/metro-runtime": "~6.1.2", - "@fortawesome/fontawesome-svg-core": "^6.7.2", - "@fortawesome/free-solid-svg-icons": "^6.7.2", - "@fortawesome/react-native-fontawesome": "^0.3.2", - "@gorhom/bottom-sheet": "^5.2.6", - "@notifee/react-native": "^9.1.8", + "@bacons/apple-targets": "3.0.6", + "@expo-google-fonts/poppins": "0.4.1", + "@expo/config-plugins": "54.0.4", + "@expo/metro-runtime": "6.1.2", + "@fortawesome/fontawesome-svg-core": "6.7.2", + "@fortawesome/free-solid-svg-icons": "6.7.2", + "@fortawesome/react-native-fontawesome": "0.3.2", + "@gorhom/bottom-sheet": "5.2.8", + "@notifee/react-native": "9.1.8", "@react-native-community/datetimepicker": "8.4.4", "@react-native-picker/picker": "2.11.1", - "@rnrepo/expo-config-plugin": "^0.1.0-beta.0", + "@rnrepo/expo-config-plugin": "0.1.0-beta.1", "@scheduli/types": "workspace:*", - "babel-preset-expo": "~54.0.9", - "expo": "~54.0.30", - "expo-application": "~7.0.7", - "expo-constants": "~18.0.12", - "expo-dev-client": "~6.0.17", - "expo-device": "~8.0.9", - "expo-file-system": "~19.0.21", - "expo-font": "~14.0.9", - "expo-haptics": "~15.0.7", - "expo-linking": "~8.0.11", - "expo-localization": "~17.0.7", - "expo-modules-core": "^3.0.29", - "expo-router": "~6.0.21", - "expo-splash-screen": "~31.0.13", - "expo-status-bar": "~3.0.8", - "nativewind": "^2.0.11", - "posthog-react-native": "^4.10.4", - "posthog-react-native-session-replay": "^1.2.1", + "babel-preset-expo": "54.0.9", + "expo": "54.0.30", + "expo-application": "7.0.8", + "expo-constants": "18.0.12", + "expo-dev-client": "6.0.20", + "expo-device": "8.0.10", + "expo-file-system": "19.0.21", + "expo-font": "14.0.10", + "expo-haptics": "15.0.8", + "expo-linking": "8.0.11", + "expo-localization": "17.0.8", + "expo-modules-core": "3.0.29", + "expo-router": "6.0.21", + "expo-splash-screen": "31.0.13", + "expo-status-bar": "3.0.9", + "nativewind": "2.0.11", + "posthog-react-native": "4.16.2", + "posthog-react-native-session-replay": "1.2.3", "react": "catalog:", "react-dom": "catalog:", "react-native": "0.81.5", - "react-native-android-widget": "^0.17.2", - "react-native-gesture-handler": "~2.28.0", - "react-native-mmkv": "^3.3.3", - "react-native-modal-datetime-picker": "^18.0.0", - "react-native-paper": "^5.14.5", - "react-native-reanimated": "~4.1.3", + "react-native-android-widget": "0.17.2", + "react-native-gesture-handler": "2.28.0", + "react-native-mmkv": "3.3.3", + "react-native-modal-datetime-picker": "18.0.0", + "react-native-paper": "5.14.5", + "react-native-reanimated": "4.1.6", "react-native-safe-area-context": "5.6.2", "react-native-screens": "4.16.0", - "react-native-store-version": "^1.4.1", + "react-native-store-version": "1.4.1", "react-native-svg": "15.12.1", - "react-native-web": "~0.21.1", + "react-native-web": "0.21.2", "react-native-worklets": "0.5.1", - "typescript": "~5.9.3" + "typescript": "5.9.3" }, "devDependencies": { - "@babel/core": "^7.28.4", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@types/node": "^25.0.8", + "@babel/core": "7.28.5", + "@babel/plugin-proposal-export-namespace-from": "7.18.9", + "@types/node": "25.0.8", "@types/react": "catalog:", "tailwindcss": "3.3.2", - "tsx": "^4.21.0" + "tsx": "4.21.0" }, "patchedDependencies": { "react-native-mmkv@3.3.3": "apps/mobile/patches/react-native-mmkv@3.3.3.patch" diff --git a/apps/web/package.json b/apps/web/package.json index a4443b6..9a28318 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,27 +11,27 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@formkit/auto-animate": "^0.8.1", - "@fortawesome/fontawesome-svg-core": "^6.4.2", - "@fortawesome/free-brands-svg-icons": "^6.4.2", - "@fortawesome/free-solid-svg-icons": "^6.4.2", - "@fortawesome/react-fontawesome": "^0.2.0", - "@headlessui/react": "^1.7.17", - "@headlessui/tailwindcss": "^0.2.0", + "@formkit/auto-animate": "0.8.4", + "@fortawesome/fontawesome-svg-core": "6.7.2", + "@fortawesome/free-brands-svg-icons": "6.7.2", + "@fortawesome/free-solid-svg-icons": "6.7.2", + "@fortawesome/react-fontawesome": "0.2.6", + "@headlessui/react": "1.7.19", + "@headlessui/tailwindcss": "0.2.2", "@scheduli/types": "workspace:*", "next": "16.0.10", - "posthog-js": "^1.276.0", - "posthog-node": "^5.10.0", + "posthog-js": "1.309.1", + "posthog-node": "5.17.4", "react": "catalog:", "react-dom": "catalog:" }, "devDependencies": { - "@playwright/test": "^1.57.0", - "@tailwindcss/postcss": "^4", - "@types/node": "^20", + "@playwright/test": "1.57.0", + "@tailwindcss/postcss": "4.1.18", + "@types/node": "20.19.27", "@types/react": "catalog:", "@types/react-dom": "catalog:", - "tailwindcss": "^4", - "typescript": "^5" + "tailwindcss": "4.1.18", + "typescript": "5.9.3" } } diff --git a/bun.lock b/bun.lock index e2447f7..3be01ed 100644 --- a/bun.lock +++ b/bun.lock @@ -5,97 +5,97 @@ "": { "name": "scheduli", "devDependencies": { - "oxfmt": "^0.35.0", - "oxlint": "^1.50.0", - "turbo": "latest", + "oxfmt": "0.35.0", + "oxlint": "1.50.0", + "turbo": "2.7.1", }, }, "apps/mobile": { "name": "@scheduli/mobile", - "version": "1.9.8", + "version": "1.10.0", "dependencies": { - "@bacons/apple-targets": "^3.0.5", - "@expo-google-fonts/poppins": "^0.4.1", - "@expo/config-plugins": "^54.0.4", - "@expo/metro-runtime": "~6.1.2", - "@fortawesome/fontawesome-svg-core": "^6.7.2", - "@fortawesome/free-solid-svg-icons": "^6.7.2", - "@fortawesome/react-native-fontawesome": "^0.3.2", - "@gorhom/bottom-sheet": "^5.2.6", - "@notifee/react-native": "^9.1.8", + "@bacons/apple-targets": "3.0.6", + "@expo-google-fonts/poppins": "0.4.1", + "@expo/config-plugins": "54.0.4", + "@expo/metro-runtime": "6.1.2", + "@fortawesome/fontawesome-svg-core": "6.7.2", + "@fortawesome/free-solid-svg-icons": "6.7.2", + "@fortawesome/react-native-fontawesome": "0.3.2", + "@gorhom/bottom-sheet": "5.2.8", + "@notifee/react-native": "9.1.8", "@react-native-community/datetimepicker": "8.4.4", "@react-native-picker/picker": "2.11.1", - "@rnrepo/expo-config-plugin": "^0.1.0-beta.0", + "@rnrepo/expo-config-plugin": "0.1.0-beta.1", "@scheduli/types": "workspace:*", - "babel-preset-expo": "~54.0.9", - "expo": "~54.0.30", - "expo-application": "~7.0.7", - "expo-constants": "~18.0.12", - "expo-dev-client": "~6.0.17", - "expo-device": "~8.0.9", - "expo-file-system": "~19.0.21", - "expo-font": "~14.0.9", - "expo-haptics": "~15.0.7", - "expo-linking": "~8.0.11", - "expo-localization": "~17.0.7", - "expo-modules-core": "^3.0.29", - "expo-router": "~6.0.21", - "expo-splash-screen": "~31.0.13", - "expo-status-bar": "~3.0.8", - "nativewind": "^2.0.11", - "posthog-react-native": "^4.10.4", - "posthog-react-native-session-replay": "^1.2.1", + "babel-preset-expo": "54.0.9", + "expo": "54.0.30", + "expo-application": "7.0.8", + "expo-constants": "18.0.12", + "expo-dev-client": "6.0.20", + "expo-device": "8.0.10", + "expo-file-system": "19.0.21", + "expo-font": "14.0.10", + "expo-haptics": "15.0.8", + "expo-linking": "8.0.11", + "expo-localization": "17.0.8", + "expo-modules-core": "3.0.29", + "expo-router": "6.0.21", + "expo-splash-screen": "31.0.13", + "expo-status-bar": "3.0.9", + "nativewind": "2.0.11", + "posthog-react-native": "4.16.2", + "posthog-react-native-session-replay": "1.2.3", "react": "catalog:", "react-dom": "catalog:", "react-native": "0.81.5", - "react-native-android-widget": "^0.17.2", - "react-native-gesture-handler": "~2.28.0", - "react-native-mmkv": "^3.3.3", - "react-native-modal-datetime-picker": "^18.0.0", - "react-native-paper": "^5.14.5", - "react-native-reanimated": "~4.1.3", + "react-native-android-widget": "0.17.2", + "react-native-gesture-handler": "2.28.0", + "react-native-mmkv": "3.3.3", + "react-native-modal-datetime-picker": "18.0.0", + "react-native-paper": "5.14.5", + "react-native-reanimated": "4.1.6", "react-native-safe-area-context": "5.6.2", "react-native-screens": "4.16.0", - "react-native-store-version": "^1.4.1", + "react-native-store-version": "1.4.1", "react-native-svg": "15.12.1", - "react-native-web": "~0.21.1", + "react-native-web": "0.21.2", "react-native-worklets": "0.5.1", - "typescript": "~5.9.3", + "typescript": "5.9.3", }, "devDependencies": { - "@babel/core": "^7.28.4", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@types/node": "^25.0.8", + "@babel/core": "7.28.5", + "@babel/plugin-proposal-export-namespace-from": "7.18.9", + "@types/node": "25.0.8", "@types/react": "catalog:", "tailwindcss": "3.3.2", - "tsx": "^4.21.0", + "tsx": "4.21.0", }, }, "apps/web": { "name": "@scheduli/web", "dependencies": { - "@formkit/auto-animate": "^0.8.1", - "@fortawesome/fontawesome-svg-core": "^6.4.2", - "@fortawesome/free-brands-svg-icons": "^6.4.2", - "@fortawesome/free-solid-svg-icons": "^6.4.2", - "@fortawesome/react-fontawesome": "^0.2.0", - "@headlessui/react": "^1.7.17", - "@headlessui/tailwindcss": "^0.2.0", + "@formkit/auto-animate": "0.8.4", + "@fortawesome/fontawesome-svg-core": "6.7.2", + "@fortawesome/free-brands-svg-icons": "6.7.2", + "@fortawesome/free-solid-svg-icons": "6.7.2", + "@fortawesome/react-fontawesome": "0.2.6", + "@headlessui/react": "1.7.19", + "@headlessui/tailwindcss": "0.2.2", "@scheduli/types": "workspace:*", "next": "16.0.10", - "posthog-js": "^1.276.0", - "posthog-node": "^5.10.0", + "posthog-js": "1.309.1", + "posthog-node": "5.17.4", "react": "catalog:", "react-dom": "catalog:", }, "devDependencies": { - "@playwright/test": "^1.57.0", - "@tailwindcss/postcss": "^4", - "@types/node": "^20", + "@playwright/test": "1.57.0", + "@tailwindcss/postcss": "4.1.18", + "@types/node": "20.19.27", "@types/react": "catalog:", "@types/react-dom": "catalog:", - "tailwindcss": "^4", - "typescript": "^5", + "tailwindcss": "4.1.18", + "typescript": "5.9.3", }, }, "packages/types": { diff --git a/package.json b/package.json index f8a0b90..7dc2770 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,9 @@ "clean": "git clean -dfx" }, "devDependencies": { - "oxfmt": "^0.35.0", - "oxlint": "^1.50.0", - "turbo": "latest" + "oxfmt": "0.35.0", + "oxlint": "1.50.0", + "turbo": "2.7.1" }, - "packageManager": "bun@1.3.4" + "packageManager": "bun@1.3.14" } From 72175278caae00d7072118b88dc45247419aae42 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 12:12:35 +0000 Subject: [PATCH 2/5] ci: pin actions by version tag and inline setup into each workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use exact patch tags (@v6.1.0) instead of commit SHAs. A tag can be force-moved by the maintainer where a SHA cannot, but exact tags are far easier to read and bump and still close the floating-major hole that broke CI in the first place. futureware-tech/simulator-action only publishes major tags, so @v4 stays as the most specific pin available. Drop the .github/actions/setup composite and inline its steps, keeping the action list to the five already in use. Each workflow now declares its own tool versions in a top-level env: block. This also drops actions/setup-node, so Node is no longer pinned explicitly. Pinning the runner image label pins it in practice — the major version can't change within a given image — but patch bumps land. Recorded in .github/README.md alongside the other things that can't be fully pinned. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QRXxEuKNzgWvK2RxJjQ8TC --- .github/README.md | 47 ++++++++---- .github/actions/setup/action.yml | 114 ---------------------------- .github/workflows/build-android.yml | 16 +++- .github/workflows/build-ios.yml | 44 ++++++++++- .github/workflows/lint.yml | 10 ++- .github/workflows/test-android.yml | 16 +++- .github/workflows/test-ios.yml | 79 ++++++++++++++++--- .github/workflows/test-web.yml | 8 +- .github/workflows/typecheck.yml | 6 +- 9 files changed, 185 insertions(+), 155 deletions(-) delete mode 100644 .github/actions/setup/action.yml diff --git a/.github/README.md b/.github/README.md index 7267d6b..20c6d63 100644 --- a/.github/README.md +++ b/.github/README.md @@ -13,13 +13,12 @@ range, or "whatever the runner image ships". | What | Pinned to | File | | --- | --- | --- | | Runner image | `macos-26`, `ubuntu-24.04` | `.github/workflows/*.yml` | -| Third-party actions | full commit SHA (`# vX.Y.Z` comment) | `.github/workflows/*.yml` | -| Bun | `1.3.14` | `.github/actions/setup/action.yml`, `packageManager` in `package.json` | -| Node.js | `24.18.0` (macOS), `22.23.2` (Ubuntu) | `.github/actions/setup/action.yml` | -| Temurin JDK | `21.0.11+10` | `.github/actions/setup/action.yml` | -| Xcode | `26.6` | `.github/actions/setup/action.yml` | -| CocoaPods | `1.17.0` | `.github/actions/setup/action.yml` | -| Maestro | `2.8.0` | `.github/actions/setup/action.yml` | +| Actions | exact patch tag (`@v6.1.0`) | `.github/workflows/*.yml` | +| Bun | `1.3.14` | `env:` block in each workflow, `packageManager` in `package.json` | +| Temurin JDK | `21.0.11+10` | `env:` block in each workflow | +| Xcode | `26.6` | `env:` block in the iOS workflows | +| CocoaPods | `1.17.0` | `env:` block in the iOS workflows | +| Maestro | `2.8.0` | `env:` block in `test-ios.yml` | | iOS simulator | `iPhone 17 Pro`, iOS `26.5` | `.github/workflows/test-ios.yml` | | EAS CLI | `22.0.0` | `apps/mobile/package.json` scripts, `apps/mobile/eas.json` | | npm dependencies | exact versions, no ranges | `package.json`, `apps/*/package.json` | @@ -27,8 +26,23 @@ range, or "whatever the runner image ships". | Native iOS pods | `MMKVCore` / `MMKVAppExtension` `2.4.0` | `apps/mobile/plugins/withAddPodDepsToTargets.ts` | | `react-native-mmkv` patch | `3.3.3` | `apps/mobile/patches/`, `patchedDependencies` | -Most of the toolchain lives in one file, `.github/actions/setup/action.yml`, so -a bump is a one-line edit there and CI proves it. +Each workflow declares its own versions in a top-level `env:` block, so a +workflow is readable on its own. When bumping something shared — Bun, the JDK, +Xcode — grep for it: `rg BUN_VERSION .github/workflows`. + +## Why exact patch tags for actions, not `@v6` + +`@v6` is a moving target: the maintainer repoints it at every 6.x release, so +the same workflow silently runs different code over time. `@v6.1.0` names one +release. + +A tag is not quite as strong as a commit SHA — a maintainer *can* force-move a +release tag, and a SHA can't be moved at all. Exact tags are the deliberate +trade here: they're far easier to read and bump, and they still close the +floating-major hole that actually bit us. + +`futureware-tech/simulator-action` is the exception: it only publishes major +tags (`v1`…`v5`), so `@v4` is the most specific pin available. ## Why exact npm versions, when `bun.lock` already exists @@ -47,7 +61,7 @@ Exact versions make both impossible. `pod` is resolved from `PATH` by the EAS build, so `gem install cocoapods -v X` is not enough — a newer CocoaPods preinstalled on a future runner image would -still win. The setup action writes a small `pod` shim that always calls the +still win. The iOS workflows write a small `pod` shim that always calls the pinned gem version explicitly. ## Bumping something @@ -60,18 +74,23 @@ pinned gem version explicitly. For npm dependencies, bump the exact version in the relevant `package.json`, run `bun install` to refresh `bun.lock`, and commit both. -## What still isn't fully pinnable +## What still isn't pinned Worth knowing about, because these can still break a build that never changed: +- **Node.js** comes from the runner image. Pinning the image label pins it in + practice (`macos-26` ships Node 24.x, `ubuntu-24.04` ships 22.x), and the + major version won't change within a given image, but patch bumps do land. +- **Ruby** likewise comes from the image; only the CocoaPods gem on top of it + is pinned. - **Runner images are eventually retired.** `macos-26` and `ubuntu-24.04` will be removed by GitHub in time. When that happens, the job fails immediately and loudly with an unknown-label error rather than drifting — that's the - point. Move to the next image and re-pin Xcode, Node and CocoaPods to - whatever it ships. + point. Move to the next image and re-pin Xcode and CocoaPods to whatever it + ships. - **GitHub retires action runtimes.** Actions declaring `node20` (such as `futureware-tech/simulator-action`) will eventually be forced onto a newer - Node by the runner regardless of the SHA pin. + Node by the runner regardless of how they're pinned. - **The Android SDK components** Gradle downloads during a build come from the versions the generated `android/` project requests, which are set by the pinned Expo/React Native versions. diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml deleted file mode 100644 index 029c3a0..0000000 --- a/.github/actions/setup/action.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: Setup -description: Installs the pinned toolchain that every Scheduli CI job builds on. - -# --------------------------------------------------------------------------- -# This file is the single source of truth for CI tool versions. -# -# Everything here is pinned to an exact version on purpose: a green CI run -# today should still be green in a year without touching the repo. Nothing may -# resolve to "latest", a floating tag, or "whatever the runner image ships". -# -# The values below were captured from the GitHub-hosted `macos-26` and -# `ubuntu-24.04` images (image version 20260728.0273.1), so pinning them is a -# no-op today and a guarantee later. -# -# To upgrade a tool, change it here, in this file only, and let CI prove it. -# --------------------------------------------------------------------------- - -inputs: - java: - description: Install the pinned Temurin JDK (Android builds, and Maestro's runtime). - required: false - default: "false" - xcode: - description: Select the pinned Xcode and force the pinned CocoaPods (macOS only). - required: false - default: "false" - maestro: - description: Install the pinned Maestro CLI. - required: false - default: "false" - -runs: - using: composite - steps: - - name: Install Bun - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - with: - bun-version: 1.3.14 # keep in sync with "packageManager" in package.json - - # Node is pinned per-OS to the version each runner image ships today, so - # this freezes current behaviour instead of changing it. Both are already - # in the runner tool cache, so setup-node is a fast no-op. - - name: Install Node.js - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 - with: - node-version: ${{ runner.os == 'macOS' && '24.18.0' || '22.23.2' }} - - - name: Install Java - if: inputs.java == 'true' - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1 - with: - distribution: temurin - java-version: 21.0.11+10 - - - name: Select Xcode - if: inputs.xcode == 'true' - shell: bash - run: | - set -euo pipefail - xcode_app="/Applications/Xcode_${XCODE_VERSION}.app" - if [ ! -d "$xcode_app" ]; then - echo "::error::Xcode ${XCODE_VERSION} is not installed on this runner image." - echo "Available Xcode versions:" - ls -d /Applications/Xcode*.app - echo "Pick one of the above and update XCODE_VERSION in .github/actions/setup/action.yml." - exit 1 - fi - sudo xcode-select --switch "$xcode_app" - xcodebuild -version - env: - XCODE_VERSION: "26.6" - - # `pod` is resolved from PATH by the EAS build, so pinning the gem is not - # enough: a newer CocoaPods preinstalled on a future runner image would - # still win. A shim that always calls the exact gem version removes that. - - name: Pin CocoaPods - if: inputs.xcode == 'true' - shell: bash - run: | - set -euo pipefail - if ! gem list -i -v "$COCOAPODS_VERSION" cocoapods > /dev/null 2>&1; then - gem install cocoapods -v "$COCOAPODS_VERSION" --no-document \ - || sudo gem install cocoapods -v "$COCOAPODS_VERSION" --no-document - fi - real_pod="$(command -v pod)" - shim_dir="$RUNNER_TEMP/pinned-bin" - mkdir -p "$shim_dir" - printf '#!/usr/bin/env bash\nexec "%s" _%s_ "$@"\n' "$real_pod" "$COCOAPODS_VERSION" > "$shim_dir/pod" - chmod +x "$shim_dir/pod" - echo "$shim_dir" >> "$GITHUB_PATH" - "$shim_dir/pod" --version - env: - COCOAPODS_VERSION: "1.17.0" - - # Deliberately not `curl https://get.maestro.mobile.dev | bash`: that - # installs whatever the newest release happens to be, and pipes a script - # that can change under us. This fetches one immutable release asset. - - name: Install Maestro - if: inputs.maestro == 'true' - shell: bash - run: | - set -euo pipefail - download_dir="$RUNNER_TEMP/maestro-download" - mkdir -p "$download_dir" - curl -fsSL --retry 3 --retry-all-errors \ - -o "$download_dir/maestro.zip" \ - "https://github.com/mobile-dev-inc/maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip" - unzip -q "$download_dir/maestro.zip" -d "$download_dir" - mkdir -p "$HOME/.maestro" - cp -rf "$download_dir/maestro/." "$HOME/.maestro/" - echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" - "$HOME/.maestro/bin/maestro" --version - env: - MAESTRO_VERSION: "2.8.0" diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index ed0c240..52f12b8 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -6,15 +6,25 @@ on: branches: - "main" +# Pinned tool versions for this workflow. See .github/README.md. +env: + BUN_VERSION: 1.3.14 # keep in sync with "packageManager" in package.json + JAVA_VERSION: 21.0.11+10 + jobs: build: runs-on: macos-26 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@v6.1.0 + + - uses: actions/setup-java@v4.9.1 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} - - uses: ./.github/actions/setup + - uses: oven-sh/setup-bun@v2.2.0 with: - java: true + bun-version: ${{ env.BUN_VERSION }} - name: Install dependencies run: bun ci diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index fd232c7..523dcaa 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -6,15 +6,53 @@ on: branches: - "main" +# Pinned tool versions for this workflow. See .github/README.md. +env: + BUN_VERSION: 1.3.14 # keep in sync with "packageManager" in package.json + XCODE_VERSION: "26.6" + COCOAPODS_VERSION: "1.17.0" + jobs: build: runs-on: macos-26 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@v6.1.0 - - uses: ./.github/actions/setup + - uses: oven-sh/setup-bun@v2.2.0 with: - xcode: true + bun-version: ${{ env.BUN_VERSION }} + + - name: Select Xcode + run: | + set -euo pipefail + xcode_app="/Applications/Xcode_${XCODE_VERSION}.app" + if [ ! -d "$xcode_app" ]; then + echo "::error::Xcode ${XCODE_VERSION} is not installed on this runner image." + echo "Available Xcode versions:" + ls -d /Applications/Xcode*.app + echo "Pick one of the above and update XCODE_VERSION in this workflow." + exit 1 + fi + sudo xcode-select --switch "$xcode_app" + xcodebuild -version + + # `pod` is resolved from PATH by the EAS build, so pinning the gem is not + # enough: a newer CocoaPods preinstalled on a future runner image would + # still win. A shim that always calls the exact gem version removes that. + - name: Pin CocoaPods + run: | + set -euo pipefail + if ! gem list -i -v "$COCOAPODS_VERSION" cocoapods > /dev/null 2>&1; then + gem install cocoapods -v "$COCOAPODS_VERSION" --no-document \ + || sudo gem install cocoapods -v "$COCOAPODS_VERSION" --no-document + fi + real_pod="$(command -v pod)" + shim_dir="$RUNNER_TEMP/pinned-bin" + mkdir -p "$shim_dir" + printf '#!/usr/bin/env bash\nexec "%s" _%s_ "$@"\n' "$real_pod" "$COCOAPODS_VERSION" > "$shim_dir/pod" + chmod +x "$shim_dir/pod" + echo "$shim_dir" >> "$GITHUB_PATH" + "$shim_dir/pod" --version - name: Install dependencies run: bun ci diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fdc2a74..e3ef902 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,13 +7,15 @@ on: jobs: lint: # Pinned image, not `ubuntu-latest`: the `latest` alias moves to a new - # Ubuntu release without warning. Tool versions come from - # .github/actions/setup. + # Ubuntu release without warning, taking the whole preinstalled toolchain + # with it. runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@v6.1.0 - - uses: ./.github/actions/setup + - uses: oven-sh/setup-bun@v2.2.0 + with: + bun-version: 1.3.14 # keep in sync with "packageManager" in package.json - name: Install dependencies run: bun ci diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index a850c20..125aec0 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -4,15 +4,25 @@ on: workflow_dispatch: pull_request: +# Pinned tool versions for this workflow. See .github/README.md. +env: + BUN_VERSION: 1.3.14 # keep in sync with "packageManager" in package.json + JAVA_VERSION: 21.0.11+10 + jobs: build: runs-on: macos-26 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@v6.1.0 + + - uses: actions/setup-java@v4.9.1 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} - - uses: ./.github/actions/setup + - uses: oven-sh/setup-bun@v2.2.0 with: - java: true + bun-version: ${{ env.BUN_VERSION }} - name: Install dependencies run: bun ci diff --git a/.github/workflows/test-ios.yml b/.github/workflows/test-ios.yml index 0918fdc..8bd9466 100644 --- a/.github/workflows/test-ios.yml +++ b/.github/workflows/test-ios.yml @@ -4,6 +4,14 @@ on: workflow_dispatch: pull_request: +# Pinned tool versions for this workflow. See .github/README.md. +env: + BUN_VERSION: 1.3.14 # keep in sync with "packageManager" in package.json + JAVA_VERSION: 21.0.11+10 + XCODE_VERSION: "26.6" + COCOAPODS_VERSION: "1.17.0" + MAESTRO_VERSION: "2.8.0" + jobs: test: # Pinned image, not `macos-latest`: that alias follows each new macOS @@ -12,13 +20,66 @@ jobs: runs-on: macos-26 timeout-minutes: 60 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@v6.1.0 + + # Maestro runs on the JVM. + - uses: actions/setup-java@v4.9.1 + with: + distribution: temurin + java-version: ${{ env.JAVA_VERSION }} - - uses: ./.github/actions/setup + - uses: oven-sh/setup-bun@v2.2.0 with: - java: true # Maestro runs on the JVM - xcode: true - maestro: true + bun-version: ${{ env.BUN_VERSION }} + + - name: Select Xcode + run: | + set -euo pipefail + xcode_app="/Applications/Xcode_${XCODE_VERSION}.app" + if [ ! -d "$xcode_app" ]; then + echo "::error::Xcode ${XCODE_VERSION} is not installed on this runner image." + echo "Available Xcode versions:" + ls -d /Applications/Xcode*.app + echo "Pick one of the above and update XCODE_VERSION in this workflow." + exit 1 + fi + sudo xcode-select --switch "$xcode_app" + xcodebuild -version + + # `pod` is resolved from PATH by the EAS build, so pinning the gem is not + # enough: a newer CocoaPods preinstalled on a future runner image would + # still win. A shim that always calls the exact gem version removes that. + - name: Pin CocoaPods + run: | + set -euo pipefail + if ! gem list -i -v "$COCOAPODS_VERSION" cocoapods > /dev/null 2>&1; then + gem install cocoapods -v "$COCOAPODS_VERSION" --no-document \ + || sudo gem install cocoapods -v "$COCOAPODS_VERSION" --no-document + fi + real_pod="$(command -v pod)" + shim_dir="$RUNNER_TEMP/pinned-bin" + mkdir -p "$shim_dir" + printf '#!/usr/bin/env bash\nexec "%s" _%s_ "$@"\n' "$real_pod" "$COCOAPODS_VERSION" > "$shim_dir/pod" + chmod +x "$shim_dir/pod" + echo "$shim_dir" >> "$GITHUB_PATH" + "$shim_dir/pod" --version + + # Deliberately not `curl https://get.maestro.mobile.dev | bash`: that + # installs whatever the newest release happens to be, and pipes a script + # that can change under us. This fetches one immutable release asset. + - name: Install Maestro + run: | + set -euo pipefail + download_dir="$RUNNER_TEMP/maestro-download" + mkdir -p "$download_dir" + curl -fsSL --retry 3 --retry-all-errors \ + -o "$download_dir/maestro.zip" \ + "https://github.com/mobile-dev-inc/maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip" + unzip -q "$download_dir/maestro.zip" -d "$download_dir" + mkdir -p "$HOME/.maestro" + cp -rf "$download_dir/maestro/." "$HOME/.maestro/" + echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" + "$HOME/.maestro/bin/maestro" --version - name: Install dependencies run: bun ci @@ -30,9 +91,9 @@ jobs: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} run: bunx turbo run build:ios:simulator - # `os` is pinned as well as `model`: without it the action boots whatever - # the newest installed runtime happens to be. - - uses: futureware-tech/simulator-action@dab10d813144ef59b48d401cd95da151222ef8cd # v4 + # `os_version` is pinned as well as `model`: without it the action boots + # whatever the newest installed runtime happens to be. + - uses: futureware-tech/simulator-action@v4 with: model: "iPhone 17 Pro" os: "iOS" @@ -58,7 +119,7 @@ jobs: - name: Upload Debug Information if: failure() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@v6.0.0 with: name: debug-info path: ./debug-info/ diff --git a/.github/workflows/test-web.yml b/.github/workflows/test-web.yml index 6a40979..1ab200e 100644 --- a/.github/workflows/test-web.yml +++ b/.github/workflows/test-web.yml @@ -9,9 +9,11 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 60 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@v6.1.0 - - uses: ./.github/actions/setup + - uses: oven-sh/setup-bun@v2.2.0 + with: + bun-version: 1.3.14 # keep in sync with "packageManager" in package.json - name: Install dependencies run: bun ci @@ -24,7 +26,7 @@ jobs: - name: Run Playwright tests run: bun run --filter @scheduli/web test - - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + - uses: actions/upload-artifact@v6.0.0 if: failure() with: name: playwright-report diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index fe2c483..85e86c0 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -8,9 +8,11 @@ jobs: typecheck: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + - uses: actions/checkout@v6.1.0 - - uses: ./.github/actions/setup + - uses: oven-sh/setup-bun@v2.2.0 + with: + bun-version: 1.3.14 # keep in sync with "packageManager" in package.json - name: Install dependencies run: bun ci From e8f739e02947c7eef2f8b25743c8701a0eae525d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 12:22:44 +0000 Subject: [PATCH 3/5] ci: pin PostHog pod, drop workflow comments and CI readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audited every native dependency on both platforms. iOS, resolved from the CocoaPods CDN rather than node_modules: - PostHog was the one real gap. posthog-react-native-session-replay's podspec asks for `~> 3.21`, which today resolves to 3.69.8 — 115 published versions satisfy that range and the newest landed yesterday. Pin it in the app target's Podfile, same mechanism as MMKVCore. - Everything else is local: react-native-mmkv compiles MMKV from vendored source, NotifeeCore builds from a local subspec, and Quick/Nimble/ OHHTTPStubs/ExpoModulesTestCore are test_spec-only so they are never installed. Android, verified already deterministic: - app.notifee:core:+ resolves against notifee's bundled file repo, which ships exactly one version (202108261754) and is published to no remote. - com.facebook.react:react-native:+ is substituted by the RN Gradle plugin to the exact react-native version. - org.webkit:android-jsc:+ has a single Maven Central artifact (r174650, 2015) and jsc-android is not installed. - Kotlin/Compose/Coil/Fresco versions interpolate from properties fixed inside their own packages. Verified the Podfile change by running `expo prebuild --platform ios`: the pin lands inside `target 'Scheduli'` and the MMKV block is intact. Also drops the comments from the workflows and the .github/README.md. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QRXxEuKNzgWvK2RxJjQ8TC --- .github/README.md | 98 ------------------- .github/workflows/build-android.yml | 3 +- .github/workflows/build-ios.yml | 10 +- .github/workflows/lint.yml | 5 +- .github/workflows/test-android.yml | 3 +- .github/workflows/test-ios.yml | 19 +--- .github/workflows/test-web.yml | 4 +- .github/workflows/typecheck.yml | 2 +- .../mobile/plugins/withAddPodDepsToTargets.ts | 25 ++++- 9 files changed, 33 insertions(+), 136 deletions(-) delete mode 100644 .github/README.md diff --git a/.github/README.md b/.github/README.md deleted file mode 100644 index 20c6d63..0000000 --- a/.github/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# CI version pinning - -Everything CI touches is pinned to an exact version. The goal is blunt: a run -that is green today should still be green in a year with no changes to this -repo. If a build breaks, it should be because someone changed the code — not -because an upstream release happened. - -Nothing in CI may use `latest`, a floating major tag (`@v4`), a caret/tilde -range, or "whatever the runner image ships". - -## Where the pins live - -| What | Pinned to | File | -| --- | --- | --- | -| Runner image | `macos-26`, `ubuntu-24.04` | `.github/workflows/*.yml` | -| Actions | exact patch tag (`@v6.1.0`) | `.github/workflows/*.yml` | -| Bun | `1.3.14` | `env:` block in each workflow, `packageManager` in `package.json` | -| Temurin JDK | `21.0.11+10` | `env:` block in each workflow | -| Xcode | `26.6` | `env:` block in the iOS workflows | -| CocoaPods | `1.17.0` | `env:` block in the iOS workflows | -| Maestro | `2.8.0` | `env:` block in `test-ios.yml` | -| iOS simulator | `iPhone 17 Pro`, iOS `26.5` | `.github/workflows/test-ios.yml` | -| EAS CLI | `22.0.0` | `apps/mobile/package.json` scripts, `apps/mobile/eas.json` | -| npm dependencies | exact versions, no ranges | `package.json`, `apps/*/package.json` | -| Transitive npm deps | resolved tree | `bun.lock` (CI runs `bun ci`, which is frozen-lockfile) | -| Native iOS pods | `MMKVCore` / `MMKVAppExtension` `2.4.0` | `apps/mobile/plugins/withAddPodDepsToTargets.ts` | -| `react-native-mmkv` patch | `3.3.3` | `apps/mobile/patches/`, `patchedDependencies` | - -Each workflow declares its own versions in a top-level `env:` block, so a -workflow is readable on its own. When bumping something shared — Bun, the JDK, -Xcode — grep for it: `rg BUN_VERSION .github/workflows`. - -## Why exact patch tags for actions, not `@v6` - -`@v6` is a moving target: the maintainer repoints it at every 6.x release, so -the same workflow silently runs different code over time. `@v6.1.0` names one -release. - -A tag is not quite as strong as a commit SHA — a maintainer *can* force-move a -release tag, and a SHA can't be moved at all. Exact tags are the deliberate -trade here: they're far easier to read and bump, and they still close the -floating-major hole that actually bit us. - -`futureware-tech/simulator-action` is the exception: it only publishes major -tags (`v1`…`v5`), so `@v4` is the most specific pin available. - -## Why exact npm versions, when `bun.lock` already exists - -`bun ci` is frozen, so CI installs exactly what the lockfile says. Ranges still -bite in two places: - -- Anyone running plain `bun install` locally silently drifts off the lockfile, - and then commits the drift. -- `patchedDependencies` keys are exact (`react-native-mmkv@3.3.3`). If the - range floats to `3.3.4`, the patch stops applying **silently** — the app-group - MMKV path fix just disappears, and nothing fails until a widget misbehaves. - -Exact versions make both impossible. - -## Why the CocoaPods shim - -`pod` is resolved from `PATH` by the EAS build, so `gem install cocoapods -v X` -is not enough — a newer CocoaPods preinstalled on a future runner image would -still win. The iOS workflows write a small `pod` shim that always calls the -pinned gem version explicitly. - -## Bumping something - -1. Change the version in the one place listed above. -2. Open a PR and let CI run. -3. If it's green, merge. If not, you found out on your terms rather than on a - random Tuesday. - -For npm dependencies, bump the exact version in the relevant `package.json`, -run `bun install` to refresh `bun.lock`, and commit both. - -## What still isn't pinned - -Worth knowing about, because these can still break a build that never changed: - -- **Node.js** comes from the runner image. Pinning the image label pins it in - practice (`macos-26` ships Node 24.x, `ubuntu-24.04` ships 22.x), and the - major version won't change within a given image, but patch bumps do land. -- **Ruby** likewise comes from the image; only the CocoaPods gem on top of it - is pinned. -- **Runner images are eventually retired.** `macos-26` and `ubuntu-24.04` will - be removed by GitHub in time. When that happens, the job fails immediately - and loudly with an unknown-label error rather than drifting — that's the - point. Move to the next image and re-pin Xcode and CocoaPods to whatever it - ships. -- **GitHub retires action runtimes.** Actions declaring `node20` (such as - `futureware-tech/simulator-action`) will eventually be forced onto a newer - Node by the runner regardless of how they're pinned. -- **The Android SDK components** Gradle downloads during a build come from the - versions the generated `android/` project requests, which are set by the - pinned Expo/React Native versions. -- **EAS Build's server-side behaviour.** `eas build --local` still talks to - Expo's servers for credentials and project config. diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 52f12b8..bbdeb72 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -6,9 +6,8 @@ on: branches: - "main" -# Pinned tool versions for this workflow. See .github/README.md. env: - BUN_VERSION: 1.3.14 # keep in sync with "packageManager" in package.json + BUN_VERSION: 1.3.14 JAVA_VERSION: 21.0.11+10 jobs: diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 523dcaa..c32a442 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -6,9 +6,8 @@ on: branches: - "main" -# Pinned tool versions for this workflow. See .github/README.md. env: - BUN_VERSION: 1.3.14 # keep in sync with "packageManager" in package.json + BUN_VERSION: 1.3.14 XCODE_VERSION: "26.6" COCOAPODS_VERSION: "1.17.0" @@ -27,18 +26,13 @@ jobs: set -euo pipefail xcode_app="/Applications/Xcode_${XCODE_VERSION}.app" if [ ! -d "$xcode_app" ]; then - echo "::error::Xcode ${XCODE_VERSION} is not installed on this runner image." - echo "Available Xcode versions:" + echo "::error::Xcode ${XCODE_VERSION} is not installed. Available:" ls -d /Applications/Xcode*.app - echo "Pick one of the above and update XCODE_VERSION in this workflow." exit 1 fi sudo xcode-select --switch "$xcode_app" xcodebuild -version - # `pod` is resolved from PATH by the EAS build, so pinning the gem is not - # enough: a newer CocoaPods preinstalled on a future runner image would - # still win. A shim that always calls the exact gem version removes that. - name: Pin CocoaPods run: | set -euo pipefail diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e3ef902..988557d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,16 +6,13 @@ on: jobs: lint: - # Pinned image, not `ubuntu-latest`: the `latest` alias moves to a new - # Ubuntu release without warning, taking the whole preinstalled toolchain - # with it. runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6.1.0 - uses: oven-sh/setup-bun@v2.2.0 with: - bun-version: 1.3.14 # keep in sync with "packageManager" in package.json + bun-version: 1.3.14 - name: Install dependencies run: bun ci diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index 125aec0..47809db 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -4,9 +4,8 @@ on: workflow_dispatch: pull_request: -# Pinned tool versions for this workflow. See .github/README.md. env: - BUN_VERSION: 1.3.14 # keep in sync with "packageManager" in package.json + BUN_VERSION: 1.3.14 JAVA_VERSION: 21.0.11+10 jobs: diff --git a/.github/workflows/test-ios.yml b/.github/workflows/test-ios.yml index 8bd9466..4ac98d8 100644 --- a/.github/workflows/test-ios.yml +++ b/.github/workflows/test-ios.yml @@ -4,9 +4,8 @@ on: workflow_dispatch: pull_request: -# Pinned tool versions for this workflow. See .github/README.md. env: - BUN_VERSION: 1.3.14 # keep in sync with "packageManager" in package.json + BUN_VERSION: 1.3.14 JAVA_VERSION: 21.0.11+10 XCODE_VERSION: "26.6" COCOAPODS_VERSION: "1.17.0" @@ -14,15 +13,11 @@ env: jobs: test: - # Pinned image, not `macos-latest`: that alias follows each new macOS - # release, which swaps the default Xcode, the simulator runtimes and the - # preinstalled CocoaPods underneath a build that never changed. runs-on: macos-26 timeout-minutes: 60 steps: - uses: actions/checkout@v6.1.0 - # Maestro runs on the JVM. - uses: actions/setup-java@v4.9.1 with: distribution: temurin @@ -37,18 +32,13 @@ jobs: set -euo pipefail xcode_app="/Applications/Xcode_${XCODE_VERSION}.app" if [ ! -d "$xcode_app" ]; then - echo "::error::Xcode ${XCODE_VERSION} is not installed on this runner image." - echo "Available Xcode versions:" + echo "::error::Xcode ${XCODE_VERSION} is not installed. Available:" ls -d /Applications/Xcode*.app - echo "Pick one of the above and update XCODE_VERSION in this workflow." exit 1 fi sudo xcode-select --switch "$xcode_app" xcodebuild -version - # `pod` is resolved from PATH by the EAS build, so pinning the gem is not - # enough: a newer CocoaPods preinstalled on a future runner image would - # still win. A shim that always calls the exact gem version removes that. - name: Pin CocoaPods run: | set -euo pipefail @@ -64,9 +54,6 @@ jobs: echo "$shim_dir" >> "$GITHUB_PATH" "$shim_dir/pod" --version - # Deliberately not `curl https://get.maestro.mobile.dev | bash`: that - # installs whatever the newest release happens to be, and pipes a script - # that can change under us. This fetches one immutable release asset. - name: Install Maestro run: | set -euo pipefail @@ -91,8 +78,6 @@ jobs: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} run: bunx turbo run build:ios:simulator - # `os_version` is pinned as well as `model`: without it the action boots - # whatever the newest installed runtime happens to be. - uses: futureware-tech/simulator-action@v4 with: model: "iPhone 17 Pro" diff --git a/.github/workflows/test-web.yml b/.github/workflows/test-web.yml index 1ab200e..aaad112 100644 --- a/.github/workflows/test-web.yml +++ b/.github/workflows/test-web.yml @@ -13,13 +13,11 @@ jobs: - uses: oven-sh/setup-bun@v2.2.0 with: - bun-version: 1.3.14 # keep in sync with "packageManager" in package.json + bun-version: 1.3.14 - name: Install dependencies run: bun ci - # The browser build is chosen by the exact @playwright/test version in - # apps/web/package.json, so this is pinned by the lockfile. - name: Install Playwright Browsers run: bun run --filter @scheduli/web test:setup diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 85e86c0..e347b69 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -12,7 +12,7 @@ jobs: - uses: oven-sh/setup-bun@v2.2.0 with: - bun-version: 1.3.14 # keep in sync with "packageManager" in package.json + bun-version: 1.3.14 - name: Install dependencies run: bun ci diff --git a/apps/mobile/plugins/withAddPodDepsToTargets.ts b/apps/mobile/plugins/withAddPodDepsToTargets.ts index b07965e..3e6a0d3 100644 --- a/apps/mobile/plugins/withAddPodDepsToTargets.ts +++ b/apps/mobile/plugins/withAddPodDepsToTargets.ts @@ -14,13 +14,17 @@ target "StatusWidget" do end `; +const PINNED_PODS = ` + pod "PostHog", "3.69.8" +`; + export const withAddPodDepsToTargets: ConfigPlugin = (config) => { return withDangerousMod(config, [ "ios", async (config) => { const file = path.join(config.modRequest.platformProjectRoot, "Podfile"); const contents = await fs.promises.readFile(file, "utf8"); - await fs.promises.writeFile(file, addPodDepsToTargets(contents), "utf8"); + await fs.promises.writeFile(file, pinPods(addPodDepsToTargets(contents)), "utf8"); return config; }, ]); @@ -36,3 +40,22 @@ function addPodDepsToTargets(src: string) { comment: "#", }).contents; } + +function pinPods(src: string) { + const result = mergeContents({ + tag: `with-pinned-pods`, + src, + newSrc: PINNED_PODS.replace(/\n$/, ""), + anchor: /use_expo_modules!/, + offset: 1, + comment: "#", + }); + + if (!result.didMerge) { + throw new Error( + "withAddPodDepsToTargets: could not find `use_expo_modules!` in the Podfile, so pinned pods were not applied.", + ); + } + + return result.contents; +} From fca4252d5fa23fab2a04115c5d5031c2e68c964d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 12:31:27 +0000 Subject: [PATCH 4/5] ci: fix Java pin rejected by setup-java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `21.0.11+10` came from the Adoptium API release name, but setup-java matches against its own list where the same build reads `21.0.11+10.0.LTS`. When the requested version carries build metadata setup-java requires an exact compareBuild match, so `+10` never matched `+10.0.LTS` and every macOS job failed at Install Java. Use `21.0.11`. With no build metadata setup-java falls through to a normal semver range, which ignores build metadata and resolves to the single available 21.0.11 build. Still an exact JDK patch pin, and it does not depend on how Adoptium renders the build suffix — which is what broke. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QRXxEuKNzgWvK2RxJjQ8TC --- .github/workflows/build-android.yml | 2 +- .github/workflows/test-android.yml | 2 +- .github/workflows/test-ios.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index bbdeb72..17739e6 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -8,7 +8,7 @@ on: env: BUN_VERSION: 1.3.14 - JAVA_VERSION: 21.0.11+10 + JAVA_VERSION: 21.0.11 jobs: build: diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index 47809db..516788d 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -6,7 +6,7 @@ on: env: BUN_VERSION: 1.3.14 - JAVA_VERSION: 21.0.11+10 + JAVA_VERSION: 21.0.11 jobs: build: diff --git a/.github/workflows/test-ios.yml b/.github/workflows/test-ios.yml index 4ac98d8..343fccd 100644 --- a/.github/workflows/test-ios.yml +++ b/.github/workflows/test-ios.yml @@ -6,7 +6,7 @@ on: env: BUN_VERSION: 1.3.14 - JAVA_VERSION: 21.0.11+10 + JAVA_VERSION: 21.0.11 XCODE_VERSION: "26.6" COCOAPODS_VERSION: "1.17.0" MAESTRO_VERSION: "2.8.0" From 08482f7f62dedc1b4deeb599898a5c019e4a08a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 23:36:19 +0000 Subject: [PATCH 5/5] ci: inline pinned versions and simplify the macOS setup steps Drop the per-workflow env: blocks. Every version now sits at its use site, like the action tags already did. Select Xcode is one line: xcode-select --switch already exits non-zero on a path that is not there, so the existence check and the error message were restating the failure it produces anyway. Pin CocoaPods is one line. The PATH shim it replaces only mattered if a future runner image ships CocoaPods newer than the pin, since `pod` resolves to the newest installed version. Install Maestro is three lines: fetch the pinned release asset, unzip, add to PATH. The launcher is a Gradle start script that resolves its own location, so extracting outside ~/.maestro is fine, and curl -f already fails on a version that does not exist. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QRXxEuKNzgWvK2RxJjQ8TC --- .github/workflows/build-android.yml | 8 ++--- .github/workflows/build-ios.yml | 32 ++---------------- .github/workflows/test-android.yml | 8 ++--- .github/workflows/test-ios.yml | 50 ++++------------------------- 4 files changed, 14 insertions(+), 84 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 6bd2ae2..1b67490 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -6,10 +6,6 @@ on: branches: - "main" -env: - BUN_VERSION: 1.3.14 - JAVA_VERSION: 21.0.11 - jobs: build: runs-on: ubuntu-24.04 @@ -19,11 +15,11 @@ jobs: - uses: actions/setup-java@v5.7.0 with: distribution: temurin - java-version: ${{ env.JAVA_VERSION }} + java-version: 21.0.11 - uses: oven-sh/setup-bun@v2.2.0 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: 1.3.14 - name: Install dependencies run: bun ci diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index c32a442..464fe71 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -6,11 +6,6 @@ on: branches: - "main" -env: - BUN_VERSION: 1.3.14 - XCODE_VERSION: "26.6" - COCOAPODS_VERSION: "1.17.0" - jobs: build: runs-on: macos-26 @@ -19,34 +14,13 @@ jobs: - uses: oven-sh/setup-bun@v2.2.0 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: 1.3.14 - name: Select Xcode - run: | - set -euo pipefail - xcode_app="/Applications/Xcode_${XCODE_VERSION}.app" - if [ ! -d "$xcode_app" ]; then - echo "::error::Xcode ${XCODE_VERSION} is not installed. Available:" - ls -d /Applications/Xcode*.app - exit 1 - fi - sudo xcode-select --switch "$xcode_app" - xcodebuild -version + run: sudo xcode-select --switch /Applications/Xcode_26.6.app - name: Pin CocoaPods - run: | - set -euo pipefail - if ! gem list -i -v "$COCOAPODS_VERSION" cocoapods > /dev/null 2>&1; then - gem install cocoapods -v "$COCOAPODS_VERSION" --no-document \ - || sudo gem install cocoapods -v "$COCOAPODS_VERSION" --no-document - fi - real_pod="$(command -v pod)" - shim_dir="$RUNNER_TEMP/pinned-bin" - mkdir -p "$shim_dir" - printf '#!/usr/bin/env bash\nexec "%s" _%s_ "$@"\n' "$real_pod" "$COCOAPODS_VERSION" > "$shim_dir/pod" - chmod +x "$shim_dir/pod" - echo "$shim_dir" >> "$GITHUB_PATH" - "$shim_dir/pod" --version + run: gem install cocoapods -v 1.17.0 --no-document - name: Install dependencies run: bun ci diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index 38f8b35..51577c3 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -4,10 +4,6 @@ on: workflow_dispatch: pull_request: -env: - BUN_VERSION: 1.3.14 - JAVA_VERSION: 21.0.11 - jobs: build: runs-on: ubuntu-24.04 @@ -17,11 +13,11 @@ jobs: - uses: actions/setup-java@v5.7.0 with: distribution: temurin - java-version: ${{ env.JAVA_VERSION }} + java-version: 21.0.11 - uses: oven-sh/setup-bun@v2.2.0 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: 1.3.14 - name: Install dependencies run: bun ci diff --git a/.github/workflows/test-ios.yml b/.github/workflows/test-ios.yml index e59c9d1..a6f3de1 100644 --- a/.github/workflows/test-ios.yml +++ b/.github/workflows/test-ios.yml @@ -4,13 +4,6 @@ on: workflow_dispatch: pull_request: -env: - BUN_VERSION: 1.3.14 - JAVA_VERSION: 21.0.11 - XCODE_VERSION: "26.6" - COCOAPODS_VERSION: "1.17.0" - MAESTRO_VERSION: "2.8.0" - jobs: test: runs-on: macos-26 @@ -21,52 +14,23 @@ jobs: - uses: actions/setup-java@v5.7.0 with: distribution: temurin - java-version: ${{ env.JAVA_VERSION }} + java-version: 21.0.11 - uses: oven-sh/setup-bun@v2.2.0 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: 1.3.14 - name: Select Xcode - run: | - set -euo pipefail - xcode_app="/Applications/Xcode_${XCODE_VERSION}.app" - if [ ! -d "$xcode_app" ]; then - echo "::error::Xcode ${XCODE_VERSION} is not installed. Available:" - ls -d /Applications/Xcode*.app - exit 1 - fi - sudo xcode-select --switch "$xcode_app" - xcodebuild -version + run: sudo xcode-select --switch /Applications/Xcode_26.6.app - name: Pin CocoaPods - run: | - set -euo pipefail - if ! gem list -i -v "$COCOAPODS_VERSION" cocoapods > /dev/null 2>&1; then - gem install cocoapods -v "$COCOAPODS_VERSION" --no-document \ - || sudo gem install cocoapods -v "$COCOAPODS_VERSION" --no-document - fi - real_pod="$(command -v pod)" - shim_dir="$RUNNER_TEMP/pinned-bin" - mkdir -p "$shim_dir" - printf '#!/usr/bin/env bash\nexec "%s" _%s_ "$@"\n' "$real_pod" "$COCOAPODS_VERSION" > "$shim_dir/pod" - chmod +x "$shim_dir/pod" - echo "$shim_dir" >> "$GITHUB_PATH" - "$shim_dir/pod" --version + run: gem install cocoapods -v 1.17.0 --no-document - name: Install Maestro run: | - set -euo pipefail - download_dir="$RUNNER_TEMP/maestro-download" - mkdir -p "$download_dir" - curl -fsSL --retry 3 --retry-all-errors \ - -o "$download_dir/maestro.zip" \ - "https://github.com/mobile-dev-inc/maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip" - unzip -q "$download_dir/maestro.zip" -d "$download_dir" - mkdir -p "$HOME/.maestro" - cp -rf "$download_dir/maestro/." "$HOME/.maestro/" - echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" - "$HOME/.maestro/bin/maestro" --version + curl -fsSL https://github.com/mobile-dev-inc/maestro/releases/download/cli-2.8.0/maestro.zip -o "$RUNNER_TEMP/maestro.zip" + unzip -q "$RUNNER_TEMP/maestro.zip" -d "$HOME" + echo "$HOME/maestro/bin" >> "$GITHUB_PATH" - name: Install dependencies run: bun ci