diff --git a/scripts/tag-release.ps1 b/scripts/tag-release.ps1 index c671793b..ff84db6f 100644 --- a/scripts/tag-release.ps1 +++ b/scripts/tag-release.ps1 @@ -13,6 +13,12 @@ that sha is on main, matches the versionName being tagged, and that the AAB built from it exists locally before creating the release. + The tag is created and pushed with git, then the release is created against it. `gh release + create --target ` is NOT used: it returns HTTP 403 for Actions tokens and fine-grained + PATs even with contents: write (cli/cli#9514), while plain `git push` of the tag is unaffected. + That bug is invisible when running as a human, which is why it only surfaced once the release + moved into a workflow. + .PARAMETER Version Tag name / versionName, e.g. "1.0.49" (no "v" prefix — matches 1.0.49, the first tag cut for this repo). .PARAMETER Sha The chore/release- bump's merge commit on main. Get it right after @@ -45,9 +51,21 @@ if ($LASTEXITCODE -ne 0) { Write-Error "‘$Sha’ is not a commit in this repo. git merge-base --is-ancestor $Sha origin/main if ($LASTEXITCODE -ne 0) { Write-Error "$Sha is not an ancestor of origin/main — refusing to tag it."; exit 1 } -# 2. The tag must not already exist — no silent re-tagging. -$existing = git tag -l $Version -if ($existing) { Write-Error "Tag $Version already exists (points at $(git rev-parse $Version)). Bump the version or delete the stale tag first."; exit 1 } +# 2. An existing tag is either a re-run to finish, or a mistake. The tag is pushed before the +# release is created, so a failure between those two steps leaves the tag in place with nothing +# attached; refusing outright would make that state permanent. Same sha and no release yet means +# resume. A DIFFERENT sha is still a hard stop — that tag says something else shipped. +$tagExists = [bool](git tag -l $Version) +if ($tagExists) { + $taggedSha = (git rev-list -n 1 $Version) + if ($taggedSha -ne $Sha) { + Write-Error "Tag $Version already exists and points at $taggedSha, not $Sha. Bump the version or delete the stale tag first." + exit 1 + } + gh release view $Version *> $null + if ($LASTEXITCODE -eq 0) { Write-Error "$Version is already tagged AND released. Nothing to do."; exit 1 } + Write-Host "Tag $Version already present at $Sha with no release — creating the release for it." +} # 3. versionName at that sha must match the tag being cut — catches a wrong/stale sha. # git show returns a string[] (one element per line) in PowerShell; -match on an array filters @@ -65,9 +83,18 @@ if (-not (Test-Path $aab)) { Write-Warning "$aab not found locally — this tag should be cut AFTER building and uploading that bundle (release-signing-and-aab.md #3-5). Continuing anyway; verify you meant to." } -Write-Host "Tagging $Version at $Sha (versionCode $versionCode)..." -$ghArgs = @("release", "create", $Version, "--target", $Sha, "--title", $Title) +# 5. Push the tag ourselves, then create the release against it. See .DESCRIPTION for why this is +# not `gh release create --target`. +if (-not $tagExists) { + Write-Host "Tagging $Version at $Sha (versionCode $versionCode)..." + git tag $Version $Sha + if ($LASTEXITCODE -ne 0) { Write-Error "Could not create the tag $Version locally."; exit 1 } + git push origin "refs/tags/$Version" + if ($LASTEXITCODE -ne 0) { Write-Error "Could not push the tag $Version. The local tag remains; delete it with ``git tag -d $Version`` if you are not retrying."; exit 1 } +} + +$ghArgs = @("release", "create", $Version, "--title", $Title) if ($NotesFile) { $ghArgs += @("-F", $NotesFile) } else { $ghArgs += "--generate-notes" } gh @ghArgs -if ($LASTEXITCODE -ne 0) { Write-Error "gh release create failed."; exit 1 } +if ($LASTEXITCODE -ne 0) { Write-Error "gh release create failed. The tag $Version is pushed; re-run this script to attach the release."; exit 1 } Write-Host "Done: https://github.com/$(gh repo view --json nameWithOwner -q .nameWithOwner)/releases/tag/$Version"