From 9b0510741f90982ab0b8f99428adbf1fd83c1643 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 16:54:31 +0000 Subject: [PATCH] Split iOS workflow from release train Agent-Logs-Url: https://github.com/OpenKnots/okcode/sessions/8ea02c90-6101-4817-8bca-bcbc5647227e Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com> --- .github/workflows/release-ios.yml | 277 ++++++++++++++++++++- .github/workflows/release.yml | 238 +----------------- README.md | 4 +- docs/release.md | 36 +-- docs/releases/v0.26.0/rollout-checklist.md | 4 +- scripts/prepare-release.ts | 8 +- 6 files changed, 307 insertions(+), 260 deletions(-) diff --git a/.github/workflows/release-ios.yml b/.github/workflows/release-ios.yml index 7d83b0eef..84f1182c6 100644 --- a/.github/workflows/release-ios.yml +++ b/.github/workflows/release-ios.yml @@ -1,6 +1,9 @@ -name: iOS Release Dry Run +name: Release iOS on: + push: + tags: + - "v*.*.*" workflow_dispatch: inputs: version: @@ -12,11 +15,273 @@ permissions: contents: read jobs: - guidance: - name: Coordinated train guidance + preflight: + name: Resolve iOS release metadata runs-on: ubuntu-24.04 + outputs: + version: ${{ steps.release_meta.outputs.version }} + tag: ${{ steps.release_meta.outputs.tag }} + release_channel: ${{ steps.release_meta.outputs.release_channel }} + build_timestamp: ${{ steps.release_meta.outputs.build_timestamp }} + ref: ${{ github.sha }} steps: - - name: Explain release entrypoint + - id: release_meta + name: Resolve release version + shell: bash run: | - echo "Use .github/workflows/release.yml for official tags and coordinated RC/stable releases." - echo "This workflow is reserved for manual iOS dry runs while stabilizing the TestFlight lane." + set -euo pipefail + + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + raw="${{ github.event.inputs.version }}" + else + raw="${GITHUB_REF_NAME}" + fi + + version="${raw#v}" + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then + echo "Invalid release version: $raw" >&2 + exit 1 + fi + + build_timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "tag=v$version" >> "$GITHUB_OUTPUT" + echo "build_timestamp=$build_timestamp" >> "$GITHUB_OUTPUT" + + if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "release_channel=stable" >> "$GITHUB_OUTPUT" + else + echo "release_channel=prerelease" >> "$GITHUB_OUTPUT" + fi + + ios_signing_preflight: + name: iOS signing preflight + needs: [preflight] + runs-on: ubuntu-24.04 + steps: + - name: Check iOS signing secrets + shell: bash + env: + APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }} + IOS_PROVISIONING_PROFILE_NAME: ${{ secrets.IOS_PROVISIONING_PROFILE_NAME }} + run: | + set -euo pipefail + + required_secrets=( + APPLE_API_KEY + APPLE_API_KEY_ID + APPLE_API_ISSUER + APPLE_TEAM_ID + IOS_PROVISIONING_PROFILE + IOS_PROVISIONING_PROFILE_NAME + ) + + missing=() + for secret_name in "${required_secrets[@]}"; do + if [[ -z "${!secret_name}" ]]; then + missing+=("$secret_name") + fi + done + + if (( ${#missing[@]} > 0 )); then + missing_csv="$(IFS=,; echo "${missing[*]}")" + echo "Missing required iOS signing secrets: $missing_csv" >&2 + exit 1 + fi + + echo "All required iOS signing secrets are configured." + + ios_testflight: + name: iOS TestFlight + needs: [preflight, ios_signing_preflight] + runs-on: macos-14 + env: + RELEASE_VERSION: ${{ needs.preflight.outputs.version }} + OKCODE_COMMIT_HASH: ${{ github.sha }} + OKCODE_BUILD_TIMESTAMP: ${{ needs.preflight.outputs.build_timestamp }} + OKCODE_RELEASE_CHANNEL: ${{ needs.preflight.outputs.release_channel }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ needs.preflight.outputs.ref }} + fetch-depth: 0 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version-file: package.json + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version-file: package.json + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Patch Capacitor local-notifications for Xcode 15 + run: bun run patch:capacitor-local-notifications + + - name: Align package versions to release version + run: node scripts/update-release-package-versions.ts "$RELEASE_VERSION" + + - name: Update iOS version in Xcode project + run: node scripts/update-ios-version.ts "$RELEASE_VERSION" --build-number "$GITHUB_RUN_NUMBER" + + - name: Build mobile web bundle + run: bun run --cwd apps/mobile build + + - name: Sync Capacitor iOS + run: bunx cap sync ios --deployment + working-directory: apps/mobile + + - name: Log iOS build metadata + run: | + echo "version=$RELEASE_VERSION" + echo "tag=${{ needs.preflight.outputs.tag }}" + echo "commit=$OKCODE_COMMIT_HASH" + echo "build_timestamp=$OKCODE_BUILD_TIMESTAMP" + echo "channel=$OKCODE_RELEASE_CHANNEL" + + - name: Install App Store Connect API key and provisioning profile + env: + APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }} + IOS_PROVISIONING_PROFILE_NAME: ${{ secrets.IOS_PROVISIONING_PROFILE_NAME }} + run: | + set -euo pipefail + for secret_name in APPLE_API_KEY APPLE_API_KEY_ID APPLE_API_ISSUER IOS_PROVISIONING_PROFILE IOS_PROVISIONING_PROFILE_NAME; do + if [[ -z "${!secret_name}" ]]; then + echo "Missing required secret: $secret_name" >&2 + exit 1 + fi + done + + KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" + printf '%s' "$APPLE_API_KEY" > "$KEY_PATH" + echo "APPLE_API_KEY_PATH=$KEY_PATH" >> "$GITHUB_ENV" + + PROFILE_PATH="$RUNNER_TEMP/profile.mobileprovision" + echo "$IOS_PROVISIONING_PROFILE" | base64 --decode > "$PROFILE_PATH" + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/ + echo "Installed provisioning profile: $IOS_PROVISIONING_PROFILE_NAME" + + - name: Simulator smoke build + run: | + set -euo pipefail + xcodebuild build \ + -project apps/mobile/ios/App/App.xcodeproj \ + -scheme App \ + -configuration Debug \ + -destination 'platform=iOS Simulator,name=iPhone 15' \ + COMPILER_INDEX_STORE_ENABLE=NO + + - name: Build iOS archive + env: + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + run: | + set -euo pipefail + + xcodebuild archive \ + -project apps/mobile/ios/App/App.xcodeproj \ + -scheme App \ + -configuration Release \ + -destination 'generic/platform=iOS' \ + -archivePath "$RUNNER_TEMP/App.xcarchive" \ + -allowProvisioningUpdates \ + -authenticationKeyPath "$APPLE_API_KEY_PATH" \ + -authenticationKeyID "$APPLE_API_KEY_ID" \ + -authenticationKeyIssuerID "$APPLE_API_ISSUER" \ + CODE_SIGN_STYLE=Manual \ + DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \ + CODE_SIGN_IDENTITY="Apple Distribution" \ + PROVISIONING_PROFILE_SPECIFIER="${{ secrets.IOS_PROVISIONING_PROFILE_NAME }}" \ + COMPILER_INDEX_STORE_ENABLE=NO + + - name: Generate ExportOptions.plist + env: + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: | + cat > "$RUNNER_TEMP/ExportOptions.plist" < + + + + method + app-store-connect + destination + upload + teamID + ${APPLE_TEAM_ID} + uploadSymbols + + signingStyle + manual + provisioningProfiles + + com.openknots.okcode.mobile + ${{ secrets.IOS_PROVISIONING_PROFILE_NAME }} + + + + PLIST + + - name: Export IPA + env: + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + run: | + set -euo pipefail + xcodebuild -exportArchive \ + -archivePath "$RUNNER_TEMP/App.xcarchive" \ + -exportPath "$RUNNER_TEMP/export" \ + -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \ + -allowProvisioningUpdates \ + -authenticationKeyPath "$APPLE_API_KEY_PATH" \ + -authenticationKeyID "$APPLE_API_KEY_ID" \ + -authenticationKeyIssuerID "$APPLE_API_ISSUER" + + - name: Stage App Store Connect API key for upload + env: + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + run: | + set -euo pipefail + KEY_DIR="$HOME/private_keys" + mkdir -p "$KEY_DIR" + cp "$APPLE_API_KEY_PATH" "$KEY_DIR/AuthKey_${APPLE_API_KEY_ID}.p8" + + - name: Upload to TestFlight + env: + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} + run: | + set -euo pipefail + + IPA_FILE=$(find "$RUNNER_TEMP/export" -name "*.ipa" -print -quit) + if [[ -z "$IPA_FILE" ]]; then + echo "No IPA file found in export directory" >&2 + exit 1 + fi + + xcrun altool --upload-app \ + -f "$IPA_FILE" \ + -t ios \ + --apiKey "$APPLE_API_KEY_ID" \ + --apiIssuer "$APPLE_API_ISSUER" + + - name: Cleanup signing assets + if: always() + run: | + rm -f "$APPLE_API_KEY_PATH" || true + rm -f "$HOME/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8" || true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e621a2b0..6c9a7b7f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -279,240 +279,10 @@ jobs: path: release-publish/* if-no-files-found: error - ios_signing_preflight: - name: iOS signing preflight - needs: [preflight] - runs-on: ubuntu-24.04 - steps: - - name: Check iOS signing secrets - shell: bash - env: - APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} - APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }} - IOS_PROVISIONING_PROFILE_NAME: ${{ secrets.IOS_PROVISIONING_PROFILE_NAME }} - run: | - set -euo pipefail - - required_secrets=( - APPLE_API_KEY - APPLE_API_KEY_ID - APPLE_API_ISSUER - APPLE_TEAM_ID - IOS_PROVISIONING_PROFILE - IOS_PROVISIONING_PROFILE_NAME - ) - - missing=() - for secret_name in "${required_secrets[@]}"; do - if [[ -z "${!secret_name}" ]]; then - missing+=("$secret_name") - fi - done - - if (( ${#missing[@]} > 0 )); then - missing_csv="$(IFS=,; echo "${missing[*]}")" - echo "Missing required iOS signing secrets: $missing_csv" >&2 - exit 1 - fi - - echo "All required iOS signing secrets are configured." - - ios_testflight: - name: iOS TestFlight - needs: [preflight, ios_signing_preflight] - runs-on: macos-14 - env: - RELEASE_VERSION: ${{ needs.preflight.outputs.version }} - OKCODE_COMMIT_HASH: ${{ github.sha }} - OKCODE_BUILD_TIMESTAMP: ${{ needs.preflight.outputs.build_timestamp }} - OKCODE_RELEASE_CHANNEL: ${{ needs.preflight.outputs.release_channel }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ needs.preflight.outputs.ref }} - fetch-depth: 0 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version-file: package.json - - - name: Setup Node - uses: actions/setup-node@v6 - with: - node-version-file: package.json - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Patch Capacitor local-notifications for Xcode 15 - run: bun run patch:capacitor-local-notifications - - - name: Align package versions to release version - run: node scripts/update-release-package-versions.ts "$RELEASE_VERSION" - - - name: Update iOS version in Xcode project - run: node scripts/update-ios-version.ts "$RELEASE_VERSION" --build-number "$GITHUB_RUN_NUMBER" - - - name: Build mobile web bundle - run: bun run --cwd apps/mobile build - - - name: Sync Capacitor iOS - run: bunx cap sync ios --deployment - working-directory: apps/mobile - - - name: Log iOS build metadata - run: | - echo "version=$RELEASE_VERSION" - echo "commit=$OKCODE_COMMIT_HASH" - echo "build_timestamp=$OKCODE_BUILD_TIMESTAMP" - echo "channel=$OKCODE_RELEASE_CHANNEL" - - - name: Install App Store Connect API key and provisioning profile - env: - APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} - APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - IOS_PROVISIONING_PROFILE: ${{ secrets.IOS_PROVISIONING_PROFILE }} - IOS_PROVISIONING_PROFILE_NAME: ${{ secrets.IOS_PROVISIONING_PROFILE_NAME }} - run: | - set -euo pipefail - for secret_name in APPLE_API_KEY APPLE_API_KEY_ID APPLE_API_ISSUER IOS_PROVISIONING_PROFILE IOS_PROVISIONING_PROFILE_NAME; do - if [[ -z "${!secret_name}" ]]; then - echo "Missing required secret: $secret_name" >&2 - exit 1 - fi - done - - KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" - printf '%s' "$APPLE_API_KEY" > "$KEY_PATH" - echo "APPLE_API_KEY_PATH=$KEY_PATH" >> "$GITHUB_ENV" - - PROFILE_PATH="$RUNNER_TEMP/profile.mobileprovision" - echo "$IOS_PROVISIONING_PROFILE" | base64 --decode > "$PROFILE_PATH" - mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles - cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/ - echo "Installed provisioning profile: $IOS_PROVISIONING_PROFILE_NAME" - - - name: Simulator smoke build - run: | - set -euo pipefail - xcodebuild build \ - -project apps/mobile/ios/App/App.xcodeproj \ - -scheme App \ - -configuration Debug \ - -destination 'platform=iOS Simulator,name=iPhone 15' \ - COMPILER_INDEX_STORE_ENABLE=NO - - - name: Build iOS archive - env: - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - run: | - set -euo pipefail - - xcodebuild archive \ - -project apps/mobile/ios/App/App.xcodeproj \ - -scheme App \ - -configuration Release \ - -destination 'generic/platform=iOS' \ - -archivePath "$RUNNER_TEMP/App.xcarchive" \ - -allowProvisioningUpdates \ - -authenticationKeyPath "$APPLE_API_KEY_PATH" \ - -authenticationKeyID "$APPLE_API_KEY_ID" \ - -authenticationKeyIssuerID "$APPLE_API_ISSUER" \ - CODE_SIGN_STYLE=Manual \ - DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \ - CODE_SIGN_IDENTITY="Apple Distribution" \ - PROVISIONING_PROFILE_SPECIFIER="${{ secrets.IOS_PROVISIONING_PROFILE_NAME }}" \ - COMPILER_INDEX_STORE_ENABLE=NO - - - name: Generate ExportOptions.plist - env: - APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - run: | - cat > "$RUNNER_TEMP/ExportOptions.plist" < - - - - method - app-store-connect - destination - upload - teamID - ${APPLE_TEAM_ID} - uploadSymbols - - signingStyle - manual - provisioningProfiles - - com.openknots.okcode.mobile - ${{ secrets.IOS_PROVISIONING_PROFILE_NAME }} - - - - PLIST - - - name: Export IPA - env: - APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - run: | - set -euo pipefail - xcodebuild -exportArchive \ - -archivePath "$RUNNER_TEMP/App.xcarchive" \ - -exportPath "$RUNNER_TEMP/export" \ - -exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \ - -allowProvisioningUpdates \ - -authenticationKeyPath "$APPLE_API_KEY_PATH" \ - -authenticationKeyID "$APPLE_API_KEY_ID" \ - -authenticationKeyIssuerID "$APPLE_API_ISSUER" - - - name: Stage App Store Connect API key for upload - env: - APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - run: | - set -euo pipefail - KEY_DIR="$HOME/private_keys" - mkdir -p "$KEY_DIR" - cp "$APPLE_API_KEY_PATH" "$KEY_DIR/AuthKey_${APPLE_API_KEY_ID}.p8" - - - name: Upload to TestFlight - env: - APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - run: | - set -euo pipefail - - IPA_FILE=$(find "$RUNNER_TEMP/export" -name "*.ipa" -print -quit) - if [[ -z "$IPA_FILE" ]]; then - echo "No IPA file found in export directory" >&2 - exit 1 - fi - - xcrun altool --upload-app \ - -f "$IPA_FILE" \ - -t ios \ - --apiKey "$APPLE_API_KEY_ID" \ - --apiIssuer "$APPLE_API_ISSUER" - - - name: Cleanup signing assets - if: always() - run: | - rm -f "$APPLE_API_KEY_PATH" || true - rm -f "$HOME/private_keys/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8" || true - publish_cli: name: Publish CLI - needs: [preflight, desktop_build, ios_signing_preflight, ios_testflight] - if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.desktop_build.result == 'success' && needs.ios_signing_preflight.result == 'success' && needs.ios_testflight.result == 'success' }} + needs: [preflight, desktop_build] + if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.desktop_build.result == 'success' }} runs-on: ubuntu-24.04 env: NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} @@ -577,8 +347,8 @@ jobs: release: name: Publish GitHub Release - needs: [preflight, desktop_build, ios_signing_preflight, ios_testflight, publish_cli] - if: ${{ always() && !cancelled() && needs.preflight.result == 'success' && needs.desktop_build.result == 'success' && needs.ios_signing_preflight.result == 'success' && needs.ios_testflight.result == 'success' && needs.publish_cli.result == 'success' }} + needs: [preflight, desktop_build, publish_cli] + if: ${{ always() && !cancelled() && needs.preflight.result == 'success' && needs.desktop_build.result == 'success' && needs.publish_cli.result == 'success' }} runs-on: ubuntu-24.04 steps: - name: Checkout diff --git a/README.md b/README.md index 95df45bbf..3029afd5c 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ Notes: Release is driven by `.github/workflows/release.yml` and the canonical runbook in [`docs/release.md`](/Users/buns/.okcode/worktrees/okcode/okcode-1c7a5554/docs/release.md). -- The stable train now coordinates desktop, iOS TestFlight, and `okcodes` CLI on one version. +- Release tags publish desktop artifacts and `okcodes` from `release.yml`, while `release-ios.yml` uploads the matching iOS TestFlight build separately. - Preflight runs format, lint, typecheck, tests, browser tests, desktop smoke, and release smoke. - The separate Intel mac workflow is compatibility-only and non-blocking. - Publishing still requires release notes and an asset manifest for the tagged version. @@ -236,7 +236,7 @@ Release is driven by `.github/workflows/release.yml` and the canonical runbook i 2. Confirm macOS, Windows, Linux, iOS TestFlight, and CLI release inputs are ready. 3. Confirm signing secrets availability for macOS/Windows targets. 4. Confirm `docs/releases/v.md` and `docs/releases/v/assets.md` exist. -5. Trigger release and monitor all jobs. +5. Trigger the desktop/CLI release and monitor both the desktop/CLI and iOS workflows. ## 12) Contributing expectations diff --git a/docs/release.md b/docs/release.md index 82e5b3e0a..0b224ba26 100644 --- a/docs/release.md +++ b/docs/release.md @@ -2,11 +2,11 @@ Canonical release process documentation for OK Code. -**Last updated:** 2026-04-05 +**Last updated:** 2026-04-20 ## Overview -The next stable train ships one semver across all blocking surfaces: +The next stable train ships one semver across desktop, CLI, and iOS surfaces: - macOS arm64 desktop DMG plus updater metadata - Windows x64 signed NSIS installer @@ -30,20 +30,28 @@ The next stable train ships one semver across all blocking surfaces: - Publish prereleases to npm with the `next` dist-tag. - Publish stable releases to npm with the `latest` dist-tag. -## Coordinated release workflow +## Release workflows -Official releases are cut through [`release.yml`](../.github/workflows/release.yml). +Official release tags now fan out into two workflows: -Job order: +- [`release.yml`](../.github/workflows/release.yml) for desktop artifacts, npm publish, GitHub Release publication, and finalize. +- [`release-ios.yml`](../.github/workflows/release-ios.yml) for the matching TestFlight upload from the same tag. + +`release.yml` job order: 1. `preflight` 2. `desktop_build` +3. `publish_cli` +4. `release` +5. `finalize` + +`release-ios.yml` job order: + +1. `preflight` +2. `ios_signing_preflight` 3. `ios_testflight` -4. `publish_cli` -5. `release` -6. `finalize` -The GitHub release must not publish until every blocking surface succeeds. +Desktop/CLI publishing must not be blocked by iOS signing availability or TestFlight upload retries. ## Required checks @@ -80,7 +88,7 @@ Blocking stable matrix: | macOS arm64 | `macos-14` | signed/notarized DMG + updater metadata | yes | | Windows x64 | `windows-2022` | signed NSIS installer | yes | | Linux x64 | `ubuntu-24.04` | AppImage | yes | -| iOS | `macos-14` | TestFlight upload | yes | +| iOS | `macos-14` | TestFlight upload | separate | | CLI | `ubuntu-24.04` | npm publish | yes | Non-blocking compatibility lane: @@ -105,7 +113,7 @@ Non-blocking compatibility lane: - Reuse the same release version as desktop and CLI. - Build the mobile web bundle and sync Capacitor before archiving. - Run a simulator build in CI before archive/upload. -- Upload the archive to TestFlight from the coordinated release workflow. +- Upload the archive to TestFlight from the dedicated `release-ios.yml` workflow. - During RC soak, manually verify on: - one current supported iPhone/iOS - one older supported iPhone/iOS @@ -124,7 +132,7 @@ Manual RC device checks: - Build the CLI package from `apps/server`. - Verify `npm pack`. - Verify local `okcode --version`, `okcode --help`, and `okcode doctor --help`. -- Publish only after desktop and iOS blockers pass. +- Publish after desktop artifacts pass; do not wait on the separate iOS workflow. - Verify the published package with `npx okcodes@ --version`. ## Release preparation @@ -167,12 +175,12 @@ If any blocker fails, cut a new RC and repeat the soak. ## Post-release expectations - The GitHub release includes desktop artifacts plus release notes and asset manifest. -- iOS is distributed through TestFlight, not attached to the GitHub release. +- iOS is distributed through TestFlight by `release-ios.yml`, not attached to the GitHub release. - `finalize` updates version strings and pushes the post-release bump to `main`. ## Troubleshooting - If `preflight` fails, reproduce locally with the exact failing command before retriggering the workflow. - If `desktop_build` fails, inspect the target-specific signing secrets first. -- If `ios_testflight` fails, re-check provisioning, App Store Connect API key setup, and archive export logs. +- If `ios_testflight` fails, re-check provisioning, App Store Connect API key setup, and archive/export logs in `release-ios.yml`. - If `publish_cli` fails, do not continue the train. Fix the publish issue so the app and CLI do not drift. diff --git a/docs/releases/v0.26.0/rollout-checklist.md b/docs/releases/v0.26.0/rollout-checklist.md index 8a88dc77e..7bf8df048 100644 --- a/docs/releases/v0.26.0/rollout-checklist.md +++ b/docs/releases/v0.26.0/rollout-checklist.md @@ -35,7 +35,9 @@ Step-by-step playbook for the v0.26.0 release. Each phase must complete before a - [ ] Push the release-prep commit to `main`. - [ ] Create and push tag `v0.26.0`. - [ ] Verify the coordinated `release.yml` workflow starts. -- [ ] Monitor the pipeline through Preflight, Desktop builds, iOS signing preflight, iOS TestFlight, Publish CLI, Publish GitHub Release, and Finalize release. +- [ ] Verify the separate `release-ios.yml` workflow starts for `v0.26.0`, or trigger it manually with the same version if needed. +- [ ] Monitor `release.yml` through Preflight, Desktop builds, Publish CLI, Publish GitHub Release, and Finalize release. +- [ ] Monitor `release-ios.yml` through Preflight, iOS signing preflight, and iOS TestFlight. ### Asset verification diff --git a/scripts/prepare-release.ts b/scripts/prepare-release.ts index e9c98f614..c6d84af0f 100644 --- a/scripts/prepare-release.ts +++ b/scripts/prepare-release.ts @@ -263,7 +263,7 @@ ${highlights || "- See changelog for detailed changes."} ## Upgrade and install -- **CLI:** \`npm install -g okcodes@${version}\` once the coordinated release workflow finishes. +- **CLI:** \`npm install -g okcodes@${version}\` once the desktop/CLI release workflow finishes. - **Desktop:** Download from [GitHub Releases](${REPO_URL}/releases/tag/v${version}). Filenames are listed in [assets.md](v${version}/assets.md). - **iOS:** Available via TestFlight (uploaded automatically by the Release iOS workflow). @@ -274,7 +274,7 @@ OK Code remains early work in progress. Expect rough edges around session recove ## Release operations - Review the [asset manifest](v${version}/assets.md) to confirm every expected GitHub Release attachment is present. -- Use the [rollout checklist](v${version}/rollout-checklist.md) to walk the coordinated release from preflight through post-release verification. +- Use the [rollout checklist](v${version}/rollout-checklist.md) to walk the desktop/CLI release plus the matching iOS TestFlight workflow through post-release verification. - Use the [soak test plan](v${version}/soak-test-plan.md) to validate the highest-risk surfaces after the tag is live. `; } @@ -378,7 +378,9 @@ Step-by-step playbook for the v${version} release. Each phase must complete befo - [ ] Push the release-prep commit to \`main\`. - [ ] Create and push tag \`v${version}\`. - [ ] Verify the coordinated \`release.yml\` workflow starts. -- [ ] Monitor the pipeline through Preflight, Desktop builds, iOS signing preflight, iOS TestFlight, Publish CLI, Publish GitHub Release, and Finalize release. +- [ ] Verify the separate \`release-ios.yml\` workflow starts for the same tag or trigger it manually with the same version if needed. +- [ ] Monitor \`release.yml\` through Preflight, Desktop builds, Publish CLI, Publish GitHub Release, and Finalize release. +- [ ] Monitor \`release-ios.yml\` through Preflight, iOS signing preflight, and iOS TestFlight. ### Asset verification