chore(release): fastlane for iOS and macOS release (#4685)
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: fastlane-apple-appstore-submission
|
||||
description: "fastlane lanes for iOS/macOS App Store + TestFlight submission, and two gotchas (Tauri notarization trigger, fastlane cwd)"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: 6604c57a-dee4-4a6e-8624-540162f41a80
|
||||
---
|
||||
|
||||
Readest's Apple App Store + TestFlight submission via fastlane (root `fastlane/Fastfile`, alongside the existing Android `upload_to_play_store` lanes). Builds are unchanged (`pnpm run release-ios-appstore` / `release-macos-universial-appstore` → `tauri build` + `xcrun altool --upload-app`); fastlane only does the post-upload App Store version + review submission and TestFlight distribution on the already-uploaded build.
|
||||
|
||||
Lanes (per-platform, each does App Store review submit AND TestFlight, sharing a `submit_apple_build` helper): `release_ios`, `release_macos`. App Store via `upload_to_app_store(skip_binary_upload: true, ipa:/pkg:, platform: "ios"/"osx", submit_for_review: true, automatic_release: true, force: true, skip_screenshots: true, skip_metadata: false, release_notes:{"en-US"=>...}, promotional_text:{"en-US"=>...})`; TestFlight via `upload_to_testflight(distribute_only: true, app_platform: "ios"/"osx", distribute_external: true, groups:["Beta Testers"])`. App Store submit runs FIRST (it waits for build processing, which the TestFlight distribute then needs). `release_notes_text` parses `apps/readest-app/release-notes.json` (latest version by `Gem::Version`, drops notes matching `/\b(?:Android|Windows|Linux)\b/i`, prefixes each `– `). Auth: `app_store_connect_api_key`. Commands: `pnpm run submit-appstore-ios` / `submit-appstore-macos`.
|
||||
|
||||
GOTCHA 1 (Tauri notarization): `tauri build` auto-notarizes the macOS App Store bundle whenever the FULL App Store Connect API key trio (`APPLE_API_KEY` + `APPLE_API_ISSUER` + `APPLE_API_KEY_PATH`) is in the build env. Notarization REJECTS App Store builds ("not signed with a valid Developer ID certificate" / "no secure timestamp") because they use an Apple Distribution cert — App Store apps are NOT notarized. So `APPLE_API_KEY_PATH` must stay OUT of `.env.apple-appstore.local` (the macOS build env). `asc_api_key` instead DERIVES the `.p8` path from the key id: `repo_path("apps/readest-app/private_keys/AuthKey_#{key_id}.p8")` (the keys are named `AuthKey_<KEYID>.p8`, same convention altool uses; honors an explicit `APPLE_API_KEY_PATH` when set, e.g. the iOS build env which DOES need it and iOS doesn't notarize).
|
||||
|
||||
GOTCHA 2 (fastlane cwd): fastlane changes cwd to the `./fastlane` folder when EXECUTING a lane (`__dir__` is just "."), so raw `File.read("./apps/...")` breaks with "No such file". `fastlane lanes` only PARSES (doesn't run lane bodies) so it won't catch this — verify path-dependent lanes by actually RUNNING one. Fix = `repo_path(rel) = File.expand_path(rel, File.expand_path("..", __dir__))`, route every path (release-notes.json, .p8, ipa, pkg) through it.
|
||||
|
||||
GOTCHA 3 (dotenv shadowing): bare `dotenv` on PATH is the Ruby gem (`-f` syntax); package.json scripts use the npm `dotenv-cli` (`-e` syntax) resolved from `apps/readest-app/node_modules/.bin`. The submit scripts run `dotenv -e .env.apple-appstore.local -- bash -c 'cd ../.. && fastlane release_*'` — the `cd ../..` is required because fastlane does NOT search upward for the `fastlane/` dir (pnpm runs scripts from `apps/readest-app`).
|
||||
@@ -71,6 +71,8 @@
|
||||
"build-ios-appstore": "dotenv -e .env.ios-appstore.local -- tauri ios build --export-method app-store-connect",
|
||||
"release-macos-universial-appstore": "dotenv -e .env.tauri.local -e .env.apple-appstore.local -- bash scripts/release-mac-appstore.sh",
|
||||
"release-ios-appstore": "dotenv -e .env.ios-appstore.local -- bash scripts/release-ios-appstore.sh",
|
||||
"submit-appstore-ios": "dotenv -e .env.apple-appstore.local -- bash -c 'cd ../.. && fastlane release_ios'",
|
||||
"submit-appstore-macos": "dotenv -e .env.apple-appstore.local -- bash -c 'cd ../.. && fastlane release_macos'",
|
||||
"release-google-play": "dotenv -e .env.google-play.local -- bash scripts/release-google-play.sh",
|
||||
"config-wrangler": "sed -i \"s/\\${TRANSLATIONS_KV_ID}/$TRANSLATIONS_KV_ID/g\" wrangler.toml",
|
||||
"preview": "pnpm patch-build-webpack && NEXT_PUBLIC_APP_PLATFORM=web opennextjs-cloudflare build && pnpm restore-build-original && opennextjs-cloudflare preview --ip 0.0.0.0 --port 3001",
|
||||
|
||||
@@ -4,3 +4,7 @@ BUNDLE_DIR=src-tauri/gen/apple/build/arm64
|
||||
IPA_BUNDLE=$BUNDLE_DIR/Readest.ipa
|
||||
|
||||
xcrun altool --upload-app --type ios --file $IPA_BUNDLE --apiKey $APPLE_API_KEY --apiIssuer $APPLE_API_ISSUER
|
||||
|
||||
echo "iOS build uploaded to App Store Connect."
|
||||
echo "Submit it to App Store + TestFlight with:"
|
||||
echo " pnpm run submit-appstore-ios"
|
||||
|
||||
@@ -24,4 +24,8 @@ APP_BUNDLE=$BUNDLE_DIR/Readest.app
|
||||
INSTALLER_BUNDLE=$BUNDLE_DIR/Readest.pkg
|
||||
|
||||
xcrun productbuild --sign "$APPLE_INSTALLER_SIGNING_IDENTITY" --component $APP_BUNDLE /Applications $INSTALLER_BUNDLE
|
||||
xcrun altool --upload-app --type macos --file $INSTALLER_BUNDLE --apiKey $APPLE_API_KEY --apiIssuer $APPLE_API_ISSUER
|
||||
xcrun altool --upload-app --type macos --file $INSTALLER_BUNDLE --apiKey $APPLE_API_KEY --apiIssuer $APPLE_API_ISSUER
|
||||
|
||||
echo "macOS build uploaded to App Store Connect."
|
||||
echo "Submit it to App Store + TestFlight with:"
|
||||
echo " pnpm run submit-appstore-macos"
|
||||
@@ -1,3 +1,5 @@
|
||||
require "json"
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
@@ -42,3 +44,89 @@ platform :android do
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# fastlane executes lanes from the ./fastlane folder, so paths are anchored to the
|
||||
# repo root (its parent) to stay independent of the caller's working directory.
|
||||
def repo_path(relative)
|
||||
File.expand_path(relative, File.expand_path("..", __dir__))
|
||||
end
|
||||
|
||||
def asc_api_key
|
||||
key_id = ENV["APPLE_API_KEY"]
|
||||
# APPLE_API_KEY_PATH is deliberately kept OUT of the App Store build env: with the
|
||||
# full key_id/issuer/path trio present, `tauri build` notarizes the App Store binary,
|
||||
# which fails (App Store apps use an Apple Distribution cert, not Developer ID).
|
||||
# Derive the .p8 path from the key id (same convention altool uses); honor an
|
||||
# explicit APPLE_API_KEY_PATH when set (e.g. the iOS build env).
|
||||
app_store_connect_api_key(
|
||||
key_id: key_id,
|
||||
issuer_id: ENV["APPLE_API_ISSUER"],
|
||||
key_filepath: ENV["APPLE_API_KEY_PATH"] || repo_path("apps/readest-app/private_keys/AuthKey_#{key_id}.p8"),
|
||||
)
|
||||
end
|
||||
|
||||
# Release notes from the latest version in release-notes.json, excluding notes
|
||||
# that mention Android, Windows, or Linux. Used for both the TestFlight changelog
|
||||
# and the App Store "What's New" text.
|
||||
def release_notes_text
|
||||
releases = JSON.parse(File.read(repo_path("apps/readest-app/release-notes.json")))["releases"]
|
||||
latest = releases.keys.max_by { |version| Gem::Version.new(version) }
|
||||
releases[latest]["notes"]
|
||||
.reject { |note| note =~ /\b(?:Android|Windows|Linux)\b/i }
|
||||
.map { |note| "– #{note}" }
|
||||
.join("\n")
|
||||
end
|
||||
|
||||
# App Store "Promotional Text". All other metadata fields keep their
|
||||
# existing App Store Connect values.
|
||||
PROMOTIONAL_TEXT = "Experience the Ultimate Ebook Reader Across All Your Devices for Avid Readers"
|
||||
|
||||
# iOS and macOS are one App Store Connect app (com.bilingify.readest), built from
|
||||
# separate binaries and submitted independently. Each lane acts only on a build
|
||||
# already uploaded by altool (skip_binary_upload / distribute_only) — it never
|
||||
# builds or uploads a binary. For its platform it submits the build for App Store
|
||||
# review AND distributes it to TestFlight. The App Store submit runs first because
|
||||
# it waits for build processing, which the TestFlight distribute then relies on.
|
||||
# pnpm run submit-appstore-ios # iOS
|
||||
# pnpm run submit-appstore-macos # macOS
|
||||
def submit_apple_build(deliver_opts, testflight_opts)
|
||||
api_key = asc_api_key
|
||||
upload_to_app_store({
|
||||
api_key: api_key,
|
||||
app_identifier: "com.bilingify.readest",
|
||||
skip_binary_upload: true, # use the altool-uploaded build; read version from the binary
|
||||
submit_for_review: true,
|
||||
automatic_release: true,
|
||||
force: true, # skip the HTML metadata preview prompt
|
||||
skip_screenshots: true,
|
||||
skip_metadata: false, # upload What's New + Promotional Text; other fields stay as-is
|
||||
release_notes: { "en-US" => release_notes_text },
|
||||
promotional_text: { "en-US" => PROMOTIONAL_TEXT },
|
||||
precheck_include_in_app_purchases: false,
|
||||
submission_information: { add_id_info_uses_idfa: false },
|
||||
}.merge(deliver_opts))
|
||||
upload_to_testflight({
|
||||
api_key: api_key,
|
||||
app_identifier: "com.bilingify.readest",
|
||||
distribute_only: true, # use the already-uploaded build; do not upload
|
||||
distribute_external: true,
|
||||
groups: ["Beta Testers"],
|
||||
changelog: release_notes_text,
|
||||
}.merge(testflight_opts))
|
||||
end
|
||||
|
||||
desc "Submit the uploaded iOS build for App Store review and to TestFlight"
|
||||
lane :release_ios do
|
||||
submit_apple_build(
|
||||
{ platform: "ios", ipa: repo_path("apps/readest-app/src-tauri/gen/apple/build/arm64/Readest.ipa") },
|
||||
{ app_platform: "ios" }
|
||||
)
|
||||
end
|
||||
|
||||
desc "Submit the uploaded macOS build for App Store review and to TestFlight"
|
||||
lane :release_macos do
|
||||
submit_apple_build(
|
||||
{ platform: "osx", pkg: repo_path("target/universal-apple-darwin/release/bundle/macos/Readest.pkg") },
|
||||
{ app_platform: "osx" }
|
||||
)
|
||||
end
|
||||
|
||||
@@ -13,6 +13,33 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
|
||||
|
||||
# Available Actions
|
||||
|
||||
### verify_paths
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane verify_paths
|
||||
```
|
||||
|
||||
|
||||
|
||||
### release_ios
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane release_ios
|
||||
```
|
||||
|
||||
Submit the uploaded iOS build for App Store review and to TestFlight
|
||||
|
||||
### release_macos
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane release_macos
|
||||
```
|
||||
|
||||
Submit the uploaded macOS build for App Store review and to TestFlight
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Android
|
||||
|
||||
### android upload_production
|
||||
|
||||
Reference in New Issue
Block a user