import { fork } from 'node:child_process'; import process from 'node:process'; import { defineCommand } from 'citty'; import { isSocketSupported } from 'get-port-please'; import { createProxyServer } from 'httpxy'; import { listen } from 'listhen'; import { getArgs, parseArgs } from 'listhen/cli'; import { resolve } from 'pathe'; import { satisfies } from 'semver'; import { isTest, isBun, isDeno } from 'std-env'; import { i as initialize, r as resolveLoadingTemplate, a as renderError, b as isSocketURL, p as parseSocketURL } from './index.mjs'; import { s as showVersions } from '../shared/cli.Dz2be-Ai.mjs'; import { o as overrideEnv } from '../shared/cli.BEUGgaW4.mjs'; import { l as loadKit } from '../shared/cli.qKvs7FJ2.mjs'; import { l as logger } from '../shared/cli.B9AmABr3.mjs'; import { e as extendsArgs, b as envNameArgs, l as legacyRootDirArgs, d as dotEnvArgs, a as logLevelArgs, c as cwdArgs } from '../shared/cli.CTXRG5Cu.mjs'; import 'defu'; import 'node:http'; import 'node:events'; import 'node:fs'; import 'node:fs/promises'; import 'node:url'; import 'exsolve'; import 'h3'; import 'perfect-debounce'; import 'ufo'; import '../shared/cli.pLQ0oPGc.mjs'; import '../shared/cli.At9IMXtr.mjs'; import 'ohash'; import 'youch'; import 'consola/utils'; import 'consola'; import 'node:path'; const startTime = Date.now(); const forkSupported = !isTest && (!isBun || isBunForkSupported()); const listhenArgs = getArgs(); const command = defineCommand({ meta: { name: "dev", description: "Run Nuxt development server" }, args: { ...cwdArgs, ...logLevelArgs, ...dotEnvArgs, ...legacyRootDirArgs, ...envNameArgs, ...extendsArgs, clear: { type: "boolean", description: "Clear console on restart", negativeDescription: "Disable clear console on restart" }, fork: { type: "boolean", description: forkSupported ? "Disable forked mode" : "Enable forked mode", negativeDescription: "Disable forked mode", default: forkSupported, alias: ["f"] }, ...{ ...listhenArgs, "port": { ...listhenArgs.port, description: "Port to listen on (default: `NUXT_PORT || NITRO_PORT || PORT || nuxtOptions.devServer.port`)", alias: ["p"] }, "open": { ...listhenArgs.open, alias: ["o"], default: false }, "host": { ...listhenArgs.host, alias: ["h"], description: "Host to listen on (default: `NUXT_HOST || NITRO_HOST || HOST || nuxtOptions.devServer?.host`)" }, "clipboard": { ...listhenArgs.clipboard, default: false }, "https.domains": { ...listhenArgs["https.domains"], description: "Comma separated list of domains and IPs, the autogenerated certificate should be valid for (https: true)" } }, sslCert: { type: "string", description: "(DEPRECATED) Use `--https.cert` instead." }, sslKey: { type: "string", description: "(DEPRECATED) Use `--https.key` instead." } }, async run(ctx) { overrideEnv("development"); const cwd = resolve(ctx.args.cwd || ctx.args.rootDir); showVersions(cwd); const { loadNuxtConfig } = await loadKit(cwd); const nuxtOptions = await loadNuxtConfig({ cwd, dotenv: { cwd, fileName: ctx.args.dotenv }, envName: ctx.args.envName, // c12 will fall back to NODE_ENV overrides: { dev: true, logLevel: ctx.args.logLevel, ...ctx.args.extends && { extends: ctx.args.extends }, ...ctx.data?.overrides } }); const listenOptions = resolveListenOptions(nuxtOptions, ctx.args); if (!ctx.args.fork) { const { listener, close: close2 } = await initialize({ cwd, args: ctx.args, hostname: listenOptions.hostname, public: listenOptions.public, publicURLs: void 0, proxy: { https: listenOptions.https } }, { data: ctx.data }, listenOptions); return { listener, async close() { await close2(); await listener.close(); } }; } const devProxy = await createDevProxy(cwd, nuxtOptions, listenOptions); const nuxtSocketEnv = process.env.NUXT_SOCKET ? process.env.NUXT_SOCKET === "1" : void 0; const useSocket = nuxtSocketEnv ?? (nuxtOptions._majorVersion === 4 && await isSocketSupported()); const urls = await devProxy.listener.getURLs(); const { onRestart, onReady, close } = await initialize({ cwd, args: ctx.args, hostname: listenOptions.hostname, public: listenOptions.public, publicURLs: urls.map((r) => r.url), proxy: { url: devProxy.listener.url, urls, https: devProxy.listener.https, addr: devProxy.listener.address } // if running with nuxt v4 or `NUXT_SOCKET=1`, we use the socket listener // otherwise pass 'true' to listen on a random port instead }, {}, useSocket ? void 0 : true); onReady((address) => devProxy.setAddress(address)); const fork2 = startSubprocess(cwd, ctx.args, ctx.rawArgs, listenOptions); onRestart(async (devServer) => { const [subprocess] = await Promise.all([ fork2, devServer.close().catch(() => { }) ]); await subprocess.initialize(devProxy, useSocket); }); return { listener: devProxy.listener, async close() { await close(); const subprocess = await fork2; subprocess.kill(0); await devProxy.listener.close(); } }; } }); async function createDevProxy(cwd, nuxtOptions, listenOptions) { let loadingMessage = "Nuxt dev server is starting..."; let error; let address; let loadingTemplate = nuxtOptions.devServer.loadingTemplate; const proxy = createProxyServer({}); proxy.on("proxyReq", (proxyReq, req) => { if (!proxyReq.hasHeader("x-forwarded-for")) { const address2 = req.socket.remoteAddress; if (address2) { proxyReq.appendHeader("x-forwarded-for", address2); } } if (!proxyReq.hasHeader("x-forwarded-port")) { const localPort = req?.socket?.localPort; if (localPort) { proxyReq.setHeader("x-forwarded-port", req.socket.localPort); } } if (!proxyReq.hasHeader("x-forwarded-Proto")) { const encrypted = req?.connection?.encrypted; proxyReq.setHeader("x-forwarded-proto", encrypted ? "https" : "http"); } }); const listener = await listen((req, res) => { if (error) { renderError(req, res, error); return; } if (!address) { res.statusCode = 503; res.setHeader("Content-Type", "text/html"); res.setHeader("Cache-Control", "no-store"); if (loadingTemplate) { res.end(loadingTemplate({ loading: loadingMessage })); return; } async function resolveLoadingMessage() { loadingTemplate = await resolveLoadingTemplate(cwd); res.end(loadingTemplate({ loading: loadingMessage })); } return resolveLoadingMessage(); } const target = isSocketURL(address) ? parseSocketURL(address) : address; proxy.web(req, res, { target }); }, listenOptions); listener.server.on("upgrade", (req, socket, head) => { if (!address) { if (!socket.destroyed) { socket.end(); } return; } const target = isSocketURL(address) ? parseSocketURL(address) : address; return proxy.ws(req, socket, { target, xfwd: true }, head).catch(() => { if (!socket.destroyed) { socket.end(); } }); }); return { listener, setAddress: (_addr) => { address = _addr; }, setLoadingMessage: (_msg) => { loadingMessage = _msg; }, setError: (_error) => { error = _error; }, clearError() { error = void 0; } }; } async function startSubprocess(cwd, args, rawArgs, listenOptions) { let childProc; let devProxy; let ready; const kill = (signal) => { if (childProc) { childProc.kill(signal === 0 && isDeno ? "SIGTERM" : signal); childProc = void 0; } }; async function initialize2(proxy, socket) { devProxy = proxy; const urls = await devProxy.listener.getURLs(); await ready; childProc.send({ type: "nuxt:internal:dev:context", socket, context: { cwd, args, hostname: listenOptions.hostname, public: listenOptions.public, publicURLs: urls.map((r) => r.url), proxy: { url: devProxy.listener.url, urls, https: devProxy.listener.https } } }); } async function restart() { devProxy?.clearError(); if (process.platform === "win32") { kill("SIGTERM"); } else { kill("SIGHUP"); } childProc = fork(globalThis.__nuxt_cli__.devEntry, rawArgs, { execArgv: ["--enable-source-maps", process.argv.find((a) => a.includes("--inspect"))].filter(Boolean), env: { ...process.env, __NUXT__FORK: "true" } }); childProc.on("close", (errorCode) => { if (errorCode) { process.exit(errorCode); } }); ready = new Promise((resolve2, reject) => { childProc.on("error", reject); childProc.on("message", (message) => { if (message.type === "nuxt:internal:dev:fork-ready") { resolve2(); } else if (message.type === "nuxt:internal:dev:ready") { devProxy.setAddress(message.address); if (startTime) { logger.debug(`Dev server ready for connections in ${Date.now() - startTime}ms`); } } else if (message.type === "nuxt:internal:dev:loading") { devProxy.setAddress(void 0); devProxy.setLoadingMessage(message.message); devProxy.clearError(); } else if (message.type === "nuxt:internal:dev:loading:error") { devProxy.setAddress(void 0); devProxy.setError(message.error); } else if (message.type === "nuxt:internal:dev:restart") { restart(); } else if (message.type === "nuxt:internal:dev:rejection") { logger.info(`Restarting Nuxt due to error: \`${message.message}\``); restart(); } }); }); } for (const signal of [ "exit", "SIGTERM", "SIGINT", "SIGQUIT" ]) { process.once(signal, () => { kill(signal === "exit" ? 0 : signal); }); } await restart(); return { initialize: initialize2, restart, kill }; } function resolveListenOptions(nuxtOptions, args) { const _port = args.port ?? args.p ?? process.env.NUXT_PORT ?? process.env.NITRO_PORT ?? process.env.PORT ?? nuxtOptions.devServer.port; const _hostname = typeof args.host === "string" ? args.host : args.host === true ? "" : process.env.NUXT_HOST ?? process.env.NITRO_HOST ?? process.env.HOST ?? (nuxtOptions.devServer?.host || void 0) ?? void 0; const _public = args.public ?? (_hostname && !["localhost", "127.0.0.1", "::1"].includes(_hostname)) ? true : void 0; const _httpsCert = args["https.cert"] || args.sslCert || process.env.NUXT_SSL_CERT || process.env.NITRO_SSL_CERT || typeof nuxtOptions.devServer.https !== "boolean" && nuxtOptions.devServer.https && "cert" in nuxtOptions.devServer.https && nuxtOptions.devServer.https.cert || ""; const _httpsKey = args["https.key"] || args.sslKey || process.env.NUXT_SSL_KEY || process.env.NITRO_SSL_KEY || typeof nuxtOptions.devServer.https !== "boolean" && nuxtOptions.devServer.https && "key" in nuxtOptions.devServer.https && nuxtOptions.devServer.https.key || ""; const _httpsPfx = args["https.pfx"] || typeof nuxtOptions.devServer.https !== "boolean" && nuxtOptions.devServer.https && "pfx" in nuxtOptions.devServer.https && nuxtOptions.devServer.https.pfx || ""; const _httpsPassphrase = args["https.passphrase"] || typeof nuxtOptions.devServer.https !== "boolean" && nuxtOptions.devServer.https && "passphrase" in nuxtOptions.devServer.https && nuxtOptions.devServer.https.passphrase || ""; const httpsEnabled = !!(args.https ?? nuxtOptions.devServer.https); const _listhenOptions = parseArgs({ ...args, "open": args.o || args.open, "https": httpsEnabled, "https.cert": _httpsCert, "https.key": _httpsKey, "https.pfx": _httpsPfx, "https.passphrase": _httpsPassphrase }); const httpsOptions = httpsEnabled && { ...nuxtOptions.devServer.https, ..._listhenOptions.https }; return { ..._listhenOptions, port: _port, hostname: _hostname, public: _public, https: httpsOptions, baseURL: nuxtOptions.app.baseURL.startsWith("./") ? nuxtOptions.app.baseURL.slice(1) : nuxtOptions.app.baseURL }; } function isBunForkSupported() { const bunVersion = globalThis.Bun.version; return satisfies(bunVersion, ">=1.2"); } export { command as default };