zscripts-token-savers/zversion.ps1
Kelly Michels 4230566e47
feat: two blank lines after every z-script run (#57)
zdeploy ended with two blank lines and nothing else did, so its output
was the only one that did not butt up against the next prompt. Applied
everywhere.

Implemented in Stop-ZTracking rather than per script, because every
tracked script ends by calling it - including the usage and guard paths
that do `Stop-ZTracking; exit 1`. One place therefore covers every exit
of sixteen scripts.

  * Write-ZTrailer emits the two lines. Stop-ZTracking calls it on both
    paths, including the early return when tracking never started - a
    script that printed output still deserves the separation.
  * -FinalNote prints one last line AFTER the tracking footer and BEFORE
    the blanks. zdeploy's "Last deployed at ..." now goes through it, so
    it stays the last thing on screen instead of being followed by the
    token count.
  * The standalone tools (zchecksums, zversion, zrelease) get a local
    three-line copy at each exit rather than dot-sourcing ZHelpers -
    they are deliberately dependency-free so they run from an extracted
    release zip with nothing beside them.
  * Redundant trailing blanks were removed where a script already
    printed one before exiting, so the count is exactly two, not three.

Also fixes a related gap found while testing: the error exits inside
Get-ZConfig and Get-ZProject bypassed Stop-ZTracking entirely, so a run
that died on a missing zconfig.json printed no trailer AND no token
footer. Those three exits now route through Stop-ZTracking.

zkill/zrestart are one-line wrappers around scripts that already trail,
so they are untouched - adding it would double the blanks.

Verified by running each script and counting the trailing blank lines in
its captured output: 2 on every success path, every usage path, and the
config-error path. Suite 231/231. CHECKSUMS.txt refreshed.
2026-08-25 15:37:45 -05:00

167 lines
6.8 KiB
PowerShell

# Evomedia.net — https://github.com/evomedia-net/evo.zscripts
# Created by Kelly Michels · dev@evomedia.net
# Licensed under the MIT License. See LICENSE.
# Version: v1.0.0.0.14
# zversion.ps1 - manage the toolkit version.
#
# Usage:
# zversion print the current version
# zversion bump build + 1 (one bump per PR / per defect fix)
# zversion bump-stage alpha alpha + 1, build -> 0
# zversion bump-stage beta beta + 1, alpha/build -> 0
# zversion bump-stage rc rc + 1, beta/alpha/build -> 0
# zversion bump-stage release major + 1, everything below -> 0
# zversion set v1.2.0.0.5 set an exact version
#
# THE SCHEME
# ----------
# v{major}.{rc}.{beta}.{alpha}.{build}
#
# Priority runs left to right, and bumping any stage zeroes every lower segment
# including build. One version for the whole toolkit, not per script.
#
# WHAT A BUMP TOUCHES
# -------------------
# Anything other than a plain read rewrites three things together, because they
# are only useful if they agree:
# 1. build-version.json - the source of truth
# 2. the "# Version:" line in every .ps1 / .cmd header, so a script that has
# been copied out of the repo still says which release it came from
# 3. CHECKSUMS.txt - stamping changes every file, so the manifest must
# be regenerated or verification fails immediately
#
# Run this on a clean checkout: it hashes files as they sit on disk, and an
# editor that writes LF leaves a file git still considers unchanged. See the
# note in zchecksums.ps1.
[CmdletBinding()]
param(
[Parameter(Position = 0)][string]$Command = "get",
[Parameter(Position = 1)][string]$Value
)
$ErrorActionPreference = "Stop"
# Two blank lines after this script's output, matching every other z-script, so
# a run is visually separated from the next prompt. Local rather than from
# ZHelpers: this script is deliberately standalone, so it can run from an
# extracted release zip with nothing beside it.
function Write-ZTrailer { Write-Host ""; Write-Host "" }
$VersionFile = Join-Path $PSScriptRoot "build-version.json"
$VersionRe = '^v(\d+)\.(\d+)\.(\d+)\.(\d+)\.(\d+)$'
$DefaultVersion = "v1.0.0.0.0"
function Read-Version {
if (-not (Test-Path -LiteralPath $VersionFile)) { return $DefaultVersion }
try {
$v = (Get-Content -LiteralPath $VersionFile -Raw -Encoding UTF8 | ConvertFrom-Json).version
if ($v -match $VersionRe) { return $v }
} catch { }
return $DefaultVersion
}
function Write-Version([string]$Version) {
$json = [ordered]@{ version = $Version } | ConvertTo-Json
[IO.File]::WriteAllText($VersionFile, $json + "`n", (New-Object Text.UTF8Encoding($false)))
}
function Split-Version([string]$Version) {
if ($Version -notmatch $VersionRe) {
throw "Invalid version '$Version'. Expected v{major}.{rc}.{beta}.{alpha}.{build}, e.g. v1.0.0.0.3."
}
return [int[]]@($Matches[1], $Matches[2], $Matches[3], $Matches[4], $Matches[5])
}
# Rewrite (or insert) the "# Version:" header line in every covered script.
function Set-ScriptVersionHeaders([string]$Version) {
$stamped = 0
foreach ($f in Get-ChildItem -LiteralPath $PSScriptRoot -File | Where-Object { $_.Extension -in @('.ps1', '.cmd') }) {
$text = [IO.File]::ReadAllText($f.FullName)
# Keep each file's own comment marker: # for PowerShell, REM for cmd.
$marker = if ($f.Extension -eq '.cmd') { 'REM' } else { '#' }
$line = "$marker Version: $Version"
if ($text -match "(?m)^(#|REM) Version: v[\d\.]+\r?$") {
$new = [regex]::Replace($text, "(?m)^(#|REM) Version: v[\d\.]+\r?$", [System.Text.RegularExpressions.MatchEvaluator] { param($m) $line })
} else {
# Insert directly after the licence line, which every header carries.
$pattern = "(?m)^((#|REM) Licensed under the MIT License\. See LICENSE\.)\r?$"
if ($text -notmatch $pattern) { continue }
$new = [regex]::Replace($text, $pattern, [System.Text.RegularExpressions.MatchEvaluator] { param($m) $m.Groups[1].Value + "`r`n" + $line }, 1)
}
if ($new -ne $text) {
# .ps1/.cmd are pinned to CRLF by .gitattributes - write them that
# way or every stamped file shows up as changed on the next clone.
$new = ($new -replace "`r`n", "`n") -replace "`n", "`r`n"
[IO.File]::WriteAllText($f.FullName, $new, (New-Object Text.UTF8Encoding($false)))
$stamped++
}
}
return $stamped
}
function Update-Everything([string]$Version) {
Write-Version $Version
$n = Set-ScriptVersionHeaders $Version
& (Join-Path $PSScriptRoot "zchecksums.ps1") -Update | Out-Null
Write-Host ""
Write-Host "=== zversion ===" -ForegroundColor Cyan
Write-Host " Version: $Version" -ForegroundColor Green
Write-Host " Stamped: $n script header(s)" -ForegroundColor Gray
Write-Host " Refreshed: CHECKSUMS.txt" -ForegroundColor Gray
Write-Host " Commit build-version.json, the stamped scripts and CHECKSUMS.txt together." -ForegroundColor DarkGray
Write-Host ""
}
$current = Read-Version
switch ($Command.ToLowerInvariant().TrimStart('-')) {
"get" {
Write-Host $current
Write-ZTrailer
exit 0
}
"bump" {
$p = Split-Version $current
Update-Everything ("v{0}.{1}.{2}.{3}.{4}" -f $p[0], $p[1], $p[2], $p[3], ($p[4] + 1))
Write-ZTrailer
exit 0
}
"bump-stage" {
$p = Split-Version $current
$major, $rc, $beta, $alpha = $p[0], $p[1], $p[2], $p[3]
switch (("$Value").ToLowerInvariant()) {
"release" { $major++; $rc = 0; $beta = 0; $alpha = 0 }
"rc" { $rc++; $beta = 0; $alpha = 0 }
"beta" { $beta++; $alpha = 0 }
"alpha" { $alpha++ }
default {
Write-Host "ERROR: stage must be one of: release, rc, beta, alpha" -ForegroundColor Red
Write-ZTrailer
exit 1
}
}
# Bumping a stage zeroes every lower segment, build included.
Update-Everything ("v{0}.{1}.{2}.{3}.0" -f $major, $rc, $beta, $alpha)
Write-ZTrailer
exit 0
}
"set" {
[void](Split-Version $Value)
Update-Everything $Value
Write-ZTrailer
exit 0
}
default {
Write-Host ""
Write-Host "Usage: zversion [get | bump | bump-stage <release|rc|beta|alpha> | set <version>]" -ForegroundColor Yellow
Write-Host " Scheme: v{major}.{rc}.{beta}.{alpha}.{build} (current: $current)" -ForegroundColor Gray
Write-Host " bump build + 1 - one per PR, one per defect fix" -ForegroundColor Gray
Write-Host " bump-stage raise a stage; every lower segment resets to 0" -ForegroundColor Gray
Write-ZTrailer
exit 1
}
}