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
105 changes: 105 additions & 0 deletions .codex/scripts/bump-version.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
param(
[Parameter(Mandatory = $true)]
[ValidatePattern('^\d+\.\d+\.\d+$')]
[string]$Version,

[switch]$AllowEmptyChangelog
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$RepoRoot = Split-Path -Parent (Split-Path -Parent $ScriptRoot)

$ProjectPath = Join-Path $RepoRoot "Eclipse.csproj"
$ThunderstorePath = Join-Path $RepoRoot "thunderstore.toml"
$ChangelogPath = Join-Path $RepoRoot "CHANGELOG.md"

function Set-TextPreservingUtf8Bom {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Value
)

$Bytes = [System.IO.File]::ReadAllBytes($Path)
$HasUtf8Bom = $Bytes.Length -ge 3 -and $Bytes[0] -eq 0xEF -and $Bytes[1] -eq 0xBB -and $Bytes[2] -eq 0xBF
$Encoding = [System.Text.UTF8Encoding]::new($HasUtf8Bom)
[System.IO.File]::WriteAllText($Path, $Value, $Encoding)
}

function Update-FirstMatch {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Pattern,
[Parameter(Mandatory = $true)]
[string]$Replacement,
[Parameter(Mandatory = $true)]
[string]$Description
)

$Text = Get-Content -Raw -Path $Path
$Regex = [regex]::new($Pattern, [System.Text.RegularExpressions.RegexOptions]::Multiline)
$Match = $Regex.Match($Text)
if (-not $Match.Success) {
throw "Unable to update $Description in $Path."
}

$Updated = $Text.Substring(0, $Match.Index) + $Regex.Replace($Match.Value, $Replacement, 1) + $Text.Substring($Match.Index + $Match.Length)
Set-TextPreservingUtf8Bom -Path $Path -Value $Updated
}

function Update-Changelog {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Version,
[Parameter(Mandatory = $true)]
[bool]$AllowEmpty
)

$Text = Get-Content -Raw -Path $Path

$VersionHeadingPattern = '(?m)^`' + [regex]::Escape($Version) + '`$'
if ($Text -match $VersionHeadingPattern) {
throw "CHANGELOG.md already contains a $Version entry."
}

$UnreleasedPattern = '(?ms)^## Unreleased\s*(?<body>.*?)(?=^`[^`]+`\s*$|^## |\z)'
$Match = [regex]::Match($Text, $UnreleasedPattern)
if (-not $Match.Success) {
throw "CHANGELOG.md must contain an '## Unreleased' section before bumping."
}

$Body = $Match.Groups["body"].Value.Trim()
if (-not $AllowEmpty -and [string]::IsNullOrWhiteSpace($Body)) {
throw "CHANGELOG.md '## Unreleased' is empty. Use -AllowEmptyChangelog to bump anyway."
}

$ReleasedBody = if ([string]::IsNullOrWhiteSpace($Body)) { "- No user-facing changes recorded." } else { $Body }
$VersionHeading = '`' + $Version + '`'
$Replacement = "## Unreleased`r`n`r`n$VersionHeading`r`n$ReleasedBody`r`n`r`n"
$Updated = $Text.Substring(0, $Match.Index) + $Replacement + $Text.Substring($Match.Index + $Match.Length)
Set-TextPreservingUtf8Bom -Path $Path -Value $Updated
}

Update-FirstMatch `
-Path $ProjectPath `
-Pattern '<Version>[^<]+</Version>' `
-Replacement "<Version>$Version</Version>" `
-Description "project version"

Update-FirstMatch `
-Path $ThunderstorePath `
-Pattern '^versionNumber = "[^"]+"' `
-Replacement "versionNumber = `"$Version`"" `
-Description "Thunderstore version"

Update-Changelog -Path $ChangelogPath -Version $Version -AllowEmpty:$AllowEmptyChangelog.IsPresent

Write-Host "Updated Eclipse version metadata to $Version."
181 changes: 181 additions & 0 deletions .codex/scripts/release-hygiene.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$ScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$ReleaseNudgePath = Join-Path $ScriptRoot "release-nudge.ps1"
$BumpVersionPath = Join-Path $ScriptRoot "bump-version.ps1"

