From 46c9661ed8c55cbbcf2984ac82a26a8281607e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Wed, 23 Sep 2026 15:44:54 -0400 Subject: [PATCH] Validate release versions before publishing Fail version bumps when Cargo.lock cannot be refreshed, and use the bump script to check all release version inputs before packaging. Align remaining 0.7.0 metadata. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/release.yml | 6 ++ .../Devolutions.Psign/Devolutions.Psign.psd1 | 2 +- README.md | 4 +- nuget/tool/Devolutions.Psign.Tool.csproj | 2 +- scripts/bump-version.ps1 | 94 ++++++++++++++++--- 5 files changed, 90 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1eeb4b7..733d5cd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,6 +57,12 @@ jobs: with: fetch-depth: 0 + - name: Validate release version across manifests and lockfile + shell: pwsh + env: + RELEASE_VERSION: ${{ github.event.inputs.version }} + run: ./scripts/bump-version.ps1 -Version $env:RELEASE_VERSION -Check + - name: Resolve release info id: info shell: pwsh diff --git a/PowerShell/Devolutions.Psign/Devolutions.Psign.psd1 b/PowerShell/Devolutions.Psign/Devolutions.Psign.psd1 index 60ffce0..d1b3e75 100644 --- a/PowerShell/Devolutions.Psign/Devolutions.Psign.psd1 +++ b/PowerShell/Devolutions.Psign/Devolutions.Psign.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'Devolutions.Psign.psm1' - ModuleVersion = '0.6.3' + ModuleVersion = '0.7.0' GUID = 'e6e50e4b-bf25-4ed6-a343-49f904e79f8f' Author = 'Devolutions' CompanyName = 'Devolutions' diff --git a/README.md b/README.md index 9aab0be..e27da37 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,13 @@ dotnet tool run psign-tool -- --help Create local dotnet tool packages from prebuilt release artifacts: ```powershell -pwsh ./nuget/pack-psign-dotnet-tool.ps1 -Version 0.6.3 -ArtifactsRoot ./dist -OutputDir ./dist/nuget +pwsh ./nuget/pack-psign-dotnet-tool.ps1 -Version 0.7.0 -ArtifactsRoot ./dist -OutputDir ./dist/nuget ``` The package is built from native `psign-tool` artifacts for `win-x64`, `win-arm64`, `linux-x64`, `linux-arm64`, `osx-x64`, and `osx-arm64`, plus an `any` fallback package for unsupported runtimes. +Before dispatching the release workflow, run `pwsh ./scripts/bump-version.ps1 -Version ` and commit the updated files, including `Cargo.lock`. The release preflight runs the same script with `-Check` and rejects a version mismatch in any of those files. + ## Linux / portable tooling The canonical **`psign-tool`** CLI (package **`psign`**) supports an optional backend selector: **`--mode auto|windows|portable`**. When omitted, **`auto`** is used; **`PSIGN_TOOL_MODE`** can set the same default for parity automation. Windows mode uses Win32 APIs and registered SIP DLLs. Portable mode and the **`psign-tool portable ...`** namespace use the cross-platform Rust implementations from **`psign-sip-digest`**, **`psign-authenticode-trust`**, **`psign-opc-sign`**, **`psign-codesigning-rest`**, and **`psign-azure-kv-rest`** without **`WinVerifyTrust`** or the OS trust store. diff --git a/nuget/tool/Devolutions.Psign.Tool.csproj b/nuget/tool/Devolutions.Psign.Tool.csproj index 8bfed36..bd990a9 100644 --- a/nuget/tool/Devolutions.Psign.Tool.csproj +++ b/nuget/tool/Devolutions.Psign.Tool.csproj @@ -8,7 +8,7 @@ psign-tool Devolutions.Psign.Tool - 0.6.3 + 0.7.0 Devolutions RID-specific dotnet tool wrapper around prebuilt psign-tool native executables. README.md diff --git a/scripts/bump-version.ps1 b/scripts/bump-version.ps1 index 3af38b4..839d0b8 100644 --- a/scripts/bump-version.ps1 +++ b/scripts/bump-version.ps1 @@ -2,7 +2,9 @@ param( [Parameter(Mandatory = $true)] [string]$Version, - [switch]$SkipCargoLock + [switch]$SkipCargoLock, + + [switch]$Check ) $ErrorActionPreference = "Stop" @@ -18,6 +20,10 @@ if ($Version -notmatch '^\d+\.\d+\.\d+([-.][0-9A-Za-z.-]+)?$') { throw "Invalid version format: $Version (expected 1.2.3 or 1.2.3-suffix)." } +if ($Check -and $SkipCargoLock) { + throw "-Check cannot be combined with -SkipCargoLock." +} + function Set-FileText { param( [Parameter(Mandatory = $true)] @@ -47,21 +53,26 @@ function Update-RequiredRegex { $text = [System.IO.File]::ReadAllText($Path) $regex = [regex]::new($Pattern) - $script:replaceCount = 0 + $count = $regex.Matches($text).Count + if ($count -ne 1) { + throw "Expected exactly one $Description match in $Path; found $count." + } + $updated = $regex.Replace( $text, [System.Text.RegularExpressions.MatchEvaluator] { param($match) - $script:replaceCount++ & $Replacement $match }, 1 ) - $count = $script:replaceCount - $script:replaceCount = 0 - if ($count -ne 1) { - throw "Expected exactly one $Description match in $Path; found $count." + if ($Check) { + if ($updated -ne $text) { + throw "$Description in $Path does not match version $Version." + } + Write-Host "Verified $Description in $Path" + return } if ($updated -ne $text) { @@ -126,6 +137,10 @@ function Update-PowerShellModuleManifestVersion { return } + if ($Check) { + throw "PowerShell module prerelease in $Path does not match version $Version." + } + $regex = [regex]::new('(?m)^(\s*)PSData\s*=\s*@\{(\r?\n)') $script:replaceCount = 0 $updated = $regex.Replace( @@ -151,6 +166,12 @@ function Update-PowerShellModuleManifestVersion { } $regex = [regex]::new("(?m)^\s*Prerelease\s*=\s*'[^']*'\r?\n?") + if ($Check) { + if ($regex.IsMatch($text)) { + throw "PowerShell module prerelease in $Path does not match version $Version." + } + return + } $updated = $regex.Replace($text, '', 1) if ($updated -ne $text) { Set-FileText -Path $Path -Text $updated @@ -158,6 +179,38 @@ function Update-PowerShellModuleManifestVersion { } } +function Assert-CargoLockVersions { + param( + [Parameter(Mandatory = $true)] + [string[]]$Manifests + ) + + $lockPath = Join-Path $repoRoot "Cargo.lock" + $lockText = [System.IO.File]::ReadAllText($lockPath) + foreach ($manifest in $Manifests) { + $manifestText = [System.IO.File]::ReadAllText($manifest) + $packageBlock = [regex]::Match($manifestText, '(?ms)^\[package\]\r?\n(.*?)(?=^\[|\z)') + if (-not $packageBlock.Success) { + throw "Missing [package] section in $manifest." + } + + $nameMatches = [regex]::Matches($packageBlock.Groups[1].Value, '(?m)^name\s*=\s*"([^"]+)"') + if ($nameMatches.Count -ne 1) { + throw "Expected exactly one package name in $manifest; found $($nameMatches.Count)." + } + + $packageName = $nameMatches[0].Groups[1].Value + $pattern = '(?m)^\[\[package\]\]\r?\nname = "{0}"\r?\nversion = "([^"]+)"' -f [regex]::Escape($packageName) + $lockMatches = [regex]::Matches($lockText, $pattern) + if ($lockMatches.Count -ne 1) { + throw "Expected exactly one Cargo.lock entry for $packageName; found $($lockMatches.Count)." + } + if ($lockMatches[0].Groups[1].Value -ne $Version) { + throw "Cargo.lock entry for $packageName is $($lockMatches[0].Groups[1].Value), expected $Version." + } + } +} + $cargoManifests = @((Join-Path $repoRoot "Cargo.toml")) $cratesRoot = Join-Path $repoRoot "crates" if (Test-Path -LiteralPath $cratesRoot) { @@ -204,14 +257,25 @@ Update-RequiredRegex ` } if (-not $SkipCargoLock) { - Push-Location $repoRoot - try { - cargo metadata --format-version 1 --quiet | Out-Null - } - finally { - Pop-Location + if (-not $Check) { + Push-Location $repoRoot + try { + cargo metadata --format-version 1 --quiet | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "cargo metadata failed with exit code $LASTEXITCODE; Cargo.lock may be stale." + } + } + finally { + Pop-Location + } } - Write-Host "Refreshed Cargo.lock" + Assert-CargoLockVersions -Manifests $cargoManifests + Write-Host "$(if ($Check) { 'Verified' } else { 'Refreshed' }) Cargo.lock" } -Write-Host "Version bumped to $Version" +if ($Check) { + Write-Host "Version $Version is consistent" +} +else { + Write-Host "Version bumped to $Version" +}