@smicolon/ai-kit 0.3.2 → 0.4.1
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/README.md +73 -40
- package/dist/index.js +312 -127
- package/package.json +5 -5
- package/.claude-plugin/marketplace.json +0 -369
- package/packs/architect/CHANGELOG.md +0 -17
- package/packs/architect/README.md +0 -58
- package/packs/architect/agents/system-architect.md +0 -768
- package/packs/architect/commands/diagram-create.md +0 -300
- package/packs/better-auth/.mcp.json +0 -14
- package/packs/better-auth/CHANGELOG.md +0 -26
- package/packs/better-auth/README.md +0 -125
- package/packs/better-auth/agents/auth-architect.md +0 -278
- package/packs/better-auth/commands/auth-provider-add.md +0 -265
- package/packs/better-auth/commands/auth-setup.md +0 -298
- package/packs/better-auth/skills/auth-security/SKILL.md +0 -425
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +0 -455
- package/packs/dev-loop/CHANGELOG.md +0 -69
- package/packs/dev-loop/README.md +0 -155
- package/packs/dev-loop/commands/cancel-dev.md +0 -21
- package/packs/dev-loop/commands/dev-loop.md +0 -72
- package/packs/dev-loop/commands/dev-plan.md +0 -351
- package/packs/dev-loop/hooks/hooks.json +0 -15
- package/packs/dev-loop/hooks/stop-hook.sh +0 -178
- package/packs/dev-loop/scripts/setup-dev-loop.sh +0 -194
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +0 -249
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +0 -874
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +0 -260
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +0 -275
- package/packs/django/CHANGELOG.md +0 -39
- package/packs/django/README.md +0 -92
- package/packs/django/agents/django-architect.md +0 -182
- package/packs/django/agents/django-builder.md +0 -250
- package/packs/django/agents/django-feature-based.md +0 -420
- package/packs/django/agents/django-reviewer.md +0 -253
- package/packs/django/agents/django-tester.md +0 -230
- package/packs/django/commands/api-endpoint.md +0 -285
- package/packs/django/commands/model-create.md +0 -178
- package/packs/django/commands/test-generate.md +0 -325
- package/packs/django/rules/migrations.md +0 -138
- package/packs/django/rules/models.md +0 -167
- package/packs/django/rules/serializers.md +0 -126
- package/packs/django/rules/services.md +0 -131
- package/packs/django/rules/tests.md +0 -140
- package/packs/django/rules/views.md +0 -102
- package/packs/django/skills/import-convention-enforcer/SKILL.md +0 -226
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +0 -343
- package/packs/django/skills/migration-safety-checker/SKILL.md +0 -375
- package/packs/django/skills/model-entity-validator/SKILL.md +0 -298
- package/packs/django/skills/performance-optimizer/SKILL.md +0 -447
- package/packs/django/skills/red-phase-verifier/SKILL.md +0 -180
- package/packs/django/skills/security-first-validator/SKILL.md +0 -435
- package/packs/django/skills/test-coverage-advisor/SKILL.md +0 -394
- package/packs/django/skills/test-validity-checker/SKILL.md +0 -194
- package/packs/failure-log/CHANGELOG.md +0 -20
- package/packs/failure-log/README.md +0 -168
- package/packs/failure-log/commands/failure-add.md +0 -106
- package/packs/failure-log/commands/failure-list.md +0 -89
- package/packs/failure-log/hooks/hooks.json +0 -16
- package/packs/failure-log/hooks/scripts/inject-failures.sh +0 -64
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +0 -164
- package/packs/flutter/CHANGELOG.md +0 -19
- package/packs/flutter/README.md +0 -170
- package/packs/flutter/agents/flutter-architect.md +0 -166
- package/packs/flutter/agents/flutter-builder.md +0 -303
- package/packs/flutter/agents/release-manager.md +0 -355
- package/packs/flutter/commands/fastlane-setup.md +0 -188
- package/packs/flutter/commands/flutter-build.md +0 -90
- package/packs/flutter/commands/flutter-deploy.md +0 -133
- package/packs/flutter/commands/flutter-test.md +0 -117
- package/packs/flutter/commands/signing-setup.md +0 -209
- package/packs/flutter/hooks/hooks.json +0 -17
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +0 -193
- package/packs/flutter/skills/flutter-architecture/SKILL.md +0 -127
- package/packs/flutter/skills/store-publishing/SKILL.md +0 -163
- package/packs/hono/CHANGELOG.md +0 -19
- package/packs/hono/README.md +0 -143
- package/packs/hono/agents/hono-architect.md +0 -240
- package/packs/hono/agents/hono-builder.md +0 -285
- package/packs/hono/agents/hono-reviewer.md +0 -279
- package/packs/hono/agents/hono-tester.md +0 -346
- package/packs/hono/commands/middleware-create.md +0 -223
- package/packs/hono/commands/project-init.md +0 -306
- package/packs/hono/commands/route-create.md +0 -153
- package/packs/hono/commands/rpc-client.md +0 -263
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +0 -408
- package/packs/hono/skills/hono-patterns/SKILL.md +0 -309
- package/packs/hono/skills/rpc-typesafe/SKILL.md +0 -388
- package/packs/hono/skills/zod-validation/SKILL.md +0 -332
- package/packs/nestjs/CHANGELOG.md +0 -29
- package/packs/nestjs/README.md +0 -75
- package/packs/nestjs/agents/nestjs-architect.md +0 -402
- package/packs/nestjs/agents/nestjs-builder.md +0 -301
- package/packs/nestjs/agents/nestjs-tester.md +0 -437
- package/packs/nestjs/commands/module-create.md +0 -369
- package/packs/nestjs/rules/controllers.md +0 -92
- package/packs/nestjs/rules/dto.md +0 -124
- package/packs/nestjs/rules/entities.md +0 -102
- package/packs/nestjs/rules/services.md +0 -106
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +0 -389
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +0 -365
- package/packs/nextjs/CHANGELOG.md +0 -36
- package/packs/nextjs/README.md +0 -76
- package/packs/nextjs/agents/frontend-tester.md +0 -680
- package/packs/nextjs/agents/frontend-visual.md +0 -820
- package/packs/nextjs/agents/nextjs-architect.md +0 -331
- package/packs/nextjs/agents/nextjs-modular.md +0 -433
- package/packs/nextjs/commands/component-create.md +0 -398
- package/packs/nextjs/rules/api-routes.md +0 -129
- package/packs/nextjs/rules/components.md +0 -106
- package/packs/nextjs/rules/hooks.md +0 -132
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +0 -445
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +0 -399
- package/packs/nextjs/skills/react-form-validator/SKILL.md +0 -569
- package/packs/nuxtjs/CHANGELOG.md +0 -30
- package/packs/nuxtjs/README.md +0 -56
- package/packs/nuxtjs/agents/frontend-tester.md +0 -680
- package/packs/nuxtjs/agents/frontend-visual.md +0 -820
- package/packs/nuxtjs/agents/nuxtjs-architect.md +0 -537
- package/packs/nuxtjs/commands/component-create.md +0 -223
- package/packs/nuxtjs/rules/components.md +0 -101
- package/packs/nuxtjs/rules/composables.md +0 -118
- package/packs/nuxtjs/rules/server-routes.md +0 -127
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +0 -183
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +0 -196
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +0 -190
- package/packs/onboard/CHANGELOG.md +0 -22
- package/packs/onboard/README.md +0 -103
- package/packs/onboard/agents/onboard-guide.md +0 -118
- package/packs/onboard/commands/onboard.md +0 -313
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +0 -98
- package/packs/tanstack-router/CHANGELOG.md +0 -30
- package/packs/tanstack-router/README.md +0 -113
- package/packs/tanstack-router/agents/tanstack-architect.md +0 -173
- package/packs/tanstack-router/agents/tanstack-builder.md +0 -360
- package/packs/tanstack-router/agents/tanstack-tester.md +0 -454
- package/packs/tanstack-router/commands/form-create.md +0 -313
- package/packs/tanstack-router/commands/query-create.md +0 -263
- package/packs/tanstack-router/commands/route-create.md +0 -190
- package/packs/tanstack-router/commands/table-create.md +0 -413
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +0 -370
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +0 -346
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +0 -415
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +0 -425
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +0 -341
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +0 -359
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +0 -285
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +0 -351
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +0 -531
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +0 -428
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +0 -490
- package/packs/worktree/CHANGELOG.md +0 -45
- package/packs/worktree/README.md +0 -219
- package/packs/worktree/commands/wt.md +0 -93
- package/packs/worktree/scripts/wt.sh +0 -957
- package/packs/worktree/skills/worktree-manager/SKILL.md +0 -113
|
@@ -1,957 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
# =============================================================================
|
|
5
|
-
# wt.sh - Git Worktree Manager
|
|
6
|
-
# Naming: ~/code/project/ → ~/code/project--branch-name/
|
|
7
|
-
# =============================================================================
|
|
8
|
-
|
|
9
|
-
# Colors
|
|
10
|
-
RED='\033[0;31m'
|
|
11
|
-
GREEN='\033[0;32m'
|
|
12
|
-
YELLOW='\033[1;33m'
|
|
13
|
-
BLUE='\033[0;34m'
|
|
14
|
-
CYAN='\033[0;36m'
|
|
15
|
-
NC='\033[0m' # No Color
|
|
16
|
-
|
|
17
|
-
# Helpers
|
|
18
|
-
info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
|
19
|
-
success() { echo -e "${GREEN}✓${NC} $1"; }
|
|
20
|
-
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
21
|
-
error() { echo -e "${RED}✗${NC} $1" >&2; }
|
|
22
|
-
|
|
23
|
-
# Get repo root and name (finds main worktree, not current worktree)
|
|
24
|
-
get_repo_info() {
|
|
25
|
-
# First check we're in a git repo
|
|
26
|
-
git rev-parse --show-toplevel &>/dev/null || {
|
|
27
|
-
error "Not in a git repository"
|
|
28
|
-
exit 1
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# Get the main worktree (first line of git worktree list)
|
|
32
|
-
REPO_ROOT=$(git worktree list --porcelain | grep "^worktree " | head -1 | sed 's/^worktree //')
|
|
33
|
-
|
|
34
|
-
if [[ -z "$REPO_ROOT" ]]; then
|
|
35
|
-
error "Could not determine main worktree"
|
|
36
|
-
exit 1
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
REPO_NAME=$(basename "$REPO_ROOT")
|
|
40
|
-
REPO_PARENT=$(dirname "$REPO_ROOT")
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
# Detect package manager
|
|
44
|
-
detect_pkg_manager() {
|
|
45
|
-
local dir="${1:-$REPO_ROOT}"
|
|
46
|
-
if [[ -f "$dir/bun.lockb" ]] || [[ -f "$dir/bun.lock" ]]; then
|
|
47
|
-
echo "bun"
|
|
48
|
-
elif [[ -f "$dir/pnpm-lock.yaml" ]]; then
|
|
49
|
-
echo "pnpm"
|
|
50
|
-
elif [[ -f "$dir/yarn.lock" ]]; then
|
|
51
|
-
echo "yarn"
|
|
52
|
-
elif [[ -f "$dir/package-lock.json" ]] || [[ -f "$dir/package.json" ]]; then
|
|
53
|
-
echo "npm"
|
|
54
|
-
else
|
|
55
|
-
echo ""
|
|
56
|
-
fi
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
# Detect if monorepo
|
|
60
|
-
is_monorepo() {
|
|
61
|
-
local dir="${1:-$REPO_ROOT}"
|
|
62
|
-
[[ -f "$dir/pnpm-workspace.yaml" ]] && return 0
|
|
63
|
-
[[ -f "$dir/turbo.json" ]] && return 0
|
|
64
|
-
[[ -f "$dir/nx.json" ]] && return 0
|
|
65
|
-
[[ -f "$dir/lerna.json" ]] && return 0
|
|
66
|
-
if [[ -f "$dir/package.json" ]]; then
|
|
67
|
-
grep -q '"workspaces"' "$dir/package.json" 2>/dev/null && return 0
|
|
68
|
-
fi
|
|
69
|
-
return 1
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
# Get worktree path from branch name
|
|
73
|
-
get_worktree_path() {
|
|
74
|
-
local branch="$1"
|
|
75
|
-
local safe_branch="${branch//\//-}"
|
|
76
|
-
echo "${REPO_PARENT}/${REPO_NAME}--${safe_branch}"
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
# Strip main repo name prefix from worktree basename
|
|
80
|
-
get_branch_from_worktree() {
|
|
81
|
-
local wt_path="$1"
|
|
82
|
-
local wt_name=$(basename "$wt_path")
|
|
83
|
-
echo "${wt_name#${REPO_NAME}--}"
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
# =============================================================================
|
|
87
|
-
# Phase 1: .worktreeinclude Parsing & File Copying
|
|
88
|
-
# =============================================================================
|
|
89
|
-
|
|
90
|
-
# Generate default .worktreeinclude if it doesn't exist
|
|
91
|
-
generate_default_worktreeinclude() {
|
|
92
|
-
local target="$REPO_ROOT/.worktreeinclude"
|
|
93
|
-
[[ -f "$target" ]] && return 0
|
|
94
|
-
|
|
95
|
-
cat > "$target" << 'TEMPLATE'
|
|
96
|
-
# .worktreeinclude — Files to copy to new worktrees
|
|
97
|
-
# Commit this file so your team shares the same config
|
|
98
|
-
.env*
|
|
99
|
-
|
|
100
|
-
# Monorepo (uncomment as needed)
|
|
101
|
-
# apps/*/.env*
|
|
102
|
-
# packages/*/.env*
|
|
103
|
-
# services/*/.env*
|
|
104
|
-
|
|
105
|
-
[rewrite]
|
|
106
|
-
auto
|
|
107
|
-
|
|
108
|
-
[docker]
|
|
109
|
-
auto
|
|
110
|
-
# file=apps/backend/docker-compose.local.yml
|
|
111
|
-
# port_offset=10
|
|
112
|
-
TEMPLATE
|
|
113
|
-
info "Generated .worktreeinclude (commit this file)"
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
# Parse .worktreeinclude into 3 arrays: file patterns, rewrite lines, docker lines
|
|
117
|
-
parse_worktreeinclude() {
|
|
118
|
-
local include_file="$REPO_ROOT/.worktreeinclude"
|
|
119
|
-
WT_FILE_PATTERNS=()
|
|
120
|
-
WT_REWRITE_LINES=()
|
|
121
|
-
WT_DOCKER_LINES=()
|
|
122
|
-
|
|
123
|
-
[[ -f "$include_file" ]] || return 0
|
|
124
|
-
|
|
125
|
-
local current_section="files"
|
|
126
|
-
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
127
|
-
# Strip trailing whitespace
|
|
128
|
-
line="${line%"${line##*[![:space:]]}"}"
|
|
129
|
-
# Skip empty lines and comments
|
|
130
|
-
[[ -z "$line" ]] && continue
|
|
131
|
-
[[ "$line" == \#* ]] && continue
|
|
132
|
-
|
|
133
|
-
# Detect section headers
|
|
134
|
-
if [[ "$line" == "[rewrite]" ]]; then
|
|
135
|
-
current_section="rewrite"
|
|
136
|
-
continue
|
|
137
|
-
elif [[ "$line" == "[docker]" ]]; then
|
|
138
|
-
current_section="docker"
|
|
139
|
-
continue
|
|
140
|
-
fi
|
|
141
|
-
|
|
142
|
-
case "$current_section" in
|
|
143
|
-
files) WT_FILE_PATTERNS+=("$line") ;;
|
|
144
|
-
rewrite) WT_REWRITE_LINES+=("$line") ;;
|
|
145
|
-
docker) WT_DOCKER_LINES+=("$line") ;;
|
|
146
|
-
esac
|
|
147
|
-
done < "$include_file"
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
# Expand glob patterns against main worktree, return matched files (relative paths)
|
|
151
|
-
match_files_by_patterns() {
|
|
152
|
-
local src="$1"
|
|
153
|
-
MATCHED_FILES=()
|
|
154
|
-
|
|
155
|
-
for pattern in "${WT_FILE_PATTERNS[@]}"; do
|
|
156
|
-
# Use subshell with nullglob to safely expand globs
|
|
157
|
-
while IFS= read -r -d '' file; do
|
|
158
|
-
local rel="${file#"$src"/}"
|
|
159
|
-
MATCHED_FILES+=("$rel")
|
|
160
|
-
done < <(
|
|
161
|
-
cd "$src"
|
|
162
|
-
shopt -s nullglob
|
|
163
|
-
shopt -s globstar 2>/dev/null || true # bash 4+ only, needed for **
|
|
164
|
-
for f in $pattern; do
|
|
165
|
-
[[ -f "$f" ]] && printf '%s\0' "$src/$f"
|
|
166
|
-
done
|
|
167
|
-
)
|
|
168
|
-
done
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
# Copy matched files preserving directory structure
|
|
172
|
-
copy_matched_files() {
|
|
173
|
-
local src="$1"
|
|
174
|
-
local dst="$2"
|
|
175
|
-
local count=0
|
|
176
|
-
|
|
177
|
-
for rel in "${MATCHED_FILES[@]+"${MATCHED_FILES[@]}"}"; do
|
|
178
|
-
local dir=$(dirname "$rel")
|
|
179
|
-
[[ "$dir" != "." ]] && mkdir -p "$dst/$dir"
|
|
180
|
-
cp "$src/$rel" "$dst/$rel"
|
|
181
|
-
((count++))
|
|
182
|
-
done
|
|
183
|
-
|
|
184
|
-
echo "$count"
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
# =============================================================================
|
|
188
|
-
# Phase 2: Env Var Rewriting
|
|
189
|
-
# =============================================================================
|
|
190
|
-
|
|
191
|
-
# Branch name → safe slug for suffixing (lowercase, special chars → _, max 30)
|
|
192
|
-
sanitize_branch_for_suffix() {
|
|
193
|
-
local branch="$1"
|
|
194
|
-
local slug
|
|
195
|
-
slug=$(printf '%s' "$branch" | tr '[:upper:]' '[:lower:]' | tr -c '[:alnum:]' '_')
|
|
196
|
-
# Trim leading/trailing underscores
|
|
197
|
-
slug="${slug#_}"
|
|
198
|
-
slug="${slug%_}"
|
|
199
|
-
# Truncate to 30 chars
|
|
200
|
-
printf '%s' "${slug:0:30}"
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
# Rewrite a single .env file — auto-detect known keys + template {{BRANCH}}
|
|
204
|
-
rewrite_env_file() {
|
|
205
|
-
local env_file="$1"
|
|
206
|
-
local branch_slug="$2"
|
|
207
|
-
[[ -f "$env_file" ]] || return 0
|
|
208
|
-
local has_auto=false
|
|
209
|
-
|
|
210
|
-
# Check if auto mode is enabled
|
|
211
|
-
for line in "${WT_REWRITE_LINES[@]}"; do
|
|
212
|
-
[[ "$line" == "auto" ]] && has_auto=true
|
|
213
|
-
done
|
|
214
|
-
|
|
215
|
-
local tmpfile
|
|
216
|
-
tmpfile=$(mktemp)
|
|
217
|
-
local changed=false
|
|
218
|
-
|
|
219
|
-
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
220
|
-
local newline="$line"
|
|
221
|
-
|
|
222
|
-
# Template: replace {{BRANCH}} placeholders
|
|
223
|
-
if [[ "$line" == *'{{BRANCH}}'* ]]; then
|
|
224
|
-
newline="${line//\{\{BRANCH\}\}/$branch_slug}"
|
|
225
|
-
changed=true
|
|
226
|
-
elif [[ "$has_auto" == true ]]; then
|
|
227
|
-
# Auto-detect known keys (only lines with = that aren't comments)
|
|
228
|
-
if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)=(.*) ]]; then
|
|
229
|
-
local key="${BASH_REMATCH[1]}"
|
|
230
|
-
local val="${BASH_REMATCH[2]}"
|
|
231
|
-
# Strip surrounding quotes from val for processing
|
|
232
|
-
local raw_val="$val"
|
|
233
|
-
raw_val="${raw_val#\"}"
|
|
234
|
-
raw_val="${raw_val%\"}"
|
|
235
|
-
raw_val="${raw_val#\'}"
|
|
236
|
-
raw_val="${raw_val%\'}"
|
|
237
|
-
|
|
238
|
-
case "$key" in
|
|
239
|
-
DB_NAME|POSTGRES_DB|MYSQL_DATABASE|DATABASE_NAME)
|
|
240
|
-
# Suffix the value
|
|
241
|
-
newline="${key}=${raw_val}_${branch_slug}"
|
|
242
|
-
changed=true
|
|
243
|
-
;;
|
|
244
|
-
DATABASE_URL|POSTGRES_URL)
|
|
245
|
-
# Parse URL: scheme://user:pass@host:port/dbname?params
|
|
246
|
-
# Suffix the database name portion
|
|
247
|
-
if [[ "$raw_val" =~ ^(.*://[^/]*/?)([^?]+)(.*) ]]; then
|
|
248
|
-
local prefix="${BASH_REMATCH[1]}"
|
|
249
|
-
local dbname="${BASH_REMATCH[2]}"
|
|
250
|
-
local suffix="${BASH_REMATCH[3]}"
|
|
251
|
-
newline="${key}=${prefix}${dbname}_${branch_slug}${suffix}"
|
|
252
|
-
changed=true
|
|
253
|
-
fi
|
|
254
|
-
;;
|
|
255
|
-
COMPOSE_PROJECT_NAME)
|
|
256
|
-
newline="${key}=${raw_val}_${branch_slug}"
|
|
257
|
-
changed=true
|
|
258
|
-
;;
|
|
259
|
-
esac
|
|
260
|
-
fi
|
|
261
|
-
fi
|
|
262
|
-
|
|
263
|
-
printf '%s\n' "$newline"
|
|
264
|
-
done < "$env_file" > "$tmpfile"
|
|
265
|
-
|
|
266
|
-
if [[ "$changed" == true ]]; then
|
|
267
|
-
mv "$tmpfile" "$env_file"
|
|
268
|
-
else
|
|
269
|
-
rm -f "$tmpfile"
|
|
270
|
-
fi
|
|
271
|
-
|
|
272
|
-
echo "$changed"
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
# Find and rewrite all .env* files in the worktree
|
|
276
|
-
rewrite_all_env_files() {
|
|
277
|
-
local wt_path="$1"
|
|
278
|
-
local branch_slug="$2"
|
|
279
|
-
local count=0
|
|
280
|
-
|
|
281
|
-
# Only proceed if there are rewrite lines configured
|
|
282
|
-
[[ ${#WT_REWRITE_LINES[@]} -gt 0 ]] || return 0
|
|
283
|
-
|
|
284
|
-
while IFS= read -r -d '' env_file; do
|
|
285
|
-
local basename_f
|
|
286
|
-
basename_f=$(basename "$env_file")
|
|
287
|
-
# Only process .env* files (not .envrc or similar non-env files)
|
|
288
|
-
[[ "$basename_f" == .env* ]] || continue
|
|
289
|
-
local result
|
|
290
|
-
result=$(rewrite_env_file "$env_file" "$branch_slug")
|
|
291
|
-
[[ "$result" == "true" ]] && ((count++))
|
|
292
|
-
done < <(find "$wt_path" -type f -name '.env*' -not -path '*/node_modules/*' -not -path '*/.git/*' -print0 2>/dev/null)
|
|
293
|
-
|
|
294
|
-
echo "$count"
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
# =============================================================================
|
|
298
|
-
# Phase 3: Docker Compose Isolation
|
|
299
|
-
# =============================================================================
|
|
300
|
-
|
|
301
|
-
# Deterministic port offset from branch name (1-100 range via cksum)
|
|
302
|
-
branch_to_port_offset() {
|
|
303
|
-
local branch="$1"
|
|
304
|
-
local hash
|
|
305
|
-
hash=$(printf '%s' "$branch" | cksum | awk '{print $1}')
|
|
306
|
-
echo $(( (hash % 100) + 1 ))
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
# Minimal YAML parser: extract host ports from docker-compose services
|
|
310
|
-
# Output format: service_name:host_port:container_port (one per line)
|
|
311
|
-
parse_docker_compose_ports() {
|
|
312
|
-
local compose_file="$1"
|
|
313
|
-
awk '
|
|
314
|
-
/^[[:space:]]*[a-zA-Z_][a-zA-Z0-9_-]*:/ && !/^\s*-/ && !/ports:/ && !/image:/ && !/container_name:/ {
|
|
315
|
-
# Top-level or nested service name
|
|
316
|
-
indent = 0
|
|
317
|
-
for (i = 1; i <= length($0); i++) {
|
|
318
|
-
if (substr($0, i, 1) == " ") indent++
|
|
319
|
-
else break
|
|
320
|
-
}
|
|
321
|
-
line = $0
|
|
322
|
-
gsub(/^[[:space:]]+/, "", line)
|
|
323
|
-
gsub(/:.*/, "", line)
|
|
324
|
-
if (indent <= 4) current_service = line
|
|
325
|
-
}
|
|
326
|
-
/ports:/ {
|
|
327
|
-
in_ports = 1
|
|
328
|
-
next
|
|
329
|
-
}
|
|
330
|
-
in_ports && /^[[:space:]]*-[[:space:]]*"?[0-9]/ {
|
|
331
|
-
line = $0
|
|
332
|
-
gsub(/^[[:space:]]*-[[:space:]]*/, "", line)
|
|
333
|
-
gsub(/"/, "", line)
|
|
334
|
-
gsub(/[[:space:]].*/, "", line)
|
|
335
|
-
# line is now like "5432:5432" or "8025:8025"
|
|
336
|
-
split(line, parts, ":")
|
|
337
|
-
if (length(parts) >= 2) {
|
|
338
|
-
print current_service ":" parts[1] ":" parts[2]
|
|
339
|
-
}
|
|
340
|
-
next
|
|
341
|
-
}
|
|
342
|
-
in_ports && /^[[:space:]]*[^-[:space:]]/ {
|
|
343
|
-
in_ports = 0
|
|
344
|
-
}
|
|
345
|
-
' "$compose_file"
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
# Detect compose file path — checks [docker] file= directive, then auto-detects
|
|
349
|
-
detect_compose_file() {
|
|
350
|
-
local wt_path="$1"
|
|
351
|
-
local compose_file=""
|
|
352
|
-
|
|
353
|
-
# Check for file= directive in [docker] config
|
|
354
|
-
for line in "${WT_DOCKER_LINES[@]}"; do
|
|
355
|
-
if [[ "$line" =~ ^file=(.+) ]]; then
|
|
356
|
-
local specified="${BASH_REMATCH[1]}"
|
|
357
|
-
specified="${specified#"${specified%%[![:space:]]*}"}" # trim leading
|
|
358
|
-
specified="${specified%"${specified##*[![:space:]]}"}" # trim trailing
|
|
359
|
-
if [[ -f "$wt_path/$specified" ]]; then
|
|
360
|
-
echo "$specified"
|
|
361
|
-
return 0
|
|
362
|
-
else
|
|
363
|
-
warn "Specified compose file not found: $specified"
|
|
364
|
-
fi
|
|
365
|
-
fi
|
|
366
|
-
done
|
|
367
|
-
|
|
368
|
-
# Auto-detect in standard locations
|
|
369
|
-
local candidates=(
|
|
370
|
-
"local.yml"
|
|
371
|
-
"docker-compose.local.yml"
|
|
372
|
-
"docker-compose.yml"
|
|
373
|
-
"docker-compose.yaml"
|
|
374
|
-
"compose.yml"
|
|
375
|
-
"compose.yaml"
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
# Check root first
|
|
379
|
-
for candidate in "${candidates[@]}"; do
|
|
380
|
-
if [[ -f "$wt_path/$candidate" ]]; then
|
|
381
|
-
echo "$candidate"
|
|
382
|
-
return 0
|
|
383
|
-
fi
|
|
384
|
-
done
|
|
385
|
-
|
|
386
|
-
# Check nested app directories
|
|
387
|
-
for subdir in apps services; do
|
|
388
|
-
[[ -d "$wt_path/$subdir" ]] || continue
|
|
389
|
-
for app_dir in "$wt_path/$subdir"/*/; do
|
|
390
|
-
[[ -d "$app_dir" ]] || continue
|
|
391
|
-
for candidate in "${candidates[@]}"; do
|
|
392
|
-
if [[ -f "$app_dir$candidate" ]]; then
|
|
393
|
-
local rel="${app_dir#"$wt_path"/}$candidate"
|
|
394
|
-
echo "$rel"
|
|
395
|
-
return 0
|
|
396
|
-
fi
|
|
397
|
-
done
|
|
398
|
-
done
|
|
399
|
-
done
|
|
400
|
-
|
|
401
|
-
return 1
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
# Generate docker-compose.worktree.yml override with port offsets
|
|
405
|
-
generate_docker_compose_override() {
|
|
406
|
-
local compose_file="$1" # full path to original compose file
|
|
407
|
-
local offset="$2"
|
|
408
|
-
local override_dir
|
|
409
|
-
override_dir=$(dirname "$compose_file")
|
|
410
|
-
local override_file="$override_dir/docker-compose.worktree.yml"
|
|
411
|
-
|
|
412
|
-
local ports_data
|
|
413
|
-
ports_data=$(parse_docker_compose_ports "$compose_file")
|
|
414
|
-
|
|
415
|
-
[[ -z "$ports_data" ]] && return 1
|
|
416
|
-
|
|
417
|
-
local tmpfile
|
|
418
|
-
tmpfile=$(mktemp)
|
|
419
|
-
|
|
420
|
-
cat > "$tmpfile" << 'HEADER'
|
|
421
|
-
# Auto-generated by wt — DO NOT EDIT
|
|
422
|
-
# Port offsets for worktree isolation
|
|
423
|
-
services:
|
|
424
|
-
HEADER
|
|
425
|
-
|
|
426
|
-
local current_service=""
|
|
427
|
-
while IFS=: read -r service host_port container_port; do
|
|
428
|
-
[[ -z "$service" ]] && continue
|
|
429
|
-
local new_port=$((host_port + offset))
|
|
430
|
-
|
|
431
|
-
if [[ "$service" != "$current_service" ]]; then
|
|
432
|
-
printf ' %s:\n' "$service" >> "$tmpfile"
|
|
433
|
-
printf ' ports:\n' >> "$tmpfile"
|
|
434
|
-
current_service="$service"
|
|
435
|
-
fi
|
|
436
|
-
printf ' - "%d:%s"\n' "$new_port" "$container_port" >> "$tmpfile"
|
|
437
|
-
done <<< "$ports_data"
|
|
438
|
-
|
|
439
|
-
mv "$tmpfile" "$override_file"
|
|
440
|
-
echo "$override_file"
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
# Orchestrate Docker isolation: detect, offset, generate override, update .env
|
|
444
|
-
setup_docker_isolation() {
|
|
445
|
-
local wt_path="$1"
|
|
446
|
-
local branch="$2"
|
|
447
|
-
local branch_slug="$3"
|
|
448
|
-
|
|
449
|
-
# Check if docker section has 'auto' or any config
|
|
450
|
-
local has_docker=false
|
|
451
|
-
for line in "${WT_DOCKER_LINES[@]}"; do
|
|
452
|
-
[[ "$line" == "auto" || "$line" =~ ^file= ]] && has_docker=true
|
|
453
|
-
done
|
|
454
|
-
[[ "$has_docker" == true ]] || return 0
|
|
455
|
-
|
|
456
|
-
local compose_rel
|
|
457
|
-
compose_rel=$(detect_compose_file "$wt_path") || {
|
|
458
|
-
warn "No docker-compose file found, skipping Docker isolation"
|
|
459
|
-
return 0
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
local compose_full="$wt_path/$compose_rel"
|
|
463
|
-
local offset
|
|
464
|
-
offset=$(branch_to_port_offset "$branch")
|
|
465
|
-
|
|
466
|
-
# Check for custom port_offset
|
|
467
|
-
for line in "${WT_DOCKER_LINES[@]}"; do
|
|
468
|
-
if [[ "$line" =~ ^port_offset=([0-9]+) ]]; then
|
|
469
|
-
offset="${BASH_REMATCH[1]}"
|
|
470
|
-
fi
|
|
471
|
-
done
|
|
472
|
-
|
|
473
|
-
info "Docker isolation: $compose_rel (port offset +$offset)"
|
|
474
|
-
|
|
475
|
-
local override_file
|
|
476
|
-
override_file=$(generate_docker_compose_override "$compose_full" "$offset") || {
|
|
477
|
-
warn "No ports found in compose file, skipping port override"
|
|
478
|
-
return 0
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
local override_rel="${override_file#"$wt_path"/}"
|
|
482
|
-
success "Generated $override_rel"
|
|
483
|
-
|
|
484
|
-
# Set COMPOSE_FILE and COMPOSE_PROJECT_NAME in root .env
|
|
485
|
-
local root_env="$wt_path/.env"
|
|
486
|
-
local compose_file_val="${compose_rel}:${override_rel}"
|
|
487
|
-
local project_name="${REPO_NAME}_${branch_slug}"
|
|
488
|
-
|
|
489
|
-
# Append or update COMPOSE_FILE in .env
|
|
490
|
-
if [[ -f "$root_env" ]]; then
|
|
491
|
-
local tmpfile
|
|
492
|
-
tmpfile=$(mktemp)
|
|
493
|
-
local found_cf=false
|
|
494
|
-
local found_cpn=false
|
|
495
|
-
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
496
|
-
if [[ "$line" =~ ^COMPOSE_FILE= ]]; then
|
|
497
|
-
printf 'COMPOSE_FILE=%s\n' "$compose_file_val"
|
|
498
|
-
found_cf=true
|
|
499
|
-
elif [[ "$line" =~ ^COMPOSE_PROJECT_NAME= ]]; then
|
|
500
|
-
printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name"
|
|
501
|
-
found_cpn=true
|
|
502
|
-
else
|
|
503
|
-
printf '%s\n' "$line"
|
|
504
|
-
fi
|
|
505
|
-
done < "$root_env" > "$tmpfile"
|
|
506
|
-
[[ "$found_cf" == false ]] && printf 'COMPOSE_FILE=%s\n' "$compose_file_val" >> "$tmpfile"
|
|
507
|
-
[[ "$found_cpn" == false ]] && printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name" >> "$tmpfile"
|
|
508
|
-
mv "$tmpfile" "$root_env"
|
|
509
|
-
else
|
|
510
|
-
printf 'COMPOSE_FILE=%s\n' "$compose_file_val" > "$root_env"
|
|
511
|
-
printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name" >> "$root_env"
|
|
512
|
-
fi
|
|
513
|
-
|
|
514
|
-
# Print port mapping summary
|
|
515
|
-
local ports_data
|
|
516
|
-
ports_data=$(parse_docker_compose_ports "$compose_full")
|
|
517
|
-
if [[ -n "$ports_data" ]]; then
|
|
518
|
-
echo ""
|
|
519
|
-
echo -e " ${CYAN}Port mappings:${NC}"
|
|
520
|
-
while IFS=: read -r service host_port container_port; do
|
|
521
|
-
[[ -z "$service" ]] && continue
|
|
522
|
-
local new_port=$((host_port + offset))
|
|
523
|
-
echo -e " ${service}: ${host_port} → ${GREEN}${new_port}${NC}"
|
|
524
|
-
done <<< "$ports_data"
|
|
525
|
-
fi
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
# =============================================================================
|
|
529
|
-
# Phase 4: Database Auto-Creation
|
|
530
|
-
# =============================================================================
|
|
531
|
-
|
|
532
|
-
# Detect Postgres — checks Docker containers first, then local
|
|
533
|
-
detect_postgres() {
|
|
534
|
-
# Check Docker containers for postgres
|
|
535
|
-
if command -v docker &>/dev/null; then
|
|
536
|
-
local pg_container
|
|
537
|
-
pg_container=$(docker ps --filter "ancestor=postgres" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
|
|
538
|
-
if [[ -n "$pg_container" ]]; then
|
|
539
|
-
echo "docker:$pg_container"
|
|
540
|
-
return 0
|
|
541
|
-
fi
|
|
542
|
-
# Also check by port binding (for custom images)
|
|
543
|
-
pg_container=$(docker ps --filter "publish=5432" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
|
|
544
|
-
if [[ -n "$pg_container" ]]; then
|
|
545
|
-
echo "docker:$pg_container"
|
|
546
|
-
return 0
|
|
547
|
-
fi
|
|
548
|
-
fi
|
|
549
|
-
|
|
550
|
-
# Check local postgres
|
|
551
|
-
if command -v pg_isready &>/dev/null && pg_isready -q 2>/dev/null; then
|
|
552
|
-
echo "local"
|
|
553
|
-
return 0
|
|
554
|
-
fi
|
|
555
|
-
|
|
556
|
-
return 1
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
# Create database if it doesn't exist (idempotent)
|
|
560
|
-
create_database_if_not_exists() {
|
|
561
|
-
local db_name="$1"
|
|
562
|
-
local pg_source="$2" # "docker:container_name" or "local"
|
|
563
|
-
|
|
564
|
-
local exists_query="SELECT 1 FROM pg_database WHERE datname='$db_name'"
|
|
565
|
-
local create_query="CREATE DATABASE \"$db_name\""
|
|
566
|
-
local result=""
|
|
567
|
-
|
|
568
|
-
if [[ "$pg_source" == local ]]; then
|
|
569
|
-
result=$(psql -tAc "$exists_query" postgres 2>/dev/null || true)
|
|
570
|
-
if [[ "$result" != "1" ]]; then
|
|
571
|
-
psql -c "$create_query" postgres 2>/dev/null && return 0 || return 1
|
|
572
|
-
fi
|
|
573
|
-
elif [[ "$pg_source" == docker:* ]]; then
|
|
574
|
-
local container="${pg_source#docker:}"
|
|
575
|
-
result=$(docker exec "$container" psql -U postgres -tAc "$exists_query" postgres 2>/dev/null || true)
|
|
576
|
-
if [[ "$result" != "1" ]]; then
|
|
577
|
-
docker exec "$container" psql -U postgres -c "$create_query" postgres 2>/dev/null && return 0 || return 1
|
|
578
|
-
fi
|
|
579
|
-
fi
|
|
580
|
-
|
|
581
|
-
return 0 # Already exists
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
# Read rewritten DB name from .env files and auto-create databases
|
|
585
|
-
auto_create_databases() {
|
|
586
|
-
local wt_path="$1"
|
|
587
|
-
local pg_source
|
|
588
|
-
|
|
589
|
-
pg_source=$(detect_postgres) || {
|
|
590
|
-
warn "Postgres not running, skipping database auto-creation"
|
|
591
|
-
return 0
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
# Collect unique database names from all .env files
|
|
595
|
-
local db_names=()
|
|
596
|
-
while IFS= read -r -d '' env_file; do
|
|
597
|
-
while IFS= read -r line; do
|
|
598
|
-
if [[ "$line" =~ ^(DB_NAME|POSTGRES_DB|MYSQL_DATABASE|DATABASE_NAME)=(.+) ]]; then
|
|
599
|
-
local val="${BASH_REMATCH[2]}"
|
|
600
|
-
val="${val#\"}" ; val="${val%\"}"
|
|
601
|
-
val="${val#\'}" ; val="${val%\'}"
|
|
602
|
-
# Only add if not already in array
|
|
603
|
-
local found=false
|
|
604
|
-
for existing in "${db_names[@]+"${db_names[@]}"}"; do
|
|
605
|
-
[[ "$existing" == "$val" ]] && found=true
|
|
606
|
-
done
|
|
607
|
-
[[ "$found" == false ]] && db_names+=("$val")
|
|
608
|
-
elif [[ "$line" =~ ^(DATABASE_URL|POSTGRES_URL)=(.+) ]]; then
|
|
609
|
-
local url="${BASH_REMATCH[2]}"
|
|
610
|
-
url="${url#\"}" ; url="${url%\"}"
|
|
611
|
-
# Extract db name from URL: scheme://user:pass@host:port/dbname
|
|
612
|
-
if [[ "$url" =~ ://[^/]*/([^?]+) ]]; then
|
|
613
|
-
local val="${BASH_REMATCH[1]}"
|
|
614
|
-
local found=false
|
|
615
|
-
for existing in "${db_names[@]+"${db_names[@]}"}"; do
|
|
616
|
-
[[ "$existing" == "$val" ]] && found=true
|
|
617
|
-
done
|
|
618
|
-
[[ "$found" == false ]] && db_names+=("$val")
|
|
619
|
-
fi
|
|
620
|
-
fi
|
|
621
|
-
done < "$env_file"
|
|
622
|
-
done < <(find "$wt_path" -maxdepth 3 -name '.env*' -not -path '*/node_modules/*' -not -path '*/.git/*' -print0 2>/dev/null)
|
|
623
|
-
|
|
624
|
-
for db_name in "${db_names[@]+"${db_names[@]}"}"; do
|
|
625
|
-
if create_database_if_not_exists "$db_name" "$pg_source"; then
|
|
626
|
-
success "Database ready: $db_name"
|
|
627
|
-
else
|
|
628
|
-
warn "Could not create database: $db_name"
|
|
629
|
-
fi
|
|
630
|
-
done
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
# =============================================================================
|
|
634
|
-
# Commands
|
|
635
|
-
# =============================================================================
|
|
636
|
-
|
|
637
|
-
cmd_create() {
|
|
638
|
-
local branch="${1:-}"
|
|
639
|
-
|
|
640
|
-
if [[ -z "$branch" ]]; then
|
|
641
|
-
error "Usage: wt create <branch-name>"
|
|
642
|
-
echo " Creates a new worktree for the given branch"
|
|
643
|
-
exit 1
|
|
644
|
-
fi
|
|
645
|
-
|
|
646
|
-
get_repo_info
|
|
647
|
-
local wt_path=$(get_worktree_path "$branch")
|
|
648
|
-
|
|
649
|
-
if [[ -d "$wt_path" ]]; then
|
|
650
|
-
error "Worktree already exists: $wt_path"
|
|
651
|
-
exit 1
|
|
652
|
-
fi
|
|
653
|
-
|
|
654
|
-
# --- Step 1: Create git worktree ---
|
|
655
|
-
info "Creating worktree for branch: $branch"
|
|
656
|
-
|
|
657
|
-
if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
|
|
658
|
-
info "Checking out existing local branch: $branch"
|
|
659
|
-
git worktree add "$wt_path" "$branch"
|
|
660
|
-
elif git show-ref --verify --quiet "refs/remotes/origin/$branch" 2>/dev/null; then
|
|
661
|
-
info "Checking out existing remote branch: origin/$branch"
|
|
662
|
-
git worktree add "$wt_path" "$branch"
|
|
663
|
-
else
|
|
664
|
-
info "Creating new branch: $branch"
|
|
665
|
-
git worktree add -b "$branch" "$wt_path"
|
|
666
|
-
fi
|
|
667
|
-
success "Worktree created at: $wt_path"
|
|
668
|
-
|
|
669
|
-
# --- Step 2: Load .worktreeinclude ---
|
|
670
|
-
generate_default_worktreeinclude
|
|
671
|
-
parse_worktreeinclude
|
|
672
|
-
|
|
673
|
-
# --- Step 3: Copy files matching patterns ---
|
|
674
|
-
if [[ ${#WT_FILE_PATTERNS[@]} -gt 0 ]]; then
|
|
675
|
-
info "Copying files from .worktreeinclude..."
|
|
676
|
-
match_files_by_patterns "$REPO_ROOT"
|
|
677
|
-
local file_count
|
|
678
|
-
file_count=$(copy_matched_files "$REPO_ROOT" "$wt_path")
|
|
679
|
-
if [[ "$file_count" -gt 0 ]]; then
|
|
680
|
-
success "Copied $file_count file(s)"
|
|
681
|
-
else
|
|
682
|
-
warn "No matching files found to copy"
|
|
683
|
-
fi
|
|
684
|
-
fi
|
|
685
|
-
|
|
686
|
-
# --- Step 4: Rewrite env vars ---
|
|
687
|
-
local branch_slug
|
|
688
|
-
branch_slug=$(sanitize_branch_for_suffix "$branch")
|
|
689
|
-
|
|
690
|
-
if [[ ${#WT_REWRITE_LINES[@]} -gt 0 ]]; then
|
|
691
|
-
info "Rewriting env vars (suffix: _$branch_slug)..."
|
|
692
|
-
local rewrite_count
|
|
693
|
-
rewrite_count=$(rewrite_all_env_files "$wt_path" "$branch_slug")
|
|
694
|
-
if [[ "$rewrite_count" -gt 0 ]]; then
|
|
695
|
-
success "Rewrote $rewrite_count .env file(s)"
|
|
696
|
-
fi
|
|
697
|
-
fi
|
|
698
|
-
|
|
699
|
-
# --- Step 5: Docker Compose isolation ---
|
|
700
|
-
if [[ ${#WT_DOCKER_LINES[@]} -gt 0 ]]; then
|
|
701
|
-
setup_docker_isolation "$wt_path" "$branch" "$branch_slug"
|
|
702
|
-
fi
|
|
703
|
-
|
|
704
|
-
# --- Step 6: Auto-create database ---
|
|
705
|
-
if [[ ${#WT_REWRITE_LINES[@]} -gt 0 ]]; then
|
|
706
|
-
auto_create_databases "$wt_path"
|
|
707
|
-
fi
|
|
708
|
-
|
|
709
|
-
# --- Step 7: Install dependencies ---
|
|
710
|
-
local pkg_mgr=$(detect_pkg_manager "$wt_path")
|
|
711
|
-
if [[ -n "$pkg_mgr" ]]; then
|
|
712
|
-
info "Detected package manager: $pkg_mgr"
|
|
713
|
-
|
|
714
|
-
if is_monorepo "$wt_path"; then
|
|
715
|
-
info "Monorepo detected, installing at root..."
|
|
716
|
-
fi
|
|
717
|
-
|
|
718
|
-
info "Running $pkg_mgr install..."
|
|
719
|
-
cd "$wt_path"
|
|
720
|
-
case "$pkg_mgr" in
|
|
721
|
-
bun) bun install ;;
|
|
722
|
-
pnpm) pnpm install ;;
|
|
723
|
-
yarn) yarn install ;;
|
|
724
|
-
npm) npm install ;;
|
|
725
|
-
esac
|
|
726
|
-
success "Dependencies installed"
|
|
727
|
-
fi
|
|
728
|
-
|
|
729
|
-
# --- Step 8: Summary ---
|
|
730
|
-
echo ""
|
|
731
|
-
success "Worktree ready!"
|
|
732
|
-
echo ""
|
|
733
|
-
echo -e "${GREEN}cd ${wt_path}${NC}"
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
cmd_list() {
|
|
737
|
-
get_repo_info
|
|
738
|
-
|
|
739
|
-
info "Worktrees for: $REPO_NAME"
|
|
740
|
-
echo ""
|
|
741
|
-
|
|
742
|
-
local found=0
|
|
743
|
-
while IFS= read -r line; do
|
|
744
|
-
local wt_path=$(echo "$line" | awk '{print $1}')
|
|
745
|
-
local wt_branch=$(echo "$line" | awk '{print $3}' | sed 's/\[//;s/\]//')
|
|
746
|
-
|
|
747
|
-
if [[ "$wt_path" == "$REPO_ROOT" ]]; then
|
|
748
|
-
echo -e " ${BLUE}●${NC} $wt_path ${GREEN}(main)${NC}"
|
|
749
|
-
else
|
|
750
|
-
local branch_display=$(get_branch_from_worktree "$wt_path")
|
|
751
|
-
echo -e " ${YELLOW}○${NC} $wt_path ${YELLOW}($branch_display)${NC}"
|
|
752
|
-
fi
|
|
753
|
-
((found++))
|
|
754
|
-
done < <(git worktree list --porcelain | grep "^worktree " | sed 's/^worktree //')
|
|
755
|
-
|
|
756
|
-
if [[ "$found" -eq 0 ]]; then
|
|
757
|
-
warn "No worktrees found"
|
|
758
|
-
fi
|
|
759
|
-
echo ""
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
cmd_remove() {
|
|
763
|
-
local branch="${1:-}"
|
|
764
|
-
local delete_branch=false
|
|
765
|
-
|
|
766
|
-
if [[ "${2:-}" == "--delete-branch" ]] || [[ "${2:-}" == "-d" ]]; then
|
|
767
|
-
delete_branch=true
|
|
768
|
-
fi
|
|
769
|
-
|
|
770
|
-
if [[ -z "$branch" ]]; then
|
|
771
|
-
error "Usage: wt remove <branch-name> [--delete-branch|-d]"
|
|
772
|
-
echo " Removes the worktree for the given branch"
|
|
773
|
-
echo " --delete-branch, -d: Also delete the git branch"
|
|
774
|
-
exit 1
|
|
775
|
-
fi
|
|
776
|
-
|
|
777
|
-
get_repo_info
|
|
778
|
-
local wt_path=$(get_worktree_path "$branch")
|
|
779
|
-
|
|
780
|
-
if [[ ! -d "$wt_path" ]]; then
|
|
781
|
-
error "Worktree not found: $wt_path"
|
|
782
|
-
exit 1
|
|
783
|
-
fi
|
|
784
|
-
|
|
785
|
-
# Stop Docker containers if compose setup exists
|
|
786
|
-
if [[ -f "$wt_path/.env" ]]; then
|
|
787
|
-
local compose_file_val=""
|
|
788
|
-
while IFS= read -r line; do
|
|
789
|
-
[[ "$line" =~ ^COMPOSE_FILE=(.+) ]] && compose_file_val="${BASH_REMATCH[1]}"
|
|
790
|
-
done < "$wt_path/.env"
|
|
791
|
-
|
|
792
|
-
if [[ -n "$compose_file_val" ]] && command -v docker &>/dev/null; then
|
|
793
|
-
info "Stopping Docker containers..."
|
|
794
|
-
(cd "$wt_path" && docker compose down 2>/dev/null) || warn "Could not stop containers (may not be running)"
|
|
795
|
-
fi
|
|
796
|
-
fi
|
|
797
|
-
|
|
798
|
-
info "Removing worktree: $wt_path"
|
|
799
|
-
git worktree remove "$wt_path" --force
|
|
800
|
-
success "Worktree removed"
|
|
801
|
-
info "Note: databases are preserved — drop manually if no longer needed"
|
|
802
|
-
|
|
803
|
-
if [[ "$delete_branch" == true ]]; then
|
|
804
|
-
info "Deleting branch: $branch"
|
|
805
|
-
git branch -D "$branch" 2>/dev/null || warn "Branch not found or already deleted"
|
|
806
|
-
success "Branch deleted"
|
|
807
|
-
fi
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
cmd_open() {
|
|
811
|
-
local branch=""
|
|
812
|
-
local editor=""
|
|
813
|
-
|
|
814
|
-
while [[ $# -gt 0 ]]; do
|
|
815
|
-
case "$1" in
|
|
816
|
-
--cursor|-c)
|
|
817
|
-
editor="cursor"
|
|
818
|
-
shift
|
|
819
|
-
;;
|
|
820
|
-
--agy|-a)
|
|
821
|
-
editor="agy"
|
|
822
|
-
shift
|
|
823
|
-
;;
|
|
824
|
-
--code|-v)
|
|
825
|
-
editor="code"
|
|
826
|
-
shift
|
|
827
|
-
;;
|
|
828
|
-
-*)
|
|
829
|
-
error "Unknown option: $1"
|
|
830
|
-
exit 1
|
|
831
|
-
;;
|
|
832
|
-
*)
|
|
833
|
-
branch="$1"
|
|
834
|
-
shift
|
|
835
|
-
;;
|
|
836
|
-
esac
|
|
837
|
-
done
|
|
838
|
-
|
|
839
|
-
if [[ -z "$branch" ]]; then
|
|
840
|
-
error "Usage: wt open <branch-name> [--cursor|-c] [--agy|-a] [--code|-v]"
|
|
841
|
-
echo " Opens the worktree in specified editor (auto-detects if not specified)"
|
|
842
|
-
exit 1
|
|
843
|
-
fi
|
|
844
|
-
|
|
845
|
-
get_repo_info
|
|
846
|
-
local wt_path=$(get_worktree_path "$branch")
|
|
847
|
-
|
|
848
|
-
if [[ ! -d "$wt_path" ]]; then
|
|
849
|
-
error "Worktree not found: $wt_path"
|
|
850
|
-
echo " Run 'wt create $branch' first"
|
|
851
|
-
exit 1
|
|
852
|
-
fi
|
|
853
|
-
|
|
854
|
-
if [[ -z "$editor" && -n "${WT_EDITOR:-}" ]]; then
|
|
855
|
-
editor="$WT_EDITOR"
|
|
856
|
-
fi
|
|
857
|
-
|
|
858
|
-
if [[ -n "$editor" ]]; then
|
|
859
|
-
if ! command -v "$editor" &>/dev/null; then
|
|
860
|
-
error "$editor not found in PATH"
|
|
861
|
-
exit 1
|
|
862
|
-
fi
|
|
863
|
-
info "Opening in $editor: $wt_path"
|
|
864
|
-
"$editor" "$wt_path"
|
|
865
|
-
else
|
|
866
|
-
if command -v cursor &>/dev/null; then
|
|
867
|
-
info "Opening in Cursor: $wt_path"
|
|
868
|
-
cursor "$wt_path"
|
|
869
|
-
elif command -v agy &>/dev/null; then
|
|
870
|
-
info "Opening in Antigravity: $wt_path"
|
|
871
|
-
agy "$wt_path"
|
|
872
|
-
elif command -v code &>/dev/null; then
|
|
873
|
-
info "Opening in VS Code: $wt_path"
|
|
874
|
-
code "$wt_path"
|
|
875
|
-
else
|
|
876
|
-
error "No supported editor found (cursor, agy, code)"
|
|
877
|
-
exit 1
|
|
878
|
-
fi
|
|
879
|
-
fi
|
|
880
|
-
success "Opened!"
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
cmd_help() {
|
|
884
|
-
echo "wt - Git Worktree Manager"
|
|
885
|
-
echo ""
|
|
886
|
-
echo "Usage: wt <command> [args]"
|
|
887
|
-
echo ""
|
|
888
|
-
echo "Commands:"
|
|
889
|
-
echo " create, c <branch> Create worktree with isolation (env, docker, db)"
|
|
890
|
-
echo " list, ls List all worktrees for current repo"
|
|
891
|
-
echo " remove, rm <branch> Remove worktree (add -d to delete branch too)"
|
|
892
|
-
echo " open, o <branch> Open worktree (flag > WT_EDITOR > auto-detect)"
|
|
893
|
-
echo " Options: --cursor|-c, --agy|-a, --code|-v"
|
|
894
|
-
echo " help, -h, --help Show this help"
|
|
895
|
-
echo ""
|
|
896
|
-
echo "Environment:"
|
|
897
|
-
echo " WT_EDITOR Default editor (cursor, agy, code)"
|
|
898
|
-
echo ""
|
|
899
|
-
echo "Isolation (.worktreeinclude):"
|
|
900
|
-
echo " Auto-generated on first 'wt create' if missing."
|
|
901
|
-
echo " Controls which files to copy and how to isolate services."
|
|
902
|
-
echo ""
|
|
903
|
-
echo " Sections:"
|
|
904
|
-
echo " (top) Glob patterns for files to copy (e.g., .env*)"
|
|
905
|
-
echo " [rewrite] 'auto' to suffix DB_NAME, DATABASE_URL, etc."
|
|
906
|
-
echo " Use {{BRANCH}} for custom templates"
|
|
907
|
-
echo " [docker] 'auto' to generate port-offset override"
|
|
908
|
-
echo " 'file=path' for custom compose file"
|
|
909
|
-
echo ""
|
|
910
|
-
echo "Naming convention:"
|
|
911
|
-
echo " ~/code/project/ → main repo"
|
|
912
|
-
echo " ~/code/project--feat-auth/ → worktree for feat-auth branch"
|
|
913
|
-
echo ""
|
|
914
|
-
echo "Examples:"
|
|
915
|
-
echo " wt create feature/authentication"
|
|
916
|
-
echo " wt c feat-payments"
|
|
917
|
-
echo " wt ls"
|
|
918
|
-
echo " wt rm feature/authentication"
|
|
919
|
-
echo " wt rm feat-payments -d"
|
|
920
|
-
echo " wt o feat-payments"
|
|
921
|
-
echo " wt o feat-payments --agy"
|
|
922
|
-
echo " WT_EDITOR=agy wt o feat-payments"
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
# =============================================================================
|
|
926
|
-
# Main
|
|
927
|
-
# =============================================================================
|
|
928
|
-
|
|
929
|
-
main() {
|
|
930
|
-
local cmd="${1:-help}"
|
|
931
|
-
shift || true
|
|
932
|
-
|
|
933
|
-
case "$cmd" in
|
|
934
|
-
create|c)
|
|
935
|
-
cmd_create "$@"
|
|
936
|
-
;;
|
|
937
|
-
list|ls)
|
|
938
|
-
cmd_list "$@"
|
|
939
|
-
;;
|
|
940
|
-
remove|rm)
|
|
941
|
-
cmd_remove "$@"
|
|
942
|
-
;;
|
|
943
|
-
open|o)
|
|
944
|
-
cmd_open "$@"
|
|
945
|
-
;;
|
|
946
|
-
help|-h|--help)
|
|
947
|
-
cmd_help
|
|
948
|
-
;;
|
|
949
|
-
*)
|
|
950
|
-
error "Unknown command: $cmd"
|
|
951
|
-
cmd_help
|
|
952
|
-
exit 1
|
|
953
|
-
;;
|
|
954
|
-
esac
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
main "$@"
|