Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.319.0 to 1.321.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/ruby/setup-ruby/releases">ruby/setup-ruby's releases</a>.</em></p> <blockquote> <h2>v1.321.0</h2> <h2>What's Changed</h2> <ul> <li>Add jruby-10.1.1.0 by <a href="https://github.com/ruby-builder-bot"><code>@ruby-builder-bot</code></a> in <a href="https://redirect.github.com/ruby/setup-ruby/pull/932">ruby/setup-ruby#932</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/ruby/setup-ruby/compare/v1.320.0...v1.321.0">https://github.com/ruby/setup-ruby/compare/v1.320.0...v1.321.0</a></p> <h2>v1.320.0</h2> <h2>What's Changed</h2> <ul> <li>Update CRuby releases on Windows by <a href="https://github.com/ruby-builder-bot"><code>@ruby-builder-bot</code></a> in <a href="https://redirect.github.com/ruby/setup-ruby/pull/931">ruby/setup-ruby#931</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/ruby/setup-ruby/compare/v1.319.0...v1.320.0">https://github.com/ruby/setup-ruby/compare/v1.319.0...v1.320.0</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/ruby/setup-ruby/commit/95ef2b042f9d7a56d8268cba8559e2842e2ad01b"><code>95ef2b0</code></a> Add jruby-10.1.1.0</li> <li><a href="https://github.com/ruby/setup-ruby/commit/a30dfa457ad68707b8b910ac3a244714b61c0626"><code>a30dfa4</code></a> Update CRuby releases on Windows</li> <li>See full diff in <a href="https://github.com/ruby/setup-ruby/compare/003a5c4d8d6321bd302e38f6f0ec593f77f06600...95ef2b042f9d7a56d8268cba8559e2842e2ad01b">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
191 lines
9.2 KiB
YAML
191 lines
9.2 KiB
YAML
name: iOS App Store Candidate
|
|
|
|
on:
|
|
push:
|
|
tags: ["ios-v*"]
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
|
|
concurrency:
|
|
group: ios-app-store-candidate
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
resolve:
|
|
name: Resolve exact tested build
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 15
|
|
outputs:
|
|
enabled: ${{ steps.resolve.outputs.enabled }}
|
|
source_sha: ${{ steps.resolve.outputs.source_sha }}
|
|
version: ${{ steps.resolve.outputs.version }}
|
|
build_number: ${{ steps.manifest.outputs.build_number }}
|
|
app_store_build_id: ${{ steps.manifest.outputs.app_store_build_id }}
|
|
steps:
|
|
- name: Checkout tagged source
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Validate protected tag and release version
|
|
id: resolve
|
|
shell: bash
|
|
env:
|
|
AUTOMATION_ENABLED: ${{ vars.APP_STORE_AUTOMATION_ENABLED || 'false' }}
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "$GITHUB_REF_NAME" =~ ^ios-v([0-9]+\.[0-9]+\.[0-9]+)$ ]] || { echo "Tag must be ios-vX.Y.Z." >&2; exit 1; }
|
|
version="${BASH_REMATCH[1]}"
|
|
source_sha="$(git rev-parse HEAD)"
|
|
manifest_version="$(node -p "JSON.parse(require('fs').readFileSync('ios/release.json')).marketingVersion")"
|
|
[[ "$version" == "$manifest_version" ]] || { echo "Tag version $version does not match ios/release.json $manifest_version." >&2; exit 1; }
|
|
git show-ref --verify --quiet refs/remotes/origin/master || { echo "origin/master was not included in the full checkout." >&2; exit 1; }
|
|
git merge-base --is-ancestor "$source_sha" origin/master || { echo "Tagged commit is not reachable from master." >&2; exit 1; }
|
|
enabled=false
|
|
[[ "$AUTOMATION_ENABLED" == true ]] && enabled=true
|
|
echo "enabled=$enabled" >> "$GITHUB_OUTPUT"
|
|
echo "source_sha=$source_sha" >> "$GITHUB_OUTPUT"
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
if [[ "$enabled" != true ]]; then
|
|
echo "### Candidate promotion safely disabled" >> "$GITHUB_STEP_SUMMARY"
|
|
echo 'No App Store environment or credentials were accessed. Enable only after the signed canary.' >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
|
|
- name: Download exact TestFlight release manifest
|
|
if: steps.resolve.outputs.enabled == 'true'
|
|
id: manifest
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
SOURCE_SHA: ${{ steps.resolve.outputs.source_sha }}
|
|
EXPECTED_VERSION: ${{ steps.resolve.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
artifact_name="ios-release-manifest-$SOURCE_SHA"
|
|
response="$RUNNER_TEMP/ios-artifacts.json"
|
|
curl --fail --silent --show-error --location \
|
|
-H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
|
|
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/artifacts?name=$artifact_name&per_page=100" > "$response"
|
|
artifact_id="$(jq -r --arg sha "$SOURCE_SHA" '[.artifacts[] | select(.expired == false) | select(.workflow_run.head_sha == $sha)] | sort_by(.created_at) | last | .id // empty' "$response")"
|
|
[[ "$artifact_id" =~ ^[0-9]+$ ]] || { echo "No successful TestFlight release manifest exists for $SOURCE_SHA." >&2; exit 1; }
|
|
mkdir -p output/candidate
|
|
curl --fail --silent --show-error --location \
|
|
-H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
|
|
"$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/artifacts/$artifact_id/zip" -o "$RUNNER_TEMP/manifest.zip"
|
|
unzip -q "$RUNNER_TEMP/manifest.zip" -d output/candidate
|
|
MANIFEST=output/candidate/ios-release-manifest.json node <<'NODE'
|
|
const fs = require("node:fs");
|
|
const manifest = JSON.parse(fs.readFileSync(process.env.MANIFEST, "utf8"));
|
|
const checks = {
|
|
schema: manifest.schemaVersion === 1,
|
|
repository: manifest.repository === process.env.GITHUB_REPOSITORY,
|
|
source: manifest.sourceSha === process.env.SOURCE_SHA,
|
|
version: manifest.marketingVersion === process.env.EXPECTED_VERSION,
|
|
bundle: manifest.bundleId === "io.truckwash.app",
|
|
build: /^[1-9][0-9]*$/.test(manifest.buildNumber),
|
|
appStoreBuild: typeof manifest.appStoreBuildId === "string" && manifest.appStoreBuildId.length > 0,
|
|
};
|
|
const failed = Object.entries(checks).filter(([, ok]) => !ok).map(([name]) => name);
|
|
if (failed.length) throw new Error(`Invalid iOS release manifest: ${failed.join(", ")}`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `build_number=${manifest.buildNumber}\napp_store_build_id=${manifest.appStoreBuildId}\n`);
|
|
NODE
|
|
|
|
promote:
|
|
name: Sync and verify App Store candidate
|
|
needs: resolve
|
|
if: needs.resolve.outputs.enabled == 'true'
|
|
runs-on: macos-15
|
|
timeout-minutes: 60
|
|
environment: app-store-candidate
|
|
env:
|
|
IOS_SOURCE_SHA: ${{ needs.resolve.outputs.source_sha }}
|
|
IOS_MARKETING_VERSION: ${{ needs.resolve.outputs.version }}
|
|
IOS_BUILD_NUMBER: ${{ needs.resolve.outputs.build_number }}
|
|
EXPECTED_APP_STORE_BUILD_ID: ${{ needs.resolve.outputs.app_store_build_id }}
|
|
IOS_BUNDLE_ID: ${{ vars.IOS_BUNDLE_ID || 'io.truckwash.app' }}
|
|
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
|
|
APP_STORE_CONNECT_API_KEY_ID: ${{ vars.APP_STORE_CONNECT_API_KEY_ID }}
|
|
APP_STORE_CONNECT_ISSUER_ID: ${{ vars.APP_STORE_CONNECT_ISSUER_ID || '' }}
|
|
APP_STORE_CONNECT_APP_ID: ${{ vars.APP_STORE_CONNECT_APP_ID }}
|
|
APP_STORE_CONNECT_API_PRIVATE_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY_BASE64 }}
|
|
steps:
|
|
- name: Checkout exact candidate source
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
ref: ${{ env.IOS_SOURCE_SHA }}
|
|
persist-credentials: false
|
|
|
|
- name: Setup Ruby and pinned Fastlane
|
|
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1
|
|
with:
|
|
ruby-version: "3.3"
|
|
bundler-cache: true
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: 22
|
|
|
|
- name: Validate complete candidate storefront
|
|
run: node scripts/mobile/validate-app-store.mjs --strict
|
|
|
|
- name: Verify public storefront URLs
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
for file in support_url privacy_url marketing_url; do
|
|
url="$(tr -d '\r\n' < "fastlane/metadata/da-DK/$file.txt")"
|
|
curl --fail --silent --show-error --location --connect-timeout 10 --max-time 30 --output /dev/null "$url"
|
|
done
|
|
|
|
- name: Verify exact processed TestFlight build
|
|
run: node scripts/mobile/app-store-connect.mjs verify-candidate
|
|
|
|
- name: Sync metadata and screenshots without App Review submission
|
|
run: bundle exec fastlane ios prepare_candidate
|
|
|
|
- name: Configure automatic release after approval
|
|
run: node scripts/mobile/app-store-connect.mjs configure-release-policy
|
|
|
|
- name: Read back exact App Store candidate
|
|
id: readback
|
|
run: node scripts/mobile/app-store-connect.mjs verify-store-version
|
|
|
|
- name: Verify Denmark-only availability and no preorder
|
|
id: availability
|
|
run: node scripts/mobile/app-store-connect.mjs verify-availability
|
|
|
|
- name: Write candidate handoff
|
|
env:
|
|
APP_STORE_STATE: ${{ steps.readback.outputs.app_store_state }}
|
|
APP_STORE_VERSION_ID: ${{ steps.readback.outputs.app_store_version_id }}
|
|
RELEASE_TYPE: ${{ steps.readback.outputs.release_type }}
|
|
AVAILABLE_TERRITORIES: ${{ steps.availability.outputs.available_territories }}
|
|
run: |
|
|
echo "### iOS $IOS_MARKETING_VERSION candidate prepared" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "- Source: \`$IOS_SOURCE_SHA\`" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "- Exact tested build: \`$IOS_BUILD_NUMBER\` (\`$EXPECTED_APP_STORE_BUILD_ID\`)" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "- App Store state: \`$APP_STORE_STATE\`" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "- App Store version ID: \`$APP_STORE_VERSION_ID\`" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "- Release policy: \`$RELEASE_TYPE\`" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "- Availability: \`$AVAILABLE_TERRITORIES\` only; preorder disabled" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "- [Open the app in App Store Connect](https://appstoreconnect.apple.com/apps/$APP_STORE_CONNECT_APP_ID/appstore)" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "- App Review submission remains manual; Apple will release automatically after approval." >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
disabled:
|
|
name: Promotion disabled
|
|
needs: resolve
|
|
if: needs.resolve.outputs.enabled != 'true'
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- run: echo "App Store candidate promotion is disabled; no environment or credentials were accessed."
|