Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions .github/workflows/publish-example-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
name: Publish example app

on:
release:
types: [published]
Comment on lines +4 to +5

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensure we only deploy on releases on main. we may need to fetch tags from main and compare to skip releases from other branches such as 5.x

workflow_dispatch:
inputs:
tag:
description: Release tag to publish the example app for, e.g. v6.0.0
required: true
platform:
description: Platform to build. Defaults to all.
required: false
default: all
type: choice
options:
- all
- ios
- android

permissions:
contents: read

defaults:
run:
shell: bash

jobs:
check:
name: Check release is on main
runs-on: ubuntu-latest
env:
TAG: ${{ github.event.release.tag_name || inputs.tag }}
outputs:
on-main: ${{ steps.check.outputs.on-main }}
sha: ${{ steps.check.outputs.sha }}
Comment on lines +31 to +36

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have github.event.release.tag_name || inputs.tag in three places. We could define it once in the check job and pass it on as an output:

Suggested change
runs-on: ubuntu-latest
outputs:
on-main: ${{ steps.check.outputs.on-main }}
sha: ${{ steps.check.outputs.sha }}
runs-on: ubuntu-latest
env:
TAG: ${{ github.event.release.tag_name || inputs.tag }}
outputs:
on-main: ${{ steps.check.outputs.on-main }}
sha: ${{ steps.check.outputs.sha }}
tag: ${{ env.TAG }}

Then we can drop TAG from the step's env below, and read it from the output in the other two jobs:

TAG: ${{ needs.check.outputs.tag }}

tag: ${{ env.TAG }}
profile: ${{ steps.check.outputs.profile }}
steps:
- id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
status=$(gh api "repos/$GH_REPO/compare/main...$TAG" --jq .status)

if [ "$status" = "identical" ] || [ "$status" = "behind" ]; then
sha=$(gh api "repos/$GH_REPO/commits/$TAG" --jq .sha)
echo "on-main=true" >> "$GITHUB_OUTPUT"
echo "sha=$sha" >> "$GITHUB_OUTPUT"
else
echo "on-main=false" >> "$GITHUB_OUTPUT"
echo "::warning::$TAG is not on main ($status), nothing to deploy."
echo "$TAG is not on main ($status), nothing to deploy." >> "$GITHUB_STEP_SUMMARY"
fi

if [ "${TAG#*-}" != "$TAG" ]; then
echo "profile=preview" >> "$GITHUB_OUTPUT"
else
echo "profile=production" >> "$GITHUB_OUTPUT"
fi

publish:
name: Build and submit example app
needs: check
if: needs.check.outputs.on-main == 'true'
runs-on: ubuntu-latest
concurrency:
# One queue per channel, so a pending preview run doesn't cancel a production one
group: publish-example-app-${{ needs.check.outputs.profile }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.check.outputs.sha }}

- name: Setup
uses: ./.github/actions/setup

- name: Setup Expo
uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8.2.1
with:
eas-version: 24.12.1
token: ${{ secrets.EXPO_TOKEN }}

- name: Check Expo authentication
working-directory: ./example
run: |
if ! eas whoami; then
echo "::error::Could not authenticate with Expo, check EXPO_TOKEN."
exit 1
fi

- name: Set example app version from the release tag
working-directory: ./example
env:
TAG: ${{ needs.check.outputs.tag }}
run: |
version="${TAG#v}"
version="${version%%-*}"

if ! echo "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::Could not read a major.minor.patch version from $TAG."
exit 1
fi

jq --arg version "$version" '.expo.version = $version' app.json > app.json.tmp
mv app.json.tmp app.json

- name: Decide between an update and a build
id: decide
working-directory: ./example
env:
PROFILE: ${{ needs.check.outputs.profile }}
PLATFORM: ${{ inputs.platform || 'all' }}
run: |
platforms="$PLATFORM"
if [ "$PLATFORM" = "all" ]; then
platforms="ios android"
fi

build=()
update=()
for platform in $platforms; do
hash=$(npx expo-updates fingerprint:generate --platform "$platform" | jq -er .hash)

existing=$(eas build:list \
--platform "$platform" \
--channel "$PROFILE" \
--fingerprint-hash "$hash" \
--status finished \
--limit 1 \
--json \
--non-interactive | jq 'if type == "array" then length else "notanarray" end')

case "$existing" in
''|*[!0-9]*)
echo "::error::Could not read builds for $platform."
exit 1
;;
esac

echo "$platform: fingerprint $hash, $existing matching build(s)"
if [ "$existing" -eq 0 ]; then
build+=("$platform")
else
update+=("$platform")
fi
done

# EAS takes a single platform or "all"
to_platform() {
case $# in
0) echo "" ;;
1) echo "$1" ;;
*) echo all ;;
esac
}

echo "build=$(to_platform "${build[@]}")" >> "$GITHUB_OUTPUT"
echo "update=$(to_platform "${update[@]}")" >> "$GITHUB_OUTPUT"
echo "Building: ${build[*]:-none}. Updating: ${update[*]:-none}."

