a
File size: 9,829 Bytes
cca484c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#!/usr/bin/env bash
#
# dev-worktree.sh β€” prepare and run an OpenPosterDB git worktree for local dev.
#
# A freshly created git worktree is missing all the gitignored bits that the
# app needs (api/.env, api/db, api/cache, web/node_modules). This script makes
# a new worktree runnable in one command:
#
#   1. Copies api/.env from the MAIN worktree if this worktree doesn't have one.
#   2. Installs web dependencies (npm) if web/node_modules is missing.
#   3. Runs the Rust API (api/) and the Vite web dev server (web/) together.
#      Ctrl+C β€” or either process exiting on its own β€” stops both.
#
# Run it from inside the worktree you want to set up (any subdirectory is fine).

set -euo pipefail

usage() {
    cat <<'EOF'
dev-worktree.sh β€” prepare and run an OpenPosterDB worktree for local dev.

Usage:
  ./scripts/dev-worktree.sh              Set up (if needed) then run API + web.
  ./scripts/dev-worktree.sh --fresh      Also delete this worktree's api/db and
                                          api/cache first, for a clean slate.
  ./scripts/dev-worktree.sh --release     Build/run the API in release mode.
  ./scripts/dev-worktree.sh --setup-only  Copy env + install deps, but don't run.
  ./scripts/dev-worktree.sh --yes         Skip the --fresh confirmation prompt
                                          (needed for non-interactive use).
  ./scripts/dev-worktree.sh --help        Show this help.

Note: --fresh in the MAIN worktree deletes your primary db/cache, so it asks
for confirmation first (or pass --yes). In a linked worktree it wipes without
asking.

What it does:
  1. Copies api/.env from the main worktree if this one lacks it.
  2. Installs web deps (npm) if web/node_modules is missing.
  3. Runs the Rust API (api/) and the Vite dev server (web/) together;
     Ctrl+C β€” or either process exiting β€” stops both.

Run it from inside the worktree you want to set up.
EOF
}

# --- Parse arguments --------------------------------------------------------
FRESH=0
SETUP_ONLY=0
ASSUME_YES=0
CARGO_RELEASE=""   # "" = debug build, "--release" = release build

while [ $# -gt 0 ]; do
    case "$1" in
        --fresh|--clean)  FRESH=1 ;;
        --release)        CARGO_RELEASE="--release" ;;
        --setup-only)     SETUP_ONLY=1 ;;
        -y|--yes|--force) ASSUME_YES=1 ;;
        -h|--help)        usage; exit 0 ;;
        *)
            echo "Unknown option: $1" >&2
            echo "Run '$0 --help' for usage." >&2
            exit 2
            ;;
    esac
    shift
done

# --- Locate the current and main worktrees ----------------------------------
if ! WT="$(git rev-parse --show-toplevel 2>/dev/null)"; then
    echo "Error: not inside a git repository. cd into your worktree first." >&2
    exit 1
fi

# The main worktree is the parent directory of the shared git dir. For a linked
# worktree, --git-common-dir resolves to <main>/.git; for the main worktree it
# is the main repo's own .git. Either way, dirname gives the main worktree root.
# Capture into its own variable (rather than nesting inside dirname) so a git
# failure β€” e.g. git older than 2.31, which lacks --path-format β€” surfaces as a
# clear error instead of silently degrading MAIN_ROOT to ".".
if ! COMMON_GIT_DIR="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || [ -z "$COMMON_GIT_DIR" ]; then
    echo "Error: could not determine the main worktree (git rev-parse failed; needs git >= 2.31)." >&2
    exit 1
fi
MAIN_ROOT="$(dirname "$COMMON_GIT_DIR")"

if [ ! -d "$WT/api" ] || [ ! -d "$WT/web" ]; then
    echo "Error: $WT does not look like the OpenPosterDB repo (missing api/ or web/)." >&2
    exit 1
fi

echo ">> Worktree : $WT"
echo ">> Main repo: $MAIN_ROOT"

# --- Tooling check ----------------------------------------------------------
for tool in cargo npm; do
    if ! command -v "$tool" >/dev/null 2>&1; then
        echo "Error: '$tool' not found in PATH." >&2
        exit 1
    fi
done

# --- 1. Copy api/.env if this worktree doesn't have one ---------------------
SRC_ENV="$MAIN_ROOT/api/.env"
DST_ENV="$WT/api/.env"

if [ -f "$DST_ENV" ]; then
    echo ">> api/.env already present β€” leaving it untouched."
elif [ "$WT" = "$MAIN_ROOT" ]; then
    echo "Error: this is the main worktree and it has no api/.env." >&2
    echo "       Create it first:  (cd \"$WT/api\" && cp .env.example .env)  then fill in your keys." >&2
    exit 1
elif [ ! -d "$MAIN_ROOT/api" ]; then
    # MAIN_ROOT isn't a usable checkout (e.g. a bare-repo-centric layout where
    # the common git dir's parent is not a worktree). Point at THIS worktree.
    echo "Error: couldn't find a main worktree with api/ to copy .env from (looked in $MAIN_ROOT)." >&2
    echo "       Create api/.env directly in this worktree:" >&2
    echo "       (cd \"$WT/api\" && cp .env.example .env)  then fill in your keys." >&2
    exit 1
