diff --git a/packages/nuxt-cli/src/dev/utils.ts b/packages/nuxt-cli/src/dev/utils.ts index 18437cbb7..c0b4a0c49 100644 --- a/packages/nuxt-cli/src/dev/utils.ts +++ b/packages/nuxt-cli/src/dev/utils.ts @@ -697,6 +697,8 @@ export class NuxtDevServer extends EventEmitter { /** * Serve `report` as a live error page, or `false` when it could not be * rendered. The page dismisses itself once the channel clears the error. + * The report is build state broken for everyone, so any peer sees it; the + * history spans other requests, so only a peer on this machine sees that. */ async #renderReport(req: IncomingMessage, res: ServerResponse, report: ErrorReport): Promise { if (!String(req.headers.accept || '').includes('text/html')) { @@ -708,7 +710,7 @@ export class NuxtDevServer extends EventEmitter { const html = await renderErrorPage(report, { cwd: this.#rootDir(), channel: channel && this.#errorChannel, - history: channel?.history, + history: isLoopbackAddress(req.socket?.remoteAddress) ? channel?.history : undefined, }) res.statusCode = 500 res.setHeader('Content-Type', 'text/html') diff --git a/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts b/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts index a829a80c5..65fdca92c 100644 --- a/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts +++ b/packages/nuxt-cli/test/unit/dev/lifecycle.spec.ts @@ -116,6 +116,29 @@ async function serveLocally(server: InstanceType, path: st return { status: res.statusCode } } +/** Drive `handler` as a peer at `remoteAddress`, collecting the body it writes. */ +async function serveAsPeer(server: InstanceType, remoteAddress: string, path = '/'): Promise<{ status: number, body: string }> { + const res = new EventEmitter() as any + res.statusCode = 200 + res.headersSent = false + res.writableEnded = false + res.body = '' + res.setHeader = () => {} + res.end = (chunk?: string) => { + if (chunk) { + res.body += chunk + } + res.writableEnded = true + res.headersSent = true + res.emit('close') + } + const closed = new Promise(resolve => res.once('close', resolve)) + const req = { url: path, method: 'GET', headers: { accept: 'text/html', host: '127.0.0.1' }, rawHeaders: [], socket: { remoteAddress } } as any + await server.handler(req, res) + await closed + return { status: res.statusCode, body: res.body } +} + async function makeTempDir(): Promise { const dir = await mkdtemp(join(tmpdir(), 'nuxt-dev-lifecycle-')) tempDirs.push(dir) @@ -262,6 +285,23 @@ describe('dev server failures', () => { expect(body).toContain('broken on reload') }) + it('should keep the error history out of the failure page served to another machine', async () => { + const server = createServer() + await server.init() + + loadNuxt.mockImplementation(() => Promise.reject(new Error('broken on reload'))) + await server.load(true, { type: 'config', files: [join(cwd, 'nuxt.config.ts')] }) + + const local = await serveAsPeer(server, '127.0.0.1') + const remote = await serveAsPeer(server, '192.168.0.31') + + expect(local.status).toBe(500) + expect(local.body).toMatch(/"history":\[\s*\{/) + expect(remote.status).toBe(500) + expect(remote.body).toContain('broken on reload') + expect(remote.body).not.toMatch(/"history":\[\s*\{/) + }) + it('should recover once the config loads again', async () => { const server = createServer() await server.init()