Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/workflows/build-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:

- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0 # GitVersion.MsBuild needs full history

- name: Setup .NET Core
uses: actions/setup-dotnet@v5
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v6
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
fetch-depth: 0 # GitVersion.MsBuild needs full history

# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
Expand Down
120 changes: 120 additions & 0 deletions .github/workflows/finalize-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Finalize Release

# Runs automatically when a release PR is merged into main.
# Creates the git tag, GitHub Release, and opens a back-merge PR to develop.

on:
pull_request:
types: [closed]
branches: [main]

jobs:
finalize-release:
name: Tag, Release, and Back-merge
# Only run when a release PR is actually merged (not just closed)
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout main
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0
token: ${{ secrets.RELEASE_PAT }}

- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Extract release info from branch name
id: release
run: |
BRANCH="${{ github.event.pull_request.head.ref }}"
# Branch name is release/vX.Y.Z or release/vX.Y.Z-beta
TAG="${BRANCH#release/}"
SEMVER="${TAG#v}"

# Determine if this is a pre-release
if echo "$SEMVER" | grep -q '-'; then
PRERELEASE="true"
else
PRERELEASE="false"
fi

echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "semver=${SEMVER}" >> $GITHUB_OUTPUT
echo "prerelease=${PRERELEASE}" >> $GITHUB_OUTPUT

- name: Check if tag already exists
run: |
if git rev-parse "${{ steps.release.outputs.tag }}" >/dev/null 2>&1; then
echo "::error::Tag ${{ steps.release.outputs.tag }} already exists. Skipping."
exit 1
fi

- name: Create annotated tag
run: |
TAG="${{ steps.release.outputs.tag }}"
git tag -a "$TAG" -m "Release $TAG"
git push origin "$TAG"
echo "Created and pushed tag: $TAG"

- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
TAG: ${{ steps.release.outputs.tag }}
SEMVER: ${{ steps.release.outputs.semver }}
PRERELEASE: ${{ steps.release.outputs.prerelease }}
run: |
PRERELEASE_FLAG=""
if [ "$PRERELEASE" = "true" ]; then
PRERELEASE_FLAG="--prerelease"
fi

gh release create "$TAG" \
--title "$TAG" \
--generate-notes \
$PRERELEASE_FLAG

- name: Delete release branch
run: |
BRANCH="${{ github.event.pull_request.head.ref }}"
git push origin --delete "$BRANCH" || true
echo "Deleted branch: $BRANCH"

- name: Create back-merge PR (main → develop)
env:
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
TAG: ${{ steps.release.outputs.tag }}
run: |
BACKMERGE_BRANCH="backmerge/${TAG}"

# Create a branch from main for the back-merge
git checkout -b "$BACKMERGE_BRANCH" main
git push origin "$BACKMERGE_BRANCH"

# Create the PR
gh pr create \
--base develop \
--head "$BACKMERGE_BRANCH" \
--title "Back-merge ${TAG} from main into develop" \
--body "Automatic back-merge after release ${TAG}. Merge this to keep \`develop\` in sync with \`main\`."

- name: Summary
run: |
echo "## Release Finalized :rocket:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Tag:** ${{ steps.release.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** ${{ steps.release.outputs.semver }}" >> $GITHUB_STEP_SUMMARY
echo "- **Pre-release:** ${{ steps.release.outputs.prerelease }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### What happened" >> $GITHUB_STEP_SUMMARY
echo "1. ✅ Tag \`${{ steps.release.outputs.tag }}\` created" >> $GITHUB_STEP_SUMMARY
echo "2. ✅ GitHub Release created with auto-generated notes" >> $GITHUB_STEP_SUMMARY
echo "3. ✅ Publish workflow triggered (NuGet)" >> $GITHUB_STEP_SUMMARY
echo "4. ✅ Back-merge PR opened to develop" >> $GITHUB_STEP_SUMMARY
2 changes: 2 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0 # GitVersion.MsBuild needs full history

- name: Setup .NET Core
uses: actions/setup-dotnet@v5
Expand Down
208 changes: 208 additions & 0 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
name: Prepare Release

# Creates a release PR from develop → main.
# Maintainers trigger this from Actions → Prepare Release → Run workflow.
# After reviewing, merge the PR — the "Finalize Release" workflow handles the rest.

on:
workflow_dispatch:
inputs:
release_type:
description: 'Release type (controls the pre-release label on main)'
required: true
type: choice
options:
- beta
- rc
- stable
default: 'beta'
version_override:
description: 'Version override (optional, e.g., 2.0.0). Leave blank to let GitVersion calculate it.'
required: false
type: string

# Prevent two prepare-release runs from picking the same version number
concurrency:
group: prepare-release
cancel-in-progress: false

jobs:
prepare-release:
name: Create Release PR
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout develop
uses: actions/checkout@v6
with:
ref: develop
fetch-depth: 0
token: ${{ secrets.RELEASE_PAT }}

- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v4.5.0
with:
versionSpec: '6.x'

- name: Determine Version
uses: gittools/actions/gitversion/execute@v4.5.0
with:
useConfigFile: true
id: gitversion

- name: Compute release version
id: version
run: |
if [ -n "${{ github.event.inputs.version_override }}" ]; then
VERSION="${{ github.event.inputs.version_override }}"
else
VERSION="${{ steps.gitversion.outputs.MajorMinorPatch }}"
fi

RELEASE_TYPE="${{ github.event.inputs.release_type }}"

if [ "$RELEASE_TYPE" = "stable" ]; then
SEMVER="${VERSION}"
TAG="v${VERSION}"
else
# Find the latest existing tag for this version + release type
# and increment the pre-release number.
# --sort=-v:refname gives correct numeric ordering (e.g., .9 before .10)
LATEST_TAG=$(git tag -l "v${VERSION}-${RELEASE_TYPE}.*" --sort=-v:refname | head -1)