function Assert-Equal {
param(
[Parameter(Mandatory = $true)]
[string]$Actual,
[Parameter(Mandatory = $true)]
[string]$Expected,
[Parameter(Mandatory = $true)]
[string]$Message
)

if ($Actual -ne $Expected) {
throw "$Message Expected '$Expected', got '$Actual'."
}
}

function Assert-Match {
param(
[Parameter(Mandatory = $true)]
[string]$Text,
[Parameter(Mandatory = $true)]
[string]$Pattern,
[Parameter(Mandatory = $true)]
[string]$Message
)

if ($Text -notmatch $Pattern) {
throw "$Message Pattern '$Pattern' was not found."
}
}

function Invoke-Git {
param(
[Parameter(Mandatory = $true)]
[string[]]$Arguments,
[Parameter(Mandatory = $true)]
[string]$WorkingDirectory
)

Push-Location $WorkingDirectory
try {
& git -c core.autocrlf=false @Arguments | Out-String
if ($LASTEXITCODE -ne 0) {
throw "git $($Arguments -join ' ') failed in $WorkingDirectory"
}
}
finally {
Pop-Location
}
}

function New-FixtureRepo {
$FixtureRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("eclipse-release-hygiene-" + [guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $FixtureRoot | Out-Null
New-Item -ItemType Directory -Path (Join-Path $FixtureRoot ".codex/scripts") -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $FixtureRoot "Systems") -Force | Out-Null

Copy-Item -Path $ReleaseNudgePath -Destination (Join-Path $FixtureRoot ".codex/scripts/release-nudge.ps1")
Copy-Item -Path $BumpVersionPath -Destination (Join-Path $FixtureRoot ".codex/scripts/bump-version.ps1")

Set-Content -Path (Join-Path $FixtureRoot "Eclipse.csproj") -Value @"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.2.3</Version>
</PropertyGroup>
</Project>
"@ -NoNewline

Set-Content -Path (Join-Path $FixtureRoot "thunderstore.toml") -Value @"
[package]
name = "Eclipse"
versionNumber = "1.2.3"
"@ -NoNewline

Set-Content -Path (Join-Path $FixtureRoot "CHANGELOG.md") -Value @"
## Unreleased

- planned fix

`1.2.3`
- previous release
"@ -NoNewline

Set-Content -Path (Join-Path $FixtureRoot "Systems/FamiliarHealthChangeSystem.cs") -Value "class FamiliarHealthChangeSystem {}" -NoNewline

Invoke-Git -WorkingDirectory $FixtureRoot -Arguments @("init", "-b", "main") | Out-Null
Invoke-Git -WorkingDirectory $FixtureRoot -Arguments @("config", "user.email", "codex@example.invalid") | Out-Null
Invoke-Git -WorkingDirectory $FixtureRoot -Arguments @("config", "user.name", "Codex") | Out-Null
Invoke-Git -WorkingDirectory $FixtureRoot -Arguments @("add", ".") | Out-Null
Invoke-Git -WorkingDirectory $FixtureRoot -Arguments @("commit", "-m", "baseline") | Out-Null

return $FixtureRoot
}

function Test-BumpVersionUpdatesEclipseMetadata {
$FixtureRoot = New-FixtureRepo
try {
& pwsh -NoProfile -File (Join-Path $FixtureRoot ".codex/scripts/bump-version.ps1") -Version "1.2.4"
if ($LASTEXITCODE -ne 0) {
throw "bump-version.ps1 exited with $LASTEXITCODE"
}

$ProjectText = Get-Content -Raw -Path (Join-Path $FixtureRoot "Eclipse.csproj")
$ThunderstoreText = Get-Content -Raw -Path (Join-Path $FixtureRoot "thunderstore.toml")
$ChangelogText = Get-Content -Raw -Path (Join-Path $FixtureRoot "CHANGELOG.md")

Assert-Match -Text $ProjectText -Pattern '<Version>1\.2\.4</Version>' -Message "Project version was not updated."
Assert-Match -Text $ThunderstoreText -Pattern 'versionNumber = "1\.2\.4"' -Message "Thunderstore version was not updated."
Assert-Match -Text $ChangelogText -Pattern '(?m)^## Unreleased\s+`1\.2\.4`\s+- planned fix' -Message "Changelog release entry was not created."
}
finally {
Remove-Item -LiteralPath $FixtureRoot -Recurse -Force
}
}

function Test-ReleaseNudgeWarnOnlyFlagsUnreleasedNotes {
$FixtureRoot = New-FixtureRepo
try {
Set-Content -Path (Join-Path $FixtureRoot "Systems/FamiliarHealthChangeSystem.cs") -Value "class FamiliarHealthChangeSystem { void Changed() {} }" -NoNewline

$Output = & pwsh -NoProfile -File (Join-Path $FixtureRoot ".codex/scripts/release-nudge.ps1") -BaseRef "main" -WarnOnly 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
throw "release-nudge.ps1 -WarnOnly exited with $LASTEXITCODE"
}

Assert-Match -Text $Output -Pattern 'CHANGELOG\.md has Unreleased notes' -Message "Release nudge did not flag unreleased notes."
}
finally {
Remove-Item -LiteralPath $FixtureRoot -Recurse -Force
}
}

