import { a as green, c as url, i as gray, l as yellow, n as bold, o as magenta, r as cyan, s as red } from "./_chunks/_color.mjs"; import { parseArgs } from "node:util"; import { fileURLToPath, pathToFileURL } from "node:url"; import * as nodeHTTP$1 from "node:http"; import { dirname, extname, relative, resolve } from "node:path"; import { fork } from "node:child_process"; import { existsSync } from "node:fs"; //#region src/cli.ts const defaultEntries = [ "server", "index", "src/server", "src/index", "server/index" ]; const defaultExts = [ ".mts", ".ts", ".cts", ".js", ".mjs", ".cjs", ".jsx", ".tsx" ]; const args = process.argv.slice(2); const options = parseArgs$1(args); if (process.send) { setupProcessErrorHandlers(); await serve(); } async function main(mainOpts) { setupProcessErrorHandlers(); if (options._version) { console.log(await version()); process.exit(0); } if (options._help) { console.log(usage(mainOpts)); process.exit(options._help ? 0 : 1); } const isBun = !!process.versions.bun; const isDeno = !!process.versions.deno; const isNode = !isBun && !isDeno; const runtimeArgs = []; if (!options._prod) runtimeArgs.push("--watch"); if (isNode || isDeno) runtimeArgs.push(...[".env", options._prod ? ".env.production" : ".env.local"].filter((f) => existsSync(f)).map((f) => `--env-file=${f}`)); if (isNode) { const [major, minor] = process.versions.node.split("."); if (major === "22" && +minor >= 6) runtimeArgs.push("--experimental-strip-types"); if (options._import) runtimeArgs.push(`--import=${options._import}`); } const child = fork(fileURLToPath(import.meta.url), args, { execArgv: [...process.execArgv, ...runtimeArgs].filter(Boolean) }); child.on("error", (error) => { console.error("Error in child process:", error); process.exit(1); }); child.on("exit", (code) => { if (code !== 0) { console.error(`Child process exited with code ${code}`); process.exit(code); } }); let cleanupCalled = false; const cleanup = (signal, exitCode) => { if (cleanupCalled) return; cleanupCalled = true; try { child.kill(signal || "SIGTERM"); } catch (error) { console.error("Error killing child process:", error); } if (exitCode !== void 0) process.exit(exitCode); }; process.on("exit", () => cleanup("SIGTERM")); process.on("SIGINT", () => cleanup("SIGINT", 130)); process.on("SIGTERM", () => cleanup("SIGTERM", 143)); } async function serve() { try { if (!process.env.NODE_ENV) process.env.NODE_ENV = options._prod ? "production" : "development"; const entry = await loadEntry(options); const { serve: srvxServe } = entry._legacyNode ? await import("srvx/node") : await import("srvx"); const { serveStatic } = await import("srvx/static"); const { log } = await import("srvx/log"); const staticDir = resolve(options._dir, options._static); options._static = existsSync(staticDir) ? staticDir : ""; const server = srvxServe({ error: (error) => { console.error(error); return renderError(error); }, ...entry, middleware: [ log(), options._static ? serveStatic({ dir: options._static }) : void 0, ...entry.middleware || [] ].filter(Boolean) }); globalThis.__srvx__ = server; await server.ready(); await globalThis.__srvx_listen_cb__?.(); printInfo(entry); } catch (error) { console.error(error); process.exit(1); } } async function loadEntry(opts) { try { if (!opts._entry) for (const entry of defaultEntries) { for (const ext of defaultExts) { const entryPath = resolve(opts._dir, `${entry}${ext}`); if (existsSync(entryPath)) { opts._entry = entryPath; break; } } if (opts._entry) break; } if (!opts._entry) { const _error$1 = `No server entry file found.\nPlease specify an entry file or ensure one of the default entries exists (${defaultEntries.join(", ")}).`; return { _error: _error$1, fetch: () => renderError(_error$1, 404, "No Server Entry"), ...opts }; } const entryURL = opts._entry.startsWith("file://") ? opts._entry : pathToFileURL(resolve(opts._entry)).href; const { res: mod, listenHandler } = await interceptListen(() => import(entryURL)); let fetchHandler = mod.fetch || mod.default?.fetch || mod.default?.default?.fetch; let _legacyNode = false; if (!fetchHandler) { const nodeHandler = listenHandler || (typeof mod.default === "function" ? mod.default : void 0); if (nodeHandler) { _legacyNode = true; const { callNodeHandler } = await import("./_chunks/call2.mjs"); fetchHandler = (webReq) => callNodeHandler(nodeHandler, webReq); } } let _error; if (!fetchHandler) { _error = `The entry file "${relative(".", opts._entry)}" does not export a valid fetch handler.`; fetchHandler = () => renderError(_error, 500, "Invalid Entry"); } return { ...mod, ...mod.default, ...opts, _error, _legacyNode, fetch: fetchHandler }; } catch (error) { if (error?.code === "ERR_UNKNOWN_FILE_EXTENSION") { const message = String(error); if (/"\.(m|c)?ts"/g.test(message)) console.error(red(`\nMake sure you're using Node.js v22.18+ or v24+ for TypeScript support (current version: ${process.versions.node})\n\n`)); else if (/"\.(m|c)?tsx"/g.test(message)) console.error(red(`\nYou need a compatible loader for JSX support (Deno, Bun or srvx --register jiti/register)\n\n`)); } if (error instanceof Error) Error.captureStackTrace?.(error, serve); throw error; } } function renderError(error, status = 500, title = "Server Error") { let html = `${title}`; if (options._prod) html += `

${title}

Something went wrong while processing your request.

`; else html += `

${title}

