zscripts-token-savers/zstart.ps1
Kelly Michels b26be3cd2a
chore: write the site name as evomedia.net, lowercase (#92)
* chore: write the site name as evomedia.net, lowercase

The name is a domain and is written as one. Script headers, the README,
CHANGELOG and elevator pitch, their .txt twins, and the site page --
matching the same sweep in the private evo.scripts so the mirror does not
drift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: refresh CHECKSUMS.txt for the lowercase sweep

Every script's header changed, so every hash did. The repo's own
Checksums test caught it -- which is what it is for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 13:40:03 -05:00

232 lines
9.4 KiB
PowerShell

# evomedia.net Token Savers — 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.27
# 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) {
# Never lets a pull stand between the user and a running server: the
# helper reports, it does not throw (#130). The inline version this
# replaced aborted on git's ordinary stderr progress under Stop.
$pull = Invoke-StartGitPull -Root $Proj.localRoot
if ($pull.Ok) {
Write-Host " git pull: $($pull.Message)" -ForegroundColor DarkGray
} else {
Write-Host " Auto-pull skipped - starting with the current checkout. ($($pull.Message); set start.gitPull=false to stop trying)" -ForegroundColor Yellow
}
}
}
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