mirror of
https://github.com/kellymichels/zscripts-token-savers
synced 2026-10-07 07:18:18 +00:00
start.gitPull ran `git pull --ff-only` before starting the server; in an environment with no cached git credentials (e.g. WSL against an HTTPS GitHub remote) git prompted "Username for 'https://github.com':" and the whole start blocked on stdin. Run the pull with GIT_TERMINAL_PROMPT=0 so it fails fast, log a clear "auto-pull skipped" note, and start with the current checkout. Bash + PowerShell.
204 lines
7.9 KiB
Bash
204 lines
7.9 KiB
Bash
#!/usr/bin/env bash
|
|
# 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.
|
|
#
|
|
# zstart — start local dev servers for any project defined in zconfig.json.
|
|
#
|
|
# Usage:
|
|
# zstart <project> [<project> ...] [--port N] [--bind-host host] [--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).
|
|
# Detached servers log to /tmp/zstart-<project>.log ; stop them with zkill.
|
|
|
|
set -uo pipefail
|
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/zhelpers.sh"
|
|
z_track_start "$@"
|
|
z_need_config
|
|
|
|
projects=(); port_override=0; bind_host="127.0.0.1"; detached=0
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-p|--port) port_override="${2:-0}"; shift 2 ;;
|
|
--bind-host) bind_host="${2:-127.0.0.1}"; shift 2 ;;
|
|
-d|--detached) detached=1; shift ;;
|
|
-*) err "Unknown option: $1"; exit 1 ;;
|
|
*) projects+=("$1"); shift ;;
|
|
esac
|
|
done
|
|
|
|
if [ "${#projects[@]}" -eq 0 ]; then
|
|
printf '\n'; warn "Usage: zstart <project> [<project> ...] [--port N] [--detached] [--bind-host host]"
|
|
dim " Projects in zconfig.json: $(zproj_csv)"
|
|
exit 1
|
|
fi
|
|
|
|
warn_if_privileged_port() { # <port>
|
|
if [ "$1" -lt 1024 ] && [ "$(id -u)" -ne 0 ]; then
|
|
err "WARNING: Port $1 requires root privileges to bind. Run with sudo!"
|
|
fi
|
|
}
|
|
|
|
detach() { # <key> <workdir> <cmd...>
|
|
local key="$1" dir="$2"; shift 2
|
|
local log="/tmp/zstart-${key}.log"
|
|
# Sever every fd tied to this shell before exec'ing the long-lived server:
|
|
# stdin<-/dev/null, stdout/stderr->log, and CLOSE the ztokens tracking fds
|
|
# (3/4 = saved stdout/stderr; 1/2 fed the capture FIFO). If the server keeps
|
|
# any open, the parent's EXIT-trap footer (a `tee` on the FIFO) never gets
|
|
# EOF and `zstart --detached` hangs instead of returning.
|
|
( cd "$dir" && exec </dev/null >"$log" 2>&1 3>&- 4>&-; exec nohup "$@" ) &
|
|
disown 2>/dev/null || true
|
|
ok "Started in detached mode (logs: $log)."
|
|
dim "Use zkill $key to stop it."
|
|
}
|
|
|
|
# Optional per-project pre-start steps from zconfig.json:
|
|
# "start": { "gitPull": true, "env": { "SOME_FLAG": "1" } }
|
|
start_prep() { # <key>
|
|
local key="$1" root kv name val
|
|
root="$(zproj_root "$key")"
|
|
while IFS=$'\t' read -r name val; do
|
|
[ -n "$name" ] || continue
|
|
export "$name=$val"
|
|
dim " env $name=$val"
|
|
done < <(jq -r --arg k "$key" '.projects[$k].start.env // {} | to_entries[] | "\(.key)\t\(.value)"' "$ZCONFIG")
|
|
if [ "$(zproj "$key" .start.gitPull)" = "true" ] && [ -d "$root/.git" ]; then
|
|
# GIT_TERMINAL_PROMPT=0 so a repo that needs credentials fails fast instead
|
|
# of blocking the server start on an interactive "Username for ..." prompt.
|
|
local out rc
|
|
out="$( (cd "$root" && GIT_TERMINAL_PROMPT=0 git pull --ff-only 2>&1) )"; rc=$?
|
|
dim " git pull: $(printf '%s\n' "$out" | tail -1)"
|
|
[ "$rc" -eq 0 ] || warn " Auto-pull skipped (exit $rc) - starting with the current checkout. (git needs credentials here, or set start.gitPull=false)"
|
|
fi
|
|
}
|
|
|
|
start_python() { # <key> <port>
|
|
local key="$1" port="$2" root module app exe bv="" run_cmd=() what using_venv=1
|
|
root="$(zproj_root "$key")"
|
|
module="$(zproj "$key" .startModule)"
|
|
app="$(zproj "$key" .startApp)"
|
|
[ -d "$root" ] || { err "Project root not found: $root"; return 1; }
|
|
[ -n "$module" ] || [ -n "$app" ] || { err "Project '$key' (kind=python) needs 'startModule' (python -m ...) or 'startApp' (uvicorn app:app) in zconfig.json."; return 1; }
|
|
|
|
# Prefer the project venv. The Scripts/python.exe branch is a *Windows* venv
|
|
# layout — only runnable from a Windows-family shell (Git Bash/MSYS/Cygwin).
|
|
# On WSL/Linux/macOS a Windows-built venv may sit on a mounted drive and look
|
|
# executable but can't exec, so guard that branch to fall through to python3.
|
|
if [ -x "$root/.venv/bin/python" ]; then exe="$root/.venv/bin/python"
|
|
elif [ -x "$root/.venv/Scripts/python.exe" ] && [[ "$OSTYPE" == msys* || "$OSTYPE" == cygwin* ]]; then exe="$root/.venv/Scripts/python.exe"
|
|
elif command -v python3 >/dev/null 2>&1; then exe="python3"; using_venv=0
|
|
else exe="python"; using_venv=0; fi
|
|
|
|
# Optional convention: scripts/build_version_tool.py bumps the version on dev start.
|
|
if [ -f "$root/scripts/build_version_tool.py" ]; then
|
|
bv="$(cd "$root" && "$exe" scripts/build_version_tool.py bump 2>/dev/null | tail -1)"
|
|
[ -n "$bv" ] || bv="$(cd "$root" && "$exe" scripts/build_version_tool.py get 2>/dev/null | tail -1)"
|
|
fi
|
|
|
|
# startApp (uvicorn ASGI target, e.g. app.main:app) takes precedence over
|
|
# startModule (python -m ...). uvicorn is run via the venv python's -m so no
|
|
# PATH juggling is needed, and it gets zstart's bind-host + dev port.
|
|
if [ -n "$app" ]; then
|
|
run_cmd=("$exe" -m uvicorn "$app" --host "$bind_host" --port "$port" --reload)
|
|
what="uvicorn $app"
|
|
else
|
|
run_cmd=("$exe" -m "$module")
|
|
what="python -m $module"
|
|
fi
|
|
|
|
printf '\n'; info "=== zstart ($(zproj "$key" .label)) ==="
|
|
# No project venv found - warn, since the system interpreter usually lacks the
|
|
# app's deps (the failure would otherwise be a cryptic ModuleNotFoundError).
|
|
if [ "$using_venv" -eq 0 ]; then
|
|
warn "No project venv at $root/.venv - using system '$exe' (its deps may be missing)."
|
|
dim " Create one: (cd \"$root\" && python3 -m venv .venv && .venv/bin/pip install -e .)"
|
|
fi
|
|
info "Starting $what on port $port..."
|
|
[ -n "$bv" ] && note "Build Version: $bv"
|
|
show_project_motd "$root"
|
|
dim "Press Ctrl+C to stop the server"
|
|
warn_if_privileged_port "$port"
|
|
printf '\n'
|
|
|
|
if [ "$detached" -eq 1 ]; then
|
|
detach "$key" "$root" "${run_cmd[@]}"
|
|
else
|
|
( cd "$root" && exec "${run_cmd[@]}" )
|
|
fi
|
|
}
|
|
|
|
start_vite() { # <key> <port>
|
|
local key="$1" port="$2" root
|
|
root="$(zproj_root "$key")"
|
|
[ -d "$root" ] || { err "Project root not found: $root"; return 1; }
|
|
[ -f "$root/package.json" ] || { err "package.json not found in $root"; return 1; }
|
|
|
|
printf '\n'; info "=== zstart ($(zproj "$key" .label) - Vite) ==="
|
|
dim "Directory: $root"
|
|
info "URL: http://${bind_host}:${port}/"
|
|
printf '\n'
|
|
|
|
if [ ! -d "$root/node_modules" ]; then
|
|
warn "node_modules missing - running npm install..."
|
|
(cd "$root" && npm install) || { err "npm install failed"; return 1; }
|
|
fi
|
|
|
|
show_project_motd "$root"
|
|
dim "Press Ctrl+C to stop the dev server"
|
|
warn_if_privileged_port "$port"
|
|
printf '\n'
|
|
|
|
if [ "$detached" -eq 1 ]; then
|
|
detach "$key" "$root" npm run dev -- --host "$bind_host" --port "$port"
|
|
else
|
|
( cd "$root" && exec npm run dev -- --host "$bind_host" --port "$port" )
|
|
fi
|
|
}
|
|
|
|
start_next() { # <key> <port>
|
|
local key="$1" port="$2" root
|
|
root="$(zproj_root "$key")"
|
|
[ -d "$root" ] || { err "Project root not found: $root"; return 1; }
|
|
[ -f "$root/package.json" ] || { err "package.json not found in $root"; return 1; }
|
|
|
|
printf '\n'; info "=== zstart ($(zproj "$key" .label) - Next.js) ==="
|
|
info "Starting on port $port..."
|
|
printf '\n'
|
|
show_project_motd "$root"
|
|
dim "Press Ctrl+C to stop the server"
|
|
warn_if_privileged_port "$port"
|
|
printf '\n'
|
|
|
|
export PORT="$port"
|
|
if [ "$detached" -eq 1 ]; then
|
|
detach "$key" "$root" npm run dev
|
|
else
|
|
( cd "$root" && exec npm run dev )
|
|
fi
|
|
}
|
|
|
|
rc=0
|
|
for key in "${projects[@]}"; do
|
|
zproj_require "$key"
|
|
kind="$(zproj "$key" .kind)"
|
|
if [ "$port_override" -gt 0 ] 2>/dev/null; then port="$port_override"
|
|
else port="$(zproj "$key" .ports.dev)"; [[ "$port" =~ ^[0-9]+$ ]] || port=3000; fi
|
|
|
|
start_prep "$key"
|
|
|
|
case "$kind" in
|
|
python) start_python "$key" "$port" || rc=1 ;;
|
|
vite) start_vite "$key" "$port" || rc=1 ;;
|
|
nextjs) start_next "$key" "$port" || rc=1 ;;
|
|
*)
|
|
printf '\n'
|
|
warn "Project '$key' has kind '$kind' - no local dev server to start."
|
|
dim "Supported kinds for zstart: python, vite, nextjs"
|
|
;;
|
|
esac
|
|
done
|
|
exit "$rc"
|