From 395a11119d7b78b13e34cd5af521d1a750c05865 Mon Sep 17 00:00:00 2001 From: Jared Grimes Date: Sun, 1 Mar 2026 21:32:06 -0600 Subject: [PATCH 1/2] fix(config): apply env layers when NODE_ENV is unset --- src/config/loader.ts | 2 + test/unit/config-loader-env.test.ts | 74 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 test/unit/config-loader-env.test.ts diff --git a/src/config/loader.ts b/src/config/loader.ts index e280e40804..ca27a4925a 100644 --- a/src/config/loader.ts +++ b/src/config/loader.ts @@ -79,6 +79,7 @@ async function _loadUserConfig( let preset: string | undefined = (configOverrides.preset as string) || process.env.NITRO_PRESET || process.env.SERVER_PRESET const _dotenv = opts.dotenv ?? (configOverrides.dev && { fileName: [".env", ".env.local"] }); + const envName = opts.c12?.envName ?? (configOverrides.dev ? "development" : "production"); const loadedConfig = await ( opts.watch ? watchConfig @@ -87,6 +88,7 @@ async function _loadUserConfig( name: "nitro", cwd: configOverrides.rootDir, dotenv: _dotenv, + envName, extend: { extendKey: ["extends", "preset"] }, defaults: NitroDefaults, async overrides({ rawConfigs }) { diff --git a/test/unit/config-loader-env.test.ts b/test/unit/config-loader-env.test.ts new file mode 100644 index 0000000000..47aec5dc5c --- /dev/null +++ b/test/unit/config-loader-env.test.ts @@ -0,0 +1,74 @@ +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "pathe"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +vi.mock("nitro/meta", () => ({ + version: "0.0.0-test", + runtimeDir: "/tmp", + presetsDir: "/tmp", + pkgDir: "/tmp", + runtimeDependencies: [], +})); + +const originalNodeEnv = process.env.NODE_ENV; +const tempDirs: string[] = []; + +async function createFixtureConfig() { + const rootDir = await mkdtemp(join(tmpdir(), "nitro-config-env-")); + tempDirs.push(rootDir); + + await writeFile( + join(rootDir, "nitro.config.ts"), + `export default defineNitroConfig({ + preset: 'node-server', + routeRules: { + '/base': { headers: { 'x-env': 'base' } } + }, + $production: { + routeRules: { + '/prod': { headers: { 'x-env': 'production' } } + } + }, + $development: { + routeRules: { + '/dev': { headers: { 'x-env': 'development' } } + } + } +}) +` + ); + + return rootDir; +} + +afterEach(async () => { + process.env.NODE_ENV = originalNodeEnv; + for (const dir of tempDirs.splice(0, tempDirs.length)) { + await rm(dir, { recursive: true, force: true }); + } +}); + +describe("config loader env layers", () => { + it("applies $production when NODE_ENV is unset and dev=false", async () => { + delete process.env.NODE_ENV; + const rootDir = await createFixtureConfig(); + + const { loadOptions } = await import("../../src/config/loader.ts"); + const options = await loadOptions({ rootDir, dev: false }); + + expect(options.routeRules["/prod"]?.headers?.["x-env"]).toBe("production"); + expect(options.routeRules["/dev"]).toBeUndefined(); + }); + + it("applies $development when NODE_ENV is unset and dev=true", async () => { + delete process.env.NODE_ENV; + const rootDir = await createFixtureConfig(); + + const { loadOptions } = await import("../../src/config/loader.ts"); + const options = await loadOptions({ rootDir, dev: true }); + + expect(options.routeRules["/dev"]?.headers?.["x-env"]).toBe("development"); + expect(options.routeRules["/prod"]).toBeUndefined(); + }); +}); From 1940d6b7e301baafffe85408d36ac0685241a51f Mon Sep 17 00:00:00 2001 From: Jared Grimes Date: Mon, 2 Mar 2026 19:02:26 -0600 Subject: [PATCH 2/2] test(config): assert base route survives env layer merge --- test/unit/config-loader-env.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/config-loader-env.test.ts b/test/unit/config-loader-env.test.ts index 47aec5dc5c..a44fa45d23 100644 --- a/test/unit/config-loader-env.test.ts +++ b/test/unit/config-loader-env.test.ts @@ -59,6 +59,7 @@ describe("config loader env layers", () => { expect(options.routeRules["/prod"]?.headers?.["x-env"]).toBe("production"); expect(options.routeRules["/dev"]).toBeUndefined(); + expect(options.routeRules["/base"]?.headers?.["x-env"]).toBe("base"); }); it("applies $development when NODE_ENV is unset and dev=true", async () => { @@ -70,5 +71,6 @@ describe("config loader env layers", () => { expect(options.routeRules["/dev"]?.headers?.["x-env"]).toBe("development"); expect(options.routeRules["/prod"]).toBeUndefined(); + expect(options.routeRules["/base"]?.headers?.["x-env"]).toBe("base"); }); });