- name: Publish an update
# Before the build, so a slow or timed-out build can't hold up the update
if: steps.decide.outputs.update != ''
working-directory: ./example
env:
PROFILE: ${{ needs.check.outputs.profile }}
PLATFORM: ${{ steps.decide.outputs.update }}
TAG: ${{ needs.check.outputs.tag }}
run: |
eas update \
--channel "$PROFILE" \
--platform "$PLATFORM" \
--message "$TAG" \
--non-interactive

echo "Published an OTA update for $TAG for $PLATFORM on the $PROFILE channel." >> "$GITHUB_STEP_SUMMARY"

- name: Build and submit to the app stores
# Don't let a failed update for one platform skip the other's build
if: ${{ !cancelled() && steps.decide.outputs.build != '' }}
working-directory: ./example
env:
PROFILE: ${{ needs.check.outputs.profile }}
PLATFORM: ${{ steps.decide.outputs.build }}
TAG: ${{ needs.check.outputs.tag }}
run: |
eas build \
--platform "$PLATFORM" \
--profile "$PROFILE" \
--auto-submit-with-profile "$PROFILE" \
--non-interactive

echo "Built and submitted $TAG for $PLATFORM using the $PROFILE profile." >> "$GITHUB_STEP_SUMMARY"

- name: Report failure
if: failure() || cancelled()
env:
TAG: ${{ needs.check.outputs.tag }}
run: |
echo "::error::Example app deployment failed for $TAG. The release itself is fine, only the demo app did not update."
echo "Check EAS and the stores before re-running, a build may still be running or already submitted. Re-running before it finishes starts a second build." >> "$GITHUB_STEP_SUMMARY"
echo "If the build finished but the store upload failed, resubmit that build with \`eas submit --id <build id>\`. Re-running this workflow will publish an OTA update instead." >> "$GITHUB_STEP_SUMMARY"
echo "If only the update failed, re-running is safe." >> "$GITHUB_STEP_SUMMARY"
27 changes: 0 additions & 27 deletions .github/workflows/updates.yml

This file was deleted.

9 changes: 9 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ yarn release

NOTE: You must have a `GITHUB_TOKEN` environment variable available. You can create a GitHub access token with the "repo" access [here](https://github.com/settings/tokens).

Releases from main also deploy the example app via EAS. If there were no native code changes, it'll ship an OTA update.

Otherwise, for stable releases:

- The Android build is automatically submitted to Google Play for review
- The iOS build is automatically uploaded to App Store Connect and needs to be manually submitted for review

Pre-release builds are shipped to the internal track on Google Play for Android and TestFlight for iOS. They shouldn't be submitted to the App Store for review, or the stable build with the same version would be rejected.

## Reporting issues

You can report issues on our [bug tracker](https://github.com/callstack/react-native-paper/issues). Please follow the issue template when opening an issue.
Expand Down
2 changes: 2 additions & 0 deletions example/.fingerprintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# built by pod install, would make the fingerprint differ from the one EAS computes
**/expo-modules-jsi/apple/Products/**
1 change: 1 addition & 0 deletions example/.nvmrc
7 changes: 4 additions & 3 deletions example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
"owner": "react-native-paper",
"ios": {
"bundleIdentifier": "com.callstack.reactnativepaperexample",
"buildNumber": "26.0.5"
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false
}
},
"android": {
"package": "com.callstack.reactnativepaperexample",
"versionCode": 38,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with appVersionSource: "remote" and no local versionCode, ensure we have run eas build:version:set so EAS has a correct version to start from

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one is done, I already seeded the values (iOS 26.0.5, Android 38) and they auto-incremented as expected to 26.0.7 and 39 from my test builds

"permissions": [
"READ_EXTERNAL_STORAGE",
"WRITE_EXTERNAL_STORAGE"
Expand All @@ -37,7 +38,7 @@
}
},
"runtimeVersion": {
"policy": "sdkVersion"
"policy": "fingerprint"
},
"updates": {
"url": "https://u.expo.dev/ba8f5139-58fe-48f0-b2f2-2d675b6eb2d4"
Expand Down
34 changes: 34 additions & 0 deletions example/eas.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"cli": {
"appVersionSource": "remote"
},
"build": {
"production": {
"autoIncrement": true,
"android": {
"buildType": "app-bundle"
},
"channel": "production"
},
"preview": {
"extends": "production",
"channel": "preview"
}
},
"submit": {
"production": {
"android": {
"track": "production"
},
"ios": {
"ascAppId": "1548934513"
}
},
"preview": {
"extends": "production",
"android": {
"track": "internal"
}
}
}
}
7 changes: 7 additions & 0 deletions example/fingerprint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('expo/fingerprint').Config} */
module.exports = {
sourceSkips: [
'ExpoConfigVersions',
'PackageJsonAndroidAndIosScriptsIfNotContainRun',
],
};
Loading