From 513ea18f74147d174e0ab44ef61680ad3f84392b Mon Sep 17 00:00:00 2001 From: ChrisJr404 Date: Tue, 5 May 2026 14:40:19 -0400 Subject: [PATCH] fix(functions): edge-runtime template offline bootstrap The embedded edge-runtime entrypoint imported two std modules from deno.land at the top of the file. Deno's graph builder fetches those URLs every time the worker boots, so `supabase start` cannot bootstrap the runtime container without internet access. Inline the small surface that the template actually uses (5 status codes + 1 status-text lookup, plus posix.dirname/join/toFileUrl) and drop the remote imports. The local posix shim was checked against node:path.posix for the path shapes this file constructs. Add a regression-pin test that fails if any HTTPS deno.land/esm.sh/ cdn.jsdelivr/unpkg import is reintroduced into templates/main.ts. Closes supabase/supabase#45570 --- internal/functions/serve/serve_test.go | 16 +++++ internal/functions/serve/templates/main.ts | 79 +++++++++++++++++++++- 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/internal/functions/serve/serve_test.go b/internal/functions/serve/serve_test.go index 95b3a91fdb..bcbb2e0874 100644 --- a/internal/functions/serve/serve_test.go +++ b/internal/functions/serve/serve_test.go @@ -128,6 +128,22 @@ func TestServeCommand(t *testing.T) { }) } +func TestMainTemplateNoRemoteImports(t *testing.T) { + // Regression pin for supabase/supabase#45570: the embedded edge-runtime + // entrypoint must not re-introduce remote imports, otherwise `supabase + // start` cannot bootstrap the worker container offline. + for _, scheme := range []string{ + "https://deno.land/", + "http://deno.land/", + "https://esm.sh/", + "https://cdn.jsdelivr.net/", + "https://unpkg.com/", + } { + assert.NotContains(t, mainFuncEmbed, scheme, + "templates/main.ts must not import from %s (offline boot regression)", scheme) + } +} + func TestServeFunctions(t *testing.T) { require.NoError(t, utils.Config.Load("testdata/config.toml", testdata)) utils.UpdateDockerIds() diff --git a/internal/functions/serve/templates/main.ts b/internal/functions/serve/templates/main.ts index bac39b39eb..38f686a179 100644 --- a/internal/functions/serve/templates/main.ts +++ b/internal/functions/serve/templates/main.ts @@ -1,8 +1,81 @@ -import { STATUS_CODE, STATUS_TEXT } from "https://deno.land/std/http/status.ts"; -import * as posix from "https://deno.land/std/path/posix/mod.ts"; - import * as jose from "jsr:@panva/jose@6"; +// Inlined from std/http/status to avoid remote-import resolution at boot, +// which fails when `supabase start` runs offline (no network reachable from +// the edge-runtime container). Only the values referenced below are kept. +// Ref: supabase/supabase#45570 +const STATUS_CODE = { + OK: 200, + Unauthorized: 401, + NotFound: 404, + InternalServerError: 500, + ServiceUnavailable: 503, +} as const; + +const STATUS_TEXT: Record = { + [STATUS_CODE.OK]: "OK", + [STATUS_CODE.Unauthorized]: "Unauthorized", + [STATUS_CODE.NotFound]: "Not Found", + [STATUS_CODE.InternalServerError]: "Internal Server Error", + [STATUS_CODE.ServiceUnavailable]: "Service Unavailable", +}; + +// Inlined from std/path/posix. Same offline-boot reason as above. +// Only the three helpers used by this file are reproduced. +const posix = { + dirname(path: string): string { + if (path.length === 0) return "."; + let end = -1; + let matched = false; + for (let i = path.length - 1; i >= 1; --i) { + if (path.charCodeAt(i) === 0x2f /* / */) { + if (matched) { + end = i; + break; + } + } else { + matched = true; + } + } + if (end === -1) return path.charCodeAt(0) === 0x2f ? "/" : "."; + if (end === 0 && path.charCodeAt(0) === 0x2f) return "/"; + return path.slice(0, end); + }, + join(...segments: string[]): string { + if (segments.length === 0) return "."; + let joined = ""; + for (const segment of segments) { + if (segment.length > 0) { + joined = joined.length === 0 ? segment : `${joined}/${segment}`; + } + } + if (joined.length === 0) return "."; + // Normalize duplicate slashes and `.`/`..` segments while keeping a leading slash. + const isAbsolute = joined.charCodeAt(0) === 0x2f; + const parts: string[] = []; + for (const part of joined.split("/")) { + if (part === "" || part === ".") continue; + if (part === "..") { + if (parts.length > 0 && parts[parts.length - 1] !== "..") parts.pop(); + else if (!isAbsolute) parts.push(".."); + continue; + } + parts.push(part); + } + let result = parts.join("/"); + if (isAbsolute) result = "/" + result; + return result.length === 0 ? (isAbsolute ? "/" : ".") : result; + }, + toFileUrl(path: string): URL { + if (path.charCodeAt(0) !== 0x2f) { + throw new TypeError(`Path must be absolute: received "${path}"`); + } + const url = new URL("file:///"); + url.pathname = encodeURI(path).replace(/[?#]/g, encodeURIComponent); + return url; + }, +}; + const SB_SPECIFIC_ERROR_CODE = { BootError: STATUS_CODE.ServiceUnavailable, /** Service Unavailable (RFC 7231, 6.6.4) */