Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
115 changes: 115 additions & 0 deletions .github/workflows/bump-major-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Bump main to next major preview

on:
workflow_dispatch:
inputs:
next_major:
description: 'Next major version (e.g., 10) for the new preview line on main'
required: true
type: string

permissions:
contents: write
pull-requests: write

jobs:
bump:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0

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

- name: Validate inputs
shell: pwsh
run: |
$next = '${{ inputs.next_major }}'
if ($next -notmatch '^\d+$') {
throw "next_major must be a single integer (got: '$next')"
}
$nextInt = [int]$next

$versionJson = Get-Content version.json -Raw | ConvertFrom-Json
$ver = $versionJson.version
if ($ver -notmatch '^(\d+)\.\d+-preview') {
throw "main's version is '$ver' but should be '<major>.<minor>-preview.{height}'"
}
$currentMajor = [int]$matches[1]
if ($nextInt -le $currentMajor) {
throw "next_major ($nextInt) must be greater than current major ($currentMajor)"
}

git fetch --tags --force 2>$null
$latestStable = git tag --list --sort=-v:refname |
Where-Object { $_ -match '^(\d+)\.\d+\.\d+$' } |
Select-Object -First 1
if ($latestStable) {
if ($latestStable -notmatch '^(\d+)\.') {
throw "Could not parse latest stable tag: '$latestStable'"
}
$latestStableMajor = [int]$matches[1]
$expectedMajor = $latestStableMajor + 1
if ($nextInt -ne $expectedMajor) {
throw "next_major ($nextInt) must be exactly one greater than the latest stable major ($latestStableMajor; tag '$latestStable'). Expected: $expectedMajor. Major versions must be incremented sequentially to avoid accidentally skipping a major."
}
Write-Host "OK: next_major ($nextInt) is exactly one greater than latest stable major ($latestStableMajor)."
} else {
Write-Host "No stable tags found; skipping sequential-major check."
}

$newVersion = "$next.0-preview.{height}"
Add-Content -Path $env:GITHUB_ENV -Value "NEW_VERSION=$newVersion"
Add-Content -Path $env:GITHUB_ENV -Value "NEXT_MAJOR=$next"
Add-Content -Path $env:GITHUB_ENV -Value "BUMP_BRANCH=bot/bump-major-$next"

- name: Create bump branch
run: git checkout -b "$BUMP_BRANCH"

