diff --git a/.github/skills/prepare-release/SKILL.md b/.github/skills/prepare-release/SKILL.md new file mode 100644 index 00000000..1c29e584 --- /dev/null +++ b/.github/skills/prepare-release/SKILL.md @@ -0,0 +1,149 @@ +--- +name: prepare-release +description: "Prepare a release for the Feature Management .NET SDK. Use when user mentions release preparation, version bump, creating merge PRs, preview release, or stable release for this project." +--- + +# Prepare Release + +This skill automates the release preparation workflow for the [Feature Management .NET SDK](https://github.com/microsoft/FeatureManagement-Dotnet) project. + +## When to Use This Skill + +Use this skill when you need to: +- Bump the package version for a new stable or preview release +- Create merge PRs to sync branches (main → release, preview → release) +- Prepare all the PRs needed before publishing a new release + +## Background + +### Repository Information +- **GitHub Repo**: https://github.com/microsoft/FeatureManagement-Dotnet +- **Packages** (all 3 are released together with the same version): + 1. `Microsoft.FeatureManagement` — Base package + 2. `Microsoft.FeatureManagement.AspNetCore` — ASP.NET Core package + 3. `Microsoft.FeatureManagement.Telemetry.ApplicationInsights` — Application Insights telemetry package + +### Branch Structure +- `main` – primary development branch for stable releases +- `preview` – development branch for preview releases +- `release/v{major}` – release branch (e.g., `release/v4`) + +### Version Files +The version is defined by ``, ``, ``, and optionally `` properties in **all three** `.csproj` files simultaneously: +1. `src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj` +2. `src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj` +3. `src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj` + +Each file contains a version block like: +```xml + + + 4 + 1 + 0 + +``` + +For preview versions, a `` line is also present: +```xml + + + 4 + 0 + 0 + -preview5 + +``` + +### Version Format +- **Stable**: `{major}.{minor}.{patch}` (e.g., `4.1.0`) +- **Preview**: `{major}.{minor}.{patch}-preview{N}` (e.g., `4.0.0-preview5`) + +## Quick Start + +Ask the user whether this is a **stable** or **preview** release, and what the **new version number** should be. Then follow the appropriate workflow below. + +--- + +### Workflow A: Stable Release + +#### Step 1: Version Bump PR + +Create a version bump PR targeting `main` by running the version bump script: + +```powershell +.\scripts\version-bump.ps1 +``` + +For example: `.\scripts\version-bump.ps1 4.2.0` + +The script will automatically: +1. Read the current version from the first `.csproj` file. +2. Create a new branch from `main` named `/version-bump-` (e.g., `linglingye/version-bump-4.2.0`). +3. Update ``, ``, `` in all three `.csproj` files, and remove `` if present. +4. Commit, push, and create a PR to `main` with title: `Version bump `. + +When the script prompts `Proceed? [y/N]`, confirm by entering `y`. + +**Sample PR**: https://github.com/microsoft/FeatureManagement-Dotnet/pull/540 + +#### Step 2: Merge Main to Release Branch + +After the version bump PR is merged, create a PR to merge `main` into the release branch by running: + +```powershell +.\scripts\merge-to-release.ps1 +``` + +For example: `.\scripts\merge-to-release.ps1 4.2.0` + +When the script prompts `Proceed? [y/N]`, confirm by entering `y`. + +> **Important**: Use "Create a merge commit" (not "Squash and merge") when merging this PR to preserve commit history. + +**Sample PR**: https://github.com/microsoft/FeatureManagement-Dotnet/pull/541 + +--- + +### Workflow B: Preview Release + +#### Step 1: Version Bump PR + +Create a version bump PR targeting `preview` by running the version bump script with the `-Preview` flag: + +```powershell +.\scripts\version-bump.ps1 -Preview +``` + +For example: `.\scripts\version-bump.ps1 4.0.0-preview4 -Preview` + +When the script prompts `Proceed? [y/N]`, confirm by entering `y`. + +**Sample PR**: https://github.com/microsoft/FeatureManagement-Dotnet/pull/476 + +#### Step 2: Merge Preview to Release Branch + +After the version bump PR is merged, create a PR to merge `preview` into the release branch by running: + +```powershell +.\scripts\merge-to-release.ps1 -Preview +``` + +For example: `.\scripts\merge-to-release.ps1 4.0.0-preview4 -Preview` + +When the script prompts `Proceed? [y/N]`, confirm by entering `y`. + +> **Important**: Use "Create a merge commit" (not "Squash and merge") when merging this PR to preserve commit history. + +**Sample PR**: https://github.com/microsoft/FeatureManagement-Dotnet/pull/477 + +--- + +## Review Checklist + +Each PR should be reviewed with the following checks: +- [ ] Version is updated consistently across all 3 `.csproj` files +- [ ] No unintended file changes are included +- [ ] Merge PRs use **merge commit** strategy (not squash) +- [ ] Branch names follow the naming conventions +- [ ] All CI checks pass diff --git a/scripts/merge-to-release.ps1 b/scripts/merge-to-release.ps1 new file mode 100644 index 00000000..1c46cbbe --- /dev/null +++ b/scripts/merge-to-release.ps1 @@ -0,0 +1,100 @@ +<# +.SYNOPSIS + Creates a PR to merge a development branch into its corresponding release branch. +.DESCRIPTION + Used after a version bump PR has been merged. Creates a PR from main (or preview) + into the appropriate release branch via the GitHub CLI (gh). +.PARAMETER Version + The version that was just bumped (used to determine major version and PR title) +.PARAMETER Preview + Merge preview -> release/v{major} instead of main -> release/v{major} +.EXAMPLE + .\scripts\merge-to-release.ps1 4.2.0 + # main -> release/v4 +.EXAMPLE + .\scripts\merge-to-release.ps1 4.0.0-preview5 -Preview + # preview -> release/v4 +.NOTES + Prerequisites: git and gh (GitHub CLI) must be installed and authenticated +#> + +param( + [Parameter(Mandatory = $true, Position = 0)] + [string]$Version, + + [switch]$Preview +) + +$ErrorActionPreference = "Stop" + +# ── Validate version format ────────────────────────────────────────────────── + +if ($Version -notmatch '^\d+\.\d+\.\d+(-preview\d+)?$') { + Write-Error "Invalid version format '$Version'. Expected: X.Y.Z or X.Y.Z-previewN" + exit 1 +} + +# ── Determine branches ─────────────────────────────────────────────────────── + +# Extract major version (e.g. "4" from "4.2.0" or "4.0.0-preview5") +$MajorVersion = ($Version -split '\.')[0] + +if ($Preview) { + $SourceBranch = "preview" + $TargetBranch = "release/v$MajorVersion" + $PrTitle = "Merge preview to release/v$MajorVersion" +} +else { + $SourceBranch = "main" + $TargetBranch = "release/v$MajorVersion" + $PrTitle = "Merge main to release/v$MajorVersion" +} + +Write-Host "-- Source branch : $SourceBranch" +Write-Host "-- Target branch : $TargetBranch" +Write-Host "-- PR title : $PrTitle" +Write-Host "" + +# ── Confirm with user ──────────────────────────────────────────────────────── + +$confirm = Read-Host "Proceed? [y/N]" +if ($confirm -notmatch '^[Yy]$') { + Write-Host "Aborted." + exit 0 +} +Write-Host "" + +# ── Resolve project directory ──────────────────────────────────────────────── + +$ProjectDir = Split-Path $PSScriptRoot -Parent +Push-Location $ProjectDir + +try { + # ── Fetch latest branches ──────────────────────────────────────────────── + + Write-Host "-- Fetching latest branches..." + git fetch origin $SourceBranch + git fetch origin $TargetBranch + + # ── Create PR ──────────────────────────────────────────────────────────── + + Write-Host "-- Creating pull request..." + $Body = @" +Merge ``$SourceBranch`` into ``$TargetBranch`` after version bump ``$Version``. + +> **Important**: Use **Create a merge commit** (not "Squash and merge") when merging this PR to preserve commit history. + +--- +*This PR was created automatically by ``scripts/merge-to-release.ps1``.* +"@ + + $PrUrl = gh pr create --base $TargetBranch --head $SourceBranch --title $PrTitle --body $Body + + Write-Host "" + Write-Host "-- Done! PR created: $PrUrl" + Write-Host "" + Write-Host "WARNING: Use 'Create a merge commit' (not 'Squash and merge') when merging this PR." +} +finally { + Pop-Location +} diff --git a/scripts/version-bump.ps1 b/scripts/version-bump.ps1 new file mode 100644 index 00000000..1e2b3564 --- /dev/null +++ b/scripts/version-bump.ps1 @@ -0,0 +1,192 @@ +<# +.SYNOPSIS + Automates the version bump workflow for Feature Management .NET SDK. +.DESCRIPTION + Updates MajorVersion, MinorVersion, PatchVersion, and PreviewVersion in all 3 package + .csproj files, creates a branch, commits, pushes, and opens a PR via the GitHub CLI (gh). +.PARAMETER NewVersion + The version to bump to (e.g. 4.2.0 or 4.0.0-preview5) +.PARAMETER Preview + Target the preview branch instead of main +.EXAMPLE + .\scripts\version-bump.ps1 4.2.0 + # stable release -> PR to main +.EXAMPLE + .\scripts\version-bump.ps1 4.0.0-preview5 -Preview + # preview release -> PR to preview +.NOTES + Prerequisites: git and gh (GitHub CLI) must be installed and authenticated +#> + +param( + [Parameter(Mandatory = $true, Position = 0)] + [string]$NewVersion, + + [switch]$Preview +) + +$ErrorActionPreference = "Stop" + +# ── Validate version format ────────────────────────────────────────────────── + +if ($NewVersion -notmatch '^\d+\.\d+\.\d+(-preview\d+)?$') { + Write-Error "Invalid version format '$NewVersion'. Expected: X.Y.Z or X.Y.Z-previewN" + exit 1 +} + +if ($NewVersion -match '-preview' -and -not $Preview) { + Write-Error "Version '$NewVersion' looks like a preview version. Did you forget -Preview?" + exit 1 +} + +# ── Parse new version ──────────────────────────────────────────────────────── + +if ($NewVersion -match '^(\d+)\.(\d+)\.(\d+)(-preview\d+)?$') { + $NewMajor = $Matches[1] + $NewMinor = $Matches[2] + $NewPatch = $Matches[3] + $NewPreview = if ($Matches[4]) { $Matches[4] } else { $null } +} + +# ── Resolve paths & context ────────────────────────────────────────────────── + +$ProjectDir = Split-Path $PSScriptRoot -Parent + +$CsprojRelPaths = @( + "src/Microsoft.FeatureManagement/Microsoft.FeatureManagement.csproj", + "src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj", + "src/Microsoft.FeatureManagement.Telemetry.ApplicationInsights/Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj" +) + +$CsprojFiles = $CsprojRelPaths | ForEach-Object { Join-Path $ProjectDir $_ } + +# Determine target branch +$TargetBranch = if ($Preview) { "preview" } else { "main" } + +# Get git username for branch naming +$GitUsername = git config user.name 2>$null +if (-not $GitUsername) { + Write-Error "Could not determine git user.name. Please set it with: git config user.name " + exit 1 +} +$BranchPrefix = ($GitUsername -split '\s+')[0].ToLower() +$BranchName = "$BranchPrefix/version-bump-$NewVersion" + +# ── Show plan ──────────────────────────────────────────────────────────────── + +Write-Host "-- New version : $NewVersion" +Write-Host "-- Target branch : $TargetBranch" +Write-Host "-- New branch : $BranchName" +Write-Host "" + +# ── Confirm with user ──────────────────────────────────────────────────────── + +$confirm = Read-Host "Proceed? [y/N]" +if ($confirm -notmatch '^[Yy]$') { + Write-Host "Aborted." + exit 0 +} +Write-Host "" + +# ── Create branch from target ──────────────────────────────────────────────── + +Push-Location $ProjectDir +try { + Write-Host "-- Fetching latest $TargetBranch..." + git fetch origin $TargetBranch + + Write-Host "-- Creating branch '$BranchName' from origin/$TargetBranch..." + git checkout -b $BranchName "origin/$TargetBranch" + + # ── Read current version ───────────────────────────────────────────────── + + $content = [System.IO.File]::ReadAllText($CsprojFiles[0]) + $curMajor = if ($content -match '(\d+)') { $Matches[1] } else { throw "Could not find MajorVersion" } + $curMinor = if ($content -match '(\d+)') { $Matches[1] } else { throw "Could not find MinorVersion" } + $curPatch = if ($content -match '(\d+)') { $Matches[1] } else { throw "Could not find PatchVersion" } + $curPreview = if ($content -match '([^<]+)') { $Matches[1] } else { "" } + $CurrentVersion = "$curMajor.$curMinor.$curPatch$curPreview" + + Write-Host "-- Current version : $CurrentVersion" + + if ($CurrentVersion -eq $NewVersion) { + throw "Current version is already $NewVersion. Nothing to do." + } + + # ── Update version in all .csproj files ────────────────────────────────── + + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + + foreach ($csproj in $CsprojFiles) { + $fileName = Split-Path $csproj -Leaf + Write-Host "-- Updating $fileName..." + $text = [System.IO.File]::ReadAllText($csproj) + + # Update MajorVersion, MinorVersion, PatchVersion + $text = $text -replace '\d+', "$NewMajor" + $text = $text -replace '\d+', "$NewMinor" + $text = $text -replace '\d+', "$NewPatch" + + # Handle PreviewVersion + if ($NewPreview) { + # Add or update PreviewVersion + if ($text -match '[^<]*') { + $text = $text -replace '[^<]*', "$NewPreview" + } else { + # Insert PreviewVersion after PatchVersion line + $text = $text -replace '(\d+)', "`$1`n $NewPreview" + } + } else { + # Remove PreviewVersion line if present (stable release) + $text = $text -replace '\s*[^<]*', '' + } + + [System.IO.File]::WriteAllText($csproj, $text, $utf8NoBom) + } + + # ── Verify changes ─────────────────────────────────────────────────────── + + Write-Host "-- Verifying updates..." + foreach ($csproj in $CsprojFiles) { + $text = [System.IO.File]::ReadAllText($csproj) + if ($text -notmatch "$NewMajor") { throw "MajorVersion not updated in $(Split-Path $csproj -Leaf)" } + if ($text -notmatch "$NewMinor") { throw "MinorVersion not updated in $(Split-Path $csproj -Leaf)" } + if ($text -notmatch "$NewPatch") { throw "PatchVersion not updated in $(Split-Path $csproj -Leaf)" } + if ($NewPreview -and $text -notmatch "$([regex]::Escape($NewPreview))") { + throw "PreviewVersion not updated in $(Split-Path $csproj -Leaf)" + } + } + Write-Host "-- All version files updated" + Write-Host "" + + # ── Commit, push, and create PR ────────────────────────────────────────── + + Write-Host "-- Committing changes..." + git add $CsprojRelPaths + git commit -m "Version bump $NewVersion" + + Write-Host "-- Pushing branch '$BranchName'..." + git push origin $BranchName + + Write-Host "-- Creating pull request..." + $Body = @" +Bump version from ``$CurrentVersion`` to ``$NewVersion``. + +### Changes +- Updated version properties in all 3 package .csproj files: + - ``Microsoft.FeatureManagement.csproj`` + - ``Microsoft.FeatureManagement.AspNetCore.csproj`` + - ``Microsoft.FeatureManagement.Telemetry.ApplicationInsights.csproj`` + +--- +*This PR was created automatically by ``scripts/version-bump.ps1``.* +"@ + + $PrUrl = gh pr create --base $TargetBranch --head $BranchName --title "Version bump $NewVersion" --body $Body + + Write-Host "" + Write-Host "-- Done! PR created: $PrUrl" +} +finally { + Pop-Location +}