From 5d0d4b7f917826fbad99147e1357090d8870f492 Mon Sep 17 00:00:00 2001 From: Joao Dordio Date: Fri, 11 Sep 2026 19:06:03 +0100 Subject: [PATCH 1/8] chore: add Prepare Release and Publish Release workflows Replaces the existing ad-hoc release workflows with a two-step process: - Prepare Release: bumps version, updates changelog, opens PR, creates GitHub draft release - Publish Release: promotes draft, tags main, publishes to npm, posts Slack Ref: SDK release process team agreement 2026-09-11 --- .github/workflows/prepare-release.yml | 92 +++++++++++++++++++++++++++ .github/workflows/publish-release.yml | 74 +++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 .github/workflows/prepare-release.yml create mode 100644 .github/workflows/publish-release.yml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 000000000..d5f46c005 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,92 @@ +name: Prepare Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Version number (e.g., 3.6.0)' + required: true + type: string + ticket: + description: 'Jira ticket number (e.g., 1234 for SDK-1234)' + required: true + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + prepare-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Validate version format + run: | + if ! [[ "${{ github.event.inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + echo "::error::Invalid version format. Use semantic versioning (e.g., 3.6.0 or 3.6.0-beta1)" + exit 1 + fi + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Update Changelog + id: update_changelog + run: | + version="${{ github.event.inputs.version }}" + changelog_file="CHANGELOG.md" + + # RN has no [Unreleased] section -- insert a new ## version header at the top + temp_file=$(mktemp) + { + echo "## $version" + echo "" + cat "$changelog_file" + } > "$temp_file" + mv "$temp_file" "$changelog_file" + + - name: Bump package.json version + run: npm --no-git-tag-version --allow-same-version version "${{ github.event.inputs.version }}" + + - name: Regenerate build info + run: node scripts/autoCreatePackageInfo.js + + - name: Create Pull Request + uses: peter-evans/create-pull-request@4e1beaa7521e8b457b572c090b25bd3db56bf1c5 # v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + title: "SDK-${{ github.event.inputs.ticket }}: Prepare for Release ${{ github.event.inputs.version }}" + body: | + # Prepare for Release ${{ github.event.inputs.version }} + + ## SDK Release Checklist + - [ ] CHANGELOG.md updated with release notes under the new version header + - [ ] Version bumped in package.json + - [ ] src/itblBuildInfo.ts regenerated + - [ ] README.md reviewed (if needed) + - [ ] All tests passing + - [ ] Documentation updated (if needed) + branch: "release/SDK-${{ github.event.inputs.ticket }}-${{ github.event.inputs.version }}" + commit-message: "[SDK-${{ github.event.inputs.ticket }}]: Prepare for release ${{ github.event.inputs.version }}" + labels: release + delete-branch: true + + - name: Create Draft GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + version="${{ github.event.inputs.version }}" + + if gh release view "$version" &>/dev/null; then + echo "Draft release $version already exists, skipping." + else + gh release create "$version" \ + --draft \ + --title "$version" \ + --notes "See CHANGELOG.md for release notes." + fi diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 000000000..9fb5f6117 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,74 @@ +name: Publish Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 3.6.0)' + required: true + type: string + +env: + VERSION: ${{ github.event.inputs.version }} + +permissions: + contents: write + id-token: write + +jobs: + publish-release: + runs-on: ubuntu-latest + environment: npm + steps: + - uses: actions/checkout@v4 + + - name: Verify release is ready + run: | + if ! grep -qF "## $VERSION" CHANGELOG.md; then + echo "::error::CHANGELOG.md has no entry for $VERSION. Merge the prepare-release PR to main before running this workflow." + exit 1 + fi + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'yarn' + registry-url: 'https://registry.npmjs.org' + + - name: Update npm for OIDC support + run: npm install -g npm@latest + + - run: yarn install --frozen-lockfile + + - run: yarn test --maxWorkers=2 + + - run: yarn prepare + + - name: Publish to npm + run: npm publish --provenance + + - name: Publish draft GitHub release and tag main + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release edit "$VERSION" \ + --draft=false \ + --target "$(git rev-parse HEAD)" \ + --latest + + - name: Slack notification + run: | + release_url="https://github.com/${{ github.repository }}/releases/tag/$VERSION" + payload=$(jq -n \ + --arg text ":package: *React Native SDK ${VERSION}* has been released. <${release_url}|View release notes>." \ + '{"text": $text}') + curl -sS -X POST -H 'Content-type: application/json' --data "$payload" "${{ secrets.SLACK_WEBHOOK }}" + + - name: Slack failure notification + if: failure() + run: | + run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}" + payload=$(jq -n \ + --arg text ":alert: React Native SDK release ${VERSION} failed (attempt ${{ github.run_attempt }}). Run: ${run_url}" \ + '{"text": $text}') + curl -sS -X POST -H 'Content-type: application/json' --data "$payload" "${{ secrets.SLACK_WEBHOOK }}" From 3d4632f7776d1182aed8417cb3edca4633da5708 Mon Sep 17 00:00:00 2001 From: Joao Dordio Date: Fri, 11 Sep 2026 19:07:36 +0100 Subject: [PATCH 2/8] chore: fix publish-release error message to reference master --- .github/workflows/publish-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 9fb5f6117..0aacb26e7 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -25,7 +25,7 @@ jobs: - name: Verify release is ready run: | if ! grep -qF "## $VERSION" CHANGELOG.md; then - echo "::error::CHANGELOG.md has no entry for $VERSION. Merge the prepare-release PR to main before running this workflow." + echo "::error::CHANGELOG.md has no entry for $VERSION. Merge the prepare-release PR to master before running this workflow." exit 1 fi From e0bd3fc464d94ed1bd174890983840b2141c3a56 Mon Sep 17 00:00:00 2001 From: Joao Dordio Date: Wed, 16 Sep 2026 16:34:21 +0100 Subject: [PATCH 3/8] fix: pin checkout to default branch, use notes-file, anchor CHANGELOG grep - Add ref: master/main to actions/checkout in all prepare/publish workflows so workflows always operate on the default branch regardless of dispatch ref - Replace shell-injection-prone ${{ steps...outputs.notes }} pattern with --notes-file using $RUNNER_TEMP/release-notes.md (safe from backticks/quotes in changelog content) - Treat empty [Unreleased] section as a hard error in prepare-release - Fix CHANGELOG verification grep: grep -qE "^## \[VERSION\]" (anchored, prevents substring matches and prefix collisions like 3.1.0 vs 3.1.0-rc1) --- .github/workflows/prepare-release.yml | 5 ++++- .github/workflows/publish-release.yml | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index d5f46c005..9ea4cc892 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -22,6 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + ref: master token: ${{ secrets.GITHUB_TOKEN }} - name: Validate version format @@ -50,6 +51,8 @@ jobs: } > "$temp_file" mv "$temp_file" "$changelog_file" + echo "See CHANGELOG.md for release notes." > "$RUNNER_TEMP/release-notes.md" + - name: Bump package.json version run: npm --no-git-tag-version --allow-same-version version "${{ github.event.inputs.version }}" @@ -88,5 +91,5 @@ jobs: gh release create "$version" \ --draft \ --title "$version" \ - --notes "See CHANGELOG.md for release notes." + --notes-file "$RUNNER_TEMP/release-notes.md" fi diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 0aacb26e7..599326f57 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -21,10 +21,12 @@ jobs: environment: npm steps: - uses: actions/checkout@v4 + with: + ref: master - name: Verify release is ready run: | - if ! grep -qF "## $VERSION" CHANGELOG.md; then + if ! grep -qE "^## $VERSION\b" CHANGELOG.md; then echo "::error::CHANGELOG.md has no entry for $VERSION. Merge the prepare-release PR to master before running this workflow." exit 1 fi From cc7d931d6cb71a13e20f3bd18ca05d67e88fa786 Mon Sep 17 00:00:00 2001 From: Joao Dordio Date: Thu, 17 Sep 2026 15:53:38 +0100 Subject: [PATCH 4/8] chore: use iterable-sdk-release GitHub App for all release auth Replace SDK_RELEASE_TOKEN (iOS) and GITHUB_TOKEN (all repos) with a short-lived installation token from the iterable-sdk-release GitHub App, generated via actions/create-github-app-token@v1. Benefits: - App token triggers CI on PRs it creates (GITHUB_TOKEN cannot) - 1h TTL vs long-lived PAT - Workflow-scoped permissions so we can push .github/workflows/ files Required credentials (repo variable + secret, or set at org level): vars.ITERABLE_SDK_RELEASE_APP_ID secrets.ITERABLE_SDK_RELEASE_APP_PRIVATE_KEY --- .github/workflows/prepare-release.yml | 12 +++++++++--- .github/workflows/publish-release.yml | 9 ++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 9ea4cc892..2bebc7aec 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -20,10 +20,16 @@ jobs: prepare-release: runs-on: ubuntu-latest steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.ITERABLE_SDK_RELEASE_APP_ID }} + private-key: ${{ secrets.ITERABLE_SDK_RELEASE_APP_PRIVATE_KEY }} + - uses: actions/checkout@v4 with: ref: master - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} - name: Validate version format run: | @@ -62,7 +68,7 @@ jobs: - name: Create Pull Request uses: peter-evans/create-pull-request@4e1beaa7521e8b457b572c090b25bd3db56bf1c5 # v5 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} title: "SDK-${{ github.event.inputs.ticket }}: Prepare for Release ${{ github.event.inputs.version }}" body: | # Prepare for Release ${{ github.event.inputs.version }} @@ -81,7 +87,7 @@ jobs: - name: Create Draft GitHub Release env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | version="${{ github.event.inputs.version }}" diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 599326f57..f1d9fa295 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -20,9 +20,16 @@ jobs: runs-on: ubuntu-latest environment: npm steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.ITERABLE_SDK_RELEASE_APP_ID }} + private-key: ${{ secrets.ITERABLE_SDK_RELEASE_APP_PRIVATE_KEY }} + - uses: actions/checkout@v4 with: ref: master + token: ${{ steps.app-token.outputs.token }} - name: Verify release is ready run: | @@ -51,7 +58,7 @@ jobs: - name: Publish draft GitHub release and tag main env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | gh release edit "$VERSION" \ --draft=false \ From 90ee4edacb307bd32b166f48186cbea198e7dc34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferr=C3=A3o?= Date: Tue, 6 Oct 2026 13:10:42 +0100 Subject: [PATCH 5/8] fix: harden prepare/publish release workflows per PR review Add docs_pr input for validate-release, verify version identity at master HEAD before publish, validate publish version format, publish GitHub release before npm with idempotent retries, and run format validation before checkout. --- .github/workflows/prepare-release.yml | 6 +++ .github/workflows/publish-release.yml | 60 ++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 2bebc7aec..a6cfef97e 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -11,6 +11,10 @@ on: description: 'Jira ticket number (e.g., 1234 for SDK-1234)' required: true type: string + docs_pr: + description: 'iterable-docs PR number (e.g., 123 for validate-release.yml)' + required: true + type: string permissions: contents: write @@ -73,6 +77,8 @@ jobs: body: | # Prepare for Release ${{ github.event.inputs.version }} + Docs PR: https://github.com/Iterable/iterable-docs/pull/${{ github.event.inputs.docs_pr }} + ## SDK Release Checklist - [ ] CHANGELOG.md updated with release notes under the new version header - [ ] Version bumped in package.json diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index f1d9fa295..55b16a9b0 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -26,15 +26,35 @@ jobs: app-id: ${{ vars.ITERABLE_SDK_RELEASE_APP_ID }} private-key: ${{ secrets.ITERABLE_SDK_RELEASE_APP_PRIVATE_KEY }} + - name: Validate version format + run: | + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then + echo "::error::Invalid version format. Use semantic versioning (e.g., 3.6.0 or 3.6.0-beta1)" + exit 1 + fi + - uses: actions/checkout@v4 with: ref: master token: ${{ steps.app-token.outputs.token }} - - name: Verify release is ready + - name: Verify release is ready on master HEAD run: | - if ! grep -qE "^## $VERSION\b" CHANGELOG.md; then - echo "::error::CHANGELOG.md has no entry for $VERSION. Merge the prepare-release PR to master before running this workflow." + changelog_top=$(grep -m1 -E '^## [0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$' CHANGELOG.md | sed -E 's/^## //') + if [ "$changelog_top" != "$VERSION" ]; then + echo "::error::CHANGELOG.md top version is '$changelog_top', expected '$VERSION' at master HEAD. Merge the prepare-release PR before running this workflow." + exit 1 + fi + + pkg_ver=$(node -p "require('./package.json').version") + if [ "$pkg_ver" != "$VERSION" ]; then + echo "::error::package.json version is '$pkg_ver', expected '$VERSION'." + exit 1 + fi + + build_info_ver=$(grep -oP "version:\s*'\K[^']+" src/itblBuildInfo.ts) + if [ "$build_info_ver" != "$VERSION" ]; then + echo "::error::src/itblBuildInfo.ts version is '$build_info_ver', expected '$VERSION'." exit 1 fi @@ -53,17 +73,35 @@ jobs: - run: yarn prepare - - name: Publish to npm - run: npm publish --provenance - - - name: Publish draft GitHub release and tag main + - name: Publish draft GitHub release and tag master env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | - gh release edit "$VERSION" \ - --draft=false \ - --target "$(git rev-parse HEAD)" \ - --latest + target="$(git rev-parse HEAD)" + + if ! gh release view "$VERSION" &>/dev/null; then + echo "::error::No GitHub release named $VERSION. Run Prepare Release first." + exit 1 + fi + + is_draft=$(gh release view "$VERSION" --json isDraft -q .isDraft) + if [ "$is_draft" = "true" ]; then + gh release edit "$VERSION" \ + --draft=false \ + --target "$target" \ + --latest + echo "Published GitHub release $VERSION at $target." + else + echo "GitHub release $VERSION is already published, skipping." + fi + + - name: Publish to npm + run: | + if npm view "@iterable/react-native-sdk@${VERSION}" version 2>/dev/null | grep -qxF "$VERSION"; then + echo "Version $VERSION is already on npm, skipping publish." + else + npm publish --provenance + fi - name: Slack notification run: | From 9268cc20b998c8cb4940cb8fa229c4ab6de4386f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferr=C3=A3o?= Date: Tue, 6 Oct 2026 14:24:57 +0100 Subject: [PATCH 6/8] fix: address release workflow review (stable-only, notes, pins) Restrict Prepare/Publish to X.Y.Z, validate docs_pr digits, refresh GitHub release notes from CHANGELOG at publish, verify tag matches master HEAD on retry, and align Node/Yarn/npm with repo CI pins. --- .github/workflows/prepare-release.yml | 14 ++++++--- .github/workflows/publish-release.yml | 43 +++++++++++++++++++++------ 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index a6cfef97e..0705782a5 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -35,16 +35,22 @@ jobs: ref: master token: ${{ steps.app-token.outputs.token }} - - name: Validate version format + - name: Validate inputs run: | - if ! [[ "${{ github.event.inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then - echo "::error::Invalid version format. Use semantic versioning (e.g., 3.6.0 or 3.6.0-beta1)" + version="${{ github.event.inputs.version }}" + docs_pr="${{ github.event.inputs.docs_pr }}" + if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Invalid version format. Use a stable semver release (e.g., 3.6.0). Prereleases are not supported by this workflow." + exit 1 + fi + if ! [[ "$docs_pr" =~ ^[0-9]+$ ]]; then + echo "::error::docs_pr must be the iterable-docs pull request number (digits only)." exit 1 fi - uses: actions/setup-node@v4 with: - node-version: '22' + node-version-file: .nvmrc - name: Update Changelog id: update_changelog diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 55b16a9b0..66db3376f 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -28,8 +28,8 @@ jobs: - name: Validate version format run: | - if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then - echo "::error::Invalid version format. Use semantic versioning (e.g., 3.6.0 or 3.6.0-beta1)" + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Invalid version format. Use a stable semver release (e.g., 3.6.0). Prereleases are not supported by this workflow." exit 1 fi @@ -40,7 +40,7 @@ jobs: - name: Verify release is ready on master HEAD run: | - changelog_top=$(grep -m1 -E '^## [0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$' CHANGELOG.md | sed -E 's/^## //') + changelog_top=$(grep -m1 -E '^## [0-9]+\.[0-9]+\.[0-9]+$' CHANGELOG.md | sed -E 's/^## //') if [ "$changelog_top" != "$VERSION" ]; then echo "::error::CHANGELOG.md top version is '$changelog_top', expected '$VERSION' at master HEAD. Merge the prepare-release PR before running this workflow." exit 1 @@ -58,16 +58,31 @@ jobs: exit 1 fi + - name: Extract changelog release notes + run: | + section=$(awk -v ver="$VERSION" ' + $0 == "## " ver { capturing=1; next } + capturing && /^## [0-9]+\.[0-9]+\.[0-9]+$/ { exit } + capturing { print } + ' CHANGELOG.md) + + if [ -z "$(printf '%s' "$section" | tr -d '[:space:]')" ]; then + echo "::error::No CHANGELOG.md content under ## $VERSION. Add release notes on the prepare-release PR before publishing." + exit 1 + fi + + printf '%s\n' "$section" > "$RUNNER_TEMP/release-notes.md" + - uses: actions/setup-node@v4 with: - node-version: '22' + node-version-file: .nvmrc cache: 'yarn' registry-url: 'https://registry.npmjs.org' - - name: Update npm for OIDC support - run: npm install -g npm@latest + - name: Install npm for OIDC trusted publishing + run: npm install -g npm@11.17.0 - - run: yarn install --frozen-lockfile + - run: yarn install --immutable - run: yarn test --maxWorkers=2 @@ -89,10 +104,20 @@ jobs: gh release edit "$VERSION" \ --draft=false \ --target "$target" \ - --latest + --latest \ + --notes-file "$RUNNER_TEMP/release-notes.md" echo "Published GitHub release $VERSION at $target." else - echo "GitHub release $VERSION is already published, skipping." + if ! git rev-parse "refs/tags/$VERSION" >/dev/null 2>&1; then + echo "::error::GitHub release $VERSION is published but tag $VERSION is missing." + exit 1 + fi + tag_sha=$(git rev-parse "refs/tags/$VERSION^{commit}") + if [ "$tag_sha" != "$target" ]; then + echo "::error::Release $VERSION tag points to $tag_sha but master HEAD is $target. Fix the release tag or run Publish from the commit the tag targets." + exit 1 + fi + echo "GitHub release $VERSION is already published at $target, skipping release edit." fi - name: Publish to npm From 8565a71f62a2b66ddaedf36d1c1b05401095cf51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferr=C3=A3o?= Date: Tue, 6 Oct 2026 14:31:56 +0100 Subject: [PATCH 7/8] fix: resolve release tag checks on retry and deprecate laptop publish Use git ls-remote for origin tag SHA so Publish retries work without fetch-tags, validate Prepare inputs via job env, and point yarn release and release_next_version.sh at the GitHub Actions workflows. --- .github/workflows/prepare-release.yml | 24 +++++++++--------- .github/workflows/publish-release.yml | 35 ++++++++++++++++++++------- package.json | 3 ++- scripts/deprecated-npm-release.js | 6 +++++ scripts/release_next_version.sh | 3 +++ 5 files changed, 48 insertions(+), 23 deletions(-) create mode 100644 scripts/deprecated-npm-release.js diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 0705782a5..784439d43 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -23,6 +23,9 @@ permissions: jobs: prepare-release: runs-on: ubuntu-latest + env: + VERSION: ${{ github.event.inputs.version }} + DOCS_PR: ${{ github.event.inputs.docs_pr }} steps: - uses: actions/create-github-app-token@v1 id: app-token @@ -37,13 +40,11 @@ jobs: - name: Validate inputs run: | - version="${{ github.event.inputs.version }}" - docs_pr="${{ github.event.inputs.docs_pr }}" - if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::Invalid version format. Use a stable semver release (e.g., 3.6.0). Prereleases are not supported by this workflow." exit 1 fi - if ! [[ "$docs_pr" =~ ^[0-9]+$ ]]; then + if ! [[ "$DOCS_PR" =~ ^[0-9]+$ ]]; then echo "::error::docs_pr must be the iterable-docs pull request number (digits only)." exit 1 fi @@ -55,13 +56,12 @@ jobs: - name: Update Changelog id: update_changelog run: | - version="${{ github.event.inputs.version }}" changelog_file="CHANGELOG.md" # RN has no [Unreleased] section -- insert a new ## version header at the top temp_file=$(mktemp) { - echo "## $version" + echo "## $VERSION" echo "" cat "$changelog_file" } > "$temp_file" @@ -70,7 +70,7 @@ jobs: echo "See CHANGELOG.md for release notes." > "$RUNNER_TEMP/release-notes.md" - name: Bump package.json version - run: npm --no-git-tag-version --allow-same-version version "${{ github.event.inputs.version }}" + run: npm --no-git-tag-version --allow-same-version version "$VERSION" - name: Regenerate build info run: node scripts/autoCreatePackageInfo.js @@ -101,13 +101,11 @@ jobs: env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | - version="${{ github.event.inputs.version }}" - - if gh release view "$version" &>/dev/null; then - echo "Draft release $version already exists, skipping." + if gh release view "$VERSION" &>/dev/null; then + echo "Draft release $VERSION already exists, skipping." else - gh release create "$version" \ + gh release create "$VERSION" \ --draft \ - --title "$version" \ + --title "$VERSION" \ --notes-file "$RUNNER_TEMP/release-notes.md" fi diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 66db3376f..8f1399b2b 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -92,6 +92,7 @@ jobs: env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | + set -euo pipefail target="$(git rev-parse HEAD)" if ! gh release view "$VERSION" &>/dev/null; then @@ -99,6 +100,29 @@ jobs: exit 1 fi + release_tag_commit() { + git ls-remote origin "refs/tags/${1}^{}" 2>/dev/null | awk '{print $1}' + } + + assert_release_tag_at_head() { + local tag_sha attempt + for attempt in 1 2 3 4 5; do + tag_sha=$(release_tag_commit "$VERSION") + if [ -n "$tag_sha" ]; then + break + fi + sleep 2 + done + if [ -z "$tag_sha" ]; then + echo "::error::Release tag $VERSION not found on origin." + exit 1 + fi + if [ "$tag_sha" != "$target" ]; then + echo "::error::Release $VERSION tag points to $tag_sha but master HEAD is $target. Fix the release tag or run Publish from the commit the tag targets." + exit 1 + fi + } + is_draft=$(gh release view "$VERSION" --json isDraft -q .isDraft) if [ "$is_draft" = "true" ]; then gh release edit "$VERSION" \ @@ -107,16 +131,9 @@ jobs: --latest \ --notes-file "$RUNNER_TEMP/release-notes.md" echo "Published GitHub release $VERSION at $target." + assert_release_tag_at_head else - if ! git rev-parse "refs/tags/$VERSION" >/dev/null 2>&1; then - echo "::error::GitHub release $VERSION is published but tag $VERSION is missing." - exit 1 - fi - tag_sha=$(git rev-parse "refs/tags/$VERSION^{commit}") - if [ "$tag_sha" != "$target" ]; then - echo "::error::Release $VERSION tag points to $tag_sha but master HEAD is $target. Fix the release tag or run Publish from the commit the tag targets." - exit 1 - fi + assert_release_tag_at_head echo "GitHub release $VERSION is already published at $target, skipping release edit." fi diff --git a/package.json b/package.json index 686e9cc09..d408fa695 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "prebuild": "yarn add_build_info", "build": "yarn add_build_info && yarn bob build", "prepare": "yarn build", - "release": "release-it" + "release": "node ./scripts/deprecated-npm-release.js", + "release:legacy": "release-it" }, "keywords": [ "react-native", diff --git a/scripts/deprecated-npm-release.js b/scripts/deprecated-npm-release.js new file mode 100644 index 000000000..cb5850cae --- /dev/null +++ b/scripts/deprecated-npm-release.js @@ -0,0 +1,6 @@ +#!/usr/bin/env node + +console.error( + 'The yarn release script is deprecated. Publish via GitHub Actions: Prepare Release and Publish Release (.github/workflows/prepare-release.yml, publish-release.yml).' +); +process.exit(1); diff --git a/scripts/release_next_version.sh b/scripts/release_next_version.sh index 6c8f63700..46b2eea90 100755 --- a/scripts/release_next_version.sh +++ b/scripts/release_next_version.sh @@ -1,6 +1,9 @@ #!/bin/bash set -euxo pipefail + +echo "ERROR: scripts/release_next_version.sh is deprecated. Use GitHub Actions Prepare Release and Publish Release workflows." >&2 +exit 1 export SDK_DIR=$(cd `dirname "$0"`/.. && pwd) cd $SDK_DIR export VERSION=`sed -nE 's/"version": "([[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+)",/\1/p' package.json | xargs` From e535ea93e3b046e315d6df520246c1e441377069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferr=C3=A3o?= Date: Tue, 6 Oct 2026 14:41:32 +0100 Subject: [PATCH 8/8] fix: resolve lightweight release tags in publish workflow Read both peeled and direct refs/tags lines from origin so GitHub release tags match master HEAD before npm publish. --- .github/workflows/publish-release.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 8f1399b2b..67a8c0321 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -101,7 +101,17 @@ jobs: fi release_tag_commit() { - git ls-remote origin "refs/tags/${1}^{}" 2>/dev/null | awk '{print $1}' + local tag="$1" + local peeled direct + peeled=$(git ls-remote origin "refs/tags/${tag}^{}" 2>/dev/null | awk '{print $1}') + if [ -n "$peeled" ]; then + echo "$peeled" + return + fi + direct=$(git ls-remote origin "refs/tags/${tag}" 2>/dev/null | awk '{print $1}') + if [ -n "$direct" ]; then + echo "$direct" + fi } assert_release_tag_at_head() {