- name: Bump version.json
shell: pwsh
run: |
$path = 'version.json'
$content = [System.IO.File]::ReadAllText($path)
$new = [regex]::Replace($content, '"version":\s*"[^"]+"', "`"version`": `"$env:NEW_VERSION`"")
[System.IO.File]::WriteAllText($path, $new)

- name: Commit and push
run: |
git add version.json
git commit -m "Bump main to $NEW_VERSION (next major preview line)"
git push -u origin "$BUMP_BRANCH"

- name: Open PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: pwsh
run: |
$body = @"
Bumps ``main`` from its current preview to ``$env:NEW_VERSION`` ahead of upcoming breaking changes for ``$env:NEXT_MAJOR.0``.

Merge this **before** any PR labeled ``breaking-change`` so the prereleases are correctly versioned as ``$env:NEXT_MAJOR.0.0-preview.N``.
"@
$url = gh pr create `
--base main `
--head "$env:BUMP_BRANCH" `
--title "Bump main to $env:NEW_VERSION (next major preview)" `
--body $body
Add-Content -Path $env:GITHUB_ENV -Value "PR_URL=$url"

- name: Summary
shell: pwsh
run: |
$summary = @"
## Major preview bump

- **PR**: $env:PR_URL
- Merge this before landing the first breaking-change PR.
"@
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $summary
2 changes: 1 addition & 1 deletion .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Build

on:
pull_request:
branches: [ main ]
branches: [ main, release/** ]

env:
configuration: Release
Expand Down
134 changes: 134 additions & 0 deletions .github/workflows/cut-major.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: Cut major release

on:
workflow_dispatch:
inputs:
major_version:
description: 'Major version to ship as stable (e.g., 10). Main must currently be on this major''s preview line.'
required: true
type: string
next_main_version:
description: 'Next preview version for main as major.minor (default: <major>.1). Use the next major if more breaking changes are queued.'
required: false
type: string

permissions:
contents: write
pull-requests: write

jobs:
cut:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

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

- name: Validate inputs
shell: pwsh
run: |
$major = '${{ inputs.major_version }}'
if ($major -notmatch '^\d+$') {
throw "major_version must be a single integer (got: '$major')"
}

$releaseBranch = "release/$major.x"
$stableVersion = "$major.0"

$mainVersionJson = git show "origin/main:version.json" | ConvertFrom-Json
$mainVer = $mainVersionJson.version
if ($mainVer -notmatch "^$major\.0-preview") {
throw "main's version is '$mainVer' but should be '$major.0-preview.{height}' to cut $major.0 stable. Run 'Bump main to next major preview' first if needed."
}

$existing = git ls-remote --heads origin "refs/heads/$releaseBranch"
if ($existing) {
throw "Branch '$releaseBranch' already exists. Cannot cut a new major release branch that exists."
}

$nextMain = '${{ inputs.next_main_version }}'
if (-not $nextMain) { $nextMain = "$major.1" }
if ($nextMain -notmatch '^(\d+)\.(\d+)$') {
throw "next_main_version must be 'major.minor' (got: '$nextMain')"
}
$nextPreview = "$nextMain-preview.{height}"
Comment thread
dwcullop marked this conversation as resolved.
Outdated

Add-Content -Path $env:GITHUB_ENV -Value "RELEASE_BRANCH=$releaseBranch"
Add-Content -Path $env:GITHUB_ENV -Value "STABLE_VERSION=$stableVersion"
Add-Content -Path $env:GITHUB_ENV -Value "NEXT_PREVIEW=$nextPreview"
Add-Content -Path $env:GITHUB_ENV -Value "NEXT_MAIN_VERSION=$nextMain"
Add-Content -Path $env:GITHUB_ENV -Value "BUMP_BRANCH=bot/bump-main-after-$stableVersion"

- name: Create release branch from main
run: |
git checkout main
git checkout -b "$RELEASE_BRANCH"

- name: Set stable version on release branch
shell: pwsh
run: |
$path = 'version.json'
$content = [System.IO.File]::ReadAllText($path)
$new = [regex]::Replace($content, '"version":\s*"[^"]+"', "`"version`": `"$env:STABLE_VERSION`"")
[System.IO.File]::WriteAllText($path, $new)

- name: Commit and push release branch
run: |
git add version.json
git commit -m "Cut $RELEASE_BRANCH at $STABLE_VERSION stable"
git push -u origin "$RELEASE_BRANCH"

- name: Create main bump branch
run: |
git checkout main
git checkout -b "$BUMP_BRANCH"

- name: Bump main to next preview
shell: pwsh
run: |
$path = 'version.json'
$content = [System.IO.File]::ReadAllText($path)
$new = [regex]::Replace($content, '"version":\s*"[^"]+"', "`"version`": `"$env:NEXT_PREVIEW`"")
[System.IO.File]::WriteAllText($path, $new)

- name: Commit and push main bump branch
run: |
git add version.json
git commit -m "Bump main to $NEXT_PREVIEW after cutting $RELEASE_BRANCH"
git push -u origin "$BUMP_BRANCH"

- name: Open main bump PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: pwsh
run: |
$body = @"
Companion PR to cutting ``$env:RELEASE_BRANCH``.

The release branch ``$env:RELEASE_BRANCH`` has been pushed and will publish ``$env:STABLE_VERSION.0`` stable.

This PR advances ``main`` to ``$env:NEXT_PREVIEW`` so subsequent PRs publish that preview line.
"@
$url = gh pr create `
--base main `
--head "$env:BUMP_BRANCH" `
--title "Bump main to $env:NEXT_PREVIEW after $env:RELEASE_BRANCH cut" `
--body $body
Add-Content -Path $env:GITHUB_ENV -Value "BUMP_PR_URL=$url"

- name: Summary
shell: pwsh
run: |
$summary = @"
## Major release cut

- **New release branch**: ``$env:RELEASE_BRANCH`` (will publish ``$env:STABLE_VERSION.0`` stable on push).
- **Main bump PR** (merge to advance main to next preview): $env:BUMP_PR_URL
"@
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $summary
76 changes: 76 additions & 0 deletions .github/workflows/pr-version-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: PR version check

on:
pull_request:
branches: [main, release/**]
types: [opened, edited, labeled, unlabeled, synchronize, reopened]

permissions:
contents: read
pull-requests: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout PR head
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0

- name: Validate version.json against PR labels and history
shell: pwsh
env:
BASE_REF: ${{ github.base_ref }}
LABELS_JSON: ${{ toJSON(github.event.pull_request.labels.*.name) }}
run: |
$labels = $env:LABELS_JSON | ConvertFrom-Json
$isBreaking = ($labels -contains 'breaking-change') -or ($labels -contains 'semver:major')

if (-not $isBreaking) {
Write-Host "PR is not labeled 'breaking-change' or 'semver:major'; no major-bump check needed."
exit 0
}

Write-Host "PR is labeled as breaking. Verifying main's version.json reflects a newer major than the latest stable release."

git fetch origin $env:BASE_REF --depth=1 2>$null
$baseVersionJson = git show "origin/${env:BASE_REF}:version.json" | ConvertFrom-Json
$baseVer = $baseVersionJson.version
if ($baseVer -notmatch '^(\d+)\.') {
Write-Error "::error file=version.json::Could not parse major version from base version.json: '$baseVer'"
exit 1
}
$baseMajor = [int]$matches[1]

git fetch --tags --force 2>$null
$latestStable = git tag --list --sort=-v:refname |
Where-Object { $_ -match '^(\d+)\.\d+\.\d+$' } |
Select-Object -First 1

if (-not $latestStable) {
Write-Host "No stable tags found; cannot determine latest released major. Skipping check."
exit 0
}

if ($latestStable -notmatch '^(\d+)\.') {
Write-Error "Could not parse latest stable tag: '$latestStable'"
exit 1
}
$latestMajor = [int]$matches[1]

Write-Host "Latest stable major: $latestMajor; current main major: $baseMajor"

$expectedMajor = $latestMajor + 1
if ($baseMajor -ne $expectedMajor) {
if ($baseMajor -le $latestMajor) {
$msg = "PR is labeled breaking-change but main's major ($baseMajor) is not greater than the latest stable release major ($latestMajor; tag '$latestStable'). Run the 'Bump main to next major preview' workflow with next_major=$expectedMajor first, then re-target this PR."
} else {
$msg = "PR is labeled breaking-change but main's major ($baseMajor) skips past major $expectedMajor. Main must be at exactly one greater than the latest stable major ($latestMajor + 1 = $expectedMajor). Major versions must be incremented sequentially. Reset main to $expectedMajor.0-preview.{height} before merging this PR."
}
Write-Error "::error file=version.json::$msg"
exit 1
}

Write-Host "OK: main major ($baseMajor) is exactly one greater than latest stable major ($latestMajor)."
Loading
Loading