diff --git a/README.md b/README.md index 2e38dc3..8c77feb 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ The example config ships with sample projects named by their kind — `pyapp`, ` "kind": "python", // python | vite | nextjs | edge | docker "localRoot": "C:\\dev\\myapp", // project folder on this machine "startModule": "myapp.main", // python kind: runs "python -m myapp.main" + // "startApp": "app.main:app", // ...or, for ASGI/FastAPI: uvicorn app.main:app --port --reload "ports": { "dev": 8080, "prod": 3000 }, // local dev port / direct server port "domain": "www.myapp.com", // public domain (health checks + verification) "start": { // optional zstart pre-steps @@ -135,7 +136,7 @@ The `.cmd` wrappers are the everyday interface. Every command takes one or more zstart [ ...] [-Port N] [-BindHost ] [-Detached] ``` -Starts each project's dev server using the handler for its `kind`: **python** runs `python -m ` (preferring the project's `.venv`), **vite** runs `npm run dev -- --host --port`, **nextjs** runs `npm run dev` with `PORT` set. Runs `npm install` automatically if `node_modules` is missing. A project's optional `start` config block runs first — `gitPull` fast-forwards the checkout and `env` sets process environment variables. Two more opt-in conveniences: if the project has a `motd/` folder of `.txt` files, one is shown (rotating) at startup; if it has `scripts/build_version_tool.py`, the build number is bumped on each start. +Starts each project's dev server using the handler for its `kind`: **python** runs `python -m ` — or, for an ASGI/FastAPI app, `uvicorn ` (e.g. `app.main:app`) with the dev port and `--reload` — preferring the project's `.venv`; **vite** runs `npm run dev -- --host --port`, **nextjs** runs `npm run dev` with `PORT` set. Runs `npm install` automatically if `node_modules` is missing. A project's optional `start` config block runs first — `gitPull` fast-forwards the checkout and `env` sets process environment variables. Two more opt-in conveniences: if the project has a `motd/` folder of `.txt` files, one is shown (rotating) at startup; if it has `scripts/build_version_tool.py`, the build number is bumped on each start. ```powershell zstart viteapp # dev server on its configured port diff --git a/bash/zconfig.example.json b/bash/zconfig.example.json index d0f018b..dcc28da 100644 --- a/bash/zconfig.example.json +++ b/bash/zconfig.example.json @@ -29,6 +29,7 @@ "kind": "python", "localRoot": "/home/youruser/code/pyapp", "startModule": "pyapp.main", + "_startApp_note": "ASGI/FastAPI app? Use \"startApp\": \"app.main:app\" instead of startModule (runs uvicorn --port --reload).", "ports": { "dev": 8080 }, "domain": "pyapp.yourdomain.com", "start": { diff --git a/bash/zstart b/bash/zstart index 51f6f0b..79fd1f2 100644 --- a/bash/zstart +++ b/bash/zstart @@ -8,8 +8,9 @@ # Usage: # zstart [ ...] [--port N] [--bind-host host] [--detached] # -# Handlers by kind: python (python -m , prefers .venv), -# vite (npm run dev -- --host --port), nextjs (npm run dev with PORT). +# Handlers by kind: python (python -m , or uvicorn for +# ASGI/FastAPI apps; prefers .venv), vite (npm run dev -- --host --port), +# nextjs (npm run dev with PORT). # Detached servers log to /tmp/zstart-.log ; stop them with zkill. set -uo pipefail @@ -67,11 +68,12 @@ start_prep() { # } start_python() { # - local key="$1" port="$2" root module exe bv="" + local key="$1" port="$2" root module app exe bv="" run_cmd=() what root="$(zproj_root "$key")" module="$(zproj "$key" .startModule)" + app="$(zproj "$key" .startApp)" [ -d "$root" ] || { err "Project root not found: $root"; return 1; } - [ -n "$module" ] || { err "Project '$key' (kind=python) needs 'startModule' in zconfig.json (e.g. \"startModule\": \"pyapp.main\")."; 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). @@ -88,8 +90,19 @@ start_python() { # [ -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)) ===" - info "Starting python -m $module on port $port..." + info "Starting $what on port $port..." [ -n "$bv" ] && note "Build Version: $bv" show_project_motd "$root" dim "Press Ctrl+C to stop the server" @@ -97,9 +110,9 @@ start_python() { # printf '\n' if [ "$detached" -eq 1 ]; then - detach "$key" "$root" "$exe" -m "$module" + detach "$key" "$root" "${run_cmd[@]}" else - ( cd "$root" && exec "$exe" -m "$module" ) + ( cd "$root" && exec "${run_cmd[@]}" ) fi } diff --git a/zconfig.example.json b/zconfig.example.json index bfa96cd..d8f090d 100644 --- a/zconfig.example.json +++ b/zconfig.example.json @@ -24,6 +24,7 @@ "kind": "python", "localRoot": "C:\\YourRoot\\pyapp", "startModule": "pyapp.main", + "_startApp_note": "ASGI/FastAPI app? Use \"startApp\": \"app.main:app\" instead of startModule (runs uvicorn --port --reload).", "ports": { "dev": 8080 }, "domain": "pyapp.yourdomain.com", "start": { diff --git a/zstart.ps1 b/zstart.ps1 index 5a8ff7c..df53a4c 100644 --- a/zstart.ps1 +++ b/zstart.ps1 @@ -12,8 +12,9 @@ # zstart viteapp -Port 3000 # zstart pyapp nextapp -Detached # -# Handlers by kind: python (python -m , prefers .venv), -# vite (npm run dev -- --host --port), nextjs (npm run dev with PORT). +# Handlers by kind: python (python -m , or uvicorn for +# ASGI/FastAPI apps; prefers .venv), vite (npm run dev -- --host --port), +# nextjs (npm run dev with PORT). # param( [Parameter(Position = 0, ValueFromRemainingArguments = $true)] @@ -45,12 +46,13 @@ function Test-PortNeedsAdmin { } function Start-PythonProject { - param([string]$Key, $Proj, [int]$ListenPort, [bool]$RunDetached) + 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 - if (-not $module) { - throw "Project '$Key' (kind=python) needs 'startModule' in zconfig.json (e.g. `"startModule`": `"pyapp.main`" runs 'python -m pyapp.main')." + $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" @@ -77,9 +79,20 @@ function Start-PythonProject { } } + # 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 - Write-Host "Starting python -m $module on port $ListenPort..." -ForegroundColor Cyan + Write-Host "Starting $what on port $ListenPort..." -ForegroundColor Cyan if (-not [string]::IsNullOrWhiteSpace($buildVersion)) { Write-Host "Build Version: $buildVersion" -ForegroundColor Magenta } @@ -91,11 +104,11 @@ function Start-PythonProject { Write-Host "" if ($RunDetached) { - Start-Process -FilePath $exe -ArgumentList "-m", $module -WorkingDirectory $root | Out-Null + 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 -m $module + & $exe @runArgs } } @@ -201,7 +214,7 @@ foreach ($key in $Projects) { Invoke-ProjectStartPrep -Proj $proj switch ([string]$proj.kind) { - "python" { Start-PythonProject -Key $key -Proj $proj -ListenPort $devPort -RunDetached $Detached.IsPresent } + "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 {