${error instanceof Error ? error.stack || error.message : String(error)}
`; return new Response(html, { status, headers: { "Content-Type": "text/html; charset=utf-8" } }); } function printInfo(entry) { let entryInfo; if (options._entry) entryInfo = cyan("./" + relative(".", options._entry)); else entryInfo = gray(`(create ${bold(`server.ts`)} to enable)`); console.log(gray(`${bold(gray("λ"))} Server handler: ${entryInfo}`)); if (options._entry && entry._error) console.error(red(` ${entry._error}`)); let staticInfo; if (options._static) staticInfo = cyan("./" + relative(".", options._static) + "/"); else staticInfo = gray(`(add ${bold("public/")} dir to enable)`); console.log(gray(`${bold(gray("∘"))} Static files: ${staticInfo}`)); } async function interceptListen(cb) { const originalListen = nodeHTTP$1.Server.prototype.listen; let res; let listenHandler; try { nodeHTTP$1.Server.prototype.listen = function(arg1, arg2) { listenHandler = this._events.request; if (Array.isArray(listenHandler)) listenHandler = listenHandler[0]; nodeHTTP$1.Server.prototype.listen = originalListen; globalThis.__srvx_listen_cb__ = [arg1, arg2].find((arg) => typeof arg === "function"); return new Proxy({}, { get(_, prop) { return globalThis.__srvx__?.node?.server?.[prop]; } }); }; res = await cb(); } finally { nodeHTTP$1.Server.prototype.listen = originalListen; } return { res, listenHandler }; } async function version() { return `srvx ${globalThis.__srvx_version__ || "unknown"}\n${runtime()}`; } function runtime() { if (process.versions.bun) return `bun ${process.versions.bun}`; else if (process.versions.deno) return `deno ${process.versions.deno}`; else return `node ${process.versions.node}`; } function parseArgs$1(args$1) { const { values, positionals } = parseArgs({ args: args$1, allowPositionals: true, options: { help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" }, prod: { type: "boolean" }, port: { type: "string", short: "p" }, host: { type: "string", short: "H" }, static: { type: "string", short: "s" }, import: { type: "string" }, tls: { type: "boolean" }, cert: { type: "string" }, key: { type: "string" } } }); const input = positionals[0] || "."; let dir; let entry = ""; if (extname(input) === "") dir = resolve(input); else { entry = resolve(input); dir = dirname(entry); } if (!existsSync(dir)) { console.error(red(`Directory "${dir}" does not exist.\n`)); process.exit(1); } return { _dir: dir, _entry: entry, _prod: values.prod ?? process.env.NODE_ENV === "production", _help: values.help, _static: values.static || "public", _version: values.version, _import: values.import, port: values.port ? Number.parseInt(values.port, 10) : void 0, hostname: values.host, tls: values.tls ? { cert: values.cert, key: values.key } : void 0 }; } function example() { const useTs = !options._entry || options._entry.endsWith(".ts"); return `${bold(gray("// server.ts"))} ${magenta("export default")} { ${cyan("fetch")}(req${useTs ? ": Request" : ""}) { ${magenta("return")} new Response(${green("\"Hello, World!\"")}); } }`; } function usage(mainOpts) { const command = mainOpts.command; return ` ${cyan(command)} - Start an HTTP server with the specified entry path. ${bold("USAGE")} ${existsSync(options._entry) ? "" : `\n${example()}\n`} ${gray("# srvx [options] [entry]")} ${gray("$")} ${cyan(command)} ${gray("./server.ts")} ${gray("# Start development server")} ${gray("$")} ${cyan(command)} --prod ${gray("# Start production server")} ${gray("$")} ${cyan(command)} --port=8080 ${gray("# Listen on port 8080")} ${gray("$")} ${cyan(command)} --host=localhost ${gray("# Bind to localhost only")} ${gray("$")} ${cyan(command)} --import=jiti/register ${gray(`# Enable ${url("jiti", "https://github.com/unjs/jiti")} loader`)} ${gray("$")} ${cyan(command)} --tls --cert=cert.pem --key=key.pem ${gray("# Enable TLS (HTTPS/HTTP2)")} ${bold("ARGUMENTS")} ${yellow("")} Server entry path to serve. Default: ${defaultEntries.map((e) => cyan(e)).join(", ")} ${gray(`(${defaultExts.join(",")})`)} ${bold("OPTIONS")} ${green("-p, --port")} ${yellow("")} Port to listen on (default: ${yellow("3000")}) ${green("--host")} ${yellow("")} Host to bind to (default: all interfaces) ${green("-s, --static")} ${yellow("")} Serve static files from the specified directory (default: ${yellow("public")}) ${green("--prod")} Run in production mode (no watch, no debug) ${green("--import")} ${yellow("")} ES module to preload ${green("--tls")} Enable TLS (HTTPS/HTTP2) ${green("--cert")} ${yellow("")} TLS certificate file ${green("--key")} ${yellow("")} TLS private key file ${green("-h, --help")} Show this help message ${green("-v, --version")} Show server and runtime versions ${bold("ENVIRONMENT")} ${green("PORT")} Override port ${green("HOST")} Override host ${green("NODE_ENV")} Set to ${yellow("production")} for production mode. ➤ ${url("Documentation", mainOpts.docs || "https://srvx.h3.dev")} ➤ ${url("Report issues", mainOpts.issues || "https://github.com/h3js/srvx/issues")} `.trim(); } function setupProcessErrorHandlers() { process.on("uncaughtException", (error) => { console.error("Uncaught exception:", error); process.exit(1); }); process.on("unhandledRejection", (reason) => { console.error("Unhandled rejection:", reason); process.exit(1); }); } //#endregion export { main, usage };