mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

build: Fastlane setup for iOS and Android builds

* screenshot automation

* gh actions workflow

+825 -7
+46
.env.example
··· 1 + # Lazurite local environment template 2 + # Copy to .env and fill values for local Fastlane lanes. 3 + 4 + # ----------------------------------------------------------------------------- 5 + # Bluesky test account (used by screenshot automation) 6 + # ----------------------------------------------------------------------------- 7 + BSKY_TEST_HANDLE=example.bsky.social 8 + BSKY_TEST_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx 9 + 10 + # ----------------------------------------------------------------------------- 11 + # App identifiers and Apple account metadata 12 + # ----------------------------------------------------------------------------- 13 + IOS_APP_IDENTIFIER=org.stormlightlabs.lazurite 14 + ANDROID_APP_IDENTIFIER=org.stormlightlabs.lazurite 15 + APP_STORE_CONNECT_APPLE_ID=your-apple-id@example.com 16 + APPLE_DEVELOPER_TEAM_ID=8TVR4TPL9Y 17 + APP_STORE_CONNECT_TEAM_ID=YOUR_ITC_TEAM_ID 18 + 19 + # ----------------------------------------------------------------------------- 20 + # TODO: App Store Connect API (distribution lanes) 21 + # ----------------------------------------------------------------------------- 22 + ASC_KEY_ID= 23 + ASC_ISSUER_ID= 24 + # Multi-line .p8 contents; use quoted/newline-escaped format if needed. 25 + ASC_PRIVATE_KEY= 26 + 27 + # ----------------------------------------------------------------------------- 28 + # TODO: Optional match-based signing (distribution lanes) 29 + # ----------------------------------------------------------------------------- 30 + MATCH_GIT_URL= 31 + MATCH_PASSWORD= 32 + 33 + # ----------------------------------------------------------------------------- 34 + # TODO: Android signing (distribution lanes) 35 + # ----------------------------------------------------------------------------- 36 + # Base64-encoded upload-keystore.jks 37 + ANDROID_KEYSTORE_BASE64= 38 + ANDROID_KEYSTORE_PASSWORD= 39 + ANDROID_KEY_ALIAS= 40 + ANDROID_KEY_PASSWORD= 41 + 42 + # ----------------------------------------------------------------------------- 43 + # TODO: Google Play API (distribution lanes) 44 + # ----------------------------------------------------------------------------- 45 + # Raw JSON or path strategy based on your workflow conventions. 46 + PLAY_SERVICE_ACCOUNT_JSON=
+118
.github/workflows/fastlane.yml
··· 1 + name: Fastlane 2 + 3 + on: 4 + workflow_dispatch: 5 + inputs: 6 + platform: 7 + description: 'Platform lane group to run' 8 + required: true 9 + type: choice 10 + default: ios 11 + options: 12 + - ios 13 + - android 14 + lane: 15 + description: 'Lane to run for selected platform' 16 + required: true 17 + type: choice 18 + default: build 19 + options: 20 + - build 21 + - screenshots 22 + 23 + jobs: 24 + ios: 25 + if: ${{ inputs.platform == 'ios' }} 26 + runs-on: macos-latest 27 + timeout-minutes: 45 28 + env: 29 + BSKY_TEST_HANDLE: ${{ secrets.BSKY_TEST_HANDLE }} 30 + BSKY_TEST_APP_PASSWORD: ${{ secrets.BSKY_TEST_APP_PASSWORD }} 31 + IOS_APP_IDENTIFIER: ${{ secrets.IOS_APP_IDENTIFIER }} 32 + APP_STORE_CONNECT_APPLE_ID: ${{ secrets.APP_STORE_CONNECT_APPLE_ID }} 33 + APPLE_DEVELOPER_TEAM_ID: ${{ secrets.APPLE_DEVELOPER_TEAM_ID }} 34 + APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }} 35 + # TODO: Add ASC_KEY_ID / ASC_ISSUER_ID / ASC_PRIVATE_KEY for distribution lanes. 36 + # TODO: Add MATCH_GIT_URL / MATCH_PASSWORD when signing lanes are introduced. 37 + 38 + steps: 39 + - name: Checkout repository 40 + uses: actions/checkout@v4 41 + 42 + - name: Setup Ruby 43 + uses: ruby/setup-ruby@v1 44 + with: 45 + ruby-version: '3.3' 46 + bundler-cache: true 47 + 48 + - name: Setup Flutter 49 + uses: subosito/flutter-action@v2 50 + with: 51 + channel: stable 52 + cache: true 53 + 54 + - name: Get dependencies 55 + run: flutter pub get 56 + 57 + - name: Run Fastlane 58 + run: bundle exec fastlane ios ${{ inputs.lane }} 59 + 60 + - name: Upload iOS artifacts 61 + if: always() 62 + uses: actions/upload-artifact@v4 63 + with: 64 + name: ios-fastlane-${{ inputs.lane }} 65 + if-no-files-found: ignore 66 + path: | 67 + build/fastlane_artifacts/ios 68 + fastlane/screenshots/ios 69 + 70 + android: 71 + if: ${{ inputs.platform == 'android' }} 72 + runs-on: ubuntu-latest 73 + timeout-minutes: 45 74 + env: 75 + BSKY_TEST_HANDLE: ${{ secrets.BSKY_TEST_HANDLE }} 76 + BSKY_TEST_APP_PASSWORD: ${{ secrets.BSKY_TEST_APP_PASSWORD }} 77 + ANDROID_APP_IDENTIFIER: ${{ secrets.ANDROID_APP_IDENTIFIER }} 78 + # TODO: Add ANDROID_KEYSTORE_BASE64 / ANDROID_KEYSTORE_PASSWORD / ANDROID_KEY_ALIAS / ANDROID_KEY_PASSWORD for release signing. 79 + # TODO: Add PLAY_SERVICE_ACCOUNT_JSON for Play upload lanes. 80 + 81 + steps: 82 + - name: Checkout repository 83 + uses: actions/checkout@v4 84 + 85 + - name: Setup Java 86 + uses: actions/setup-java@v4 87 + with: 88 + distribution: zulu 89 + java-version: '17' 90 + cache: gradle 91 + 92 + - name: Setup Ruby 93 + uses: ruby/setup-ruby@v1 94 + with: 95 + ruby-version: '3.3' 96 + bundler-cache: true 97 + 98 + - name: Setup Flutter 99 + uses: subosito/flutter-action@v2 100 + with: 101 + channel: stable 102 + cache: true 103 + 104 + - name: Get dependencies 105 + run: flutter pub get 106 + 107 + - name: Run Fastlane 108 + run: bundle exec fastlane android ${{ inputs.lane }} 109 + 110 + - name: Upload Android artifacts 111 + if: always() 112 + uses: actions/upload-artifact@v4 113 + with: 114 + name: android-fastlane-${{ inputs.lane }} 115 + if-no-files-found: ignore 116 + path: | 117 + build/fastlane_artifacts/android 118 + fastlane/screenshots/android
+11 -7
DEVELOPMENT.md
··· 12 12 13 13 Use `just` for common workflows: 14 14 15 - | Command | Description | 16 - | ------------- | -------------------------------------- | 17 - | `just format` | Run `dart format` | 18 - | `just lint` | Run `flutter analyze` | 19 - | `just test` | Run the full `flutter test` suite | 20 - | `just gen` | Run `build_runner` for code generation | 21 - | `just check` | Format, lint, and test in sequence | 15 + | Command | Description | 16 + | ----------------------------- | -------------------------------------------- | 17 + | `just format` | Run `dart format` | 18 + | `just lint` | Run `flutter analyze` | 19 + | `just test` | Run the full `flutter test` suite | 20 + | `just gen` | Run `build_runner` for code generation | 21 + | `just check` | Format, lint, and test in sequence | 22 + | `just fastlane-lanes` | List all configured Fastlane lanes | 23 + | `just fastlane-ios-build` | Build iOS simulator artifacts via Fastlane | 24 + | `just fastlane-android-build` | Build Android APK/AAB artifacts via Fastlane | 22 25 23 26 ## Architecture 24 27 ··· 170 173 - [Flutter Documentation](https://flutter.dev/docs) 171 174 - [Drift Documentation](https://drift.simonbinder.eu/) 172 175 - [ObjectBox Flutter](https://docs.objectbox.io/flutter) 176 + - [Fastlane Guide](docs/fastlane.md)
+3
Gemfile
··· 1 + source 'https://rubygems.org' 2 + 3 + gem 'fastlane', '~> 2.226'
+231
Gemfile.lock
··· 1 + GEM 2 + remote: https://rubygems.org/ 3 + specs: 4 + CFPropertyList (3.0.9) 5 + abbrev (0.1.2) 6 + addressable (2.9.0) 7 + public_suffix (>= 2.0.2, < 8.0) 8 + artifactory (3.0.17) 9 + atomos (0.1.3) 10 + aws-eventstream (1.3.2) 11 + aws-partitions (1.1109.0) 12 + aws-sdk-core (3.224.1) 13 + aws-eventstream (~> 1, >= 1.3.0) 14 + aws-partitions (~> 1, >= 1.992.0) 15 + aws-sigv4 (~> 1.9) 16 + base64 17 + jmespath (~> 1, >= 1.6.1) 18 + logger 19 + aws-sdk-kms (1.101.0) 20 + aws-sdk-core (~> 3, >= 3.216.0) 21 + aws-sigv4 (~> 1.5) 22 + aws-sdk-s3 (1.188.0) 23 + aws-sdk-core (~> 3, >= 3.224.1) 24 + aws-sdk-kms (~> 1) 25 + aws-sigv4 (~> 1.5) 26 + aws-sigv4 (1.11.0) 27 + aws-eventstream (~> 1, >= 1.0.2) 28 + babosa (1.0.4) 29 + base64 (0.2.0) 30 + claide (1.1.0) 31 + colored (1.2) 32 + colored2 (3.1.2) 33 + commander (4.6.0) 34 + highline (~> 2.0.0) 35 + csv (3.3.5) 36 + declarative (0.0.20) 37 + digest-crc (0.7.0) 38 + rake (>= 12.0.0, < 14.0.0) 39 + domain_name (0.5.20190701) 40 + unf (>= 0.0.5, < 1.0.0) 41 + dotenv (2.8.1) 42 + emoji_regex (3.2.3) 43 + excon (0.109.0) 44 + faraday (1.10.5) 45 + faraday-em_http (~> 1.0) 46 + faraday-em_synchrony (~> 1.0) 47 + faraday-excon (~> 1.1) 48 + faraday-httpclient (~> 1.0) 49 + faraday-multipart (~> 1.0) 50 + faraday-net_http (~> 1.0) 51 + faraday-net_http_persistent (~> 1.0) 52 + faraday-patron (~> 1.0) 53 + faraday-rack (~> 1.0) 54 + faraday-retry (~> 1.0) 55 + ruby2_keywords (>= 0.0.4) 56 + faraday-cookie_jar (0.0.8) 57 + faraday (>= 0.8.0) 58 + http-cookie (>= 1.0.0) 59 + faraday-em_http (1.0.0) 60 + faraday-em_synchrony (1.0.1) 61 + faraday-excon (1.1.0) 62 + faraday-httpclient (1.0.1) 63 + faraday-multipart (1.2.0) 64 + multipart-post (~> 2.0) 65 + faraday-net_http (1.0.2) 66 + faraday-net_http_persistent (1.2.0) 67 + faraday-patron (1.0.0) 68 + faraday-rack (1.0.0) 69 + faraday-retry (1.0.4) 70 + faraday_middleware (1.2.1) 71 + faraday (~> 1.0) 72 + fastimage (2.4.1) 73 + fastlane (2.230.0) 74 + CFPropertyList (>= 2.3, < 4.0.0) 75 + abbrev (~> 0.1.2) 76 + addressable (>= 2.8, < 3.0.0) 77 + artifactory (~> 3.0) 78 + aws-sdk-s3 (~> 1.0) 79 + babosa (>= 1.0.3, < 2.0.0) 80 + base64 (~> 0.2.0) 81 + bundler (>= 1.12.0, < 3.0.0) 82 + colored (~> 1.2) 83 + commander (~> 4.6) 84 + csv (~> 3.3) 85 + dotenv (>= 2.1.1, < 3.0.0) 86 + emoji_regex (>= 0.1, < 4.0) 87 + excon (>= 0.71.0, < 1.0.0) 88 + faraday (~> 1.0) 89 + faraday-cookie_jar (~> 0.0.6) 90 + faraday_middleware (~> 1.0) 91 + fastimage (>= 2.1.0, < 3.0.0) 92 + fastlane-sirp (>= 1.0.0) 93 + gh_inspector (>= 1.1.2, < 2.0.0) 94 + google-apis-androidpublisher_v3 (~> 0.3) 95 + google-apis-playcustomapp_v1 (~> 0.1) 96 + google-cloud-env (>= 1.6.0, < 2.0.0) 97 + google-cloud-storage (~> 1.31) 98 + highline (~> 2.0) 99 + http-cookie (~> 1.0.5) 100 + json (< 3.0.0) 101 + jwt (>= 2.1.0, < 3) 102 + logger (>= 1.6, < 2.0) 103 + mini_magick (>= 4.9.4, < 5.0.0) 104 + multipart-post (>= 2.0.0, < 3.0.0) 105 + mutex_m (~> 0.3.0) 106 + naturally (~> 2.2) 107 + nkf (~> 0.2.0) 108 + optparse (>= 0.1.1, < 1.0.0) 109 + plist (>= 3.1.0, < 4.0.0) 110 + rubyzip (>= 2.0.0, < 3.0.0) 111 + security (= 0.1.5) 112 + simctl (~> 1.6.3) 113 + terminal-notifier (>= 2.0.0, < 3.0.0) 114 + terminal-table (~> 3) 115 + tty-screen (>= 0.6.3, < 1.0.0) 116 + tty-spinner (>= 0.8.0, < 1.0.0) 117 + word_wrap (~> 1.0.0) 118 + xcodeproj (>= 1.13.0, < 2.0.0) 119 + xcpretty (~> 0.4.1) 120 + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) 121 + fastlane-sirp (1.1.0) 122 + gh_inspector (1.1.3) 123 + google-apis-androidpublisher_v3 (0.54.0) 124 + google-apis-core (>= 0.11.0, < 2.a) 125 + google-apis-core (0.11.3) 126 + addressable (~> 2.5, >= 2.5.1) 127 + googleauth (>= 0.16.2, < 2.a) 128 + httpclient (>= 2.8.1, < 3.a) 129 + mini_mime (~> 1.0) 130 + representable (~> 3.0) 131 + retriable (>= 2.0, < 4.a) 132 + rexml 133 + google-apis-iamcredentials_v1 (0.17.0) 134 + google-apis-core (>= 0.11.0, < 2.a) 135 + google-apis-playcustomapp_v1 (0.13.0) 136 + google-apis-core (>= 0.11.0, < 2.a) 137 + google-apis-storage_v1 (0.29.0) 138 + google-apis-core (>= 0.11.0, < 2.a) 139 + google-cloud-core (1.6.1) 140 + google-cloud-env (>= 1.0, < 3.a) 141 + google-cloud-errors (~> 1.0) 142 + google-cloud-env (1.6.0) 143 + faraday (>= 0.17.3, < 3.0) 144 + google-cloud-errors (1.3.1) 145 + google-cloud-storage (1.45.0) 146 + addressable (~> 2.8) 147 + digest-crc (~> 0.4) 148 + google-apis-iamcredentials_v1 (~> 0.1) 149 + google-apis-storage_v1 (~> 0.29.0) 150 + google-cloud-core (~> 1.6) 151 + googleauth (>= 0.16.2, < 2.a) 152 + mini_mime (~> 1.0) 153 + googleauth (1.8.1) 154 + faraday (>= 0.17.3, < 3.a) 155 + jwt (>= 1.4, < 3.0) 156 + multi_json (~> 1.11) 157 + os (>= 0.9, < 2.0) 158 + signet (>= 0.16, < 2.a) 159 + highline (2.0.3) 160 + http-cookie (1.0.8) 161 + domain_name (~> 0.5) 162 + httpclient (2.9.0) 163 + mutex_m 164 + jmespath (1.6.2) 165 + json (2.7.6) 166 + jwt (2.10.2) 167 + base64 168 + logger (1.7.0) 169 + mini_magick (4.13.2) 170 + mini_mime (1.1.5) 171 + multi_json (1.15.0) 172 + multipart-post (2.4.1) 173 + mutex_m (0.3.0) 174 + nanaimo (0.4.0) 175 + naturally (2.3.0) 176 + nkf (0.2.0) 177 + optparse (0.8.1) 178 + os (1.1.4) 179 + plist (3.7.2) 180 + public_suffix (5.1.1) 181 + rake (13.4.2) 182 + representable (3.2.0) 183 + declarative (< 0.1.0) 184 + trailblazer-option (>= 0.1.1, < 0.2.0) 185 + uber (< 0.2.0) 186 + retriable (3.4.1) 187 + rexml (3.4.4) 188 + rouge (3.28.0) 189 + ruby2_keywords (0.0.5) 190 + rubyzip (2.4.1) 191 + security (0.1.5) 192 + signet (0.18.0) 193 + addressable (~> 2.8) 194 + faraday (>= 0.17.5, < 3.a) 195 + jwt (>= 1.5, < 3.0) 196 + multi_json (~> 1.10) 197 + simctl (1.6.10) 198 + CFPropertyList 199 + naturally 200 + terminal-notifier (2.0.0) 201 + terminal-table (3.0.2) 202 + unicode-display_width (>= 1.1.1, < 3) 203 + trailblazer-option (0.1.2) 204 + tty-cursor (0.7.1) 205 + tty-screen (0.8.2) 206 + tty-spinner (0.9.3) 207 + tty-cursor (~> 0.7) 208 + uber (0.1.0) 209 + unf (0.2.0) 210 + unicode-display_width (2.6.0) 211 + word_wrap (1.0.0) 212 + xcodeproj (1.27.0) 213 + CFPropertyList (>= 2.3.3, < 4.0) 214 + atomos (~> 0.1.3) 215 + claide (>= 1.0.2, < 2.0) 216 + colored2 (~> 3.1) 217 + nanaimo (~> 0.4.0) 218 + rexml (>= 3.3.6, < 4.0) 219 + xcpretty (0.4.1) 220 + rouge (~> 3.28.0) 221 + xcpretty-travis-formatter (1.0.1) 222 + xcpretty (~> 0.2, >= 0.0.7) 223 + 224 + PLATFORMS 225 + ruby 226 + 227 + DEPENDENCIES 228 + fastlane (~> 2.226) 229 + 230 + BUNDLED WITH 231 + 1.17.2
+201
docs/fastlane.md
··· 1 + # Fastlane Guide 2 + 3 + This project includes a first-pass Fastlane setup for local and GitHub Actions usage. 4 + 5 + ## Scope 6 + 7 + - Build artifacts (no production signing in this pass) 8 + - Screenshot automation scaffolding for iOS and Android 9 + - Manual GitHub Actions workflow dispatch for running lanes 10 + 11 + ## Prerequisites 12 + 13 + - Flutter SDK 14 + - Ruby 3.x and Bundler 15 + - Xcode + iOS Simulator tooling (for iOS lanes) 16 + - Android SDK + emulator/device tooling (for Android lanes) 17 + 18 + Install gem dependencies: 19 + 20 + ```sh 21 + bundle install 22 + ``` 23 + 24 + Create local env file: 25 + 26 + ```sh 27 + cp .env.example .env 28 + ``` 29 + 30 + ## Local Commands 31 + 32 + List lanes: 33 + 34 + ```sh 35 + bundle exec fastlane lanes 36 + ``` 37 + 38 + Build lanes: 39 + 40 + ```sh 41 + bundle exec fastlane ios build 42 + bundle exec fastlane android build 43 + ``` 44 + 45 + Screenshot lanes: 46 + 47 + ```sh 48 + bundle exec fastlane ios screenshots 49 + bundle exec fastlane android screenshots 50 + ``` 51 + 52 + Equivalent `just` wrappers are available: 53 + 54 + ```sh 55 + just fastlane-lanes 56 + just fastlane-ios-build 57 + just fastlane-android-build 58 + just fastlane-ios-screenshots 59 + just fastlane-android-screenshots 60 + ``` 61 + 62 + ## Authentication for Screenshots 63 + 64 + The app is auth-gated, so screenshot lanes currently assume a dedicated test account. 65 + 66 + Required environment variables: 67 + 68 + - `BSKY_TEST_HANDLE` 69 + - `BSKY_TEST_APP_PASSWORD` 70 + 71 + Current state: 72 + 73 + - iOS screenshots require `ios/RunnerUITests` to exist and perform app-password login before calling `snapshot(...)`. 74 + - Android screenshots require `android/app/src/androidTest` screengrab tests that log in and call `Screengrab.screenshot(...)`. 75 + 76 + Both lanes fail fast with actionable TODO messages until those test hooks are implemented. 77 + 78 + ## Build Artifacts 79 + 80 + Fastlane copies outputs to: 81 + 82 + - iOS: `build/fastlane_artifacts/ios` 83 + - Android APK: `build/fastlane_artifacts/android/apk` 84 + - Android AAB: `build/fastlane_artifacts/android/aab` 85 + 86 + ## GitHub Actions 87 + 88 + Workflow file: `.github/workflows/fastlane.yml` 89 + 90 + Trigger mode: 91 + 92 + - `workflow_dispatch` only 93 + - inputs: 94 + - `platform`: `ios` or `android` 95 + - `lane`: `build` or `screenshots` 96 + 97 + The workflow uploads any generated artifacts/screenshots. 98 + 99 + ## Keys and Secrets TODOs 100 + 101 + This setup intentionally defers production signing/distribution. Add these secrets later as you enable those lanes. 102 + 103 + ### iOS/App Store Connect 104 + 105 + - `ASC_KEY_ID` (TODO) 106 + - `ASC_ISSUER_ID` (TODO) 107 + - `ASC_PRIVATE_KEY` (TODO) 108 + - `MATCH_GIT_URL` (TODO if using match) 109 + - `MATCH_PASSWORD` (TODO if using match) 110 + 111 + Current optional iOS identity variables: 112 + 113 + - `IOS_APP_IDENTIFIER` 114 + - `APP_STORE_CONNECT_APPLE_ID` 115 + - `APPLE_DEVELOPER_TEAM_ID` 116 + - `APP_STORE_CONNECT_TEAM_ID` 117 + 118 + ### Android/Play 119 + 120 + - `ANDROID_KEYSTORE_BASE64` (TODO) 121 + - `ANDROID_KEYSTORE_PASSWORD` (TODO) 122 + - `ANDROID_KEY_ALIAS` (TODO) 123 + - `ANDROID_KEY_PASSWORD` (TODO) 124 + - `PLAY_SERVICE_ACCOUNT_JSON` (TODO) 125 + 126 + Current optional Android variable: 127 + 128 + - `ANDROID_APP_IDENTIFIER` 129 + 130 + ## How to Obtain Non-Bluesky Keys/Secrets 131 + 132 + This section covers only Apple/Google signing and store automation credentials. 133 + 134 + ### App Store Connect API (`ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_PRIVATE_KEY`) 135 + 136 + 1. Sign in to App Store Connect with an account that can manage API keys. 137 + 2. Open `Users and Access` -> `Integrations` -> `App Store Connect API`. 138 + 3. Create a Team API key with the minimum role needed for release automation. 139 + 4. Download the `.p8` key file immediately (Apple allows a one-time download). 140 + 5. Save values: 141 + - `ASC_KEY_ID`: Key ID shown for the generated key. 142 + - `ASC_ISSUER_ID`: Issuer ID shown in the API section. 143 + - `ASC_PRIVATE_KEY`: The full `.p8` file content stored as a GitHub secret. 144 + 145 + ### Apple Team/App Identity (`APPLE_DEVELOPER_TEAM_ID`, `APP_STORE_CONNECT_TEAM_ID`, `APP_STORE_CONNECT_APPLE_ID`) 146 + 147 + 1. `APPLE_DEVELOPER_TEAM_ID`: 148 + - Get this from Apple Developer account membership details. 149 + 2. `APP_STORE_CONNECT_TEAM_ID`: 150 + - Use your App Store Connect team ID from account/team settings. 151 + 3. `APP_STORE_CONNECT_APPLE_ID`: 152 + - Use the Apple ID email used for App Store Connect automation ownership. 153 + 154 + ### Android Signing (`ANDROID_KEYSTORE_BASE64`, `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD`) 155 + 156 + 1. Generate a release keystore (example): 157 + 158 + ```sh 159 + keytool -genkeypair -v \ 160 + -keystore upload-keystore.jks \ 161 + -alias upload \ 162 + -keyalg RSA -keysize 2048 -validity 10000 163 + ``` 164 + 165 + 2. Base64 encode the keystore for GitHub secret storage: 166 + 167 + ```sh 168 + base64 -i upload-keystore.jks | pbcopy 169 + ``` 170 + 171 + 3. Store secrets: 172 + - `ANDROID_KEYSTORE_BASE64`: base64 value of `.jks`. 173 + - `ANDROID_KEYSTORE_PASSWORD`: keystore password. 174 + - `ANDROID_KEY_ALIAS`: alias used at creation (example `upload`). 175 + - `ANDROID_KEY_PASSWORD`: key password (may match keystore password). 176 + 177 + ### Google Play API (`PLAY_SERVICE_ACCOUNT_JSON`) 178 + 179 + 1. In Google Play Console, open `Users and permissions` and invite/link a service account from Google Cloud. 180 + 2. In Google Cloud IAM, create a service account key (JSON) for that account. 181 + 3. Grant least-privilege Play permissions needed for release automation. 182 + 4. Store the JSON securely: 183 + - Either the raw JSON as `PLAY_SERVICE_ACCOUNT_JSON` (multi-line secret), or 184 + - base64-encode it and decode during workflow runtime. 185 + 186 + ### Optional Match Secrets (`MATCH_GIT_URL`, `MATCH_PASSWORD`) 187 + 188 + 1. Create a private certificates/profiles repository. 189 + 2. Set `MATCH_GIT_URL` to that repository URL. 190 + 3. Set `MATCH_PASSWORD` to the encryption passphrase used by match. 191 + 192 + ### Secret Hygiene 193 + 194 + - Use repository or environment-level GitHub secrets, not committed files. 195 + - Rotate any secret immediately if leaked. 196 + - Restrict secret access to release workflows/environments. 197 + 198 + ## Notes 199 + 200 + - CI artifact lanes are non-distribution lanes. 201 + - Adding signed IPA/AAB and store upload lanes is a follow-up once keys are provisioned.
+12
docs/tasks/fastlane.md
··· 1 + # Fastlane Setup Tasks 2 + 3 + - [x] Add Bundler + Fastlane scaffolding (`Gemfile`, `fastlane/Fastfile`, `fastlane/Appfile`) 4 + - [x] Add iOS and Android build lanes for non-distribution artifacts 5 + - [x] Add iOS and Android screenshot scaffolding lanes with auth + test-hook TODOs 6 + - [x] Add manual-dispatch GitHub Actions workflow for Fastlane lanes 7 + - [x] Add `docs/fastlane.md` with local + CI usage and secrets TODO guidance 8 + - [x] Add `.env.example` for local Fastlane and signing/screenshot variables 9 + - [x] Link Fastlane docs from `DEVELOPMENT.md` 10 + - [ ] Add `ios/RunnerUITests` snapshot coverage 11 + - [ ] Add `android/app/src/androidTest` screengrab coverage 12 + - [ ] Add production signing and store upload lanes once keys are provisioned
+17
fastlane/Appfile
··· 1 + # iOS identifiers 2 + app_identifier(ENV.fetch('IOS_APP_IDENTIFIER', 'org.stormlightlabs.lazurite')) 3 + 4 + # TODO: Set APP_STORE_CONNECT_APPLE_ID when App Store Connect account ownership is finalized. 5 + apple_id(ENV.fetch('APP_STORE_CONNECT_APPLE_ID', 'TODO_APPLE_ID@example.com')) 6 + 7 + # TODO: Set APPLE_DEVELOPER_TEAM_ID if team changes from current default. 8 + team_id(ENV.fetch('APPLE_DEVELOPER_TEAM_ID', '8TVR4TPL9Y')) 9 + 10 + # TODO: Set APP_STORE_CONNECT_TEAM_ID for App Store Connect API workflows (deliver/pilot). 11 + itc_team_id(ENV.fetch('APP_STORE_CONNECT_TEAM_ID', 'TODO_ITC_TEAM_ID')) 12 + 13 + # Android identifiers 14 + package_name(ENV.fetch('ANDROID_APP_IDENTIFIER', 'org.stormlightlabs.lazurite')) 15 + 16 + # TODO: For Play upload lanes, provide PLAY_SERVICE_ACCOUNT_JSON path to service account key. 17 + json_key_file(ENV['PLAY_SERVICE_ACCOUNT_JSON']) if ENV['PLAY_SERVICE_ACCOUNT_JSON']
+131
fastlane/Fastfile
··· 1 + require 'shellwords' 2 + 3 + fastlane_version '2.226.0' 4 + default_platform(:ios) 5 + 6 + ENV['FLUTTER_SUPPRESS_ANALYTICS'] = 'true' 7 + ENV['DART_SUPPRESS_ANALYTICS'] = 'true' 8 + 9 + private_lane :repo_root do 10 + File.expand_path('..', __dir__) 11 + end 12 + 13 + private_lane :require_env_vars do |options| 14 + required = Array(options[:required]) 15 + context = options[:context] || 'lane' 16 + missing = required.select { |key| ENV[key].to_s.strip.empty? } 17 + return if missing.empty? 18 + 19 + UI.user_error!("#{context} is missing required env vars: #{missing.join(', ')}") 20 + end 21 + 22 + private_lane :require_paths do |options| 23 + paths = Array(options[:paths]) 24 + context = options[:context] || 'lane' 25 + missing = paths.reject { |path| File.exist?(File.join(repo_root, path)) } 26 + return if missing.empty? 27 + 28 + UI.user_error!("#{context} is missing required files/directories: #{missing.join(', ')}") 29 + end 30 + 31 + private_lane :copy_artifacts do |options| 32 + source_glob = options[:source_glob] 33 + destination = File.join(repo_root, options[:destination]) 34 + artifacts = Dir.glob(File.join(repo_root, source_glob)) 35 + 36 + if artifacts.empty? 37 + UI.important("No artifacts matched #{source_glob}") 38 + next 39 + end 40 + 41 + sh("mkdir -p #{destination.shellescape}") 42 + artifacts.each do |artifact| 43 + sh("cp -R #{artifact.shellescape} #{destination.shellescape}/") 44 + end 45 + UI.success("Copied #{artifacts.size} artifact(s) to #{destination}") 46 + end 47 + 48 + platform :ios do 49 + desc 'Build iOS simulator artifact without signing for local/CI validation' 50 + lane :build do 51 + sh("cd #{repo_root.shellescape} && flutter build ios --simulator --debug --no-codesign") 52 + 53 + copy_artifacts( 54 + source_glob: 'build/ios/iphonesimulator/Runner.app', 55 + destination: 'build/fastlane_artifacts/ios', 56 + ) 57 + end 58 + 59 + desc 'Capture iOS screenshots via snapshot (requires UI tests and auth secrets)' 60 + lane :screenshots do 61 + require_env_vars(required: %w[BSKY_TEST_HANDLE BSKY_TEST_APP_PASSWORD], context: 'ios screenshots') 62 + require_paths(paths: %w[fastlane/Snapfile ios/Runner.xcodeproj], context: 'ios screenshots') 63 + 64 + unless Dir.exist?(File.join(repo_root, 'ios/RunnerUITests')) 65 + UI.user_error!( 66 + "TODO: Add ios/RunnerUITests snapshot coverage. " \ 67 + 'Create UI tests that perform app-password login and call snapshot("name") for each target screen.' 68 + ) 69 + end 70 + 71 + snapshot( 72 + scheme: ENV.fetch('IOS_SNAPSHOT_SCHEME', 'Runner'), 73 + devices: ['iPhone 15 Pro'], 74 + languages: ['en-US'], 75 + output_directory: 'fastlane/screenshots/ios', 76 + clear_previous_screenshots: true, 77 + stop_after_first_error: true, 78 + skip_open_summary: true, 79 + erase_simulator: true, 80 + reinstall_app: true, 81 + launch_arguments: [ 82 + "-BSKY_TEST_HANDLE #{ENV['BSKY_TEST_HANDLE']}", 83 + "-BSKY_TEST_APP_PASSWORD #{ENV['BSKY_TEST_APP_PASSWORD']}", 84 + '-FASTLANE_AUTH_LOGIN YES', 85 + ], 86 + ) 87 + end 88 + end 89 + 90 + platform :android do 91 + desc 'Build Android APK + AAB artifacts without production signing' 92 + lane :build do 93 + gradle(task: 'assemble', build_type: 'Release', project_dir: 'android') 94 + gradle(task: 'bundle', build_type: 'Release', project_dir: 'android') 95 + 96 + copy_artifacts( 97 + source_glob: 'build/app/outputs/flutter-apk/*.apk', 98 + destination: 'build/fastlane_artifacts/android/apk', 99 + ) 100 + copy_artifacts( 101 + source_glob: 'build/app/outputs/bundle/release/*.aab', 102 + destination: 'build/fastlane_artifacts/android/aab', 103 + ) 104 + end 105 + 106 + desc 'Capture Android screenshots via screengrab (scaffold; requires androidTest setup)' 107 + lane :screenshots do 108 + require_env_vars(required: %w[BSKY_TEST_HANDLE BSKY_TEST_APP_PASSWORD], context: 'android screenshots') 109 + require_paths(paths: %w[fastlane/Screengrabfile android/app/build.gradle.kts], context: 'android screenshots') 110 + 111 + has_android_test = Dir.glob(File.join(repo_root, 'android/app/src/androidTest/**/*')).any? { |p| File.file?(p) } 112 + unless has_android_test 113 + UI.user_error!( 114 + "TODO: Add android/app/src/androidTest screengrab tests. " \ 115 + 'Include fastlane screengrab dependency, instrumentation login flow, and Screengrab.screenshot(...) calls.' 116 + ) 117 + end 118 + 119 + gradle(tasks: %w[assembleDebug assembleAndroidTest], project_dir: 'android') 120 + 121 + screengrab( 122 + clear_previous_screenshots: true, 123 + output_directory: 'fastlane/screenshots/android', 124 + skip_open_summary: true, 125 + launch_arguments: [ 126 + "bsky_test_handle #{ENV['BSKY_TEST_HANDLE']}", 127 + "bsky_test_app_password #{ENV['BSKY_TEST_APP_PASSWORD']}", 128 + ], 129 + ) 130 + end 131 + end
+16
fastlane/Screengrabfile
··· 1 + # TODO: Add more locales/devices after androidTest screengrab coverage exists. 2 + locales(['en-US']) 3 + 4 + clear_previous_screenshots(true) 5 + output_directory('fastlane/screenshots/android') 6 + skip_open_summary(true) 7 + 8 + # TODO: androidTest should consume these launch args to perform app-password login. 9 + launch_arguments([ 10 + "bsky_test_handle #{ENV['BSKY_TEST_HANDLE']}", 11 + "bsky_test_app_password #{ENV['BSKY_TEST_APP_PASSWORD']}", 12 + ]) 13 + 14 + # TODO: Set explicit package names once screengrab tests are added. 15 + # app_package_name('org.stormlightlabs.lazurite') 16 + # tests_package_name('org.stormlightlabs.lazurite.test')
+24
fastlane/Snapfile
··· 1 + # TODO: Add additional devices/languages once coverage is stable. 2 + devices([ 3 + 'iPhone 15 Pro', 4 + ]) 5 + 6 + languages([ 7 + 'en-US', 8 + ]) 9 + 10 + # TODO: Use a dedicated UI-test scheme once RunnerUITests is created. 11 + scheme(ENV.fetch('IOS_SNAPSHOT_SCHEME', 'Runner')) 12 + 13 + output_directory('fastlane/screenshots/ios') 14 + clear_previous_screenshots(true) 15 + skip_open_summary(true) 16 + reinstall_app(true) 17 + erase_simulator(true) 18 + 19 + # TODO: RunnerUITests should read these launch args and perform app-password login using secrets. 20 + launch_arguments([ 21 + "-BSKY_TEST_HANDLE #{ENV['BSKY_TEST_HANDLE']}", 22 + "-BSKY_TEST_APP_PASSWORD #{ENV['BSKY_TEST_APP_PASSWORD']}", 23 + '-FASTLANE_AUTH_LOGIN YES', 24 + ])
+15
justfile
··· 39 39 # Run format, lint, and test 40 40 check: format lint test 41 41 42 + fastlane-lanes: 43 + bundle exec fastlane lanes 44 + 45 + fastlane-ios-build: 46 + bundle exec fastlane ios build 47 + 48 + fastlane-android-build: 49 + bundle exec fastlane android build 50 + 51 + fastlane-ios-screenshots: 52 + bundle exec fastlane ios screenshots 53 + 54 + fastlane-android-screenshots: 55 + bundle exec fastlane android screenshots 56 + 42 57 find-comments: 43 58 rg -n --pcre2 '^\s*//(?![!/])' -g '*.dart' -g '!*.g.dart' -g '!*.freezed.dart' 44 59