zscripts-token-savers/zsync.ps1
KellyMichels 502c0e8b29 feat(zversion, zrelease): toolkit versioning + downloadable release zips
Implements the versioning rule (SmartPlant's 5-segment scheme, now the global
standard; currently only sp and zscripts are on it at v1.x):

    v{major}.{rc}.{beta}.{alpha}.{build}

zversion: get / bump / bump-stage / set. A stage bump zeroes every lower
segment including build. 'bump' is one per PR and one per defect fix, not per
file. Any write rewrites three things together, because they are only useful
when they agree: build-version.json (source of truth), a '# Version:' line in
all 42 script headers (a lone copied script still says which release it came
from), and CHECKSUMS.txt (stamping changes every file).

zrelease: packages the current version as releases/zscripts-<version>.zip with
a sibling .sha256, for people who want the toolkit without cloning. One hash
verifies the download; the bundled CHECKSUMS.txt verifies the extracted
contents. Refuses to overwrite an existing version's zip (released = immutable;
bump instead), and refuses to package when zchecksums fails. tests/ excluded
from the zip; releases/ never packages itself.

First release included: releases/zscripts-v1.0.0.0.0.zip (42 scripts + 7
support files) and its .sha256.

.gitattributes: releases/*.sha256 pinned LF (sha256sum treats a trailing CR as
part of the filename), releases/*.zip marked binary.

Verified end-to-end as a downloader would experience it, in WSL: sha256sum -c
on the zip passes, unzip, sha256sum -c CHECKSUMS.txt inside gives 42 OK / 0
FAILED, and the extracted zdeploy.ps1 header and build-version.json both read
v1.0.0.0.0. Double-release guard and -Verify mode exercised. Full Pester suite
219/219 (the checksum tests absorb the new files automatically).
2026-07-28 13:27:50 -05:00

131 lines
5.0 KiB
PowerShell

# Evomedia.net Token Savers — https://github.com/kellymichels/zscripts-token-savers
# Created by Kelly Michels · dev@evomedia.net
# Licensed under the MIT License. See LICENSE.
# Version: v1.0.0.0.0
# zsync.ps1 — copy new backup files offsite; or build + mirror a vite project's dist.
#
# Usage:
# zsync # new files: backups folder -> paths.oneDriveBackups
# zsync <viteproject> -Destination 'D:\mirror\dist'
#
# The no-args mode copies only files that don't exist at the destination yet
# (never overwrites, never deletes). The project mode runs npm run build, then
# robocopy /MIR of dist/ to -Destination (or $env:ZSYNC_DEST).
#
param(
[Parameter(Position = 0, ValueFromRemainingArguments = $true)]
[string[]]$Projects = @(),
[string]$Destination = $env:ZSYNC_DEST
)
$ErrorActionPreference = "Stop"
. (Join-Path $PSScriptRoot "ZHelpers.ps1")
Start-ZTracking
$cfg = Get-ZConfig
$BackupsRoot = Split-Path -Parent $cfg.paths.backupsLocal
$OneDriveBackupsRoot = $cfg.paths.oneDriveBackups
# Copy any file under $SourceRoot that does not yet exist at the matching path under $DestinationRoot.
function Sync-NewFilesTree {
param(
[Parameter(Mandatory)] [string]$SourceRoot,
[Parameter(Mandatory)] [string]$DestinationRoot
)
if (-not (Test-Path -LiteralPath $SourceRoot)) {
Write-Host " Source not found (skipped): $SourceRoot" -ForegroundColor DarkYellow
return @{ copied = 0; skipped = 0 }
}
if (-not (Test-Path -LiteralPath $DestinationRoot)) {
New-Item -ItemType Directory -Path $DestinationRoot -Force | Out-Null
}
$sourceFull = (Get-Item -LiteralPath $SourceRoot).FullName.TrimEnd('\')
$files = Get-ChildItem -LiteralPath $SourceRoot -Recurse -File -Force -ErrorAction SilentlyContinue
$copied = 0
$skipped = 0
foreach ($file in $files) {
$rel = $file.FullName.Substring($sourceFull.Length).TrimStart('\')
$destFile = Join-Path $DestinationRoot $rel
$destParent = Split-Path -Parent $destFile
if ($destParent -and -not (Test-Path -LiteralPath $destParent)) {
New-Item -ItemType Directory -Path $destParent -Force | Out-Null
}
if (Test-Path -LiteralPath $destFile) { $skipped++; continue }
Copy-Item -LiteralPath $file.FullName -Destination $destFile -Force
$sizeMb = "{0:N2}" -f ($file.Length / 1MB)
Write-Host " Copied: $rel ($sizeMb MB)" -ForegroundColor Green
$copied++
}
return @{ copied = $copied; skipped = $skipped }
}
if ($Projects.Count -eq 0) {
Write-Host ""
Write-Host "=== zsync (backups -> offsite) ===" -ForegroundColor Cyan
Write-Host " Source: $BackupsRoot" -ForegroundColor Gray
Write-Host " Destination: $OneDriveBackupsRoot" -ForegroundColor Gray
Write-Host " Mode: new files and folders only" -ForegroundColor Gray
Write-Host ""
if ([string]::IsNullOrWhiteSpace($OneDriveBackupsRoot)) {
Write-Host " paths.oneDriveBackups is not set in zconfig.json - nothing to do." -ForegroundColor Yellow
Stop-ZTracking; exit 0
}
$stats = Sync-NewFilesTree -SourceRoot $BackupsRoot -DestinationRoot $OneDriveBackupsRoot
Write-Host ""
Write-Host " Done: $($stats.copied) copied, $($stats.skipped) already present" -ForegroundColor Cyan
Write-Host ""
Stop-ZTracking; exit 0
}
foreach ($key in $Projects) {
$proj = Get-ZProject -Key $key
if ($proj.kind -ne "vite") {
Write-Host ""
Write-Host "zsync project mode only supports vite kind (builds and mirrors dist/). '$key' is kind '$($proj.kind)'." -ForegroundColor Yellow
Stop-ZTracking; exit 1
}
$root = $proj.localRoot
if ([string]::IsNullOrWhiteSpace($Destination)) {
Write-Host ""
Write-Host "=== zsync ($($proj.label)) ===" -ForegroundColor Cyan
Write-Host "No destination set." -ForegroundColor Yellow
Write-Host "Set ZSYNC_DEST or run: zsync $key -Destination 'D:\path\to\folder'" -ForegroundColor DarkGray
Write-Host "This runs npm run build, then robocopy dist -> destination." -ForegroundColor DarkGray
Stop-ZTracking; exit 1
}
$prevLoc = Get-Location
try {
Set-Location -LiteralPath $root
Write-Host ""
Write-Host "=== zsync ($($proj.label)) ===" -ForegroundColor Cyan
Write-Host "Build + mirror dist -> $Destination" -ForegroundColor Cyan
Write-Host ""
& npm run build
if ($LASTEXITCODE -ne 0) { throw "npm run build failed" }
$dist = Join-Path $root "dist"
if (-not (Test-Path -LiteralPath $dist)) { throw "dist/ missing after build." }
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
& robocopy $dist $Destination /MIR /NFL /NDL /NJH /NJS /NP
$rc = $LASTEXITCODE
if ($rc -ge 8) { throw "robocopy failed with exit code $rc" }
Write-Host ""
Write-Host "Sync complete." -ForegroundColor Green
}
finally {
Set-Location -LiteralPath $prevLoc
}
}
Stop-ZTracking