Files
mxaccessgw/scripts/check-client-versions.ps1
T
Joseph Doherty 7200023bcc 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).
2026-08-20 09:22:28 -04:00

211 lines
8.9 KiB
PowerShell

#!/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
}