Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
56a3fa6
Add next-version.sh to compute the next release version from tags
blink1073 Aug 5, 2026
faafeea
Add bump-major-version.sh to update version.txt on major releases
blink1073 Aug 5, 2026
84164d0
Run release script tests in CI
blink1073 Aug 5, 2026
e2fd785
Add release workflow for patch/minor/major version bumps
blink1073 Aug 5, 2026
a8c63ee
Fix release.yml v3 tag race and surface next-version diagnostics
blink1073 Aug 5, 2026
60edc5e
Replace update-action-tag workflow with the release workflow
blink1073 Aug 5, 2026
982c7d9
Rename shell-script test job to test-release-scripts
blink1073 Aug 5, 2026
9d8986f
Pin actions/checkout and actions/create-github-app-token to commit SHAs
blink1073 Aug 5, 2026
e820b8b
Use exact version numbers in SHA-pin comments
blink1073 Aug 5, 2026
fef4e09
Fix release.yml v3 tag race and surface next-version diagnostics
blink1073 Aug 5, 2026
424b3ab
Remove bootstrap-behavior doc note, covered by this ticket's first re…
blink1073 Aug 5, 2026
704f77f
Merge remote-tracking branch 'upstream/main' into actual-version
blink1073 Aug 5, 2026
079eb5a
Merge remote-tracking branch 'upstream/main' into actual-version
blink1073 Aug 21, 2026
8695d04
Tighten prose in CONTRIBUTING.md and README.md
blink1073 Aug 21, 2026
b64722a
Update .github/workflows/release.yml
blink1073 Aug 21, 2026
ace2ef0
Direct the floating-tag retirement warning to open a PR
blink1073 Aug 21, 2026
dadbe9e
Serialize releases, gate major bump on actual change, drop auto-boots…
blink1073 Sep 3, 2026
8854f6e
Remove the no-semver-tag guard; the first tag is created manually
blink1073 Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/scripts/bump-major-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail

VERSION="${1:?Usage: bump-major-version.sh <version> [version_file]}"
VERSION_FILE="${2:-.github/workflows/version.txt}"
MAJOR="${VERSION%%.*}"

echo "$MAJOR" > "$VERSION_FILE"
43 changes: 43 additions & 0 deletions .github/scripts/bump-major-version.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUMP_MAJOR_SH="$SCRIPT_DIR/bump-major-version.sh"
FAILURES=0

assert_eq() {
local description="$1" expected="$2" actual="$3"
if [ "$expected" != "$actual" ]; then
echo "FAIL: $description (expected '$expected', got '$actual')" >&2
FAILURES=$((FAILURES + 1))
else
echo "PASS: $description"
fi
}

# Test: writes the major version number to the given file
tmp_file=$(mktemp)
bash "$BUMP_MAJOR_SH" "4.0.0" "$tmp_file"
assert_eq "writes major version" "4" "$(cat "$tmp_file")"
rm -f "$tmp_file"

# Test: multi-digit major version
tmp_file=$(mktemp)
bash "$BUMP_MAJOR_SH" "12.3.4" "$tmp_file"
assert_eq "writes multi-digit major version" "12" "$(cat "$tmp_file")"
rm -f "$tmp_file"

# Test: missing version argument fails
if bash "$BUMP_MAJOR_SH" >/dev/null 2>&1; then
echo "FAIL: expected failure with no version argument" >&2
FAILURES=$((FAILURES + 1))
else
echo "PASS: fails with no version argument"
fi

if [ "$FAILURES" -gt 0 ]; then
echo "$FAILURES test(s) failed" >&2
exit 1
fi

echo "All bump-major-version.sh tests passed"
56 changes: 56 additions & 0 deletions .github/scripts/next-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail

BUMP="${1:?Usage: next-version.sh <patch|minor|major>}"

case "$BUMP" in
patch|minor|major) ;;
*)
echo "Unknown bump type: $BUMP" >&2
exit 1
;;
esac

LATEST_SEMVER=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
| sed 's/^v//' \
| sort -t. -k1,1n -k2,2n -k3,3n \
| tail -1)

MAJOR=$(echo "$LATEST_SEMVER" | cut -d. -f1)
MINOR=$(echo "$LATEST_SEMVER" | cut -d. -f2)
PATCH=$(echo "$LATEST_SEMVER" | cut -d. -f3)
PREVIOUS_MAJOR="$MAJOR"