elif [ -f "$SRC_ENV" ]; then
    cp "$SRC_ENV" "$DST_ENV"
    echo ">> Copied api/.env from main worktree -> $DST_ENV"
else
    echo "Error: the main worktree has no api/.env to copy ($SRC_ENV)." >&2
    echo "       Create it first:  (cd \"$MAIN_ROOT/api\" && cp .env.example .env)  then fill in your keys." >&2
    exit 1
fi

# --- 2. Optional fresh start: wipe this worktree's local DB + cache ---------
if [ "$FRESH" -eq 1 ]; then
    # In the main worktree, --fresh would delete your PRIMARY db/cache, so
    # require confirmation there β€” an interactive prompt, or --yes for
    # non-interactive use. In a linked worktree the data is throwaway, so wipe
    # it without asking.
    if [ "$WT" = "$MAIN_ROOT" ] && [ "$ASSUME_YES" -ne 1 ]; then
        echo "Warning: --fresh on the main worktree deletes your PRIMARY local data:" >&2
        echo "           $WT/api/db" >&2
        echo "           $WT/api/cache" >&2
        if [ -t 0 ]; then
            printf "Delete them and start fresh? [y/N] " >&2
            read -r reply || reply=""
            case "$reply" in
                [yY]|[yY][eE][sS]) ;;
                *) echo "Aborted β€” your data was left untouched." >&2; exit 1 ;;
            esac
        else
            echo "Refusing: not an interactive terminal. Re-run with --yes to confirm." >&2
            exit 1
        fi
    fi
    echo ">> --fresh: wiping this worktree's local data before starting."
    for sub in db cache; do
        target="$WT/api/$sub"
        if [ -e "$target" ]; then
            echo "   removing $target"
            rm -rf "$target"
        else
            echo "   (nothing at $target)"
        fi
    done
fi

# --- 3. Install web dependencies if missing ---------------------------------
if [ ! -d "$WT/web/node_modules" ]; then
    echo ">> Installing web dependencies (first run for this worktree)..."
    if ! ( cd "$WT/web" && npm ci ); then
        echo ">> 'npm ci' failed; falling back to 'npm install'..."
        ( cd "$WT/web" && npm install )
    fi
fi

if [ "$SETUP_ONLY" -eq 1 ]; then
    echo ">> Setup complete (--setup-only). Run again without it to start the servers."
    exit 0
fi

# --- 4. Run the API and the web dev server together -------------------------
# Both servers run as background subshells. On shutdown we must tear down the
# whole tree (cargo + the API binary, npm + vite), not just the subshells.
#
# When this script leads its own process group β€” the normal case when it is run
# as a foreground command from an interactive shell β€” `kill 0` signals every
# process in that group, reaching all descendants cleanly. If instead it was
# launched without job control (from a wrapper script, a Makefile, an npm
# script, nohup, or any non-interactive parent) it shares the caller's process
# group, where `kill 0` would also signal unrelated processes. We detect that
# case and fall back to killing the two services (and their children) directly.
SCRIPT_PGID="$(ps -o pgid= -p "$$" 2>/dev/null | tr -d ' ')" || SCRIPT_PGID=""
API_PID=""
WEB_PID=""

# Terminate a process and all of its descendants, deepest first, so that a
# grandchild (e.g. vite under npm, or the API binary under cargo) doesn't get
# reparented and orphaned when its parent dies. Used only in the fallback path.
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
}

cleanup() {
    trap - INT TERM EXIT
    printf '\n>> Shutting down API + web dev server...\n'
    if [ -n "$SCRIPT_PGID" ] && [ "$SCRIPT_PGID" = "$$" ]; then
        # We lead our own group: one signal reaps both servers and their kids.
        kill 0 2>/dev/null || true
    else
        # Not a group leader β€” signal only our own service subtrees, so we
        # never hit the caller's process group.
        for p in "$API_PID" "$WEB_PID"; do
            [ -n "$p" ] || continue
            kill_tree "$p"
        done
    fi
}
trap cleanup INT TERM EXIT

if [ -n "$CARGO_RELEASE" ]; then
    echo ">> API backend:  http://localhost:3000   (cargo run --release)"
else
    echo ">> API backend:  http://localhost:3000   (cargo run)"
fi
echo ">> Open the app:  http://localhost:5173   (Vite dev server β€” uses the next free port if 5173 is taken)"
echo ">> Press Ctrl+C to stop both."
echo

# Rust API. The 'if' wrapper keeps a non-zero exit (e.g. a compile error) from
# tripping 'set -e' before we notify the parent, so a crash tears down both.
# $$ inside a subshell is the parent script's PID, which is what we want here.
(
    if cd "$WT/api" && cargo run $CARGO_RELEASE; then ec=0; else ec=$?; fi
    echo ">> [api] cargo run exited (code $ec)"
    kill -TERM $$ 2>/dev/null || true
) &
API_PID=$!

# Web dev server.
(
    if cd "$WT/web" && npm run dev; then ec=0; else ec=$?; fi
    echo ">> [web] dev server exited (code $ec)"
    kill -TERM $$ 2>/dev/null || true
) &
WEB_PID=$!

wait