function Test-ReleaseNudgeBlocksWithoutWarnOnly {
$FixtureRoot = New-FixtureRepo
try {
Set-Content -Path (Join-Path $FixtureRoot "Systems/FamiliarHealthChangeSystem.cs") -Value "class FamiliarHealthChangeSystem { void Changed() {} }" -NoNewline

$Output = & pwsh -NoProfile -File (Join-Path $FixtureRoot ".codex/scripts/release-nudge.ps1") -BaseRef "main" 2>&1 | Out-String
Assert-Equal -Actual "$LASTEXITCODE" -Expected "1" -Message "Release nudge should block when nudges exist."
Assert-Match -Text $Output -Pattern 'Eclipse' -Message "Release nudge output should name Eclipse."
}
finally {
Remove-Item -LiteralPath $FixtureRoot -Recurse -Force
}
}

function Test-ReleaseNudgeUsesGitHubEventBeforeWhenBaseRefOmitted {
$FixtureRoot = New-FixtureRepo
$OriginalEventBefore = $env:GITHUB_EVENT_BEFORE
try {
$BeforeSha = (Invoke-Git -WorkingDirectory $FixtureRoot -Arguments @("rev-parse", "HEAD")).Trim()
Set-Content -Path (Join-Path $FixtureRoot "Systems/FamiliarHealthChangeSystem.cs") -Value "class FamiliarHealthChangeSystem { void Changed() {} }" -NoNewline
Invoke-Git -WorkingDirectory $FixtureRoot -Arguments @("add", ".") | Out-Null
Invoke-Git -WorkingDirectory $FixtureRoot -Arguments @("commit", "-m", "change system") | Out-Null

$env:GITHUB_EVENT_BEFORE = $BeforeSha
$Output = & pwsh -NoProfile -File (Join-Path $FixtureRoot ".codex/scripts/release-nudge.ps1") -WarnOnly 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
throw "release-nudge.ps1 -WarnOnly exited with $LASTEXITCODE"
}

Assert-Match -Text $Output -Pattern 'Eclipse CHANGELOG\.md has Unreleased notes' -Message "Release nudge did not use GITHUB_EVENT_BEFORE when BaseRef was omitted."
}
finally {
$env:GITHUB_EVENT_BEFORE = $OriginalEventBefore
Remove-Item -LiteralPath $FixtureRoot -Recurse -Force
}
}

Test-BumpVersionUpdatesEclipseMetadata
Test-ReleaseNudgeWarnOnlyFlagsUnreleasedNotes
Test-ReleaseNudgeBlocksWithoutWarnOnly
Test-ReleaseNudgeUsesGitHubEventBeforeWhenBaseRefOmitted

Write-Host "release-hygiene tests passed"
Loading
Loading