case "$BUMP" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac

if [ "$MAJOR" != "$PREVIOUS_MAJOR" ]; then
MAJOR_BUMPED=true
else
MAJOR_BUMPED=false
fi

NEXT_VERSION="${MAJOR}.${MINOR}.${PATCH}"

if [ "$MAJOR" = "3" ]; then
IS_V3=true
else
IS_V3=false
fi

echo "NEXT_VERSION=${NEXT_VERSION}"
echo "IS_V3=${IS_V3}"
echo "MAJOR_BUMPED=${MAJOR_BUMPED}"
81 changes: 81 additions & 0 deletions .github/scripts/next-version.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
NEXT_VERSION_SH="$SCRIPT_DIR/next-version.sh"
FAILURES=0

assert_eq() {
local description="$1" expected="$2" actual="$3"
if [ "$expected" != "$actual" ]; then
echo "FAIL: $description (expected '$expected', got '$actual')" >&2
FAILURES=$((FAILURES + 1))
else
echo "PASS: $description"
fi
}

new_temp_repo() {
local tmp
tmp=$(mktemp -d)
git init -q "$tmp"
git -C "$tmp" config user.email "test@example.com"
git -C "$tmp" config user.name "Test"
git -C "$tmp" commit -q --allow-empty -m "init"
echo "$tmp"
}

# Test: patch bump from full semver tag
repo=$(new_temp_repo)
git -C "$repo" tag v3.2.1
OUTPUT=$(cd "$repo" && bash "$NEXT_VERSION_SH" patch)
assert_eq "patch bump" "$(printf 'NEXT_VERSION=3.2.2\nIS_V3=true\nMAJOR_BUMPED=false')" "$OUTPUT"
rm -rf "$repo"

# Test: minor bump resets patch
repo=$(new_temp_repo)
git -C "$repo" tag v3.2.1
OUTPUT=$(cd "$repo" && bash "$NEXT_VERSION_SH" minor)
assert_eq "minor bump resets patch" "$(printf 'NEXT_VERSION=3.3.0\nIS_V3=true\nMAJOR_BUMPED=false')" "$OUTPUT"
rm -rf "$repo"

# Test: major bump resets minor/patch, flips IS_V3 to false, and reports a major bump
repo=$(new_temp_repo)
git -C "$repo" tag v3.2.1
OUTPUT=$(cd "$repo" && bash "$NEXT_VERSION_SH" major)
assert_eq "major bump resets minor/patch" "$(printf 'NEXT_VERSION=4.0.0\nIS_V3=false\nMAJOR_BUMPED=true')" "$OUTPUT"
rm -rf "$repo"

# Test: a full semver tag takes priority over an existing floating tag
repo=$(new_temp_repo)
git -C "$repo" tag v3
git -C "$repo" tag v3.2.1
OUTPUT=$(cd "$repo" && bash "$NEXT_VERSION_SH" patch)
assert_eq "full semver tag wins over floating tag" "$(printf 'NEXT_VERSION=3.2.2\nIS_V3=true\nMAJOR_BUMPED=false')" "$OUTPUT"
rm -rf "$repo"

# Test: numeric sort, not lexicographic (v3.10.0 > v3.2.1)
repo=$(new_temp_repo)
git -C "$repo" tag v3.2.1
git -C "$repo" tag v3.10.0
OUTPUT=$(cd "$repo" && bash "$NEXT_VERSION_SH" patch)
assert_eq "numeric sort picks v3.10.0 over v3.2.1" "$(printf 'NEXT_VERSION=3.10.1\nIS_V3=true\nMAJOR_BUMPED=false')" "$OUTPUT"
rm -rf "$repo"

# Test: invalid bump type is rejected
repo=$(new_temp_repo)
git -C "$repo" tag v3.2.1
if (cd "$repo" && bash "$NEXT_VERSION_SH" bogus) >/dev/null 2>&1; then
echo "FAIL: expected failure for invalid bump type" >&2
FAILURES=$((FAILURES + 1))
else
echo "PASS: rejects invalid bump type"
fi
rm -rf "$repo"

if [ "$FAILURES" -gt 0 ]; then
echo "$FAILURES test(s) failed" >&2
exit 1
fi

echo "All next-version.sh tests passed"
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ jobs:
working-directory: ${{ matrix.working-directory }}
run: npm run ci-test

test-release-scripts:
name: Release Scripts Tests
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Run next-version.sh tests
run: bash .github/scripts/next-version.test.sh

