From 0fe10ba6a44571ca9876a5c254e6287bcddce24a Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 15:19:56 +0100 Subject: [PATCH 01/24] test: add dev server tests for multi-runtime --- .github/workflows/ci.yml | 6 + packages/nuxt-cli/test/e2e/runtimes.spec.ts | 310 ++++++++++++++++++++ playground/{ => app}/pages/index.vue | 0 playground/{ => app}/pages/ws.vue | 0 playground/server/api/echo.ts | 10 + playground/server/api/hello.ts | 8 + playground/tsconfig.json | 17 +- 7 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 packages/nuxt-cli/test/e2e/runtimes.spec.ts rename playground/{ => app}/pages/index.vue (100%) rename playground/{ => app}/pages/ws.vue (100%) create mode 100644 playground/server/api/echo.ts create mode 100644 playground/server/api/hello.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74ba02629..012844f7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,12 @@ jobs: with: node-version: lts/-1 cache: pnpm + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - uses: denoland/setup-deno@v1 + with: + deno-version: v2.x - name: 📦 Install dependencies run: pnpm install diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts new file mode 100644 index 000000000..bb9838fe9 --- /dev/null +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -0,0 +1,310 @@ +import type { ChildProcess } from 'node:child_process' +import { spawn, spawnSync } from 'node:child_process' +import { rm } from 'node:fs/promises' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { getPort, waitForPort } from 'get-port-please' +import { isCI } from 'std-env' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' + +const playgroundDir = fileURLToPath(new URL('../../../../playground', import.meta.url)) +const nuxiPath = join(fileURLToPath(new URL('../..', import.meta.url)), 'bin/nuxi.mjs') + +const hasBun = spawnSync('bun', ['--version'], { stdio: 'ignore' }).status === 0 +const hasDeno = spawnSync('deno', ['--version'], { stdio: 'ignore' }).status === 0 + +describe.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (runtime) => { + let server: DevServerInstance + + if (runtime === 'bun' && !hasBun && !isCI) { + it.skip('should pass with bun') + return + } + + if (runtime === 'deno' && !hasDeno && !isCI) { + it.skip('should pass with deno') + return + } + + beforeAll(async () => { + await rm(join(playgroundDir, '.nuxt'), { recursive: true, force: true }) + server = await startDevServer({ cwd: playgroundDir, runtime }) + }, isCI ? 60_000 : 30_000) + + afterAll(() => server?.close()) + + it('should serve the main page with socket fetch', async () => { + const response = await fetch(server.url) + expect(response.status).toBe(200) + + const html = await response.text() + expect(html).toContain('Welcome to the Nuxt CLI playground') + expect(html).toContain('') + }) + + it('should serve static assets', async () => { + const response = await fetch(`${server.url}/favicon.ico`) + expect(response.status).toBe(200) + expect(response.headers.get('content-type')).toContain('image/') + }) + + it('should handle API routes', async () => { + const response = await fetch(`${server.url}/api/hello`) + expect(response.status).toBe(200) + }) + + it('should handle POST requests', async () => { + const response = await fetch(`${server.url}/api/echo`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ test: 'data' }), + }) + + expect(response.status).toBe(200) + }) + + it('should preserve request headers', async () => { + const response = await fetch(`${server.url}/`, { + headers: { + 'X-Custom-Header': 'test-value', + 'User-Agent': 'socket-fetch-test', + }, + }) + + expect(response.status).toBe(200) + const html = await response.text() + expect(html).toContain('Welcome to the Nuxt CLI playground') + }) + + it('should handle concurrent requests', async () => { + const requests = Array.from({ length: 5 }, () => fetch(server.url)) + const responses = await Promise.all(requests) + + for (const response of responses) { + expect(response.status).toBe(200) + expect(await response.text()).toContain('Welcome to the Nuxt CLI playground') + } + }) + + it('should handle large request payloads', async () => { + const largePayload = { data: 'x'.repeat(10000) } // 10KB payload + const response = await fetch(`${server.url}/api/echo`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(largePayload), + }) + + expect(response.status).toBe(200) + const result = await response.json() + expect(result.echoed.data).toBe(largePayload.data) + }) + + it('should handle different HTTP methods', async () => { + const methods = ['GET', 'POST', 'PUT', 'DELETE'] + + for (const method of methods) { + const response = await fetch(`${server.url}/api/hello`, { method }) + expect(response.status).toBe(200) + + const result = await response.json() + expect(result.method).toBe(method) + } + }) + + // TODO: fix websockets in bun + deno + it.skipIf(runtime === 'bun')('should establish websocket connection and handle ping/pong', async () => { + const wsUrl = `${server.url.replace('http', 'ws')}/_ws` + + // Create a promise that resolves when the websocket test is complete + const wsTest = new Promise((resolve, reject) => { + const ws = new WebSocket(wsUrl) + + let isConnected = false + let receivedPong = false + + const timeout = setTimeout(() => { + if (!isConnected) { + reject(new Error('WebSocket connection timeout')) + } + else if (!receivedPong) { + reject(new Error('Did not receive pong response')) + } + ws.close() + }, 5000) + + ws.addEventListener('open', () => { + isConnected = true + // Send ping message to test echo functionality + ws.send('ping test message') + }) + + ws.addEventListener('message', (event) => { + const message = event.data.toString() + if (message === 'pong') { + receivedPong = true + clearTimeout(timeout) + ws.close() + resolve() + } + }) + + ws.addEventListener('error', (error) => { + clearTimeout(timeout) + reject(new Error(`WebSocket error: ${error}`)) + }) + + ws.addEventListener('close', () => { + if (isConnected && receivedPong) { + resolve() + } + }) + }) + + await wsTest + }, 10000) + + // TODO: fix websockets in bun + deno + it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle multiple concurrent websocket connections', async () => { + const wsUrl = `${server.url.replace('http', 'ws')}/_ws` + const connectionCount = 3 + + const connectionPromises = Array.from({ length: connectionCount }, (_, index) => { + return new Promise((resolve, reject) => { + const ws = new WebSocket(wsUrl) + + const timeout = setTimeout(() => { + reject(new Error(`WebSocket ${index} connection timeout`)) + ws.close() + }, 5000) + + ws.addEventListener('open', () => { + ws.send(`ping from connection ${index}`) + }) + + ws.addEventListener('message', (event) => { + const message = event.data.toString() + if (message === 'pong') { + clearTimeout(timeout) + ws.close() + resolve() + } + }) + + ws.addEventListener('error', (error) => { + clearTimeout(timeout) + reject(new Error(`WebSocket ${index} error: ${error}`)) + }) + }) + }) + + await Promise.all(connectionPromises) + }, 15000) + + // TODO: fix websockets in bun + deno + it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle websocket connection close gracefully', async () => { + const wsUrl = `${server.url.replace('http', 'ws')}/_ws` + + const wsTest = new Promise((resolve, reject) => { + const ws = new WebSocket(wsUrl) + + let isConnected = false + + const timeout = setTimeout(() => { + reject(new Error('WebSocket close test timeout')) + }, 5000) + + ws.addEventListener('open', () => { + isConnected = true + // Immediately close the connection to test graceful handling + ws.close(1000, 'Test close') + }) + + ws.addEventListener('close', (event) => { + clearTimeout(timeout) + expect(isConnected).toBe(true) + expect(event.code).toBe(1000) + expect(event.reason).toBe('Test close') + resolve() + }) + + ws.addEventListener('error', (error) => { + clearTimeout(timeout) + reject(new Error(`WebSocket close test error: ${error}`)) + }) + }) + + await wsTest + }, 10000) +}) + +interface DevServerInstance { + process: ChildProcess + url: string + port: number + close: () => Promise +} + +async function startDevServer(options: { + cwd: string + port?: number + runtime?: 'node' | 'bun' | 'deno' + env?: Record +}): Promise { + const { cwd, port: preferredPort, runtime = 'node', env = {} } = options + const port = preferredPort || await getPort({ port: 3100 }) + const host = '127.0.0.1' + const url = `http://${host}:${port}` + + let command: string + switch (runtime) { + case 'bun': + command = `bun ${nuxiPath} dev --port ${port} --host ${host}` + break + case 'deno': + command = `deno run --allow-all ${nuxiPath} dev --port ${port} --host ${host}` + break + default: + command = `node ${nuxiPath} dev --port ${port} --host ${host}` + } + + const [cmd, ...args] = command.split(' ') + + // Start the dev server process + const child = spawn(cmd!, args, { + cwd, + stdio: 'pipe', + env: { + ...process.env, + ...env, + NUXT_TELEMETRY_DISABLED: '1', + PORT: String(port), + HOST: host, + }, + }) + + try { + await waitForPort(port, { delay: 1000, retries: 25, host }) + } + catch (error) { + child.kill() + throw new Error(`Dev server failed to start on port ${port} with ${runtime}: ${error}`) + } + + return { + process: child, + url, + port, + close: async () => { + return new Promise((resolve) => { + child.kill('SIGTERM') + setTimeout(() => { + if (!child.killed) { + child.kill('SIGKILL') + } + }, 5000) + child.on('exit', () => resolve()) + }) + }, + } +} diff --git a/playground/pages/index.vue b/playground/app/pages/index.vue similarity index 100% rename from playground/pages/index.vue rename to playground/app/pages/index.vue diff --git a/playground/pages/ws.vue b/playground/app/pages/ws.vue similarity index 100% rename from playground/pages/ws.vue rename to playground/app/pages/ws.vue diff --git a/playground/server/api/echo.ts b/playground/server/api/echo.ts new file mode 100644 index 000000000..9687afe9f --- /dev/null +++ b/playground/server/api/echo.ts @@ -0,0 +1,10 @@ +export default defineEventHandler(async (event) => { + const body = await readBody(event).catch(() => ({})) + + return { + message: 'Echo API endpoint', + echoed: body, + method: event.method, + timestamp: new Date().toISOString(), + } +}) diff --git a/playground/server/api/hello.ts b/playground/server/api/hello.ts new file mode 100644 index 000000000..d89eacc03 --- /dev/null +++ b/playground/server/api/hello.ts @@ -0,0 +1,8 @@ +export default defineEventHandler(async (event) => { + return { + message: 'Hello from API!', + timestamp: new Date().toISOString(), + method: event.method, + url: getRequestURL(event).pathname, + } +}) diff --git a/playground/tsconfig.json b/playground/tsconfig.json index a746f2a70..aa0f81939 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -1,4 +1,17 @@ { - // https://nuxt.com/docs/guide/concepts/typescript - "extends": "./.nuxt/tsconfig.json" + "references": [ + { + "path": "./.nuxt/tsconfig.app.json" + }, + { + "path": "./.nuxt/tsconfig.server.json" + }, + { + "path": "./.nuxt/tsconfig.shared.json" + }, + { + "path": "./.nuxt/tsconfig.node.json" + } + ], + "files": [] } From 3c01fb7034e2e34525a1a6e20a8b38692c48c8cf Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 15:40:41 +0100 Subject: [PATCH 02/24] test: wait for server to be ready (not 503) --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index bb9838fe9..51e2980c0 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url' import { getPort, waitForPort } from 'get-port-please' import { isCI } from 'std-env' -import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' const playgroundDir = fileURLToPath(new URL('../../../../playground', import.meta.url)) const nuxiPath = join(fileURLToPath(new URL('../..', import.meta.url)), 'bin/nuxi.mjs') @@ -285,6 +285,12 @@ async function startDevServer(options: { try { await waitForPort(port, { delay: 1000, retries: 25, host }) + await vi.waitFor(async () => { + const res = await fetch(url) + if (res.status === 503) { + throw new Error('Server not ready') + } + }) } catch (error) { child.kill() From f1b3eb81ab793a464e05fd22a263d1cb9db1a983 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 15:46:34 +0100 Subject: [PATCH 03/24] test: bump waitFor timeout --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 51e2980c0..6505a3c89 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -290,7 +290,7 @@ async function startDevServer(options: { if (res.status === 503) { throw new Error('Server not ready') } - }) + }, { timeout: isCI ? 60_000 : 30_000 }) } catch (error) { child.kill() From b3ffabb2fd3fd931132be82a6cad2df44f904847 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 15:47:54 +0100 Subject: [PATCH 04/24] chore: update knip config --- knip.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/knip.json b/knip.json index a3fc7daec..f5388974e 100644 --- a/knip.json +++ b/knip.json @@ -12,7 +12,7 @@ "playground": { "entry": [ "test/**", - "pages/**", + "app/pages/**", "server/**", "some-layer/**" ] From a8bc33025f5014529c9c3566107908e51672a943 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 15:54:57 +0100 Subject: [PATCH 05/24] test: run sequentially --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 6505a3c89..6bc4fa8f5 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -14,7 +14,7 @@ const nuxiPath = join(fileURLToPath(new URL('../..', import.meta.url)), 'bin/nux const hasBun = spawnSync('bun', ['--version'], { stdio: 'ignore' }).status === 0 const hasDeno = spawnSync('deno', ['--version'], { stdio: 'ignore' }).status === 0 -describe.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (runtime) => { +describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (runtime) => { let server: DevServerInstance if (runtime === 'bun' && !hasBun && !isCI) { From 8281fcb3fffb3f6e7056abd0031a4a4a71ed6c49 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 15:55:29 +0100 Subject: [PATCH 06/24] test: use undici websocket implementation --- package.json | 1 + packages/nuxt-cli/test/e2e/runtimes.spec.ts | 1 + pnpm-lock.yaml | 9 +++++++++ 3 files changed, 11 insertions(+) diff --git a/package.json b/package.json index bff9a6288..d80b2e2a6 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "std-env": "^3.9.0", "tinyexec": "^1.0.1", "typescript": "^5.9.2", + "undici": "^7.16.0", "vitest": "^3.2.4", "vue": "^3.5.21" }, diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 6bc4fa8f5..0b021a10f 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url' import { getPort, waitForPort } from 'get-port-please' import { isCI } from 'std-env' +import { WebSocket } from 'undici' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' const playgroundDir = fileURLToPath(new URL('../../../../playground', import.meta.url)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c84e71e34..8e78d185f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,6 +61,9 @@ importers: typescript: specifier: ^5.9.2 version: 5.9.2 + undici: + specifier: ^7.16.0 + version: 7.16.0 vitest: specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(jiti@2.6.0)(terser@5.43.1)(yaml@2.8.1) @@ -5000,6 +5003,10 @@ packages: resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} engines: {node: '>=18.17'} + undici@7.16.0: + resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} + engines: {node: '>=20.18.1'} + unenv@2.0.0-rc.20: resolution: {integrity: sha512-8tn4tAl9vD5nWoggAAPz28vf0FY8+pQAayhU94qD+ZkIbVKCBAH/E1MWEEmhb9Whn5EgouYVfBJB20RsTLRDdg==} @@ -10742,6 +10749,8 @@ snapshots: undici@6.21.3: {} + undici@7.16.0: {} + unenv@2.0.0-rc.20: dependencies: defu: 6.1.4 From c7ea5495d12c363b6db115c00624b0f60db32c6d Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 16:08:18 +0100 Subject: [PATCH 07/24] test: run command tests sequentially --- packages/nuxt-cli/test/e2e/commands.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nuxt-cli/test/e2e/commands.spec.ts b/packages/nuxt-cli/test/e2e/commands.spec.ts index 77e0e9184..a42076686 100644 --- a/packages/nuxt-cli/test/e2e/commands.spec.ts +++ b/packages/nuxt-cli/test/e2e/commands.spec.ts @@ -152,7 +152,7 @@ describe('commands', () => { }) const testsToRun = Object.entries(tests).filter(([_, value]) => value !== 'todo') - it.each(testsToRun)(`%s`, { timeout: isWindows ? 200000 : 50000 }, (_, test) => (test as () => Promise)()) + it.sequential.each(testsToRun)(`%s`, { timeout: isWindows ? 200000 : 50000 }, (_, test) => (test as () => Promise)()) for (const [command, value] of Object.entries(tests)) { if (value === 'todo') { From 5efedfb09dc8c77384fc892e5739309a2804ed1e Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 16:14:24 +0100 Subject: [PATCH 08/24] chore: update test descriptions --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 0b021a10f..617573435 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -35,7 +35,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r afterAll(() => server?.close()) - it('should serve the main page with socket fetch', async () => { + it('should serve the main page', async () => { const response = await fetch(server.url) expect(response.status).toBe(200) @@ -69,7 +69,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r const response = await fetch(`${server.url}/`, { headers: { 'X-Custom-Header': 'test-value', - 'User-Agent': 'socket-fetch-test', + 'User-Agent': 'vitest', }, }) @@ -89,7 +89,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }) it('should handle large request payloads', async () => { - const largePayload = { data: 'x'.repeat(10000) } // 10KB payload + const largePayload = { data: 'x'.repeat(10000) } const response = await fetch(`${server.url}/api/echo`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, From 8fb0fe67c3c560bffb6587b74d622e61014d01a8 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 16:25:23 +0100 Subject: [PATCH 09/24] test: use separate directories for runtime tests --- packages/nuxt-cli/test/e2e/commands.spec.ts | 2 +- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/commands.spec.ts b/packages/nuxt-cli/test/e2e/commands.spec.ts index a42076686..77e0e9184 100644 --- a/packages/nuxt-cli/test/e2e/commands.spec.ts +++ b/packages/nuxt-cli/test/e2e/commands.spec.ts @@ -152,7 +152,7 @@ describe('commands', () => { }) const testsToRun = Object.entries(tests).filter(([_, value]) => value !== 'todo') - it.sequential.each(testsToRun)(`%s`, { timeout: isWindows ? 200000 : 50000 }, (_, test) => (test as () => Promise)()) + it.each(testsToRun)(`%s`, { timeout: isWindows ? 200000 : 50000 }, (_, test) => (test as () => Promise)()) for (const [command, value] of Object.entries(tests)) { if (value === 'todo') { diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 617573435..0494d00f7 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -1,7 +1,7 @@ import type { ChildProcess } from 'node:child_process' import { spawn, spawnSync } from 'node:child_process' -import { rm } from 'node:fs/promises' -import { join } from 'node:path' +import { cp, rm } from 'node:fs/promises' +import { join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { getPort, waitForPort } from 'get-port-please' @@ -28,12 +28,17 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r return } + const cwd = resolve(playgroundDir, `..`, runtime) beforeAll(async () => { - await rm(join(playgroundDir, '.nuxt'), { recursive: true, force: true }) - server = await startDevServer({ cwd: playgroundDir, runtime }) + await cp(playgroundDir, cwd, { recursive: true }) + await rm(join(cwd, '.nuxt'), { recursive: true, force: true }) + server = await startDevServer({ cwd, runtime }) }, isCI ? 60_000 : 30_000) - afterAll(() => server?.close()) + afterAll(async () => { + await server?.close() + await rm(cwd, { recursive: true, force: true }) + }) it('should serve the main page', async () => { const response = await fetch(server.url) From d4d5de7d9083fca6e646500357eca4630aacc61c Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 16:35:02 +0100 Subject: [PATCH 10/24] test: bump pong timeout --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 0494d00f7..f3074842d 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -28,7 +28,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r return } - const cwd = resolve(playgroundDir, `..`, runtime) + const cwd = resolve(playgroundDir, `../playground-${runtime}`) beforeAll(async () => { await cp(playgroundDir, cwd, { recursive: true }) await rm(join(cwd, '.nuxt'), { recursive: true, force: true }) @@ -94,7 +94,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }) it('should handle large request payloads', async () => { - const largePayload = { data: 'x'.repeat(10000) } + const largePayload = { data: 'x'.repeat(10_000) } const response = await fetch(`${server.url}/api/echo`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -137,7 +137,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r reject(new Error('Did not receive pong response')) } ws.close() - }, 5000) + }, 10_000) ws.addEventListener('open', () => { isConnected = true @@ -168,7 +168,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }) await wsTest - }, 10000) + }, 10_000) // TODO: fix websockets in bun + deno it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle multiple concurrent websocket connections', async () => { @@ -241,7 +241,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }) await wsTest - }, 10000) + }, 10_000) }) interface DevServerInstance { From fc0fdb70603596050dd863f0334e369e18f7d28b Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 16:42:18 +0100 Subject: [PATCH 11/24] test: filter files whilst copying --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index f3074842d..7b3cb421a 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -30,8 +30,10 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r const cwd = resolve(playgroundDir, `../playground-${runtime}`) beforeAll(async () => { - await cp(playgroundDir, cwd, { recursive: true }) - await rm(join(cwd, '.nuxt'), { recursive: true, force: true }) + await cp(playgroundDir, cwd, { + recursive: true, + filter: src => !src.includes('.nuxt') && !src.includes('.output'), + }) server = await startDevServer({ cwd, runtime }) }, isCI ? 60_000 : 30_000) From 6fa0f9aaa2d9b33c1b74f94688852a57a5eee960 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 16:49:05 +0100 Subject: [PATCH 12/24] chore: bump timeout again --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 7b3cb421a..4a2ed4728 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -139,7 +139,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r reject(new Error('Did not receive pong response')) } ws.close() - }, 10_000) + }, 20_000) ws.addEventListener('open', () => { isConnected = true @@ -170,7 +170,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }) await wsTest - }, 10_000) + }, 20_000) // TODO: fix websockets in bun + deno it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle multiple concurrent websocket connections', async () => { From 06636a0863e69b53f18d973d746d7d1e2709be6a Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 16:51:30 +0100 Subject: [PATCH 13/24] test: skip bun runtime tests in windows ci --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 4a2ed4728..6e103184d 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -5,7 +5,7 @@ import { join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { getPort, waitForPort } from 'get-port-please' -import { isCI } from 'std-env' +import { isCI, isWindows } from 'std-env' import { WebSocket } from 'undici' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -18,7 +18,7 @@ const hasDeno = spawnSync('deno', ['--version'], { stdio: 'ignore' }).status === describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (runtime) => { let server: DevServerInstance - if (runtime === 'bun' && !hasBun && !isCI) { + if (runtime === 'bun' && ((!hasBun && !isCI) || isWindows)) { it.skip('should pass with bun') return } From 60cb4d809cf77b207a8ab0e03afa1516970a7fed Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 16:57:29 +0100 Subject: [PATCH 14/24] test: skip deno on mac/windows --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 6e103184d..3309cbb52 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -5,7 +5,7 @@ import { join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { getPort, waitForPort } from 'get-port-please' -import { isCI, isWindows } from 'std-env' +import { isCI, isLinux, isWindows } from 'std-env' import { WebSocket } from 'undici' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -121,7 +121,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }) // TODO: fix websockets in bun + deno - it.skipIf(runtime === 'bun')('should establish websocket connection and handle ping/pong', async () => { + it.skipIf(runtime === 'bun' || (runtime === 'deno' && !isLinux))('should establish websocket connection and handle ping/pong', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` // Create a promise that resolves when the websocket test is complete @@ -173,7 +173,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }, 20_000) // TODO: fix websockets in bun + deno - it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle multiple concurrent websocket connections', async () => { + it.skipIf(runtime === 'bun' || (runtime === 'deno' && !isLinux))('should handle multiple concurrent websocket connections', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` const connectionCount = 3 @@ -210,7 +210,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }, 15000) // TODO: fix websockets in bun + deno - it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle websocket connection close gracefully', async () => { + it.skipIf(runtime === 'bun' || (runtime === 'deno' && !isLinux))('should handle websocket connection close gracefully', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` const wsTest = new Promise((resolve, reject) => { From b3fdccf6217e484b174e1345fae3c0a41e1134f3 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 17:57:25 +0100 Subject: [PATCH 15/24] test: skip deno entirely for second two ws tests --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 3309cbb52..9c7d48dfc 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -173,7 +173,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }, 20_000) // TODO: fix websockets in bun + deno - it.skipIf(runtime === 'bun' || (runtime === 'deno' && !isLinux))('should handle multiple concurrent websocket connections', async () => { + it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle multiple concurrent websocket connections', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` const connectionCount = 3 @@ -210,7 +210,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }, 15000) // TODO: fix websockets in bun + deno - it.skipIf(runtime === 'bun' || (runtime === 'deno' && !isLinux))('should handle websocket connection close gracefully', async () => { + it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle websocket connection close gracefully', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` const wsTest = new Promise((resolve, reject) => { From dba522c9b1d03d6f6f1be87c2734cf805e13ac51 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 23 Sep 2025 17:59:17 +0100 Subject: [PATCH 16/24] chore: use sync methods for fs cp/rm --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 9c7d48dfc..e902d9603 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -8,6 +8,7 @@ import { getPort, waitForPort } from 'get-port-please' import { isCI, isLinux, isWindows } from 'std-env' import { WebSocket } from 'undici' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { cpSync, rmSync } from 'node:fs' const playgroundDir = fileURLToPath(new URL('../../../../playground', import.meta.url)) const nuxiPath = join(fileURLToPath(new URL('../..', import.meta.url)), 'bin/nuxi.mjs') @@ -30,7 +31,8 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r const cwd = resolve(playgroundDir, `../playground-${runtime}`) beforeAll(async () => { - await cp(playgroundDir, cwd, { + rmSync(cwd, { recursive: true, force: true }) + cpSync(playgroundDir, cwd, { recursive: true, filter: src => !src.includes('.nuxt') && !src.includes('.output'), }) From 4514088f65e4c41d930bd4c6b6a06d1fde1a4048 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:02:40 +0000 Subject: [PATCH 17/24] [autofix.ci] apply automated fixes --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index e902d9603..0d1cccd79 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -1,14 +1,14 @@ import type { ChildProcess } from 'node:child_process' import { spawn, spawnSync } from 'node:child_process' -import { cp, rm } from 'node:fs/promises' +import { cpSync, rmSync } from 'node:fs' +import { rm } from 'node:fs/promises' import { join, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' +import { fileURLToPath } from 'node:url' import { getPort, waitForPort } from 'get-port-please' import { isCI, isLinux, isWindows } from 'std-env' import { WebSocket } from 'undici' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' -import { cpSync, rmSync } from 'node:fs' const playgroundDir = fileURLToPath(new URL('../../../../playground', import.meta.url)) const nuxiPath = join(fileURLToPath(new URL('../..', import.meta.url)), 'bin/nuxi.mjs') From 5203ee10d31b7c02ba8c613bcf9f6e9bdf1d75a8 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 24 Sep 2025 09:17:02 +0100 Subject: [PATCH 18/24] test: use `it.fails` to detect when things start working --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 36 +++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 0d1cccd79..4b607ead8 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -1,14 +1,13 @@ import type { ChildProcess } from 'node:child_process' import { spawn, spawnSync } from 'node:child_process' import { cpSync, rmSync } from 'node:fs' -import { rm } from 'node:fs/promises' import { join, resolve } from 'node:path' - import { fileURLToPath } from 'node:url' + import { getPort, waitForPort } from 'get-port-please' import { isCI, isLinux, isWindows } from 'std-env' import { WebSocket } from 'undici' -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { afterAll, describe, expect, it, vi } from 'vitest' const playgroundDir = fileURLToPath(new URL('../../../../playground', import.meta.url)) const nuxiPath = join(fileURLToPath(new URL('../..', import.meta.url)), 'bin/nuxi.mjs') @@ -19,31 +18,40 @@ const hasDeno = spawnSync('deno', ['--version'], { stdio: 'ignore' }).status === describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (runtime) => { let server: DevServerInstance - if (runtime === 'bun' && ((!hasBun && !isCI) || isWindows)) { + if (runtime === 'bun' && !hasBun && !isCI) { + console.warn('Not testing locally with bun as it is not installed.') it.skip('should pass with bun') return } if (runtime === 'deno' && !hasDeno && !isCI) { + console.warn('Not testing locally with deno as it is not installed.') it.skip('should pass with deno') return } const cwd = resolve(playgroundDir, `../playground-${runtime}`) - beforeAll(async () => { + + afterAll(async () => { + await server?.close() + rmSync(cwd, { recursive: true, force: true }) + }) + + const assertNonBun = isWindows && runtime === 'bun' ? it.fails : it + assertNonBun('should start dev server', { timeout: isCI ? 60_000 : 30_000 }, async () => { rmSync(cwd, { recursive: true, force: true }) cpSync(playgroundDir, cwd, { recursive: true, filter: src => !src.includes('.nuxt') && !src.includes('.output'), }) server = await startDevServer({ cwd, runtime }) - }, isCI ? 60_000 : 30_000) - - afterAll(async () => { - await server?.close() - await rm(cwd, { recursive: true, force: true }) }) + if (runtime === 'bun' && isCI && isWindows) { + it.skip('should work with bun') + return + } + it('should serve the main page', async () => { const response = await fetch(server.url) expect(response.status).toBe(200) @@ -123,7 +131,8 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }) // TODO: fix websockets in bun + deno - it.skipIf(runtime === 'bun' || (runtime === 'deno' && !isLinux))('should establish websocket connection and handle ping/pong', async () => { + const assertNonLinux = runtime === 'bun' || (runtime === 'deno' && !isLinux) ? it.fails : it + assertNonLinux('should establish websocket connection and handle ping/pong', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` // Create a promise that resolves when the websocket test is complete @@ -175,7 +184,8 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }, 20_000) // TODO: fix websockets in bun + deno - it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle multiple concurrent websocket connections', async () => { + const assertNonNode = runtime === 'bun' || runtime === 'deno' ? it.fails : it + assertNonNode('should handle multiple concurrent websocket connections', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` const connectionCount = 3 @@ -212,7 +222,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }, 15000) // TODO: fix websockets in bun + deno - it.skipIf(runtime === 'bun' || runtime === 'deno')('should handle websocket connection close gracefully', async () => { + assertNonNode('should handle websocket connection close gracefully', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` const wsTest = new Promise((resolve, reject) => { From c5d6a66075ec31300d8939780ba1e1d610336135 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 24 Sep 2025 09:25:52 +0100 Subject: [PATCH 19/24] test: equalise condition --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 4b607ead8..a4ed381c3 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -47,7 +47,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r server = await startDevServer({ cwd, runtime }) }) - if (runtime === 'bun' && isCI && isWindows) { + if (runtime === 'bun' && isWindows) { it.skip('should work with bun') return } From 444a1b76ab5062a9e39b551e23e991e2bcf11965 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 24 Sep 2025 09:46:23 +0100 Subject: [PATCH 20/24] test: handle rejection in websocket closure --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index a4ed381c3..599c3ce56 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -242,10 +242,15 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r ws.addEventListener('close', (event) => { clearTimeout(timeout) - expect(isConnected).toBe(true) - expect(event.code).toBe(1000) - expect(event.reason).toBe('Test close') - resolve() + try { + expect(isConnected).toBe(true) + expect(event.code).toBe(1000) + expect(event.reason).toBe('Test close') + resolve() + } + catch (error) { + reject(error) + } }) ws.addEventListener('error', (error) => { From 396da2a18271e9777e7bfeb4a967dad7a873ecdd Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 24 Sep 2025 09:51:47 +0100 Subject: [PATCH 21/24] test: mark test as passing with node + linux + skip deno on windows --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 599c3ce56..a8bc6b2e0 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -37,7 +37,8 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r rmSync(cwd, { recursive: true, force: true }) }) - const assertNonBun = isWindows && runtime === 'bun' ? it.fails : it + const isWindowsNonNode = isWindows && (runtime === 'bun' || runtime === 'deno') + const assertNonBun = isWindowsNonNode ? it.fails : it assertNonBun('should start dev server', { timeout: isCI ? 60_000 : 30_000 }, async () => { rmSync(cwd, { recursive: true, force: true }) cpSync(playgroundDir, cwd, { @@ -47,8 +48,8 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r server = await startDevServer({ cwd, runtime }) }) - if (runtime === 'bun' && isWindows) { - it.skip('should work with bun') + if (isWindowsNonNode) { + it.todo('should run rest of tests on windows') return } @@ -184,8 +185,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }, 20_000) // TODO: fix websockets in bun + deno - const assertNonNode = runtime === 'bun' || runtime === 'deno' ? it.fails : it - assertNonNode('should handle multiple concurrent websocket connections', async () => { + assertNonLinux('should handle multiple concurrent websocket connections', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` const connectionCount = 3 @@ -222,6 +222,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r }, 15000) // TODO: fix websockets in bun + deno + const assertNonNode = runtime === 'bun' || runtime === 'deno' ? it.fails : it assertNonNode('should handle websocket connection close gracefully', async () => { const wsUrl = `${server.url.replace('http', 'ws')}/_ws` From 3225f7297b95c90c18af4a6b4492f08c90a8ab4b Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 24 Sep 2025 10:05:54 +0100 Subject: [PATCH 22/24] test: try running bun on windows --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index a8bc6b2e0..fe89de774 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -37,9 +37,9 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r rmSync(cwd, { recursive: true, force: true }) }) - const isWindowsNonNode = isWindows && (runtime === 'bun' || runtime === 'deno') - const assertNonBun = isWindowsNonNode ? it.fails : it - assertNonBun('should start dev server', { timeout: isCI ? 60_000 : 30_000 }, async () => { + const isWindowsNonDeno = isWindows && runtime === 'deno' + const assertNonDeno = isWindowsNonDeno ? it.fails : it + assertNonDeno('should start dev server', { timeout: isCI ? 60_000 : 30_000 }, async () => { rmSync(cwd, { recursive: true, force: true }) cpSync(playgroundDir, cwd, { recursive: true, @@ -48,7 +48,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r server = await startDevServer({ cwd, runtime }) }) - if (isWindowsNonNode) { + if (isWindowsNonDeno) { it.todo('should run rest of tests on windows') return } From 38b0cb8d0e95a0c671e69a7e19c6a675c2070d38 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 24 Sep 2025 10:44:34 +0100 Subject: [PATCH 23/24] test: assert bun failures on windows --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index fe89de774..36a3013a6 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -1,6 +1,7 @@ import type { ChildProcess } from 'node:child_process' import { spawn, spawnSync } from 'node:child_process' import { cpSync, rmSync } from 'node:fs' +import { rm } from 'node:fs/promises' import { join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' @@ -34,7 +35,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r afterAll(async () => { await server?.close() - rmSync(cwd, { recursive: true, force: true }) + await rm(cwd, { recursive: true, force: true }).catch(() => null) }) const isWindowsNonDeno = isWindows && runtime === 'deno' @@ -62,18 +63,19 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r expect(html).toContain('') }) - it('should serve static assets', async () => { + const assertNonWindowsBun = runtime === 'bun' && isWindows ? it.fails : it + assertNonWindowsBun('should serve static assets', async () => { const response = await fetch(`${server.url}/favicon.ico`) expect(response.status).toBe(200) expect(response.headers.get('content-type')).toContain('image/') }) - it('should handle API routes', async () => { + assertNonWindowsBun('should handle API routes', async () => { const response = await fetch(`${server.url}/api/hello`) expect(response.status).toBe(200) }) - it('should handle POST requests', async () => { + assertNonWindowsBun('should handle POST requests', async () => { const response = await fetch(`${server.url}/api/echo`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -83,7 +85,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r expect(response.status).toBe(200) }) - it('should preserve request headers', async () => { + assertNonWindowsBun('should preserve request headers', async () => { const response = await fetch(`${server.url}/`, { headers: { 'X-Custom-Header': 'test-value', @@ -96,7 +98,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r expect(html).toContain('Welcome to the Nuxt CLI playground') }) - it('should handle concurrent requests', async () => { + assertNonWindowsBun('should handle concurrent requests', async () => { const requests = Array.from({ length: 5 }, () => fetch(server.url)) const responses = await Promise.all(requests) @@ -106,7 +108,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r } }) - it('should handle large request payloads', async () => { + assertNonWindowsBun('should handle large request payloads', async () => { const largePayload = { data: 'x'.repeat(10_000) } const response = await fetch(`${server.url}/api/echo`, { method: 'POST', @@ -119,7 +121,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r expect(result.echoed.data).toBe(largePayload.data) }) - it('should handle different HTTP methods', async () => { + assertNonWindowsBun('should handle different HTTP methods', async () => { const methods = ['GET', 'POST', 'PUT', 'DELETE'] for (const method of methods) { From 3bf0a4a2ff1f06974d2b1c51ffb2a4c5ec7c48c3 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 24 Sep 2025 10:57:31 +0100 Subject: [PATCH 24/24] chore: nothing works --- packages/nuxt-cli/test/e2e/runtimes.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nuxt-cli/test/e2e/runtimes.spec.ts b/packages/nuxt-cli/test/e2e/runtimes.spec.ts index 36a3013a6..a177737dc 100644 --- a/packages/nuxt-cli/test/e2e/runtimes.spec.ts +++ b/packages/nuxt-cli/test/e2e/runtimes.spec.ts @@ -54,7 +54,8 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r return } - it('should serve the main page', async () => { + const assertNonWindowsBun = runtime === 'bun' && isWindows ? it.fails : it + assertNonWindowsBun('should serve the main page', async () => { const response = await fetch(server.url) expect(response.status).toBe(200) @@ -63,7 +64,6 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r expect(html).toContain('') }) - const assertNonWindowsBun = runtime === 'bun' && isWindows ? it.fails : it assertNonWindowsBun('should serve static assets', async () => { const response = await fetch(`${server.url}/favicon.ico`) expect(response.status).toBe(200)