diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..52c4547e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +/lib/ @braintrustdata/sdk-eng +/*.gemspec @braintrustdata/sdk-eng +/.github/ @braintrustdata/sdk-eng +/scripts/ @braintrustdata/sdk-eng diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c677cb1..374109e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,12 +21,12 @@ jobs: steps: # Security: Pin to commit SHA instead of tag to prevent tag hijacking # actions/checkout@v4.3.1 - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Ruby # Security: Pin to commit SHA instead of tag # ruby/setup-ruby@v1.295.0 - uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f + uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 with: ruby-version: ${{ matrix.ruby-version }} bundler: 'latest' @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 2 steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - run: bash scripts/ensure-pinned-actions.sh # Summary job that requires all matrix tests to pass diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml deleted file mode 100644 index 3282f6e2..00000000 --- a/.github/workflows/prerelease.yml +++ /dev/null @@ -1,131 +0,0 @@ -# -# Prerelease workflow for the Ruby SDK. -# Publishes a release candidate (rc) from any ref to RubyGems. -# Version is auto-generated as {base_version}.rc.{run_number} — no version bump required. -# Follows the same approval gate as stable releases. -# - -name: Prerelease Ruby SDK - -on: - workflow_dispatch: - inputs: - ref: - description: "Branch, tag, or commit SHA to publish as prerelease" - required: true - type: string - default: "main" - -jobs: - validate: - runs-on: ubuntu-latest - timeout-minutes: 10 - outputs: - release_tag: ${{ steps.get-tag.outputs.tag }} - sha: ${{ steps.get-tag.outputs.sha }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - ref: ${{ inputs.ref }} - fetch-depth: 0 - - - uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 - with: - ruby-version: '3.4' - bundler-cache: true - - - name: Generate rc version and validate - id: get-tag - run: | - VERSION=$(ruby -r "./lib/braintrust/version.rb" -e "puts Braintrust::VERSION") - TAG="v${VERSION}.rc.${GITHUB_RUN_NUMBER}" - SHA=$(git rev-parse HEAD) - - if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Error: Tag $TAG already exists" - exit 1 - fi - - echo "tag=$TAG" >> $GITHUB_OUTPUT - echo "sha=$SHA" >> $GITHUB_OUTPUT - echo "Ready to release $TAG @ $SHA" - - notify: - needs: validate - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: read - steps: - - name: Post release summary and notify Slack - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - SLACK_CHANNEL: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }} - TAG: ${{ needs.validate.outputs.release_tag }} - SHA: ${{ needs.validate.outputs.sha }} - run: | - APPROVE_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" - - echo "## braintrust-sdk-ruby $TAG (prerelease)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Ref:** ${{ inputs.ref }}" >> $GITHUB_STEP_SUMMARY - echo "**SHA:** $SHA" >> $GITHUB_STEP_SUMMARY - - curl -s -X POST "https://slack.com/api/chat.postMessage" \ - -H "Authorization: Bearer $SLACK_BOT_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"channel\": \"$SLACK_CHANNEL\", - \"text\": \":gem: *braintrust-sdk-ruby $TAG* (prerelease) is awaiting approval. <$APPROVE_URL|View & approve>\" - }" - - publish: - needs: [validate, notify] - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: rubygems-publish - - permissions: - contents: write - id-token: write - - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - ref: ${{ needs.validate.outputs.sha }} - fetch-depth: 0 - - - uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 - with: - ruby-version: '3.4' - bundler-cache: true - - - name: Unfreeze bundler for version modification - run: bundle config set --local frozen false - - - name: Configure RubyGems credentials - uses: rubygems/configure-rubygems-credentials@a991f145d5e4a60c4b0a3ddb204f557dc1a4f985 # main - - - name: Build and publish prerelease gem - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_RUN_NUMBER: ${{ github.run_number }} - TAG: ${{ needs.validate.outputs.release_tag }} - run: | - bundle exec rake release:prerelease - git tag "$TAG" - gh release create "$TAG" --title "$TAG" --prerelease --generate-notes - - - name: Notify Slack on release - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - SLACK_CHANNEL: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }} - TAG: ${{ needs.validate.outputs.release_tag }} - run: | - curl -s -X POST "https://slack.com/api/chat.postMessage" \ - -H "Authorization: Bearer $SLACK_BOT_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"channel\": \"$SLACK_CHANNEL\", - \"text\": \":white_check_mark: *braintrust-sdk-ruby $TAG* (prerelease) has been published to RubyGems.\" - }" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b605ec42..252128e6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,123 +1,265 @@ # -# Primary release workflow for the Ruby SDK. -# Triggered manually via GitHub Actions UI (browser session required — not via local git push). -# Reads the version from lib/braintrust/version.rb on main, posts a job summary with the -# list of changes for the reviewer, then waits for human approval before publishing. +# Release workflow for the Ruby SDK. +# Triggered manually via GitHub Actions UI — requires an explicit commit SHA. +# Version is read from version.rb at that SHA; a version bump PR is always required before releasing. +# Covers all release types: standard releases, backports, hotfixes, and release candidates. # name: Release Ruby SDK on: workflow_dispatch: + inputs: + _instructions: + description: "⚠️ Before starting: Merge a version bump PR to the target branch. The version is read from the SHA: it cannot be overridden." + type: string + default: "I have merged a version bump PR" + required: false + sha: + description: "Commit SHA (of the version bump) to release" + required: true + type: string + dry_run: + description: "Dry run: Build without tagging or publishing" + type: boolean + default: false jobs: validate: + # Generic except where marked LANGUAGE-SPECIFIC runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: read outputs: - release_tag: ${{ steps.get-tag.outputs.tag }} - sha: ${{ steps.get-tag.outputs.sha }} + release_tag: ${{ steps.validate-release.outputs.tag }} + commit_message: ${{ steps.validate-release.outputs.commit_message }} + branch: ${{ steps.validate-release.outputs.branch }} + on_main: ${{ steps.validate-release.outputs.on_main }} + prev_tag: ${{ steps.validate-release.outputs.prev_tag }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: main + ref: ${{ inputs.sha }} fetch-depth: 0 - - uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 + # LANGUAGE-SPECIFIC: replace with your language's setup action + - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 with: ruby-version: '3.4' bundler-cache: true - - name: Read version and validate - id: get-tag + # LANGUAGE-SPECIFIC: replace with your language's version read command + - name: Read version + id: read-version run: | VERSION=$(ruby -r "./lib/braintrust/version.rb" -e "puts Braintrust::VERSION") + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Validate release + id: validate-release + run: | + VERSION="${{ steps.read-version.outputs.version }}" TAG="v${VERSION}" - SHA=$(git log -1 --format="%H" -- lib/braintrust/version.rb) + COMMIT_MSG=$(git log -1 --format="%s" HEAD) + BRANCH=$(git branch -r --contains HEAD --format="%(refname:short)" | sed 's|origin/||' | head -1) if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Error: Tag $TAG already exists — has the version been bumped?" - exit 1 + if [ "${{ inputs.dry_run }}" = "true" ]; then + echo "Warning: Tag $TAG already exists — skipping in dry run" + else + echo "Error: Tag $TAG already exists — has the version been bumped?" + exit 1 + fi + fi + + if git merge-base --is-ancestor HEAD origin/main 2>/dev/null; then + ON_MAIN=true + else + ON_MAIN=false fi + PREV_TAG=$(git describe --tags --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' HEAD^ 2>/dev/null || echo "") + echo "tag=$TAG" >> $GITHUB_OUTPUT - echo "sha=$SHA" >> $GITHUB_OUTPUT - echo "Ready to release $TAG @ $SHA" + echo "commit_message=$COMMIT_MSG" >> $GITHUB_OUTPUT + echo "branch=$BRANCH" >> $GITHUB_OUTPUT + echo "on_main=$ON_MAIN" >> $GITHUB_OUTPUT + echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT + echo "Ready to release $TAG @ ${{ inputs.sha }} ($COMMIT_MSG)" - notify: + prepare: needs: validate runs-on: ubuntu-latest timeout-minutes: 5 permissions: - contents: read + contents: write # required for releases/generate-notes API + outputs: + pr_list: ${{ steps.pr-list.outputs.pr_list }} + notes: ${{ steps.pr-list.outputs.notes }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - ref: ${{ needs.validate.outputs.sha }} - fetch-depth: 0 - - - name: Post release summary and notify Slack + - name: Fetch PR list and release notes + id: pr-list env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - SLACK_CHANNEL: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }} - TAG: ${{ needs.validate.outputs.release_tag }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.validate.outputs.release_tag }} run: | - PREVIOUS_TAG=$(git describe --tags --abbrev=0 \ - --match='v[0-9]*.[0-9]*.[0-9]*' HEAD^ 2>/dev/null || echo "") - - NOTES=$(gh api "repos/$GITHUB_REPOSITORY/releases/generate-notes" \ + PREV_TAG="${{ needs.validate.outputs.prev_tag }}" + BODY=$(gh api "repos/$GITHUB_REPOSITORY/releases/generate-notes" \ --method POST \ --field tag_name="$TAG" \ - --jq '.body' 2>/dev/null || echo "_No previous release found — initial release._") + --field target_commitish="${{ inputs.sha }}" \ + ${PREV_TAG:+--field previous_tag_name="$PREV_TAG"} \ + --jq '.body' 2>/dev/null || echo "") - APPROVE_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + PR_LIST=$(echo "$BODY" \ + | grep "^\* " \ + | grep -v "made their first contribution" \ + | grep -v "Full Changelog" \ + | head -10 \ + | sed 's|^\* ||' \ + | sed 's| by @[^ ]*||' \ + | sed 's@ in \(https://[^ ]*/pull/\([0-9]*\)\)@ (<\1|#\2>)@' \ + | sed 's/^/• /' \ + | tr '\n' $'\x1f' || echo "") + + echo "pr_list=$PR_LIST" >> $GITHUB_OUTPUT + echo "notes=$(echo "$BODY" | base64 -w 0)" >> $GITHUB_OUTPUT + + notify: + needs: [validate, prepare] + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - name: Post release summary + env: + TAG: ${{ needs.validate.outputs.release_tag }} + run: | + NOTES=$(echo "${{ needs.prepare.outputs.notes }}" | base64 -d 2>/dev/null) + if [ -z "$NOTES" ]; then NOTES="_Release notes unavailable._"; fi + + BRANCH_LABEL="[${{ needs.validate.outputs.branch }}]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/tree/${{ needs.validate.outputs.branch }})" + if [ "${{ needs.validate.outputs.on_main }}" = "false" ]; then + BRANCH_LABEL="$BRANCH_LABEL ⚠️" + fi - # Write full PR list to job summary — visible on the approval page echo "## braintrust-sdk-ruby $TAG" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ inputs.dry_run }}" = "true" ]; then + echo "> [!NOTE]" >> $GITHUB_STEP_SUMMARY + echo "> Dry run: Nothing will be tagged or published." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.validate.outputs.on_main }}" = "false" ]; then + echo "> [!WARNING]" >> $GITHUB_STEP_SUMMARY + echo "> Release SHA is not on main: Is this a special release? (e.g. beta, backport, etc)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + echo "**SHA:** [${{ inputs.sha }}]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/${{ inputs.sha }})" >> $GITHUB_STEP_SUMMARY + echo "**Commit:** ${{ needs.validate.outputs.commit_message }}" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** $BRANCH_LABEL" >> $GITHUB_STEP_SUMMARY + + PREV_TAG="${{ needs.validate.outputs.prev_tag }}" + if [ -n "$PREV_TAG" ]; then + DIFF_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/${PREV_TAG}...${{ inputs.sha }}" + echo "**Diff:** [${PREV_TAG}...$TAG]($DIFF_URL)" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY echo "$NOTES" >> $GITHUB_STEP_SUMMARY - # Slack message is intentionally brief — full details are one click away + - name: Notify Slack + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + SLACK_CHANNEL: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }} + TAG: ${{ needs.validate.outputs.release_tag }} + run: | + APPROVE_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + + BRANCH_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/tree/${{ needs.validate.outputs.branch }}" + BRANCH_LINK="<$BRANCH_URL|${{ needs.validate.outputs.branch }}>" + if [ "${{ needs.validate.outputs.on_main }}" = "false" ]; then + BRANCH_INFO="> ⚠️ NOT on main: $BRANCH_LINK" + else + BRANCH_INFO="$BRANCH_LINK" + fi + + PREV_TAG="${{ needs.validate.outputs.prev_tag }}" + DIFF_PART="" + if [ -n "$PREV_TAG" ]; then + DIFF_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/${PREV_TAG}...${{ inputs.sha }}" + DIFF_PART=" · <$DIFF_URL|${PREV_TAG}...$TAG>" + fi + + PR_LIST=$(echo "${{ needs.prepare.outputs.pr_list }}" | tr $'\x1f' '\n' | sed '/^$/d') + + TEXT=":ruby: *braintrust-sdk-ruby $TAG* awaiting approval" + + if [ "${{ inputs.dry_run }}" = "true" ]; then + TEXT="$TEXT\n> :information_source: _Dry run: nothing will be tagged or published._" + fi + + TEXT="$TEXT\n${BRANCH_INFO}${DIFF_PART}" + + if [ -n "$PR_LIST" ]; then + TEXT="$TEXT\n$PR_LIST" + fi + + TEXT="$TEXT\n<$APPROVE_URL|View changes & approve>" + curl -s -X POST "https://slack.com/api/chat.postMessage" \ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"channel\": \"$SLACK_CHANNEL\", - \"text\": \":gem: *braintrust-sdk-ruby $TAG* is awaiting release approval. <$APPROVE_URL|View changes & approve>\" - }" + -H "Content-Type: application/json; charset=utf-8" \ + -d "{\"channel\": \"$SLACK_CHANNEL\", \"text\": \"$TEXT\"}" publish: - needs: [validate, notify] + needs: [validate, prepare, notify] runs-on: ubuntu-latest timeout-minutes: 15 - environment: rubygems-publish + environment: ${{ inputs.dry_run && 'rubygems-publish-dry-run' || 'rubygems-publish' }} permissions: contents: write id-token: write steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ needs.validate.outputs.sha }} + ref: ${{ inputs.sha }} fetch-depth: 0 - - uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0 + # LANGUAGE-SPECIFIC: replace with your language's setup action + - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 with: ruby-version: '3.4' bundler-cache: true - - name: Configure RubyGems credentials - uses: rubygems/configure-rubygems-credentials@a991f145d5e4a60c4b0a3ddb204f557dc1a4f985 # main + - name: Create release tag + if: ${{ !inputs.dry_run }} + run: git tag "$GITHUB_REF_NAME" + env: + GITHUB_REF_NAME: ${{ needs.validate.outputs.release_tag }} - - name: Publish gem + # LANGUAGE-SPECIFIC: replace with your language's publish command + - name: Publish package with attestation + if: ${{ !inputs.dry_run }} + uses: rubygems/release-gem@6317d8d1f7e28c24d28f6eff169ea854948bd9f7 # v1.2.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ needs.validate.outputs.release_tag }} + + # LANGUAGE-SPECIFIC: replace with your language's build/check command + - name: Dry run — build and check only + if: ${{ inputs.dry_run }} run: | - git tag "$GITHUB_REF_NAME" - bundle exec rake release + bundle exec rake lint + bundle exec rake build + echo "DRY RUN: would push gem ${{ needs.validate.outputs.release_tag }} to RubyGems and create tag" - name: Notify Slack on release env: @@ -125,10 +267,46 @@ jobs: SLACK_CHANNEL: ${{ vars.SLACK_SDK_RELEASE_CHANNEL }} TAG: ${{ needs.validate.outputs.release_tag }} run: | + RELEASE_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$TAG" + + PREV_TAG="${{ needs.validate.outputs.prev_tag }}" + DIFF_PART="" + if [ -n "$PREV_TAG" ]; then + DIFF_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/compare/${PREV_TAG}...$TAG" + DIFF_PART="<$DIFF_URL|${PREV_TAG}...$TAG> · " + fi + + PR_LIST=$(echo "${{ needs.prepare.outputs.pr_list }}" | tr $'\x1f' '\n' | sed '/^$/d') + + RUN_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" + + if [ "${{ inputs.dry_run }}" = "true" ]; then + TEXT=":white_check_mark: *braintrust-sdk-ruby $TAG* complete" + else + TEXT=":white_check_mark: *braintrust-sdk-ruby $TAG* published to RubyGems" + fi + + if [ "${{ inputs.dry_run }}" = "true" ]; then + TEXT="$TEXT\n> :information_source: _Dry run: gem built, nothing tagged or published._" + fi + + BRANCH_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/tree/${{ needs.validate.outputs.branch }}" + BRANCH_LINK="<$BRANCH_URL|${{ needs.validate.outputs.branch }}>" + if [ "${{ needs.validate.outputs.on_main }}" = "false" ]; then + TEXT="$TEXT\n> ⚠️ NOT on main: $BRANCH_LINK" + fi + + if [ "${{ inputs.dry_run }}" = "true" ]; then + TEXT="$TEXT\n${DIFF_PART}<$RUN_URL|View dry run>" + else + TEXT="$TEXT\n${DIFF_PART}<$RELEASE_URL|View release>" + fi + + if [ -n "$PR_LIST" ]; then + TEXT="$TEXT\n$PR_LIST" + fi + curl -s -X POST "https://slack.com/api/chat.postMessage" \ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"channel\": \"$SLACK_CHANNEL\", - \"text\": \":white_check_mark: *braintrust-sdk-ruby $TAG* has been published to RubyGems.\" - }" + -H "Content-Type: application/json; charset=utf-8" \ + -d "{\"channel\": \"$SLACK_CHANNEL\", \"text\": \"$TEXT\"}" diff --git a/scripts/push-release-tag.sh b/scripts/push-release-tag.sh deleted file mode 100755 index 0c8bab49..00000000 --- a/scripts/push-release-tag.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash -# Script to push a release tag for the Ruby SDK -# Inspired by py/scripts/push-release-tag.sh - -set -euo pipefail - -# Parse arguments -DRY_RUN=false -while [[ $# -gt 0 ]]; do - case $1 in - --dry-run) - DRY_RUN=true - shift - ;; - *) - echo "Unknown option: $1" - echo "Usage: $0 [--dry-run]" - exit 1 - ;; - esac -done - -# Get the repository root -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$REPO_ROOT" - -# Fetch latest tags -echo "Fetching latest tags..." -git fetch --tags - -# Get version from version.rb -VERSION=$(ruby -r "./lib/braintrust/version.rb" -e "puts Braintrust::VERSION") -TAG="v${VERSION}" - -# Check if tag already exists -if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Error: Tag $TAG already exists" - exit 1 -fi - -# Get current commit info -COMMIT_SHA=$(git rev-parse HEAD) -COMMIT_SHORT_SHA=$(git rev-parse --short HEAD) -REPO_URL=$(git config --get remote.origin.url | sed 's/\.git$//' | sed 's/git@github.com:/https:\/\/github.com\//') - -# Get the previous tag for comparison -PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") - -echo "" -echo "========================================" -echo "Release Information" -echo "========================================" -echo "New version tag: $TAG" -echo "Current commit: $COMMIT_SHA" -echo "Commit URL: ${REPO_URL}/commit/${COMMIT_SHA}" -if [ -n "$PREVIOUS_TAG" ]; then - echo "Previous tag: $PREVIOUS_TAG" - echo "Changelog: ${REPO_URL}/compare/${PREVIOUS_TAG}...${COMMIT_SHORT_SHA}" -fi -echo "========================================" -echo "" - -if [ "$DRY_RUN" = true ]; then - echo "DRY RUN: Would create and push tag $TAG" - echo "Exiting without making changes." - exit 0 -fi - -# Require confirmation -echo "This will create and push tag $TAG to trigger the production release." -echo "Type 'YOLO' to confirm:" -read -r CONFIRMATION - -if [ "$CONFIRMATION" != "YOLO" ]; then - echo "Confirmation failed. Aborting." - exit 1 -fi - -# Create and push the tag -echo "" -echo "Creating tag $TAG..." -git tag "$TAG" - -echo "Pushing tag $TAG..." -git push origin "$TAG" - -echo "" -echo "✓ Tag $TAG has been pushed successfully!" -echo "" -echo "Monitor the release workflow at:" -echo "${REPO_URL}/actions" diff --git a/scripts/validate-release-tag.sh b/scripts/validate-release-tag.sh index c7b86908..0b93363c 100755 --- a/scripts/validate-release-tag.sh +++ b/scripts/validate-release-tag.sh @@ -1,62 +1,27 @@ #!/usr/bin/env bash -# Script to validate a release tag for the Ruby SDK -# Ensures the tag matches the version in version.rb and is on the main branch +# Sanity-checks that GITHUB_REF_NAME is set and matches version.rb. +# Called internally by rake release via rake release:validate. +# The release workflow handles all meaningful pre-release checks +# (tag existence, explicit SHA targeting) before this script runs. set -euo pipefail -# Get the repository root REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$REPO_ROOT" -# Get the current tag (should be set in CI environment) RELEASE_TAG="${GITHUB_REF_NAME:-}" if [ -z "$RELEASE_TAG" ]; then echo "Error: GITHUB_REF_NAME is not set" - echo "This script should be run in a GitHub Actions environment" exit 1 fi -echo "Validating release tag: $RELEASE_TAG" - -# Extract version from tag (remove 'v' prefix) TAG_VERSION="${RELEASE_TAG#v}" - -# Get version from version.rb VERSION=$(ruby -r "./lib/braintrust/version.rb" -e "puts Braintrust::VERSION") -echo "Tag version: $TAG_VERSION" -echo "version.rb: $VERSION" - -# Validate version matches if [ "$TAG_VERSION" != "$VERSION" ]; then - echo "" - echo "Error: Tag version does not match version.rb" - echo " Tag: $TAG_VERSION" - echo " version.rb: $VERSION" + echo "Error: Tag version ($TAG_VERSION) does not match version.rb ($VERSION)" exit 1 fi -echo "✓ Version matches" - -# Validate the tag is on the main branch -MAIN_BRANCH="main" -TAG_COMMIT=$(git rev-parse "$RELEASE_TAG") -MAIN_COMMIT=$(git rev-parse "origin/$MAIN_BRANCH") - -# Check if the tag commit is an ancestor of main or is main -if ! git merge-base --is-ancestor "$TAG_COMMIT" "$MAIN_COMMIT" && [ "$TAG_COMMIT" != "$MAIN_COMMIT" ]; then - echo "" - echo "Error: Tag $RELEASE_TAG is not on the $MAIN_BRANCH branch" - echo " Tag commit: $TAG_COMMIT" - echo " Main commit: $MAIN_COMMIT" - exit 1 -fi - -echo "✓ Tag is on the $MAIN_BRANCH branch" - -echo "" -echo "✓ Release tag validation successful" -echo " Tag: $RELEASE_TAG" -echo " Version: $VERSION" -echo " Commit: $TAG_COMMIT" +echo "✓ Tag $RELEASE_TAG matches version.rb ($VERSION)"