11import type { Nuxt , NuxtConfig } from '@nuxt/schema'
2- import type { IncomingMessage , ServerResponse } from 'node:http '
2+ import type { ViteDevServer } from 'vite '
33import { mkdir , mkdtemp , rm , writeFile } from 'node:fs/promises'
44import { createServer } from 'node:http'
55import { tmpdir } from 'node:os'
66import { join } from 'node:path'
77import { fileURLToPath } from 'node:url'
88import { loadNuxt } from 'nuxt'
9+ import { createServer as createViteServer } from 'vite'
910import { afterEach , describe , expect , it } from 'vitest'
1011
1112const onboardModule = fileURLToPath ( new URL ( '../src/module.ts' , import . meta. url ) )
@@ -56,26 +57,24 @@ function onboardScripts(nuxt: Nuxt) {
5657 return ( nuxt . options . app . head . script ?? [ ] ) . filter ( script => typeof script === 'object' && script . src === '/__devtools/embedded.js' )
5758}
5859
59- type Middleware = ( req : IncomingMessage , res : ServerResponse , next : ( ) => void ) => void
60-
61- /** Serves what the module mounts on the client Vite server; resolves to its origin. */
62- async function serveViteMiddleware ( nuxt : Nuxt ) : Promise < string > {
63- const mounted : Middleware [ ] = [ ]
64- // Only `middlewares.use` is read; the rest of a real ViteDevServer is irrelevant here.
65- const viteServer = { middlewares : { use : ( fn : Middleware ) => mounted . push ( fn ) } } as never
60+ /** Serves what the module mounts on a real client Vite server; resolves to its origin. */
61+ async function serveViteMiddleware ( nuxt : Nuxt ) : Promise < { origin : string , viteServer : ViteDevServer } > {
62+ const viteServer = await createViteServer ( { configFile : false , logLevel : 'silent' , server : { middlewareMode : true , ws : false } } )
63+ cleanups . push ( ( ) => viteServer . close ( ) )
64+ // Mount only what the module adds, not Vite's own transform middlewares.
65+ viteServer . middlewares . stack . length = 0
6666 await nuxt . callHook ( 'vite:serverCreated' , viteServer , { isClient : true , isServer : false } )
67- const [ middleware ] = mounted
68- if ( ! middleware )
69- throw new Error ( 'onboarding middleware was not mounted' )
70-
71- const server = createServer ( ( req , res ) => middleware ( req , res , ( ) => {
72- res . statusCode = 404
73- res . end ( )
74- } ) )
67+
68+ const server = createServer ( viteServer . middlewares )
7569 await new Promise < void > ( resolve => server . listen ( 0 , '127.0.0.1' , resolve ) )
7670 cleanups . push ( ( ) => new Promise ( resolve => server . close ( ( ) => resolve ( ) ) ) )
7771 const { port } = server . address ( ) as { port : number } // listening on a TCP port, never a pipe
78- return `http://127.0.0.1:${ port } `
72+ return { origin : `http://127.0.0.1:${ port } ` , viteServer }
73+ }
74+
75+ /** Nuxt's dev handler only passes a URL to Vite when a middleware is mounted on a path other than `/`. */
76+ function nuxtRoutesToVite ( viteServer : ViteDevServer , url : string ) {
77+ return viteServer . middlewares . stack . some ( ( { route } ) => route . length > 1 && url . startsWith ( route ) )
7978}
8079
8180const onboardStatus = ( origin : string ) => fetch ( `${ origin } /__devtools/__onboard/status` ) . then ( r => r . json ( ) )
@@ -104,7 +103,12 @@ describe('@nuxt/devtools-onboard', () => {
104103 expect ( nuxt . options . _requiredModules [ '@nuxt/devtools' ] ) . toBeUndefined ( )
105104 expect ( onboardScripts ( nuxt ) ) . toHaveLength ( 1 )
106105
107- const status = await onboardStatus ( await serveViteMiddleware ( nuxt ) )
106+ const { origin, viteServer } = await serveViteMiddleware ( nuxt )
107+ expect ( nuxtRoutesToVite ( viteServer , '/__devtools/embedded.js' ) ) . toBe ( true )
108+ const script = await fetch ( `${ origin } /__devtools/embedded.js` )
109+ expect ( script . headers . get ( 'content-type' ) ) . toMatch ( / j a v a s c r i p t / )
110+
111+ const status = await onboardStatus ( origin )
108112 expect ( status . state ) . toBe ( 'idle' )
109113 // No lockfile in the fixture, so the package manager falls back to npm.
110114 expect ( status . command . slice ( 0 , 3 ) ) . toEqual ( [ 'npm' , 'i' , '-D' ] )
@@ -114,7 +118,7 @@ describe('@nuxt/devtools-onboard', () => {
114118
115119 it ( 'refuses to install when nobody can confirm it in the terminal' , async ( ) => {
116120 const nuxt = await bootNuxt ( { dev : true } , { withoutDevtools : true } )
117- const origin = await serveViteMiddleware ( nuxt )
121+ const { origin } = await serveViteMiddleware ( nuxt )
118122
119123 // Vitest workers get a piped stdin, so no terminal can answer the question.
120124 const install = await fetch ( `${ origin } /__devtools/__onboard/install` , { method : 'POST' } )
0 commit comments