Keep embedded demo assets in sync

This commit is contained in:
rcourtman 2026-04-12 14:33:02 +01:00
parent 53b050f674
commit 12294b587e
3 changed files with 95 additions and 4 deletions

View file

@ -193,7 +193,10 @@ export ALLOWED_ORIGINS
EXTRA_CLEANUP_PORT=$((PULSE_DEV_API_PORT + 1))
HOT_DEV_RESTART_SENTINEL="${ROOT_DIR}/tmp/hot-dev.restart"
HOT_DEV_VERIFY_LOCK="${HOT_DEV_VERIFY_LOCK_FILE:-${ROOT_DIR}/tmp/hot-dev.verify.lock}"
HOT_DEV_BUILD_LOCK="${HOT_DEV_BUILD_LOCK_FILE:-${ROOT_DIR}/tmp/hot-dev.build.lock}"
HOT_DEV_WATCHER_STARTUP_GRACE_SECONDS="${HOT_DEV_WATCHER_STARTUP_GRACE_SECONDS:-5}"
EMBEDDED_FRONTEND_DIR="${ROOT_DIR}/internal/api/frontend-modern"
EMBEDDED_FRONTEND_DIST_DIR="${EMBEDDED_FRONTEND_DIR}/dist"
# --- Startup Checks ---
@ -215,7 +218,7 @@ Toggle mock mode: npm run mock:on / npm run mock:off
Mock config: npm run mock:edit
Frontend: Edit files and see changes instantly!
Backend: Auto-rebuilds when .go files change!
Backend: Auto-rebuilds when Go or embedded demo assets change!
Press Ctrl+C to stop
=========================================
BANNER
@ -534,6 +537,10 @@ log_info "Starting backend file watcher..."
}
manual_build_event_suppressed() {
if build_lock_active; then
return 0
fi
local now
now=$(date +%s)
(( now < SELF_BUILD_IGNORE_UNTIL ))
@ -542,6 +549,10 @@ log_info "Starting backend file watcher..."
should_rebuild_backend_for_change() {
local changed_file=$1
if [[ "${changed_file}" == "${EMBEDDED_FRONTEND_DIST_DIR}" ]] || [[ "${changed_file}" == "${EMBEDDED_FRONTEND_DIST_DIR}/"* ]]; then
return 0
fi
[[ "${changed_file}" == *.go ]] || return 1
[[ "${changed_file}" == *_test.go ]] && return 1
@ -562,6 +573,34 @@ log_info "Starting backend file watcher..."
LAST_PULSE_BINARY_MARKER="$(file_event_marker "${ROOT_DIR}/pulse" || true)"
build_lock_active() {
local owner_pid
[[ -f "${HOT_DEV_BUILD_LOCK}" ]] || return 1
owner_pid="$(awk -F= '/^pid=/{print $2; exit}' "${HOT_DEV_BUILD_LOCK}" 2>/dev/null || true)"
if [[ -z "${owner_pid}" ]]; then
rm -f "${HOT_DEV_BUILD_LOCK}"
return 1
fi
if ! kill -0 "${owner_pid}" 2>/dev/null; then
rm -f "${HOT_DEV_BUILD_LOCK}"
return 1
fi
return 0
}
set_build_lock() {
mkdir -p "$(dirname "${HOT_DEV_BUILD_LOCK}")"
printf 'pid=%s\ncreated_at=%s\n' "${BASHPID:-$$}" "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" > "${HOT_DEV_BUILD_LOCK}"
}
clear_build_lock() {
rm -f "${HOT_DEV_BUILD_LOCK}"
}
verify_lock_active() {
local owner_pid
@ -661,13 +700,21 @@ log_info "Starting backend file watcher..."
log_info "🔄 Change detected: $(basename "$changed_file")"
log_info "Rebuilding backend..."
# Suppress manual pulse-binary restart handling for the build output
# generated by this managed rebuild before the compiler touches ./pulse.
mark_self_build_output
set_build_lock
# Use the same build logic as the initial build.
if build_backend_binary; then
mark_self_build_output
clear_build_lock
restart_backend
else
clear_build_lock
log_error "✗ Build failed; keeping the current backend process running."
fi
LAST_REBUILD_TIME=$(date +%s)
log_info "Watching for changes..."
}
@ -719,9 +766,11 @@ log_info "Starting backend file watcher..."
log_info "Using fswatch for file monitoring"
# Watch the pulse binary too — if someone does `go build -o pulse`
# manually, we should restart without rebuilding.
# manually, we should restart without rebuilding. Watch the embedded
# frontend parent dir as well so atomic dist swaps trigger a backend
# rebuild for the backend-served demo surface.
fswatch --event Created --event Updated --event Renamed --event AttributeModified \
"${ROOT_DIR}/pulse" "${HOT_DEV_RESTART_SENTINEL}" 2>/dev/null | \
"${ROOT_DIR}/pulse" "${HOT_DEV_RESTART_SENTINEL}" "${EMBEDDED_FRONTEND_DIR}" 2>/dev/null | \
while read -r changed_file; do
if watcher_within_startup_grace && { [[ "$changed_file" == "${HOT_DEV_RESTART_SENTINEL}" ]] || [[ "$changed_file" == "${ROOT_DIR}/pulse" ]]; }; then
continue
@ -733,7 +782,13 @@ log_info "Starting backend file watcher..."
fi
LAST_RESTART_SENTINEL_MARKER="${event_marker}"
log_info "🚀 Managed restart requested, restarting backend..."
else
elif should_rebuild_backend_for_change "$changed_file"; then
if verify_lock_active; then
continue
fi
rebuild_backend "$changed_file"
continue
elif [[ "$changed_file" == "${ROOT_DIR}/pulse" ]]; then
if verify_lock_active; then
continue
fi
@ -746,6 +801,8 @@ log_info "Starting backend file watcher..."
fi
LAST_PULSE_BINARY_MARKER="${event_marker}"
log_info "🚀 Manual build detected (pulse binary changed), restarting..."
else
continue
fi
restart_backend
done &

View file

@ -656,12 +656,42 @@ test_hot_dev_script_ignores_test_only_backend_churn() {
assert_contains "hot-dev watcher ignores Go test files" "${output}" '[[ "${changed_file}" == *_test.go ]] && return 1'
assert_contains "hot-dev watcher routes rebuild decisions through shared helper" "${output}" 'should_rebuild_backend_for_change "$changed_file"'
assert_contains "hot-dev watcher rebuilds for embedded frontend demo dist changes" "${output}" '[[ "${changed_file}" == "${EMBEDDED_FRONTEND_DIST_DIR}" ]] || [[ "${changed_file}" == "${EMBEDDED_FRONTEND_DIST_DIR}/"* ]]'
assert_contains "hot-dev fswatch covers the embedded frontend parent dir" "${output}" '"${ROOT_DIR}/pulse" "${HOT_DEV_RESTART_SENTINEL}" "${EMBEDDED_FRONTEND_DIR}"'
assert_contains "hot-dev fswatch only treats the pulse binary path as a manual build trigger" "${output}" 'elif [[ "$changed_file" == "${ROOT_DIR}/pulse" ]]; then'
assert_contains "hot-dev watcher declares a managed build lock path" "${output}" 'HOT_DEV_BUILD_LOCK="${HOT_DEV_BUILD_LOCK_FILE:-${ROOT_DIR}/tmp/hot-dev.build.lock}"'
assert_contains "hot-dev watcher suppresses manual binary restarts while a managed build is active" "${output}" 'if build_lock_active; then'
assert_contains "hot-dev watcher suppresses self-build binary restart loops" "${output}" 'if manual_build_event_suppressed; then'
assert_contains "hot-dev watcher suppresses startup self-build pulse events" "${output}" 'SELF_BUILD_IGNORE_UNTIL=$((WATCHER_READY_AT + HOT_DEV_WATCHER_STARTUP_GRACE_SECONDS + 5))'
assert_contains "hot-dev watcher seeds the startup pulse marker" "${output}" 'LAST_PULSE_BINARY_MARKER="$(file_event_marker "${ROOT_DIR}/pulse" || true)"'
assert_contains "hot-dev watcher suppresses source churn during managed verification" "${output}" 'verify_lock_active'
}
test_hot_dev_script_marks_managed_rebuild_output_before_build() {
local rebuild_block mark_line build_line
rebuild_block="$(
awk '
/rebuild_backend\(\)/ { capture=1 }
capture { print }
capture && /^ if command -v inotifywait/ { exit }
' "${HOT_DEV}"
)"
assert_contains "hot-dev rebuild path suppresses self-build binary churn" "${rebuild_block}" 'mark_self_build_output'
assert_contains "hot-dev rebuild path raises the managed build lock" "${rebuild_block}" 'set_build_lock'
assert_contains "hot-dev rebuild path clears the managed build lock" "${rebuild_block}" 'clear_build_lock'
mark_line="$(printf '%s\n' "${rebuild_block}" | awk '/mark_self_build_output/ { print NR; exit }')"
build_line="$(printf '%s\n' "${rebuild_block}" | awk '/if build_backend_binary; then/ { print NR; exit }')"
if [[ -n "${mark_line}" && -n "${build_line}" && "${mark_line}" -lt "${build_line}" ]]; then
echo "[PASS] hot-dev rebuild path marks self-build output before compiling pulse"
else
echo "[FAIL] hot-dev rebuild path marks self-build output before compiling pulse" >&2
((failures++))
fi
}
test_hot_dev_bg_script_advertises_managed_entrypoint() {
local output
output="$(cat "${ROOT_DIR}/scripts/hot-dev-bg.sh")"