Skip to content
Open
153 changes: 153 additions & 0 deletions .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: Auto Release on Version Bump

# Automatically creates a release when Cargo.toml version changes
# Triggered by Renovate PRs merging to development

on:
push:
branches: [development, main]
paths:
- 'Cargo.toml'

permissions:
contents: write
pull-requests: write

jobs:
check-version-change:
name: Check Version Change
runs-on: ubuntu-latest
outputs:
version_changed: ${{ steps.check.outputs.changed }}
new_version: ${{ steps.check.outputs.version }}
should_release: ${{ steps.check.outputs.should_release }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2

- name: Check if version changed
id: check
run: |
# Get current version
NEW_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/.*= "\(.*\)"/\1/')
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT

# Get previous version
git checkout HEAD^
OLD_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/.*= "\(.*\)"/\1/')
git checkout -

echo "Current version: $NEW_VERSION"
echo "Previous version: $OLD_VERSION"

if [ "$NEW_VERSION" != "$OLD_VERSION" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
echo "should_release=true" >> $GITHUB_OUTPUT
echo "✅ Version changed from $OLD_VERSION to $NEW_VERSION"
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "should_release=false" >> $GITHUB_OUTPUT
echo "ℹ️ Version unchanged"
fi

- name: Check if tag already exists
if: steps.check.outputs.changed == 'true'
run: |
VERSION="${{ steps.check.outputs.version }}"
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
echo "⚠️ Tag v$VERSION already exists, skipping release"
echo "should_release=false" >> $GITHUB_OUTPUT
fi

create-pr-to-main:
name: Create PR to Main
needs: check-version-change
if: needs.check-version-change.outputs.version_changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Create release branch
run: |
VERSION="${{ needs.check-version-change.outputs.new_version }}"
git checkout -b "release/v$VERSION"
git push -u origin "release/v$VERSION"

- name: Create PR to main
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.check-version-change.outputs.new_version }}"

# Check if PR already exists
EXISTING_PR=$(gh pr list --base main --head "release/v$VERSION" --json number --jq '.[0].number')

if [ -n "$EXISTING_PR" ]; then
echo "PR #$EXISTING_PR already exists"
exit 0
fi

# Get commit messages since last release
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
CHANGES=$(git log --pretty=format:"- %s (%h)" "$LAST_TAG"..HEAD)
else
CHANGES=$(git log --pretty=format:"- %s (%h)" -10)
fi

gh pr create \
--base main \
--head "release/v$VERSION" \
--title "Release v$VERSION" \
--body "## Release v$VERSION

Automated release created by version bump in Cargo.toml.

### Changes
$CHANGES

### Checklist
- [ ] All CI checks pass
- [ ] Documentation is up to date
- [ ] CHANGELOG.md updated (if needed)

