From 58fb03fb3bead80c1e0df1677cbbfe459d328cb6 Mon Sep 17 00:00:00 2001 From: Sandro Circi Date: Wed, 11 Feb 2026 18:38:20 +0100 Subject: [PATCH 1/5] feat(bunny): add new preset --- src/presets/_all.gen.ts | 2 + src/presets/_types.gen.ts | 4 +- src/presets/bunny/preset.ts | 48 +++++++++++++++ src/presets/bunny/runtime/edge-scripting.ts | 20 +++++++ test/presets/bunny.test.ts | 65 +++++++++++++++++++++ test/tests.ts | 2 + 6 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 src/presets/bunny/preset.ts create mode 100644 src/presets/bunny/runtime/edge-scripting.ts create mode 100644 test/presets/bunny.test.ts diff --git a/src/presets/_all.gen.ts b/src/presets/_all.gen.ts index 0aabd48fde..5d6f7a8aca 100644 --- a/src/presets/_all.gen.ts +++ b/src/presets/_all.gen.ts @@ -7,6 +7,7 @@ import _awsAmplify from "./aws-amplify/preset.ts"; import _awsLambda from "./aws-lambda/preset.ts"; import _azure from "./azure/preset.ts"; import _bun from "./bun/preset.ts"; +import _bunny from "./bunny/preset.ts"; import _cleavr from "./cleavr/preset.ts"; import _cloudflare from "./cloudflare/preset.ts"; import _deno from "./deno/preset.ts"; @@ -36,6 +37,7 @@ export default [ ..._awsLambda, ..._azure, ..._bun, + ..._bunny, ..._cleavr, ..._cloudflare, ..._deno, diff --git a/src/presets/_types.gen.ts b/src/presets/_types.gen.ts index 974925a21a..a52a7c9dde 100644 --- a/src/presets/_types.gen.ts +++ b/src/presets/_types.gen.ts @@ -20,6 +20,6 @@ export interface PresetOptions { export const presetsWithConfig = ["awsAmplify","awsLambda","azure","cloudflare","firebase","netlify","vercel"] as const; -export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure-swa" | "base-worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-pages" | "cloudflare-pages-static" | "deno" | "deno-deploy" | "deno-server" | "digital-ocean" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis-handler" | "iis-node" | "koyeb" | "netlify" | "netlify-edge" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-middleware" | "node-server" | "platform-sh" | "render-com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zerops" | "zerops-static"; +export type PresetName = "alwaysdata" | "aws-amplify" | "aws-lambda" | "azure-swa" | "base-worker" | "bun" | "bunny" | "bunny-edge-scripting" | "cleavr" | "cloudflare-dev" | "cloudflare-durable" | "cloudflare-module" | "cloudflare-pages" | "cloudflare-pages-static" | "deno" | "deno-deploy" | "deno-server" | "digital-ocean" | "firebase-app-hosting" | "flight-control" | "genezio" | "github-pages" | "gitlab-pages" | "heroku" | "iis-handler" | "iis-node" | "koyeb" | "netlify" | "netlify-edge" | "netlify-static" | "nitro-dev" | "nitro-prerender" | "node" | "node-cluster" | "node-middleware" | "node-server" | "platform-sh" | "render-com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "winterjs" | "zeabur" | "zeabur-static" | "zerops" | "zerops-static"; -export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "cleavr" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "netlify" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-middleware" | "nodeMiddleware" | "node_middleware" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {}); +export type PresetNameInput = "alwaysdata" | "aws-amplify" | "awsAmplify" | "aws_amplify" | "aws-lambda" | "awsLambda" | "aws_lambda" | "azure-swa" | "azureSwa" | "azure_swa" | "base-worker" | "baseWorker" | "base_worker" | "bun" | "bunny" | "bunny-edge-scripting" | "bunnyEdgeScripting" | "bunny_edge_scripting" | "cleavr" | "cloudflare-dev" | "cloudflareDev" | "cloudflare_dev" | "cloudflare-durable" | "cloudflareDurable" | "cloudflare_durable" | "cloudflare-module" | "cloudflareModule" | "cloudflare_module" | "cloudflare-pages" | "cloudflarePages" | "cloudflare_pages" | "cloudflare-pages-static" | "cloudflarePagesStatic" | "cloudflare_pages_static" | "deno" | "deno-deploy" | "denoDeploy" | "deno_deploy" | "deno-server" | "denoServer" | "deno_server" | "digital-ocean" | "digitalOcean" | "digital_ocean" | "firebase-app-hosting" | "firebaseAppHosting" | "firebase_app_hosting" | "flight-control" | "flightControl" | "flight_control" | "genezio" | "github-pages" | "githubPages" | "github_pages" | "gitlab-pages" | "gitlabPages" | "gitlab_pages" | "heroku" | "iis-handler" | "iisHandler" | "iis_handler" | "iis-node" | "iisNode" | "iis_node" | "koyeb" | "netlify" | "netlify-edge" | "netlifyEdge" | "netlify_edge" | "netlify-static" | "netlifyStatic" | "netlify_static" | "nitro-dev" | "nitroDev" | "nitro_dev" | "nitro-prerender" | "nitroPrerender" | "nitro_prerender" | "node" | "node-cluster" | "nodeCluster" | "node_cluster" | "node-middleware" | "nodeMiddleware" | "node_middleware" | "node-server" | "nodeServer" | "node_server" | "platform-sh" | "platformSh" | "platform_sh" | "render-com" | "renderCom" | "render_com" | "standard" | "static" | "stormkit" | "vercel" | "vercel-static" | "vercelStatic" | "vercel_static" | "winterjs" | "zeabur" | "zeabur-static" | "zeaburStatic" | "zeabur_static" | "zerops" | "zerops-static" | "zeropsStatic" | "zerops_static" | (string & {}); diff --git a/src/presets/bunny/preset.ts b/src/presets/bunny/preset.ts new file mode 100644 index 0000000000..9a27dafff9 --- /dev/null +++ b/src/presets/bunny/preset.ts @@ -0,0 +1,48 @@ +import { defineNitroPreset } from "../_utils/preset.ts"; +import { builtinModules } from "node:module"; +import { rm } from "node:fs/promises"; + +const edgeScripting = defineNitroPreset( + { + entry: "./bunny/runtime/edge-scripting.ts", + + exportConditions: ["deno"], + commands: { + preview: "deno -A ./bunny-edge-scripting.mjs", + }, + + output: { + dir: "{{ rootDir }}/.output", + serverDir: "{{ output.dir }}", + publicDir: "{{ output.dir }}/public", + }, + + rollupConfig: { + output: { + format: "esm", + entryFileNames: "bunny-edge-scripting.mjs", + inlineDynamicImports: true, + hoistTransitiveImports: false, + }, + external: (id: string) => + id.startsWith("https://") || id.startsWith("node:") || builtinModules.includes(id), + }, + + serveStatic: "inline", + minify: true, + + hooks: { + async compiled(nitro) { + const publicDir = nitro.options.output.publicDir; + // remove the public dir and all its files, as they are inlined in the server bundle + await rm(publicDir, { recursive: true, force: true }); + }, + }, + }, + { + aliases: ["bunny"], + name: "bunny-edge-scripting" as const, + } +); + +export default [edgeScripting] as const; diff --git a/src/presets/bunny/runtime/edge-scripting.ts b/src/presets/bunny/runtime/edge-scripting.ts new file mode 100644 index 0000000000..993da94be1 --- /dev/null +++ b/src/presets/bunny/runtime/edge-scripting.ts @@ -0,0 +1,20 @@ +import "#nitro/virtual/polyfills"; +import { useNitroApp } from "nitro/app"; + +const nitroApp = useNitroApp(); + +// @ts-expect-error +if (typeof Bunny !== "undefined") { + // @ts-expect-error + Bunny.v1.serve(nitroApp.fetch); +} else { + const _parsedPort = Number.parseInt(process.env.NITRO_PORT ?? process.env.PORT ?? ""); + + Deno.serve( + { + port: Number.isNaN(_parsedPort) ? 3000 : _parsedPort, + hostname: process.env.NITRO_HOST || process.env.HOST, + }, + nitroApp.fetch + ); +} diff --git a/test/presets/bunny.test.ts b/test/presets/bunny.test.ts new file mode 100644 index 0000000000..5ede6ca0f4 --- /dev/null +++ b/test/presets/bunny.test.ts @@ -0,0 +1,65 @@ +import { promises as fsp } from "node:fs"; +import { execa, execaCommandSync } from "execa"; +import { getRandomPort, waitForPort } from "get-port-please"; +import { resolve } from "pathe"; +import { describe, expect, it } from "vitest"; +import { setupTest, testNitro } from "../tests.ts"; + +const hasDeno = + execaCommandSync("deno --version", { stdio: "ignore", reject: false }).exitCode === 0; + +describe.runIf(hasDeno)("nitro:preset:bunny", async () => { + const ctx = await setupTest("bunny-edge-scripting"); + + testNitro(ctx, async () => { + const port = await getRandomPort(); + execa("deno", ["run", "-A", "./bunny-edge-scripting.mjs"], { + cwd: ctx.outDir, + stdio: "ignore", + env: { + NITRO_PORT: String(port), + NITRO_HOST: "127.0.0.1", + }, + }); + ctx.server = { + url: `http://127.0.0.1:${port}`, + close: () => { + // Process cleanup handled by test teardown + }, + } as any; + await waitForPort(port, { delay: 1000, retries: 20, host: "127.0.0.1" }); + return async ({ url, ...opts }) => { + const res = await ctx.fetch(url, opts); + return res; + }; + }); + + it("should generate the bunny-edge-scripting.mjs file", async () => { + const serverFiles = await fsp.readdir(resolve(ctx.outDir)); + expect(serverFiles).toContain("bunny-edge-scripting.mjs"); + }); + + it("should not have a separate server directory", async () => { + const serverFiles = await fsp.readdir(resolve(ctx.outDir)); + expect(serverFiles).not.toContain("server"); + }); + + it("should not have a public directory (assets should be inlined)", async () => { + const serverFiles = await fsp.readdir(resolve(ctx.outDir)); + expect(serverFiles).not.toContain("public"); + }); + + it("should have minified output", async () => { + const entry = await fsp.readFile(resolve(ctx.outDir, "bunny-edge-scripting.mjs"), "utf8"); + // Check file is actually minified - should have very few newlines relative to size + const newlineCount = (entry.match(/\n/g) || []).length; + const ratio = entry.length / Math.max(1, newlineCount); + // Fixture is small, so we expect a high ratio of characters to newlines in minified code + expect(ratio).toBeGreaterThan(500); + }); + + it("should contain the Bunny.v1.serve call", async () => { + const entry = await fsp.readFile(resolve(ctx.outDir, "bunny-edge-scripting.mjs"), "utf8"); + expect(entry).toContain("Bunny"); + }); +}); diff --git a/test/tests.ts b/test/tests.ts index 684406edc3..aaa3a30aa9 100644 --- a/test/tests.ts +++ b/test/tests.ts @@ -77,6 +77,7 @@ export async function setupTest( preset, isDev: preset === "nitro-dev", isWorker: [ + "bunny-edge-scripting", "cloudflare-worker", "cloudflare-module", "cloudflare-module-legacy", @@ -641,6 +642,7 @@ export function testNitro( // TODO: Investigate ctx.preset === "bun" || ctx.preset === "deno-server" || + ctx.preset === "bunny-edge-scripting" || ctx.preset === "nitro-dev" )("sourcemap works", async () => { const { data } = await callHandler({ url: "/error-stack" }); From aa35ab584fcb5baa6c4c3bfc90824d67ee2fcd11 Mon Sep 17 00:00:00 2001 From: Sandro Circi Date: Wed, 11 Feb 2026 19:17:40 +0100 Subject: [PATCH 2/5] fix(bunny): preset entry --- src/presets/bunny/preset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/presets/bunny/preset.ts b/src/presets/bunny/preset.ts index 9a27dafff9..1cec323763 100644 --- a/src/presets/bunny/preset.ts +++ b/src/presets/bunny/preset.ts @@ -4,7 +4,7 @@ import { rm } from "node:fs/promises"; const edgeScripting = defineNitroPreset( { - entry: "./bunny/runtime/edge-scripting.ts", + entry: "./bunny/runtime/edge-scripting", exportConditions: ["deno"], commands: { From e8c3aa03b1af03c359a2634596e624de36940b17 Mon Sep 17 00:00:00 2001 From: Sandro Circi Date: Wed, 11 Feb 2026 19:39:32 +0100 Subject: [PATCH 3/5] test(bunny): properly kill spawned test server --- test/presets/bunny.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/presets/bunny.test.ts b/test/presets/bunny.test.ts index 5ede6ca0f4..b9e00e0f73 100644 --- a/test/presets/bunny.test.ts +++ b/test/presets/bunny.test.ts @@ -13,7 +13,7 @@ describe.runIf(hasDeno)("nitro:preset:bunny", async () => { testNitro(ctx, async () => { const port = await getRandomPort(); - execa("deno", ["run", "-A", "./bunny-edge-scripting.mjs"], { + const p = execa("deno", ["run", "-A", "./bunny-edge-scripting.mjs"], { cwd: ctx.outDir, stdio: "ignore", env: { @@ -23,9 +23,7 @@ describe.runIf(hasDeno)("nitro:preset:bunny", async () => { }); ctx.server = { url: `http://127.0.0.1:${port}`, - close: () => { - // Process cleanup handled by test teardown - }, + close: () => p.kill(), } as any; await waitForPort(port, { delay: 1000, retries: 20, host: "127.0.0.1" }); return async ({ url, ...opts }) => { From ae8ae090b9cd3238d209562144b29ea3a4921d5a Mon Sep 17 00:00:00 2001 From: Sandro Circi Date: Wed, 11 Feb 2026 20:28:31 +0100 Subject: [PATCH 4/5] fix(bunny): support `serveStatic: false` --- src/presets/bunny/preset.ts | 20 ++++++++++++++++---- test/presets/bunny.test.ts | 7 ++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/presets/bunny/preset.ts b/src/presets/bunny/preset.ts index 1cec323763..6925bbda15 100644 --- a/src/presets/bunny/preset.ts +++ b/src/presets/bunny/preset.ts @@ -1,4 +1,5 @@ import { defineNitroPreset } from "../_utils/preset.ts"; +import type { Nitro } from "nitro/types"; import { builtinModules } from "node:module"; import { rm } from "node:fs/promises"; @@ -32,10 +33,21 @@ const edgeScripting = defineNitroPreset( minify: true, hooks: { - async compiled(nitro) { - const publicDir = nitro.options.output.publicDir; - // remove the public dir and all its files, as they are inlined in the server bundle - await rm(publicDir, { recursive: true, force: true }); + "build:before": (nitro: Nitro) => { + if (nitro.options.serveStatic !== "inline" && nitro.options.serveStatic !== false) { + nitro.options.serveStatic = "inline"; + nitro.logger.warn( + "Bunny Edge Scripting preset requires `serveStatic` to be `inline` or `false`. Overriding to `inline`." + ) + } + }, + async compiled(nitro: Nitro) { + // Remove public dir when inlined, usecase is for + // managing assets directly in Bunny Storage + if (nitro.options.serveStatic === "inline") { + const publicDir = nitro.options.output.publicDir; + await rm(publicDir, { recursive: true, force: true }); + } }, }, }, diff --git a/test/presets/bunny.test.ts b/test/presets/bunny.test.ts index b9e00e0f73..85cf605cb1 100644 --- a/test/presets/bunny.test.ts +++ b/test/presets/bunny.test.ts @@ -44,7 +44,12 @@ describe.runIf(hasDeno)("nitro:preset:bunny", async () => { it("should not have a public directory (assets should be inlined)", async () => { const serverFiles = await fsp.readdir(resolve(ctx.outDir)); - expect(serverFiles).not.toContain("public"); + if (ctx.nitro?.options.serveStatic === "inline") { + expect(serverFiles).not.toContain("public"); + } + else { + expect(serverFiles).toContain("public"); + } }); it("should have minified output", async () => { From 8006e457e83fff50f657f3df615e5d7ae575fcae Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 19:29:12 +0000 Subject: [PATCH 5/5] chore: apply automated updates --- src/presets/bunny/preset.ts | 2 +- test/presets/bunny.test.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/presets/bunny/preset.ts b/src/presets/bunny/preset.ts index 6925bbda15..b822d9f143 100644 --- a/src/presets/bunny/preset.ts +++ b/src/presets/bunny/preset.ts @@ -38,7 +38,7 @@ const edgeScripting = defineNitroPreset( nitro.options.serveStatic = "inline"; nitro.logger.warn( "Bunny Edge Scripting preset requires `serveStatic` to be `inline` or `false`. Overriding to `inline`." - ) + ); } }, async compiled(nitro: Nitro) { diff --git a/test/presets/bunny.test.ts b/test/presets/bunny.test.ts index 85cf605cb1..b7feec494c 100644 --- a/test/presets/bunny.test.ts +++ b/test/presets/bunny.test.ts @@ -46,8 +46,7 @@ describe.runIf(hasDeno)("nitro:preset:bunny", async () => { const serverFiles = await fsp.readdir(resolve(ctx.outDir)); if (ctx.nitro?.options.serveStatic === "inline") { expect(serverFiles).not.toContain("public"); - } - else { + } else { expect(serverFiles).toContain("public"); } });