Skip to content

Commit f1385de

Browse files
committed
ci(release): enforce daily release version sequence (2026.6.15.0-18FE)
1 parent 18fe60e commit f1385de

3 files changed

Lines changed: 177 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env pwsh
2+
[CmdletBinding()]
3+
param(
4+
[Parameter(Mandatory = $true)]
5+
[string] $Tag,
6+
[string] $RepoRoot = (Get-Location).Path
7+
)
8+
9+
$ErrorActionPreference = "Stop"
10+
11+
function Invoke-Git {
12+
param([Parameter(Mandatory = $true)][string[]] $Arguments)
13+
14+
$output = & git @Arguments
15+
if ($LASTEXITCODE -ne 0) {
16+
throw "git $($Arguments -join ' ') failed with exit code $LASTEXITCODE"
17+
}
18+
return @($output)
19+
}
20+
21+
function Get-ExpectedReleaseRevision {
22+
param(
23+
[string] $DateStamp,
24+
[string] $ExcludeTag
25+
)
26+
27+
$escapedDate = [regex]::Escape($DateStamp)
28+
$pattern = "^v$escapedDate\.(\d+)(-[A-Za-z0-9]{4})?$"
29+
$existingTags = @(Invoke-Git -Arguments @("tag", "--list", "v$DateStamp.*"))
30+
$highest = -1
31+
32+
foreach ($existingTag in $existingTags) {
33+
if ($existingTag -eq $ExcludeTag) {
34+
continue
35+
}
36+
if ($existingTag -match $pattern) {
37+
$value = [int] $Matches[1]
38+
if ($value -gt $highest) {
39+
$highest = $value
40+
}
41+
}
42+
}
43+
44+
return $highest + 1
45+
}
46+
47+
$repoRootPath = (Resolve-Path -LiteralPath $RepoRoot).Path
48+
Push-Location $repoRootPath
49+
try {
50+
if ($Tag -notmatch "^v(\d{4})\.(\d+)\.(\d+)\.(\d+)(-[A-Za-z0-9]{4})?$") {
51+
throw "Release tag must be vYYYY.M.D.N or vYYYY.M.D.N-XXXX, got '$Tag'."
52+
}
53+
54+
$dateStamp = "$($Matches[1]).$($Matches[2]).$($Matches[3])"
55+
$actualRevision = [int] $Matches[4]
56+
$expectedRevision = Get-ExpectedReleaseRevision -DateStamp $dateStamp -ExcludeTag $Tag
57+
58+
if ($actualRevision -ne $expectedRevision) {
59+
throw "Release tag $Tag uses revision $actualRevision, expected $expectedRevision for $dateStamp. Use .0 when no release exists for the day; otherwise increment the highest same-day release or prerelease revision by one."
60+
}
61+
62+
Write-Host "Release tag $Tag uses the expected same-day revision $expectedRevision."
63+
}
64+
finally {
65+
Pop-Location
66+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env pwsh
2+
[CmdletBinding()]
3+
param()
4+
5+
$ErrorActionPreference = "Stop"
6+
7+
$ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
8+
$AssertScript = Join-Path $ScriptRoot "Assert-ReleaseVersionSequence.ps1"
9+
10+
function Invoke-TestGit {
11+
param(
12+
[string] $RepoRoot,
13+
[string[]] $Arguments
14+
)
15+
16+
Push-Location $RepoRoot
17+
try {
18+
$output = & git @Arguments
19+
if ($LASTEXITCODE -ne 0) {
20+
throw "git $($Arguments -join ' ') failed with exit code $LASTEXITCODE"
21+
}
22+
return @($output)
23+
}
24+
finally {
25+
Pop-Location
26+
}
27+
}
28+
29+
function New-TestRepo {
30+
$root = Join-Path ([System.IO.Path]::GetTempPath()) ("handoff-release-sequence-" + [System.Guid]::NewGuid().ToString("N"))
31+
New-Item -ItemType Directory -Path $root | Out-Null
32+
Invoke-TestGit -RepoRoot $root -Arguments @("init", "-q", ".") | Out-Null
33+
Invoke-TestGit -RepoRoot $root -Arguments @("config", "user.name", "Handoff Tests") | Out-Null
34+
Invoke-TestGit -RepoRoot $root -Arguments @("config", "user.email", "handoff-tests@example.invalid") | Out-Null
35+
Set-Content -LiteralPath (Join-Path $root "sample.txt") -Value "base" -Encoding ASCII
36+
Invoke-TestGit -RepoRoot $root -Arguments @("add", ".") | Out-Null
37+
Invoke-TestGit -RepoRoot $root -Arguments @("commit", "-q", "-m", "initial") | Out-Null
38+
return $root
39+
}
40+
41+
function Assert-Passes {
42+
param(
43+
[string] $RepoRoot,
44+
[string] $Tag,
45+
[string] $Message
46+
)
47+
48+
& $AssertScript -RepoRoot $RepoRoot -Tag $Tag | Out-Host
49+
if ($LASTEXITCODE -ne 0) {
50+
throw $Message
51+
}
52+
}
53+
54+
function Assert-Fails {
55+
param(
56+
[string] $RepoRoot,
57+
[string] $Tag,
58+
[string] $Message
59+
)
60+
61+
$failed = $false
62+
try {
63+
& $AssertScript -RepoRoot $RepoRoot -Tag $Tag | Out-Host
64+
}
65+
catch {
66+
$failed = $true
67+
}
68+
69+
if (-not $failed) {
70+
throw $Message
71+
}
72+
}
73+
74+
$tempRoots = [System.Collections.Generic.List[string]]::new()
75+
try {
76+
$repo = New-TestRepo
77+
$tempRoots.Add($repo) | Out-Null
78+
Assert-Passes -RepoRoot $repo -Tag "v2026.6.15.0-beta" -Message "First same-day prerelease should be .0."
79+
Assert-Fails -RepoRoot $repo -Tag "v2026.6.15.1-beta" -Message ".1 should fail when no same-day release exists."
80+
81+
$repo = New-TestRepo
82+
$tempRoots.Add($repo) | Out-Null
83+
Invoke-TestGit -RepoRoot $repo -Arguments @("tag", "v2026.6.15.0") | Out-Null
84+
Assert-Passes -RepoRoot $repo -Tag "v2026.6.15.1-beta" -Message "Prerelease after same-day stable .0 should be .1."
85+
Assert-Fails -RepoRoot $repo -Tag "v2026.6.15.0-beta" -Message ".0-beta should fail after same-day stable .0."
86+
87+
$repo = New-TestRepo
88+
$tempRoots.Add($repo) | Out-Null
89+
Invoke-TestGit -RepoRoot $repo -Arguments @("tag", "v2026.6.15.0-beta") | Out-Null
90+
Invoke-TestGit -RepoRoot $repo -Arguments @("tag", "v2026.6.15.1") | Out-Null
91+
Assert-Passes -RepoRoot $repo -Tag "v2026.6.15.2-beta" -Message "Sequence should increment the highest same-day tag."
92+
93+
$repo = New-TestRepo
94+
$tempRoots.Add($repo) | Out-Null
95+
Invoke-TestGit -RepoRoot $repo -Arguments @("tag", "v2026.6.15.0-beta") | Out-Null
96+
Assert-Passes -RepoRoot $repo -Tag "v2026.6.15.0-beta" -Message "Rerunning the exact release tag should be allowed."
97+
98+
Write-Host "Release version sequence tests passed."
99+
}
100+
finally {
101+
foreach ($tempRoot in $tempRoots) {
102+
if (Test-Path -LiteralPath $tempRoot) {
103+
Remove-Item -LiteralPath $tempRoot -Recurse -Force
104+
}
105+
}
106+
}

.github/workflows/release.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
run: |
3535
./.github/scripts/Test-WorkflowSyntax.ps1
3636
./.github/scripts/Test-ResolveReleaseBaseTag.ps1
37+
./.github/scripts/Test-ReleaseVersionSequence.ps1
3738
3839
- name: Set up Go
3940
uses: actions/setup-go@v6
@@ -65,6 +66,10 @@ jobs:
6566
"is_pre=$($isPre.ToString().ToLower())" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
6667
Write-Host "tag=$tag version=$version is_pre=$isPre"
6768
69+
- name: Validate release version sequence
70+
shell: pwsh
71+
run: ./.github/scripts/Assert-ReleaseVersionSequence.ps1 -Tag '${{ steps.ver.outputs.tag }}'
72+
6873
- name: Fetch picotool
6974
shell: pwsh
7075
run: |

0 commit comments

Comments
 (0)