diff --git a/ISSUE-535.md b/ISSUE-535.md new file mode 100644 index 0000000..80bab13 --- /dev/null +++ b/ISSUE-535.md @@ -0,0 +1,69 @@ +## Background — what already exists in this repo + +The Android AAB build-and-upload pipeline is already fully automated. The user does **not** need to drop a file into Play Console at all — the same machinery that publishes to `internal` can publish to the closed-testing track. + +- `ci/main.go:952` — `PublishAndroid()` builds a release AAB, stamps the versionCode (`time.Now().Unix()`), re-signs with the upload key, and uploads to Play Store. +- `ci/main.go:899` — `UploadToPlayStore()` invokes `scripts/deploy_playstore.py` inside a container. +- `scripts/deploy_playstore.py:14` — pinned to `TRACK = "internal"`. This is the only thing limiting the existing pipeline to internal testing. +- `Taskfile.yml:230` — `task publish-android` wraps the Dagger call. +- `.forgejo/workflows/deploy.yml` — `deploy-playstore` job runs every hour (`0 * * * *`) and calls `task publish-android` whenever android-relevant files changed since the last successful deploy. +- `Taskfile.yml:214` — `task build-android-bundle` builds an **unsigned** AAB locally to `build/app/outputs/bundle/release/app-release.aab`. The signed AAB built by `PublishAndroid` lives only inside Dagger and is **not** currently exported to disk on the runner. + +## Recommended path (A): publish to the closed-testing track from CI — no manual upload + +This piggybacks on what's already running every hour. + +1. **In Play Console**, decide which track maps to "closed testing": + - The built-in `alpha` track is treated as closed testing by the API. + - Or create a custom closed-testing track (e.g. `closed-testers`) under Testing → Closed testing → Create track. Configure the testers list there. Note the track ID exactly as it appears in the URL/console. + +2. **Edit `scripts/deploy_playstore.py`** so the upload goes to both internal and the closed track in the same Play edit (one commit, atomic). Concretely: + - Replace `TRACK = "internal"` (line 14) with `TRACKS = ["internal", "alpha"]` (or `["internal", ""]`). + - Replace the single `track_resp = session.put(... /tracks/{TRACK} ...)` block (lines 97-101) with a loop that PUTs the same `versionCodes: [version_code]` payload to each track in `TRACKS` before the commit at line 104. + - Internal stays as the smoke-test target; the closed track is what gets the testers. + +3. **Update the test fixture** in `scripts/test_deploy_playstore.py` (and any test for `verify_playstore_deploy.py`) to expect the new track list. Run `python3 scripts/test_deploy_playstore.py` locally. + +4. **Trigger a deploy**: push the change to `main` and either wait for the next hourly run or `workflow_dispatch` `.forgejo/workflows/deploy.yml` from the Forgejo UI. Note that `deploy.yml:114` already includes `scripts/deploy_playstore\.py` in the change-detection regex, so any normal hourly tick after the merge will pick it up. + +5. **Result**: closed-testing release appears in Play Console with the AAB pre-attached. Reviewers only need to edit the release notes (already generated by `task generate-changelog` — see `Taskfile.yml:232`) and click "Roll out". No "Drop app bundles here" step. + +Caveat — first time a track is used, Google requires that internal testing has been previously published; that is already true here, so no blocker. + +## Alternative path (B): one-shot manual drop — export the signed AAB as a CI artifact + +Use this if you want to do this single closed-testing release by hand and decide on full automation later. + +1. **Modify `ci/main.go`** to expose the signed AAB as a file return: add a thin wrapper exporting the result of `SignAndroidBundle(StampAndroidVersionCode(BuildAndroidRelease(commitHash), versionCode), ...)` as `*dagger.File` (something like `SignedAndroidBundle(...) *dagger.File`). The existing `PublishAndroid` discards the signed bundle after upload, so a separate entry point is cleaner than reshaping it. + +2. **Add a `Taskfile.yml` target** `build-android-bundle-signed` that calls the new Dagger function with `-o build/app/outputs/bundle/release/app-release.aab` (mirroring `build-android-bundle` at `Taskfile.yml:218`). + +3. **Add to `.forgejo/workflows/deploy.yml` `deploy-playstore` job** (after `task publish-android`) — or to a new dedicated job — these two steps: + ```yaml + - name: Export signed AAB locally + env: { DAGGER_NO_NAG: "1" } + run: task build-android-bundle-signed + - name: Upload AAB artifact + uses: actions/upload-artifact@v4 + with: + name: app-release-aab + path: build/app/outputs/bundle/release/app-release.aab + if-no-files-found: error + ``` + +4. **Trigger the workflow** (`workflow_dispatch`), then download `app-release-aab` from the run's artifacts page on Forgejo. Drop it into Play Console. + +Risk to flag: the version code stamped via the artifact path must be strictly greater than every version code already on internal. Since the same Unix-timestamp scheme is used everywhere this naturally holds, but if the artifact run and a regular publish run race, the closed release may fail with "Version code N has already been used." Easy fix: only run one or the other. + +## Alternative path (C): build locally — fastest one-off + +```bash +export ANDROID_KEYSTORE_BASE64=... # same secret used by CI +bash scripts/build_android_bundle_local.sh +``` + +AAB lands at `build/app/outputs/bundle/release/app-release.aab`. Note this script uses `fvm flutter build appbundle` directly and does **not** match the Dagger pipeline's stamp/sign step exactly — the keystore path is wired via `ANDROID_KEYSTORE_PATH` and the signing config in `android/app/build.gradle.kts` reads from there. Verify the bundle is correctly signed (`jarsigner -verify -verbose -certs`) before uploading. + +## Recommendation + +Go with path A. The diff is small (~10 lines in `deploy_playstore.py` + test update), reuses the build-and-sign pipeline that has already been proven in `deploy.yml` for months, and removes the "Drop app bundles here" step entirely instead of just satisfying it once.