fix(CLI-46): bump client versions past the already-published train + add PR-time drift guard
.NET/Go/Python/Rust clients (plus Contracts, which releases in lockstep
with the .NET Client) move 0.2.0 -> 0.2.1; Java moves 0.2.1 -> 0.2.2. This
starts the next release train the finding recommended, since post-publish
source changes (including the Python connect() event-loop fix, a8f86b5)
had been sitting at already-published version numbers for nine days with
nothing recording the divergence.
- Note the pypi 0.2.0 connect() defect and its supersession by 0.2.1 in
clients/python/README.md and docs/ClientPackaging.md.
- Add scripts/check-client-versions.ps1: a PR-time guard that finds each
client's latest publish tag (clients/go/vX.Y.Z for Go, the only client
with a git tag; the other four publish straight to a registry with no
tag, so the check reports "no tags found" and passes for them) and fails
if clients/<lang> changed since that tag while the version constant
still equals the tag's version. Wired into .gitea/workflows/ci.yml's
portable job next to the codegen freshness checks (needs fetch-depth: 0
for tag history).
- Mark remediation batch A Done in
archreview/2026-08-19/remediation/00-tracking.md.
Verified on macOS: dotnet build clients/dotnet/...slnx (pass), cargo check
--workspace (pass), python -m pytest (168 passed, 1 skipped), gradle test
with JDK 17 (pass, no generated-tree churn to revert this time), gofmt -l
+ go build ./... + go test ./... (clean), and
scripts/check-client-versions.ps1 via pwsh (pass; also verified it fails
correctly when a version constant is rolled back).
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Fails a PR that changes a client's source without bumping its version
|
||||
past the last thing actually published (CLI-46).
|
||||
|
||||
.DESCRIPTION
|
||||
CLI-39 (2026-07-12 cycle) added publish-time guards that refuse to
|
||||
re-publish an already-published name+version
|
||||
(scripts/pack-clients.ps1's Assert-GiteaPackageNotPublished,
|
||||
scripts/tag-go-module.ps1's ClientVersion-vs-tag check). Those guards
|
||||
only fire when someone actually runs a publish. CLI-46 (2026-08-19
|
||||
cycle) found the same hazard recurring silently in between publishes:
|
||||
library source (including a real bug fix, commit a8f86b5) shipped
|
||||
while the version constant still equalled the already-published
|
||||
number, so nothing in the tree recorded that the published artifact
|
||||
and the in-repo source had diverged. This script is the PR-time half
|
||||
of that guard: catch the drift as soon as it happens, not at the next
|
||||
publish.
|
||||
|
||||
For each client this repo ships, the script:
|
||||
1. Finds the latest "publish tag" for that client, per the tag
|
||||
naming convention its own publish tooling actually uses.
|
||||
2. If no such tag exists, says so and passes (nothing to compare
|
||||
against yet — this is expected for every client except Go; see
|
||||
below).
|
||||
3. If a tag exists, reads the version recorded in-source at that tag
|
||||
and the version recorded in-source now. If `clients/<lang>` has
|
||||
changed since the tag AND the version constant is unchanged, that
|
||||
is exactly the CLI-46 pattern: source moved, the published
|
||||
coordinate did not, and nothing says so. Fail.
|
||||
|
||||
Tag conventions, as implemented today (verified against
|
||||
scripts/pack-clients.ps1, scripts/tag-go-module.ps1, and `git tag -l`):
|
||||
- Go is the only client with a git tag at all: `tag-go-module.ps1`
|
||||
creates `clients/go/vX.Y.Z` (CLI-21/CLI-39 already keep this tag's
|
||||
version in lockstep with clients/go/mxgateway/version.go's
|
||||
ClientVersion at *tag time*; this script is what keeps it in
|
||||
lockstep *between* tags).
|
||||
- .NET, Python, Rust, and Java publish straight to a package
|
||||
registry via `pack-clients.ps1 -Publish` (NuGet/PyPI/cargo/Maven
|
||||
API pushes) with no corresponding git tag of any kind — there is
|
||||
nothing in this repo's history to diff against for those four.
|
||||
The script looks for a same-shaped tag anyway
|
||||
(`clients/<lang>/v*`) so it stays correct if that ever changes,
|
||||
but today it will report "no tags found" and pass for all four.
|
||||
|
||||
A client with no tag is not a violation of anything — it means no
|
||||
release train has ever tagged that client, so there is no "since the
|
||||
last publish" window to check. The check exists to catch drift once a
|
||||
baseline exists, not to demand one.
|
||||
|
||||
.EXAMPLE
|
||||
pwsh scripts/check-client-versions.ps1
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
Push-Location $repoRoot
|
||||
try {
|
||||
# One entry per official client. `TagGlob` is the tag naming pattern that
|
||||
# client's own publish tooling uses (see doc comment above); `VersionFile`
|
||||
# + `VersionPattern` locate the single version-of-record constant
|
||||
# docs/ClientPackaging.md's "Versioning" section names as authoritative
|
||||
# for that client; `SourceDir` is the diff scope (matches the literal
|
||||
# `clients/<lang>` scope described in the CLI-46 remediation task).
|
||||
$clients = @(
|
||||
[ordered]@{
|
||||
Name = 'dotnet'
|
||||
SourceDir = 'clients/dotnet'
|
||||
TagGlob = 'clients/dotnet/v*'
|
||||
VersionFile = 'clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj'
|
||||
VersionPattern = '<Version>([^<]+)</Version>'
|
||||
},
|
||||
[ordered]@{
|
||||
Name = 'go'
|
||||
SourceDir = 'clients/go'
|
||||
TagGlob = 'clients/go/v*'
|
||||
VersionFile = 'clients/go/mxgateway/version.go'
|
||||
VersionPattern = 'ClientVersion\s*=\s*"([^"]+)"'
|
||||
},
|
||||
[ordered]@{
|
||||
Name = 'python'
|
||||
SourceDir = 'clients/python'
|
||||
TagGlob = 'clients/python/v*'
|
||||
VersionFile = 'clients/python/src/zb_mom_ww_mxgateway/version.py'
|
||||
VersionPattern = '__version__\s*=\s*"([^"]+)"'
|
||||
},
|
||||
[ordered]@{
|
||||
Name = 'rust'
|
||||
SourceDir = 'clients/rust'
|
||||
TagGlob = 'clients/rust/v*'
|
||||
VersionFile = 'clients/rust/Cargo.toml'
|
||||
VersionPattern = 'version\s*=\s*"([^"]+)"'
|
||||
},
|
||||
[ordered]@{
|
||||
Name = 'java'
|
||||
SourceDir = 'clients/java'
|
||||
TagGlob = 'clients/java/v*'
|
||||
VersionFile = 'clients/java/build.gradle'
|
||||
VersionPattern = "version\s*=\s*'([^']+)'"
|
||||
}
|
||||
)
|
||||
|
||||
function Get-LatestTag {
|
||||
param([string]$Glob)
|
||||
|
||||
# --sort=-v:refname orders by version-aware descending semver, so the
|
||||
# first line is the newest tag matching the glob. Empty result means
|
||||
# no tag of this shape has ever been created for this client.
|
||||
$tags = git tag -l --sort='-v:refname' $Glob
|
||||
if (-not $tags) {
|
||||
return $null
|
||||
}
|
||||
return @($tags -split "`n")[0].Trim()
|
||||
}
|
||||
|
||||
function Get-VersionFromFile {
|
||||
param([string]$Path, [string]$Pattern)
|
||||
|
||||
if (-not (Test-Path $Path)) {
|
||||
throw "Version file not found: $Path"
|
||||
}
|
||||
$content = Get-Content -Path $Path -Raw
|
||||
$match = [regex]::Match($content, $Pattern)
|
||||
if (-not $match.Success) {
|
||||
throw "Could not find a version matching /$Pattern/ in $Path"
|
||||
}
|
||||
return $match.Groups[1].Value
|
||||
}
|
||||
|
||||
function Get-VersionFromFileAtRevision {
|
||||
param([string]$Revision, [string]$Path, [string]$Pattern)
|
||||
|
||||
$content = git show "${Revision}:${Path}" 2>$null
|
||||
if ($LASTEXITCODE -ne 0 -or -not $content) {
|
||||
# The version file did not exist (or was at a different path) at
|
||||
# that tag. Treat as "unknown" rather than failing the whole
|
||||
# check — the drift comparison below only needs "does the
|
||||
# current version still equal the tag's version", and an
|
||||
# unresolvable historical version can never equal the current
|
||||
# one, so this degrades to "assume it changed" (safe: it will
|
||||
# not mask a real drift).
|
||||
return $null
|
||||
}
|
||||
$match = [regex]::Match(($content -join "`n"), $Pattern)
|
||||
if (-not $match.Success) {
|
||||
return $null
|
||||
}
|
||||
return $match.Groups[1].Value
|
||||
}
|
||||
|
||||
$failures = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
foreach ($client in $clients) {
|
||||
Write-Host ''
|
||||
Write-Host "== $($client.Name) ==" -ForegroundColor Cyan
|
||||
|
||||
$tag = Get-LatestTag -Glob $client.TagGlob
|
||||
if (-not $tag) {
|
||||
Write-Host " No publish tags matching '$($client.TagGlob)' — nothing to compare against yet. Pass." -ForegroundColor Yellow
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host " Latest publish tag: $tag"
|
||||
|
||||
$currentVersion = Get-VersionFromFile -Path $client.VersionFile -Pattern $client.VersionPattern
|
||||
Write-Host " Current version constant ($($client.VersionFile)): $currentVersion"
|
||||
|
||||
# Any diff at all in the client's source tree since its last publish
|
||||
# tag — matches the CLI-46 remediation task's literal check.
|
||||
git diff --quiet $tag -- $client.SourceDir
|
||||
$sourceChanged = ($LASTEXITCODE -ne 0)
|
||||
|
||||
if (-not $sourceChanged) {
|
||||
Write-Host " No changes under $($client.SourceDir) since $tag. Pass." -ForegroundColor Green
|
||||
continue
|
||||
}
|
||||
|
||||
$tagVersion = Get-VersionFromFileAtRevision -Revision $tag -Path $client.VersionFile -Pattern $client.VersionPattern
|
||||
Write-Host " Source under $($client.SourceDir) changed since $tag (tag's version constant: $(if ($tagVersion) { $tagVersion } else { '<unresolvable>' }))."
|
||||
|
||||
if ($currentVersion -eq $tagVersion) {
|
||||
$failures.Add("$($client.Name): source changed since publish tag '$tag' but $($client.VersionFile) still reads '$currentVersion' (the version at '$tag'). Bump the version before another publish (CLI-46).")
|
||||
Write-Host " FAIL: version constant unchanged since the publish tag." -ForegroundColor Red
|
||||
}
|
||||
else {
|
||||
Write-Host " Version constant already bumped ('$tagVersion' -> '$currentVersion') since $tag. Pass." -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
if ($failures.Count -gt 0) {
|
||||
Write-Host 'Client version freshness check FAILED:' -ForegroundColor Red
|
||||
foreach ($failure in $failures) {
|
||||
Write-Host " - $failure" -ForegroundColor Red
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host 'Client version freshness check passed.' -ForegroundColor Green
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
Reference in New Issue
Block a user