PNRxA commited on
Commit
cca484c
Β·
1 Parent(s): 617b567

feat: add dev-worktree.sh to set up and run a worktree for local dev

Browse files

Makes a freshly created git worktree runnable in one command:

- Copies api/.env from the main worktree if the current one lacks it
(never overwrites an existing .env).
- --fresh wipes the worktree's api/db and api/cache for a clean slate.
On the main worktree this would delete primary data, so it prompts for
confirmation (or --yes); linked worktrees wipe without asking.
- Installs web deps (npm ci, falling back to npm install) when missing.
- Runs the Rust API (cargo run) and the Vite dev server (npm run dev)
together; Ctrl+C or either process exiting tears down both.

Also supports --release, --setup-only, and --help, and documents the
script in scripts/README.md.

Files changed (2) hide show
  1. scripts/README.md +28 -0
  2. scripts/dev-worktree.sh +246 -0
scripts/README.md CHANGED
@@ -2,6 +2,34 @@
2
 
3
  Utility scripts for developing, testing, releasing, and seeding OpenPosterDB.
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  ## seed.sh
6
 
7
  Warms the OpenPosterDB cache by requesting posters for titles from the IMDB dataset. Entries are processed newest-first, using `endYear` for series (if available) and `startYear` otherwise.
 
2
 
3
  Utility scripts for developing, testing, releasing, and seeding OpenPosterDB.
4
 
5
+ ## dev-worktree.sh
6
+
7
+ Prepares a freshly created git worktree for local development and runs it. A new
8
+ worktree is missing all the gitignored bits the app needs (`api/.env`, `api/db`,
9
+ `api/cache`, `web/node_modules`); this gets one runnable in a single command.
10
+
11
+ ```bash
12
+ ./scripts/dev-worktree.sh # set up (if needed) then run API + web
13
+ ./scripts/dev-worktree.sh --fresh # also wipe api/db + api/cache first
14
+ ./scripts/dev-worktree.sh --release # build/run the API in release mode
15
+ ./scripts/dev-worktree.sh --setup-only # copy env + install deps, don't run
16
+ ```
17
+
18
+ Run it from inside the worktree you want to set up (any subdirectory is fine).
19
+
20
+ ### What it does
21
+
22
+ 1. Copies `api/.env` from the **main** worktree if the current worktree lacks one (never overwrites an existing one).
23
+ 2. With `--fresh`, deletes the current worktree's `api/db` and `api/cache` for a clean slate before running. In the **main** worktree this would wipe your primary data, so it asks for confirmation first (or pass `--yes`); in a linked worktree it wipes without asking.
24
+ 3. Installs web dependencies (`npm ci`, falling back to `npm install`) if `web/node_modules` is missing.
25
+ 4. Runs the Rust API (`cargo run` in `api/`, on `http://localhost:3000`) and the Vite dev server (`npm run dev` in `web/`, usually `http://localhost:5173`) together. Press Ctrl+C β€” or let either process exit β€” to stop both.
26
+
27
+ > The dev UI is served by Vite over plain HTTP at `http://localhost:5173`. If login
28
+ > appears broken, add `COOKIE_SECURE=false` to `api/.env` (see the main README) β€”
29
+ > otherwise the browser drops the auth cookie.
30
+
31
+ ---
32
+
33
  ## seed.sh
34
 
35
  Warms the OpenPosterDB cache by requesting posters for titles from the IMDB dataset. Entries are processed newest-first, using `endYear` for series (if available) and `startYear` otherwise.
