Skip to content
Merged
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
10 changes: 9 additions & 1 deletion packages/nuxt-cli/src/dev/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class ForkPool {
execArgv: ['--enable-source-maps'],
stdio: pipeOutput ? ['ignore', 'pipe', 'pipe', 'ipc'] : undefined,
env: {
...process.env,
...withoutDotenvVars(process.env),
__NUXT__FORK: 'true',
...pipeOutput
? {
Expand Down Expand Up @@ -250,6 +250,14 @@ function trackServing(child: ChildProcess): Promise<void> {
})
}

function withoutDotenvVars(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const dotenvVars = (globalThis as { __c12_dotenv_vars__?: Map<object, Set<string>> }).__c12_dotenv_vars__?.get(env)
if (!dotenvVars?.size) {
return env
}
return Object.fromEntries(Object.entries(env).filter(([key]) => !dotenvVars.has(key)))
}

/** Colour settings for a piped fork, which cannot detect colour support itself. */
function forcedColorEnv(): Record<string, string> {
if (process.env.NO_COLOR || process.env.FORCE_COLOR) {
Expand Down
73 changes: 73 additions & 0 deletions packages/nuxt-cli/test/e2e/dev-dotenv.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { spawn } from 'node:child_process'
import { once } from 'node:events'
import { writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { getPort } from 'get-port-please'
import { expect, it, vi } from 'vitest'
import { createDevFixture } from '../utils'

const fixtureDir = await createDevFixture('dev-dotenv')
const nuxi = fileURLToPath(new URL('../../bin/nuxi.mjs', import.meta.url))

it('should reload dotenv values across successive fork restarts while preserving shell variables', { timeout: 180_000 }, async () => {
await writeFile(join(fixtureDir, 'nuxt.config.ts'), `export default defineNuxtConfig({
runtimeConfig: { public: { marker: 'default', shell: 'default' } },
})
`)
await writeFile(join(fixtureDir, 'server/api/dotenv.ts'), `export default defineEventHandler(() => ({
pid: process.pid,
...useRuntimeConfig().public,
}))
`)
const dotenv = join(fixtureDir, '.env')
await writeFile(dotenv, 'NUXT_PUBLIC_MARKER=before\nNUXT_PUBLIC_SHELL=file\n')

const host = '127.0.0.1'
const port = await getPort({ host })
const server = spawn(process.execPath, [nuxi, 'dev', fixtureDir, '--host', host, '--port', String(port), '--no-tui'], {
env: { ...process.env, NODE_ENV: 'development', TEST: '', CI: '1', NUXT_PUBLIC_MARKER: undefined, NUXT_PUBLIC_SHELL: 'shell' },
stdio: ['ignore', 'pipe', 'pipe'],
})
const exited = once(server, 'exit')
let output = ''
server.stdout.on('data', (chunk) => {
output += chunk
})
server.stderr.on('data', (chunk) => {
output += chunk
})

async function waitForMarker(marker: string, previousPid?: number): Promise<number> {
let pid = 0
await vi.waitFor(async () => {
expect(server.exitCode, output).toBeNull()
const response = await fetch(`http://${host}:${port}/api/dotenv`, { signal: AbortSignal.timeout(5000) })
expect(response.status).toBe(200)
const value = await response.json() as { pid: number, marker: string, shell: string }
expect(value).toMatchObject({ marker, shell: 'shell' })
expect(value.pid).not.toBe(previousPid)
pid = value.pid
}, { timeout: 45_000, interval: 250 })
return pid
}

try {
let pid = await waitForMarker('before')
for (const marker of ['after', 'third']) {
await writeFile(dotenv, `NUXT_PUBLIC_MARKER=${marker}\nNUXT_PUBLIC_SHELL=file\n`)
pid = await waitForMarker(marker, pid)
}
await writeFile(dotenv, '')
await waitForMarker('default', pid)
}
catch (error) {
console.error(output)
throw error
}
finally {
server.kill('SIGTERM')
await exited
}
})
Loading