mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-01 08:59:46 +02:00
feat: single dev run (UI+backend via concurrently) + preview_urls; UI on base port
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c099dad03d
commit
de88d4f21e
5 changed files with 129 additions and 44 deletions
|
|
@ -12,13 +12,16 @@ Conductor gives every workspace a block of **10 ports** starting at
|
|||
|
||||
| Port | Service | Script |
|
||||
| --------------- | ------------------ | ----------------------- |
|
||||
| `CONDUCTOR_PORT` | Backend (uvicorn) | `run-backend.sh` |
|
||||
| `CONDUCTOR_PORT + 1` | UI (Next.js) | `run-ui.sh` |
|
||||
| `CONDUCTOR_PORT` | UI (Next.js) | `run-ui.sh` |
|
||||
| `CONDUCTOR_PORT + 1` | Backend (uvicorn) | `run-backend.sh` |
|
||||
| `+2 .. +9` | reserved | — |
|
||||
|
||||
The UI is wired to its own workspace's backend (`NEXT_PUBLIC_BACKEND_URL`,
|
||||
`BACKEND_URL`) and the backend's CORS / `UI_APP_URL` point back at its own UI —
|
||||
all derived from `$CONDUCTOR_PORT`, so no two workspaces interfere.
|
||||
The UI sits on the base port so Conductor's **Open** button (`preview_urls`)
|
||||
lands on it directly — preview templates only substitute `$CONDUCTOR_PORT`, with
|
||||
no `+1` arithmetic. The UI is wired to its own workspace's backend
|
||||
(`NEXT_PUBLIC_BACKEND_URL`, `BACKEND_URL`) and the backend's CORS / `UI_APP_URL`
|
||||
point back at its own UI — all derived from `$CONDUCTOR_PORT`, so no two
|
||||
workspaces interfere.
|
||||
|
||||
### Shared, NOT per-workspace
|
||||
|
||||
|
|
@ -32,21 +35,28 @@ for everyone.
|
|||
|
||||
## The Run menu
|
||||
|
||||
Conductor shows three buttons (`run_mode = "concurrent"`, so they coexist):
|
||||
> **Important:** a Conductor workspace runs **one run script at a time**.
|
||||
> `run_mode = "concurrent"` lets *different workspaces* run simultaneously — it
|
||||
> does **not** let you click two run buttons in the *same* workspace. That's why
|
||||
> **dev** starts the UI and backend together instead of relying on two buttons.
|
||||
|
||||
- **backend** (default) — FastAPI/uvicorn with `--reload` on `$CONDUCTOR_PORT`
|
||||
- **ui** — Next.js dev server on `$CONDUCTOR_PORT + 1`
|
||||
- **worker** — Arq worker; start in just one workspace
|
||||
The Run dropdown offers:
|
||||
|
||||
Start **backend** and **ui** in each workspace you want to use. Start **worker**
|
||||
once (e.g. in your primary workspace) when you need background jobs — the same
|
||||
way you used to run a single arq worker in VS Code.
|
||||
- **dev** (default) — UI (`$CONDUCTOR_PORT`) **and** backend (`$CONDUCTOR_PORT+1`)
|
||||
together, via `concurrently`. **Use this day to day.**
|
||||
- **ui** — Next.js only, for debugging the frontend alone.
|
||||
- **backend** — uvicorn only, for debugging the API alone.
|
||||
- **worker** — Arq worker; start in just one workspace.
|
||||
|
||||
Hit **dev** and both servers come up; click Conductor's **Open** button to launch
|
||||
the UI. Start **worker** once (e.g. in your primary workspace) when you need
|
||||
background jobs — the same way you used to run a single arq worker in VS Code.
|
||||
|
||||
## Proving you're in the right worktree
|
||||
|
||||
`run-ui.sh` exports `NEXT_PUBLIC_WORKSPACE_NAME=$CONDUCTOR_WORKSPACE_NAME`, which
|
||||
`ui/src/components/WorkspaceBadge.tsx` renders as a small color-coded pill in the
|
||||
bottom-left corner (e.g. `⬡ pattaya :8201`). The color is derived from the
|
||||
bottom-left corner (e.g. `⬡ pattaya :8200`). The color is derived from the
|
||||
workspace name, so two worktrees are instantly distinguishable. The badge is
|
||||
invisible in production and in a plain `npm run dev` (no env var set).
|
||||
|
||||
|
|
@ -69,10 +79,11 @@ The first setup is slow (deps); afterwards the Run buttons are instant.
|
|||
|
||||
| File | Purpose |
|
||||
| --------------------- | -------------------------------------------------- |
|
||||
| `settings.toml` | Conductor config: setup + run scripts, run_mode |
|
||||
| `settings.toml` | Conductor config: setup + run scripts, preview_urls |
|
||||
| `setup.sh` | One-time workspace bootstrap |
|
||||
| `run-backend.sh` | Foreground uvicorn on `$CONDUCTOR_PORT` |
|
||||
| `run-ui.sh` | Foreground Next.js on `$CONDUCTOR_PORT + 1` |
|
||||
| `run-dev.sh` | Default run: UI + backend together (`concurrently`) |
|
||||
| `run-ui.sh` | Foreground Next.js on `$CONDUCTOR_PORT` |
|
||||
| `run-backend.sh` | Foreground uvicorn on `$CONDUCTOR_PORT + 1` |
|
||||
| `run-worker.sh` | Foreground Arq worker (run in one workspace only) |
|
||||
| `../.worktreeinclude` | Gitignored env files Conductor copies per workspace |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
# Conductor run script — Backend (FastAPI/uvicorn) for THIS workspace.
|
||||
#
|
||||
# Binds $CONDUCTOR_PORT and points CORS / UI_APP_URL at this workspace's UI
|
||||
# ($CONDUCTOR_PORT + 1). Runs in the FOREGROUND with exec (no &) so Conductor's
|
||||
# SIGHUP cleanly tears it down.
|
||||
# Binds $CONDUCTOR_PORT + 1 and points CORS / UI_APP_URL at this workspace's UI
|
||||
# (which runs on $CONDUCTOR_PORT). Runs in the FOREGROUND with exec (no &) so
|
||||
# Conductor's SIGHUP cleanly tears it down.
|
||||
set -euo pipefail
|
||||
cd "${CONDUCTOR_WORKSPACE_PATH:-$PWD}"
|
||||
|
||||
PORT="${CONDUCTOR_PORT:-8000}"
|
||||
UI_PORT="$((PORT + 1))"
|
||||
UI_PORT="${CONDUCTOR_PORT:-8000}"
|
||||
BACKEND_PORT="$((UI_PORT + 1))"
|
||||
|
||||
# Load the workspace's api/.env, then override the port-specific bits. The
|
||||
# backend reads config via os.getenv, and these exports happen after the source,
|
||||
# so they win over the values copied from the main checkout (which assume 8000).
|
||||
# so they win over the values copied from the main checkout (which assume 8000/3000).
|
||||
if [[ -f api/.env ]]; then set -a; # shellcheck disable=SC1091
|
||||
source api/.env; set +a; fi
|
||||
export FASTAPI_PORT="$PORT"
|
||||
export FASTAPI_PORT="$BACKEND_PORT"
|
||||
export UI_APP_URL="http://localhost:${UI_PORT}"
|
||||
export CORS_ALLOWED_ORIGINS="http://localhost:${UI_PORT},http://127.0.0.1:${UI_PORT}"
|
||||
|
||||
|
|
@ -26,5 +26,5 @@ fi
|
|||
# shellcheck disable=SC1091
|
||||
source venv/bin/activate
|
||||
|
||||
echo "[backend] workspace=${CONDUCTOR_WORKSPACE_NAME:-?} port=${PORT} ui=${UI_PORT}"
|
||||
exec uvicorn api.app:app --host 0.0.0.0 --port "$PORT" --reload --reload-dir api
|
||||
echo "[backend] workspace=${CONDUCTOR_WORKSPACE_NAME:-?} port=${BACKEND_PORT} ui=${UI_PORT}"
|
||||
exec uvicorn api.app:app --host 0.0.0.0 --port "$BACKEND_PORT" --reload --reload-dir api
|
||||
|
|
|
|||
59
.conductor/run-dev.sh
Executable file
59
.conductor/run-dev.sh
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env bash
|
||||
# Conductor run script — full dev stack (UI + backend) for THIS workspace.
|
||||
#
|
||||
# A Conductor workspace runs ONE run script at a time (run_mode governs
|
||||
# concurrency ACROSS workspaces, not multiple run buttons within one). So to get
|
||||
# the UI AND the backend up together, we launch both with `concurrently`.
|
||||
#
|
||||
# Conductor stops a run with SIGHUP (then SIGKILL after 200ms). Neither npx nor
|
||||
# concurrently reliably tears down their child tree on SIGHUP, so we supervise:
|
||||
# trap the signal and recursively kill the whole tree ourselves. We do NOT exec,
|
||||
# so this shell stays alive to handle the trap.
|
||||
#
|
||||
# Ports: UI on $CONDUCTOR_PORT, backend on $CONDUCTOR_PORT + 1.
|
||||
set -uo pipefail
|
||||
cd "${CONDUCTOR_WORKSPACE_PATH:-$PWD}"
|
||||
|
||||
UI_PORT="${CONDUCTOR_PORT:-8000}"
|
||||
BACKEND_PORT="$((UI_PORT + 1))"
|
||||
|
||||
# Ensure node/npx is on PATH (load nvm + honor .nvmrc if needed).
|
||||
if ! command -v npx >/dev/null 2>&1; then
|
||||
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
|
||||
# shellcheck disable=SC1091
|
||||
[[ -s "$NVM_DIR/nvm.sh" ]] && . "$NVM_DIR/nvm.sh"
|
||||
command -v nvm >/dev/null 2>&1 && nvm use >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Prefer a locally-installed concurrently (fast); fall back to npx, which fetches
|
||||
# it once then caches — so this also works in workspaces created before it existed.
|
||||
if [[ -x ui/node_modules/.bin/concurrently ]]; then
|
||||
RUNNER=(ui/node_modules/.bin/concurrently)
|
||||
else
|
||||
RUNNER=(npx --yes concurrently)
|
||||
fi
|
||||
|
||||
# Recursively SIGTERM a process and every descendant (children first). npm/npx/
|
||||
# next don't reliably forward signals, so we signal each PID in the tree directly.
|
||||
kill_tree() {
|
||||
local pid=$1 child
|
||||
for child in $(pgrep -P "$pid" 2>/dev/null); do kill_tree "$child"; done
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
trap - HUP INT TERM EXIT
|
||||
[[ -n "${CHILD:-}" ]] && kill_tree "$CHILD"
|
||||
exit 0
|
||||
}
|
||||
trap shutdown HUP INT TERM EXIT
|
||||
|
||||
echo "[dev] workspace=${CONDUCTOR_WORKSPACE_NAME:-?} ui=:${UI_PORT} backend=:${BACKEND_PORT}"
|
||||
"${RUNNER[@]}" \
|
||||
--names "ui,backend" \
|
||||
--prefix-colors "magenta,cyan" \
|
||||
--kill-others \
|
||||
"bash .conductor/run-ui.sh" \
|
||||
"bash .conductor/run-backend.sh" &
|
||||
CHILD=$!
|
||||
wait "$CHILD"
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
# Conductor run script — UI (Next.js) for THIS workspace.
|
||||
#
|
||||
# Binds $CONDUCTOR_PORT + 1, talks to this workspace's backend on $CONDUCTOR_PORT,
|
||||
# and tags the build with the workspace identity (NEXT_PUBLIC_WORKSPACE_NAME) so
|
||||
# the in-app WorkspaceBadge shows which worktree you're looking at.
|
||||
# Runs in the FOREGROUND with exec (no &) so Conductor can stop it cleanly.
|
||||
# Binds $CONDUCTOR_PORT (so Conductor's Open button / preview_urls land here),
|
||||
# talks to this workspace's backend on $CONDUCTOR_PORT + 1, and tags the build
|
||||
# with the workspace identity (NEXT_PUBLIC_WORKSPACE_NAME) so the in-app
|
||||
# WorkspaceBadge shows which worktree you're looking at. Foreground exec (no &)
|
||||
# so Conductor can stop it cleanly.
|
||||
set -euo pipefail
|
||||
cd "${CONDUCTOR_WORKSPACE_PATH:-$PWD}"
|
||||
|
||||
PORT="${CONDUCTOR_PORT:-8000}"
|
||||
UI_PORT="$((PORT + 1))"
|
||||
BACKEND="http://localhost:${PORT}"
|
||||
UI_PORT="${CONDUCTOR_PORT:-8000}"
|
||||
BACKEND_PORT="$((UI_PORT + 1))"
|
||||
BACKEND="http://localhost:${BACKEND_PORT}"
|
||||
|
||||
# Ensure node is on PATH (load nvm + honor .nvmrc if needed).
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
|
|
|
|||
|
|
@ -1,38 +1,52 @@
|
|||
# Conductor project config — shared, committed so every contributor (and every
|
||||
# new workspace) gets the same per-worktree dev workflow.
|
||||
#
|
||||
# Model: each Conductor workspace is its own git worktree and gets its own
|
||||
# 10-port range starting at $CONDUCTOR_PORT. We use:
|
||||
# $CONDUCTOR_PORT -> backend (FastAPI/uvicorn)
|
||||
# $CONDUCTOR_PORT + 1 -> ui (Next.js)
|
||||
# Each Conductor workspace is its own git worktree and gets its own 10-port range
|
||||
# starting at $CONDUCTOR_PORT. We use:
|
||||
# $CONDUCTOR_PORT -> ui (Next.js) <- Conductor's Open button lands here
|
||||
# $CONDUCTOR_PORT + 1 -> backend (FastAPI/uvicorn)
|
||||
# +2 .. +9 -> reserved for future per-workspace services
|
||||
#
|
||||
# Postgres/Redis/MinIO are a single SHARED Docker stack (project name "dograh"),
|
||||
# not per-workspace — see .conductor/setup.sh and .conductor/README.md.
|
||||
"$schema" = "https://conductor.build/schemas/settings.repo.schema.json"
|
||||
|
||||
# Conductor's Open button. The UI runs on $CONDUCTOR_PORT, so this opens it.
|
||||
# (Preview URL templates only substitute $CONDUCTOR_PORT — no "+1" arithmetic —
|
||||
# which is why the UI, the thing you actually open, sits on the base port.)
|
||||
[[preview_urls]]
|
||||
name = "UI"
|
||||
url = "http://localhost:$CONDUCTOR_PORT"
|
||||
|
||||
[scripts]
|
||||
# Runs once when a workspace is created: copies gitignored env files, inits the
|
||||
# pipecat submodule, builds the venv + node_modules, ensures the shared Docker
|
||||
# stack, and runs migrations.
|
||||
setup = "bash .conductor/setup.sh"
|
||||
|
||||
# concurrent => multiple workspaces can run their dev servers at the same time
|
||||
# (each on its own CONDUCTOR_PORT range), and within a workspace the backend +
|
||||
# ui + worker run buttons don't kill each other.
|
||||
# concurrent => multiple WORKSPACES can run their dev servers at the same time
|
||||
# (each on its own CONDUCTOR_PORT range). NOTE: within a single workspace,
|
||||
# Conductor runs ONE run script at a time — that's why the default "dev" script
|
||||
# below starts the UI and backend together rather than relying on two buttons.
|
||||
run_mode = "concurrent"
|
||||
|
||||
# Backend (FastAPI/uvicorn) on $CONDUCTOR_PORT. Default Run button.
|
||||
[scripts.run.backend]
|
||||
command = "bash .conductor/run-backend.sh"
|
||||
icon = "server"
|
||||
# Full dev stack: UI ($CONDUCTOR_PORT) + backend ($CONDUCTOR_PORT+1) together.
|
||||
# Default Run button — this is the one to use day to day.
|
||||
[scripts.run.dev]
|
||||
command = "bash .conductor/run-dev.sh"
|
||||
icon = "play"
|
||||
default = true
|
||||
|
||||
# UI (Next.js) on $CONDUCTOR_PORT + 1, wired to this workspace's backend.
|
||||
# UI only (Next.js) on $CONDUCTOR_PORT — handy for debugging the frontend alone.
|
||||
[scripts.run.ui]
|
||||
command = "bash .conductor/run-ui.sh"
|
||||
icon = "play"
|
||||
|
||||
# Backend only (FastAPI/uvicorn) on $CONDUCTOR_PORT + 1 — debugging the API alone.
|
||||
[scripts.run.backend]
|
||||
command = "bash .conductor/run-backend.sh"
|
||||
icon = "server"
|
||||
|
||||
# Arq background worker. Start in ONE workspace only — all workspaces share the
|
||||
# same Redis/Postgres, so a single worker drains the queue for everyone.
|
||||
[scripts.run.worker]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue