Compare commits

..

1 Commits

Author SHA1 Message Date
Gauvain
b04cd5541a feat(settings): show Actions run number for CI builds, hide store build number
CI builds now display `version · commit · #run` so anyone can map a
sideloaded build back to its Actions run (artifacts + logs) without
Expo access. Store builds show the bare version - TestFlight already
surfaces the build number to testers.

Signed CI iOS builds move to a dedicated `ci` EAS profile (extends
production, autoIncrement off) so they stop inflating the store build
counter (counter was at 241 while the last TestFlight upload was 92).
2026-06-10 19:53:52 +02:00
8 changed files with 45 additions and 21 deletions

View File

@@ -12,10 +12,13 @@ on:
branches: [develop, master] branches: [develop, master]
# Exposed to `expo prebuild` (app.config.js → extra.build) so Settings can show the # Exposed to `expo prebuild` (app.config.js → extra.build) so Settings can show the
# branch + commit a CI build was made from. EAS cloud builds use EAS_BUILD_* instead. # branch + commit + Actions run a CI build was made from. EAS cloud builds use
# EAS_BUILD_* instead. The run number maps a sideloaded build back to its Actions
# run (artifacts + logs) without needing Expo access.
env: env:
EXPO_PUBLIC_GIT_BRANCH: ${{ github.head_ref || github.ref_name }} EXPO_PUBLIC_GIT_BRANCH: ${{ github.head_ref || github.ref_name }}
EXPO_PUBLIC_GIT_COMMIT: ${{ github.sha }} EXPO_PUBLIC_GIT_COMMIT: ${{ github.sha }}
EXPO_PUBLIC_GIT_RUN_NUMBER: ${{ github.run_number }}
jobs: jobs:
build-android-phone: build-android-phone:
@@ -237,7 +240,9 @@ jobs:
- name: 🚀 Build iOS app - name: 🚀 Build iOS app
env: env:
EXPO_TV: 0 EXPO_TV: 0
run: eas build -p ios --local --non-interactive # `ci` profile (extends production, autoIncrement off): keeps CI builds out of
# the production version tier and stops them inflating the store build counter.
run: eas build -p ios --local --non-interactive --profile ci
- name: 📅 Set date tag - name: 📅 Set date tag
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV
@@ -362,7 +367,7 @@ jobs:
- name: 🚀 Build iOS app - name: 🚀 Build iOS app
env: env:
EXPO_TV: 1 EXPO_TV: 1
run: eas build -p ios --local --non-interactive run: eas build -p ios --local --non-interactive --profile ci_tv
- name: 📅 Set date tag - name: 📅 Set date tag
run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV

View File

@@ -17,7 +17,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: 🚩 Apply merge conflict label - name: 🚩 Apply merge conflict label
uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.0 uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
with: with:
dirtyLabel: '⚔️ merge-conflict' dirtyLabel: '⚔️ merge-conflict'
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.' commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'

View File

@@ -33,6 +33,12 @@ const buildMeta = {
process.env.EAS_BUILD_PROFILE || process.env.EAS_BUILD_PROFILE ||
process.env.EXPO_PUBLIC_BUILD_PROFILE || process.env.EXPO_PUBLIC_BUILD_PROFILE ||
null, null,
// GitHub Actions run number (#2098) — lets anyone map a sideloaded CI build back
// to its Actions run (artifacts + logs) without Expo access. Null outside CI.
runNumber:
process.env.GITHUB_RUN_NUMBER ||
process.env.EXPO_PUBLIC_GIT_RUN_NUMBER ||
null,
builtAt: new Date().toISOString(), builtAt: new Date().toISOString(),
}; };

View File

