377 lines
13 KiB
JavaScript
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 };
|