zscripts-token-savers/zrelease.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

158 lines
6.3 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
# zrelease.ps1 - package the current version as a downloadable zip.
#
# Usage:
# zrelease build releases/zscripts-<version>.zip
# zrelease -Force overwrite an existing zip for this version
# zrelease -Verify re-check the zip already on disk for this version
#
# For people who want the toolkit without cloning: one zip, one hash to check.
#
# WHAT GOES IN
# ------------
# every *.ps1 / *.cmd the commands themselves
# CHECKSUMS.txt per-file manifest, so the contents can be
# re-verified after unzipping
# zconfig.example.json you need it to configure anything
# README.md, CHANGELOG.md, LICENSE, TOKEN_SAVINGS.md
# build-version.json which release this is
#
# Left out: tests/ (a user does not need the suite to run the commands) and
# releases/ (never package the packages).
#
# WHAT COMES OUT
# releases/zscripts-v1.0.0.0.1.zip
# releases/zscripts-v1.0.0.0.1.zip.sha256 <- sha256sum format, one line
#
# Two layers on purpose: the .sha256 verifies you downloaded the zip intact,
# and CHECKSUMS.txt inside verifies the individual scripts after extraction.
# Both are integrity checks, not signatures - see the note in zchecksums.ps1.
[CmdletBinding()]
param(
[switch]$Force,
[switch]$Verify
)
$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 "" }
$ReleasesDir = Join-Path $PSScriptRoot "releases"
$VersionFile = Join-Path $PSScriptRoot "build-version.json"
if (-not (Test-Path -LiteralPath $VersionFile)) {
Write-Host "ERROR: build-version.json not found. Run 'zversion set v1.0.0.0.0' first." -ForegroundColor Red
Write-ZTrailer
exit 1
}
$version = (Get-Content -LiteralPath $VersionFile -Raw -Encoding UTF8 | ConvertFrom-Json).version
if ($version -notmatch '^v\d+\.\d+\.\d+\.\d+\.\d+$') {
Write-Host "ERROR: build-version.json holds an invalid version '$version'." -ForegroundColor Red
Write-ZTrailer
exit 1
}
$zipName = "zscripts-$version.zip"
$zipPath = Join-Path $ReleasesDir $zipName
$shaPath = "$zipPath.sha256"
function Get-Sha256([string]$Path) {
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
# ---- verify mode -------------------------------------------------------------
if ($Verify) {
if (-not (Test-Path -LiteralPath $zipPath)) {
Write-Host "ERROR: $zipName not found in releases/." -ForegroundColor Red
Write-ZTrailer
exit 1
}
if (-not (Test-Path -LiteralPath $shaPath)) {
Write-Host "ERROR: $zipName.sha256 not found." -ForegroundColor Red
Write-ZTrailer
exit 1
}
$expected = ((Get-Content -LiteralPath $shaPath -Raw) -split '\s+')[0].ToLowerInvariant()
$actual = Get-Sha256 $zipPath
Write-Host ""
Write-Host "=== zrelease (verify) ===" -ForegroundColor Cyan
if ($actual -eq $expected) {
Write-Host " OK - $zipName matches its .sha256." -ForegroundColor Green
Write-ZTrailer
exit 0
}
Write-Host " FAILED - $zipName does not match its .sha256." -ForegroundColor Red
Write-ZTrailer
exit 1
}
# ---- build mode --------------------------------------------------------------
if ((Test-Path -LiteralPath $zipPath) -and -not $Force) {
Write-Host ""
Write-Host "ERROR: releases/$zipName already exists." -ForegroundColor Red
Write-Host " A released version is immutable - bump instead: zversion bump" -ForegroundColor DarkGray
Write-Host " (or pass -Force if you are rebuilding one that was never published)" -ForegroundColor DarkGray
Write-ZTrailer
exit 1
}
# Refuse to package scripts that disagree with the manifest.
& (Join-Path $PSScriptRoot "zchecksums.ps1") -Quiet | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host ""
Write-Host "ERROR: checksum verification failed - refusing to package." -ForegroundColor Red
Write-Host " Run 'zchecksums' to see what differs, then 'zversion bump' to restamp." -ForegroundColor DarkGray
Write-ZTrailer
exit 1
}
New-Item -ItemType Directory -Force -Path $ReleasesDir | Out-Null
$staging = Join-Path ([IO.Path]::GetTempPath()) ("zrel-" + [guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $staging -Force | Out-Null
try {
$scripts = @(Get-ChildItem -LiteralPath $PSScriptRoot -File | Where-Object { $_.Extension -in @('.ps1', '.cmd') })
foreach ($f in $scripts) { Copy-Item -LiteralPath $f.FullName -Destination $staging }
$extras = @('CHECKSUMS.txt', 'zconfig.example.json', 'README.md', 'CHANGELOG.md', 'LICENSE', 'TOKEN_SAVINGS.md', 'build-version.json')
$included = @()
foreach ($name in $extras) {
$p = Join-Path $PSScriptRoot $name
if (Test-Path -LiteralPath $p) { Copy-Item -LiteralPath $p -Destination $staging; $included += $name }
}
if (Test-Path -LiteralPath $zipPath) { Remove-Item -LiteralPath $zipPath -Force }
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue
[IO.Compression.ZipFile]::CreateFromDirectory($staging, $zipPath, [IO.Compression.CompressionLevel]::Optimal, $false)
}
finally {
Remove-Item -LiteralPath $staging -Recurse -Force -ErrorAction SilentlyContinue
}
$hash = Get-Sha256 $zipPath
# sha256sum format, LF ending, so `sha256sum -c` works on Linux/macOS.
[IO.File]::WriteAllText($shaPath, "$hash $zipName`n", (New-Object Text.UTF8Encoding($false)))
$sizeKb = [math]::Round((Get-Item -LiteralPath $zipPath).Length / 1KB, 1)
Write-Host ""
Write-Host "=== zrelease ===" -ForegroundColor Cyan
Write-Host " Version: $version" -ForegroundColor Green
Write-Host " Zip: releases/$zipName ($sizeKb KB, $($scripts.Count) scripts + $($included.Count) support file(s))" -ForegroundColor Gray
Write-Host " SHA-256: $hash" -ForegroundColor Gray
Write-Host " Digest: releases/$zipName.sha256" -ForegroundColor Gray
Write-Host ""
Write-Host " Commit both files - a release lives in the repo under releases/." -ForegroundColor DarkGray
Write-ZTrailer
exit 0