if [ -z "$LATEST_TAG" ]; then
NEXT_NUM=1
else
# Extract the trailing number: v2.0.0-beta.217 → 217
CURRENT_NUM="${LATEST_TAG##*.}"
NEXT_NUM=$((CURRENT_NUM + 1))
fi

SEMVER="${VERSION}-${RELEASE_TYPE}.${NEXT_NUM}"
TAG="v${SEMVER}"
fi

echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "semver=${SEMVER}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "release_type=${RELEASE_TYPE}" >> $GITHUB_OUTPUT

echo "::notice::Computed version: ${SEMVER} (tag: ${TAG})"

- name: Check for conflicts
run: |
TAG="${{ steps.version.outputs.tag }}"

# Ensure tag does not already exist
if git rev-parse "${TAG}" >/dev/null 2>&1; then
echo "::error::Tag ${TAG} already exists."
echo "::error::Choose a different version or delete the existing tag first."
exit 1
fi

# Ensure no release branch or open PR already targets this version
BRANCH="release/${TAG}"
if git ls-remote --heads origin "${BRANCH}" | grep -q .; then
echo "::error::Branch ${BRANCH} already exists on the remote."
echo "::error::Delete it first or choose a different version."
exit 1
fi

- name: Create release branch
run: |
BRANCH="release/${{ steps.version.outputs.tag }}"
git checkout -b "$BRANCH"
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
id: branch

- name: Update GitVersion.yml label for release type
run: |
RELEASE_TYPE="${{ steps.version.outputs.release_type }}"

if [ "$RELEASE_TYPE" = "stable" ]; then
NEW_LABEL="''"
else
NEW_LABEL="${RELEASE_TYPE}"
fi

# Update the label line that follows the main branch regex.
sed -i "/regex: \^main/,/^ [a-z]/{s/^ label: .*/ label: ${NEW_LABEL}/}" GitVersion.yml

echo "Updated GitVersion.yml main label to: '${NEW_LABEL}'"
grep -A 5 "^ main:" GitVersion.yml

- name: Commit label change
run: |
if git diff --quiet GitVersion.yml; then
echo "No label change needed"
else
git add GitVersion.yml
git commit -m "Set release label to '${{ steps.version.outputs.release_type }}' for ${{ steps.version.outputs.tag }}"
fi

- name: Push release branch
run: |
BRANCH="release/${{ steps.version.outputs.tag }}"
git push origin "$BRANCH"

- name: Create Pull Request
env:
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
SEMVER: ${{ steps.version.outputs.semver }}
TAG: ${{ steps.version.outputs.tag }}
RELEASE_TYPE: ${{ steps.version.outputs.release_type }}
run: |
BRANCH="release/${TAG}"

if [ "$RELEASE_TYPE" = "stable" ]; then
PRERELEASE_NOTE=""
else
PRERELEASE_NOTE="This is a **${RELEASE_TYPE}** pre-release."
fi

cat > /tmp/pr_body.md << EOF
## Release ${TAG}

${PRERELEASE_NOTE}

**Version:** \`${SEMVER}\`
**NuGet Package:** \`Terminal.Gui ${SEMVER}\`

### What happens when this PR is merged

1. ✅ The **Finalize Release** workflow will automatically create tag \`${TAG}\`
2. ✅ The **Publish** workflow will build and push to [NuGet.org](https://www.nuget.org/packages/Terminal.Gui)
3. ✅ A **GitHub Release** will be created with auto-generated notes
4. ✅ A **back-merge PR** from \`main\` → \`develop\` will be opened

### Checklist

- [ ] CI passes on this PR
- [ ] Version looks correct: \`${SEMVER}\`
- [ ] Release notes reviewed (will be auto-generated on merge)
EOF

gh pr create \
--base main \
--head "$BRANCH" \
--title "Release ${TAG}" \
--body-file /tmp/pr_body.md

- name: Summary
run: |
echo "## Release PR Created :rocket:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** ${{ steps.version.outputs.semver }}" >> $GITHUB_STEP_SUMMARY
echo "- **Tag:** ${{ steps.version.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "- **Type:** ${{ steps.version.outputs.release_type }}" >> $GITHUB_STEP_SUMMARY
echo "- **Branch:** release/${{ steps.version.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Review and merge the PR to trigger the release." >> $GITHUB_STEP_SUMMARY
6 changes: 4 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ jobs:
with:
fetch-depth: 0 # fetch-depth is needed for GitVersion https://github.com/GitTools/actions/blob/main/docs/cloning.md

# GitVersion.MsBuild (in Terminal.Gui.csproj) automatically computes the version
# from git history + GitVersion.yml during build. We still run the CLI here to
# capture the SemVer for use in subsequent steps (push, template dispatch).
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v4.5.0
with:
Expand All @@ -28,7 +31,6 @@ jobs:
uses: gittools/actions/gitversion/execute@v4.5.0
with:
useConfigFile: true
updateAssemblyInfo: true
id: gitversion # step id used as reference for output values

- name: Setup dotnet
Expand All @@ -41,7 +43,7 @@ jobs:
run: dotnet build Terminal.Gui/Terminal.Gui.csproj --no-incremental --nologo --force --configuration Release

- name: Pack Release Terminal.Gui ${{ steps.gitversion.outputs.SemVer }} for Nuget
run: dotnet pack Terminal.Gui/Terminal.Gui.csproj -c Release --include-symbols -p:Version='${{ steps.gitversion.outputs.SemVer }}'
run: dotnet pack Terminal.Gui/Terminal.Gui.csproj -c Release --include-symbols --no-build

# - name: Test to generate Code Coverage Report
# run: |
Expand Down
Loading
Loading