2025-09-05 14:59:21 +08:00

377 lines
13 KiB
JavaScript

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 };