Skip to content

Commit 7a6c4cb

Browse files
chore(runway): cherry-pick ci: switch OTA hotfix release branch to -ota suffix convention (#29586)
- ci: switch OTA hotfix release branch to `-ota` suffix convention (#29353) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** Replaces the implicit "two-digit patch encodes an OTA hotfix" convention (e.g. `release/7.73.01`) with an explicit `-ota` suffix on the branch name (e.g. `release/7.75.2-ota`). The suffix is **branch-name-only**: `OTA_VERSION`, the `CHANGELOG.md` header, and the production git tag all keep using the bare strict-SemVer `X.Y.Z` (e.g. `v7.75.2`). ### Why We decided to use OTA project in Runway so we now have ability to create a branch with pre-release tag (`-ota`) ### What happens for each release-branch type | Branch | Type | Native version files (`package.json`, `android/build.gradle`, `ios/…/project.pbxproj`, `bitrise.yml`) | `OTA_VERSION` (`app/constants/ota.ts`) | Changelog header / `release-changelog/*` branch | Production git tag | Release PR title | | --------------------------- | --------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------- | ----------------------------------------------- | ------------------ | ------------------------ | | `release/7.75.0` | Regular release | bumped to `7.75.0` (via `MetaMask/github-tools/.github/actions/create-release-pr@v1`) | unchanged | `7.75.0` / `release-changelog/7.75.0` | `v7.75.0` | `release: 7.75.0` | | `release/7.75.1` | Native hotfix | bumped to `7.75.1` (same action, `previous-version-ref: null`) | unchanged | `7.75.1` / `release-changelog/7.75.1` | `v7.75.1` | `release: 7.75.1` | | `release/7.75.2-ota` | OTA hotfix | **not** bumped (native stays at the prior native version, e.g. `7.75.1`) | bumped to `v7.75.2` | `7.75.2` / `release-changelog/7.75.2` | `v7.75.2` | `release: 7.75.2 (OTA)` | ### What changed #### Branch-name parsing & validation - **`.github/scripts/extract-semver.sh`** — accepts `release/X.Y.Z` and `release/X.Y.Z-ota`. Strips the suffix, sets `is_ota=true|false`, validates strict SemVer (no leading zeros). - **`.github/scripts/bump-ota-version-constants.sh`** — semver argument must be strict SemVer. - **`.github/scripts/run-update-release-changelog-mobile.sh`** — accepts both branch shapes; for `-ota` branches passes the bare `X.Y.Z` to `auto-changelog` as the explicit version (because `package.json` on an OTA branch still holds the prior native version). The previous prerelease-rewrite + URL-patching dance is gone. - **`.github/workflows/auto-create-release-pr.yml`** — exposes `is_ota` from the extract job and passes it as `is-ota` into `create-release-pr.yml`. - **`.github/workflows/build-rc-auto.yml`**, **`.github/workflows/update-release-changelog.yml`**, **`.github/workflows/create-bug-report.yml`** — branch-validation regex now accepts the `-ota` suffix so these workflows continue to fire correctly on OTA branches. #### Release-PR creation - **`.github/workflows/create-release-pr.yml`** — adds an `is-ota` boolean input (both `workflow_dispatch` and `workflow_call`). Native path is unchanged and still calls `MetaMask/github-tools/.github/actions/create-release-pr@v1`. OTA path operates on `release/X.Y.Z-ota`, runs only the OTA-specific bump + changelog steps, and opens a draft PR with an OTA-specific title/body. PR-existence check runs before the bump so retries don't double-write `OTA_VERSION`. #### OTA-bump detection (porting forward two safety guards) - **`.github/workflows/runway-ota-build-core.yml`** — added two early-exit guards in the `decide` job so stale/empty OTA states don't trigger pushes: 1. `OTA_VERSION == "vX.XX.X"` (sentinel, no OTA configured) → `ota_bump=false`. 2. A git tag matching `OTA_VERSION` already exists (already shipped, e.g. via a forward-merge from a prior release) → `ota_bump=false`. - **`.github/workflows/runway-ota-resolve-context.yml`** — mirrored the same two guards into the new reusable resolve-context workflow so both OTA-bump detectors stay aligned. Current callers (`runway-ota-rc.yml`, `runway-ota-production.yml`) ignore `ota_bump` today, so this is a no-op for shipped behaviour but prevents drift if a future caller starts consuming it. #### Documentation - **`app/constants/ota.ts`** — doc-comment refreshed to describe the `-ota` branch convention, the branch-name-only nature of the suffix, and the patch-increment guarantee that prevents tag collisions. ### Rollout notes - Runway is being configured to use `release/X.Y.Z-ota` as the OTA hotfix branch name. The first OTA hotfix to use the new scheme should produce a clean strict-SemVer changelog header (e.g. `## 7.75.2`) and a `v7.75.2` git tag, with no URL fix-ups required. ### Testing The 2 patch digits workflows were tested by creating real release branches which caused some automations to break. Since this change is similar so I did not test it by creating release branches. We can keep an eye out next time when we create an OTA release. The impact is small. If PRs are created incorrectly then we'll manually create them and fix. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry:null ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches multiple GitHub Actions workflows and scripts in the release/OTA pipeline; mistakes could cause release PR, changelog, or OTA/build gating to misfire, though changes are mostly regex/branch parsing and guardrails. > > **Overview** > Switches OTA hotfix identification from *two-digit patch encoding* to an explicit `release/X.Y.Z-ota` branch suffix, while keeping `OTA_VERSION`, changelog headers, and production tags on the bare strict SemVer `X.Y.Z`. > > Updates release automation to propagate an `is-ota` flag (`extract-semver.sh` → `auto-create-release-pr.yml` → `create-release-pr.yml`), bump/push `OTA_VERSION` and create PRs against `release/X.Y.Z-ota`, and broaden branch-validation in RC builds, bug-report creation, and changelog refresh to accept and strip the `-ota` suffix. > > Simplifies OTA changelog generation by removing prerelease/version-rewrite logic and instead passing the bare `X.Y.Z` explicitly for `-ota` branches, and adds guards in OTA/bump detection to treat the sentinel `vX.XX.X` or an already-existing `OTA_VERSION` git tag as *no bump* (avoid stale/shipped OTAs triggering OTA flows). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 07bf749. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> [4e11714](4e11714) Co-authored-by: Wei Sun <wei.sun@consensys.net>
1 parent 9336824 commit 7a6c4cb

11 files changed

Lines changed: 160 additions & 106 deletions

.github/scripts/bump-ota-version-constants.sh

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
# Updates OTA_VERSION in app/constants/ota.ts.
33
#
44
# With a semver second argument (OTA hotfix release workflow): sets OTA_VERSION to v<semver>
5-
# exactly as provided (e.g. 7.73.01 -> v7.73.01, 7.73.21 -> v7.73.21). No normalization is applied.
6-
# Two-digit patch AB means OTA hotfix: base patch A, iteration B.
5+
# exactly as provided (e.g. 7.75.2 -> v7.75.2). No normalization is applied.
6+
# The OTA hotfix branch is release/X.Y.Z-ota; the bare X.Y.Z is passed here. Runway always
7+
# increments the patch past any existing native tag on the same X.Y line, so v<X.Y.Z> is
8+
# unique (no collision with a native release tag).
79
#
810
# Without semver (local / legacy): increments in place — vX.XX.X -> v0, vN -> v(N+1), vA.B.C -> vA.B.(C+1)
911
set -euo pipefail
@@ -30,8 +32,9 @@ fi
3032

3133
new=""
3234
if [[ -n "$SEMVER" ]]; then
33-
if ! [[ "$SEMVER" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
34-
echo "Error: semver must be numeric X.Y.Z, got: ${SEMVER}" >&2
35+
# Strict SemVer core: no leading zeros on numeric identifiers.
36+
if ! [[ "$SEMVER" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
37+
echo "Error: semver must be strict SemVer X.Y.Z (no leading zeros), got: ${SEMVER}" >&2
3538
exit 1
3639
fi
3740
new="v${SEMVER}"

.github/scripts/extract-semver.sh

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,42 @@
22

33
set -euo pipefail
44

5+
# Extracts semver from a release branch name and flags whether it's an OTA hotfix.
6+
#
7+
# Supported branch shapes:
8+
# release/X.Y.Z → semver=X.Y.Z, is_ota=false
9+
# release/X.Y.Z-ota → semver=X.Y.Z, is_ota=true
10+
#
11+
# The `-ota` suffix is the signal for an OTA hotfix. Runway always increments the
12+
# patch past any native tag on the same X.Y line, so the stripped semver is still
13+
# unique (e.g. native v7.75.1 → OTA branch release/7.75.2-ota; git tag v7.75.2 is
14+
# fresh). OTA_VERSION in app/constants/ota.ts is set to the stripped semver
15+
# (v7.75.2), and the production OTA tag uses the same v-prefixed string.
16+
517
ref_name="${GITHUB_REF#refs/heads/}"
618

7-
# Extract semver only for release/X.Y.Z
8-
if [[ "$ref_name" == release/* ]]; then
9-
semver="${ref_name#release/}"
10-
else
11-
echo "Error: Branch name must be release/X.Y.Z where X, Y, Z are numbers. Got: $ref_name" >&2
19+
if [[ "$ref_name" != release/* ]]; then
20+
echo "Error: Branch name must be release/X.Y.Z or release/X.Y.Z-ota. Got: $ref_name" >&2
1221
exit 1
1322
fi
1423

15-
# Validate semver format X.Y.Z where X, Y, Z are numbers
16-
if ! [[ "$semver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
17-
echo "Error: Invalid semver in branch name: $ref_name (extracted: $semver; must be numeric X.Y.Z)" >&2
24+
tail="${ref_name#release/}"
25+
26+
is_ota="false"
27+
if [[ "$tail" == *-ota ]]; then
28+
is_ota="true"
29+
semver="${tail%-ota}"
30+
else
31+
semver="$tail"
32+
fi
33+
34+
# Strict SemVer core: no leading zeros on numeric identifiers (0 alone is fine).
35+
if ! [[ "$semver" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
36+
echo "Error: Invalid semver in branch name: $ref_name (extracted: $semver; must be strict SemVer X.Y.Z with no leading zeros)" >&2
1837
exit 1
1938
fi
2039

2140
echo " semver-version: ${semver}"
41+
echo " is-ota: ${is_ota}"
2242
echo "semver=${semver}" >> "$GITHUB_OUTPUT"
43+
echo "is_ota=${is_ota}" >> "$GITHUB_OUTPUT"
Lines changed: 22 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
#!/usr/bin/env bash
22
# Invokes MetaMask github-tools update-release-changelog.sh with OTA version handling.
33
#
4-
# OTA hotfix detection: a two-digit patch in the branch version (e.g. release/7.77.01).
5-
# auto-changelog rejects these as invalid SemVer (leading zeros are forbidden).
6-
#
7-
# This wrapper converts the Runway OTA version to a valid SemVer prerelease
8-
# for the changelog tool:
9-
# Runway 7.77.01 → SemVer 7.77.0-ota.1
10-
# Runway 7.77.21 → SemVer 7.77.2-ota.1
11-
#
12-
# The SemVer prerelease format stays in CHANGELOG.md permanently. auto-changelog
13-
# validates ALL version headers on every run, so non-SemVer versions like 7.77.01
14-
# would break all future changelog generation (not just OTA runs).
15-
#
16-
# The Runway version (7.77.01) is used only in branch names, OTA_VERSION, and PR titles.
17-
#
18-
# Mapping (two-digit patch AB): X.Y.AB → X.Y.A-ota.B
19-
# Single-digit patches pass through unchanged.
4+
# Branch conventions:
5+
# release/X.Y.Z → native release/hotfix; passes through unchanged (tool derives
6+
# the version from package.json on the branch).
7+
# release/X.Y.Z-ota → OTA hotfix; the -ota suffix exists only on the branch name.
8+
# The changelog header and the production git tag both use the
9+
# bare X.Y.Z. package.json on the OTA branch still carries the
10+
# previous native version (OTA doesn't bump native), so we must
11+
# pass X.Y.Z explicitly to auto-changelog. Runway always advances
12+
# X.Y.Z past any existing native patch on the same X.Y line, so
13+
# the bare header never collides with a prior native entry.
2014
set -euo pipefail
2115

22-
RELEASE_BRANCH="${1:?release branch (e.g. release/7.73.01)}"
16+
RELEASE_BRANCH="${1:?release branch (e.g. release/7.75.0 or release/7.75.2-ota)}"
2317
PLATFORM="${2:?platform (mobile|extension)}"
2418
REPO_URL="${3:?repository https URL}"
2519
PREV_REF="${4:-null}"
@@ -30,47 +24,24 @@ if [[ ! -f "$SCRIPT" ]]; then
3024
exit 1
3125
fi
3226

33-
# Extract version from branch name
34-
if [[ "$RELEASE_BRANCH" =~ ^release/([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
27+
# Extract version and detect OTA suffix from branch name
28+
if [[ "$RELEASE_BRANCH" =~ ^release/([0-9]+\.[0-9]+\.[0-9]+)(-ota)?$ ]]; then
3529
RUNWAY_VERSION="${BASH_REMATCH[1]}"
30+
OTA_SUFFIX="${BASH_REMATCH[2]}"
3631
else
37-
echo "Error: branch does not match release/X.Y.Z: ${RELEASE_BRANCH}" >&2
32+
echo "Error: branch does not match release/X.Y.Z or release/X.Y.Z-ota: ${RELEASE_BRANCH}" >&2
3833
exit 1
3934
fi
4035

41-
PATCH="${RUNWAY_VERSION##*.}"
36+
if [[ -n "$OTA_SUFFIX" ]]; then
37+
# OTA hotfix: pass the bare X.Y.Z as the explicit SemVer version because package.json
38+
# on the OTA branch is still pinned to the prior native version.
39+
echo "OTA hotfix detected: branch ${RELEASE_BRANCH} → changelog header ${RUNWAY_VERSION}, tag v${RUNWAY_VERSION}"
4240

43-
if [[ ${#PATCH} -eq 2 ]]; then
44-
# OTA hotfix: two-digit patch AB → X.Y.A-ota.B
45-
BASE_PATCH="${PATCH:0:1}"
46-
OTA_NUM="${PATCH:1:1}"
47-
SEMVER_VERSION="${RUNWAY_VERSION%.*}.${BASE_PATCH}-ota.${OTA_NUM}"
48-
49-
echo "OTA hotfix detected: Runway ${RUNWAY_VERSION} → SemVer ${SEMVER_VERSION}"
50-
51-
# Pass real branch (arg 1) for git ops, valid SemVer (arg 6) for auto-changelog.
41+
# Pass real branch (arg 1) for git ops, bare SemVer (arg 6) for auto-changelog.
5242
# Changelog branch (arg 5) left empty so the tool auto-derives it from the SemVer version.
53-
"$SCRIPT" "$RELEASE_BRANCH" "$PLATFORM" "$REPO_URL" "$PREV_REF" "" "$SEMVER_VERSION"
54-
55-
# Fix comparison URLs on the changelog branch.
56-
# auto-changelog generates tag refs like "v7.76.0-ota.1" but the actual git tag
57-
# is "v7.76.01" (Runway format). Only URL tag refs carry the "v" prefix, so this
58-
# sed does not touch version headers (which are bare "7.76.0-ota.1" without "v").
59-
CL_BRANCH="release-changelog/${SEMVER_VERSION}"
60-
git fetch origin "$CL_BRANCH" 2>/dev/null || true
61-
if git rev-parse "origin/${CL_BRANCH}" >/dev/null 2>&1; then
62-
git checkout "$CL_BRANCH"
63-
if grep -q "v${SEMVER_VERSION}" CHANGELOG.md 2>/dev/null; then
64-
sed -i "s|v${SEMVER_VERSION}|v${RUNWAY_VERSION}|g" CHANGELOG.md
65-
if ! git diff --quiet CHANGELOG.md; then
66-
git add CHANGELOG.md
67-
git commit -m "fix: use Runway tag v${RUNWAY_VERSION} in changelog comparison URLs"
68-
git push origin "$CL_BRANCH"
69-
echo "Fixed changelog comparison URLs: v${SEMVER_VERSION} → v${RUNWAY_VERSION}"
70-
fi
71-
fi
72-
fi
43+
exec "$SCRIPT" "$RELEASE_BRANCH" "$PLATFORM" "$REPO_URL" "$PREV_REF" "" "$RUNWAY_VERSION"
7344
else
74-
# Regular release or single-digit hotfix: pass through unchanged
45+
# Native release or hotfix: pass through; the tool derives the version from package.json.
7546
exec "$SCRIPT" "$RELEASE_BRANCH" "$PLATFORM" "$REPO_URL" "$PREV_REF"
7647
fi

.github/workflows/auto-create-release-pr.yml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
name: Auto Create Release PR
22

3-
# When Runway creates release/X.Y.Z, this workflow calls create-release-pr.yml.
4-
# Semver convention for mobile hotfixes:
5-
# - X.Y.0 — regular release (no OTA_VERSION bump)
6-
# - X.Y.Z with a single-digit patch (e.g. 7.71.1, 7.71.2) — regular hotfix (no OTA_VERSION bump)
7-
# - X.Y.AB with a two-digit patch (e.g. 7.73.01, 7.73.21) — OTA hotfix; OTA_VERSION is v7.73.01.
8-
# Two-digit patch AB → base patch A, OTA iteration B.
9-
# CHANGELOG.md uses the SemVer prerelease format (e.g. 7.73.0-ota.1) permanently
10-
# because auto-changelog validates all version headers as strict SemVer.
3+
# When Runway creates release/X.Y.Z (native) or release/X.Y.Z-ota (OTA hotfix),
4+
# this workflow calls create-release-pr.yml.
5+
# Semver / branch convention for mobile hotfixes:
6+
# - release/X.Y.0 — regular release (no OTA_VERSION bump).
7+
# - release/X.Y.Z (Z > 0) — native hotfix (no OTA_VERSION bump).
8+
# - release/X.Y.Z-ota — OTA hotfix; OTA_VERSION is set to v<X.Y.Z>.
9+
# Runway always increments Z past any existing native patch on the same X.Y
10+
# line, so the production OTA tag v<X.Y.Z> never collides with a native tag.
11+
# The `-ota` suffix is a branch-name convention ONLY: CHANGELOG.md header,
12+
# OTA_VERSION, and the production git tag all use the bare X.Y.Z.
1113

1214
on:
1315
create:
@@ -23,6 +25,7 @@ jobs:
2325
runs-on: ubuntu-latest
2426
outputs:
2527
semver: ${{ steps.out.outputs.semver }}
28+
is_ota: ${{ steps.out.outputs.is_ota }}
2629
steps:
2730
- name: Checkout repository
2831
uses: actions/checkout@v4
@@ -44,3 +47,4 @@ jobs:
4447
google-application-creds-base64: ${{ secrets.GCP_RLS_SHEET_ACCOUNT_BASE64 }}
4548
with:
4649
semver-version: ${{ needs.extract.outputs.semver }}
50+
is-ota: ${{ needs.extract.outputs.is_ota == 'true' }}

.github/workflows/build-rc-auto.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,16 @@ jobs:
5454
echo "Checking branch: $BRANCH_NAME"
5555
echo "branch-name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
5656
57-
# Validate branch matches release/x.y.z format (semantic versioning)
58-
if [[ "$BRANCH_NAME" =~ ^release/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
57+
# Validate branch matches release/x.y.z or release/x.y.z-ota (OTA hotfix) format
58+
if [[ "$BRANCH_NAME" =~ ^release/[0-9]+\.[0-9]+\.[0-9]+(-ota)?$ ]]; then
5959
VERSION="${BRANCH_NAME#release/}"
60+
# Strip `-ota` suffix: it is a branch-name convention only. Downstream consumers
61+
# (build-announce display "RC X.Y.Z", test-plan artifact name) expect strict X.Y.Z.
62+
VERSION="${VERSION%-ota}"
6063
echo "Valid release branch detected: $BRANCH_NAME (version: $VERSION)"
6164
echo "semver=$VERSION" >> "$GITHUB_OUTPUT"
6265
else
63-
echo "Branch '$BRANCH_NAME' does not match release/x.y.z pattern. Skipping."
66+
echo "Branch '$BRANCH_NAME' does not match release/x.y.z or release/x.y.z-ota pattern. Skipping."
6467
echo "semver=" >> "$GITHUB_OUTPUT"
6568
exit 1
6669
fi

.github/workflows/create-bug-report.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ jobs:
99
- name: Extract version from branch name if release branch
1010
id: extract_version
1111
run: |
12-
if [[ "$GITHUB_REF" =~ ^refs/heads/release/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
12+
if [[ "$GITHUB_REF" =~ ^refs/heads/release/[0-9]+\.[0-9]+\.[0-9]+(-ota)?$ ]]; then
1313
version="${GITHUB_REF#refs/heads/release/}"
14+
# Strip `-ota` suffix: it is a branch-name convention only. Downstream
15+
# (create-bug-report-issue.ts → isValidVersionFormat) expects strict X.Y.Z.
16+
version="${version%-ota}"
1417
echo "New release branch($version), continue next steps"
1518
echo "version=$version" >> "$GITHUB_OUTPUT"
1619
else

.github/workflows/create-release-pr.yml

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ on:
66
semver-version:
77
description: 'A semantic version, eg: x.x.x'
88
required: true
9+
is-ota:
10+
description: 'Set to true for OTA hotfix (branch release/X.Y.Z-ota). Leave false for native release/hotfix.'
11+
required: false
12+
type: boolean
13+
default: false
914

1015
workflow_call:
1116
inputs:
1217
semver-version:
1318
required: true
1419
type: string
20+
is-ota:
21+
required: false
22+
type: boolean
23+
default: false
1524
secrets:
1625
github-token:
1726
required: false
@@ -24,7 +33,8 @@ on:
2433

2534
jobs:
2635
resolve-bases:
27-
# Determines if the release is hotfix or not based on semver. Then sets the appropriate base branches for the checkout and release PR.
36+
# Determines if the release is a hotfix or not based on semver, and whether it's an OTA hotfix
37+
# based on the is-ota input. Sets the appropriate base branches for the checkout and release PR.
2838
runs-on: ubuntu-latest
2939
outputs:
3040
checkout_base: ${{ steps.out.outputs.checkout_base }}
@@ -35,26 +45,25 @@ jobs:
3545
shell: bash
3646
env:
3747
SEMVER: ${{ inputs.semver-version }}
48+
IS_OTA: ${{ inputs.is-ota }}
3849
run: |
3950
set -euo pipefail
4051
4152
echo "Provided semver input: ${SEMVER}"
42-
# Validate semver
43-
if ! [[ "${SEMVER:-}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
44-
echo "Error: semver-version must be numeric X.Y.Z, got: ${SEMVER:-<empty>}" >&2
53+
echo "Provided is-ota input: ${IS_OTA}"
54+
# Validate strict SemVer core (no leading zeros; the -ota suffix lives on the branch name, not the semver input).
55+
if ! [[ "${SEMVER:-}" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
56+
echo "Error: semver-version must be strict SemVer X.Y.Z (no leading zeros), got: ${SEMVER:-<empty>}" >&2
4557
exit 1
4658
fi
4759
4860
patch="${SEMVER##*.}"
49-
# OTA hotfix: patch has exactly two digits (e.g. 01, 21, 22).
50-
# Two-digit patch AB → base patch A, OTA iteration B.
51-
# Single-digit patches are regular hotfixes; single-digit 0 is a regular release.
52-
if [[ ${#patch} -eq 2 ]]; then
61+
if [[ "${IS_OTA}" == "true" ]]; then
5362
echo "is_ota=true" >> "$GITHUB_OUTPUT"
54-
echo "OTA hotfix (two-digit patch: ${patch}): branch release/${SEMVER}; OTA_VERSION v${SEMVER}."
63+
echo "OTA hotfix: branch release/${SEMVER}-ota; OTA_VERSION v${SEMVER}."
5564
else
5665
echo "is_ota=false" >> "$GITHUB_OUTPUT"
57-
echo "Not OTA hotfix (patch segment: ${patch})."
66+
echo "Native release/hotfix (patch=${patch})."
5867
fi
5968
6069
if [ "$patch" -gt 0 ]; then
@@ -116,7 +125,7 @@ jobs:
116125
github-token: ${{ github.event_name == 'workflow_dispatch' && secrets.PR_TOKEN || secrets.github-token }}
117126
google-application-creds-base64: ${{ github.event_name == 'workflow_dispatch' && secrets.GCP_RLS_SHEET_ACCOUNT_BASE64 || secrets.google-application-creds-base64 }}
118127

119-
# Check PR before checkout/bump/push so retries do not increment OTA_VERSION again when a PR already exists.
128+
# Check PR before checkout/bump/push so retries do not overwrite OTA_VERSION again when a PR already exists.
120129
- name: Check for existing OTA release PR
121130
id: ota_release_pr
122131
if: needs.resolve-bases.outputs.is_ota == 'true'
@@ -127,7 +136,7 @@ jobs:
127136
GH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.PR_TOKEN || secrets.github-token }}
128137
run: |
129138
set -euo pipefail
130-
BRANCH="release/${SEMVER}"
139+
BRANCH="release/${SEMVER}-ota"
131140
COUNT=$(gh pr list --repo "$GITHUB_REPOSITORY" --head "$BRANCH" --base "$RELEASE_BASE" --json number --jq 'length')
132141
if [[ "$COUNT" -gt 0 ]]; then
133142
echo "exists=true" >> "$GITHUB_OUTPUT"
@@ -141,7 +150,7 @@ jobs:
141150
if: needs.resolve-bases.outputs.is_ota == 'true' && steps.ota_release_pr.outputs.exists != 'true'
142151
uses: actions/checkout@v4
143152
with:
144-
ref: release/${{ inputs.semver-version }}
153+
ref: release/${{ inputs.semver-version }}-ota
145154
token: ${{ github.event_name == 'workflow_dispatch' && secrets.PR_TOKEN || secrets.github-token }}
146155
fetch-depth: 0
147156

@@ -153,7 +162,7 @@ jobs:
153162
GH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.PR_TOKEN || secrets.github-token }}
154163
run: |
155164
set -euo pipefail
156-
BRANCH="release/${SEMVER}"
165+
BRANCH="release/${SEMVER}-ota"
157166
git config user.name metamaskbot
158167
git config user.email metamaskbot@users.noreply.github.com
159168
bash .github/scripts/bump-ota-version-constants.sh app/constants/ota.ts "${SEMVER}"
@@ -165,7 +174,9 @@ jobs:
165174
git push origin "$BRANCH"
166175
fi
167176
168-
# Changelog: run-update-release-changelog-mobile.sh converts OTA two-digit patches to valid SemVer prereleases. The SemVer format stays in CHANGELOG.md permanently.
177+
# Changelog: run-update-release-changelog-mobile.sh passes the bare X.Y.Z (stripped from
178+
# release/X.Y.Z-ota) to auto-changelog, so the changelog header and the production git
179+
# tag match exactly (both v<X.Y.Z>). The `-ota` suffix is a branch-name convention only.
169180
- name: Checkout github-tools (release changelog)
170181
if: needs.resolve-bases.outputs.is_ota == 'true' && steps.ota_release_pr.outputs.exists != 'true'
171182
uses: actions/checkout@v4
@@ -194,7 +205,7 @@ jobs:
194205
corepack enable
195206
yarn install --immutable
196207
bash .github/scripts/run-update-release-changelog-mobile.sh \
197-
"release/${{ inputs.semver-version }}" \
208+
"release/${{ inputs.semver-version }}-ota" \
198209
mobile \
199210
"${{ github.server_url }}/${{ github.repository }}" \
200211
"null"
@@ -208,13 +219,11 @@ jobs:
208219
GH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.PR_TOKEN || secrets.github-token }}
209220
run: |
210221
set -euo pipefail
211-
BRANCH="release/${SEMVER}"
212-
PATCH="${SEMVER##*.}"
213-
SEMVER_CL="${SEMVER%.*}.${PATCH:0:1}-ota.${PATCH:1:1}"
222+
BRANCH="release/${SEMVER}-ota"
214223
BODY="OTA hotfix: branch \`${BRANCH}\`."
215224
BODY="${BODY}"$'\n\n'"- Native semver and build version are **not** bumped."
216225
BODY="${BODY}"$'\n'"- \`OTA_VERSION\` in \`app/constants/ota.ts\` is \`v${SEMVER}\`."
217-
BODY="${BODY}"$'\n'"- CHANGELOG.md uses the SemVer prerelease format \`${SEMVER_CL}\` (maps to Runway \`${SEMVER}\`) because auto-changelog validates all headers as strict SemVer."
226+
BODY="${BODY}"$'\n'"- CHANGELOG.md header and production git tag both use bare \`${SEMVER}\` / \`v${SEMVER}\`; the \`-ota\` suffix is branch-only."
218227
gh pr create --repo "$GITHUB_REPOSITORY" --draft \
219228
--base "$RELEASE_BASE" --head "$BRANCH" \
220229
--title "release: ${SEMVER} (OTA)" \

0 commit comments

Comments
 (0)