From bc2742a6bb028e1ac5956a9147256def037b8db1 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 1 Apr 2026 16:17:30 +0200 Subject: [PATCH 1/5] Add pre-release prerequisite checks to perform-release.sh Co-Authored-By: Claude Sonnet 4.6 --- tooling/perform-release.sh | 132 ++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 2 deletions(-) diff --git a/tooling/perform-release.sh b/tooling/perform-release.sh index 7e5d2345d46..c6f80284610 100755 --- a/tooling/perform-release.sh +++ b/tooling/perform-release.sh @@ -3,9 +3,11 @@ set -euo pipefail # Ask for confirmation before continuing the release function confirmOrAbort() { - read -p "Do you want to continue? (y/N): " -n 1 -r + local prompt="${1:-Do you want to continue? (y/N): }" + local reply + read -p "$prompt" -n 1 -r reply echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then + if [[ ! $reply =~ ^[Yy]$ ]]; then echo "Aborting." exit 1 fi @@ -58,6 +60,30 @@ if ! git diff-index --quiet "$REMOTE/$CURRENT_BRANCH"; then fi echo "✅ Working copy is clean and up-to-date." +# Check if gh CLI is available +if ! command -v gh &>/dev/null; then + echo "❌ GitHub CLI (gh) is not installed or not in PATH. Please install it to proceed." + exit 1 +fi + +# Check gh authentication +if ! gh auth status &>/dev/null; then + echo "❌ GitHub CLI is not authenticated. Please run 'gh auth login' first." + exit 1 +fi + +# Check push permission on this repository +if ! HAS_PUSH=$(gh api "repos/{owner}/{repo}" --jq ".permissions.push"); then + echo "❌ Failed to query repository permissions. Check your network connection and token scopes." + exit 1 +fi +if [ "$HAS_PUSH" != "true" ]; then + echo "❌ Your GitHub account does not have push (write) permission on this repository." + echo " Only release owners are allowed to push release tags." + exit 1 +fi +echo "✅ GitHub CLI authenticated with push permission." + # Check the git log history LAST_RELEASE_TAG=$(git describe --tags --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*') echo "ℹ️ Last release version: $LAST_RELEASE_TAG" @@ -84,6 +110,108 @@ else fi echo "ℹ️ Next release version: $NEXT_RELEASE_VERSION" +# Check the release tag does not already exist locally or remotely +if git rev-parse "refs/tags/$NEXT_RELEASE_VERSION" &>/dev/null; then + echo "❌ Tag '$NEXT_RELEASE_VERSION' already exists locally. Has this release already been tagged?" + exit 1 +fi +if git ls-remote --tags "$REMOTE" "refs/tags/$NEXT_RELEASE_VERSION" | grep -q .; then + echo "❌ Tag '$NEXT_RELEASE_VERSION' already exists on remote '$REMOTE'. Has this release already been tagged?" + exit 1 +fi +echo "✅ Release tag '$NEXT_RELEASE_VERSION' does not yet exist." + +# Check that an open GitHub milestone exists for the next release version +MILESTONE_TITLE="${NEXT_RELEASE_VERSION#v}" +if ! MILESTONE_NUMBERS=$(gh api --paginate "repos/{owner}/{repo}/milestones?state=open&per_page=100" \ + --jq ".[] | select(.title == \"$MILESTONE_TITLE\") | .number"); then + echo "❌ Failed to query GitHub milestones. Check your network connection." + exit 1 +fi +if [ -z "$MILESTONE_NUMBERS" ]; then + echo "❌ No open GitHub milestone found for version '$MILESTONE_TITLE'." + echo " Please create the milestone and assign PRs to it before performing a release." + exit 1 +fi +MILESTONE_COUNT=$(printf '%s\n' "$MILESTONE_NUMBERS" | wc -l | tr -d ' ') +if [ "$MILESTONE_COUNT" -gt 1 ]; then + echo "❌ Multiple open milestones found for version '$MILESTONE_TITLE' (numbers: $(printf '%s ' $MILESTONE_NUMBERS))." + echo " Please resolve the duplicate milestones before performing a release." + exit 1 +fi +MILESTONE_NUMBER="$MILESTONE_NUMBERS" +echo "✅ GitHub milestone '$MILESTONE_TITLE' found (milestone #$MILESTONE_NUMBER)." + +# Check that the milestone has no open issues or PRs +if ! OPEN_ISSUES=$(gh api "repos/{owner}/{repo}/milestones/$MILESTONE_NUMBER" \ + --jq ".open_issues"); then + echo "❌ Failed to query milestone '$MILESTONE_TITLE'. Check your network connection." + exit 1 +fi +if [ -z "$OPEN_ISSUES" ] || ! [[ "$OPEN_ISSUES" =~ ^[0-9]+$ ]]; then + echo "❌ Unexpected response when querying open issue count for milestone '$MILESTONE_TITLE': '$OPEN_ISSUES'." + exit 1 +fi +if [ "$OPEN_ISSUES" -gt 0 ]; then + echo "❌ Milestone '$MILESTONE_TITLE' still has $OPEN_ISSUES open issue(s) or PR(s):" + gh api "repos/{owner}/{repo}/issues?milestone=$MILESTONE_NUMBER&state=open&per_page=100" \ + --jq '.[] | " #\(.number) \(.title)"' || true + echo " All PRs must be merged before tagging a release." + exit 1 +fi +echo "✅ All issues and PRs in milestone '$MILESTONE_TITLE' are closed." + +# Check that all closed PRs in the milestone carry the required labels. +# Required: (comp:* or inst:*) AND (type:*), OR 'tag: no release note'. +if ! NONCOMPLIANT=$(gh api --paginate \ + "repos/{owner}/{repo}/issues?milestone=$MILESTONE_NUMBER&state=closed&per_page=100" \ + --jq '[.[] | select(.pull_request != null) | + select( + ((.labels | map(.name) | map(. == "tag: no release note") | any) | not) and + ( + ((.labels | map(.name) | map(startswith("comp:") or startswith("inst:")) | any) | not) or + ((.labels | map(.name) | map(startswith("type:")) | any) | not) + ) + ) | " #\(.number) \(.title)"] | .[]'); then + echo "❌ Failed to query milestone PRs. Check your network connection." + exit 1 +fi +if [ -n "$NONCOMPLIANT" ]; then + echo "⚠️ The following PRs in milestone '$MILESTONE_TITLE' are missing required labels:" + echo "$NONCOMPLIANT" + echo " Each PR needs (a 'comp:' or 'inst:' label) AND (a 'type:' label), or 'tag: no release note'." + confirmOrAbort "Fix labels and continue anyway? (y/N): " +else + echo "✅ All PRs in milestone '$MILESTONE_TITLE' have required labels." +fi + +# Check GPG signing key is configured and available (release tags are signed with -s) +SIGNING_KEY=$(git config --get user.signingkey 2>/dev/null || true) +if [ -n "$SIGNING_KEY" ]; then + if ! gpg --list-secret-keys "$SIGNING_KEY" &>/dev/null; then + echo "❌ GPG signing key '$SIGNING_KEY' is not available in the keyring (expired, revoked, or deleted)." + echo " Please reconfigure your signing key with: git config user.signingkey " + exit 1 + fi +else + GIT_EMAIL=$(git config --get user.email 2>/dev/null || true) + if [ -z "$GIT_EMAIL" ] || ! gpg --list-secret-keys "$GIT_EMAIL" &>/dev/null; then + echo "❌ No GPG signing key configured. Release tags must be signed." + echo " Configure one with: git config user.signingkey " + exit 1 + fi +fi +echo "✅ GPG signing key is configured." + +# For minor releases: require explicit acknowledgment of manual pre-cut verification steps +if [ "$MINOR_RELEASE" = true ]; then + echo "" + echo "ℹ️ Minor release — manual pre-cut verification required (Steps 1–2 of the release process)." + confirmOrAbort "Step 1: Have you reviewed the APM Performance SDK SLO dashboard and found no regressions? (y/N): " + confirmOrAbort "Step 2: Have you reviewed the Test Optimization Performance Dashboard and found no increased overhead? (y/N): " + echo "" +fi + # Create and push the release tag echo "ℹ️ The release tag will be created and pushed. No abort is possible after this point." confirmOrAbort From ab3fc648920ec86c524301771a6b35edd9681378 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 1 Apr 2026 16:48:45 +0200 Subject: [PATCH 2/5] Fix quoting and misleading prompt in release script Co-Authored-By: Claude Opus 4.6 (1M context) --- tooling/perform-release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tooling/perform-release.sh b/tooling/perform-release.sh index c6f80284610..0e51d70ac1b 100755 --- a/tooling/perform-release.sh +++ b/tooling/perform-release.sh @@ -135,7 +135,7 @@ if [ -z "$MILESTONE_NUMBERS" ]; then fi MILESTONE_COUNT=$(printf '%s\n' "$MILESTONE_NUMBERS" | wc -l | tr -d ' ') if [ "$MILESTONE_COUNT" -gt 1 ]; then - echo "❌ Multiple open milestones found for version '$MILESTONE_TITLE' (numbers: $(printf '%s ' $MILESTONE_NUMBERS))." + echo "❌ Multiple open milestones found for version '$MILESTONE_TITLE' (numbers: $(printf '%s ' "$MILESTONE_NUMBERS"))." echo " Please resolve the duplicate milestones before performing a release." exit 1 fi @@ -180,7 +180,7 @@ if [ -n "$NONCOMPLIANT" ]; then echo "⚠️ The following PRs in milestone '$MILESTONE_TITLE' are missing required labels:" echo "$NONCOMPLIANT" echo " Each PR needs (a 'comp:' or 'inst:' label) AND (a 'type:' label), or 'tag: no release note'." - confirmOrAbort "Fix labels and continue anyway? (y/N): " + confirmOrAbort "Continue despite missing labels? (y/N): " else echo "✅ All PRs in milestone '$MILESTONE_TITLE' have required labels." fi From 81a273341760a3618cd5d76728820b4cb51e8890 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 1 Apr 2026 17:08:12 +0200 Subject: [PATCH 3/5] Fix label name to match actual GitHub label Co-Authored-By: Claude Opus 4.6 (1M context) --- tooling/perform-release.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tooling/perform-release.sh b/tooling/perform-release.sh index 0e51d70ac1b..4f053e501ae 100755 --- a/tooling/perform-release.sh +++ b/tooling/perform-release.sh @@ -162,12 +162,12 @@ fi echo "✅ All issues and PRs in milestone '$MILESTONE_TITLE' are closed." # Check that all closed PRs in the milestone carry the required labels. -# Required: (comp:* or inst:*) AND (type:*), OR 'tag: no release note'. +# Required: (comp:* or inst:*) AND (type:*), OR 'tag: no release notes'. if ! NONCOMPLIANT=$(gh api --paginate \ "repos/{owner}/{repo}/issues?milestone=$MILESTONE_NUMBER&state=closed&per_page=100" \ --jq '[.[] | select(.pull_request != null) | select( - ((.labels | map(.name) | map(. == "tag: no release note") | any) | not) and + ((.labels | map(.name) | map(. == "tag: no release notes") | any) | not) and ( ((.labels | map(.name) | map(startswith("comp:") or startswith("inst:")) | any) | not) or ((.labels | map(.name) | map(startswith("type:")) | any) | not) @@ -179,7 +179,7 @@ fi if [ -n "$NONCOMPLIANT" ]; then echo "⚠️ The following PRs in milestone '$MILESTONE_TITLE' are missing required labels:" echo "$NONCOMPLIANT" - echo " Each PR needs (a 'comp:' or 'inst:' label) AND (a 'type:' label), or 'tag: no release note'." + echo " Each PR needs (a 'comp:' or 'inst:' label) AND (a 'type:' label), or 'tag: no release notes'." confirmOrAbort "Continue despite missing labels? (y/N): " else echo "✅ All PRs in milestone '$MILESTONE_TITLE' have required labels." From 8f51223b46c5a15e5b5a83c930176d2266885e51 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 1 Apr 2026 18:57:31 +0200 Subject: [PATCH 4/5] Address PR review comments in perform-release.sh Co-Authored-By: Claude Sonnet 4.6 --- tooling/perform-release.sh | 107 ++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 43 deletions(-) diff --git a/tooling/perform-release.sh b/tooling/perform-release.sh index 4f053e501ae..7efd398589b 100755 --- a/tooling/perform-release.sh +++ b/tooling/perform-release.sh @@ -50,7 +50,7 @@ if ! git diff-index --quiet HEAD; then fi # Check if clone is up to date -if ! git fetch "$REMOTE" --quiet ; then +if ! git fetch "$REMOTE" --tags --quiet ; then echo "❌ Unable to fetch the latest changes from $REMOTE. Please check your network connection." exit 1 fi @@ -72,17 +72,31 @@ if ! gh auth status &>/dev/null; then exit 1 fi -# Check push permission on this repository -if ! HAS_PUSH=$(gh api "repos/{owner}/{repo}" --jq ".permissions.push"); then - echo "❌ Failed to query repository permissions. Check your network connection and token scopes." +# Check that the current user is a member of the release owner team +CURRENT_USER=$(gh api user --jq '.login' 2>/dev/null || true) +if [ -z "$CURRENT_USER" ]; then + echo "❌ Failed to determine GitHub username. Check your network connection and token scopes." exit 1 fi -if [ "$HAS_PUSH" != "true" ]; then - echo "❌ Your GitHub account does not have push (write) permission on this repository." - echo " Only release owners are allowed to push release tags." +MEMBERSHIP_ERR=$(mktemp) +trap 'rm -f "$MEMBERSHIP_ERR"' EXIT +if ! MEMBERSHIP_STATE=$(gh api "orgs/DataDog/teams/dd-trace-java-releasers/memberships/$CURRENT_USER" \ + --jq '.state' 2>"$MEMBERSHIP_ERR"); then + if grep -qi "404\|not found" "$MEMBERSHIP_ERR" 2>/dev/null; then + echo "❌ You ($CURRENT_USER) are not a member of the dd-trace-java-releasers release owner team." + echo " Only release owners are allowed to perform a release." + else + echo "❌ Failed to verify team membership for $CURRENT_USER: $(cat "$MEMBERSHIP_ERR")" + echo " Check your network connection and token scopes (requires 'read:org')." + fi + exit 1 +fi +if [ "$MEMBERSHIP_STATE" != "active" ]; then + echo "❌ Your membership in the dd-trace-java-releasers team is not active (state: $MEMBERSHIP_STATE)." + echo " Only active release owners are allowed to perform a release." exit 1 fi -echo "✅ GitHub CLI authenticated with push permission." +echo "✅ GitHub CLI authenticated as release owner $CURRENT_USER." # Check the git log history LAST_RELEASE_TAG=$(git describe --tags --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*') @@ -110,13 +124,12 @@ else fi echo "ℹ️ Next release version: $NEXT_RELEASE_VERSION" -# Check the release tag does not already exist locally or remotely +# Check the release tag does not already exist locally or remotely. +# The tag can exist if it was created on a branch not reachable from HEAD (e.g. a failed release on a different branch) +# or if a prior release attempt was interrupted before pushing. After `git fetch --tags` above, remote tags are +# included in this check. if git rev-parse "refs/tags/$NEXT_RELEASE_VERSION" &>/dev/null; then - echo "❌ Tag '$NEXT_RELEASE_VERSION' already exists locally. Has this release already been tagged?" - exit 1 -fi -if git ls-remote --tags "$REMOTE" "refs/tags/$NEXT_RELEASE_VERSION" | grep -q .; then - echo "❌ Tag '$NEXT_RELEASE_VERSION' already exists on remote '$REMOTE'. Has this release already been tagged?" + echo "❌ Tag '$NEXT_RELEASE_VERSION' already exists. Has this release already been tagged?" exit 1 fi echo "✅ Release tag '$NEXT_RELEASE_VERSION' does not yet exist." @@ -142,35 +155,39 @@ fi MILESTONE_NUMBER="$MILESTONE_NUMBERS" echo "✅ GitHub milestone '$MILESTONE_TITLE' found (milestone #$MILESTONE_NUMBER)." -# Check that the milestone has no open issues or PRs -if ! OPEN_ISSUES=$(gh api "repos/{owner}/{repo}/milestones/$MILESTONE_NUMBER" \ - --jq ".open_issues"); then +# Check that the milestone has no open PRs (open issues are expected and allowed). +# Use `jq -s 'add | length'` to sum counts across all pages returned by --paginate. +if ! OPEN_PRS=$(gh api --paginate \ + "repos/{owner}/{repo}/issues?milestone=$MILESTONE_NUMBER&state=open&per_page=100" \ + --jq '[.[] | select(.pull_request != null)]' | jq -s 'add | length'); then echo "❌ Failed to query milestone '$MILESTONE_TITLE'. Check your network connection." exit 1 fi -if [ -z "$OPEN_ISSUES" ] || ! [[ "$OPEN_ISSUES" =~ ^[0-9]+$ ]]; then - echo "❌ Unexpected response when querying open issue count for milestone '$MILESTONE_TITLE': '$OPEN_ISSUES'." +if [ -z "$OPEN_PRS" ] || ! [[ "$OPEN_PRS" =~ ^[0-9]+$ ]]; then + echo "❌ Unexpected response when querying open PR count for milestone '$MILESTONE_TITLE': '$OPEN_PRS'." exit 1 fi -if [ "$OPEN_ISSUES" -gt 0 ]; then - echo "❌ Milestone '$MILESTONE_TITLE' still has $OPEN_ISSUES open issue(s) or PR(s):" - gh api "repos/{owner}/{repo}/issues?milestone=$MILESTONE_NUMBER&state=open&per_page=100" \ - --jq '.[] | " #\(.number) \(.title)"' || true +if [ "$OPEN_PRS" -gt 0 ]; then + echo "❌ Milestone '$MILESTONE_TITLE' still has $OPEN_PRS open PR(s):" + gh api --paginate "repos/{owner}/{repo}/issues?milestone=$MILESTONE_NUMBER&state=open&per_page=100" \ + --jq '.[] | select(.pull_request != null) | " #\(.number) \(.title)"' || true echo " All PRs must be merged before tagging a release." exit 1 fi -echo "✅ All issues and PRs in milestone '$MILESTONE_TITLE' are closed." +echo "✅ All PRs in milestone '$MILESTONE_TITLE' are merged." # Check that all closed PRs in the milestone carry the required labels. +# This is a release-time defense-in-depth check: even if CI enforces labels at PR submission, +# PRs merged before that enforcement (or via merge queues with bypassed checks) may be missing them. # Required: (comp:* or inst:*) AND (type:*), OR 'tag: no release notes'. if ! NONCOMPLIANT=$(gh api --paginate \ "repos/{owner}/{repo}/issues?milestone=$MILESTONE_NUMBER&state=closed&per_page=100" \ --jq '[.[] | select(.pull_request != null) | select( - ((.labels | map(.name) | map(. == "tag: no release notes") | any) | not) and + (((.labels // []) | map(.name) | map(. == "tag: no release notes") | any) | not) and ( - ((.labels | map(.name) | map(startswith("comp:") or startswith("inst:")) | any) | not) or - ((.labels | map(.name) | map(startswith("type:")) | any) | not) + (((.labels // []) | map(.name) | map(startswith("comp:") or startswith("inst:")) | any) | not) or + (((.labels // []) | map(.name) | map(startswith("type:")) | any) | not) ) ) | " #\(.number) \(.title)"] | .[]'); then echo "❌ Failed to query milestone PRs. Check your network connection." @@ -185,23 +202,23 @@ else echo "✅ All PRs in milestone '$MILESTONE_TITLE' have required labels." fi -# Check GPG signing key is configured and available (release tags are signed with -s) +# Check GPG signing key is configured and usable for signing (release tags are signed with -s). +# Use a test-sign to catch expired or revoked keys before the point of no return. SIGNING_KEY=$(git config --get user.signingkey 2>/dev/null || true) -if [ -n "$SIGNING_KEY" ]; then - if ! gpg --list-secret-keys "$SIGNING_KEY" &>/dev/null; then - echo "❌ GPG signing key '$SIGNING_KEY' is not available in the keyring (expired, revoked, or deleted)." - echo " Please reconfigure your signing key with: git config user.signingkey " - exit 1 - fi -else - GIT_EMAIL=$(git config --get user.email 2>/dev/null || true) - if [ -z "$GIT_EMAIL" ] || ! gpg --list-secret-keys "$GIT_EMAIL" &>/dev/null; then - echo "❌ No GPG signing key configured. Release tags must be signed." - echo " Configure one with: git config user.signingkey " - exit 1 - fi +if [ -z "$SIGNING_KEY" ]; then + SIGNING_KEY=$(git config --get user.email 2>/dev/null || true) +fi +if [ -z "$SIGNING_KEY" ]; then + echo "❌ No GPG signing key configured. Release tags must be signed." + echo " Configure one with: git config user.signingkey " + exit 1 fi -echo "✅ GPG signing key is configured." +if ! echo "test" | gpg --no-tty --sign --local-user "$SIGNING_KEY" --output /dev/null 2>/dev/null; then + echo "❌ GPG signing key '$SIGNING_KEY' cannot be used for signing (missing, expired, revoked, or passphrase required non-interactively)." + echo " Please verify the key is available and valid: gpg --list-secret-keys '$SIGNING_KEY'" + exit 1 +fi +echo "✅ GPG signing key is configured and usable." # For minor releases: require explicit acknowledgment of manual pre-cut verification steps if [ "$MINOR_RELEASE" = true ]; then @@ -216,5 +233,9 @@ fi echo "ℹ️ The release tag will be created and pushed. No abort is possible after this point." confirmOrAbort git tag -a -s -m "Release $NEXT_RELEASE_VERSION" "$NEXT_RELEASE_VERSION" -git push "$REMOTE" "$NEXT_RELEASE_VERSION" --no-verify +if ! git push "$REMOTE" "$NEXT_RELEASE_VERSION" --no-verify; then + echo "❌ Push failed. Deleting the local tag to allow a clean retry." + git tag -d "$NEXT_RELEASE_VERSION" + exit 1 +fi echo "✅ Release tag $NEXT_RELEASE_VERSION created and pushed to $REMOTE." From 12b7e0e3af6dd135bba7e847aa1bc548234e2f8e Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Thu, 2 Apr 2026 09:59:54 +0200 Subject: [PATCH 5/5] Simplify perform-release.sh Co-Authored-By: Claude Sonnet 4.6 --- tooling/perform-release.sh | 46 ++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/tooling/perform-release.sh b/tooling/perform-release.sh index 7efd398589b..0c9728b8337 100755 --- a/tooling/perform-release.sh +++ b/tooling/perform-release.sh @@ -60,11 +60,15 @@ if ! git diff-index --quiet "$REMOTE/$CURRENT_BRANCH"; then fi echo "✅ Working copy is clean and up-to-date." -# Check if gh CLI is available +# Check if gh CLI and jq are available if ! command -v gh &>/dev/null; then echo "❌ GitHub CLI (gh) is not installed or not in PATH. Please install it to proceed." exit 1 fi +if ! command -v jq &>/dev/null; then + echo "❌ jq is not installed or not in PATH. Please install it to proceed." + exit 1 +fi # Check gh authentication if ! gh auth status &>/dev/null; then @@ -78,17 +82,10 @@ if [ -z "$CURRENT_USER" ]; then echo "❌ Failed to determine GitHub username. Check your network connection and token scopes." exit 1 fi -MEMBERSHIP_ERR=$(mktemp) -trap 'rm -f "$MEMBERSHIP_ERR"' EXIT if ! MEMBERSHIP_STATE=$(gh api "orgs/DataDog/teams/dd-trace-java-releasers/memberships/$CURRENT_USER" \ - --jq '.state' 2>"$MEMBERSHIP_ERR"); then - if grep -qi "404\|not found" "$MEMBERSHIP_ERR" 2>/dev/null; then - echo "❌ You ($CURRENT_USER) are not a member of the dd-trace-java-releasers release owner team." - echo " Only release owners are allowed to perform a release." - else - echo "❌ Failed to verify team membership for $CURRENT_USER: $(cat "$MEMBERSHIP_ERR")" - echo " Check your network connection and token scopes (requires 'read:org')." - fi + --jq '.state'); then + echo "❌ You ($CURRENT_USER) are not an active member of the dd-trace-java-releasers release owner team." + echo " Only release owners are allowed to perform a release." exit 1 fi if [ "$MEMBERSHIP_STATE" != "active" ]; then @@ -112,15 +109,16 @@ else fi # Get the next release version -VERSION=$(echo "$LAST_RELEASE_TAG" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sed 's/^v//' || true) -if [ -z "$VERSION" ]; then +VERSION="${LAST_RELEASE_TAG#v}" +if [ -z "$VERSION" ] || ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "❌ Unable to determine the next release version from the last release tag: $LAST_RELEASE_TAG" exit 1 fi +IFS=. read -r _major _minor _patch <<< "$VERSION" if [ "$MINOR_RELEASE" = true ]; then - NEXT_RELEASE_VERSION=$(echo "$VERSION" | awk -F. '{printf "v%d.%d.0", $1, $2 + 1}') + NEXT_RELEASE_VERSION="v${_major}.$(( _minor + 1 )).0" else - NEXT_RELEASE_VERSION=$(echo "$VERSION" | awk -F. '{printf "v%d.%d.%d", $1, $2, $3 + 1}') + NEXT_RELEASE_VERSION="v${_major}.${_minor}.$(( _patch + 1 ))" fi echo "ℹ️ Next release version: $NEXT_RELEASE_VERSION" @@ -146,7 +144,7 @@ if [ -z "$MILESTONE_NUMBERS" ]; then echo " Please create the milestone and assign PRs to it before performing a release." exit 1 fi -MILESTONE_COUNT=$(printf '%s\n' "$MILESTONE_NUMBERS" | wc -l | tr -d ' ') +MILESTONE_COUNT=$(printf '%s\n' "$MILESTONE_NUMBERS" | grep -c .) if [ "$MILESTONE_COUNT" -gt 1 ]; then echo "❌ Multiple open milestones found for version '$MILESTONE_TITLE' (numbers: $(printf '%s ' "$MILESTONE_NUMBERS"))." echo " Please resolve the duplicate milestones before performing a release." @@ -163,10 +161,6 @@ if ! OPEN_PRS=$(gh api --paginate \ echo "❌ Failed to query milestone '$MILESTONE_TITLE'. Check your network connection." exit 1 fi -if [ -z "$OPEN_PRS" ] || ! [[ "$OPEN_PRS" =~ ^[0-9]+$ ]]; then - echo "❌ Unexpected response when querying open PR count for milestone '$MILESTONE_TITLE': '$OPEN_PRS'." - exit 1 -fi if [ "$OPEN_PRS" -gt 0 ]; then echo "❌ Milestone '$MILESTONE_TITLE' still has $OPEN_PRS open PR(s):" gh api --paginate "repos/{owner}/{repo}/issues?milestone=$MILESTONE_NUMBER&state=open&per_page=100" \ @@ -183,11 +177,12 @@ echo "✅ All PRs in milestone '$MILESTONE_TITLE' are merged." if ! NONCOMPLIANT=$(gh api --paginate \ "repos/{owner}/{repo}/issues?milestone=$MILESTONE_NUMBER&state=closed&per_page=100" \ --jq '[.[] | select(.pull_request != null) | + (.labels // [] | map(.name)) as $l | select( - (((.labels // []) | map(.name) | map(. == "tag: no release notes") | any) | not) and + ($l | any(. == "tag: no release notes") | not) and ( - (((.labels // []) | map(.name) | map(startswith("comp:") or startswith("inst:")) | any) | not) or - (((.labels // []) | map(.name) | map(startswith("type:")) | any) | not) + ($l | any(startswith("comp:") or startswith("inst:")) | not) or + ($l | any(startswith("type:")) | not) ) ) | " #\(.number) \(.title)"] | .[]'); then echo "❌ Failed to query milestone PRs. Check your network connection." @@ -204,10 +199,7 @@ fi # Check GPG signing key is configured and usable for signing (release tags are signed with -s). # Use a test-sign to catch expired or revoked keys before the point of no return. -SIGNING_KEY=$(git config --get user.signingkey 2>/dev/null || true) -if [ -z "$SIGNING_KEY" ]; then - SIGNING_KEY=$(git config --get user.email 2>/dev/null || true) -fi +SIGNING_KEY=$(git config --get user.signingkey 2>/dev/null || git config --get user.email 2>/dev/null || true) if [ -z "$SIGNING_KEY" ]; then echo "❌ No GPG signing key configured. Release tags must be signed." echo " Configure one with: git config user.signingkey "