@@ -105,7 +105,7 @@
"@react-native-tvos/config-tv": "0.1.6", "@react-native-tvos/config-tv": "0.1.6",
"@types/jest": "29.5.14", "@types/jest": "29.5.14",
"@types/lodash": "4.17.24", "@types/lodash": "4.17.24",
"@types/react": "19.2.17", "@types/react": "~19.2.10",
"@types/react-test-renderer": "19.1.0", "@types/react-test-renderer": "19.1.0",
"cross-env": "10.1.0", "cross-env": "10.1.0",
"expo-doctor": "1.19.9", "expo-doctor": "1.19.9",
@@ -605,7 +605,7 @@
"@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], "@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@types/react": ["@types/react@19.2.17", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw=="], "@types/react": ["@types/react@19.2.15", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q=="],
"@types/react-test-renderer": ["@types/react-test-renderer@19.1.0", "", { "dependencies": { "@types/react": "*" } }, "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ=="], "@types/react-test-renderer": ["@types/react-test-renderer@19.1.0", "", { "dependencies": { "@types/react": "*" } }, "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ=="],
@@ -2019,8 +2019,6 @@
"@testing-library/dom/pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], "@testing-library/dom/pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
"@types/react-test-renderer/@types/react": ["@types/react@19.2.15", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q=="],
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"ansi-fragments/slice-ansi": ["slice-ansi@2.1.0", "", { "dependencies": { "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" } }, "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ=="], "ansi-fragments/slice-ansi": ["slice-ansi@2.1.0", "", { "dependencies": { "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" } }, "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ=="],

View File

@@ -14,7 +14,7 @@ export const UserInfo: React.FC<Props> = ({ ...props }) => {
const { t } = useTranslation(); const { t } = useTranslation();
// Graduated build identifier — see utils/version.ts: // Graduated build identifier — see utils/version.ts:
// dev → "0.54.1 · branch · commit", develop/CI → "0.54.1 · commit", production → "0.54.1 (42)". // dev → "0.54.1 · branch · commit", develop/CI → "0.54.1 · commit · #run", production → "0.54.1".
const { display: version } = getVersionInfo(); const { display: version } = getVersionInfo();
return ( return (

View File

@@ -97,6 +97,14 @@
"credentialsSource": "local", "credentialsSource": "local",
"config": "ios-production.yml" "config": "ios-production.yml"
} }
},
"ci": {
"extends": "production",
"autoIncrement": false
},
"ci_tv": {
"extends": "production_tv",
"autoIncrement": false
} }
}, },
"submit": { "submit": {

View File

@@ -128,7 +128,7 @@
"@react-native-tvos/config-tv": "0.1.6", "@react-native-tvos/config-tv": "0.1.6",
"@types/jest": "29.5.14", "@types/jest": "29.5.14",
"@types/lodash": "4.17.24", "@types/lodash": "4.17.24",
"@types/react": "19.2.17", "@types/react": "~19.2.10",
"@types/react-test-renderer": "19.1.0", "@types/react-test-renderer": "19.1.0",
"cross-env": "10.1.0", "cross-env": "10.1.0",
"expo-doctor": "1.19.9", "expo-doctor": "1.19.9",

View File

@@ -10,6 +10,7 @@ export interface BuildMeta {
commit?: string | null; commit?: string | null;
branch?: string | null; branch?: string | null;
profile?: string | null; profile?: string | null;
runNumber?: string | null;
builtAt?: string | null; builtAt?: string | null;
} }
@@ -22,8 +23,10 @@ export interface VersionInfo {
commit: string | null; commit: string | null;
/** Git branch the build was made from, e.g. "develop". */ /** Git branch the build was made from, e.g. "develop". */
branch: string | null; branch: string | null;
/** EAS build profile, e.g. "production", "preview", or null for local. */ /** EAS build profile, e.g. "production", "ci", "preview", or null for local. */
profile: string | null; profile: string | null;
/** GitHub Actions run number the build came from, e.g. "2098". Null outside CI. */
runNumber: string | null;
isDev: boolean; isDev: boolean;
isProduction: boolean; isProduction: boolean;
/** Graduated label for the Settings "App version" row (see tiering below). */ /** Graduated label for the Settings "App version" row (see tiering below). */
@@ -34,13 +37,13 @@ export interface VersionInfo {
* Resolve a graduated version string for Settings. * Resolve a graduated version string for Settings.
* *
* Tiering (most → least detailed): * Tiering (most → least detailed):
* - dev / local build → `version · branch · commit` (full context for debugging) * - dev / local build → `version · branch · commit` (full context for debugging)
* - develop / CI / preview → `version · commit` (pin the exact source) * - develop / CI / preview → `version · commit · #run` (pin the exact source; the
* - production (store / TestFlight) → `version (build)` (store-correlatable; the * Actions run number maps the build to its run — artifacts + logs — without
* build number lets TestFlight reports pin a build whose version isn't a * Expo access)
* published release. Note: TestFlight and the public App Store ship the same * - production (store / TestFlight) → `version` (build number intentionally
* binary — telling them apart needs a runtime iOS receipt check, intentionally * not shown: TestFlight already displays it to testers, and the commit pins the
* not done here.) * binary better)
*/ */
export function getVersionInfo(): VersionInfo { export function getVersionInfo(): VersionInfo {
// Read native/config values defensively — a version string must never crash Settings // Read native/config values defensively — a version string must never crash Settings
@@ -60,6 +63,7 @@ export function getVersionInfo(): VersionInfo {
const commit = meta.commit ?? null; const commit = meta.commit ?? null;
const branch = meta.branch ?? null; const branch = meta.branch ?? null;
const profile = meta.profile ?? null; const profile = meta.profile ?? null;
const runNumber = meta.runNumber ?? null;
const isDev = __DEV__ === true; const isDev = __DEV__ === true;
const isProduction = const isProduction =
typeof profile === "string" && profile.startsWith("production"); typeof profile === "string" && profile.startsWith("production");
@@ -68,10 +72,12 @@ export function getVersionInfo(): VersionInfo {
if (isDev) { if (isDev) {
display = [version ?? "dev", branch, commit].filter(Boolean).join(" · "); display = [version ?? "dev", branch, commit].filter(Boolean).join(" · ");
} else if (isProduction) { } else if (isProduction) {
display = display = version ?? build ?? "N/A";
version && build ? `${version} (${build})` : (version ?? build ?? "N/A");
} else { } else {
display = [version, commit].filter(Boolean).join(" · ") || version || "N/A"; display =
[version, commit, runNumber && `#${runNumber}`]
.filter(Boolean)
.join(" · ") || "N/A";
} }
return { return {
@@ -80,6 +86,7 @@ export function getVersionInfo(): VersionInfo {
commit, commit,
branch, branch,
profile, profile,
runNumber,
isDev, isDev,
isProduction, isProduction,
display, display,