Once merged, the release workflow will automatically:
- Create GitHub Release v$VERSION
- Build and attach all artifacts
- Update \`latest\` tag
- Generate attestations and checksums

---
_🤖 Auto-generated by version bump workflow_"

trigger-release:
name: Create Release Tag
needs: check-version-change
if: needs.check-version-change.outputs.should_release == 'true' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Create and push release tag
run: |
VERSION="${{ needs.check-version-change.outputs.new_version }}"

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

# Create tag
git tag -a "v$VERSION" -m "Release v$VERSION

Automated release from version bump in Cargo.toml.
Triggered by Renovate dependency updates."

# Push tag (this will trigger the release workflow)
git push origin "v$VERSION"

echo "✅ Created and pushed tag v$VERSION"
echo "🚀 Release workflow will now build and publish the release"
6 changes: 3 additions & 3 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ jobs:
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
contents: write # Needed for Claude to make code changes and commits
pull-requests: write # Needed for Claude to comment on PRs
issues: write # Needed for Claude to comment on issues
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
Expand Down
116 changes: 98 additions & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,20 @@ jobs:
echo "- ✅ Documentation builds successfully" >> RELEASE_SUMMARY.md
echo "- ✅ Format check passed (rustfmt)" >> RELEASE_SUMMARY.md
echo "" >> RELEASE_SUMMARY.md
echo "## 📦 Included Reports" >> RELEASE_SUMMARY.md
echo "## 📦 What's Included" >> RELEASE_SUMMARY.md
echo "" >> RELEASE_SUMMARY.md
echo "1. **CHANGELOG.md** - Complete project history" >> RELEASE_SUMMARY.md
echo "2. **AGENTS.md** - AI/LLM-optimized usage documentation" >> RELEASE_SUMMARY.md
echo "3. **clippy-report.md** - Zero warnings validation" >> RELEASE_SUMMARY.md
echo "4. **security-audit.md** - Vulnerability scan results" >> RELEASE_SUMMARY.md
echo "5. **sbom.md** - Complete dependency list with licenses" >> RELEASE_SUMMARY.md
echo "6. **coverage-report.md** - Test coverage statistics" >> RELEASE_SUMMARY.md
echo "7. **build-info.md** - Build environment details" >> RELEASE_SUMMARY.md
echo "8. **dependency-report.md** - Dependency status" >> RELEASE_SUMMARY.md
echo "**In Crate Package:**" >> RELEASE_SUMMARY.md
echo "- Source code with full API" >> RELEASE_SUMMARY.md
echo "- AGENTS.md - AI/LLM-optimized usage documentation" >> RELEASE_SUMMARY.md
echo "- README.md, LICENSE, CONTRIBUTING.md" >> RELEASE_SUMMARY.md
echo "" >> RELEASE_SUMMARY.md
echo "**Release Reports (separate download):**" >> RELEASE_SUMMARY.md
echo "1. **clippy-report.md** - Zero warnings validation" >> RELEASE_SUMMARY.md
echo "2. **security-audit.md** - Vulnerability scan results" >> RELEASE_SUMMARY.md
echo "3. **sbom.md** - Complete dependency list with licenses" >> RELEASE_SUMMARY.md
echo "4. **coverage-report.md** - Test coverage statistics" >> RELEASE_SUMMARY.md
echo "5. **build-info.md** - Build environment details" >> RELEASE_SUMMARY.md
echo "6. **dependency-report.md** - Dependency status" >> RELEASE_SUMMARY.md
echo "" >> RELEASE_SUMMARY.md
echo "## 🚀 Installation" >> RELEASE_SUMMARY.md
echo "" >> RELEASE_SUMMARY.md
Expand All @@ -251,17 +255,11 @@ jobs:
echo "Proprietary software. All rights reserved." >> RELEASE_SUMMARY.md
echo "See LICENSE file for details." >> RELEASE_SUMMARY.md

- name: Prepare user-focused AGENTS.md
run: |
cp AGENTS.md.release AGENTS_USER.md

- name: Organize reports into subdirectory
run: |
mkdir -p release-artifacts/reports
mkdir -p release-artifacts/ai-docs
mv CHANGELOG.md release-artifacts/
mv CHANGELOG.md release-artifacts/ 2>/dev/null || true
mv RELEASE_SUMMARY.md release-artifacts/
mv AGENTS_USER.md release-artifacts/ai-docs/AGENTS.md
mv clippy-report.md release-artifacts/reports/
mv security-audit.md release-artifacts/reports/
mv sbom.md release-artifacts/reports/
Expand Down Expand Up @@ -403,9 +401,9 @@ jobs:
release-reports-v${{ needs.validate.outputs.version }}.tar.gz
release-reports-v${{ needs.validate.outputs.version }}.zip
singularity-language-registry-${{ needs.validate.outputs.version }}.crate
SHA256SUMS
INSTALL.md
PACKAGE_CONTENTS.txt
release-artifacts/ai-docs/AGENTS.md

build-artifacts:
name: Build Release Artifacts
Expand Down Expand Up @@ -450,6 +448,18 @@ jobs:
cd target/${{ matrix.target }}/release
Compress-Archive -Path *singularity_language_registry* -DestinationPath ../../../${{ matrix.artifact_name }}.zip

- name: Generate artifact attestation (Unix)
if: runner.os != 'Windows'
uses: actions/attest-build-provenance@v2
with:
subject-path: ${{ matrix.artifact_name }}.tar.gz

- name: Generate artifact attestation (Windows)
if: runner.os == 'Windows'
uses: actions/attest-build-provenance@v2
with:
subject-path: ${{ matrix.artifact_name }}.zip

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
Expand All @@ -466,17 +476,87 @@ jobs:
- name: Download all artifacts
uses: actions/download-artifact@v4

- name: Generate SHA256 checksums for binaries
run: |
find . -name "*.tar.gz" -o -name "*.zip" | while read file; do
sha256sum "$file" >> BINARY_SHA256SUMS
done
cat BINARY_SHA256SUMS || echo "No binary artifacts found"

- name: Upload to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.validate.outputs.version }}
files: |
**/*.tar.gz
**/*.zip
BINARY_SHA256SUMS

update-latest:
name: Update 'latest' Tag
needs: [validate, create-release, upload-artifacts]
runs-on: ubuntu-latest
if: success()
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Update latest tag
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

# Delete old latest tag if it exists
git tag -d latest 2>/dev/null || true
git push origin :refs/tags/latest 2>/dev/null || true

# Create new latest tag pointing to current release
git tag -a latest -m "Latest release (v${{ needs.validate.outputs.version }})"
git push origin latest

- name: Update latest release
env:
GH_TOKEN: ${{ github.token }}
run: |
# Delete old "latest" release if exists
gh release delete latest --yes 2>/dev/null || true

# Create latest release pointing to same artifacts
gh release create latest \
--title "Latest Release (v${{ needs.validate.outputs.version }})" \
--notes "This release always points to the latest stable version.

**Current Version**: v${{ needs.validate.outputs.version }}

For version-specific releases, see: https://github.com/${{ github.repository }}/releases

## Quick Install

### Mix (Elixir)
\`\`\`elixir
# Always use latest
{:singularity_language_registry, git: \"https://github.com/${{ github.repository }}\", tag: \"latest\"}

# Or pin to specific version
{:singularity_language_registry, git: \"https://github.com/${{ github.repository }}\", tag: \"v${{ needs.validate.outputs.version }}\"}
\`\`\`

### Download Binary
\`\`\`bash
# Linux
curl -L https://github.com/${{ github.repository }}/releases/download/latest/singularity-language-registry-linux-x64.tar.gz | tar xz

# macOS ARM
curl -L https://github.com/${{ github.repository }}/releases/download/latest/singularity-language-registry-macos-arm64.tar.gz | tar xz
\`\`\`

See artifacts below for all platforms and checksums." \
--latest

notify:
name: Notify Release
needs: [validate, build-crate-package, create-release]
needs: [validate, build-crate-package, create-release, update-latest]
runs-on: ubuntu-latest
if: always()
steps:
Expand Down
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ exclude = [
"WORKFLOW_GUIDE.md",
"CHANGELOG.md",

# AI documentation (repo version for developers, .release version for users)
"AGENTS.md",
# AI documentation - keep AGENTS.md.release excluded, AGENTS.md is included in crate
"AGENTS.md.release",

# IDE
Expand Down
Loading