From 06db039a034c8769e8f7c312d409ab875bf005bf Mon Sep 17 00:00:00 2001 From: Nanaloveyuki Date: Fri, 17 Jul 2026 21:26:59 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20release=20version=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Agent.md | 26 ++++++ scripts/update-version.ps1 | 184 +++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 Agent.md create mode 100644 scripts/update-version.ps1 diff --git a/Agent.md b/Agent.md new file mode 100644 index 0000000..8d511f5 --- /dev/null +++ b/Agent.md @@ -0,0 +1,26 @@ +# Agent Notes + +## Version Releases + +Do not update package versions and installation snippets by hand. Run the +repository release helper from the root instead: + +```powershell +.\scripts\update-version.ps1 +``` + +For a normal patch release, the script updates `moon.mod`, all current +installation examples, creates the next `docs/changes/().md`, +and adds it to `docs/changes/index.md`. Review and replace any generated `TODO` +entries in the release note before committing. + +For a major or minor package-version change, supply the public release number +explicitly so it is not inferred: + +```powershell +.\scripts\update-version.ps1 -ReleaseVersion +``` + +Use `-WhatIf` to preview the target files. Pass `-Change`, `-Verification`, +and `-Note` to populate release-note sections directly. Validate the edited +repository before committing with a `gitmoji + short desc` message. diff --git a/scripts/update-version.ps1 b/scripts/update-version.ps1 new file mode 100644 index 0000000..05c8678 --- /dev/null +++ b/scripts/update-version.ps1 @@ -0,0 +1,184 @@ +<# +.SYNOPSIS +Updates BitLogger's package version, installation snippets, and release-note entry. + +.DESCRIPTION +For patch releases, the public release version is inferred from the latest +changes entry. For example, after 1.1.2(0.7.2), running: + + .\scripts\update-version.ps1 0.7.3 + +creates 1.1.3(0.7.3). A major or minor package-version change requires an +explicit -ReleaseVersion so the public release sequence is never guessed. + +The generated release note uses supplied -Change, -Verification, and -Note +entries. Without them it contains TODO markers that must be completed before +the release is committed. +#> +[CmdletBinding(SupportsShouldProcess)] +param( + [Parameter(Mandatory, Position = 0)] + [ValidatePattern('^\d+\.\d+\.\d+$')] + [string]$Version, + + [ValidatePattern('^\d+\.\d+\.\d+$')] + [string]$ReleaseVersion, + + [string[]]$Change, + [string[]]$Verification, + [string[]]$Note +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Read-TextFile([string]$Path) { + [System.IO.File]::ReadAllText($Path) +} + +function Write-TextFile([string]$Path, [string]$Content) { + $encoding = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($Path, $Content, $encoding) +} + +function Replace-ExactlyOnce( + [string]$Content, + [string]$Pattern, + [string]$Replacement, + [string]$Path +) { + $matches = [regex]::Matches($Content, $Pattern) + if ($matches.Count -ne 1) { + throw "Expected exactly one match for '$Pattern' in $Path, found $($matches.Count)." + } + [regex]::Replace($Content, $Pattern, $Replacement, 1) +} + +function Format-BulletList([string[]]$Items, [string]$Fallback) { + $entries = if ($null -eq $Items -or $Items.Count -eq 0) { + @($Fallback) + } else { + $Items + } + ($entries | ForEach-Object { "- $_" }) -join [Environment]::NewLine +} + +$repoRoot = Split-Path -Parent $PSScriptRoot +$moonModPath = Join-Path $repoRoot 'moon.mod' +$changesIndexPath = Join-Path $repoRoot 'docs/changes/index.md' + +foreach ($path in @($moonModPath, $changesIndexPath)) { + if (-not (Test-Path -LiteralPath $path)) { + throw "Required repository file is missing: $path" + } +} + +$moonMod = Read-TextFile $moonModPath +$currentVersionMatch = [regex]::Match($moonMod, '(?m)^version\s*=\s*"(?\d+\.\d+\.\d+)"\s*$') +if (-not $currentVersionMatch.Success) { + throw "Could not read the package version from $moonModPath." +} +$currentVersion = [version]$currentVersionMatch.Groups['version'].Value +$targetVersion = [version]$Version +if ($targetVersion -le $currentVersion) { + throw "Target version $Version must be greater than the current version $currentVersion." +} + +$changesIndex = Read-TextFile $changesIndexPath +$latestReleaseMatch = [regex]::Match( + $changesIndex, + '(?m)^- \[(?\d+\.\d+\.\d+)\]\(\./(?\d+\.\d+\.\d+\(\d+\.\d+\.\d+\)\.md)\)\s*$' +) +if (-not $latestReleaseMatch.Success) { + throw "Could not read the latest release entry from $changesIndexPath." +} + +$latestReleaseVersion = [version]$latestReleaseMatch.Groups['release'].Value +$latestPackageVersionMatch = [regex]::Match( + $latestReleaseMatch.Groups['file'].Value, + '\((?\d+\.\d+\.\d+)\)\.md$' +) +if (-not $latestPackageVersionMatch.Success -or $latestPackageVersionMatch.Groups['version'].Value -ne $currentVersion.ToString()) { + throw 'The latest changes entry must describe the current moon.mod package version before starting a new release.' +} +if ([string]::IsNullOrWhiteSpace($ReleaseVersion)) { + if ($targetVersion.Major -ne $currentVersion.Major -or $targetVersion.Minor -ne $currentVersion.Minor) { + throw 'A major or minor package-version change requires -ReleaseVersion, for example: -ReleaseVersion 1.2.0.' + } + $ReleaseVersion = "$($latestReleaseVersion.Major).$($latestReleaseVersion.Minor).$($targetVersion.Build)" +} + +$releaseFileName = "$ReleaseVersion($Version).md" +$releasePath = Join-Path $repoRoot "docs/changes/$releaseFileName" +if (Test-Path -LiteralPath $releasePath) { + throw "Release note already exists: $releasePath" +} +if ($changesIndex.Contains("./$releaseFileName")) { + throw "Release index already contains: $releaseFileName" +} + +$updatedMoonMod = Replace-ExactlyOnce ` + $moonMod ` + '(?m)^version\s*=\s*"\d+\.\d+\.\d+"\s*$' ` + "version = `"$Version`"" ` + $moonModPath + +$installFiles = @( + 'README.md', + 'docs/README-en.md', + 'docs/examples/console.md', + 'docs/zh/examples/console.md' +) +$updatedFiles = @{} +foreach ($relativePath in $installFiles) { + $path = Join-Path $repoRoot $relativePath + if (-not (Test-Path -LiteralPath $path)) { + throw "Required installation example is missing: $path" + } + $content = Read-TextFile $path + $updatedFiles[$path] = Replace-ExactlyOnce ` + $content ` + 'Nanaloveyuki/BitLogger@\d+\.\d+\.\d+' ` + "Nanaloveyuki/BitLogger@$Version" ` + $path +} + +$updatedChangesIndex = $changesIndex.Replace( + $latestReleaseMatch.Value, + "- [$ReleaseVersion](./$releaseFileName)$([Environment]::NewLine)$($latestReleaseMatch.Value)" +) + +$releaseNote = @" +## BitLogger Update Changes + +version $ReleaseVersion($Version) + +### Changes + +$(Format-BulletList $Change 'TODO: summarize the user-visible changes in this release.') + +### Verification + +$(Format-BulletList $Verification 'TODO: record the commands and targets verified for this release.') + +### Notes + +$(Format-BulletList $Note 'TODO: record release limitations or compatibility notes when applicable.') +"@ + +$writeTargets = @($moonModPath, $changesIndexPath, $releasePath) + $updatedFiles.Keys +if ($PSCmdlet.ShouldProcess(($writeTargets -join ', '), "update to $Version")) { + Write-TextFile $moonModPath $updatedMoonMod + foreach ($path in $updatedFiles.Keys) { + Write-TextFile $path $updatedFiles[$path] + } + Write-TextFile $changesIndexPath $updatedChangesIndex + Write-TextFile $releasePath $releaseNote +} + +if ($WhatIfPreference) { + Write-Output "Previewed release $ReleaseVersion($Version)." +} else { + Write-Output "Prepared release $ReleaseVersion($Version)." +} +Write-Output "Release notes: $releasePath"