agentic-loop 3.17.0 → 3.17.2
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/package.json +1 -1
- package/ralph/code-check.sh +21 -2
- package/ralph/loop.sh +101 -12
- package/ralph/prd-check.sh +57 -4
- package/ralph/test.sh +6 -2
- package/ralph/utils.sh +1 -0
- package/ralph/verify/tests.sh +30 -30
package/package.json
CHANGED
package/ralph/code-check.sh
CHANGED
|
@@ -100,6 +100,7 @@ run_verification() {
|
|
|
100
100
|
export RALPH_STORY_TYPE="$story_type"
|
|
101
101
|
|
|
102
102
|
local failed=0
|
|
103
|
+
local failed_step=""
|
|
103
104
|
|
|
104
105
|
# ========================================
|
|
105
106
|
# STEP 1: Run lint checks
|
|
@@ -107,6 +108,7 @@ run_verification() {
|
|
|
107
108
|
echo " [1/5] Running lint checks..."
|
|
108
109
|
if ! run_configured_checks "$story_type"; then
|
|
109
110
|
failed=1
|
|
111
|
+
failed_step="lint"
|
|
110
112
|
fi
|
|
111
113
|
|
|
112
114
|
# ========================================
|
|
@@ -118,8 +120,10 @@ run_verification() {
|
|
|
118
120
|
# First check that test files exist for new code
|
|
119
121
|
if ! verify_test_files_exist; then
|
|
120
122
|
failed=1
|
|
123
|
+
failed_step="test files missing"
|
|
121
124
|
elif ! run_unit_tests; then
|
|
122
125
|
failed=1
|
|
126
|
+
failed_step="unit tests"
|
|
123
127
|
fi
|
|
124
128
|
fi
|
|
125
129
|
|
|
@@ -131,6 +135,7 @@ run_verification() {
|
|
|
131
135
|
echo " [3/5] Running PRD test steps..."
|
|
132
136
|
if ! verify_prd_criteria "$story"; then
|
|
133
137
|
failed=1
|
|
138
|
+
failed_step="PRD test steps"
|
|
134
139
|
fi
|
|
135
140
|
fi
|
|
136
141
|
|
|
@@ -140,6 +145,7 @@ run_verification() {
|
|
|
140
145
|
if [[ $failed -eq 0 ]]; then
|
|
141
146
|
if ! run_api_smoke_test "$story"; then
|
|
142
147
|
failed=1
|
|
148
|
+
failed_step="API smoke test"
|
|
143
149
|
fi
|
|
144
150
|
fi
|
|
145
151
|
|
|
@@ -149,6 +155,7 @@ run_verification() {
|
|
|
149
155
|
if [[ $failed -eq 0 ]]; then
|
|
150
156
|
if ! run_frontend_smoke_test "$story"; then
|
|
151
157
|
failed=1
|
|
158
|
+
failed_step="frontend smoke test"
|
|
152
159
|
fi
|
|
153
160
|
fi
|
|
154
161
|
|
|
@@ -160,7 +167,7 @@ run_verification() {
|
|
|
160
167
|
print_success "=== All verification passed ==="
|
|
161
168
|
return 0
|
|
162
169
|
else
|
|
163
|
-
print_error "=== Verification failed ==="
|
|
170
|
+
print_error "=== Verification failed at: $failed_step ==="
|
|
164
171
|
save_failure_context "$story"
|
|
165
172
|
return 1
|
|
166
173
|
fi
|
|
@@ -194,8 +201,20 @@ save_failure_context() {
|
|
|
194
201
|
echo ""
|
|
195
202
|
echo "=== Attempt $attempt failed for $story ==="
|
|
196
203
|
echo ""
|
|
204
|
+
# Include migration failure if present (verification may not have run)
|
|
205
|
+
if [[ -f "$RALPH_DIR/last_migration_failure.log" ]]; then
|
|
206
|
+
echo "--- Migration Error ---"
|
|
207
|
+
tail -30 "$RALPH_DIR/last_migration_failure.log"
|
|
208
|
+
echo ""
|
|
209
|
+
fi
|
|
210
|
+
# Include pre-commit failure if present
|
|
211
|
+
if [[ -f "$RALPH_DIR/last_precommit_failure.log" ]]; then
|
|
212
|
+
echo "--- Pre-commit Error ---"
|
|
213
|
+
tail -30 "$RALPH_DIR/last_precommit_failure.log"
|
|
214
|
+
echo ""
|
|
215
|
+
fi
|
|
216
|
+
# Include verification output (lint, tests, API, etc.)
|
|
197
217
|
if [[ -f "$RALPH_DIR/last_verification.log" ]]; then
|
|
198
|
-
# Shorter excerpt per attempt since we're accumulating
|
|
199
218
|
tail -50 "$RALPH_DIR/last_verification.log"
|
|
200
219
|
fi
|
|
201
220
|
echo ""
|
package/ralph/loop.sh
CHANGED
|
@@ -97,9 +97,74 @@ preflight_checks() {
|
|
|
97
97
|
echo "Aborted. Fix the issues and try again."
|
|
98
98
|
exit 1
|
|
99
99
|
fi
|
|
100
|
+
return 1 # Had warnings — don't cache this result
|
|
100
101
|
fi
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
# ============================================================================
|
|
105
|
+
# PREFLIGHT / PRD CACHE
|
|
106
|
+
# ============================================================================
|
|
107
|
+
# Caches preflight and PRD validation results so restarts within 10 minutes
|
|
108
|
+
# skip the slow connectivity checks and Claude auto-fix.
|
|
109
|
+
# Cache is invalidated by TTL expiry or config/PRD file changes (by hash).
|
|
110
|
+
|
|
111
|
+
_file_hash() {
|
|
112
|
+
[[ ! -f "$1" ]] && echo "no_file" && return
|
|
113
|
+
if command -v md5sum &>/dev/null; then
|
|
114
|
+
md5sum "$1" 2>/dev/null | cut -d' ' -f1
|
|
115
|
+
else
|
|
116
|
+
md5 -q "$1" 2>/dev/null
|
|
117
|
+
fi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_is_preflight_cached() {
|
|
121
|
+
local cache_file="$RALPH_DIR/.preflight_cache"
|
|
122
|
+
[[ ! -f "$cache_file" ]] && return 1
|
|
123
|
+
|
|
124
|
+
local cached_time cached_hash
|
|
125
|
+
read -r cached_time cached_hash < "$cache_file"
|
|
126
|
+
|
|
127
|
+
local now
|
|
128
|
+
now=$(date +%s)
|
|
129
|
+
[[ $(( now - cached_time )) -gt $PREFLIGHT_CACHE_TTL_SECONDS ]] && return 1
|
|
130
|
+
|
|
131
|
+
local config_hash
|
|
132
|
+
config_hash=$(_file_hash "$RALPH_DIR/config.json")
|
|
133
|
+
[[ "$cached_hash" != "$config_hash" ]] && return 1
|
|
134
|
+
|
|
135
|
+
return 0
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_write_preflight_cache() {
|
|
139
|
+
local config_hash
|
|
140
|
+
config_hash=$(_file_hash "$RALPH_DIR/config.json")
|
|
141
|
+
echo "$(date +%s) $config_hash" > "$RALPH_DIR/.preflight_cache"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_is_prd_cached() {
|
|
145
|
+
local cache_file="$RALPH_DIR/.prd_validated"
|
|
146
|
+
[[ ! -f "$cache_file" ]] && return 1
|
|
147
|
+
|
|
148
|
+
local cached_time cached_hash
|
|
149
|
+
read -r cached_time cached_hash < "$cache_file"
|
|
150
|
+
|
|
151
|
+
local now
|
|
152
|
+
now=$(date +%s)
|
|
153
|
+
[[ $(( now - cached_time )) -gt $PREFLIGHT_CACHE_TTL_SECONDS ]] && return 1
|
|
154
|
+
|
|
155
|
+
local prd_hash
|
|
156
|
+
prd_hash=$(_file_hash "$RALPH_DIR/prd.json")
|
|
157
|
+
[[ "$cached_hash" != "$prd_hash" ]] && return 1
|
|
158
|
+
|
|
159
|
+
return 0
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_write_prd_cache() {
|
|
163
|
+
local prd_hash
|
|
164
|
+
prd_hash=$(_file_hash "$RALPH_DIR/prd.json")
|
|
165
|
+
echo "$(date +%s) $prd_hash" > "$RALPH_DIR/.prd_validated"
|
|
166
|
+
}
|
|
167
|
+
|
|
103
168
|
# Check if failure context is trivial (lint/format-only retries)
|
|
104
169
|
# Returns 0 (trivial) if ALL error lines match trivial patterns
|
|
105
170
|
_is_trivial_failure() {
|
|
@@ -341,7 +406,15 @@ run_loop() {
|
|
|
341
406
|
check_dependencies
|
|
342
407
|
|
|
343
408
|
# Pre-loop checks to catch issues before wasting iterations
|
|
344
|
-
|
|
409
|
+
if [[ "$fast_mode" == "true" ]]; then
|
|
410
|
+
print_info "Fast mode: skipping connectivity checks"
|
|
411
|
+
elif _is_preflight_cached; then
|
|
412
|
+
print_info "Pre-loop checks passed recently, skipping"
|
|
413
|
+
else
|
|
414
|
+
if preflight_checks; then
|
|
415
|
+
_write_preflight_cache
|
|
416
|
+
fi
|
|
417
|
+
fi
|
|
345
418
|
|
|
346
419
|
if [[ ! -f "$RALPH_DIR/prd.json" ]]; then
|
|
347
420
|
# Check for misplaced PRD in subdirectories
|
|
@@ -383,8 +456,17 @@ run_loop() {
|
|
|
383
456
|
fi
|
|
384
457
|
|
|
385
458
|
# Validate PRD structure
|
|
386
|
-
if
|
|
387
|
-
|
|
459
|
+
if [[ "$fast_mode" == "true" ]]; then
|
|
460
|
+
print_info "Fast mode: structural PRD check only"
|
|
461
|
+
validate_prd "$RALPH_DIR/prd.json" "true" || return 1
|
|
462
|
+
elif _is_prd_cached; then
|
|
463
|
+
print_info "PRD validated recently, structural check only"
|
|
464
|
+
validate_prd "$RALPH_DIR/prd.json" "true" || return 1
|
|
465
|
+
else
|
|
466
|
+
if ! validate_prd "$RALPH_DIR/prd.json"; then
|
|
467
|
+
return 1
|
|
468
|
+
fi
|
|
469
|
+
_write_prd_cache
|
|
388
470
|
fi
|
|
389
471
|
|
|
390
472
|
local iteration=0
|
|
@@ -481,14 +563,15 @@ run_loop() {
|
|
|
481
563
|
if [[ $consecutive_failures -gt $max_story_retries ]]; then
|
|
482
564
|
print_error "Story $story has failed $consecutive_failures times - stopping loop"
|
|
483
565
|
echo ""
|
|
484
|
-
echo " This usually means:"
|
|
485
|
-
echo " - Test steps are too vague or ambiguous"
|
|
486
|
-
echo " - Missing prerequisites (DB setup, env vars, etc.)"
|
|
487
|
-
echo " - Story scope is too large - consider breaking it up"
|
|
488
|
-
echo ""
|
|
489
|
-
echo " Failure context saved to: $RALPH_DIR/failures/$story.txt"
|
|
490
566
|
mkdir -p "$RALPH_DIR/failures"
|
|
491
567
|
cp "$RALPH_DIR/last_failure.txt" "$RALPH_DIR/failures/$story.txt" 2>/dev/null || true
|
|
568
|
+
# Show the actual last error instead of generic guesses
|
|
569
|
+
if [[ -f "$RALPH_DIR/last_failure.txt" ]]; then
|
|
570
|
+
echo " Last failure:"
|
|
571
|
+
tail -20 "$RALPH_DIR/last_failure.txt" | sed 's/^/ /'
|
|
572
|
+
fi
|
|
573
|
+
echo ""
|
|
574
|
+
echo " Full failure context saved to: $RALPH_DIR/failures/$story.txt"
|
|
492
575
|
local passed failed
|
|
493
576
|
passed=$(jq '[.stories[] | select(.passes==true)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
|
|
494
577
|
failed=$(jq '[.stories[] | select(.passes==false)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "0")
|
|
@@ -632,18 +715,22 @@ run_loop() {
|
|
|
632
715
|
break
|
|
633
716
|
done
|
|
634
717
|
|
|
635
|
-
rm -f "$claude_output_log"
|
|
636
|
-
|
|
637
718
|
if [[ $crash_attempt -ge $max_crash_retries ]]; then
|
|
638
719
|
echo ""
|
|
639
720
|
print_warning "Claude API unavailable after $max_crash_retries attempts"
|
|
721
|
+
if [[ -f "$claude_output_log" ]]; then
|
|
722
|
+
echo " Last error:"
|
|
723
|
+
tail -5 "$claude_output_log" | sed 's/^/ /'
|
|
724
|
+
fi
|
|
640
725
|
print_info "Waiting 60s before retrying... (Ctrl+C to stop, then 'npx agentic-loop run' to restart)"
|
|
641
726
|
log_progress "$story" "CLI_CRASH" "API unavailable, waiting 60s before next iteration"
|
|
642
|
-
rm -f "$prompt_file"
|
|
727
|
+
rm -f "$prompt_file" "$claude_output_log"
|
|
643
728
|
sleep 60 # Longer cooldown before retrying
|
|
644
729
|
continue # Continue main loop instead of stopping
|
|
645
730
|
fi
|
|
646
731
|
|
|
732
|
+
rm -f "$claude_output_log"
|
|
733
|
+
|
|
647
734
|
if [[ $claude_exit_code -ne 0 ]]; then
|
|
648
735
|
((consecutive_timeouts++))
|
|
649
736
|
print_warning "Claude session ended (timeout or error) - timeout $consecutive_timeouts/$max_timeouts"
|
|
@@ -709,6 +796,8 @@ run_loop() {
|
|
|
709
796
|
|
|
710
797
|
# Clear failure context on success
|
|
711
798
|
rm -f "$RALPH_DIR/last_failure.txt"
|
|
799
|
+
rm -f "$RALPH_DIR"/last_*_failure.log
|
|
800
|
+
rm -f "$RALPH_DIR"/last_*_check.log
|
|
712
801
|
rm -f "$RALPH_DIR/last_verification.log"
|
|
713
802
|
|
|
714
803
|
# Get story title for commit message and completion display
|
package/ralph/prd-check.sh
CHANGED
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
# Returns 0 if valid (possibly after auto-fix), 1 if unrecoverable error
|
|
103
103
|
validate_prd() {
|
|
104
104
|
local prd_file="$1"
|
|
105
|
+
local dry_run="${2:-false}"
|
|
105
106
|
|
|
106
107
|
# Check file exists
|
|
107
108
|
if [[ ! -f "$prd_file" ]]; then
|
|
@@ -219,15 +220,17 @@ validate_prd() {
|
|
|
219
220
|
echo ""
|
|
220
221
|
fi
|
|
221
222
|
|
|
222
|
-
# Validate API smoke test configuration
|
|
223
|
-
|
|
223
|
+
# Validate API smoke test configuration (skip in fast/cached mode)
|
|
224
|
+
if [[ "$dry_run" != "true" ]]; then
|
|
225
|
+
_validate_api_config "$config"
|
|
226
|
+
fi
|
|
224
227
|
|
|
225
228
|
# Replace hardcoded paths with config placeholders
|
|
226
229
|
fix_hardcoded_paths "$prd_file" "$config"
|
|
227
230
|
|
|
228
231
|
# Validate and fix individual stories
|
|
229
|
-
#
|
|
230
|
-
_validate_and_fix_stories "$prd_file" "$
|
|
232
|
+
# dry_run flag — when "true", skip auto-fix
|
|
233
|
+
_validate_and_fix_stories "$prd_file" "$dry_run" || return 1
|
|
231
234
|
|
|
232
235
|
return 0
|
|
233
236
|
}
|
|
@@ -323,6 +326,7 @@ _validate_and_fix_stories() {
|
|
|
323
326
|
local cnt_frontend_tsc=0 cnt_frontend_url=0 cnt_frontend_context=0 cnt_frontend_mcp=0
|
|
324
327
|
local cnt_auth_security=0 cnt_list_pagination=0 cnt_prose_steps=0
|
|
325
328
|
local cnt_migration_prereq=0 cnt_naming_convention=0 cnt_bare_pytest=0
|
|
329
|
+
local cnt_server_only=0
|
|
326
330
|
local cnt_custom=0
|
|
327
331
|
|
|
328
332
|
echo " Checking test coverage..."
|
|
@@ -471,6 +475,32 @@ _validate_and_fix_stories() {
|
|
|
471
475
|
fi
|
|
472
476
|
fi
|
|
473
477
|
|
|
478
|
+
# Check 9: Stories where ALL testSteps depend on a live server
|
|
479
|
+
# If every testStep is a curl/wget/httpie command and none are offline
|
|
480
|
+
# (npm test, pytest, tsc, playwright, cargo test, go test, etc.),
|
|
481
|
+
# the story will always fail without a running server.
|
|
482
|
+
if [[ -n "$test_steps" ]]; then
|
|
483
|
+
local has_offline_step=false
|
|
484
|
+
local has_server_step=false
|
|
485
|
+
local step_list
|
|
486
|
+
step_list=$(jq -r --arg id "$story_id" \
|
|
487
|
+
'.stories[] | select(.id==$id) | .testSteps[]?' "$prd_file")
|
|
488
|
+
|
|
489
|
+
while IFS= read -r single_step; do
|
|
490
|
+
[[ -z "$single_step" ]] && continue
|
|
491
|
+
if echo "$single_step" | grep -qE "^(curl |wget |http )"; then
|
|
492
|
+
has_server_step=true
|
|
493
|
+
else
|
|
494
|
+
has_offline_step=true
|
|
495
|
+
fi
|
|
496
|
+
done <<< "$step_list"
|
|
497
|
+
|
|
498
|
+
if [[ "$has_server_step" == "true" && "$has_offline_step" == "false" ]]; then
|
|
499
|
+
story_issues+="all testSteps need a live server (add offline test: npm test, tsc --noEmit, pytest), "
|
|
500
|
+
cnt_server_only=$((cnt_server_only + 1))
|
|
501
|
+
fi
|
|
502
|
+
fi
|
|
503
|
+
|
|
474
504
|
# Snapshot built-in issues before custom checks append
|
|
475
505
|
local builtin_story_issues="$story_issues"
|
|
476
506
|
|
|
@@ -517,6 +547,7 @@ _validate_and_fix_stories() {
|
|
|
517
547
|
[[ $cnt_migration_prereq -gt 0 ]] && echo " ${cnt_migration_prereq}x migration: add prerequisites (DB reset)"
|
|
518
548
|
[[ $cnt_naming_convention -gt 0 ]] && echo " ${cnt_naming_convention}x API consumer: add camelCase transformation note"
|
|
519
549
|
[[ $cnt_bare_pytest -gt 0 ]] && echo " ${cnt_bare_pytest}x use 'uv run pytest' not bare 'pytest'"
|
|
550
|
+
[[ $cnt_server_only -gt 0 ]] && echo " ${cnt_server_only}x all testSteps need live server (add offline fallback)"
|
|
520
551
|
[[ $cnt_custom -gt 0 ]] && echo " ${cnt_custom} stories with custom check issues"
|
|
521
552
|
|
|
522
553
|
# Skip auto-fix in dry-run mode
|
|
@@ -630,6 +661,10 @@ RULES:
|
|
|
630
661
|
Example: \"notes\": \"Transform API responses from snake_case to camelCase. Create typed interfaces with camelCase properties and map: const user = { userName: data.user_name }\"
|
|
631
662
|
10. Each story should include its own techStack and constraints fields. Do NOT add these at the PRD root level.
|
|
632
663
|
Move any root-level techStack, globalConstraints, originalContext, testing, architecture, or testUsers into the relevant stories.
|
|
664
|
+
11. Stories where ALL testSteps are curl commands MUST also include at least one offline test step
|
|
665
|
+
that can verify code correctness without a running server.
|
|
666
|
+
Examples: \"npm test\", \"npx tsc --noEmit\", \"pytest tests/unit/\", \"go test ./...\"
|
|
667
|
+
This prevents wasted retries when the server isn't running.
|
|
633
668
|
|
|
634
669
|
CURRENT PRD:
|
|
635
670
|
$(cat "$prd_file")
|
|
@@ -788,6 +823,24 @@ validate_stories_quick() {
|
|
|
788
823
|
fi
|
|
789
824
|
fi
|
|
790
825
|
fi
|
|
826
|
+
|
|
827
|
+
# Check 8: All testSteps are server-dependent
|
|
828
|
+
if [[ -n "$test_steps" ]]; then
|
|
829
|
+
local has_offline=false has_server=false
|
|
830
|
+
local steps
|
|
831
|
+
steps=$(jq -r --arg id "$story_id" \
|
|
832
|
+
'.stories[] | select(.id==$id) | .testSteps[]?' "$prd_file")
|
|
833
|
+
while IFS= read -r s; do
|
|
834
|
+
[[ -z "$s" ]] && continue
|
|
835
|
+
if echo "$s" | grep -qE "^(curl |wget |http )"; then
|
|
836
|
+
has_server=true
|
|
837
|
+
else
|
|
838
|
+
has_offline=true
|
|
839
|
+
fi
|
|
840
|
+
done <<< "$steps"
|
|
841
|
+
[[ "$has_server" == "true" && "$has_offline" == "false" ]] && \
|
|
842
|
+
issues+="$story_id: all testSteps need live server, "
|
|
843
|
+
fi
|
|
791
844
|
done <<< "$story_ids"
|
|
792
845
|
|
|
793
846
|
echo "$issues"
|
package/ralph/test.sh
CHANGED
|
@@ -134,11 +134,15 @@ run_all_prd_tests() {
|
|
|
134
134
|
[[ -z "$step" ]] && continue
|
|
135
135
|
((total++))
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
# Expand config placeholders (e.g., {config.urls.backend})
|
|
138
|
+
local expanded_step
|
|
139
|
+
expanded_step=$(_expand_config_vars "$step")
|
|
140
|
+
|
|
141
|
+
echo -n " $expanded_step... "
|
|
138
142
|
|
|
139
143
|
local step_log
|
|
140
144
|
step_log=$(mktemp)
|
|
141
|
-
if safe_exec "$
|
|
145
|
+
if safe_exec "$expanded_step" "$step_log"; then
|
|
142
146
|
print_success "passed"
|
|
143
147
|
((passed++))
|
|
144
148
|
else
|
package/ralph/utils.sh
CHANGED
|
@@ -22,6 +22,7 @@ readonly BROWSER_TIMEOUT_SECONDS=60
|
|
|
22
22
|
readonly BROWSER_PAGE_TIMEOUT_MS=30000
|
|
23
23
|
readonly CURL_TIMEOUT_SECONDS=10
|
|
24
24
|
readonly SIGN_EXTRACTION_TIMEOUT_SECONDS=30
|
|
25
|
+
readonly PREFLIGHT_CACHE_TTL_SECONDS=600
|
|
25
26
|
|
|
26
27
|
# Common project directories (avoid duplication across files)
|
|
27
28
|
readonly FRONTEND_DIRS=("apps/web" "frontend" "client" "web")
|
package/ralph/verify/tests.sh
CHANGED
|
@@ -226,8 +226,11 @@ run_unit_tests() {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
# Expand config placeholders in a string
|
|
229
|
-
# Usage:
|
|
230
|
-
# Expands
|
|
229
|
+
# Usage: _expand_config_vars "curl {config.urls.backend}/api"
|
|
230
|
+
# Expands any {config.X.Y} placeholder from .ralph/config.json via jq.
|
|
231
|
+
# Known placeholders have fallback paths for backward compatibility:
|
|
232
|
+
# {config.urls.backend} -> .urls.backend // .api.baseUrl
|
|
233
|
+
# {config.urls.frontend} -> .urls.frontend // .testUrlBase
|
|
231
234
|
_expand_config_vars() {
|
|
232
235
|
local input="$1"
|
|
233
236
|
local config="$RALPH_DIR/config.json"
|
|
@@ -237,41 +240,38 @@ _expand_config_vars() {
|
|
|
237
240
|
|
|
238
241
|
local result="$input"
|
|
239
242
|
|
|
240
|
-
#
|
|
243
|
+
# Known placeholders with backward-compatible fallback paths
|
|
241
244
|
if [[ "$result" == *"{config.urls.backend}"* ]]; then
|
|
242
|
-
local
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
result="${result//\{config.urls.backend\}/$backend_url}"
|
|
246
|
-
fi
|
|
245
|
+
local val
|
|
246
|
+
val=$(jq -r '.urls.backend // .api.baseUrl // empty' "$config" 2>/dev/null)
|
|
247
|
+
[[ -n "$val" ]] && result="${result//\{config.urls.backend\}/$val}"
|
|
247
248
|
fi
|
|
248
249
|
|
|
249
|
-
# Expand {config.urls.frontend}
|
|
250
250
|
if [[ "$result" == *"{config.urls.frontend}"* ]]; then
|
|
251
|
-
local
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
result="${result//\{config.urls.frontend\}/$frontend_url}"
|
|
255
|
-
fi
|
|
256
|
-
fi
|
|
257
|
-
|
|
258
|
-
# Expand {config.directories.backend}
|
|
259
|
-
if [[ "$result" == *"{config.directories.backend}"* ]]; then
|
|
260
|
-
local backend_dir
|
|
261
|
-
backend_dir=$(jq -r '.directories.backend // empty' "$config" 2>/dev/null)
|
|
262
|
-
if [[ -n "$backend_dir" ]]; then
|
|
263
|
-
result="${result//\{config.directories.backend\}/$backend_dir}"
|
|
264
|
-
fi
|
|
251
|
+
local val
|
|
252
|
+
val=$(jq -r '.urls.frontend // .testUrlBase // empty' "$config" 2>/dev/null)
|
|
253
|
+
[[ -n "$val" ]] && result="${result//\{config.urls.frontend\}/$val}"
|
|
265
254
|
fi
|
|
266
255
|
|
|
267
|
-
#
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
256
|
+
# Generic expansion for any remaining {config.X.Y.Z} placeholders
|
|
257
|
+
# Handles {config.urls.app}, {config.api.healthEndpoint}, {config.directories.*}, etc.
|
|
258
|
+
local max_expansions=10
|
|
259
|
+
while [[ "$result" =~ \{config\.([a-zA-Z0-9_.]+)\} ]] && [[ $max_expansions -gt 0 ]]; do
|
|
260
|
+
local placeholder="${BASH_REMATCH[0]}"
|
|
261
|
+
local config_path="${BASH_REMATCH[1]}"
|
|
262
|
+
local jq_path=".${config_path}"
|
|
263
|
+
|
|
264
|
+
local val
|
|
265
|
+
val=$(jq -r "$jq_path // empty" "$config" 2>/dev/null)
|
|
266
|
+
if [[ -n "$val" ]]; then
|
|
267
|
+
result="${result//$placeholder/$val}"
|
|
268
|
+
else
|
|
269
|
+
# Unresolvable — warn and stop to avoid infinite loop
|
|
270
|
+
print_warning "Unresolved config placeholder: $placeholder (key '$config_path' not in config.json)" >&2
|
|
271
|
+
break
|
|
273
272
|
fi
|
|
274
|
-
|
|
273
|
+
((max_expansions--))
|
|
274
|
+
done
|
|
275
275
|
|
|
276
276
|
echo "$result"
|
|
277
277
|
}
|