Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/build/vite/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,7 @@ export async function reloadEnvRunner(ctx: NitroPluginContext) {
}

async function _loadRunner(ctx: NitroPluginContext, manager: RunnerManager) {
const runnerName = (ctx.nitro!.options.devServer.runner ||
process.env.NITRO_DEV_RUNNER ||
"node-worker") as RunnerName;
const runnerName = getDevRunnerName(ctx);
const entry = resolve(runtimeDir, "internal/vite/dev-worker.mjs");
let runner;
if (runnerName === "miniflare") {
Expand Down Expand Up @@ -233,9 +231,7 @@ export function nitroServiceProxy(): VitePlugin {
// workerd-based runners (miniflare) cannot handle CJS externals via import(),
// so all dependencies must be processed through Vite's transform pipeline.
function _isWorkerdRunner(ctx: NitroPluginContext): boolean {
const runnerName =
ctx.nitro!.options.devServer.runner || process.env.NITRO_DEV_RUNNER || "node-worker";
return runnerName === "miniflare";
return getDevRunnerName(ctx) === "miniflare";
}

function tryResolve(id: string) {
Expand All @@ -249,3 +245,15 @@ function tryResolve(id: string) {
});
return resolved || id;
}

function getDevRunnerName(ctx: NitroPluginContext): RunnerName {
return (ctx.nitro!.options.devServer.runner ||
process.env.NITRO_DEV_RUNNER ||
getDefaultDevRunnerName()) as RunnerName;
}

function getDefaultDevRunnerName(): RunnerName {
return typeof (globalThis as typeof globalThis & { Bun?: unknown }).Bun === "undefined"
? "node-worker"
: "bun-process";
}
Comment on lines +255 to +259

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor | ⚑ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check env-runner for valid runner names and bun-process support

# Search for RunnerName type definition or valid runner strings
rg -n -C5 'bun-process|RunnerName|type.*Runner.*=' --type=ts node_modules/env-runner/

# Check for bun-process runner implementation
fd -e ts -e js bun node_modules/env-runner/ --exec cat {}

Repository: nitrojs/nitro

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Repo-wide search for bun-process / RunnerName / env-runner
rg -n --hidden --no-ignore-vcs "bun-process" . || true
rg -n --hidden --no-ignore-vcs "RunnerName" . || true
rg -n --hidden --no-ignore-vcs "env-runner" . || true

# Identify env-runner version from package manifests/lockfiles
for f in package.json pnpm-lock.yaml package-lock.json yarn.lock; do
  if [ -f "$f" ]; then
    echo "---- $f ----"
    rg -n "env-runner" "$f" || true
  fi
done

Repository: nitrojs/nitro

Length of output: 4259


🌐 Web query:

unjs env-runner 0.1.9 RunnerName bun-process

πŸ’‘ Result:

The BunProcessEnvRunner is a specific runner class provided by the unjs/env-runner library designed to execute server applications using the Bun runtime or a Node.js process [1][2]. Key details regarding the runner: Isolation: It operates by spawning a child process using Bun or Node.js [1][2]. IPC Mechanism: It utilizes Bun's spawn IPC or Node.js's fork mechanism for inter-process communication [1][2]. Usage: It is one of the available runners supported by the env-runner package, which can be selected via the --runner bun-process CLI flag or imported directly from env-runner/runners/bun-process [2]. The env-runner library is a generic environment runner that supports various runtimes and deployment contexts, including Node.js (worker threads and child processes), Bun, Deno, Cloudflare Workers (via miniflare), Vercel, and Netlify [1][3]. As of June 2026, the project continues to receive updates to its runner implementations [3][4].

Citations:


🌐 Web query:

env-runner@0.1.9 RunnerName "bun-process" type

πŸ’‘ Result:

In the env-runner package, "bun-process" is a valid member of the RunnerName type [1][2]. It refers to a runner that executes applications in a separate process using the Bun runtime, utilizing Bun.spawn for IPC (Inter-Process Communication) or Node.js-style fork mechanisms [3][4][5]. The RunnerName type is a union of strings used to specify which environment runner should be utilized [1][2]. As of the current version, the available runners include: - node-worker [4][5] - node-process [4][5] - bun-process [4][5] - deno-process [4][5] - self [4][5] - miniflare [4][5] - vercel [1] - netlify [1] Users can select a runner via the --runner flag in the CLI or through configuration options in tools like Nitro [4][5][2]. The "bun-process" runner specifically provides isolation by running the target application as a child process with the Bun runtime [3][4].

Citations:


Remove duplicated getDefaultDevRunnerName() helper to prevent drift.

getDefaultDevRunnerName() is defined identically in both src/build/vite/env.ts (lines 255-259) and src/dev/server.ts (lines 254-258). Extract it into a shared internal utility and import it in both places to avoid divergence.
"bun-process" is a valid RunnerName in the repo’s env-runner@^0.1.9 dependency, so no change is needed there.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/build/vite/env.ts` around lines 255 - 259, There are two identical
implementations of getDefaultDevRunnerName() causing duplication; extract this
helper into a single internal utility (e.g., create a new module like
internal/getDefaultDevRunnerName or utils/devRunner) that exports
getDefaultDevRunnerName, update both locations that currently define the
function (the Vite env module and the dev server module) to import and use the
shared function, and remove the local definitions so both modules call the same
exported getDefaultDevRunnerName; keep the implementation exactly as-is
(checking globalThis.Bun) and ensure the import is typed as RunnerName where
used.

10 changes: 9 additions & 1 deletion src/dev/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ export class NitroDevServer extends NitroDevApp implements RunnerRPCHooks {

async #reload() {
const runnerName =
this.nitro.options.devServer.runner || process.env.NITRO_DEV_RUNNER || "node-worker";
this.nitro.options.devServer.runner ||
process.env.NITRO_DEV_RUNNER ||
getDefaultDevRunnerName();
const runner = await loadRunner(runnerName as RunnerName, {
name: `Nitro_${this.#workerIdCtr++}`,
data: { entry: this.#entry, ...this.#workerData },
Expand Down Expand Up @@ -248,3 +250,9 @@ export class NitroDevServer extends NitroDevApp implements RunnerRPCHooks {

// #endregion
}

function getDefaultDevRunnerName(): RunnerName {
return typeof (globalThis as typeof globalThis & { Bun?: unknown }).Bun === "undefined"
? "node-worker"
: "bun-process";
}
32 changes: 28 additions & 4 deletions src/runtime/internal/vite/dev-worker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,15 @@ globalThis.__transform_html__ = async function (html) {

// ----- Exports (env-runner AppEntry) -----

export function fetch(req) {
export async function fetch(req) {
const viteEnv = req?.headers.get("x-vite-env") || "nitro";
const env = envs[viteEnv];
const env = await waitForViteEnv(viteEnv);
if (!env) {
return renderError(req, httpError(500, `Unknown vite environment "${viteEnv}"`));
return normalizeBunResponse(
await renderError(req, httpError(500, `Unknown vite environment "${viteEnv}"`))
);
}
return env.fetch(req);
return normalizeBunResponse(await env.fetch(req));
}

export function upgrade(context) {
Expand Down Expand Up @@ -211,6 +213,28 @@ function httpError(status, message) {
return error;
}

async function waitForViteEnv(name) {
let env = envs[name];
for (let i = 0; i < 5 && !env; i++) {
await new Promise((resolve) => setTimeout(resolve, 100 * Math.pow(2, i)));
env = envs[name];
}
return env;
}

export function normalizeBunResponse(response) {
if (typeof Bun === "undefined") {
return response;
}
const headers = new Headers(response.headers);
headers.delete("content-length");
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
}

async function renderError(req, error) {
if (req.headers.get("accept")?.includes("application/json")) {
return new Response(
Expand Down
34 changes: 34 additions & 0 deletions test/unit/vite-dev-worker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expect, it, vi } from "vitest";

const { normalizeBunResponse } = await import("../../src/runtime/internal/vite/dev-worker.mjs");

describe("vite dev worker", () => {
it("leaves responses untouched outside Bun", () => {
const response = new Response("ok", {
headers: { "content-length": "2" },
});

expect(normalizeBunResponse(response)).toBe(response);
});

it("drops explicit content length before Bun serves the response", async () => {
vi.stubGlobal("Bun", {});
try {
const response = normalizeBunResponse(
new Response("ok", {
headers: {
"content-length": "2",
"content-type": "text/plain",
},
})
);

expect(response).toBeInstanceOf(Response);
expect(response.headers.has("content-length")).toBe(false);
expect(response.headers.get("content-type")).toBe("text/plain");
expect(await response.text()).toBe("ok");
} finally {
vi.unstubAllGlobals();
}
});
});