mirror of
https://github.com/kellymichels/zscripts-token-savers
synced 2026-10-07 07:18:18 +00:00
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).
238 lines
9.7 KiB
PowerShell
238 lines
9.7 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
|
|
|
|
# zstart.ps1 — start local dev servers for any project defined in zconfig.json.
|
|
#
|
|
# Usage:
|
|
# zstart <project> [<project> ...] [-Port N] [-BindHost host] [-Detached]
|
|
#
|
|
# Examples:
|
|
# zstart pyapp
|
|
# zstart viteapp -Port 3000
|
|
# zstart pyapp nextapp -Detached
|
|
#
|
|
# Handlers by kind: python (python -m <startModule>, or uvicorn <startApp> for
|
|
# ASGI/FastAPI apps; prefers .venv), vite (npm run dev -- --host --port),
|
|
# nextjs (npm run dev with PORT).
|
|
#
|
|
param(
|
|
[Parameter(Position = 0, ValueFromRemainingArguments = $true)]
|
|
[string[]]$Projects = @(),
|
|
[Alias("p")][int]$Port = 0,
|
|
[string]$BindHost = "127.0.0.1",
|
|
[switch]$Detached
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
. (Join-Path $PSScriptRoot "ZHelpers.ps1")
|
|
Start-ZTracking
|
|
|
|
if ($Projects.Count -eq 0) {
|
|
$keys = (Get-ZProjectKeys) -join ', '
|
|
Write-Host ""
|
|
Write-Host "Usage: zstart <project> [<project> ...] [-Port N] [-Detached] [-BindHost host]" -ForegroundColor Yellow
|
|
Write-Host " Projects in zconfig.json: $keys" -ForegroundColor Gray
|
|
Stop-ZTracking; exit 1
|
|
}
|
|
|
|
function Test-PortNeedsAdmin {
|
|
param([int]$ListenPort)
|
|
if ($ListenPort -ge 1024) { return }
|
|
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
if (-not $isAdmin) {
|
|
Write-Host "WARNING: Port $ListenPort requires Administrator privileges to bind. Run this shell as Administrator!" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
function Start-PythonProject {
|
|
param([string]$Key, $Proj, [int]$ListenPort, [bool]$RunDetached, [string]$HostBind = "127.0.0.1")
|
|
$root = $Proj.localRoot
|
|
if (-not (Test-Path -LiteralPath $root)) { throw "Project root not found: $root" }
|
|
$module = $Proj.startModule
|
|
$app = $Proj.startApp
|
|
if (-not $module -and -not $app) {
|
|
throw "Project '$Key' (kind=python) needs 'startModule' (python -m ...) or 'startApp' (uvicorn app:app) in zconfig.json."
|
|
}
|
|
Set-Location -LiteralPath $root
|
|
$venvPython = Join-Path $root ".venv\Scripts\python.exe"
|
|
$usingVenv = Test-Path -LiteralPath $venvPython
|
|
$exe = if ($usingVenv) { $venvPython } else { "python" }
|
|
|
|
# Optional convention: if the project ships scripts/build_version_tool.py,
|
|
# bump (or at least read) the build version on every dev start.
|
|
$buildVersion = ""
|
|
$versionTool = Join-Path $root "scripts\build_version_tool.py"
|
|
if (Test-Path -LiteralPath $versionTool) {
|
|
try {
|
|
$bumpOut = & $exe $versionTool bump 2>$null
|
|
if ($LASTEXITCODE -eq 0 -and $bumpOut) {
|
|
$buildVersion = ($bumpOut | Select-Object -Last 1).ToString().Trim()
|
|
}
|
|
} catch { }
|
|
if ([string]::IsNullOrWhiteSpace($buildVersion)) {
|
|
try {
|
|
$getOut = & $exe $versionTool get 2>$null
|
|
if ($LASTEXITCODE -eq 0 -and $getOut) {
|
|
$buildVersion = ($getOut | Select-Object -Last 1).ToString().Trim()
|
|
}
|
|
} catch { }
|
|
}
|
|
}
|
|
|
|
# startApp (uvicorn ASGI target, e.g. app.main:app) takes precedence over
|
|
# startModule (python -m ...). uvicorn runs via the venv python's -m, and
|
|
# gets zstart's bind-host + dev port.
|
|
if ($app) {
|
|
$runArgs = @("-m", "uvicorn", $app, "--host", $HostBind, "--port", "$ListenPort", "--reload")
|
|
$what = "uvicorn $app"
|
|
} else {
|
|
$runArgs = @("-m", $module)
|
|
$what = "python -m $module"
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "=== zstart ($($Proj.label)) ===" -ForegroundColor Cyan
|
|
if (-not $usingVenv) {
|
|
Write-Host "No project venv at $root\.venv - using system 'python' (its deps may be missing)." -ForegroundColor Yellow
|
|
Write-Host " Create one: python -m venv .venv; .\.venv\Scripts\pip install -e ." -ForegroundColor DarkGray
|
|
}
|
|
Write-Host "Starting $what on port $ListenPort..." -ForegroundColor Cyan
|
|
if (-not [string]::IsNullOrWhiteSpace($buildVersion)) {
|
|
Write-Host "Build Version: $buildVersion" -ForegroundColor Magenta
|
|
}
|
|
|
|
Show-ProjectMotd -Root $root -BuildVersion $buildVersion
|
|
|
|
Write-Host "Press Ctrl+C to stop the server" -ForegroundColor DarkGray
|
|
Test-PortNeedsAdmin -ListenPort $ListenPort
|
|
Write-Host ""
|
|
|
|
if ($RunDetached) {
|
|
Start-Process -FilePath $exe -ArgumentList $runArgs -WorkingDirectory $root | Out-Null
|
|
Write-Host "Started in detached mode." -ForegroundColor Green
|
|
Write-Host "Use zkill $Key to stop it." -ForegroundColor DarkGray
|
|
} else {
|
|
& $exe @runArgs
|
|
}
|
|
}
|
|
|
|
function Start-ViteProject {
|
|
param([string]$Key, $Proj, [int]$ListenPort, [string]$HostBind, [bool]$RunDetached)
|
|
$root = $Proj.localRoot
|
|
if (-not (Test-Path -LiteralPath $root)) { throw "Project root not found: $root" }
|
|
if (-not (Test-Path -LiteralPath (Join-Path $root "package.json"))) {
|
|
throw "package.json not found in $root"
|
|
}
|
|
Set-Location -LiteralPath $root
|
|
|
|
Write-Host ""
|
|
Write-Host "=== zstart ($($Proj.label) - Vite) ===" -ForegroundColor Cyan
|
|
Write-Host "Directory: $root" -ForegroundColor DarkGray
|
|
Write-Host "URL: http://${HostBind}:$ListenPort/" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
if (-not (Test-Path -LiteralPath (Join-Path $root "node_modules"))) {
|
|
Write-Host "node_modules missing - running npm install..." -ForegroundColor Yellow
|
|
& npm install
|
|
if ($LASTEXITCODE -ne 0) { throw "npm install failed with exit $LASTEXITCODE" }
|
|
}
|
|
|
|
Show-ProjectMotd -Root $root
|
|
|
|
Write-Host "Press Ctrl+C to stop the dev server" -ForegroundColor DarkGray
|
|
Test-PortNeedsAdmin -ListenPort $ListenPort
|
|
Write-Host ""
|
|
|
|
$npmArgs = @("run", "dev", "--", "--host", $HostBind, "--port", "$ListenPort")
|
|
if ($RunDetached) {
|
|
Start-Process -FilePath "npm" -ArgumentList $npmArgs -WorkingDirectory $root -WindowStyle Normal | Out-Null
|
|
Write-Host "Started npm run dev in a new window (detached)." -ForegroundColor Green
|
|
Write-Host "Use zkill $Key to free port $ListenPort." -ForegroundColor DarkGray
|
|
} else {
|
|
& npm @npmArgs
|
|
}
|
|
}
|
|
|
|
function Start-NextProject {
|
|
param([string]$Key, $Proj, [int]$ListenPort, [bool]$RunDetached)
|
|
$root = $Proj.localRoot
|
|
if (-not (Test-Path -LiteralPath $root)) { throw "Project root not found: $root" }
|
|
if (-not (Test-Path -LiteralPath (Join-Path $root "package.json"))) {
|
|
throw "package.json not found in $root"
|
|
}
|
|
Set-Location -LiteralPath $root
|
|
|
|
Write-Host ""
|
|
Write-Host "=== zstart ($($Proj.label) - Next.js) ===" -ForegroundColor Cyan
|
|
Write-Host "Starting on port $ListenPort..." -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
Show-ProjectMotd -Root $root
|
|
|
|
Write-Host "Press Ctrl+C to stop the server" -ForegroundColor DarkGray
|
|
Test-PortNeedsAdmin -ListenPort $ListenPort
|
|
Write-Host ""
|
|
|
|
$env:PORT = "$ListenPort"
|
|
if ($RunDetached) {
|
|
Start-Process -FilePath "npm" -ArgumentList "run", "dev" -WorkingDirectory $root | Out-Null
|
|
Write-Host "Started in detached mode on port $ListenPort." -ForegroundColor Green
|
|
Write-Host "Use zkill $Key to stop it." -ForegroundColor DarkGray
|
|
} else {
|
|
npm run dev
|
|
}
|
|
}
|
|
|
|
# Optional per-project pre-start steps from zconfig.json:
|
|
# "start": { "gitPull": true, "env": { "SOME_FLAG": "1" } }
|
|
# gitPull runs 'git pull --ff-only' in the project root; env sets process
|
|
# environment variables before the server starts.
|
|
function Invoke-ProjectStartPrep {
|
|
param($Proj)
|
|
if (-not $Proj.start) { return }
|
|
if ($Proj.start.env) {
|
|
foreach ($item in $Proj.start.env.PSObject.Properties) {
|
|
Set-Item -Path "Env:$($item.Name)" -Value ([string]$item.Value)
|
|
Write-Host " env $($item.Name)=$($item.Value)" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
if ($Proj.start.gitPull -and (Test-Path (Join-Path $Proj.localRoot ".git"))) {
|
|
Push-Location -LiteralPath $Proj.localRoot
|
|
# GIT_TERMINAL_PROMPT=0 so a repo that needs credentials fails fast
|
|
# instead of blocking the server start on a "Username for ..." prompt.
|
|
$prev = $env:GIT_TERMINAL_PROMPT; $env:GIT_TERMINAL_PROMPT = "0"
|
|
try {
|
|
$pullOut = git pull --ff-only 2>&1
|
|
$last = ($pullOut | Select-Object -Last 1)
|
|
Write-Host " git pull: $last" -ForegroundColor DarkGray
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Host " Auto-pull skipped - starting with the current checkout. (git needs credentials here, or set start.gitPull=false)" -ForegroundColor Yellow
|
|
}
|
|
} finally {
|
|
$env:GIT_TERMINAL_PROMPT = $prev
|
|
Pop-Location
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($key in $Projects) {
|
|
$proj = Get-ZProject -Key $key
|
|
$devPort = if ($Port -gt 0) { $Port } elseif ($proj.ports -and $proj.ports.dev) { [int]$proj.ports.dev } else { 3000 }
|
|
|
|
Invoke-ProjectStartPrep -Proj $proj
|
|
|
|
switch ([string]$proj.kind) {
|
|
"python" { Start-PythonProject -Key $key -Proj $proj -ListenPort $devPort -HostBind $BindHost -RunDetached $Detached.IsPresent }
|
|
"vite" { Start-ViteProject -Key $key -Proj $proj -ListenPort $devPort -HostBind $BindHost -RunDetached $Detached.IsPresent }
|
|
"nextjs" { Start-NextProject -Key $key -Proj $proj -ListenPort $devPort -RunDetached $Detached.IsPresent }
|
|
default {
|
|
Write-Host ""
|
|
Write-Host "Project '$key' has kind '$($proj.kind)' - no local dev server to start." -ForegroundColor Yellow
|
|
Write-Host "Supported kinds for zstart: python, vite, nextjs" -ForegroundColor Gray
|
|
}
|
|
}
|
|
}
|
|
Stop-ZTracking
|