mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-30 05:02:58 +00:00
Publish store screenshots from the repo on each stable release (#8186)
* feat(release): publish store screenshots from the repo Stable releases now upload the committed store renders: the App Store review lane attaches the iPhone and iPad sets to the version it submits, and a new Play lane replaces the phone and tablet screenshots after the Android submit. F-Droid reads the same Android files, which now link to the store renders instead of the website images. The Play lane needs its own copy of the submit service-account key (GOOGLE_PLAY_JSON_KEY in the EAS production environment) because the key Expo stores for eas submit is not available to custom jobs, and EAS Metadata does not handle screenshots for either store. * fix(release): rewrite the Android screenshot links on every render Google Play and F-Droid read numbered links to the Google renders. The renderer now rewrites them from the panels it exported, so adding or renaming a panel reaches all three stores.
This commit is contained in:
@@ -95,4 +95,8 @@ Callout crops are in native capture pixels. To find them, overlay a 100 px grid
|
||||
|
||||
6. Copy the captures into `raw/iphone/` or `raw/ipad/` under the names `render.mjs` uses, render, and check the preview.
|
||||
|
||||
## Publish
|
||||
|
||||
The stable release uploads these exports to the App Store and Google Play, and F-Droid reads the Google ones from the repo. See [Store screenshots](../../docs/release.md#store-screenshots). The Android metadata holds links to the Google exports, and every render rewrites them, so commit the links with the exports.
|
||||
|
||||
Dimensions are checked against [Apple's screenshot specifications](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/) and [Google Play's preview asset requirements](https://support.google.com/googleplay/android-developer/answer/9866151?hl=en).
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// elsewhere point SF_FONT_DIR at a folder holding SFNS.ttf copied from a Mac.
|
||||
|
||||
import { existsSync } from "node:fs";
|
||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
@@ -23,6 +23,7 @@ const root = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repo = path.resolve(root, "../..");
|
||||
const raw = (device, name) => path.join(root, "raw", device, `${name}.png`);
|
||||
const output = path.join(root, "output");
|
||||
const androidImages = path.join(repo, "fastlane/metadata/android/en-US/images");
|
||||
|
||||
// Native captures per device: iPhone 16 Pro Max at 3x and iPad Pro 13-inch at
|
||||
// 2x. The frame's screen radius and Dynamic Island are in capture pixels.
|
||||
@@ -116,6 +117,7 @@ const STORES = {
|
||||
},
|
||||
google: {
|
||||
device: "iphone",
|
||||
androidScreenshots: "phoneScreenshots",
|
||||
width: 1440,
|
||||
height: 2560,
|
||||
textTop: 150,
|
||||
@@ -134,6 +136,7 @@ const STORES = {
|
||||
},
|
||||
"google-tablet": {
|
||||
device: "ipad",
|
||||
androidScreenshots: "tenInchScreenshots",
|
||||
width: 1800,
|
||||
height: 3200,
|
||||
textTop: 200,
|
||||
@@ -352,6 +355,18 @@ async function expectLines(view, selector, expected, label) {
|
||||
if (lines !== expected) throw new Error(`${label} wraps to ${lines} lines, expected ${expected}`);
|
||||
}
|
||||
|
||||
// Google Play and F-Droid read their screenshots from the Android metadata,
|
||||
// which holds links to these renders. Rewriting the links on every render keeps
|
||||
// added, removed and renamed panels in step with the App Store.
|
||||
async function linkAndroidScreenshots(folder, files) {
|
||||
const dir = path.join(androidImages, folder);
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
await mkdir(dir, { recursive: true });
|
||||
for (const [index, file] of files.entries()) {
|
||||
await symlink(path.relative(dir, file), path.join(dir, `${index + 1}.png`));
|
||||
}
|
||||
}
|
||||
|
||||
async function renderPreview(rows) {
|
||||
const tile = 440;
|
||||
const gap = 40;
|
||||
@@ -387,7 +402,9 @@ try {
|
||||
const marks = await loadAgentMarks(work);
|
||||
const rows = [];
|
||||
for (const [name, store] of Object.entries(STORES)) {
|
||||
rows.push(await renderStore(browser, name, store, font, marks, work));
|
||||
const files = await renderStore(browser, name, store, font, marks, work);
|
||||
if (store.androidScreenshots) await linkAndroidScreenshots(store.androidScreenshots, files);
|
||||
rows.push(files);
|
||||
}
|
||||
await renderPreview(rows);
|
||||
} finally {
|
||||
|
||||
+4
-1
@@ -215,7 +215,8 @@ fastlane/metadata/android/
|
||||
│ ├── images/
|
||||
│ │ ├── icon.png (512x512)
|
||||
│ │ ├── featureGraphic.png (1024x500)
|
||||
│ │ └── phoneScreenshots/ (1.png, 2.png, ...)
|
||||
│ │ ├── phoneScreenshots/ (1.png, 2.png, ... linked to the store renders)
|
||||
│ │ └── tenInchScreenshots/ (1.png, 2.png, ... linked to the store renders)
|
||||
│ └── changelogs/ (generated — see below)
|
||||
├── ja/
|
||||
└── zh-CN/
|
||||
@@ -223,6 +224,8 @@ fastlane/metadata/android/
|
||||
|
||||
Locale directories generally match `packages/app/src/i18n/locales.ts`, but note that `en` becomes `en-US`.
|
||||
|
||||
Google Play reads its screenshots from the same folders; see [Store screenshots](release.md#store-screenshots).
|
||||
|
||||
F-Droid changelogs are generated from `CHANGELOG.md`. Run `npm run fdroid:changelogs`; `npm run fdroid:changelogs:check` verifies without writing. It is wired into the npm `version` lifecycle, so a release picks it up automatically and `git add -A` stages the result.
|
||||
|
||||
One changelog must be generated per-ABI-split, so each version will create **four** identical version-coded entries. F-Droid caps changelogs at 500 characters, so the generator strips some content and adds a link to the full notes.
|
||||
|
||||
+13
-2
@@ -305,8 +305,8 @@ macOS 13 maps to Darwin 22. The two values use different version domains; do not
|
||||
|
||||
iOS and Android store builds are not in `.github/workflows`. They are triggered by the EAS GitHub app the moment the `v*` tag is pushed:
|
||||
|
||||
- **Android (Play Store)** — EAS builds with profile `production` and auto-submits to the Play Store via `eas submit` (EAS-managed credentials, no Fastlane).
|
||||
- **iOS (TestFlight + App Store)** — EAS builds with profile `production`, uploads once through a `testflight` job, distributes the build to the `Paseo Beta` external group with Beta App Review submission, and a Fastlane lane submits that same build for App Store review.
|
||||
- **Android (Play Store)** — EAS builds with profile `production` and auto-submits to the Play Store via `eas submit` (EAS-managed credentials). A Fastlane lane then replaces the store screenshots.
|
||||
- **iOS (TestFlight + App Store)** — EAS builds with profile `production`, uploads once through a `testflight` job, distributes the build to the `Paseo Beta` external group with Beta App Review submission, and a Fastlane lane uploads the store screenshots and submits that same build for App Store review.
|
||||
- **Android APK (GitHub Release asset)** — separate, via `.github/workflows/android-apk-release.yml`. This is the only Android-related workflow that lives in this repo.
|
||||
|
||||
EAS uses the local app version source. `packages/app/app.config.js` derives the native version from the package version. Android `versionCode` is `major * 1_000_000 + minor * 1_000 + patch`. iOS reserves 1,000 build slots per app version: beta `N` uses slot `N`, and stable uses slot `999`. For example, `0.2.6-beta.2` appears in App Store Connect as version `0.2.6` build `2006002`; stable uses build `2006999`. Rebuilding the same tag produces the same native build number; if a store has already accepted a binary and you need a different binary, cut the next beta or patch instead of relying on EAS remote auto-increment.
|
||||
@@ -317,6 +317,16 @@ Stable iOS releases use one binary for two Apple review paths: Beta App Review f
|
||||
|
||||
There is no mobile-release workflow under `.github/workflows`. The EAS GitHub app reads the workflows under `packages/app/.eas/workflows` and handles tag triggering directly.
|
||||
|
||||
### Store screenshots
|
||||
|
||||
Each stable release publishes the screenshots committed under `assets/store-screenshots/output/` (see [its README](../assets/store-screenshots/README.md) for how they are made). To change the store screenshots, re-render and commit them before you tag.
|
||||
|
||||
- **App Store:** `submit_review` uploads `output/apple/` and `output/apple-ipad/` to the version it submits, replacing that version's `en-US` screenshots. Apple ties screenshots to an app version, so a version that is already live keeps the ones it shipped with. `Resubmit iOS for App Store review` uploads them again.
|
||||
- **Google Play:** `upload_android_screenshots` runs `fastlane android upload_screenshots` after `submit_android`. It replaces only the phone and 10-inch tablet screenshots, never the listing text or changelogs, and skips images whose checksum already matches.
|
||||
- **F-Droid:** reads the same files as Google Play. `fastlane/metadata/android/en-US/images/phoneScreenshots/` and `tenInchScreenshots/` hold symlinks to the Google renders. `render.mjs` rewrites them on every render, so added or renamed panels reach Google Play and F-Droid as well.
|
||||
|
||||
Fastlane cannot use the Play key Expo stores for `eas submit`. The Play lane reads its own copy of the same service-account key from the `GOOGLE_PLAY_JSON_KEY` file secret in the EAS `production` environment, and that service account needs the Play Console permission **Manage store presence**.
|
||||
|
||||
### Watching mobile builds from the terminal
|
||||
|
||||
Use the EAS CLI from `packages/app/`:
|
||||
@@ -359,6 +369,7 @@ For the `Release Mobile` EAS workflow, these jobs must pass:
|
||||
- `submit_ios_for_review` — iOS build submitted for App Store review via Fastlane
|
||||
- `build_android` — Android store binary built
|
||||
- `submit_android` — Android binary submitted to the Play Store
|
||||
- `upload_android_screenshots` — Google Play screenshots replaced from the repo
|
||||
|
||||
Do not treat `build_ios: SUCCESS` or `distribute_ios: SUCCESS` as a completed iOS release. `submit_ios_for_review: FAILURE` means the iOS release is blocked even if the build is visible in TestFlight.
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
../../../../../../packages/website/public/phone-1.png
|
||||
../../../../../../assets/store-screenshots/output/google/01-sidebar.png
|
||||
@@ -1 +1 @@
|
||||
../../../../../../packages/website/public/phone-2.png
|
||||
../../../../../../assets/store-screenshots/output/google/02-chat.png
|
||||
@@ -1 +1 @@
|
||||
../../../../../../packages/website/public/phone-3.png
|
||||
../../../../../../assets/store-screenshots/output/google/03-plugins.png
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../assets/store-screenshots/output/google/04-review.png
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../assets/store-screenshots/output/google/05-terminal.png
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../assets/store-screenshots/output/google-tablet/01-sidebar.png
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../assets/store-screenshots/output/google-tablet/02-chat.png
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../assets/store-screenshots/output/google-tablet/03-plugins.png
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../assets/store-screenshots/output/google-tablet/04-review.png
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../assets/store-screenshots/output/google-tablet/05-terminal.png
|
||||
@@ -42,6 +42,20 @@ jobs:
|
||||
build_id: ${{ needs.build_android.outputs.build_id }}
|
||||
profile: production
|
||||
|
||||
upload_android_screenshots:
|
||||
name: Upload Google Play screenshots
|
||||
needs: [build_android, submit_android]
|
||||
environment: production
|
||||
runs_on: macos-medium
|
||||
steps:
|
||||
- uses: eas/checkout
|
||||
- name: Install fastlane
|
||||
run: bundle install
|
||||
- name: Upload screenshots
|
||||
run: |
|
||||
export ANDROID_VERSION_CODE="${{ needs.build_android.outputs.app_build_version }}"
|
||||
bundle exec fastlane android upload_screenshots
|
||||
|
||||
submit_ios_for_review:
|
||||
name: Submit iOS for App Store review
|
||||
needs: [build_ios, distribute_ios]
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
require "fileutils"
|
||||
require "tmpdir"
|
||||
|
||||
default_platform(:ios)
|
||||
|
||||
APP_IDENTIFIER = "sh.paseo"
|
||||
|
||||
# Store screenshots are rendered into assets/store-screenshots/output; see the
|
||||
# README there. Google Play and F-Droid both read fastlane/metadata/android at
|
||||
# the repo root, whose screenshot files link to those renders.
|
||||
REPO_ROOT = File.expand_path("../../..", __dir__)
|
||||
STORE_SCREENSHOTS = File.join(REPO_ROOT, "assets/store-screenshots/output")
|
||||
ANDROID_METADATA = File.join(REPO_ROOT, "fastlane/metadata/android")
|
||||
|
||||
# deliver reads screenshots from one folder per language and picks the device
|
||||
# slot from each image's size, so both Apple sets go into en-US together.
|
||||
def stage_app_store_screenshots
|
||||
root = Dir.mktmpdir("app-store-screenshots")
|
||||
locale = File.join(root, "en-US")
|
||||
FileUtils.mkdir_p(locale)
|
||||
{ "apple" => "iphone", "apple-ipad" => "ipad" }.each do |set, device|
|
||||
Dir.glob(File.join(STORE_SCREENSHOTS, set, "*.png")).sort.each do |file|
|
||||
FileUtils.cp(file, File.join(locale, "#{device}-#{File.basename(file)}"))
|
||||
end
|
||||
end
|
||||
root
|
||||
end
|
||||
|
||||
platform :ios do
|
||||
desc "Submit the latest TestFlight build to App Store Review"
|
||||
lane :submit_review do
|
||||
@@ -87,7 +111,8 @@ platform :ios do
|
||||
},
|
||||
force: true,
|
||||
skip_binary_upload: true,
|
||||
skip_screenshots: true,
|
||||
screenshots_path: stage_app_store_screenshots,
|
||||
overwrite_screenshots: true,
|
||||
precheck_include_in_app_purchases: false,
|
||||
submission_information: {
|
||||
add_id_info_uses_idfa: false,
|
||||
@@ -96,3 +121,25 @@ platform :ios do
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
platform :android do
|
||||
desc "Replace the Google Play store screenshots with the ones in the repo"
|
||||
lane :upload_screenshots do
|
||||
# supply attaches listing changes to a release on the production track.
|
||||
# The release workflow passes the version code it just submitted; without
|
||||
# it, supply uses the track's only release.
|
||||
version_code = ENV["ANDROID_VERSION_CODE"].to_s.strip
|
||||
supply(
|
||||
package_name: APP_IDENTIFIER,
|
||||
json_key: ENV.fetch("GOOGLE_PLAY_JSON_KEY"),
|
||||
metadata_path: ANDROID_METADATA,
|
||||
version_code: version_code.empty? ? nil : version_code.to_i,
|
||||
skip_upload_apk: true,
|
||||
skip_upload_aab: true,
|
||||
skip_upload_metadata: true,
|
||||
skip_upload_changelogs: true,
|
||||
skip_upload_images: true,
|
||||
sync_image_upload: true,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user