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" +}