From 802551db644d445a94c87d20949bb8fb93a1c5bb Mon Sep 17 00:00:00 2001 From: haikdc Date: Sat, 14 Mar 2026 19:44:16 -0700 Subject: [PATCH] [Haik]: ckpt (Removed utils.sh) (Simplified run scripts) (Added global run.sh in the root) (Start sort is enabled by default when u open the mcp registry in the tools page) (Reload issue when your in a specific dashboard is now fixed) --- README.md | 4 +- backend/{run/dev.sh => run.sh} | 25 ++- backend/run/_utils.sh | 202 ------------------------- frontend/{run/dev.sh => run.sh} | 6 +- frontend/run/_utils.sh | 202 ------------------------- frontend/src/app/pages/Tools/Tools.tsx | 6 +- frontend/webpack.config.js | 1 + run.sh | 124 +++++++++++++++ 8 files changed, 145 insertions(+), 425 deletions(-) rename backend/{run/dev.sh => run.sh} (67%) delete mode 100755 backend/run/_utils.sh rename frontend/{run/dev.sh => run.sh} (78%) delete mode 100755 frontend/run/_utils.sh create mode 100755 run.sh diff --git a/README.md b/README.md index 0904df32..9537c9bc 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Frontend (React/TypeScript :3000) Backend (FastAPI/Python :8324) ### Backend ```bash -bash backend/run/dev.sh +bash backend/run.sh # API runs at http://localhost:8324 # Docs at http://localhost:8324/docs ``` @@ -49,7 +49,7 @@ bash backend/run/dev.sh ### Frontend ```bash -bash frontend/run/dev.sh +bash frontend/run.sh # App runs at http://localhost:3000 ``` diff --git a/backend/run/dev.sh b/backend/run.sh similarity index 67% rename from backend/run/dev.sh rename to backend/run.sh index 92eede71..689ecc1f 100755 --- a/backend/run/dev.sh +++ b/backend/run.sh @@ -13,14 +13,13 @@ else # echo "NOT in macOS server START" fi chmod +x "$DEV_ABSPATH" -source "$(dirname "$DEV_ABSPATH")/_utils.sh" - -PROJECT_ROOT_ABSPATH="$(dirname "$BACKEND_DIR_ABSPATH")" +PROJECT_ROOT_ABSPATH="$(dirname "$(dirname "$DEV_ABSPATH")")" +BACKEND_DIR_ABSPATH="$PROJECT_ROOT_ABSPATH/backend" # Cleanup function on exit cleanup() { - formatted_echo --yellow "Shutting down..." + echo "Shutting down..." cd - > /dev/null 2>&1 } trap cleanup EXIT INT TERM @@ -28,34 +27,34 @@ trap cleanup EXIT INT TERM # --- Create virtual environment if it doesn't exist --- VENV_DIR="$BACKEND_DIR_ABSPATH/.venv" if [[ ! -d "$VENV_DIR" ]]; then - formatted_echo --green "Creating virtual environment..." + echo "Creating virtual environment..." python -m venv "$VENV_DIR" fi source "$VENV_DIR/bin/activate" # --- Install custom debugger module if not already installed --- DEBUGGER_DIR_ABSPATH="$PROJECT_ROOT_ABSPATH/debugger" -if ! pip show debug > /dev/null 2>&1; then - formatted_echo --green "Installing debugger module..." +if ! pip3 show debug > /dev/null 2>&1; then + echo "Installing debugger module..." cd "$DEBUGGER_DIR_ABSPATH" - pip install -e . + pip3 install -e . if [[ $? -ne 0 ]]; then - formatted_error "Failed to install debugger module." + echo "Failed to install debugger module." exit 1 fi fi # --- Install Python dependencies --- -formatted_echo --green "Installing dependencies..." +echo "Installing dependencies..." cd "$BACKEND_DIR_ABSPATH" -pip install -r requirements.txt +pip3 install -r requirements.txt if [[ $? -ne 0 ]]; then - formatted_error "Failed to install Python dependencies." + echo "Failed to install Python dependencies." exit 1 fi # --- Start the backend server --- -formatted_echo --green "Starting backend server on http://0.0.0.0:8324 ..." +echo "Starting backend server on http://0.0.0.0:8324 ..." cd "$PROJECT_ROOT_ABSPATH" python -m uvicorn backend.main:app --host 0.0.0.0 --port 8324 --reload \ --reload-dir "$BACKEND_DIR_ABSPATH" \ diff --git a/backend/run/_utils.sh b/backend/run/_utils.sh deleted file mode 100755 index ea052bb5..00000000 --- a/backend/run/_utils.sh +++ /dev/null @@ -1,202 +0,0 @@ -#!/bin/bash -# Flag processing function with namespacing and global variable declaration -UTILS_FILE_ABSPATH="$(readlink -f "${BASH_SOURCE[0]}")" -if [[ "$OSTYPE" == "darwin"* ]]; then - # echo "In macOS utils sed START" - # echo "UTILS_ABSPATH: $UTILS_FILE_ABSPATH" - sed -i '' 's/\r//g' "$UTILS_FILE_ABSPATH" - # echo "In macOS utils sed END" -else - # echo "NOT in macOS utils START" - # echo "UTILS_ABSPATH: $UTILS_FILE_ABSPATH" - sed -i 's/\r//g' "$UTILS_FILE_ABSPATH" - # echo "NOT in macOS utils END" -fi -chmod +x "$UTILS_FILE_ABSPATH" - -RUN_DIR_ABSPATH="$(dirname "$UTILS_FILE_ABSPATH")" -BACKEND_DIR_ABSPATH="$(dirname "$RUN_DIR_ABSPATH")" - -formatted_error() { - # Arguments: error message and array of conflicting flags - local initial_message="$1" - shift - local conflicting_flags=("$@") - - # Red color for the error message box - local COLOR_CODE='\033[0;31m' - local NC='\033[0m' # No Color - - # Start the error message with the initial message - local error_message="$initial_message" - - # Add each conflicting flag on a new line with indentation - for conflict in "${conflicting_flags[@]}"; do - error_message+="\n $conflict" # Replacing `\t` with four spaces - done - - # Prepare for printing by finding max length of each line in the message - local lines=() - local max_length=0 - - # Use printf to interpret new lines and calculate max length with spaces instead of tabs - while IFS= read -r line; do - # Substitute tabs with spaces for consistent width measurement - local line_with_spaces="${line//$'\t'/ }" - lines+=("$line_with_spaces") - if (( ${#line_with_spaces} > max_length )); then - max_length=${#line_with_spaces} - fi - done <<< "$(printf "$error_message")" - - # Create the top and bottom borders based on the maximum line length - local border=$(printf '%*s' "$((max_length + 4))" '' | tr ' ' '-') - - # Print the formatted error message with a red box - printf "\n${COLOR_CODE}%s${NC}\n" "$border" - for line in "${lines[@]}"; do - printf "${COLOR_CODE}| %-*s |${NC}\n" "$max_length" "$line" - done - printf "${COLOR_CODE}| %-*s |${NC}\n" "$max_length" "" - printf "${COLOR_CODE}%s${NC}\n" "$border" -} - - -function process_flags() { - local -n flags_to_commands="$1" # Reference to the dictionary of flags and commands - local -n exclusives="$2" # Reference to the list of exclusive flag groups - local namespace="$3" # Unique prefix for variables - local calling_script_name="$(basename "$(readlink -f "${BASH_SOURCE[1]}")")" - local caller_id="${FUNCNAME[1]}" - local should_exit=false - if [ "$caller_id" == "main" ]; then - caller_id="$calling_script_name" - fi - # echo "caller_id: $caller_id" - # echo "Initializing flags with namespace: $namespace" - - # Initialize flag variables with namespacing in the sourcing script context - for flag in "${!flags_to_commands[@]}"; do - # Convert flag to uppercase variable name and apply namespace, e.g., MYAPP_FLAG1 - local flag_var="${namespace}_${flag^^}" # Prefix and uppercase - flag_var="${flag_var//-}" # Remove dashes - eval "declare -g $flag_var=false" # Initialize as global false - - # Diagnostic output for each initialized variable - # echo "Initialized $flag_var as false" - done - - # echo "Parsing command line arguments: $@" - - # Parse command line arguments and set flags - local unsupported_flags=() - # local potential_typos=() - for arg in "$@"; do - # echo "Processing argument: $arg" - if [[ -n "${flags_to_commands[$arg]}" ]]; then - # echo "Flag found: $arg" - local flag_var="${namespace}_${arg^^}" # Prefix and uppercase - flag_var="${flag_var//-}" # Remove dashes - eval "declare -g $flag_var=true" # Set as global true - # echo "Set $flag_var to true" - # echo "Executing command for $arg: ${flags_to_commands[$arg]}" - eval "${flags_to_commands[$arg]}" - else - if [[ "$arg" == "-"* ]]; then - # echo "Flag not found: $arg" - unsupported_flags+=("$arg") - if [[ -n "${flags_to_commands[-$arg]}" ]]; then - # echo "Potential typo: $arg" - unsupported_flags+=("\t*Note: Potential typo detected.") - unsupported_flags+=("\tDid you mean: -$arg") - fi - fi - fi - done - # Check exclusive flag groups for conflicts - # echo "Checking exclusive flag groups" - for group in "${exclusives[@]}"; do - local count=0 - local conflicting_flags=() - for flag in $group; do - local flag_var="${namespace}_${flag^^}" - flag_var="${flag_var//-}" - if [[ "$(eval echo "\$$flag_var")" == "true" ]]; then - conflicting_flags+=("$flag") - # count=$((count + 1)) - # ec "$flag_var is true in exclusive group" - fi - done - if (( ${#conflicting_flags[@]} > 1 )); then - # echo "found conflicting flags" - formatted_error "Error: $caller_id\n-----------------------------------------\nIncompatible flags:\nThe flags below cannot be used together\n-----------------------------------------\n" "${conflicting_flags[@]}" - should_exit=true - fi - done - # echo "should_exit: $should_exit" - # echo "unsupported_flags: ${#unsupported_flags[@]}" - if (( ${#unsupported_flags[@]} > 0 )); then - # echo "found unsupported flags 2" - formatted_error "Error: $caller_id\n-------------------------------------------------\nUnsupported flags:\nThe flags below are not supported by this script\n-------------------------------------------------\n" "${unsupported_flags[@]}" - should_exit=true - fi - if [[ $should_exit == true ]]; then - exit 1 - fi -} - - - -formatted_echo() { - local COLOR_CODE='\033[0m' # No Color - local NC='\033[0m' # No Color variable - # local message="$2" - # If the second argument is empty, then theres no color specified, so we use the first argument as the message - if [[ -z "$2" ]]; then - message="$1" - else - message="$2" - fi - - declare -A format_flags - format_flags=( - [--red]="COLOR_CODE='\033[0;31m'" - [--green]="COLOR_CODE='\033[0;32m'" - [--yellow]="COLOR_CODE='\033[0;33m'" - [--blue]="COLOR_CODE='\033[0;34m'" - [--purple]="COLOR_CODE='\033[0;35m'" - [--cyan]="COLOR_CODE='\033[0;36m'" - ) - exclusive_format_flags=( - "--red --green --yellow --blue --purple --cyan" - ) - - # Pass the arguments with the namespace "FORMAT" - process_flags format_flags exclusive_format_flags "FORMAT" "$@" - - # Process the message - local text - text=$(printf "%b" "$message") - - # Expand any escaped characters in the input (e.g., \n) - local lines=() - local max_length=0 - - # Read the text line by line and find the maximum length - while IFS= read -r line; do - lines+=("$line") - if (( ${#line} > max_length )); then - max_length=${#line} - fi - done <<< "$text" - - # Create the top and bottom borders based on the maximum line length - local border=$(printf '%*s' "$((max_length + 4))" '' | tr ' ' '-') - - # Print the formatted box with selected color - printf "\n${COLOR_CODE}%s${NC}\n" "$border" - for line in "${lines[@]}"; do - printf "${COLOR_CODE}| %-*s |${NC}\n" "$max_length" "$line" - done - printf "${COLOR_CODE}%s${NC}\n" "$border" -} \ No newline at end of file diff --git a/frontend/run/dev.sh b/frontend/run.sh similarity index 78% rename from frontend/run/dev.sh rename to frontend/run.sh index b81fe212..0fa200ab 100755 --- a/frontend/run/dev.sh +++ b/frontend/run.sh @@ -13,14 +13,14 @@ else # echo "NOT in macOS server START" fi chmod +x "$DEV_ABSPATH" -source "$(dirname "$DEV_ABSPATH")/_utils.sh" +FRONTEND_DIR_ABSPATH="$(dirname "$DEV_ABSPATH")" -formatted_echo --green "Installing dependencies..." +echo "Installing dependencies..." cd "$FRONTEND_DIR_ABSPATH" npm install -formatted_echo --green "Building with development mode..." +echo "Building with development mode..." npm run dev # exit back to the dir that we were in before diff --git a/frontend/run/_utils.sh b/frontend/run/_utils.sh deleted file mode 100755 index 2d24af62..00000000 --- a/frontend/run/_utils.sh +++ /dev/null @@ -1,202 +0,0 @@ -#!/bin/bash -# Flag processing function with namespacing and global variable declaration -UTILS_FILE_ABSPATH="$(readlink -f "${BASH_SOURCE[0]}")" -if [[ "$OSTYPE" == "darwin"* ]]; then - # echo "In macOS utils sed START" - # echo "UTILS_ABSPATH: $UTILS_FILE_ABSPATH" - sed -i '' 's/\r//g' "$UTILS_FILE_ABSPATH" - # echo "In macOS utils sed END" -else - # echo "NOT in macOS utils START" - # echo "UTILS_ABSPATH: $UTILS_FILE_ABSPATH" - sed -i 's/\r//g' "$UTILS_FILE_ABSPATH" - # echo "NOT in macOS utils END" -fi -chmod +x "$UTILS_FILE_ABSPATH" - -RUN_DIR_ABSPATH="$(dirname "$UTILS_FILE_ABSPATH")" -FRONTEND_DIR_ABSPATH="$(dirname "$RUN_DIR_ABSPATH")" - -formatted_error() { - # Arguments: error message and array of conflicting flags - local initial_message="$1" - shift - local conflicting_flags=("$@") - - # Red color for the error message box - local COLOR_CODE='\033[0;31m' - local NC='\033[0m' # No Color - - # Start the error message with the initial message - local error_message="$initial_message" - - # Add each conflicting flag on a new line with indentation - for conflict in "${conflicting_flags[@]}"; do - error_message+="\n $conflict" # Replacing `\t` with four spaces - done - - # Prepare for printing by finding max length of each line in the message - local lines=() - local max_length=0 - - # Use printf to interpret new lines and calculate max length with spaces instead of tabs - while IFS= read -r line; do - # Substitute tabs with spaces for consistent width measurement - local line_with_spaces="${line//$'\t'/ }" - lines+=("$line_with_spaces") - if (( ${#line_with_spaces} > max_length )); then - max_length=${#line_with_spaces} - fi - done <<< "$(printf "$error_message")" - - # Create the top and bottom borders based on the maximum line length - local border=$(printf '%*s' "$((max_length + 4))" '' | tr ' ' '-') - - # Print the formatted error message with a red box - printf "\n${COLOR_CODE}%s${NC}\n" "$border" - for line in "${lines[@]}"; do - printf "${COLOR_CODE}| %-*s |${NC}\n" "$max_length" "$line" - done - printf "${COLOR_CODE}| %-*s |${NC}\n" "$max_length" "" - printf "${COLOR_CODE}%s${NC}\n" "$border" -} - - -function process_flags() { - local -n flags_to_commands="$1" # Reference to the dictionary of flags and commands - local -n exclusives="$2" # Reference to the list of exclusive flag groups - local namespace="$3" # Unique prefix for variables - local calling_script_name="$(basename "$(readlink -f "${BASH_SOURCE[1]}")")" - local caller_id="${FUNCNAME[1]}" - local should_exit=false - if [ "$caller_id" == "main" ]; then - caller_id="$calling_script_name" - fi - # echo "caller_id: $caller_id" - # echo "Initializing flags with namespace: $namespace" - - # Initialize flag variables with namespacing in the sourcing script context - for flag in "${!flags_to_commands[@]}"; do - # Convert flag to uppercase variable name and apply namespace, e.g., MYAPP_FLAG1 - local flag_var="${namespace}_${flag^^}" # Prefix and uppercase - flag_var="${flag_var//-}" # Remove dashes - eval "declare -g $flag_var=false" # Initialize as global false - - # Diagnostic output for each initialized variable - # echo "Initialized $flag_var as false" - done - - # echo "Parsing command line arguments: $@" - - # Parse command line arguments and set flags - local unsupported_flags=() - # local potential_typos=() - for arg in "$@"; do - # echo "Processing argument: $arg" - if [[ -n "${flags_to_commands[$arg]}" ]]; then - # echo "Flag found: $arg" - local flag_var="${namespace}_${arg^^}" # Prefix and uppercase - flag_var="${flag_var//-}" # Remove dashes - eval "declare -g $flag_var=true" # Set as global true - # echo "Set $flag_var to true" - # echo "Executing command for $arg: ${flags_to_commands[$arg]}" - eval "${flags_to_commands[$arg]}" - else - if [[ "$arg" == "-"* ]]; then - # echo "Flag not found: $arg" - unsupported_flags+=("$arg") - if [[ -n "${flags_to_commands[-$arg]}" ]]; then - # echo "Potential typo: $arg" - unsupported_flags+=("\t*Note: Potential typo detected.") - unsupported_flags+=("\tDid you mean: -$arg") - fi - fi - fi - done - # Check exclusive flag groups for conflicts - # echo "Checking exclusive flag groups" - for group in "${exclusives[@]}"; do - local count=0 - local conflicting_flags=() - for flag in $group; do - local flag_var="${namespace}_${flag^^}" - flag_var="${flag_var//-}" - if [[ "$(eval echo "\$$flag_var")" == "true" ]]; then - conflicting_flags+=("$flag") - # count=$((count + 1)) - # ec "$flag_var is true in exclusive group" - fi - done - if (( ${#conflicting_flags[@]} > 1 )); then - # echo "found conflicting flags" - formatted_error "Error: $caller_id\n-----------------------------------------\nIncompatible flags:\nThe flags below cannot be used together\n-----------------------------------------\n" "${conflicting_flags[@]}" - should_exit=true - fi - done - # echo "should_exit: $should_exit" - # echo "unsupported_flags: ${#unsupported_flags[@]}" - if (( ${#unsupported_flags[@]} > 0 )); then - # echo "found unsupported flags 2" - formatted_error "Error: $caller_id\n-------------------------------------------------\nUnsupported flags:\nThe flags below are not supported by this script\n-------------------------------------------------\n" "${unsupported_flags[@]}" - should_exit=true - fi - if [[ $should_exit == true ]]; then - exit 1 - fi -} - - - -formatted_echo() { - local COLOR_CODE='\033[0m' # No Color - local NC='\033[0m' # No Color variable - # local message="$2" - # If the second argument is empty, then theres no color specified, so we use the first argument as the message - if [[ -z "$2" ]]; then - message="$1" - else - message="$2" - fi - - declare -A format_flags - format_flags=( - [--red]="COLOR_CODE='\033[0;31m'" - [--green]="COLOR_CODE='\033[0;32m'" - [--yellow]="COLOR_CODE='\033[0;33m'" - [--blue]="COLOR_CODE='\033[0;34m'" - [--purple]="COLOR_CODE='\033[0;35m'" - [--cyan]="COLOR_CODE='\033[0;36m'" - ) - exclusive_format_flags=( - "--red --green --yellow --blue --purple --cyan" - ) - - # Pass the arguments with the namespace "FORMAT" - process_flags format_flags exclusive_format_flags "FORMAT" "$@" - - # Process the message - local text - text=$(printf "%b" "$message") - - # Expand any escaped characters in the input (e.g., \n) - local lines=() - local max_length=0 - - # Read the text line by line and find the maximum length - while IFS= read -r line; do - lines+=("$line") - if (( ${#line} > max_length )); then - max_length=${#line} - fi - done <<< "$text" - - # Create the top and bottom borders based on the maximum line length - local border=$(printf '%*s' "$((max_length + 4))" '' | tr ' ' '-') - - # Print the formatted box with selected color - printf "\n${COLOR_CODE}%s${NC}\n" "$border" - for line in "${lines[@]}"; do - printf "${COLOR_CODE}| %-*s |${NC}\n" "$max_length" "$line" - done - printf "${COLOR_CODE}%s${NC}\n" "$border" -} \ No newline at end of file diff --git a/frontend/src/app/pages/Tools/Tools.tsx b/frontend/src/app/pages/Tools/Tools.tsx index b7834bfb..c04eb01f 100644 --- a/frontend/src/app/pages/Tools/Tools.tsx +++ b/frontend/src/app/pages/Tools/Tools.tsx @@ -398,7 +398,7 @@ const Tools: React.FC = () => { // Registry browser const [registryOpen, setRegistryOpen] = useState(false); const [regQuery, setRegQuery] = useState(''); - const [regSort, setRegSort] = useState<'name' | 'stars'>('name'); + const [regSort, setRegSort] = useState<'name' | 'stars'>('stars'); const [regSource, setRegSource] = useState<'' | 'community' | 'google'>(''); const [expandedServer, setExpandedServer] = useState(null); const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity?: 'success' | 'error' }>({ open: false, message: '' }); @@ -609,11 +609,11 @@ const Tools: React.FC = () => { handleMenuClose(); setRegistryOpen(true); setRegQuery(''); - setRegSort('name'); + setRegSort('stars'); setRegSource(''); setExpandedServer(null); dispatch(fetchRegistryStats()); - dispatch(searchRegistry({ q: '', limit: 20, offset: 0, sort: 'name', source: '' })); + dispatch(searchRegistry({ q: '', limit: 20, offset: 0, sort: 'stars', source: '' })); }; // --------------- Tool CRUD --------------- diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 11730c53..79ec5436 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -10,6 +10,7 @@ module.exports = (env, argv) => { output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', + publicPath: '/', clean: true }, diff --git a/run.sh b/run.sh new file mode 100755 index 00000000..857498a4 --- /dev/null +++ b/run.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# The comment above is shebang, DO NOT REMOVE +SCRIPT_ABSPATH="$(readlink -f "${BASH_SOURCE[0]}")" +if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' 's/\r//g' "$SCRIPT_ABSPATH" +else + sed -i 's/\r//g' "$SCRIPT_ABSPATH" +fi +chmod +x "$SCRIPT_ABSPATH" + +PROJECT_ROOT="$(dirname "$SCRIPT_ABSPATH")" + +BLUE='\033[0;34m' +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[0;33m' +BOLD='\033[1m' +RESET='\033[0m' + +BACKEND_PID="" +FRONTEND_PID="" +SHUTTING_DOWN=false + +kill_tree() { + local pid=$1 sig=${2:-TERM} + local children + children=$(pgrep -P "$pid" 2>/dev/null) + for child in $children; do + kill_tree "$child" "$sig" + done + kill -"$sig" "$pid" 2>/dev/null +} + +cleanup() { + $SHUTTING_DOWN && return + SHUTTING_DOWN=true + + echo "" + echo -e "${YELLOW}${BOLD}Gracefully shutting down all services...${RESET}" + + for pid in $BACKEND_PID $FRONTEND_PID; do + [[ -n "$pid" ]] && kill_tree "$pid" TERM + done + + local elapsed=0 + while (( elapsed < 5 )); do + local alive=false + for pid in $BACKEND_PID $FRONTEND_PID; do + [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null && alive=true + done + $alive || break + sleep 1 + ((elapsed++)) + done + + for pid in $BACKEND_PID $FRONTEND_PID; do + [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null && kill_tree "$pid" KILL + done + + wait 2>/dev/null + echo -e "${GREEN}${BOLD}All services stopped.${RESET}" +} + +trap 'cleanup; exit 0' INT TERM +trap cleanup EXIT + +# --- Start backend --- +echo -e "${BLUE}${BOLD}[backend]${RESET} Starting backend server..." +bash "$PROJECT_ROOT/backend/run.sh" > >( + while IFS= read -r line; do + printf "${BLUE}${BOLD}[backend]${RESET} %s\n" "$line" + done +) 2>&1 & +BACKEND_PID=$! + +# --- Wait for backend to become healthy --- +echo -e "${YELLOW}${BOLD}Waiting for backend (http://localhost:8324) to be ready...${RESET}" +MAX_WAIT=120 +elapsed=0 +while (( elapsed < MAX_WAIT )); do + if curl -s -o /dev/null --connect-timeout 1 http://localhost:8324/ 2>/dev/null; then + echo -e "${GREEN}${BOLD}Backend is ready!${RESET}" + break + fi + if ! kill -0 "$BACKEND_PID" 2>/dev/null; then + echo -e "${RED}${BOLD}Backend process exited before becoming ready.${RESET}" + exit 1 + fi + sleep 2 + ((elapsed += 2)) +done + +if (( elapsed >= MAX_WAIT )); then + echo -e "${RED}${BOLD}Backend did not become ready within ${MAX_WAIT}s. Aborting.${RESET}" + exit 1 +fi + +# --- Start frontend --- +echo -e "${GREEN}${BOLD}[frontend]${RESET} Starting frontend dev server..." +bash "$PROJECT_ROOT/frontend/run.sh" > >( + while IFS= read -r line; do + printf "${GREEN}${BOLD}[frontend]${RESET} %s\n" "$line" + done +) 2>&1 & +FRONTEND_PID=$! + +echo "" +echo -e "${BOLD}Both services are running. Press Ctrl+C to stop.${RESET}" +echo -e " Backend: ${BLUE}http://localhost:8324${RESET}" +echo -e " Frontend: ${GREEN}http://localhost:3000${RESET}" +echo "" + +# --- Monitor: if either service exits, tear down both --- +while ! $SHUTTING_DOWN; do + if ! kill -0 "$BACKEND_PID" 2>/dev/null; then + echo -e "${RED}${BOLD}Backend process exited unexpectedly. Shutting down...${RESET}" + exit 1 + fi + if [[ -n "$FRONTEND_PID" ]] && ! kill -0 "$FRONTEND_PID" 2>/dev/null; then + echo -e "${RED}${BOLD}Frontend process exited unexpectedly. Shutting down...${RESET}" + exit 1 + fi + sleep 3 +done