|
17 | 17 | ### Laravel-centric startup: optionally inject Laravel intro only |
18 | 18 |
|
19 | 19 | ############################################# |
20 | | -# Detect Laravel + Laravel Sail environment # |
| 20 | +# Detect Laravel apps (monorepo-aware) # |
21 | 21 | ############################################# |
22 | 22 |
|
23 | | -## Detect Laravel projects strictly and bail out when not detected |
24 | | -# Consider it Laravel only when the repo has the canonical `artisan` entrypoint |
25 | | -# or a composer.json that depends on `laravel/framework`. |
26 | | -is_laravel=false |
27 | | -if [ -f "artisan" ] || ( [ -f "composer.json" ] && grep -q '"laravel/framework"' composer.json ); then |
28 | | - is_laravel=true |
29 | | -fi |
| 23 | +# Exclusions to keep scanning fast |
| 24 | +EXCLUDES=( |
| 25 | + -path '*/.git*' -o -path '*/node_modules*' -o -path '*/vendor*' -o -path '*/storage*' -o -path '*/.idea*' -o -path '*/.vscode*' |
| 26 | +) |
30 | 27 |
|
31 | | -# If not a Laravel project, exit quietly so the plugin does NOT activate |
32 | | -if [ "$is_laravel" != true ]; then |
33 | | - exit 0 |
34 | | -fi |
| 28 | +find_laravel_apps() { |
| 29 | + # Find artisan files anywhere in the repo (monorepo support) |
| 30 | + if find . -maxdepth 0 >/dev/null 2>&1; then |
| 31 | + # shellcheck disable=SC2068 |
| 32 | + find . -type f -name artisan \( ${EXCLUDES[@]} \) -prune -o -type f -name artisan -print 2>/dev/null | sed 's#^\./##' |
| 33 | + else |
| 34 | + # Fallback without -maxdepth (may be slower) |
| 35 | + # shellcheck disable=SC2068 |
| 36 | + find . \( ${EXCLUDES[@]} \) -prune -o -type f -name artisan -print 2>/dev/null | sed 's#^\./##' |
| 37 | + fi |
| 38 | +} |
35 | 39 |
|
36 | | -## Detect Sail availability by executable presence, not composer.json |
37 | | -# Treat Sail as available when either vendor/bin/sail exists/executable, or a top-level |
38 | | -# ./sail helper script is present. We intentionally avoid parsing composer.json. |
39 | | -sail_available=false |
40 | | -if [ -x ./vendor/bin/sail ] || [ -f ./sail ]; then |
41 | | - sail_available=true |
42 | | -fi |
| 40 | +get_laravel_version_for_dir() { |
| 41 | + local dir="$1"; local version=""; local constraint=""; |
| 42 | + if [ -f "$dir/composer.lock" ]; then |
| 43 | + if command -v jq >/dev/null 2>&1; then |
| 44 | + version=$(jq -r '.packages[]? | select(.name=="laravel/framework") | .version' "$dir/composer.lock" 2>/dev/null | head -n1 || true) |
| 45 | + fi |
| 46 | + if [ -z "$version" ]; then |
| 47 | + version=$(awk '/"name"\s*:\s*"laravel\/framework"/{f=1} f && /"version"\s*:/ {gsub(/.*"version"\s*:\s*"/,"",$0); gsub(/".*/ ,"", $0); print; exit}' "$dir/composer.lock" 2>/dev/null || true) |
| 48 | + fi |
| 49 | + fi |
| 50 | + if [ -z "$version" ] && [ -f "$dir/composer.json" ]; then |
| 51 | + if command -v jq >/dev/null 2>&1; then |
| 52 | + constraint=$(jq -r '.require["laravel/framework"] // empty' "$dir/composer.json" 2>/dev/null || true) |
| 53 | + fi |
| 54 | + if [ -z "$constraint" ]; then |
| 55 | + constraint=$(awk '/"laravel\/framework"\s*:\s*"/{gsub(/.*"laravel\/framework"\s*:\s*"/,"",$0); gsub(/".*/ ,"", $0); print; exit}' "$dir/composer.json" 2>/dev/null || true) |
| 56 | + fi |
| 57 | + fi |
| 58 | + if [ -n "$version" ]; then |
| 59 | + echo "$version" |
| 60 | + elif [ -n "$constraint" ]; then |
| 61 | + echo "$constraint" |
| 62 | + else |
| 63 | + echo "unknown" |
| 64 | + fi |
| 65 | +} |
| 66 | + |
| 67 | +has_sail_for_dir() { |
| 68 | + local dir="$1"; |
| 69 | + if [ -x "$dir/vendor/bin/sail" ] || [ -f "$dir/sail" ]; then |
| 70 | + echo "yes" |
| 71 | + else |
| 72 | + echo "no" |
| 73 | + fi |
| 74 | +} |
43 | 75 |
|
44 | | -# Detect if Sail (docker compose) containers are running for this project |
45 | | -containers_running=false |
46 | | -compose_cmd="" |
47 | | -if command -v docker >/dev/null 2>&1; then |
48 | | - # Prefer `docker compose` plugin |
49 | | - if docker compose version >/dev/null 2>&1; then |
| 76 | +containers_running_for_dir() { |
| 77 | + local dir="$1"; local compose_cmd=""; local running="no"; |
| 78 | + if [ "${SUPERPOWERS_TEST_SAIL_RUNNING:-}" = "true" ]; then |
| 79 | + echo "yes"; return 0 |
| 80 | + elif [ "${SUPERPOWERS_TEST_SAIL_RUNNING:-}" = "false" ]; then |
| 81 | + echo "no"; return 0 |
| 82 | + fi |
| 83 | + if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then |
50 | 84 | compose_cmd="docker compose" |
| 85 | + elif command -v docker-compose >/dev/null 2>&1; then |
| 86 | + compose_cmd="docker-compose" |
51 | 87 | fi |
52 | | -fi |
53 | | -if [ -z "$compose_cmd" ] && command -v docker-compose >/dev/null 2>&1; then |
54 | | - compose_cmd="docker-compose" |
| 88 | + if [ -n "$compose_cmd" ]; then |
| 89 | + ( cd "$dir" && $compose_cmd ps -q >/dev/null 2>&1 && [ -n "$($compose_cmd ps -q 2>/dev/null)" ] ) && running="yes" || true |
| 90 | + fi |
| 91 | + echo "$running" |
| 92 | +} |
| 93 | + |
| 94 | +# Build app list |
| 95 | +declare -a app_dirs |
| 96 | +app_dirs=() |
| 97 | +while IFS= read -r f; do |
| 98 | + [ -z "$f" ] && continue |
| 99 | + d=$(dirname "$f") |
| 100 | + # Deduplicate |
| 101 | + if [ ${#app_dirs[@]} -gt 0 ] && printf '%s\n' "${app_dirs[@]}" | grep -Fxq "$d"; then continue; fi |
| 102 | + app_dirs+=("$d") |
| 103 | +done < <(find_laravel_apps) |
| 104 | + |
| 105 | +# If no Laravel apps anywhere, exit quietly so the plugin does NOT activate |
| 106 | +if [ ${#app_dirs[@]} -eq 0 ]; then |
| 107 | + exit 0 |
55 | 108 | fi |
56 | 109 |
|
57 | | -if [ -n "$compose_cmd" ]; then |
58 | | - # Run quietly; if any service container exists and is running, consider Sail "up" |
59 | | - if $compose_cmd ps -q >/dev/null 2>&1; then |
60 | | - if [ -n "$($compose_cmd ps -q 2>/dev/null)" ]; then |
61 | | - containers_running=true |
62 | | - fi |
| 110 | +# Identify active app based on current working directory (nearest ancestor with artisan) |
| 111 | +active_dir="" |
| 112 | +search_dir="$PWD" |
| 113 | +while [ "$search_dir" != "/" ]; do |
| 114 | + if [ -f "$search_dir/artisan" ]; then |
| 115 | + active_dir="$search_dir" |
| 116 | + break |
63 | 117 | fi |
64 | | -fi |
| 118 | + search_dir="$(cd "$search_dir/.." && pwd)" |
| 119 | +done |
65 | 120 |
|
66 | | -# Test override to simulate container status in CI |
67 | | -if [ "${SUPERPOWERS_TEST_SAIL_RUNNING:-}" = "true" ]; then |
68 | | - containers_running=true |
69 | | -elif [ "${SUPERPOWERS_TEST_SAIL_RUNNING:-}" = "false" ]; then |
70 | | - containers_running=false |
| 121 | +# Default to the only app if just one exists |
| 122 | +if [ -z "$active_dir" ] && [ ${#app_dirs[@]} -eq 1 ]; then |
| 123 | + active_dir="$(cd "${app_dirs[0]}" && pwd)" |
71 | 124 | fi |
72 | 125 |
|
73 | 126 | # Read Laravel intro skill |
|
80 | 133 | laravel_intro_escaped=$(echo "$laravel_intro_content" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}') |
81 | 134 | warning_escaped=$(echo "$warning_message" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}') |
82 | 135 |
|
83 | | -# Build Sail guidance based on detection |
84 | | -sail_guidance="" |
85 | | -if [ "$sail_available" = true ]; then |
86 | | - sail_guidance="Laravel Sail detected (vendor/bin/sail or ./sail present). Prefer Sail commands inside containers to avoid host/env drift.\n\nKey mappings (Sail | Non‑Sail):\n- sail artisan … | php artisan …\n- sail composer … | composer …\n- sail php … | php …\n- sail pnpm … | pnpm … (or npm/yarn/bun)\n- sail mysql/psql/redis … | mysql/psql/redis-cli …\n\nPortable alias:\n alias sail='sh $([ -f sail ] && echo sail || echo vendor/bin/sail)'\n" |
| 136 | +############################################# |
| 137 | +# Build output (apps summary + Sail guidance) # |
| 138 | +############################################# |
87 | 139 |
|
88 | | - if [ "$containers_running" = false ]; then |
89 | | - sail_guidance+="\nInteractive safety: Sail is present but containers are not running. Before executing any host commands (php, composer, mysql, node, pnpm, npm, yarn), ask the user: \"Start Sail containers now with: 'sail up -d'? Or proceed using host tools?\" Do not run host commands unless the user explicitly opts to proceed without Sail.\nTip: Start containers: 'sail up -d' then verify: 'sail ps'.\n" |
| 140 | +# Build listing lines with version and Sail per app |
| 141 | +declare -a app_lines |
| 142 | +for d in "${app_dirs[@]}"; do |
| 143 | + rel="${d#./}" |
| 144 | + [ "$rel" = "." ] && rel="." |
| 145 | + ver=$(get_laravel_version_for_dir "$d") |
| 146 | + sail=$(has_sail_for_dir "$d") |
| 147 | + running=$(containers_running_for_dir "$d") |
| 148 | + if [ "$sail" = "yes" ]; then |
| 149 | + line="- ${rel} (Laravel ${ver}; Sail: yes, containers: ${running})" |
90 | 150 | else |
91 | | - sail_guidance+="\nSail appears to be running (docker compose ps shows active containers). Use Sail commands (artisan/composer/node/db) and avoid host binaries to keep environments consistent.\n" |
| 151 | + line="- ${rel} (Laravel ${ver}; Sail: no)" |
92 | 152 | fi |
| 153 | + app_lines+=("$line") |
| 154 | +done |
| 155 | + |
| 156 | +apps_summary=$(printf "%s\n" "${app_lines[@]}") |
| 157 | +apps_summary_escaped=$(echo "$apps_summary" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}') |
| 158 | + |
| 159 | +# Active app line |
| 160 | +active_line="" |
| 161 | +if [ -n "$active_dir" ]; then |
| 162 | + # Make relative to repo root |
| 163 | + if [ "$active_dir" = "$PWD" ]; then |
| 164 | + rel_active="." |
| 165 | + else |
| 166 | + rel_active="${active_dir#${PWD}/}" |
| 167 | + [ -z "$rel_active" ] && rel_active="." |
| 168 | + fi |
| 169 | + ver_active=$(get_laravel_version_for_dir "$active_dir") |
| 170 | + active_line="Active Laravel app: ${rel_active} (Laravel ${ver_active})\n" |
| 171 | +else |
| 172 | + active_line="No active Laravel app (not currently inside any app directory).\nChange working directory to one of the listed apps to focus this session.\n" |
93 | 173 | fi |
| 174 | +active_line_escaped=$(echo "$active_line" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}') |
94 | 175 |
|
95 | | -# Escape Sail guidance |
| 176 | +# Sail guidance for the active app (if any) |
| 177 | +sail_guidance="" |
| 178 | +if [ -n "$active_dir" ] && [ "$(has_sail_for_dir "$active_dir")" = "yes" ]; then |
| 179 | + running_this=$(containers_running_for_dir "$active_dir") |
| 180 | + sail_guidance="Laravel Sail detected for active app. Prefer Sail commands inside containers to avoid host/env drift.\n\nKey mappings (Sail | Non‑Sail):\n- sail artisan … | php artisan …\n- sail composer … | composer …\n- sail php … | php …\n- sail pnpm … | pnpm … (or npm/yarn/bun)\n- sail mysql/psql/redis … | mysql/psql/redis-cli …\n\nPortable alias:\n alias sail='sh $([ -f sail ] && echo sail || echo vendor/bin/sail)'\n" |
| 181 | + if [ "$running_this" = "no" ]; then |
| 182 | + sail_guidance+="\nInteractive safety: Sail is present but containers are not running. Before executing any host commands (php, composer, mysql, node, pnpm, npm, yarn), ask the user: \"Start Sail containers now with: 'sail up -d'? Or proceed using host tools?\" Do not run host commands unless the user explicitly opts to proceed without Sail.\nTip: Start containers: 'sail up -d' then verify: 'sail ps'.\n" |
| 183 | + else |
| 184 | + sail_guidance+="\nSail appears to be running for the active app. Use Sail commands (artisan/composer/node/db) and avoid host binaries to keep environments consistent.\n" |
| 185 | + fi |
| 186 | +fi |
96 | 187 | sail_guidance_escaped=$(echo "$sail_guidance" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}') |
97 | 188 |
|
98 | 189 | # Output context injection as JSON |
99 | 190 | cat <<EOF |
100 | 191 | { |
101 | 192 | "hookSpecificOutput": { |
102 | 193 | "hookEventName": "SessionStart", |
103 | | - "additionalContext": "<EXTREMELY_IMPORTANT>\nThis repository appears to be a Laravel project. Read the following onboarding first, then use the 'Skill' tool to run any Laravel skills you need.\n\n${laravel_intro_escaped}\n\n${sail_guidance_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>" |
| 194 | + "additionalContext": "<EXTREMELY_IMPORTANT>\nLaravel projects detected in this repository. Read the onboarding below, then use the 'Skill' tool.\n\n${active_line_escaped}\nDetected apps:\n${apps_summary_escaped}\n\n${laravel_intro_escaped}\n\n${sail_guidance_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>" |
104 | 195 | } |
105 | 196 | } |
106 | 197 | EOF |
|
0 commit comments