From 6a5220393fb45085431a723d8ad9fa94bdfed260 Mon Sep 17 00:00:00 2001 From: Wojtek Majewski Date: Thu, 27 Nov 2025 17:24:48 +0100 Subject: [PATCH] fix supabase-start to check project-specific containers and improve compile 404 errors --- .../__tests__/commands/compile/index.test.ts | 39 +++++++++++++++++++ pkgs/cli/src/commands/compile/index.ts | 32 ++++++++++++--- pkgs/edge-worker/tests/config.ts | 12 ++++++ .../tests/e2e/control-plane.test.ts | 4 +- scripts/supabase-start.sh | 34 ++++++++++++---- 5 files changed, 105 insertions(+), 16 deletions(-) diff --git a/pkgs/cli/__tests__/commands/compile/index.test.ts b/pkgs/cli/__tests__/commands/compile/index.test.ts index 53bdfad0c..6aa86d5f1 100644 --- a/pkgs/cli/__tests__/commands/compile/index.test.ts +++ b/pkgs/cli/__tests__/commands/compile/index.test.ts @@ -170,6 +170,45 @@ describe('fetchFlowSQL', () => { ).rejects.toThrow('Did you add it to supabase/functions/pgflow/index.ts'); }); + it('should handle missing ControlPlane edge function (Supabase gateway 404)', async () => { + // Supabase gateway returns 404 with different error format when function doesn't exist + const mockResponse = { + ok: false, + status: 404, + json: async () => ({ + // Supabase gateway error - not our ControlPlane's "Flow Not Found" + error: 'not_found', + message: 'Function not found', + }), + }; + + global.fetch = vi.fn().mockResolvedValue(mockResponse); + + await expect( + fetchFlowSQL('test_flow', 'http://127.0.0.1:50621/functions/v1/pgflow', 'test-publishable-key') + ).rejects.toThrow('ControlPlane edge function not found'); + await expect( + fetchFlowSQL('test_flow', 'http://127.0.0.1:50621/functions/v1/pgflow', 'test-publishable-key') + ).rejects.toThrow('npx pgflow install'); + }); + + it('should handle 404 with non-JSON response (HTML error page)', async () => { + // Some gateways return HTML error pages + const mockResponse = { + ok: false, + status: 404, + json: async () => { + throw new Error('Unexpected token < in JSON'); + }, + }; + + global.fetch = vi.fn().mockResolvedValue(mockResponse); + + await expect( + fetchFlowSQL('test_flow', 'http://127.0.0.1:50621/functions/v1/pgflow', 'test-publishable-key') + ).rejects.toThrow('ControlPlane edge function not found'); + }); + it('should construct correct URL with flow slug', async () => { const mockResponse = { ok: true, diff --git a/pkgs/cli/src/commands/compile/index.ts b/pkgs/cli/src/commands/compile/index.ts index 72cb5f97f..5034f61d3 100644 --- a/pkgs/cli/src/commands/compile/index.ts +++ b/pkgs/cli/src/commands/compile/index.ts @@ -27,13 +27,33 @@ export async function fetchFlowSQL( }); if (response.status === 404) { - const errorData = await response.json(); + let errorData: { error?: string; message?: string } = {}; + try { + errorData = await response.json(); + } catch { + // JSON parse failed - likely Supabase gateway error (HTML or plain text) + } + + // Check if this is our ControlPlane's 404 (has 'Flow Not Found' error) + // vs Supabase gateway's 404 (function doesn't exist) + if (errorData.error === 'Flow Not Found') { + throw new Error( + `Flow '${flowSlug}' not found.\n\n` + + `${errorData.message || 'Did you add it to supabase/functions/pgflow/index.ts?'}\n\n` + + `Fix:\n` + + `1. Add your flow to supabase/functions/pgflow/index.ts\n` + + `2. Restart edge functions: supabase functions serve` + ); + } + + // ControlPlane edge function itself doesn't exist throw new Error( - `Flow '${flowSlug}' not found.\n\n` + - `${errorData.message || 'Did you add it to supabase/functions/pgflow/index.ts?'}\n\n` + - `Fix:\n` + - `1. Add your flow to supabase/functions/pgflow/index.ts\n` + - `2. Restart edge functions: supabase functions serve` + 'ControlPlane edge function not found.\n\n' + + 'The pgflow edge function is not installed or not running.\n\n' + + 'Fix:\n' + + '1. Run: npx pgflow install\n' + + '2. Start edge functions: supabase functions serve\n\n' + + 'Or use previous version: npx pgflow@0.8.0 compile path/to/flow.ts' ); } diff --git a/pkgs/edge-worker/tests/config.ts b/pkgs/edge-worker/tests/config.ts index b3e1c381b..ef75da72c 100644 --- a/pkgs/edge-worker/tests/config.ts +++ b/pkgs/edge-worker/tests/config.ts @@ -19,6 +19,18 @@ export const e2eConfig = { get dbUrl() { return 'postgresql://postgres:postgres@127.0.0.1:50322/postgres'; }, + + /** Max retries when waiting for server to be ready (default: 30) */ + get serverReadyMaxRetries() { + const envValue = Deno.env.get('E2E_SERVER_READY_MAX_RETRIES'); + return envValue ? parseInt(envValue, 10) : 30; + }, + + /** Delay between retries in ms (default: 1000) */ + get serverReadyRetryDelayMs() { + const envValue = Deno.env.get('E2E_SERVER_READY_RETRY_DELAY_MS'); + return envValue ? parseInt(envValue, 10) : 1000; + }, }; /** diff --git a/pkgs/edge-worker/tests/e2e/control-plane.test.ts b/pkgs/edge-worker/tests/e2e/control-plane.test.ts index 072640ca0..de4d70657 100644 --- a/pkgs/edge-worker/tests/e2e/control-plane.test.ts +++ b/pkgs/edge-worker/tests/e2e/control-plane.test.ts @@ -12,8 +12,8 @@ const BASE_URL = `${API_URL}/functions/v1/pgflow`; async function ensureServerReady() { log('Ensuring pgflow function is ready...'); - const maxRetries = 15; - const retryDelayMs = 1000; + const maxRetries = e2eConfig.serverReadyMaxRetries; + const retryDelayMs = e2eConfig.serverReadyRetryDelayMs; for (let i = 0; i < maxRetries; i++) { try { diff --git a/scripts/supabase-start.sh b/scripts/supabase-start.sh index 8f3579616..0258236e9 100755 --- a/scripts/supabase-start.sh +++ b/scripts/supabase-start.sh @@ -34,8 +34,8 @@ NC='\033[0m' # No Color # Required services for edge function development # Note: Services like imgproxy, studio, inbucket, analytics, vector, pg_meta are optional # Container names use project_id suffix from config.toml (e.g., supabase_db_cli for project_id="cli") -# We use pattern matching to handle different project suffixes -REQUIRED_SERVICES=( +# The project_id matches the "name" field in project.json +REQUIRED_SERVICE_PREFIXES=( "supabase_db_" "supabase_kong_" "supabase_edge_runtime_" @@ -43,14 +43,17 @@ REQUIRED_SERVICES=( "supabase_realtime_" ) -# Check if all required services are running via docker ps +# Check if all required services are running for a specific project # This is more reliable than `supabase status` which returns 0 even with stopped services +# Args: $1 = project_name (e.g., "cli", "edge-worker") check_required_services_running() { + local project_name="$1" local running_containers running_containers=$(docker ps --format '{{.Names}}' 2>/dev/null) - for service_prefix in "${REQUIRED_SERVICES[@]}"; do - if ! echo "$running_containers" | grep -q "^${service_prefix}"; then + for service_prefix in "${REQUIRED_SERVICE_PREFIXES[@]}"; do + local full_container_name="${service_prefix}${project_name}" + if ! echo "$running_containers" | grep -qF "$full_container_name"; then return 1 fi done @@ -72,15 +75,30 @@ if [ ! -d "$PROJECT_DIR" ]; then exit 1 fi +# Convert to absolute path for consistent file lookups after cd +PROJECT_DIR=$(realpath "$PROJECT_DIR") + # Change to project directory (Supabase CLI uses current directory) cd "$PROJECT_DIR" -echo -e "${YELLOW}Checking Supabase status in: $PROJECT_DIR${NC}" +# Extract project name from project.json (matches project_id in supabase/config.toml) +if [ ! -f "$PROJECT_DIR/project.json" ]; then + echo -e "${RED}Error: project.json not found in $PROJECT_DIR${NC}" >&2 + exit 1 +fi + +PROJECT_NAME=$(jq -r '.name' "$PROJECT_DIR/project.json") +if [ -z "$PROJECT_NAME" ] || [ "$PROJECT_NAME" = "null" ]; then + echo -e "${RED}Error: Could not read 'name' from project.json${NC}" >&2 + exit 1 +fi + +echo -e "${YELLOW}Checking Supabase status for project '$PROJECT_NAME' in: $PROJECT_DIR${NC}" # Fast path: Check if all required Supabase services are running via docker ps # This is more reliable than `supabase status` which returns 0 even with stopped services -if check_required_services_running; then - echo -e "${GREEN}✓ Supabase is already running (all required services up)${NC}" +if check_required_services_running "$PROJECT_NAME"; then + echo -e "${GREEN}✓ Supabase is already running for project '$PROJECT_NAME' (all required services up)${NC}" exit 0 fi