Skip to content

Release

Release #42

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
version:
description: "Release version (semver, e.g. 1.0.0 or 1.0.0-rc.1 — no 'v' prefix)"
required: true
type: string
dry_run:
description: "Dry run — run all steps but skip tag creation and release publish"
required: false
type: boolean
default: false
permissions:
contents: write
# Only one release pipeline at a time.
concurrency:
group: release
cancel-in-progress: false
jobs:
validate:
name: Validate inputs
runs-on: ubuntu-latest
steps:
- name: Validate version format
run: |
VERSION='${{ inputs.version }}'
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$ ]]; then
echo "::error::Invalid version format: '${VERSION}'. Expected semver (e.g. 1.0.0 or 1.0.0-rc.1)"
exit 1
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Check tag does not already exist
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Verify tag is new
run: |
TAG='${{ inputs.version }}'
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "::error::Tag ${TAG} already exists. Delete it first or choose a different version."
exit 1
fi
echo "✓ Tag ${TAG} is available"
- name: Verify CHANGELOG.md contains release version
run: |
VERSION='${{ inputs.version }}'
if ! grep -qP "^## \[${VERSION}\]" CHANGELOG.md; then
echo "::error::CHANGELOG.md has no entry for version ${VERSION}. Add a '## [${VERSION}] - YYYY-MM-DD' section before releasing."
exit 1
fi
echo "✓ CHANGELOG.md contains [${VERSION}]"
lint:
name: Lint
needs: [validate]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Run linter
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
with:
version: latest
test-unit:
name: Unit tests
needs: [validate]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Run unit tests
run: go test -race -count=1 ./...
test-integration-jira:
name: "Integration: Jira"
needs: [lint, test-unit]
runs-on: ubuntu-latest
env:
SORTIE_JIRA_TEST: "1"
SORTIE_JIRA_ENDPOINT: ${{ secrets.SORTIE_JIRA_ENDPOINT }}
SORTIE_JIRA_API_KEY: ${{ secrets.SORTIE_JIRA_API_KEY }}
SORTIE_JIRA_PROJECT: ${{ secrets.SORTIE_JIRA_PROJECT }}
steps:
- name: Verify Jira secrets are configured
run: |
missing=()
[ -z "$SORTIE_JIRA_ENDPOINT" ] && missing+=("SORTIE_JIRA_ENDPOINT")
[ -z "$SORTIE_JIRA_API_KEY" ] && missing+=("SORTIE_JIRA_API_KEY")
[ -z "$SORTIE_JIRA_PROJECT" ] && missing+=("SORTIE_JIRA_PROJECT")
if [ ${#missing[@]} -ne 0 ]; then
echo "::error::Missing required secrets: ${missing[*]}"
echo "Configure these in Settings → Secrets and variables → Actions"
exit 1
fi
echo "✓ All Jira secrets are present"
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Run integration tests
run: go test -race -count=1 -v -run 'Integration' ./internal/tracker/jira/...
test-integration-github:
name: "Integration: GitHub"
needs: [lint, test-unit]
runs-on: ubuntu-latest
env:
SORTIE_GITHUB_TEST: "1"
SORTIE_GITHUB_TOKEN: ${{ secrets.SORTIE_GITHUB_TOKEN }}
SORTIE_GITHUB_PROJECT: sortie-ai/sortie-test
steps:
- name: Verify GitHub secrets are configured
run: |
missing=()
[ -z "$SORTIE_GITHUB_TOKEN" ] && missing+=("SORTIE_GITHUB_TOKEN")
if [ ${#missing[@]} -ne 0 ]; then
echo "::error::Missing required secrets: ${missing[*]}"
echo "Configure these in Settings → Secrets and variables → Actions"
exit 1
fi
echo "✓ All GitHub secrets are present"
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Run integration tests
run: go test -race -count=1 -v -run 'Integration' ./internal/tracker/github/...
test-e2e-github:
name: "E2E: GitHub orchestrator"
needs: [lint, test-unit]
runs-on: ubuntu-latest
env:
SORTIE_GITHUB_E2E: "1"
SORTIE_GITHUB_TOKEN: ${{ secrets.SORTIE_GITHUB_TOKEN }}
SORTIE_GITHUB_PROJECT: sortie-ai/sortie-test
steps:
- name: Verify GitHub token secret is configured
run: |
missing=()
[ -z "$SORTIE_GITHUB_TOKEN" ] && missing+=("SORTIE_GITHUB_TOKEN")
if [ ${#missing[@]} -ne 0 ]; then
echo "::error::Missing required secrets: ${missing[*]}"
echo "Configure SORTIE_GITHUB_TOKEN in Settings → Secrets and variables → Actions"
exit 1
fi
echo "✓ GitHub E2E token secret is present"
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Run E2E tests
run: go test -race -count=1 -v -timeout 120s -run 'TestGitHubIntegration' ./internal/orchestrator/...
test-integration-claude:
name: "Integration: Claude Code"
needs: [lint, test-unit]
runs-on: ubuntu-latest
env:
SORTIE_CLAUDE_TEST: "1"
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- name: Verify Claude Code secrets are configured
run: |
missing=()
[ -z "$ANTHROPIC_API_KEY" ] && missing+=("ANTHROPIC_API_KEY")
if [ ${#missing[@]} -ne 0 ]; then
echo "::error::Missing required secrets: ${missing[*]}"
echo "Configure these in Settings → Secrets and variables → Actions"
exit 1
fi
echo "✓ All Claude Code secrets are present"
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "lts/*"
- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code
- name: Run integration tests
run: go test -race -count=1 -v -timeout 300s -run 'Integration' ./internal/agent/claude/...
test-integration-copilot:
name: "Integration: GitHub Copilot CLI"
needs: [lint, test-unit]
runs-on: ubuntu-latest
env:
SORTIE_COPILOT_TEST: "1"
GH_TOKEN: ${{ secrets.SORTIE_COPILOT_TOKEN }}
steps:
- name: Verify Copilot CLI secrets are configured
run: |
missing=()
[ -z "$GH_TOKEN" ] && missing+=("SORTIE_COPILOT_TOKEN")
if [ ${#missing[@]} -ne 0 ]; then
echo "::error::Missing required secrets: ${missing[*]}"
echo "Configure these in Settings → Secrets and variables → Actions"
exit 1
fi
echo "✓ All Copilot CLI secrets are present"
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "22"
- name: Install GitHub Copilot CLI
run: npm install -g @github/copilot
- name: Run integration tests
run: go test -race -count=1 -v -timeout 300s -run 'Integration' ./internal/agent/copilot/...
release:
name: Tag & Release
needs:
- lint
- test-unit
- test-integration-jira
- test-integration-github
- test-e2e-github
- test-integration-claude
- test-integration-copilot
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Install Syft
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
- name: Create and push tag
if: ${{ !inputs.dry_run }}
run: |
TAG='${{ inputs.version }}'
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "Release ${TAG}"
git push origin "$TAG"
echo "### Tagged \`${TAG}\`" >> "$GITHUB_STEP_SUMMARY"
- name: Create local tag (dry run)
if: ${{ inputs.dry_run }}
run: |
TAG='${{ inputs.version }}'
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "Dry run ${TAG}"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with:
distribution: goreleaser
version: "~> v2"
args: release ${{ inputs.dry_run && '--skip=publish --skip=announce' || '' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
- name: Release summary
if: ${{ !inputs.dry_run }}
run: |
echo "### Release ${{ inputs.version }} published" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**Artifacts:**" >> "$GITHUB_STEP_SUMMARY"
echo "- \`sortie_${{ inputs.version }}_linux_amd64.tar.gz\`" >> "$GITHUB_STEP_SUMMARY"
echo "- \`sortie_${{ inputs.version }}_linux_arm64.tar.gz\`" >> "$GITHUB_STEP_SUMMARY"
echo "- \`sortie_${{ inputs.version }}_darwin_amd64.tar.gz\`" >> "$GITHUB_STEP_SUMMARY"
echo "- \`sortie_${{ inputs.version }}_darwin_arm64.tar.gz\`" >> "$GITHUB_STEP_SUMMARY"
echo "- \`sortie_${{ inputs.version }}_windows_amd64.zip\`" >> "$GITHUB_STEP_SUMMARY"
echo "- \`sortie_${{ inputs.version }}_windows_arm64.zip\`" >> "$GITHUB_STEP_SUMMARY"
echo "- \`checksums.txt\`" >> "$GITHUB_STEP_SUMMARY"
echo "- SBOM manifests (\`*.sbom.json\`)" >> "$GITHUB_STEP_SUMMARY"
- name: Dry run summary
if: ${{ inputs.dry_run }}
run: |
echo "### Dry run complete for ${{ inputs.version }}" >> "$GITHUB_STEP_SUMMARY"
echo "No tag was created and no release was published." >> "$GITHUB_STEP_SUMMARY"