diff --git a/.changeset/fix-local-detection-step-output.md b/.changeset/fix-local-detection-step-output.md new file mode 100644 index 000000000..cfd110e1d --- /dev/null +++ b/.changeset/fix-local-detection-step-output.md @@ -0,0 +1,9 @@ +--- +'@pgflow/edge-worker': patch +'@pgflow/core': patch +--- + +Note: Version 0.13.0 was yanked due to broken local environment detection. This release (0.13.1) includes both the fix and the features from 0.13.0. + +- Fix local environment detection to use SUPABASE_URL instead of API keys +- Add step output storage optimization for 2x faster Map chains (outputs now stored in step_states.output instead of aggregated on-demand) diff --git a/pkgs/edge-worker/src/shared/localDetection.ts b/pkgs/edge-worker/src/shared/localDetection.ts index 29576a58c..f58f46c47 100644 --- a/pkgs/edge-worker/src/shared/localDetection.ts +++ b/pkgs/edge-worker/src/shared/localDetection.ts @@ -1,31 +1,19 @@ -// Old HS256 JWT keys (Supabase CLI v1) -export const KNOWN_LOCAL_ANON_KEY = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'; - -export const KNOWN_LOCAL_SERVICE_ROLE_KEY = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU'; - -// New opaque keys (Supabase CLI v2+) -// Source: https://github.com/supabase/cli/blob/develop/pkg/config/apikeys.go -export const KNOWN_LOCAL_PUBLISHABLE_KEY = - 'sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH'; - -export const KNOWN_LOCAL_SECRET_KEY = 'sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz'; +/** + * Local Supabase CLI sets SUPABASE_URL to this Docker-internal API gateway. + */ +export const LOCAL_SUPABASE_HOST = 'kong:8000'; /** - * Checks if the provided environment indicates local Supabase. - * Use when you have access to an env record (e.g., from PlatformAdapter). - * Supports both old HS256 JWT keys and new opaque keys from Supabase CLI v2+. + * Checks if the provided environment indicates local Supabase development. + * Uses SUPABASE_URL to detect local environment (more reliable than key matching). */ export function isLocalSupabaseEnv(env: Record): boolean { - const anonKey = env['SUPABASE_ANON_KEY']; - const serviceRoleKey = env['SUPABASE_SERVICE_ROLE_KEY']; - - const isLocalAnonKey = - anonKey === KNOWN_LOCAL_ANON_KEY || anonKey === KNOWN_LOCAL_PUBLISHABLE_KEY; - const isLocalServiceRoleKey = - serviceRoleKey === KNOWN_LOCAL_SERVICE_ROLE_KEY || - serviceRoleKey === KNOWN_LOCAL_SECRET_KEY; + const supabaseUrl = env['SUPABASE_URL']; + if (!supabaseUrl) return false; - return isLocalAnonKey || isLocalServiceRoleKey; + try { + return new URL(supabaseUrl).host === LOCAL_SUPABASE_HOST; + } catch { + return false; + } } diff --git a/pkgs/edge-worker/supabase/functions/auth_test/index.ts b/pkgs/edge-worker/supabase/functions/auth_test/index.ts index 41bfcb4a9..66a3e44ad 100644 --- a/pkgs/edge-worker/supabase/functions/auth_test/index.ts +++ b/pkgs/edge-worker/supabase/functions/auth_test/index.ts @@ -2,9 +2,7 @@ import { EdgeWorker } from '@pgflow/edge-worker'; import { configurePlatform } from '@pgflow/edge-worker/testing'; import { sql } from '../utils.ts'; -// Production-like keys (NOT the known local demo keys) -// These must match the values used in tests/e2e/authorization.test.ts -export const PRODUCTION_ANON_KEY = 'test-production-anon-key-abc123'; +// Production-like service role key for auth validation (must match test's Bearer token) export const PRODUCTION_SERVICE_ROLE_KEY = 'test-production-service-role-key-xyz789'; // Docker-internal URL for Supabase transaction pooler @@ -12,13 +10,13 @@ const DOCKER_POOLER_URL = 'postgresql://postgres.pooler-dev:postgres@pooler:6543 // Override environment BEFORE EdgeWorker.start() to enable auth validation // This simulates production mode where auth is NOT bypassed -// Note: We must include EDGE_WORKER_DB_URL because with production keys, +// Note: We must include EDGE_WORKER_DB_URL because with production SUPABASE_URL, // isLocalSupabaseEnv() returns false and the docker pooler fallback won't trigger configurePlatform({ getEnv: () => ({ ...Deno.env.toObject(), - SUPABASE_ANON_KEY: PRODUCTION_ANON_KEY, - SUPABASE_SERVICE_ROLE_KEY: PRODUCTION_SERVICE_ROLE_KEY, + SUPABASE_URL: 'https://test-project.supabase.co', // Production URL + SUPABASE_SERVICE_ROLE_KEY: PRODUCTION_SERVICE_ROLE_KEY, // For auth validation EDGE_WORKER_DB_URL: DOCKER_POOLER_URL, }), }); diff --git a/pkgs/edge-worker/tests/unit/platform/SupabasePlatformAdapter.test.ts b/pkgs/edge-worker/tests/unit/platform/SupabasePlatformAdapter.test.ts index 54408e4b4..6d22d5488 100644 --- a/pkgs/edge-worker/tests/unit/platform/SupabasePlatformAdapter.test.ts +++ b/pkgs/edge-worker/tests/unit/platform/SupabasePlatformAdapter.test.ts @@ -214,16 +214,14 @@ Deno.test({ // ============================================================ Deno.test({ - name: 'isLocalEnvironment returns true for local Supabase keys', + name: 'isLocalEnvironment returns true for local Supabase URL (kong)', sanitizeResources: false, fn: () => { - const KNOWN_LOCAL_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'; - const deps = createMockDeps({ getEnv: () => ({ - SUPABASE_URL: 'http://test.supabase.co', - SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY, - SUPABASE_SERVICE_ROLE_KEY: 'test-service-key', + SUPABASE_URL: 'http://kong:8000', // Local dev URL + SUPABASE_ANON_KEY: 'any-anon-key', + SUPABASE_SERVICE_ROLE_KEY: 'any-service-key', SB_EXECUTION_ID: 'test-exec-id', EDGE_WORKER_DB_URL: 'postgresql://postgres:postgres@localhost:54322/postgres', }), @@ -236,14 +234,14 @@ Deno.test({ }); Deno.test({ - name: 'isLocalEnvironment returns false for production keys', + name: 'isLocalEnvironment returns false for production URL', sanitizeResources: false, fn: () => { const deps = createMockDeps({ getEnv: () => ({ - SUPABASE_URL: 'http://test.supabase.co', - SUPABASE_ANON_KEY: 'production-anon-key', - SUPABASE_SERVICE_ROLE_KEY: 'production-service-key', + SUPABASE_URL: 'https://abc123.supabase.co', // Production URL + SUPABASE_ANON_KEY: 'any-anon-key', + SUPABASE_SERVICE_ROLE_KEY: 'any-service-key', SB_EXECUTION_ID: 'test-exec-id', EDGE_WORKER_DB_URL: 'postgresql://postgres:postgres@localhost:54322/postgres', }), diff --git a/pkgs/edge-worker/tests/unit/platform/connectionPriority.test.ts b/pkgs/edge-worker/tests/unit/platform/connectionPriority.test.ts index bda4ca7ea..d481dc157 100644 --- a/pkgs/edge-worker/tests/unit/platform/connectionPriority.test.ts +++ b/pkgs/edge-worker/tests/unit/platform/connectionPriority.test.ts @@ -1,8 +1,4 @@ import { assertEquals, assertThrows } from '@std/assert'; -import { - KNOWN_LOCAL_ANON_KEY, - KNOWN_LOCAL_SERVICE_ROLE_KEY, -} from '../../../src/shared/localDetection.ts'; import { resolveConnectionString, assertConnectionAvailable, @@ -11,31 +7,28 @@ import { } from '../../../src/platform/resolveConnection.ts'; import postgres from 'postgres'; +// Local URL for test envs (triggers isLocalSupabaseEnv = true) +const LOCAL_SUPABASE_URL = 'http://kong:8000'; + // ============================================================ // Local environment tests // ============================================================ Deno.test('connection priority - local env uses Docker pooler URL by default', () => { - const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY }; - const result = resolveConnectionString(env); - assertEquals(result, DOCKER_TRANSACTION_POOLER_URL); -}); - -Deno.test('connection priority - local env with service role key uses Docker pooler URL', () => { - const env = { SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SERVICE_ROLE_KEY }; + const env = { SUPABASE_URL: LOCAL_SUPABASE_URL }; const result = resolveConnectionString(env); assertEquals(result, DOCKER_TRANSACTION_POOLER_URL); }); Deno.test('connection priority - local env respects config.connectionString override', () => { - const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY }; + const env = { SUPABASE_URL: LOCAL_SUPABASE_URL }; const options = { connectionString: 'postgresql://custom:5432/db' }; const result = resolveConnectionString(env, options); assertEquals(result, 'postgresql://custom:5432/db'); }); Deno.test('connection priority - local env respects config.sql override', () => { - const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY }; + const env = { SUPABASE_URL: LOCAL_SUPABASE_URL }; const options = { hasSql: true }; const result = resolveConnectionString(env, options); // When sql is provided, we don't use the local pooler URL @@ -45,7 +38,7 @@ Deno.test('connection priority - local env respects config.sql override', () => Deno.test('connection priority - local env with EDGE_WORKER_DB_URL uses it instead of docker pooler', () => { const env = { - SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY, + SUPABASE_URL: LOCAL_SUPABASE_URL, EDGE_WORKER_DB_URL: 'postgresql://custom-local:5432/db', }; const result = resolveConnectionString(env); @@ -58,7 +51,7 @@ Deno.test('connection priority - local env with EDGE_WORKER_DB_URL uses it inste Deno.test('connection priority - production uses EDGE_WORKER_DB_URL', () => { const env = { - SUPABASE_ANON_KEY: 'prod-anon-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_DB_URL: 'postgresql://prod:5432/db', }; const result = resolveConnectionString(env); @@ -67,7 +60,7 @@ Deno.test('connection priority - production uses EDGE_WORKER_DB_URL', () => { Deno.test('connection priority - production config.connectionString overrides env var', () => { const env = { - SUPABASE_ANON_KEY: 'prod-anon-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_DB_URL: 'postgresql://prod:5432/db', }; const options = { connectionString: 'postgresql://override:5432/db' }; @@ -76,7 +69,7 @@ Deno.test('connection priority - production config.connectionString overrides en }); Deno.test('connection priority - production returns undefined when nothing configured', () => { - const env = { SUPABASE_ANON_KEY: 'prod-anon-key' }; + const env = { SUPABASE_URL: 'https://abc123.supabase.co' }; const result = resolveConnectionString(env); assertEquals(result, undefined); }); @@ -86,7 +79,7 @@ Deno.test('connection priority - production returns undefined when nothing confi // ============================================================ Deno.test('connection validation - throws when no connection available on production', () => { - const env = { SUPABASE_ANON_KEY: 'prod-anon-key' }; + const env = { SUPABASE_URL: 'https://abc123.supabase.co' }; const connectionString = resolveConnectionString(env); assertThrows( @@ -108,7 +101,7 @@ Deno.test('connection validation - does not throw when sql is provided', () => { }); Deno.test('connection validation - error message lists all options', () => { - const env = { SUPABASE_ANON_KEY: 'prod-anon-key' }; + const env = { SUPABASE_URL: 'https://abc123.supabase.co' }; const connectionString = resolveConnectionString(env); try { @@ -128,7 +121,7 @@ Deno.test('connection validation - error message lists all options', () => { Deno.test('connection priority - preview branch fallback pattern works', () => { // Simulates: connectionString: Deno.env.get('EDGE_WORKER_DB_URL') || Deno.env.get('SUPABASE_DB_URL') const env = { - SUPABASE_ANON_KEY: 'prod-anon-key', + SUPABASE_URL: 'https://abc123.supabase.co', // EDGE_WORKER_DB_URL not set (preview branch) }; @@ -146,7 +139,7 @@ Deno.test('connection priority - preview branch fallback pattern works', () => { Deno.test('resolveSqlConnection - priority 1: returns provided sql directly', () => { const mockSql = postgres('postgresql://mock:5432/db'); - const env = { SUPABASE_ANON_KEY: 'prod-anon-key' }; + const env = { SUPABASE_URL: 'https://abc123.supabase.co' }; const result = resolveSqlConnection(env, { sql: mockSql }); @@ -155,7 +148,7 @@ Deno.test('resolveSqlConnection - priority 1: returns provided sql directly', () }); Deno.test('resolveSqlConnection - priority 2: creates sql from connectionString', () => { - const env = { SUPABASE_ANON_KEY: 'prod-anon-key' }; + const env = { SUPABASE_URL: 'https://abc123.supabase.co' }; const options = { connectionString: 'postgresql://custom:5432/db' }; const result = resolveSqlConnection(env, options); @@ -166,7 +159,7 @@ Deno.test('resolveSqlConnection - priority 2: creates sql from connectionString' Deno.test('resolveSqlConnection - priority 3: creates sql from EDGE_WORKER_DB_URL', () => { const env = { - SUPABASE_ANON_KEY: 'prod-anon-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_DB_URL: 'postgresql://env-var:5432/db', }; @@ -177,7 +170,7 @@ Deno.test('resolveSqlConnection - priority 3: creates sql from EDGE_WORKER_DB_UR }); Deno.test('resolveSqlConnection - priority 4: creates sql from Docker URL in local env', () => { - const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY }; + const env = { SUPABASE_URL: LOCAL_SUPABASE_URL }; const result = resolveSqlConnection(env); @@ -186,7 +179,7 @@ Deno.test('resolveSqlConnection - priority 4: creates sql from Docker URL in loc }); Deno.test('resolveSqlConnection - throws when nothing configured in production', () => { - const env = { SUPABASE_ANON_KEY: 'prod-anon-key' }; + const env = { SUPABASE_URL: 'https://abc123.supabase.co' }; assertThrows( () => resolveSqlConnection(env), @@ -197,7 +190,7 @@ Deno.test('resolveSqlConnection - throws when nothing configured in production', Deno.test('resolveSqlConnection - connectionString takes priority over EDGE_WORKER_DB_URL', () => { const env = { - SUPABASE_ANON_KEY: 'prod-anon-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_DB_URL: 'postgresql://env-var:5432/db', }; const options = { connectionString: 'postgresql://explicit:5432/db' }; @@ -212,7 +205,7 @@ Deno.test('resolveSqlConnection - connectionString takes priority over EDGE_WORK Deno.test('resolveSqlConnection - sql takes priority over everything', () => { const mockSql = postgres('postgresql://mock:5432/db'); const env = { - SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY, + SUPABASE_URL: LOCAL_SUPABASE_URL, EDGE_WORKER_DB_URL: 'postgresql://env-var:5432/db', }; const options = { diff --git a/pkgs/edge-worker/tests/unit/platform/formatters.test.ts b/pkgs/edge-worker/tests/unit/platform/formatters.test.ts index d33049037..daf8d671f 100644 --- a/pkgs/edge-worker/tests/unit/platform/formatters.test.ts +++ b/pkgs/edge-worker/tests/unit/platform/formatters.test.ts @@ -39,7 +39,7 @@ Deno.test('FancyFormatter - taskCompleted outputs correct format with worker pre try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); const logger = factory.createLogger('test'); @@ -81,7 +81,7 @@ Deno.test('FancyFormatter - taskCompleted shows colors when colorsEnabled', () = try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); const logger = factory.createLogger('test'); @@ -112,7 +112,7 @@ Deno.test('FancyFormatter - taskFailed outputs error with worker prefix and flow try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); const logger = factory.createLogger('test'); @@ -152,7 +152,7 @@ Deno.test('FancyFormatter - taskStarted outputs dim styling with identifiers (de try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', EDGE_WORKER_LOG_LEVEL: 'debug', }); const logger = factory.createLogger('test'); @@ -192,7 +192,7 @@ Deno.test('FancyFormatter - taskStarted NOT logged when level is verbose', () => try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', EDGE_WORKER_LOG_LEVEL: 'verbose', }); const logger = factory.createLogger('test'); @@ -221,7 +221,7 @@ Deno.test('FancyFormatter - retry information displayed when retryAttempt > 1', try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); const logger = factory.createLogger('test'); @@ -254,7 +254,7 @@ Deno.test('FancyFormatter - polling outputs with worker prefix', () => { try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); factory.setWorkerName('greet-user-worker'); const logger = factory.createLogger('test'); @@ -278,7 +278,7 @@ Deno.test('FancyFormatter - taskCount shows count with worker prefix when > 0', try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); factory.setWorkerName('greet-user-worker'); const logger = factory.createLogger('test'); @@ -304,7 +304,7 @@ Deno.test('FancyFormatter - taskCount muted with worker prefix when count is 0', try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); factory.setWorkerName('greet-user-worker'); const logger = factory.createLogger('test'); @@ -328,7 +328,7 @@ Deno.test('FancyFormatter - startupBanner shows multi-flow with aligned list', ( try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); const logger = factory.createLogger('test'); @@ -372,7 +372,7 @@ Deno.test('FancyFormatter - shutdown shows correct phase with worker prefix', () try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); factory.setWorkerName('greet-user-worker'); const logger = factory.createLogger('test'); @@ -399,7 +399,7 @@ Deno.test('SimpleFormatter - taskCompleted outputs key=value format with worker/ try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'some-production-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_LOG_LEVEL: 'verbose', }); const logger = factory.createLogger('test'); @@ -439,7 +439,7 @@ Deno.test('SimpleFormatter - taskFailed outputs structured error with worker/que try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'some-production-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_LOG_LEVEL: 'verbose', }); const logger = factory.createLogger('test'); @@ -478,7 +478,7 @@ Deno.test('SimpleFormatter - no ANSI colors in output', () => { try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'some-production-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_LOG_LEVEL: 'verbose', }); const logger = factory.createLogger('test'); @@ -514,7 +514,7 @@ Deno.test('NO_COLOR disables colors in fancy mode', () => { try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', NO_COLOR: '1', }); const logger = factory.createLogger('test'); @@ -553,7 +553,7 @@ Deno.test('FancyFormatter - taskCompleted at DEBUG level includes identifiers', try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', EDGE_WORKER_LOG_LEVEL: 'debug', }); const logger = factory.createLogger('test'); @@ -587,7 +587,7 @@ Deno.test('FancyFormatter - taskFailed at DEBUG level includes identifiers', () try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', EDGE_WORKER_LOG_LEVEL: 'debug', }); const logger = factory.createLogger('test'); @@ -626,7 +626,7 @@ Deno.test('FancyFormatter - taskFailed shows retry pending info when retries rem try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); const logger = factory.createLogger('test'); @@ -663,7 +663,7 @@ Deno.test('FancyFormatter - taskFailed calculates exponential backoff delay corr try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); const logger = factory.createLogger('test'); @@ -699,7 +699,7 @@ Deno.test('FancyFormatter - taskFailed does NOT show retry info when no retries try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }); const logger = factory.createLogger('test'); @@ -734,7 +734,7 @@ Deno.test('SimpleFormatter - taskFailed shows retry info in key=value format', ( try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'some-production-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_LOG_LEVEL: 'verbose', }); const logger = factory.createLogger('test'); @@ -771,7 +771,7 @@ Deno.test('SimpleFormatter - taskFailed does NOT show retry info when no retries try { const factory = createLoggingFactory({ - SUPABASE_ANON_KEY: 'some-production-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_LOG_LEVEL: 'verbose', }); const logger = factory.createLogger('test'); diff --git a/pkgs/edge-worker/tests/unit/platform/logging.test.ts b/pkgs/edge-worker/tests/unit/platform/logging.test.ts index 470dcd796..a99daf273 100644 --- a/pkgs/edge-worker/tests/unit/platform/logging.test.ts +++ b/pkgs/edge-worker/tests/unit/platform/logging.test.ts @@ -162,7 +162,7 @@ Deno.test('createLoggingFactory - format defaults to simple when env not provide Deno.test('createLoggingFactory - format is fancy for local Supabase environment', () => { const localEnv = { - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }; const factory = createLoggingFactory(localEnv); @@ -171,7 +171,7 @@ Deno.test('createLoggingFactory - format is fancy for local Supabase environment Deno.test('createLoggingFactory - format is simple for hosted Supabase environment', () => { const hostedEnv = { - SUPABASE_ANON_KEY: 'some-production-key', + SUPABASE_URL: 'https://abc123.supabase.co', }; const factory = createLoggingFactory(hostedEnv); @@ -181,7 +181,7 @@ Deno.test('createLoggingFactory - format is simple for hosted Supabase environme Deno.test('createLoggingFactory - EDGE_WORKER_LOG_FORMAT overrides auto-detection', () => { // Even in local env, explicit format override should win const localEnv = { - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', EDGE_WORKER_LOG_FORMAT: 'simple', }; const factory = createLoggingFactory(localEnv); @@ -191,7 +191,7 @@ Deno.test('createLoggingFactory - EDGE_WORKER_LOG_FORMAT overrides auto-detectio Deno.test('createLoggingFactory - EDGE_WORKER_LOG_FORMAT can set fancy in hosted env', () => { const hostedEnv = { - SUPABASE_ANON_KEY: 'some-production-key', + SUPABASE_URL: 'https://abc123.supabase.co', EDGE_WORKER_LOG_FORMAT: 'fancy', }; const factory = createLoggingFactory(hostedEnv); @@ -201,7 +201,7 @@ Deno.test('createLoggingFactory - EDGE_WORKER_LOG_FORMAT can set fancy in hosted Deno.test('createLoggingFactory - default log level is verbose for local env', () => { const localEnv = { - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', }; const factory = createLoggingFactory(localEnv); @@ -210,7 +210,7 @@ Deno.test('createLoggingFactory - default log level is verbose for local env', ( Deno.test('createLoggingFactory - default log level is info for hosted env', () => { const hostedEnv = { - SUPABASE_ANON_KEY: 'some-production-key', + SUPABASE_URL: 'https://abc123.supabase.co', }; const factory = createLoggingFactory(hostedEnv); @@ -219,7 +219,7 @@ Deno.test('createLoggingFactory - default log level is info for hosted env', () Deno.test('createLoggingFactory - EDGE_WORKER_LOG_LEVEL overrides default', () => { const localEnv = { - SUPABASE_ANON_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0', + SUPABASE_URL: 'http://kong:8000', EDGE_WORKER_LOG_LEVEL: 'debug', }; const factory = createLoggingFactory(localEnv); diff --git a/pkgs/edge-worker/tests/unit/shared/authValidation.test.ts b/pkgs/edge-worker/tests/unit/shared/authValidation.test.ts index 86e2ce90a..cf7398871 100644 --- a/pkgs/edge-worker/tests/unit/shared/authValidation.test.ts +++ b/pkgs/edge-worker/tests/unit/shared/authValidation.test.ts @@ -4,10 +4,6 @@ import { createUnauthorizedResponse, createServerErrorResponse, } from '../../../src/shared/authValidation.ts'; -import { - KNOWN_LOCAL_ANON_KEY, - KNOWN_LOCAL_SERVICE_ROLE_KEY, -} from '../../../src/shared/localDetection.ts'; // ============================================================ // Helper functions @@ -23,14 +19,14 @@ function createRequest(authHeader?: string): Request { function localEnv(): Record { return { - SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY, - SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SERVICE_ROLE_KEY, + SUPABASE_URL: 'http://kong:8000', // Local dev URL + SUPABASE_SERVICE_ROLE_KEY: 'any-key', // Not used for local detection anymore }; } function productionEnv(serviceRoleKey?: string): Record { return { - SUPABASE_ANON_KEY: 'production-anon-key-abc', + SUPABASE_URL: 'https://abc123.supabase.co', // Production URL SUPABASE_SERVICE_ROLE_KEY: serviceRoleKey, }; } @@ -53,8 +49,8 @@ Deno.test('validateServiceRoleAuth - local mode: allows request with wrong auth assertEquals(result, { valid: true }); }); -Deno.test('validateServiceRoleAuth - local mode: allows request with correct auth header', () => { - const request = createRequest(`Bearer ${KNOWN_LOCAL_SERVICE_ROLE_KEY}`); +Deno.test('validateServiceRoleAuth - local mode: allows request with any auth header', () => { + const request = createRequest('Bearer any-key'); const result = validateServiceRoleAuth(request, localEnv()); assertEquals(result, { valid: true }); }); diff --git a/pkgs/edge-worker/tests/unit/shared/localDetection.test.ts b/pkgs/edge-worker/tests/unit/shared/localDetection.test.ts index 0fa654593..d08009972 100644 --- a/pkgs/edge-worker/tests/unit/shared/localDetection.test.ts +++ b/pkgs/edge-worker/tests/unit/shared/localDetection.test.ts @@ -1,136 +1,66 @@ import { assertEquals } from '@std/assert'; -import { - isLocalSupabaseEnv, - KNOWN_LOCAL_ANON_KEY, - KNOWN_LOCAL_SERVICE_ROLE_KEY, - KNOWN_LOCAL_PUBLISHABLE_KEY, - KNOWN_LOCAL_SECRET_KEY, -} from '../../../src/shared/localDetection.ts'; +import { isLocalSupabaseEnv, LOCAL_SUPABASE_HOST } from '../../../src/shared/localDetection.ts'; // ============================================================ -// Constants tests +// LOCAL_SUPABASE_HOST constant // ============================================================ -Deno.test('KNOWN_LOCAL_ANON_KEY - matches expected value', () => { - assertEquals( - KNOWN_LOCAL_ANON_KEY, - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' - ); +Deno.test('LOCAL_SUPABASE_HOST - matches expected value', () => { + assertEquals(LOCAL_SUPABASE_HOST, 'kong:8000'); }); -Deno.test('KNOWN_LOCAL_SERVICE_ROLE_KEY - matches expected value', () => { - assertEquals( - KNOWN_LOCAL_SERVICE_ROLE_KEY, - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU' - ); +// ============================================================ +// isLocalSupabaseEnv() - returns true for local dev +// ============================================================ + +Deno.test('isLocalSupabaseEnv - returns true for http://kong:8000', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: 'http://kong:8000' }), true); }); -Deno.test('KNOWN_LOCAL_PUBLISHABLE_KEY - matches expected value', () => { - assertEquals( - KNOWN_LOCAL_PUBLISHABLE_KEY, - 'sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH' - ); +Deno.test('isLocalSupabaseEnv - returns true for http://kong:8000/ (trailing slash)', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: 'http://kong:8000/' }), true); }); -Deno.test('KNOWN_LOCAL_SECRET_KEY - matches expected value', () => { - assertEquals( - KNOWN_LOCAL_SECRET_KEY, - 'sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz' - ); +Deno.test('isLocalSupabaseEnv - returns true for https://kong:8000 (different protocol)', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: 'https://kong:8000' }), true); }); // ============================================================ -// isLocalSupabaseEnv() tests +// isLocalSupabaseEnv() - returns false for non-local // ============================================================ -Deno.test('isLocalSupabaseEnv - returns true when anon key matches local', () => { - const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY }; - assertEquals(isLocalSupabaseEnv(env), true); -}); - -Deno.test('isLocalSupabaseEnv - returns true when service role key matches local', () => { - const env = { SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SERVICE_ROLE_KEY }; - assertEquals(isLocalSupabaseEnv(env), true); +Deno.test('isLocalSupabaseEnv - returns false for different port', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: 'http://kong:9000' }), false); }); -Deno.test('isLocalSupabaseEnv - returns true when both keys match local', () => { - const env = { - SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY, - SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SERVICE_ROLE_KEY, - }; - assertEquals(isLocalSupabaseEnv(env), true); +Deno.test('isLocalSupabaseEnv - returns true for uppercase KONG (URL normalizes to lowercase)', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: 'http://KONG:8000' }), true); }); -Deno.test('isLocalSupabaseEnv - returns false for non-local keys', () => { - const env = { - SUPABASE_ANON_KEY: 'prod-key', - SUPABASE_SERVICE_ROLE_KEY: 'prod-service-key', - }; - assertEquals(isLocalSupabaseEnv(env), false); -}); - -Deno.test('isLocalSupabaseEnv - returns false for empty env', () => { - assertEquals(isLocalSupabaseEnv({}), false); +Deno.test('isLocalSupabaseEnv - returns false for supabase.co URL', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: 'https://abc123.supabase.co' }), false); }); -Deno.test('isLocalSupabaseEnv - returns false for undefined values', () => { - const env = { - SUPABASE_ANON_KEY: undefined, - SUPABASE_SERVICE_ROLE_KEY: undefined, - }; - assertEquals(isLocalSupabaseEnv(env), false); +Deno.test('isLocalSupabaseEnv - returns false for custom domain', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: 'https://api.myapp.com' }), false); }); -Deno.test('isLocalSupabaseEnv - returns true when only anon key matches (service is prod)', () => { - const env = { - SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY, - SUPABASE_SERVICE_ROLE_KEY: 'prod-service-key', - }; - assertEquals(isLocalSupabaseEnv(env), true); -}); - -Deno.test('isLocalSupabaseEnv - returns true when only service role matches (anon is prod)', () => { - const env = { - SUPABASE_ANON_KEY: 'prod-anon-key', - SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SERVICE_ROLE_KEY, - }; - assertEquals(isLocalSupabaseEnv(env), true); +Deno.test('isLocalSupabaseEnv - returns false for localhost', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: 'http://localhost:54321' }), false); }); // ============================================================ -// New opaque key tests (Supabase CLI v2+) +// isLocalSupabaseEnv() - edge cases // ============================================================ -Deno.test('isLocalSupabaseEnv - returns true when anon key matches new publishable key', () => { - const env = { SUPABASE_ANON_KEY: KNOWN_LOCAL_PUBLISHABLE_KEY }; - assertEquals(isLocalSupabaseEnv(env), true); -}); - -Deno.test('isLocalSupabaseEnv - returns true when service role matches new secret key', () => { - const env = { SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SECRET_KEY }; - assertEquals(isLocalSupabaseEnv(env), true); -}); - -Deno.test('isLocalSupabaseEnv - returns true when both new opaque keys match', () => { - const env = { - SUPABASE_ANON_KEY: KNOWN_LOCAL_PUBLISHABLE_KEY, - SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SECRET_KEY, - }; - assertEquals(isLocalSupabaseEnv(env), true); +Deno.test('isLocalSupabaseEnv - returns false for empty env', () => { + assertEquals(isLocalSupabaseEnv({}), false); }); -Deno.test('isLocalSupabaseEnv - returns true when mixing old JWT anon with new secret key', () => { - const env = { - SUPABASE_ANON_KEY: KNOWN_LOCAL_ANON_KEY, - SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SECRET_KEY, - }; - assertEquals(isLocalSupabaseEnv(env), true); +Deno.test('isLocalSupabaseEnv - returns false for invalid URL', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: 'not-a-url' }), false); }); -Deno.test('isLocalSupabaseEnv - returns true when mixing new publishable with old JWT service role', () => { - const env = { - SUPABASE_ANON_KEY: KNOWN_LOCAL_PUBLISHABLE_KEY, - SUPABASE_SERVICE_ROLE_KEY: KNOWN_LOCAL_SERVICE_ROLE_KEY, - }; - assertEquals(isLocalSupabaseEnv(env), true); +Deno.test('isLocalSupabaseEnv - returns false for undefined SUPABASE_URL', () => { + assertEquals(isLocalSupabaseEnv({ SUPABASE_URL: undefined }), false); }); diff --git a/pkgs/website/src/content/docs/news/pgflow-0-10-0-auto-compilation-and-worker-management.mdx b/pkgs/website/src/content/docs/news/pgflow-0-10-0-auto-compilation-and-worker-management.mdx index 0820fdfb6..f05ee0a7c 100644 --- a/pkgs/website/src/content/docs/news/pgflow-0-10-0-auto-compilation-and-worker-management.mdx +++ b/pkgs/website/src/content/docs/news/pgflow-0-10-0-auto-compilation-and-worker-management.mdx @@ -10,7 +10,7 @@ tags: - auto-compilation - worker-management - developer-experience -featured: false +featured: true --- import { Aside } from "@astrojs/starlight/components"; diff --git a/pkgs/website/src/content/docs/news/pgflow-0-13-0-cli-fix-step-output-storage-for-conditional-execution.mdx b/pkgs/website/src/content/docs/news/pgflow-0-13-1-cli-fix-step-output-storage-for-conditional-execution.mdx similarity index 83% rename from pkgs/website/src/content/docs/news/pgflow-0-13-0-cli-fix-step-output-storage-for-conditional-execution.mdx rename to pkgs/website/src/content/docs/news/pgflow-0-13-1-cli-fix-step-output-storage-for-conditional-execution.mdx index 711de7a04..7a2575c0b 100644 --- a/pkgs/website/src/content/docs/news/pgflow-0-13-0-cli-fix-step-output-storage-for-conditional-execution.mdx +++ b/pkgs/website/src/content/docs/news/pgflow-0-13-1-cli-fix-step-output-storage-for-conditional-execution.mdx @@ -1,6 +1,6 @@ --- -draft: true -title: 'pgflow 0.13.0: CLI Fix + Step Output Storage for Conditional Execution' +draft: false +title: 'pgflow 0.13.1: CLI Fix + Step Output Storage for Conditional Execution' description: 'Fixes local development with recent Supabase CLI versions, plus 2x faster Map chains' date: 2026-01-03 authors: @@ -13,13 +13,17 @@ featured: true import { Aside, Steps } from "@astrojs/starlight/components"; -pgflow 0.13.0 fixes a compatibility issue with recent Supabase CLI versions and introduces atomic step output storage for 2x faster Map chains. + + +pgflow 0.13.1 fixes a compatibility issue with recent Supabase CLI versions and introduces atomic step output storage for 2x faster Map chains. ## Supabase CLI Compatibility Fix **If local development stopped working after a Supabase CLI update, this release fixes it.** -Recent versions of the Supabase CLI transitioned to new publishable/secret API keys instead of the previous JWT-based keys. pgflow now detects both key formats, ensuring local environment detection works correctly regardless of which CLI version you use. +pgflow now detects local development by checking the `SUPABASE_URL` environment variable. When running locally, Supabase CLI sets this to `http://kong:8000` (the Docker-internal API gateway). This is more reliable than the previous key-based detection, which broke when Supabase CLI transitioned to new opaque API keys. ## Step Output Storage