···11+# Fastlane Guide
22+33+This project includes a first-pass Fastlane setup for local and GitHub Actions usage.
44+55+## Scope
66+77+- Build artifacts (no production signing in this pass)
88+- Screenshot automation scaffolding for iOS and Android
99+- Manual GitHub Actions workflow dispatch for running lanes
1010+1111+## Prerequisites
1212+1313+- Flutter SDK
1414+- Ruby 3.x and Bundler
1515+- Xcode + iOS Simulator tooling (for iOS lanes)
1616+- Android SDK + emulator/device tooling (for Android lanes)
1717+1818+Install gem dependencies:
1919+2020+```sh
2121+bundle install
2222+```
2323+2424+Create local env file:
2525+2626+```sh
2727+cp .env.example .env
2828+```
2929+3030+## Local Commands
3131+3232+List lanes:
3333+3434+```sh
3535+bundle exec fastlane lanes
3636+```
3737+3838+Build lanes:
3939+4040+```sh
4141+bundle exec fastlane ios build
4242+bundle exec fastlane android build
4343+```
4444+4545+Screenshot lanes:
4646+4747+```sh
4848+bundle exec fastlane ios screenshots
4949+bundle exec fastlane android screenshots
5050+```
5151+5252+Equivalent `just` wrappers are available:
5353+5454+```sh
5555+just fastlane-lanes
5656+just fastlane-ios-build
5757+just fastlane-android-build
5858+just fastlane-ios-screenshots
5959+just fastlane-android-screenshots
6060+```
6161+6262+## Authentication for Screenshots
6363+6464+The app is auth-gated, so screenshot lanes currently assume a dedicated test account.
6565+6666+Required environment variables:
6767+6868+- `BSKY_TEST_HANDLE`
6969+- `BSKY_TEST_APP_PASSWORD`
7070+7171+Current state:
7272+7373+- iOS screenshots require `ios/RunnerUITests` to exist and perform app-password login before calling `snapshot(...)`.
7474+- Android screenshots require `android/app/src/androidTest` screengrab tests that log in and call `Screengrab.screenshot(...)`.
7575+7676+Both lanes fail fast with actionable TODO messages until those test hooks are implemented.
7777+7878+## Build Artifacts
7979+8080+Fastlane copies outputs to:
8181+8282+- iOS: `build/fastlane_artifacts/ios`
8383+- Android APK: `build/fastlane_artifacts/android/apk`
8484+- Android AAB: `build/fastlane_artifacts/android/aab`
8585+8686+## GitHub Actions
8787+8888+Workflow file: `.github/workflows/fastlane.yml`
8989+9090+Trigger mode:
9191+9292+- `workflow_dispatch` only
9393+- inputs:
9494+ - `platform`: `ios` or `android`
9595+ - `lane`: `build` or `screenshots`
9696+9797+The workflow uploads any generated artifacts/screenshots.
9898+9999+## Keys and Secrets TODOs
100100+101101+This setup intentionally defers production signing/distribution. Add these secrets later as you enable those lanes.
102102+103103+### iOS/App Store Connect
104104+105105+- `ASC_KEY_ID` (TODO)
106106+- `ASC_ISSUER_ID` (TODO)
107107+- `ASC_PRIVATE_KEY` (TODO)
108108+- `MATCH_GIT_URL` (TODO if using match)
109109+- `MATCH_PASSWORD` (TODO if using match)
110110+111111+Current optional iOS identity variables:
112112+113113+- `IOS_APP_IDENTIFIER`
114114+- `APP_STORE_CONNECT_APPLE_ID`
115115+- `APPLE_DEVELOPER_TEAM_ID`
116116+- `APP_STORE_CONNECT_TEAM_ID`
117117+118118+### Android/Play
119119+120120+- `ANDROID_KEYSTORE_BASE64` (TODO)
121121+- `ANDROID_KEYSTORE_PASSWORD` (TODO)
122122+- `ANDROID_KEY_ALIAS` (TODO)
123123+- `ANDROID_KEY_PASSWORD` (TODO)
124124+- `PLAY_SERVICE_ACCOUNT_JSON` (TODO)
125125+126126+Current optional Android variable:
127127+128128+- `ANDROID_APP_IDENTIFIER`
129129+130130+## How to Obtain Non-Bluesky Keys/Secrets
131131+132132+This section covers only Apple/Google signing and store automation credentials.
133133+134134+### App Store Connect API (`ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_PRIVATE_KEY`)
135135+136136+1. Sign in to App Store Connect with an account that can manage API keys.
137137+2. Open `Users and Access` -> `Integrations` -> `App Store Connect API`.
138138+3. Create a Team API key with the minimum role needed for release automation.
139139+4. Download the `.p8` key file immediately (Apple allows a one-time download).
140140+5. Save values:
141141+ - `ASC_KEY_ID`: Key ID shown for the generated key.
142142+ - `ASC_ISSUER_ID`: Issuer ID shown in the API section.
143143+ - `ASC_PRIVATE_KEY`: The full `.p8` file content stored as a GitHub secret.
144144+145145+### Apple Team/App Identity (`APPLE_DEVELOPER_TEAM_ID`, `APP_STORE_CONNECT_TEAM_ID`, `APP_STORE_CONNECT_APPLE_ID`)
146146+147147+1. `APPLE_DEVELOPER_TEAM_ID`:
148148+ - Get this from Apple Developer account membership details.
149149+2. `APP_STORE_CONNECT_TEAM_ID`:
150150+ - Use your App Store Connect team ID from account/team settings.
151151+3. `APP_STORE_CONNECT_APPLE_ID`:
152152+ - Use the Apple ID email used for App Store Connect automation ownership.
153153+154154+### Android Signing (`ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD`)
155155+156156+1. Generate a release keystore (example):
157157+158158+ ```sh
159159+ keytool -genkeypair -v \
160160+ -keystore upload-keystore.jks \
161161+ -alias upload \
162162+ -keyalg RSA -keysize 2048 -validity 10000
163163+ ```
164164+165165+2. Base64 encode the keystore for GitHub secret storage:
166166+167167+ ```sh
168168+ base64 -i upload-keystore.jks | pbcopy
169169+ ```
170170+171171+3. Store secrets:
172172+ - `ANDROID_KEYSTORE_BASE64`: base64 value of `.jks`.
173173+ - `ANDROID_KEYSTORE_PASSWORD`: keystore password.
174174+ - `ANDROID_KEY_ALIAS`: alias used at creation (example `upload`).
175175+ - `ANDROID_KEY_PASSWORD`: key password (may match keystore password).
176176+177177+### Google Play API (`PLAY_SERVICE_ACCOUNT_JSON`)
178178+179179+1. In Google Play Console, open `Users and permissions` and invite/link a service account from Google Cloud.
180180+2. In Google Cloud IAM, create a service account key (JSON) for that account.
181181+3. Grant least-privilege Play permissions needed for release automation.
182182+4. Store the JSON securely:
183183+ - Either the raw JSON as `PLAY_SERVICE_ACCOUNT_JSON` (multi-line secret), or
184184+ - base64-encode it and decode during workflow runtime.
185185+186186+### Optional Match Secrets (`MATCH_GIT_URL`, `MATCH_PASSWORD`)
187187+188188+1. Create a private certificates/profiles repository.
189189+2. Set `MATCH_GIT_URL` to that repository URL.
190190+3. Set `MATCH_PASSWORD` to the encryption passphrase used by match.
191191+192192+### Secret Hygiene
193193+194194+- Use repository or environment-level GitHub secrets, not committed files.
195195+- Rotate any secret immediately if leaked.
196196+- Restrict secret access to release workflows/environments.
197197+198198+## Notes
199199+200200+- CI artifact lanes are non-distribution lanes.
201201+- Adding signed IPA/AAB and store upload lanes is a follow-up once keys are provisioned.
+12
docs/tasks/fastlane.md
···11+# Fastlane Setup Tasks
22+33+- [x] Add Bundler + Fastlane scaffolding (`Gemfile`, `fastlane/Fastfile`, `fastlane/Appfile`)
44+- [x] Add iOS and Android build lanes for non-distribution artifacts
55+- [x] Add iOS and Android screenshot scaffolding lanes with auth + test-hook TODOs
66+- [x] Add manual-dispatch GitHub Actions workflow for Fastlane lanes
77+- [x] Add `docs/fastlane.md` with local + CI usage and secrets TODO guidance
88+- [x] Add `.env.example` for local Fastlane and signing/screenshot variables
99+- [x] Link Fastlane docs from `DEVELOPMENT.md`
1010+- [ ] Add `ios/RunnerUITests` snapshot coverage
1111+- [ ] Add `android/app/src/androidTest` screengrab coverage
1212+- [ ] Add production signing and store upload lanes once keys are provisioned
+17
fastlane/Appfile
···11+# iOS identifiers
22+app_identifier(ENV.fetch('IOS_APP_IDENTIFIER', 'org.stormlightlabs.lazurite'))
33+44+# TODO: Set APP_STORE_CONNECT_APPLE_ID when App Store Connect account ownership is finalized.
55+apple_id(ENV.fetch('APP_STORE_CONNECT_APPLE_ID', 'TODO_APPLE_ID@example.com'))
66+77+# TODO: Set APPLE_DEVELOPER_TEAM_ID if team changes from current default.
88+team_id(ENV.fetch('APPLE_DEVELOPER_TEAM_ID', '8TVR4TPL9Y'))
99+1010+# TODO: Set APP_STORE_CONNECT_TEAM_ID for App Store Connect API workflows (deliver/pilot).
1111+itc_team_id(ENV.fetch('APP_STORE_CONNECT_TEAM_ID', 'TODO_ITC_TEAM_ID'))
1212+1313+# Android identifiers
1414+package_name(ENV.fetch('ANDROID_APP_IDENTIFIER', 'org.stormlightlabs.lazurite'))
1515+1616+# TODO: For Play upload lanes, provide PLAY_SERVICE_ACCOUNT_JSON path to service account key.
1717+json_key_file(ENV['PLAY_SERVICE_ACCOUNT_JSON']) if ENV['PLAY_SERVICE_ACCOUNT_JSON']