@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.
@@ -0,0 +1,144 @@
1
+ # docker/worktree/docker-compose.isolated.template.yaml
2
+ #
3
+ # Template for isolated worktree Docker stacks.
4
+ # Generated by: setup-worktree-be.sh --isolated
5
+ # DO NOT EDIT the generated file — edit this template instead.
6
+ #
7
+ # All ports use deterministic offsets based on branch name hash.
8
+
9
+ services:
10
+ db:
11
+ image: 'mariadb:latest'
12
+ container_name: '${COMPOSE_PROJECT_NAME}-db'
13
+ environment:
14
+ MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
15
+ MYSQL_DATABASE: reservine
16
+ MYSQL_USER: reservine
17
+ MYSQL_PASSWORD: '${DB_PASSWORD}'
18
+ volumes:
19
+ - 'db_data:/var/lib/mysql'
20
+ - './docker/mysql/:/docker-entrypoint-initdb.d'
21
+ - './docker/mysql/conf.d/:/etc/mysql/conf.d'
22
+ ports:
23
+ - '${DB_PORT}:3306'
24
+ networks:
25
+ - sail
26
+ healthcheck:
27
+ test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-ureservine", "-p${DB_PASSWORD}"]
28
+ interval: 5s
29
+ timeout: 5s
30
+ retries: 10
31
+ start_period: 10s
32
+
33
+ redis:
34
+ image: 'redis:latest'
35
+ container_name: '${COMPOSE_PROJECT_NAME}-redis'
36
+ ports:
37
+ - '${REDIS_PORT}:6379'
38
+ command: 'redis-server --save 10 1 --loglevel warning --requirepass ${REDIS_PASSWORD}'
39
+ environment:
40
+ - REDIS_PASSWORD=${REDIS_PASSWORD}
41
+ volumes:
42
+ - 'redis:/data'
43
+ networks:
44
+ - sail
45
+ healthcheck:
46
+ test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
47
+ interval: 5s
48
+ timeout: 3s
49
+ retries: 5
50
+
51
+ adminer:
52
+ image: adminer
53
+ container_name: '${COMPOSE_PROJECT_NAME}-adminer'
54
+ environment:
55
+ ADMINER_DESIGN: nette
56
+ UPLOAD_LIMIT: 2G
57
+ networks:
58
+ - sail
59
+ depends_on:
60
+ - db
61
+ ports:
62
+ - '${ADMINER_PORT}:8080'
63
+
64
+ meilisearch:
65
+ image: 'getmeili/meilisearch:v1.5'
66
+ container_name: '${COMPOSE_PROJECT_NAME}-meili'
67
+ ports:
68
+ - '${MEILI_PORT}:7700'
69
+ environment:
70
+ MEILI_NO_ANALYTICS: 'true'
71
+ MEILI_MASTER_KEY: 'JCkBrarWPD_8Nj9KCRW32VJjigcZm6BPAttWg4g-OOw'
72
+ volumes:
73
+ - 'meili:/meili_data'
74
+ networks:
75
+ - sail
76
+ healthcheck:
77
+ test: ["CMD", "wget", "--no-verbose", "--spider", "--header=Authorization: Bearer JCkBrarWPD_8Nj9KCRW32VJjigcZm6BPAttWg4g-OOw", "http://127.0.0.1:7700/health"]
78
+ interval: 10s
79
+ retries: 5
80
+ timeout: 10s
81
+ start_period: 15s
82
+
83
+ soketi:
84
+ image: 'quay.io/soketi/soketi:latest-16-alpine'
85
+ container_name: '${COMPOSE_PROJECT_NAME}-soketi'
86
+ environment:
87
+ SOKETI_DEBUG: '1'
88
+ SOKETI_METRICS_SERVER_PORT: '9601'
89
+ ports:
90
+ - '${SOKETI_PORT}:6001'
91
+ networks:
92
+ - sail
93
+
94
+ laravel.test:
95
+ tty: true
96
+ init: true
97
+ container_name: '${COMPOSE_PROJECT_NAME}-app'
98
+ dns_opt:
99
+ - single-request-reopen
100
+ build:
101
+ context: ./docker/8.4
102
+ dockerfile: Dockerfile
103
+ args:
104
+ WWWGROUP: '1000'
105
+ image: sail-8.4/app
106
+ extra_hosts:
107
+ - 'host.docker.internal:host-gateway'
108
+ ports:
109
+ - '${APP_PORT}:80'
110
+ env_file:
111
+ - .env
112
+ environment:
113
+ WWWUSER: '1000'
114
+ LARAVEL_SAIL: 1
115
+ SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80"
116
+ DB_HOST: db
117
+ DB_PORT: 3306
118
+ DB_DATABASE: reservine
119
+ DB_USERNAME: reservine
120
+ DB_PASSWORD: '${DB_PASSWORD}'
121
+ REDIS_HOST: redis
122
+ REDIS_PASSWORD: '${REDIS_PASSWORD}'
123
+ REDIS_PORT: 6379
124
+ volumes:
125
+ - '.:/var/www/html'
126
+ - '/var/www/html/node_modules'
127
+ networks:
128
+ - sail
129
+ depends_on:
130
+ db:
131
+ condition: service_healthy
132
+ redis:
133
+ condition: service_healthy
134
+ meilisearch:
135
+ condition: service_healthy
136
+
137
+ networks:
138
+ sail:
139
+ driver: bridge
140
+
141
+ volumes:
142
+ db_data:
143
+ redis:
144
+ meili:
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Seed a worktree's isolated DB from a snapshot file.
4
+ # Called automatically by setup-worktree-be.sh --isolated on first run.
5
+ #
6
+ # Usage: ./seed-snapshot.sh <compose-file> <snapshot-path> [db-password] [project-name]
7
+ #
8
+ # Prerequisites:
9
+ # - Docker containers must be running
10
+ # - DB container must be healthy
11
+
12
+ set -eo pipefail
13
+
14
+ COMPOSE_FILE="${1:?Usage: seed-snapshot.sh <compose-file> <snapshot-path> [db-password] [project-name]}"
15
+ SNAPSHOT="${2:?Usage: seed-snapshot.sh <compose-file> <snapshot-path> [db-password] [project-name]}"
16
+ DB_PASS="${3:-reservine_wt_pass}"
17
+ PROJECT_NAME="${4:-}"
18
+
19
+ # Set compose project name if provided
20
+ if [[ -n "$PROJECT_NAME" ]]; then
21
+ export COMPOSE_PROJECT_NAME="$PROJECT_NAME"
22
+ fi
23
+
24
+ # Colors
25
+ GREEN='\033[0;32m'
26
+ YELLOW='\033[1;33m'
27
+ BLUE='\033[0;34m'
28
+ NC='\033[0m'
29
+
30
+ info() { echo -e "${BLUE}ℹ${NC} $1"; }
31
+ success() { echo -e "${GREEN}✓${NC} $1"; }
32
+ warn() { echo -e "${YELLOW}⚠️${NC} $1"; }
33
+
34
+ if [[ ! -f "$SNAPSHOT" ]]; then
35
+ warn "Snapshot not found: $SNAPSHOT"
36
+ warn "Place your snapshot at: $(cd "$(dirname "$0")/../.." && pwd)/snapshots/reservine.sql.gz"
37
+ exit 0 # Non-fatal — worktree still usable, just empty DB
38
+ fi
39
+
40
+ DB_CONTAINER=$(docker compose -f "$COMPOSE_FILE" ps -q db)
41
+
42
+ if [[ -z "$DB_CONTAINER" ]]; then
43
+ warn "DB container not found — skipping snapshot import"
44
+ exit 0
45
+ fi
46
+
47
+ # Check if DB already has data
48
+ TABLE_COUNT=$(docker exec "$DB_CONTAINER" \
49
+ mariadb -ureservine -p${DB_PASS} reservine \
50
+ -sNe "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='reservine'" 2>/dev/null || echo "0")
51
+
52
+ if [[ "$TABLE_COUNT" -gt 5 ]]; then
53
+ info "DB already has $TABLE_COUNT tables — skipping snapshot import"
54
+ info "To re-import, run: docker compose -f $COMPOSE_FILE down -v && re-run setup"
55
+ exit 0
56
+ fi
57
+
58
+ info "Importing snapshot ($SNAPSHOT)..."
59
+ SECONDS=0
60
+
61
+ if [[ "$SNAPSHOT" == *.gz ]]; then
62
+ gunzip -c "$SNAPSHOT" | docker exec -i "$DB_CONTAINER" \
63
+ mariadb -ureservine -p${DB_PASS} reservine
64
+ else
65
+ docker exec -i "$DB_CONTAINER" \
66
+ mariadb -ureservine -p${DB_PASS} reservine < "$SNAPSHOT"
67
+ fi
68
+
69
+ success "Snapshot imported in ${SECONDS}s"
70
+
71
+ # Run pending migrations
72
+ info "Running pending migrations..."
73
+ docker compose -f "$COMPOSE_FILE" exec -T laravel.test php artisan migrate --force 2>&1 | tail -5
74
+ success "Migrations complete"
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Shared core utilities for Reservine worktree setup scripts.
4
+ #
5
+ # This file is sourced by setup-worktree-fe.sh and setup-worktree-be.sh.
6
+ # It provides: colors, logging, environment detection, ROOT_WORKTREE_PATH
7
+ # resolution, plan file resolution, and branch setup.
8
+ #
9
+ # Do NOT execute this file directly.
10
+ #
11
+
12
+ # Colors for output
13
+ RED='\033[0;31m'
14
+ GREEN='\033[0;32m'
15
+ YELLOW='\033[1;33m'
16
+ BLUE='\033[0;34m'
17
+ NC='\033[0m' # No Color
18
+
19
+ # Log file (set by --log flag or LOG_FILE env var)
20
+ LOG_FILE="${LOG_FILE:-}"
21
+
22
+ # Internal: write to log file if enabled
23
+ _log() {
24
+ if [[ -n "$LOG_FILE" ]]; then
25
+ echo "[$(date '+%H:%M:%S')] $1" >> "$LOG_FILE"
26
+ fi
27
+ }
28
+
29
+ # Logging helpers
30
+ info() { echo -e "${BLUE}ℹ${NC} $1"; _log "INFO $1"; }
31
+ success() {
32
+ echo -e "${GREEN}✓${NC} $1"
33
+ _log "OK $1"
34
+ command -v sayy &>/dev/null && sayy 0.3 "$1" &>/dev/null &
35
+ }
36
+ warn() { echo -e "${YELLOW}⚠️${NC} $1"; _log "WARN $1"; }
37
+ error() { echo -e "${RED}❌${NC} $1" >&2; _log "ERROR $1"; }
38
+
39
+ # Initialize log file (call from setup scripts after parsing --log)
40
+ init_log() {
41
+ if [[ -n "$LOG_FILE" ]]; then
42
+ mkdir -p "$(dirname "$LOG_FILE")"
43
+ echo "# Reservine-DX setup log — $(date)" > "$LOG_FILE"
44
+ echo "# PWD: $(pwd)" >> "$LOG_FILE"
45
+ echo "# Args: $*" >> "$LOG_FILE"
46
+ echo "" >> "$LOG_FILE"
47
+ info "Logging to: $LOG_FILE"
48
+ fi
49
+ }
50
+
51
+ # Deterministic port offset from branch name (0-99)
52
+ port_hash() {
53
+ local name="$1"
54
+ local hash=$(echo -n "$name" | cksum | awk '{print $1}')
55
+ echo $(( hash % 100 ))
56
+ }
57
+
58
+ # Check if a port is in use
59
+ port_in_use() {
60
+ lsof -i ":$1" -P -n >/dev/null 2>&1
61
+ }
62
+
63
+ # Get the branch name sanitized for Docker
64
+ get_branch_slug() {
65
+ local branch
66
+ branch=$(git branch --show-current 2>/dev/null || echo "unknown")
67
+ echo "$branch" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//'
68
+ }
69
+
70
+ # Sanitize branch name for use as directory name (strip # which breaks vite @fs URLs)
71
+ sanitize_worktree_dir() {
72
+ echo "$1" | sed 's/#//g'
73
+ }
74
+
75
+ # Find the FE worktree sibling (used by Laravel for proxy config generation)
76
+ find_fe_worktree() {
77
+ local branch_name
78
+ branch_name=$(git branch --show-current 2>/dev/null)
79
+ local safe_name
80
+ safe_name=$(sanitize_worktree_dir "$branch_name")
81
+
82
+ local fe_base
83
+ if [[ -n "${ROOT_WORKTREE_PATH:-}" ]]; then
84
+ fe_base="$(dirname "$ROOT_WORKTREE_PATH")/reservine"
85
+ else
86
+ fe_base="$(dirname "$(dirname "$PWD")")/reservine"
87
+ fi
88
+
89
+ # Try sanitized name first (no #), then exact branch name, then glob
90
+ for dir in \
91
+ "$fe_base/.worktrees/$safe_name" \
92
+ "$fe_base/.worktrees"/*/"$(basename "$safe_name")" \
93
+ "$fe_base/.worktrees/$branch_name" \
94
+ "$fe_base/.worktrees"/*/"$(basename "$branch_name")" \
95
+ "$fe_base/.worktrees"/*/*"${branch_name##*/}"*; do
96
+ if [[ -d "$dir" ]]; then
97
+ echo "$dir"
98
+ return 0
99
+ fi
100
+ done
101
+ return 1
102
+ }
103
+
104
+ # Find the BE worktree sibling (used by Angular for proxy config generation)
105
+ find_be_worktree() {
106
+ local branch_name
107
+ branch_name=$(git branch --show-current 2>/dev/null)
108
+ local safe_name
109
+ safe_name=$(sanitize_worktree_dir "$branch_name")
110
+
111
+ local be_base
112
+ if [[ -n "${ROOT_WORKTREE_PATH:-}" ]]; then
113
+ be_base="$(dirname "$ROOT_WORKTREE_PATH")/ReservineBack"
114
+ else
115
+ be_base="$(dirname "$(dirname "$PWD")")/ReservineBack"
116
+ fi
117
+
118
+ for dir in \
119
+ "$be_base/.worktrees/$safe_name" \
120
+ "$be_base/.worktrees"/*/"$(basename "$safe_name")" \
121
+ "$be_base/.worktrees/$branch_name" \
122
+ "$be_base/.worktrees"/*/"$(basename "$branch_name")"; do
123
+ if [[ -d "$dir" ]]; then
124
+ echo "$dir"
125
+ return 0
126
+ fi
127
+ done
128
+ return 1
129
+ }
130
+
131
+ # ============================================================================
132
+ # Resolve DX_SCRIPTS_DIR — directory containing these scripts
133
+ # ============================================================================
134
+
135
+ # If sourced from the marketplace install:
136
+ # ~/.claude/plugins/marketplaces/reservine-dx/plugins/reservine-dx/scripts/
137
+ # If sourced from a local checkout:
138
+ # /path/to/Reservine-DX/plugins/reservine-dx/scripts/
139
+ DX_SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
140
+ DX_PLUGIN_DIR="$(dirname "$DX_SCRIPTS_DIR")"
141
+
142
+ # ============================================================================
143
+ # Resolve ROOT_WORKTREE_PATH
144
+ # ============================================================================
145
+
146
+ resolve_root_worktree_path() {
147
+ if [[ -n "${ROOT_WORKTREE_PATH:-}" ]]; then
148
+ return 0
149
+ fi
150
+
151
+ # Try to auto-detect from git worktree list
152
+ DETECTED_ROOT=$(git worktree list 2>/dev/null \
153
+ | grep -v ".claude-worktrees" \
154
+ | grep -v ".cursor" \
155
+ | grep -v ".worktrees" \
156
+ | head -1 | awk '{print $1}')
157
+
158
+ if [[ -n "$DETECTED_ROOT" ]] && [[ -d "$DETECTED_ROOT" ]]; then
159
+ ROOT_WORKTREE_PATH="$DETECTED_ROOT"
160
+ fi
161
+ }
162
+
163
+ # ============================================================================
164
+ # Detect environment (cloud / worktree / main repo)
165
+ # ============================================================================
166
+
167
+ detect_environment() {
168
+ IS_CLOUD=false
169
+ IS_WORKTREE=false
170
+
171
+ if echo "$PWD" | grep -q ".claude-worktrees"; then
172
+ IS_WORKTREE=true
173
+ info "Environment: Worktree (path contains .claude-worktrees)"
174
+ elif git worktree list 2>/dev/null | grep -q "^$(pwd) "; then
175
+ IS_WORKTREE=true
176
+ info "Environment: Worktree (git worktree list confirms)"
177
+ elif [[ "${CLAUDE_CODE_REMOTE_ENVIRONMENT_TYPE:-}" == "cloud_default" ]] || [[ -n "${CLAUDE_CODE_REMOTE:-}" ]]; then
178
+ IS_CLOUD=true
179
+ info "Environment: Cloud (CLAUDE_CODE_REMOTE or cloud_default detected)"
180
+ elif [[ -n "${CLAUDECODE:-}" ]] && [[ ! -d "$HOME" || -f "/.dockerenv" ]]; then
181
+ # CLAUDECODE set AND (no real home dir OR inside Docker container) = likely cloud
182
+ IS_CLOUD=true
183
+ info "Environment: Cloud (CLAUDECODE detected, non-local system)"
184
+ else
185
+ info "Environment: Main Repository"
186
+ fi
187
+ }
188
+
189
+ # ============================================================================
190
+ # Plan file resolution
191
+ # ============================================================================
192
+
193
+ resolve_plan_file() {
194
+ local pattern="$1"
195
+ PLAN_FILE=$(find .claude/plans/ -name "*${pattern}*" -type f 2>/dev/null | head -1)
196
+
197
+ if [[ -z "$PLAN_FILE" ]]; then
198
+ error "No plan file matching '$pattern' found in .claude/plans/"
199
+ echo " Available plans:" >&2
200
+ find .claude/plans/ -name "*.md" -type f 2>/dev/null | head -10 | sed 's/^/ /' >&2
201
+ exit 1
202
+ fi
203
+
204
+ BRANCH_NAME=$(basename "$PLAN_FILE" .md)
205
+ echo ""
206
+ echo "📋 Plan Resolution"
207
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
208
+ echo " Pattern: $pattern"
209
+ echo " Found: $PLAN_FILE"
210
+ echo " Branch: $BRANCH_NAME"
211
+ echo ""
212
+ }
213
+
214
+ # ============================================================================
215
+ # Branch setup (creates or checks out branch)
216
+ # ============================================================================
217
+
218
+ setup_branch() {
219
+ local branch="$1"
220
+ local base="$2"
221
+
222
+ echo "📦 Branch Setup"
223
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
224
+
225
+ # Check if git is clean (ignore untracked files)
226
+ if [[ -n "$(git status --porcelain 2>/dev/null | grep -v '^??')" ]]; then
227
+ error "Working directory has uncommitted changes"
228
+ echo " Please commit or stash changes before proceeding" >&2
229
+ exit 1
230
+ fi
231
+ success "Working directory is clean"
232
+
233
+ # Check if based on target branch (warning only)
234
+ if git rev-parse "$base" &>/dev/null; then
235
+ if git merge-base --is-ancestor "$(git rev-parse "$base")" HEAD 2>/dev/null; then
236
+ success "Current HEAD is based on $base"
237
+ else
238
+ warn "Not based on $base - consider rebasing before PR"
239
+ fi
240
+ else
241
+ warn "Base branch '$base' not found locally"
242
+ fi
243
+
244
+ # Check if branch already exists
245
+ if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
246
+ warn "Branch '$branch' already exists - checking out"
247
+ git checkout "$branch"
248
+ else
249
+ success "Creating branch: $branch"
250
+ git checkout -b "$branch"
251
+ fi
252
+ success "On branch: $(git branch --show-current)"
253
+ echo ""
254
+ }
255
+
256
+ # ============================================================================
257
+ # Pre-flight: validate ROOT_WORKTREE_PATH (for non-cloud environments)
258
+ # ============================================================================
259
+
260
+ preflight_check() {
261
+ echo ""
262
+ echo "🚀 Setting up worktree..."
263
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
264
+
265
+ detect_environment
266
+
267
+ if [[ "$IS_CLOUD" == false ]]; then
268
+ if [[ -z "${ROOT_WORKTREE_PATH:-}" ]]; then
269
+ error "ROOT_WORKTREE_PATH could not be auto-detected"
270
+ echo " Set it manually:" >&2
271
+ echo " ROOT_WORKTREE_PATH=~/projects/my-repo ./setup-worktree.sh" >&2
272
+ exit 1
273
+ fi
274
+
275
+ if [[ ! -d "${ROOT_WORKTREE_PATH}" ]]; then
276
+ error "ROOT_WORKTREE_PATH does not exist: ${ROOT_WORKTREE_PATH}"
277
+ exit 1
278
+ fi
279
+
280
+ # Safety: prevent running in the main repo root (would destroy node_modules/vendor)
281
+ RESOLVED_ROOT=$(cd "$ROOT_WORKTREE_PATH" && pwd -P)
282
+ RESOLVED_CWD=$(pwd -P)
283
+ if [[ "$RESOLVED_ROOT" == "$RESOLVED_CWD" ]]; then
284
+ error "You are running this from the main repo root, not a worktree"
285
+ echo "" >&2
286
+ echo " This script is meant to run INSIDE a worktree, not in the main repo." >&2
287
+ echo " Running here would destroy your node_modules/vendor directory." >&2
288
+ echo "" >&2
289
+ echo " To create a worktree first:" >&2
290
+ echo " git worktree add .worktrees/<branch> origin/<base> -b <branch>" >&2
291
+ echo " cd .worktrees/<branch>" >&2
292
+ echo " <then run this script>" >&2
293
+ echo "" >&2
294
+ echo " Or use --branch to create one:" >&2
295
+ echo " <this-script> --branch=feat/my-feature" >&2
296
+ exit 1
297
+ fi
298
+
299
+ if [[ -n "${DETECTED_ROOT:-}" ]]; then
300
+ info "Auto-detected main repo: ${ROOT_WORKTREE_PATH}"
301
+ else
302
+ echo " Main repo: ${ROOT_WORKTREE_PATH}"
303
+ fi
304
+ fi
305
+ echo " Worktree: $(pwd)"
306
+ echo ""
307
+ }
308
+
309
+ # ============================================================================
310
+ # Final state output
311
+ # ============================================================================
312
+
313
+ print_state() {
314
+ local dep_dir="$1"
315
+ local branch="$2"
316
+
317
+ if [[ -n "$branch" ]]; then
318
+ echo "📍 Current State:"
319
+ echo " Branch: $(git branch --show-current)"
320
+ echo " Path: $(pwd)"
321
+ if [[ -L "$dep_dir" ]]; then
322
+ echo " ${dep_dir}: symlinked → $(readlink "$dep_dir")"
323
+ else
324
+ echo " ${dep_dir}: installed locally"
325
+ fi
326
+ fi
327
+
328
+ # Final audio notification
329
+ command -v sayy &>/dev/null && sayy 0.5 "Worktree setup complete" &>/dev/null &
330
+ }