Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,18 @@ jobs:
name: Test
runs-on: ubuntu-24.04
needs: [build-rust]
env:
SUPPLY_TRACK: production # used by fastlane to determine track to publish to

steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'

# Cheap, no SDK: keeps the Play-track contract from regressing on every PR.
# The leftover job-level `SUPPLY_TRACK: production` here never reached
# fastlane (release-fastlane sets its own) and was a copy-paste footgun.
- name: Assert Play-track resolver
run: bash scripts/assert-play-track.sh --self-test

- name: Set up JDK
uses: actions/setup-java@v4
with:
Expand Down Expand Up @@ -412,15 +416,18 @@ jobs:
# Set SUPPLY_TRACK based on whether this is a stable (x.y.z) or pre-release tag.
# NOTE: nowsprinting/check-version-format-action classifies 0.x.x as not stable
# (semver convention), but aw-android uses 0.x.x for production releases.
# Check for pre-release suffixes directly instead.
# Check for pre-release suffixes directly instead (scripts/assert-play-track.sh).
TAG="${{ github.ref_name }}"
VERSION="${TAG#v}"
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
SUPPLY_TRACK="production"
else
SUPPLY_TRACK="internal"
fi
echo "SUPPLY_TRACK=${SUPPLY_TRACK}" >> $GITHUB_ENV
SUPPLY_TRACK="$(bash scripts/assert-play-track.sh resolve "$TAG")"
echo "SUPPLY_TRACK=${SUPPLY_TRACK}" >> "$GITHUB_ENV"
{
echo "## Play Store publish"
echo "- tag: \`${TAG}\`"
echo "- SUPPLY_TRACK: \`${SUPPLY_TRACK}\`"
} >> "$GITHUB_STEP_SUMMARY"

- name: Assert Play track matches tag
run: bash scripts/assert-play-track.sh assert "${{ github.ref_name }}" "${SUPPLY_TRACK}"

- uses: adnsio/setup-age-action@v1.2.0
- name: Load Android secrets
Expand All @@ -438,8 +445,12 @@ jobs:
# Newer fastlane versions fail hard if rubygems.org isn't in the system gem sources list.
# This is idempotent (exits 0 if already present).
gem sources --add https://rubygems.org
# Re-check at the publish boundary, then pass the asserted track on
# argv. supply's default track is production; SUPPLY_TRACK via env is
# the historical path, but an explicit --track is the fail-closed one.
bash scripts/assert-play-track.sh assert "${{ github.ref_name }}" "${SUPPLY_TRACK}"
Comment thread
TimeToBuildBob marked this conversation as resolved.
# bundle exec fastlane supply run --apk dist/*.apk
bundle exec fastlane supply run --aab dist/*.aab
bundle exec fastlane supply run --aab dist/*.aab --track "$SUPPLY_TRACK"

release-gh:
needs: [build-apk, test]
Expand Down
139 changes: 139 additions & 0 deletions scripts/assert-play-track.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/env bash
# Resolve or assert the Play Store supply track for an aw-android release tag.
#
# Contract:
# X.Y.Z (no suffix) → production
# anything else → internal (0.14.0b2, 0.14.0devYYYYMMDD, 0.14.0-rc1, …)
#
# Fail closed: a pre-release tag must never publish to production, even if
# SUPPLY_TRACK is later hardcoded or the resolver regresses. Stable tags may
# still be sent to internal (staged rollout); that is not this guard.
#
# Usage:
# scripts/assert-play-track.sh resolve <tag>
# scripts/assert-play-track.sh assert <tag> <track>
# scripts/assert-play-track.sh --self-test

set -euo pipefail

usage() {
cat <<'EOF' >&2
Usage:
scripts/assert-play-track.sh resolve <tag>
scripts/assert-play-track.sh assert <tag> <track>
scripts/assert-play-track.sh --self-test
EOF
exit 2
}

is_stable_version() {
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
}

tag_to_version() {
local tag="$1"
if [[ -z "$tag" ]]; then
echo "error: empty tag" >&2
return 1
fi
echo "${tag#v}"
}

resolve_track() {
local version
version="$(tag_to_version "$1")"
if is_stable_version "$version"; then
echo production
else
echo internal
fi
}

assert_track() {
local tag="$1"
local track="$2"
local version
version="$(tag_to_version "$tag")"

if [[ -z "$track" ]]; then
echo "error: SUPPLY_TRACK is empty; refusing to publish ${tag}" >&2
return 1
fi
if [[ "$track" != production && "$track" != internal ]]; then
echo "error: unknown SUPPLY_TRACK=${track} for ${tag}" >&2
return 1
fi
if [[ "$track" == production ]] && ! is_stable_version "$version"; then
echo "error: refusing to publish pre-release tag ${tag} to production (SUPPLY_TRACK=${track})" >&2
return 1
fi
echo "ok: tag=${tag} version=${version} SUPPLY_TRACK=${track}"
}

self_test() {
local fail=0
expect_resolve() {
local tag="$1" want="$2" got
got="$(resolve_track "$tag")"
if [[ "$got" != "$want" ]]; then
echo "FAIL resolve ${tag}: got ${got} want ${want}" >&2
fail=1
fi
}
expect_assert_ok() {
if ! assert_track "$1" "$2" >/dev/null; then
echo "FAIL assert should pass: tag=$1 track=$2" >&2
fail=1
fi
}
expect_assert_fail() {
if assert_track "$1" "$2" >/dev/null 2>&1; then
echo "FAIL assert should fail: tag=$1 track=$2" >&2
fail=1
fi
}

expect_resolve v0.14.0 production
expect_resolve 0.14.0 production
expect_resolve v1.0.0 production
expect_resolve v0.14.0b2 internal
expect_resolve v0.14.0beta2 internal
expect_resolve v0.14.0dev20260723 internal
expect_resolve v0.14.0-rc1 internal
expect_resolve v0.14.0rc1 internal
expect_resolve v0.14 internal

expect_assert_ok v0.14.0 production
expect_assert_ok v0.14.0 internal
expect_assert_ok v0.14.0b2 internal
expect_assert_fail v0.14.0b2 production
expect_assert_fail v0.14.0dev20260723 production
expect_assert_fail v0.14.0beta2 production
expect_assert_fail v0.14.0 ""
expect_assert_fail v0.14.0 alpha
expect_assert_fail "" production

if [[ "$fail" -ne 0 ]]; then
echo "assert-play-track self-test FAILED" >&2
return 1
fi
echo "assert-play-track self-test passed"
}

cmd="${1:-}"
case "$cmd" in
resolve)
[[ $# -eq 2 ]] || usage
resolve_track "$2"
;;
assert)
[[ $# -eq 3 ]] || usage
assert_track "$2" "$3"
;;
--self-test)
self_test
;;
*)
usage
;;
esac
Loading