- name: Run bump-major-version.sh tests
run: bash .github/scripts/bump-major-version.test.sh

test-python:
name: Python Tests
runs-on: ubuntu-latest
Expand Down
129 changes: 129 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: Release

on:
workflow_dispatch:
inputs:
bump:
description: Version part to bump
required: true
type: choice
options:
- patch
- minor
- major
dry_run:
description: Whether this is a dry run
required: true
type: boolean
default: true

concurrency:
group: release
cancel-in-progress: false

jobs:
release:
name: Release
runs-on: ubuntu-latest
Comment thread
Copilot marked this conversation as resolved.
environment: release
if: github.ref == 'refs/heads/main'
permissions:
contents: write
id-token: write

steps:
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-contents: write

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
token: ${{ steps.app-token.outputs.token }}
fetch-depth: 0
# Needed to push the tag and version bump commit in later steps.
persist-credentials: true

- name: Setup
uses: $/setup
with:
aws_role_arn: ${{ secrets.AWS_ROLE_ARN }}
aws_region_name: ${{ vars.AWS_REGION_NAME }}
aws_secret_id: ${{ secrets.AWS_SECRET_ID }}

- name: Determine next version
shell: bash
run: |
set +e
bash .github/scripts/next-version.sh "${{ inputs.bump }}" > /tmp/next-version.out 2> /tmp/next-version.err
STATUS=$?
set -e
if [ -s /tmp/next-version.err ]; then
cat /tmp/next-version.err
cat /tmp/next-version.err >> "$GITHUB_STEP_SUMMARY"
fi
if [ "$STATUS" -ne 0 ]; then
exit "$STATUS"
fi
cat /tmp/next-version.out >> "$GITHUB_ENV"

- name: Bump the major version file
if: env.MAJOR_BUMPED == 'true'
uses: $/bump-version
with:
version: ${{ env.NEXT_VERSION }}
version_bump_script: "bash .github/scripts/bump-major-version.sh"
push_commit: ${{ inputs.dry_run == false }}

- name: Tag the version
uses: $/tag-version
with:
version: ${{ env.NEXT_VERSION }}
tag_template: "v${VERSION}"
push_tag: ${{ inputs.dry_run == false }}

- name: Report floating v3 tag update (dry run)
if: env.IS_V3 == 'true' && inputs.dry_run == true
shell: bash
run: |
echo "[dry-run] Would move floating tag v3 to $(git rev-parse HEAD)" >> "$GITHUB_STEP_SUMMARY"

- name: Create a new signed v3 tag
if: env.IS_V3 == 'true' && inputs.dry_run == false
uses: $/git-sign
with:
command: git tag -a "v3" -f -m "Update tag" -s --local-user=${{ env.GPG_KEY_ID }}

- name: Push the v3 tag
if: env.IS_V3 == 'true' && inputs.dry_run == false
shell: bash -eux {0}
run: |
git push --force origin refs/tags/v3:refs/tags/v3

- name: Warn about floating tag retirement
if: env.IS_V3 != 'true'
shell: bash
run: |
MESSAGE="Release v${NEXT_VERSION} no longer uses floating tags. Open a PR removing the floating-tag-update steps (Create/Push v3 tag, and this warning) from .github/workflows/release.yml."
echo "::warning::$MESSAGE"
echo "$MESSAGE" >> "$GITHUB_STEP_SUMMARY"
Comment thread
GromNaN marked this conversation as resolved.

- name: Create GitHub Release
if: inputs.dry_run == false
shell: bash
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
if ! gh release create "v${NEXT_VERSION}" --title "v${NEXT_VERSION}" --generate-notes; then
echo "::error::Tag v${NEXT_VERSION} was already pushed, but creating its GitHub Release failed. Run 'gh release create v${NEXT_VERSION} --generate-notes' manually to finish; re-running this workflow would compute a new version instead."
exit 1
fi
echo "Created release v${NEXT_VERSION}" >> "$GITHUB_STEP_SUMMARY"

- name: Report release (dry run)
if: inputs.dry_run == true
shell: bash
run: |
echo "[dry-run] Would create tag v${NEXT_VERSION} and GitHub Release v${NEXT_VERSION}" >> "$GITHUB_STEP_SUMMARY"
52 changes: 0 additions & 52 deletions .github/workflows/update-action-tag.yml

This file was deleted.

Loading
Loading