scripts/dev-worktree.sh ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ #
3
+ # dev-worktree.sh β€” prepare and run an OpenPosterDB git worktree for local dev.
4
+ #
5
+ # A freshly created git worktree is missing all the gitignored bits that the
6
+ # app needs (api/.env, api/db, api/cache, web/node_modules). This script makes
7
+ # a new worktree runnable in one command:
8
+ #
9
+ # 1. Copies api/.env from the MAIN worktree if this worktree doesn't have one.
10
+ # 2. Installs web dependencies (npm) if web/node_modules is missing.
11
+ # 3. Runs the Rust API (api/) and the Vite web dev server (web/) together.
12
+ # Ctrl+C β€” or either process exiting on its own β€” stops both.
13
+ #
14
+ # Run it from inside the worktree you want to set up (any subdirectory is fine).
15
+
16
+ set -euo pipefail
17
+
18
+ usage() {
19
+ cat <<'EOF'
20
+ dev-worktree.sh β€” prepare and run an OpenPosterDB worktree for local dev.
21
+
22
+ Usage:
23
+ ./scripts/dev-worktree.sh Set up (if needed) then run API + web.
24
+ ./scripts/dev-worktree.sh --fresh Also delete this worktree's api/db and
25
+ api/cache first, for a clean slate.
26
+ ./scripts/dev-worktree.sh --release Build/run the API in release mode.
27
+ ./scripts/dev-worktree.sh --setup-only Copy env + install deps, but don't run.
28
+ ./scripts/dev-worktree.sh --yes Skip the --fresh confirmation prompt
29
+ (needed for non-interactive use).
30
+ ./scripts/dev-worktree.sh --help Show this help.
31
+
32
+ Note: --fresh in the MAIN worktree deletes your primary db/cache, so it asks
33
+ for confirmation first (or pass --yes). In a linked worktree it wipes without
34
+ asking.
35
+
36
+ What it does:
37
+ 1. Copies api/.env from the main worktree if this one lacks it.
38
+ 2. Installs web deps (npm) if web/node_modules is missing.
39
+ 3. Runs the Rust API (api/) and the Vite dev server (web/) together;
40
+ Ctrl+C β€” or either process exiting β€” stops both.
41
+
42
+ Run it from inside the worktree you want to set up.
43
+ EOF
44
+ }
45
+
46
+ # --- Parse arguments --------------------------------------------------------
47
+ FRESH=0
48
+ SETUP_ONLY=0
49
+ ASSUME_YES=0
50
+ CARGO_RELEASE="" # "" = debug build, "--release" = release build
51
+
52
+ while [ $# -gt 0 ]; do
53
+ case "$1" in
54
+ --fresh|--clean) FRESH=1 ;;
55
+ --release) CARGO_RELEASE="--release" ;;
56
+ --setup-only) SETUP_ONLY=1 ;;
57
+ -y|--yes|--force) ASSUME_YES=1 ;;
58
+ -h|--help) usage; exit 0 ;;
59
+ *)
60
+ echo "Unknown option: $1" >&2
61
+ echo "Run '$0 --help' for usage." >&2
62
+ exit 2
63
+ ;;
64
+ esac
65
+ shift
66
+ done
67
+
68
+ # --- Locate the current and main worktrees ----------------------------------
69
+ if ! WT="$(git rev-parse --show-toplevel 2>/dev/null)"; then
70
+ echo "Error: not inside a git repository. cd into your worktree first." >&2
71
+ exit 1
72
+ fi
73
+
74
+ # The main worktree is the parent directory of the shared git dir. For a linked
75
+ # worktree, --git-common-dir resolves to <main>/.git; for the main worktree it
76
+ # is the main repo's own .git. Either way, dirname gives the main worktree root.
77
+ # Capture into its own variable (rather than nesting inside dirname) so a git
78
+ # failure β€” e.g. git older than 2.31, which lacks --path-format β€” surfaces as a
79
+ # clear error instead of silently degrading MAIN_ROOT to ".".
80
+ if ! COMMON_GIT_DIR="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || [ -z "$COMMON_GIT_DIR" ]; then
81
+ echo "Error: could not determine the main worktree (git rev-parse failed; needs git >= 2.31)." >&2
82
+ exit 1
83
+ fi
84
+ MAIN_ROOT="$(dirname "$COMMON_GIT_DIR")"
85
+
86
+ if [ ! -d "$WT/api" ] || [ ! -d "$WT/web" ]; then
87
+ echo "Error: $WT does not look like the OpenPosterDB repo (missing api/ or web/)." >&2
88
+ exit 1
89
+ fi
90
+
91
+ echo ">> Worktree : $WT"
92
+ echo ">> Main repo: $MAIN_ROOT"
93
+
94
+ # --- Tooling check ----------------------------------------------------------
95
+ for tool in cargo npm; do
96
+ if ! command -v "$tool" >/dev/null 2>&1; then
97
+ echo "Error: '$tool' not found in PATH." >&2
98
+ exit 1
99
+ fi
100
+ done
101
+
102
+ # --- 1. Copy api/.env if this worktree doesn't have one ---------------------
103
+ SRC_ENV="$MAIN_ROOT/api/.env"
104
+ DST_ENV="$WT/api/.env"
105
+
106
+ if [ -f "$DST_ENV" ]; then
107
+ echo ">> api/.env already present β€” leaving it untouched."
108
+ elif [ "$WT" = "$MAIN_ROOT" ]; then
109
+ echo "Error: this is the main worktree and it has no api/.env." >&2
110
+ echo " Create it first: (cd \"$WT/api\" && cp .env.example .env) then fill in your keys." >&2
111
+ exit 1
112
+ elif [ ! -d "$MAIN_ROOT/api" ]; then
113
+ # MAIN_ROOT isn't a usable checkout (e.g. a bare-repo-centric layout where
114
+ # the common git dir's parent is not a worktree). Point at THIS worktree.
115
+ echo "Error: couldn't find a main worktree with api/ to copy .env from (looked in $MAIN_ROOT)." >&2
116
+ echo " Create api/.env directly in this worktree:" >&2
117
+ echo " (cd \"$WT/api\" && cp .env.example .env) then fill in your keys." >&2
118
+ exit 1
119
+ elif [ -f "$SRC_ENV" ]; then
120
+ cp "$SRC_ENV" "$DST_ENV"
121
+ echo ">> Copied api/.env from main worktree -> $DST_ENV"
122
+ else
123
+ echo "Error: the main worktree has no api/.env to copy ($SRC_ENV)." >&2
124
+ echo " Create it first: (cd \"$MAIN_ROOT/api\" && cp .env.example .env) then fill in your keys." >&2
125
+ exit 1
126
+ fi
127
+
128
+ # --- 2. Optional fresh start: wipe this worktree's local DB + cache ---------
129
+ if [ "$FRESH" -eq 1 ]; then
130
+ # In the main worktree, --fresh would delete your PRIMARY db/cache, so
131
+ # require confirmation there β€” an interactive prompt, or --yes for
132
+ # non-interactive use. In a linked worktree the data is throwaway, so wipe
133
+ # it without asking.
134
+ if [ "$WT" = "$MAIN_ROOT" ] && [ "$ASSUME_YES" -ne 1 ]; then
135
+ echo "Warning: --fresh on the main worktree deletes your PRIMARY local data:" >&2
136
+ echo " $WT/api/db" >&2
137
+ echo " $WT/api/cache" >&2
138
+ if [ -t 0 ]; then
139
+ printf "Delete them and start fresh? [y/N] " >&2
140
+ read -r reply || reply=""
141
+ case "$reply" in
142
+ [yY]|[yY][eE][sS]) ;;
143
+ *) echo "Aborted β€” your data was left untouched." >&2; exit 1 ;;
144
+ esac
145
+ else
146
+ echo "Refusing: not an interactive terminal. Re-run with --yes to confirm." >&2
147
+ exit 1
148
+ fi
149
+ fi
150
+ echo ">> --fresh: wiping this worktree's local data before starting."
151
+ for sub in db cache; do
152
+ target="$WT/api/$sub"
153
+ if [ -e "$target" ]; then
154
+ echo " removing $target"
155
+ rm -rf "$target"
156
+ else
157
+ echo " (nothing at $target)"
158
+ fi
159
+ done
160
+ fi
161
+
162
+ # --- 3. Install web dependencies if missing ---------------------------------
163
+ if [ ! -d "$WT/web/node_modules" ]; then
164
+ echo ">> Installing web dependencies (first run for this worktree)..."
165
+ if ! ( cd "$WT/web" && npm ci ); then
166
+ echo ">> 'npm ci' failed; falling back to 'npm install'..."
167
+ ( cd "$WT/web" && npm install )
168
+ fi
169
+ fi
170
+
171
+ if [ "$SETUP_ONLY" -eq 1 ]; then
172
+ echo ">> Setup complete (--setup-only). Run again without it to start the servers."
173
+ exit 0
174
+ fi
175
+
176
+ # --- 4. Run the API and the web dev server together -------------------------
177
+ # Both servers run as background subshells. On shutdown we must tear down the
178
+ # whole tree (cargo + the API binary, npm + vite), not just the subshells.
179
+ #
180
+ # When this script leads its own process group β€” the normal case when it is run
181
+ # as a foreground command from an interactive shell β€” `kill 0` signals every
182
+ # process in that group, reaching all descendants cleanly. If instead it was
183
+ # launched without job control (from a wrapper script, a Makefile, an npm
184
+ # script, nohup, or any non-interactive parent) it shares the caller's process
185
+ # group, where `kill 0` would also signal unrelated processes. We detect that
186
+ # case and fall back to killing the two services (and their children) directly.
187
+ SCRIPT_PGID="$(ps -o pgid= -p "$$" 2>/dev/null | tr -d ' ')" || SCRIPT_PGID=""
188
+ API_PID=""
189
+ WEB_PID=""
190
+
191
+ # Terminate a process and all of its descendants, deepest first, so that a
192
+ # grandchild (e.g. vite under npm, or the API binary under cargo) doesn't get
193
+ # reparented and orphaned when its parent dies. Used only in the fallback path.
194
+ kill_tree() {
195
+ local pid="$1" child
196
+ for child in $(pgrep -P "$pid" 2>/dev/null); do
197
+ kill_tree "$child"
198
+ done
199
+ kill -TERM "$pid" 2>/dev/null || true
200
+ }
201
+
202
+ cleanup() {
203
+ trap - INT TERM EXIT
204
+ printf '\n>> Shutting down API + web dev server...\n'
205
+ if [ -n "$SCRIPT_PGID" ] && [ "$SCRIPT_PGID" = "$$" ]; then
206
+ # We lead our own group: one signal reaps both servers and their kids.
207
+ kill 0 2>/dev/null || true
208
+ else
209
+ # Not a group leader β€” signal only our own service subtrees, so we
210
+ # never hit the caller's process group.
211
+ for p in "$API_PID" "$WEB_PID"; do
212
+ [ -n "$p" ] || continue
213
+ kill_tree "$p"
214
+ done
215
+ fi
216
+ }
217
+ trap cleanup INT TERM EXIT
218
+
219
+ if [ -n "$CARGO_RELEASE" ]; then
220
+ echo ">> API backend: http://localhost:3000 (cargo run --release)"
221
+ else
222
+ echo ">> API backend: http://localhost:3000 (cargo run)"
223
+ fi
224
+ echo ">> Open the app: http://localhost:5173 (Vite dev server β€” uses the next free port if 5173 is taken)"
225
+ echo ">> Press Ctrl+C to stop both."
226
+ echo
227
+
228
+ # Rust API. The 'if' wrapper keeps a non-zero exit (e.g. a compile error) from
229
+ # tripping 'set -e' before we notify the parent, so a crash tears down both.
230
+ # $$ inside a subshell is the parent script's PID, which is what we want here.
231
+ (
232
+ if cd "$WT/api" && cargo run $CARGO_RELEASE; then ec=0; else ec=$?; fi
233
+ echo ">> [api] cargo run exited (code $ec)"
234
+ kill -TERM $$ 2>/dev/null || true
235
+ ) &
236
+ API_PID=$!
237
+
238
+ # Web dev server.
239
+ (
240
+ if cd "$WT/web" && npm run dev; then ec=0; else ec=$?; fi
241
+ echo ">> [web] dev server exited (code $ec)"
242
+ kill -TERM $$ 2>/dev/null || true
243
+ ) &
244
+ WEB_PID=$!
245
+
246
+ wait