@reservine/dx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +22 -0
- package/README.md +303 -0
- package/bin/cli.ts +549 -0
- package/package.json +26 -0
- package/plugins/reservine-dx/.claude-plugin/plugin.json +8 -0
- package/plugins/reservine-dx/commands/cherry-pick-pr.md +221 -0
- package/plugins/reservine-dx/commands/cleanup.md +297 -0
- package/plugins/reservine-dx/commands/commit.md +118 -0
- package/plugins/reservine-dx/docker/worktree/docker-compose.isolated.template.yaml +144 -0
- package/plugins/reservine-dx/docker/worktree/seed-snapshot.sh +74 -0
- package/plugins/reservine-dx/scripts/_core.sh +330 -0
- package/plugins/reservine-dx/scripts/setup-worktree-be.sh +501 -0
- package/plugins/reservine-dx/scripts/setup-worktree-fe.sh +244 -0
- package/plugins/reservine-dx/scripts/setup-worktree.sh +59 -0
- package/plugins/reservine-dx/skills/cross-plan/SKILL.md +339 -0
- package/plugins/reservine-dx/skills/implement-plan/SKILL.md +512 -0
- package/plugins/reservine-dx/skills/implement-plan/references/plugin-contract.md +82 -0
- package/plugins/reservine-dx/skills/new-feature-planning/SKILL.md +544 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Reservine-DX — Laravel Backend Worktree Setup
|
|
4
|
+
#
|
|
5
|
+
# Sets up a git worktree for the ReservineBack Laravel backend:
|
|
6
|
+
# - Copies .env / .env.local from main repo
|
|
7
|
+
# - Symlinks (or copies) vendor/ from the main repo
|
|
8
|
+
# - Starts Sail containers (shared with main repo)
|
|
9
|
+
# - Optionally creates an isolated Docker stack (--isolated)
|
|
10
|
+
# with its own DB, Redis, Meilisearch, Soketi, and deterministic ports
|
|
11
|
+
# - Seeds the isolated DB from a snapshot
|
|
12
|
+
# - Generates FE proxy config for the sibling Angular worktree
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# ./setup-worktree-be.sh [OPTIONS]
|
|
16
|
+
#
|
|
17
|
+
# Options:
|
|
18
|
+
# --plan=<name> Search .claude/plans/ for matching file, extract branch name
|
|
19
|
+
# --branch=<name> Create and checkout this branch
|
|
20
|
+
# --base=<branch> Base branch for PR (default: dev)
|
|
21
|
+
# --no-vendor-link Run composer install instead of symlinking vendor/
|
|
22
|
+
# --skip-sail Skip Sail container startup
|
|
23
|
+
# --isolated Create isolated Docker stack with unique ports
|
|
24
|
+
# --teardown Stop and remove the isolated Docker stack (destroys data)
|
|
25
|
+
# --reseed Re-import the DB snapshot (destroys existing data)
|
|
26
|
+
# --status Show all running worktree Docker stacks
|
|
27
|
+
# -h, --help Show this help message
|
|
28
|
+
#
|
|
29
|
+
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
|
|
32
|
+
# Source shared core
|
|
33
|
+
SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
34
|
+
source "$SCRIPTS_DIR/_core.sh"
|
|
35
|
+
|
|
36
|
+
# ─── Defaults ───────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
VENDOR_LINK=true
|
|
39
|
+
SKIP_SAIL=false
|
|
40
|
+
BRANCH_NAME=""
|
|
41
|
+
BASE_BRANCH="dev"
|
|
42
|
+
PLAN_PATTERN=""
|
|
43
|
+
ISOLATED=false
|
|
44
|
+
TEARDOWN=false
|
|
45
|
+
SHOW_STATUS=false
|
|
46
|
+
RESEED=false
|
|
47
|
+
|
|
48
|
+
# ─── Help ───────────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
show_help() {
|
|
51
|
+
cat << 'EOF'
|
|
52
|
+
Reservine-DX — Laravel Backend Worktree Setup
|
|
53
|
+
|
|
54
|
+
Usage: ./setup-worktree-be.sh [OPTIONS]
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
--plan=<name> Search .claude/plans/ for matching file, extract branch name
|
|
58
|
+
--branch=<name> Create and checkout this branch
|
|
59
|
+
--base=<branch> Base branch for PR validation (default: dev)
|
|
60
|
+
--no-vendor-link Run composer install instead of symlinking vendor/
|
|
61
|
+
--skip-sail Skip Sail container startup even in local environment
|
|
62
|
+
--isolated Create isolated Docker stack with unique ports (no shared services)
|
|
63
|
+
--teardown Stop and remove the isolated Docker stack (destroys data)
|
|
64
|
+
--reseed Re-import the DB snapshot (destroys existing data)
|
|
65
|
+
--status Show all running worktree Docker stacks
|
|
66
|
+
-h, --help Show this help message
|
|
67
|
+
|
|
68
|
+
Environment Variables:
|
|
69
|
+
ROOT_WORKTREE_PATH Path to main repo (auto-detected from git worktree list)
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
# Simple setup — auto-detects everything
|
|
73
|
+
./setup-worktree-be.sh
|
|
74
|
+
|
|
75
|
+
# Setup with explicit branch
|
|
76
|
+
./setup-worktree-be.sh --branch=feat/my-feature
|
|
77
|
+
|
|
78
|
+
# Isolated Docker environment (own DB, Redis, ports)
|
|
79
|
+
./setup-worktree-be.sh --isolated
|
|
80
|
+
|
|
81
|
+
# Tear down an isolated environment
|
|
82
|
+
./setup-worktree-be.sh --teardown
|
|
83
|
+
|
|
84
|
+
# Re-seed the isolated DB from snapshot
|
|
85
|
+
./setup-worktree-be.sh --reseed
|
|
86
|
+
EOF
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# ─── Parse arguments ────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
while [[ $# -gt 0 ]]; do
|
|
92
|
+
case $1 in
|
|
93
|
+
--plan=*) PLAN_PATTERN="${1#*=}"; shift ;;
|
|
94
|
+
--branch=*) BRANCH_NAME="${1#*=}"; shift ;;
|
|
95
|
+
--base=*) BASE_BRANCH="${1#*=}"; shift ;;
|
|
96
|
+
--no-vendor-link) VENDOR_LINK=false; shift ;;
|
|
97
|
+
--skip-sail) SKIP_SAIL=true; shift ;;
|
|
98
|
+
--isolated) ISOLATED=true; shift ;;
|
|
99
|
+
--teardown) TEARDOWN=true; shift ;;
|
|
100
|
+
--status) SHOW_STATUS=true; shift ;;
|
|
101
|
+
--reseed) RESEED=true; shift ;;
|
|
102
|
+
--log) LOG_FILE="$(pwd)/setup.log"; shift ;;
|
|
103
|
+
--log=*) LOG_FILE="${1#*=}"; shift ;;
|
|
104
|
+
-h|--help) show_help; exit 0 ;;
|
|
105
|
+
*) error "Unknown option: $1"; show_help; exit 1 ;;
|
|
106
|
+
esac
|
|
107
|
+
done
|
|
108
|
+
|
|
109
|
+
init_log "$@"
|
|
110
|
+
|
|
111
|
+
# --isolated requires its own vendor (autoloader paths must match container paths)
|
|
112
|
+
if [[ "$ISOLATED" == true ]]; then
|
|
113
|
+
VENDOR_LINK=false
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# ─── Plan resolution ────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
if [[ -n "$PLAN_PATTERN" ]]; then
|
|
119
|
+
resolve_plan_file "$PLAN_PATTERN"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# ─── ROOT_WORKTREE_PATH ─────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
resolve_root_worktree_path
|
|
125
|
+
|
|
126
|
+
# ─── Quick commands (can run before full pre-flight) ─────────────────────────────
|
|
127
|
+
|
|
128
|
+
if [[ "$SHOW_STATUS" == true ]]; then
|
|
129
|
+
echo ""
|
|
130
|
+
echo "📊 Worktree Docker Stacks"
|
|
131
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
132
|
+
docker ps --filter "name=wt-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo "No worktree containers found"
|
|
133
|
+
echo ""
|
|
134
|
+
exit 0
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [[ "$TEARDOWN" == true ]]; then
|
|
138
|
+
if [[ -f "docker-compose.isolated.yaml" ]]; then
|
|
139
|
+
echo "🗑️ Tearing down isolated stack..."
|
|
140
|
+
docker compose -f docker-compose.isolated.yaml down -v
|
|
141
|
+
rm -f docker-compose.isolated.yaml .env.isolated
|
|
142
|
+
success "Isolated stack removed (volumes destroyed)"
|
|
143
|
+
else
|
|
144
|
+
warn "No docker-compose.isolated.yaml found in current directory"
|
|
145
|
+
fi
|
|
146
|
+
exit 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if [[ "$RESEED" == true ]]; then
|
|
150
|
+
if [[ -z "${ROOT_WORKTREE_PATH:-}" ]]; then
|
|
151
|
+
DETECTED_ROOT=$(git worktree list 2>/dev/null | grep -v ".worktrees" | head -1 | awk '{print $1}')
|
|
152
|
+
ROOT_WORKTREE_PATH="${DETECTED_ROOT:-$(pwd)}"
|
|
153
|
+
fi
|
|
154
|
+
SNAPSHOT_PATH="${ROOT_WORKTREE_PATH}/snapshots/reservine.sql.gz"
|
|
155
|
+
SEED_SCRIPT="${DX_PLUGIN_DIR}/docker/worktree/seed-snapshot.sh"
|
|
156
|
+
# Fallback to main repo's copy if DX script not found
|
|
157
|
+
[[ ! -f "$SEED_SCRIPT" ]] && SEED_SCRIPT="${ROOT_WORKTREE_PATH}/docker/worktree/seed-snapshot.sh"
|
|
158
|
+
if [[ -f "docker-compose.isolated.yaml" ]]; then
|
|
159
|
+
echo "🌱 Re-seeding database..."
|
|
160
|
+
docker compose -f docker-compose.isolated.yaml down -v
|
|
161
|
+
docker compose -f docker-compose.isolated.yaml up -d
|
|
162
|
+
echo "Waiting for DB to be healthy..."
|
|
163
|
+
sleep 15
|
|
164
|
+
BRANCH_SLUG=$(get_branch_slug)
|
|
165
|
+
"$SEED_SCRIPT" \
|
|
166
|
+
docker-compose.isolated.yaml "$SNAPSHOT_PATH" "reservine_wt_pass" "wt-${BRANCH_SLUG}"
|
|
167
|
+
success "Database re-seeded"
|
|
168
|
+
else
|
|
169
|
+
warn "No docker-compose.isolated.yaml found — run --isolated first"
|
|
170
|
+
fi
|
|
171
|
+
exit 0
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# ─── Pre-flight ─────────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
preflight_check
|
|
177
|
+
|
|
178
|
+
info "Stack: Laravel (composer)"
|
|
179
|
+
|
|
180
|
+
# ─── Branch setup ────────────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
if [[ -n "$BRANCH_NAME" ]]; then
|
|
183
|
+
setup_branch "$BRANCH_NAME" "$BASE_BRANCH"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# ─── Environment setup (.env, vendor) ────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
if [[ "$IS_CLOUD" == true ]]; then
|
|
189
|
+
info "Cloud environment — skipping .env and vendor setup"
|
|
190
|
+
info "Use setupClaudeWeb.sh for cloud configuration"
|
|
191
|
+
else
|
|
192
|
+
echo "⚙️ Laravel Environment Setup"
|
|
193
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
194
|
+
|
|
195
|
+
# ── .env ──
|
|
196
|
+
|
|
197
|
+
if [[ ! -f ".env" ]]; then
|
|
198
|
+
if [[ -f "${ROOT_WORKTREE_PATH}/.env" ]]; then
|
|
199
|
+
cp "${ROOT_WORKTREE_PATH}/.env" .env
|
|
200
|
+
success "Copied .env from main repo"
|
|
201
|
+
else
|
|
202
|
+
warn "No .env found in main repo — you may need to create one"
|
|
203
|
+
fi
|
|
204
|
+
else
|
|
205
|
+
info ".env already exists"
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
if [[ ! -f ".env.local" ]] && [[ -f "${ROOT_WORKTREE_PATH}/.env.local" ]]; then
|
|
209
|
+
cp "${ROOT_WORKTREE_PATH}/.env.local" .env.local
|
|
210
|
+
success "Copied .env.local from main repo"
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# ── vendor ──
|
|
214
|
+
|
|
215
|
+
if [[ "$VENDOR_LINK" == true ]]; then
|
|
216
|
+
if [[ -d "vendor" ]] && [[ ! -L "vendor" ]]; then
|
|
217
|
+
warn "vendor/ is a directory — removing to create symlink"
|
|
218
|
+
rm -rf vendor
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
if [[ -L "vendor" ]]; then
|
|
222
|
+
info "vendor/ symlink already exists → $(readlink vendor)"
|
|
223
|
+
elif [[ -d "${ROOT_WORKTREE_PATH}/vendor" ]]; then
|
|
224
|
+
ln -s "${ROOT_WORKTREE_PATH}/vendor" vendor
|
|
225
|
+
success "Symlinked vendor/ → main repo"
|
|
226
|
+
else
|
|
227
|
+
warn "Main repo has no vendor/ — running composer install"
|
|
228
|
+
composer install --no-interaction --prefer-dist
|
|
229
|
+
success "Installed vendor/"
|
|
230
|
+
fi
|
|
231
|
+
else
|
|
232
|
+
if [[ -d "vendor" ]] && [[ ! -L "vendor" ]]; then
|
|
233
|
+
info "vendor/ directory already exists (local copy)"
|
|
234
|
+
else
|
|
235
|
+
[[ -L "vendor" ]] && rm vendor
|
|
236
|
+
if [[ -d "${ROOT_WORKTREE_PATH}/vendor" ]]; then
|
|
237
|
+
info "Copying vendor/ from main repo (this takes ~10s)..."
|
|
238
|
+
cp -R "${ROOT_WORKTREE_PATH}/vendor" vendor
|
|
239
|
+
success "Copied vendor/ (local copy for isolated mode)"
|
|
240
|
+
elif command -v composer &>/dev/null; then
|
|
241
|
+
composer install --no-interaction --prefer-dist
|
|
242
|
+
success "Installed vendor/"
|
|
243
|
+
else
|
|
244
|
+
error "No vendor/ source and no composer available"
|
|
245
|
+
exit 1
|
|
246
|
+
fi
|
|
247
|
+
fi
|
|
248
|
+
fi
|
|
249
|
+
echo ""
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# ─── Sail containers (shared, local only) ────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
if [[ "$IS_CLOUD" == true ]]; then
|
|
255
|
+
info "Cloud environment — skipping Sail"
|
|
256
|
+
elif [[ "$SKIP_SAIL" == true ]]; then
|
|
257
|
+
info "Sail startup skipped (--skip-sail)"
|
|
258
|
+
elif [[ "$ISOLATED" == false ]]; then
|
|
259
|
+
echo "🐳 Sail Containers"
|
|
260
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
261
|
+
|
|
262
|
+
if [[ -x "./vendor/bin/sail" ]] || [[ -L "vendor" && -x "$(readlink -f vendor)/bin/sail" ]]; then
|
|
263
|
+
if ./vendor/bin/sail ps 2>/dev/null | grep -q "Up"; then
|
|
264
|
+
info "Sail containers already running"
|
|
265
|
+
else
|
|
266
|
+
info "Starting Sail containers..."
|
|
267
|
+
./vendor/bin/sail up -d
|
|
268
|
+
success "Sail started"
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
if [[ -f ".env" ]] && ! grep -qE "^APP_KEY=base64:" .env 2>/dev/null; then
|
|
272
|
+
./vendor/bin/sail artisan key:generate
|
|
273
|
+
success "Generated app key"
|
|
274
|
+
fi
|
|
275
|
+
else
|
|
276
|
+
warn "Sail not available — skipping container startup"
|
|
277
|
+
fi
|
|
278
|
+
echo ""
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
# ─── Isolated Docker stack (--isolated) ──────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
if [[ "$ISOLATED" == true ]] && [[ "$IS_CLOUD" == false ]]; then
|
|
284
|
+
echo "🔒 Isolated Environment Setup"
|
|
285
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
286
|
+
|
|
287
|
+
BRANCH_SLUG=$(get_branch_slug)
|
|
288
|
+
OFFSET=$(port_hash "$BRANCH_SLUG")
|
|
289
|
+
|
|
290
|
+
# Calculate ports
|
|
291
|
+
ISO_DB_PORT=$((4300 + OFFSET))
|
|
292
|
+
ISO_REDIS_PORT=$((7300 + OFFSET))
|
|
293
|
+
ISO_APP_PORT=$((8000 + OFFSET))
|
|
294
|
+
ISO_ADMINER_PORT=$((9000 + OFFSET))
|
|
295
|
+
ISO_MEILI_PORT=$((8700 + OFFSET))
|
|
296
|
+
ISO_SOKETI_PORT=$((7100 + OFFSET))
|
|
297
|
+
|
|
298
|
+
# Collision check
|
|
299
|
+
COLLISION_FOUND=true
|
|
300
|
+
ATTEMPTS=0
|
|
301
|
+
while [[ "$COLLISION_FOUND" == true ]] && [[ $ATTEMPTS -lt 10 ]]; do
|
|
302
|
+
COLLISION_FOUND=false
|
|
303
|
+
for port in $ISO_DB_PORT $ISO_REDIS_PORT $ISO_APP_PORT $ISO_ADMINER_PORT $ISO_MEILI_PORT $ISO_SOKETI_PORT; do
|
|
304
|
+
if port_in_use "$port"; then
|
|
305
|
+
if docker ps --filter "name=wt-${BRANCH_SLUG}" --format "{{.Ports}}" 2>/dev/null | grep -q ":${port}->"; then
|
|
306
|
+
continue
|
|
307
|
+
fi
|
|
308
|
+
COLLISION_FOUND=true
|
|
309
|
+
OFFSET=$(( (OFFSET + 1) % 100 ))
|
|
310
|
+
ISO_DB_PORT=$((4300 + OFFSET))
|
|
311
|
+
ISO_REDIS_PORT=$((7300 + OFFSET))
|
|
312
|
+
ISO_APP_PORT=$((8000 + OFFSET))
|
|
313
|
+
ISO_ADMINER_PORT=$((9000 + OFFSET))
|
|
314
|
+
ISO_MEILI_PORT=$((8700 + OFFSET))
|
|
315
|
+
ISO_SOKETI_PORT=$((7100 + OFFSET))
|
|
316
|
+
break
|
|
317
|
+
fi
|
|
318
|
+
done
|
|
319
|
+
ATTEMPTS=$((ATTEMPTS + 1))
|
|
320
|
+
done
|
|
321
|
+
|
|
322
|
+
info "Branch: $BRANCH_SLUG → offset $OFFSET"
|
|
323
|
+
info "Ports: App=$ISO_APP_PORT DB=$ISO_DB_PORT Redis=$ISO_REDIS_PORT"
|
|
324
|
+
|
|
325
|
+
# Resolve template — try DX plugin first, then main repo fallback
|
|
326
|
+
TEMPLATE_PATH="${DX_PLUGIN_DIR}/docker/worktree/docker-compose.isolated.template.yaml"
|
|
327
|
+
[[ ! -f "$TEMPLATE_PATH" ]] && TEMPLATE_PATH="${ROOT_WORKTREE_PATH}/docker/worktree/docker-compose.isolated.template.yaml"
|
|
328
|
+
if [[ ! -f "$TEMPLATE_PATH" ]]; then
|
|
329
|
+
error "Docker template not found"
|
|
330
|
+
echo " Expected at: ${DX_PLUGIN_DIR}/docker/worktree/docker-compose.isolated.template.yaml" >&2
|
|
331
|
+
exit 1
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
export COMPOSE_PROJECT_NAME="wt-${BRANCH_SLUG}"
|
|
335
|
+
export DB_PORT="$ISO_DB_PORT"
|
|
336
|
+
export DB_PASSWORD="reservine_wt_pass"
|
|
337
|
+
export REDIS_PASSWORD="t8eGxQbCd6uYTEPrGN2Rgta9SULy6qar"
|
|
338
|
+
export REDIS_PORT="$ISO_REDIS_PORT"
|
|
339
|
+
export APP_PORT="$ISO_APP_PORT"
|
|
340
|
+
export ADMINER_PORT="$ISO_ADMINER_PORT"
|
|
341
|
+
export MEILI_PORT="$ISO_MEILI_PORT"
|
|
342
|
+
export SOKETI_PORT="$ISO_SOKETI_PORT"
|
|
343
|
+
|
|
344
|
+
# Resolve vendor path (follow symlink to real directory)
|
|
345
|
+
if [[ -L "vendor" ]]; then
|
|
346
|
+
export VENDOR_PATH="$(readlink -f vendor)"
|
|
347
|
+
else
|
|
348
|
+
export VENDOR_PATH="$(pwd)/vendor"
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
# Mount main repo at its original path so Composer's absolute path references resolve
|
|
352
|
+
export ROOT_REPO_PATH="$ROOT_WORKTREE_PATH"
|
|
353
|
+
|
|
354
|
+
envsubst < "$TEMPLATE_PATH" > docker-compose.isolated.yaml
|
|
355
|
+
success "Generated docker-compose.isolated.yaml"
|
|
356
|
+
|
|
357
|
+
# Patch .env with isolated credentials
|
|
358
|
+
if [[ -f ".env" ]]; then
|
|
359
|
+
sed -i.bak \
|
|
360
|
+
-e "s|^DB_HOST=.*|DB_HOST=db|" \
|
|
361
|
+
-e "s|^DB_PORT=.*|DB_PORT=3306|" \
|
|
362
|
+
-e "s|^DB_PASSWORD=.*|DB_PASSWORD=${DB_PASSWORD}|" \
|
|
363
|
+
-e "s|^DB_USERNAME=.*|DB_USERNAME=reservine|" \
|
|
364
|
+
-e "s|^DB_DATABASE=.*|DB_DATABASE=reservine|" \
|
|
365
|
+
-e "s|^REDIS_HOST=.*|REDIS_HOST=redis|" \
|
|
366
|
+
-e "s|^REDIS_PORT=.*|REDIS_PORT=6379|" \
|
|
367
|
+
.env
|
|
368
|
+
rm -f .env.bak
|
|
369
|
+
success "Patched .env with isolated connection settings"
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
# Start the stack
|
|
373
|
+
info "Starting isolated Docker stack..."
|
|
374
|
+
COMPOSE_PROJECT_NAME="wt-${BRANCH_SLUG}" \
|
|
375
|
+
docker compose -f docker-compose.isolated.yaml up -d 2>&1 | tail -5
|
|
376
|
+
success "Docker stack started"
|
|
377
|
+
|
|
378
|
+
# Regenerate Composer autoloader inside container
|
|
379
|
+
info "Regenerating autoloader..."
|
|
380
|
+
docker exec "wt-${BRANCH_SLUG}-app" composer dump-autoload -d /var/www/html --quiet 2>/dev/null || true
|
|
381
|
+
success "Autoloader regenerated"
|
|
382
|
+
|
|
383
|
+
# Wait for DB healthy
|
|
384
|
+
info "Waiting for DB to be healthy..."
|
|
385
|
+
for i in $(seq 1 30); do
|
|
386
|
+
if docker exec "wt-${BRANCH_SLUG}-db" mariadb-admin ping -h localhost -ureservine -preservine_wt_pass >/dev/null 2>&1; then
|
|
387
|
+
success "DB is healthy"
|
|
388
|
+
break
|
|
389
|
+
fi
|
|
390
|
+
if [[ $i -eq 30 ]]; then
|
|
391
|
+
error "DB failed to become healthy after 30 attempts"
|
|
392
|
+
exit 1
|
|
393
|
+
fi
|
|
394
|
+
sleep 2
|
|
395
|
+
done
|
|
396
|
+
|
|
397
|
+
# Auto-import snapshot
|
|
398
|
+
SNAPSHOT_PATH="${ROOT_WORKTREE_PATH}/snapshots/reservine.sql.gz"
|
|
399
|
+
SEED_SCRIPT="${DX_PLUGIN_DIR}/docker/worktree/seed-snapshot.sh"
|
|
400
|
+
[[ ! -f "$SEED_SCRIPT" ]] && SEED_SCRIPT="${ROOT_WORKTREE_PATH}/docker/worktree/seed-snapshot.sh"
|
|
401
|
+
"$SEED_SCRIPT" \
|
|
402
|
+
docker-compose.isolated.yaml "$SNAPSHOT_PATH" "$DB_PASSWORD" "$COMPOSE_PROJECT_NAME"
|
|
403
|
+
|
|
404
|
+
# Generate FE proxy config for sibling worktree
|
|
405
|
+
FE_WORKTREE=$(find_fe_worktree || echo "")
|
|
406
|
+
if [[ -n "$FE_WORKTREE" ]]; then
|
|
407
|
+
cat > "$FE_WORKTREE/proxy.conf.json" <<PROXY_EOF
|
|
408
|
+
{
|
|
409
|
+
"/api": {
|
|
410
|
+
"target": "http://localhost:${ISO_APP_PORT}",
|
|
411
|
+
"secure": false,
|
|
412
|
+
"changeOrigin": true
|
|
413
|
+
},
|
|
414
|
+
"/sanctum": {
|
|
415
|
+
"target": "http://localhost:${ISO_APP_PORT}",
|
|
416
|
+
"secure": false,
|
|
417
|
+
"changeOrigin": true
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
PROXY_EOF
|
|
421
|
+
success "Generated FE proxy: $FE_WORKTREE/proxy.conf.json"
|
|
422
|
+
|
|
423
|
+
# Copy existing .claude/config.local from FE main repo (has developer's credentials)
|
|
424
|
+
# then append/override the isolated BE API URL
|
|
425
|
+
mkdir -p "$FE_WORKTREE/.claude"
|
|
426
|
+
FE_MAIN_CONFIG_LOCAL=""
|
|
427
|
+
if [[ -n "${ROOT_WORKTREE_PATH:-}" ]]; then
|
|
428
|
+
FE_MAIN_CONFIG_LOCAL="$(dirname "$ROOT_WORKTREE_PATH")/reservine/.claude/config.local"
|
|
429
|
+
fi
|
|
430
|
+
if [[ -f "$FE_MAIN_CONFIG_LOCAL" ]]; then
|
|
431
|
+
cp "$FE_MAIN_CONFIG_LOCAL" "$FE_WORKTREE/.claude/config.local"
|
|
432
|
+
# Append/override the isolated BE API URL
|
|
433
|
+
sed -i.bak \
|
|
434
|
+
-e "s|^MCP_LOCALBE_API_URL=.*|MCP_LOCALBE_API_URL=http://localhost:${ISO_APP_PORT}/api|" \
|
|
435
|
+
"$FE_WORKTREE/.claude/config.local"
|
|
436
|
+
# If the key didn't exist, append it
|
|
437
|
+
grep -q "^MCP_LOCALBE_API_URL=" "$FE_WORKTREE/.claude/config.local" || \
|
|
438
|
+
echo "MCP_LOCALBE_API_URL=http://localhost:${ISO_APP_PORT}/api" >> "$FE_WORKTREE/.claude/config.local"
|
|
439
|
+
rm -f "$FE_WORKTREE/.claude/config.local.bak"
|
|
440
|
+
else
|
|
441
|
+
# No main config.local found — create minimal one with just the API URL
|
|
442
|
+
cat > "$FE_WORKTREE/.claude/config.local" <<CONFIG_EOF
|
|
443
|
+
# Generated by setup-worktree-be.sh --isolated
|
|
444
|
+
# Add your MCP_DEV_EMAIL and MCP_DEV_PASSWORD here
|
|
445
|
+
MCP_LOCALBE_BASE_URL=http://localhost:1112
|
|
446
|
+
MCP_LOCALBE_API_URL=http://localhost:${ISO_APP_PORT}/api
|
|
447
|
+
CONFIG_EOF
|
|
448
|
+
warn "No .claude/config.local found in FE main repo — created minimal config"
|
|
449
|
+
info "Add your MCP_DEV_EMAIL/PASSWORD to: $FE_WORKTREE/.claude/config.local"
|
|
450
|
+
fi
|
|
451
|
+
success "Generated FE config: $FE_WORKTREE/.claude/config.local"
|
|
452
|
+
else
|
|
453
|
+
warn "No FE worktree found — skipping proxy config generation"
|
|
454
|
+
info "To generate manually, create proxy.conf.json targeting http://localhost:${ISO_APP_PORT}"
|
|
455
|
+
fi
|
|
456
|
+
|
|
457
|
+
echo ""
|
|
458
|
+
echo "🎉 Isolated environment ready!"
|
|
459
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
460
|
+
echo ""
|
|
461
|
+
echo " BE API: http://localhost:${ISO_APP_PORT}/api"
|
|
462
|
+
echo " Adminer: http://localhost:${ISO_ADMINER_PORT}"
|
|
463
|
+
echo " DB Port: ${ISO_DB_PORT}"
|
|
464
|
+
echo " Redis Port: ${ISO_REDIS_PORT}"
|
|
465
|
+
echo ""
|
|
466
|
+
if [[ -n "$FE_WORKTREE" ]]; then
|
|
467
|
+
echo " FE serve command:"
|
|
468
|
+
echo " cd $FE_WORKTREE"
|
|
469
|
+
echo " nx serve reservine --port 1112 --configuration=isolatedBE --proxy-config=proxy.conf.json"
|
|
470
|
+
echo ""
|
|
471
|
+
fi
|
|
472
|
+
echo " Teardown: ./setup-worktree-be.sh --teardown"
|
|
473
|
+
echo " Re-seed: ./setup-worktree-be.sh --reseed"
|
|
474
|
+
echo ""
|
|
475
|
+
|
|
476
|
+
SKIP_SAIL=true
|
|
477
|
+
fi
|
|
478
|
+
|
|
479
|
+
# ─── Final output ───────────────────────────────────────────────────────────────
|
|
480
|
+
|
|
481
|
+
echo "✨ Worktree setup complete!"
|
|
482
|
+
echo ""
|
|
483
|
+
|
|
484
|
+
if [[ "$IS_CLOUD" == false ]] && [[ "$ISOLATED" == false ]]; then
|
|
485
|
+
echo "⚠️ IMPORTANT: Service Locality Warning"
|
|
486
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
487
|
+
echo "These commands use SHARED local services (database, redis, meilisearch):"
|
|
488
|
+
echo " • sail artisan / php artisan"
|
|
489
|
+
echo " • sail test"
|
|
490
|
+
echo ""
|
|
491
|
+
echo "PHPStan can safely use main repo's vendor:"
|
|
492
|
+
echo " vendor/bin/phpstan analyze <files>"
|
|
493
|
+
echo ""
|
|
494
|
+
echo "Services available at:"
|
|
495
|
+
echo " Laravel: http://localhost:80"
|
|
496
|
+
echo " Vite: http://localhost:5173"
|
|
497
|
+
echo " Adminer: http://localhost:8080"
|
|
498
|
+
echo ""
|
|
499
|
+
fi
|
|
500
|
+
|
|
501
|
+
print_state "vendor" "$BRANCH_NAME"
|