From 104313268c186e8426a4b9e0fafd207a98ac11f7 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Thu, 9 Jul 2026 16:06:56 +0200 Subject: [PATCH 1/2] feat(cli): scaffold fetch-style edge functions --- .../commands/functions/new/new.handler.ts | 32 ++++++++++++------- .../functions/new/new.integration.test.ts | 31 ++++++++++++------ packages/config/src/functions-manifest.ts | 1 + packages/config/src/index.ts | 1 + 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/apps/cli/src/next/commands/functions/new/new.handler.ts b/apps/cli/src/next/commands/functions/new/new.handler.ts index c5c2c1f09c..184596fa99 100644 --- a/apps/cli/src/next/commands/functions/new/new.handler.ts +++ b/apps/cli/src/next/commands/functions/new/new.handler.ts @@ -1,7 +1,7 @@ import { dirname } from "node:path"; import { - edgeFunctionDenoConfigFileName, edgeFunctionEntrypointFileName, + edgeFunctionPackageManifestFileName, edgeFunctionsDirectoryName, findProjectPaths, } from "@supabase/config"; @@ -16,20 +16,28 @@ import { const functionSlugPattern = /^[A-Za-z0-9_-]+$/; -const denoJson = `${JSON.stringify( +const packageJson = `${JSON.stringify( { - imports: { - "@supabase/functions-js": "jsr:@supabase/functions-js@^2", + private: true, + type: "module", + dependencies: { + nanoid: "^5.1.16", }, }, null, 2, )}\n`; -const entrypointSource = `Deno.serve(async (req) => { - const { name } = await req.json(); - return Response.json({ message: \`Hello \${name}!\` }); -}); +const entrypointSource = `import { nanoid } from "nanoid"; + +export default { + async fetch(request: Request): Promise { + const name = new URL(request.url).searchParams.get("name") ?? nanoid(); + return new Response(\`Hello \${name}\`, { + headers: { "content-type": "text/plain" }, + }); + }, +}; `; function validateSlugMessage(slug: string): string | undefined { @@ -91,7 +99,7 @@ export const functionsNew = Effect.fnUntraced(function* (slugInput: Option.Optio projectPaths === null ? runtimeInfo.cwd : projectRootForConfigPath(projectPaths.configPath); const functionDir = path.join(projectRoot, "supabase", edgeFunctionsDirectoryName, slug); const entrypointPath = path.join(functionDir, edgeFunctionEntrypointFileName); - const denoConfigPath = path.join(functionDir, edgeFunctionDenoConfigFileName); + const packageManifestPath = path.join(functionDir, edgeFunctionPackageManifestFileName); if (yield* fs.exists(entrypointPath)) { return yield* Effect.fail( @@ -104,15 +112,15 @@ export const functionsNew = Effect.fnUntraced(function* (slugInput: Option.Optio yield* fs.makeDirectory(functionDir, { recursive: true }); yield* fs.writeFileString(entrypointPath, entrypointSource); - if (!(yield* fs.exists(denoConfigPath))) { - yield* fs.writeFileString(denoConfigPath, denoJson); + if (!(yield* fs.exists(packageManifestPath))) { + yield* fs.writeFileString(packageManifestPath, packageJson); } yield* output.success("Created Edge Function.", { function_slug: slug, function_dir: functionDir, entrypoint_path: entrypointPath, - deno_config_path: denoConfigPath, + package_manifest_path: packageManifestPath, }); yield* output.outro(`Created ${path.join("supabase", edgeFunctionsDirectoryName, slug)}.`); }); diff --git a/apps/cli/src/next/commands/functions/new/new.integration.test.ts b/apps/cli/src/next/commands/functions/new/new.integration.test.ts index 7396813785..f9a4d75a8f 100644 --- a/apps/cli/src/next/commands/functions/new/new.integration.test.ts +++ b/apps/cli/src/next/commands/functions/new/new.integration.test.ts @@ -98,22 +98,33 @@ describe("functions new", () => { yield* Effect.tryPromise(() => readFile(join(tempDir, "supabase", "functions", "hello-world", "index.ts"), "utf8"), ), - ).toBe(`Deno.serve(async (req) => { - const { name } = await req.json(); - return Response.json({ message: \`Hello \${name}!\` }); -}); + ).toBe(`import { nanoid } from "nanoid"; + +export default { + async fetch(request: Request): Promise { + const name = new URL(request.url).searchParams.get("name") ?? nanoid(); + return new Response(\`Hello \${name}\`, { + headers: { "content-type": "text/plain" }, + }); + }, +}; `); expect( JSON.parse( yield* Effect.tryPromise(() => - readFile(join(tempDir, "supabase", "functions", "hello-world", "deno.json"), "utf8"), + readFile(join(tempDir, "supabase", "functions", "hello-world", "package.json"), "utf8"), ), ), ).toEqual({ - imports: { - "@supabase/functions-js": "jsr:@supabase/functions-js@^2", + private: true, + type: "module", + dependencies: { + nanoid: "^5.1.16", }, }); + expect(existsSync(join(tempDir, "supabase", "functions", "hello-world", "deno.json"))).toBe( + false, + ); expect(out.messages).toContainEqual( expect.objectContaining({ type: "success", message: "Created Edge Function." }), ); @@ -151,7 +162,7 @@ describe("functions new", () => { yield* Effect.tryPromise(() => readFile(join(tempDir, "supabase", "functions", "hello-world", "index.ts"), "utf8"), ), - ).toContain("Deno.serve"); + ).toContain("export default"); }).pipe( Effect.ensuring(Effect.tryPromise(() => rm(tempDir, { recursive: true, force: true }))), ); @@ -175,7 +186,7 @@ describe("functions new", () => { yield* Effect.tryPromise(() => readFile(join(tempDir, "supabase", "functions", "hello-world", "index.ts"), "utf8"), ), - ).toContain("Deno.serve"); + ).toContain("export default"); }).pipe( Effect.ensuring(Effect.tryPromise(() => rm(tempDir, { recursive: true, force: true }))), ); @@ -239,7 +250,7 @@ describe("functions new", () => { yield* Effect.tryPromise(() => readFile(join(tempDir, "supabase", "functions", "hello-world", "index.ts"), "utf8"), ), - ).toContain("Deno.serve"); + ).toContain("export default"); }).pipe( Effect.ensuring(Effect.tryPromise(() => rm(tempDir, { recursive: true, force: true }))), ); diff --git a/packages/config/src/functions-manifest.ts b/packages/config/src/functions-manifest.ts index 63322169fa..576d5ba9d6 100644 --- a/packages/config/src/functions-manifest.ts +++ b/packages/config/src/functions-manifest.ts @@ -10,6 +10,7 @@ const emptyConfig = decodeProjectConfig({}); export const edgeFunctionsDirectoryName = "functions"; export const edgeFunctionEntrypointFileName = "index.ts"; export const edgeFunctionDenoConfigFileName = "deno.json"; +export const edgeFunctionPackageManifestFileName = "package.json"; export interface ResolvedFunctionConfig { readonly enabled: boolean; diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 77af2064ae..9124f702bc 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -22,6 +22,7 @@ export { export { edgeFunctionDenoConfigFileName, edgeFunctionEntrypointFileName, + edgeFunctionPackageManifestFileName, edgeFunctionsDirectoryName, type FunctionsManifest, type ResolvedFunctionConfig, From c67c12bc841706ea9f0ef09ebbfc995adc60abd6 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Thu, 9 Jul 2026 16:06:56 +0200 Subject: [PATCH 2/2] feat(cli): scaffold fetch-style edge functions --- .../src/next/commands/functions/new/new.handler.ts | 12 ++++-------- .../commands/functions/new/new.integration.test.ts | 12 ++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/apps/cli/src/next/commands/functions/new/new.handler.ts b/apps/cli/src/next/commands/functions/new/new.handler.ts index 184596fa99..39076f6f6a 100644 --- a/apps/cli/src/next/commands/functions/new/new.handler.ts +++ b/apps/cli/src/next/commands/functions/new/new.handler.ts @@ -20,20 +20,16 @@ const packageJson = `${JSON.stringify( { private: true, type: "module", - dependencies: { - nanoid: "^5.1.16", - }, + dependencies: {}, }, null, 2, )}\n`; -const entrypointSource = `import { nanoid } from "nanoid"; - -export default { +const entrypointSource = `export default { async fetch(request: Request): Promise { - const name = new URL(request.url).searchParams.get("name") ?? nanoid(); - return new Response(\`Hello \${name}\`, { + const name = new URL(request.url).searchParams.get("name") ?? "World"; + return new Response(\`Hello \${name}!\`, { headers: { "content-type": "text/plain" }, }); }, diff --git a/apps/cli/src/next/commands/functions/new/new.integration.test.ts b/apps/cli/src/next/commands/functions/new/new.integration.test.ts index f9a4d75a8f..19fb448f7a 100644 --- a/apps/cli/src/next/commands/functions/new/new.integration.test.ts +++ b/apps/cli/src/next/commands/functions/new/new.integration.test.ts @@ -98,12 +98,10 @@ describe("functions new", () => { yield* Effect.tryPromise(() => readFile(join(tempDir, "supabase", "functions", "hello-world", "index.ts"), "utf8"), ), - ).toBe(`import { nanoid } from "nanoid"; - -export default { + ).toBe(`export default { async fetch(request: Request): Promise { - const name = new URL(request.url).searchParams.get("name") ?? nanoid(); - return new Response(\`Hello \${name}\`, { + const name = new URL(request.url).searchParams.get("name") ?? "World"; + return new Response(\`Hello \${name}!\`, { headers: { "content-type": "text/plain" }, }); }, @@ -118,9 +116,7 @@ export default { ).toEqual({ private: true, type: "module", - dependencies: { - nanoid: "^5.1.16", - }, + dependencies: {}, }); expect(existsSync(join(tempDir, "supabase", "functions", "hello-world", "deno.json"))).toBe( false,