diff --git a/docs/1.guide/10.cli.md b/docs/1.guide/10.cli.md index 07004419..47737e70 100644 --- a/docs/1.guide/10.cli.md +++ b/docs/1.guide/10.cli.md @@ -60,58 +60,191 @@ $ echo '{"name":"foo"}' | srvx fetch -d @- /api # Body from stdin COMMON OPTIONS - --entry Server entry file to use - --dir Working directory for resolving entry file - -h, --help Show this help message - --version Show server and runtime versions + --entry Server entry file to use + --dir Working directory for resolving entry file + -h, --help Show this help message + --version Show server and runtime versions SERVE OPTIONS - -p, --port Port to listen on (default: 3000) - --host Host to bind to (default: all interfaces) - -s, --static Serve static files from the specified directory (default: public) - --prod Run in production mode (no watch, no debug) - --import ES module to preload - --tls Enable TLS (HTTPS/HTTP2) - --cert TLS certificate file - --key TLS private key file + -p, --port Port to listen on (default: 3000) + --host, --hostname Host to bind to (default: all interfaces) + -s, --static Serve static files from the specified directory (default: public) + --prod Run in production mode (no watch, no debug) + --import ES module to preload + --tls Enable TLS (HTTPS/HTTP2) + --cert TLS certificate file + --key TLS private key file FETCH OPTIONS - -X, --request HTTP method (default: GET, or POST if body is provided) - -H, --header
Add header (format: "Name: Value", can be used multiple times) - -d, --data Request body (use @- for stdin, @file for file) - -v, --verbose Show request and response headers + -X, --method HTTP method (default: GET, or POST if body is provided; --request is a curl alias) + -H, --header
Add header (format: "Name: Value", can be used multiple times) + -d, --data Request body (use @- for stdin, @file for file) + --host Host for a schemeless URL/path (default: localhost) + --tls Use https for a schemeless URL/path + -v, --verbose Show request and response headers + + Exits with code 22 on a non-2xx response (like curl --fail). ENVIRONMENT - PORT Override port - HOST Override host - NODE_ENV Set to production for production mode. + PORT Default port to listen on + HOST Default host to bind to + NODE_ENV Set to production for production mode. ``` +## Port and host precedence + +The port and host are resolved with the following precedence (highest first): + +1. CLI flag (`--port` / `--host` / `--hostname`) +2. Module option (`port` / `hostname` exported from your server entry) +3. Environment variable (`PORT` / `HOST`) +4. Default (`3000` / all interfaces) + +## Exit codes + +In **fetch mode**, `srvx fetch` exits with code **`22`** for any non-2xx response, and `0` for a 2xx response. + +## Runtime notes + +> [!NOTE] +> The `--import` flag preloads an ES module (e.g. a loader like `jiti/register`). It is applied on **Node.js and Bun only** β€” on **Deno** it is silently ignored, since Deno does not support Node's `--import` preload flag. + ## Serving static files -The CLI can serve a directory of static files, similar to [`serve`](https://github.com/vercel/serve). No server entry is required — point `--static` at any folder: +The CLI can serve a directory of static files. No server entry is required — point `--static` at any folder: ```bash npx srvx --static ./dist ``` -If `--static` is omitted, srvx serves files from a `public/` directory when one exists. When both a server entry and a static directory are present, static files take priority and unmatched requests fall through to your handler. +If `--static` is omitted, srvx serves files from a `public/` directory when one exists, and skips static serving when it does not. Passing `--static` explicitly asserts the directory exists: srvx errors out if it is missing, rather than starting up and serving nothing. + +When both a server entry and a static directory are present, static files take priority and unmatched requests fall through to your handler. Static serving includes automatic `index.html` resolution, `.html` extension fallback (e.g. `/about` β†’ `about.html`), common MIME types, gzip/Brotli compression, and path-traversal protection. -For programmatic usage, import the [`serveStatic`](https://github.com/h3js/srvx/blob/main/src/static.ts) middleware: +## Programmatic API + +Both CLI modes are built on `srvx/loader`. The same loader is available to you, so you can build a dev server, a test harness, or a framework CLI that accepts any server entry srvx accepts — without reimplementing entry discovery or handler detection. -```ts [server.ts] +```ts +import { loadServerEntry } from "srvx/loader"; import { serve } from "srvx"; -import { serveStatic } from "srvx/static"; -serve({ - middleware: [serveStatic({ dir: "public" })], - fetch: () => new Response("Not found", { status: 404 }), +const loaded = await loadServerEntry({ entry: "./server.ts" }); + +if (loaded.notFound) { + throw new Error("No server entry found"); +} + +if (!loaded.fetch) { + throw new Error("Server entry exports no fetch handler"); +} + +serve({ fetch: loaded.fetch }); +``` + +`loadServerEntry(opts)` imports a server entry module and resolves a web `fetch` handler from it. It never throws for a missing entry — check `notFound` on the result instead. + +### Entry resolution + +When `entry` is set, it is resolved against `dir` (or the current working directory). A `file://` URL is used as-is. + +When `entry` is omitted, the loader searches `dir` for the first match of each of these base names, trying `.mjs`, `.js`, `.mts`, then `.ts` for each: + +1. `server` +2. `server/index` +3. `src/server` +4. `server/server` + +Both lists are exported as `defaultEntries` and `defaultExts` if you need to reuse them (for example, to build a file watcher). If nothing matches, the result is `{ notFound: true }` with no `fetch` and no `module`. + +### Handler resolution + +Once the module is imported, the loader looks for a handler in this order: + +1. `module.fetch` +2. `module.default.fetch` +3. `module.default.default.fetch` (a double-default from a transpiled CommonJS entry) +4. The `fetch` of a server the entry created by calling `serve()` (see [Loading entries that start a server](#loading-entries-that-start-a-server)) +5. `module.default`, if it is a function taking fewer than two arguments + +If none match and `nodeCompat` is not disabled, a legacy Node.js `(req, res)` handler is wrapped into a fetch handler and `nodeCompat: true` is set on the result. Both `module.default` and a handler captured from `http.createServer()` are eligible. + +### Loading entries that start a server + +Most server entries do not export a handler — they start listening as a side effect of being imported. The loader intercepts the listen call, so importing the entry gives you its handler **without binding a port**. + +If the entry calls srvx's own `serve()`, the loader hands you back that server instance as `srvxServer`, and its handler as `fetch`. The server is fully constructed but never listens. Because the entry's `serve()` call runs during the import, the instance does not exist yet when you pass options — use a getter to close the loop: + +```ts +let server: Server | undefined; + +const loaded = await loadServerEntry({ + entry: "./server.ts", + get srvxServer() { + return server; + }, }); + +server = serve({ fetch: loaded.fetch!, port: 3000 }); +``` + +Anything the entry exports alongside its handler is on `loaded.module`, so you can read its [server options](/guide/options) and merge them with your own. + +If the entry instead calls `http.createServer(handler).listen()`, the loader captures `handler` and lets the entry run to completion without binding a port. Its listen callback still fires, so setup code that runs after listen is not skipped. + +Pass `interceptHttpListen: false` to opt out. Note that this disables **both** interceptions — an entry that calls `serve()` will then really start listening on import. + +> [!NOTE] +> Entries are intercepted one at a time — concurrent `loadServerEntry` calls are queued rather than run in parallel. + +### Reloading an entry + +The handler of an intercepted entry comes from a **side effect** of the import, and `import()` caches modules per URL. Loading the same entry twice in one process returns the cached module without re-running it, so the interception never fires and `fetch` comes back `undefined`. Entries that _export_ a handler are unaffected — their handler lives on the cached module. + +If you need to load an entry more than once (a watch mode, for example), give each load a unique entry URL: + +```ts +import { pathToFileURL } from "node:url"; + +const url = pathToFileURL(resolve("./server.ts")).href; +const loaded = await loadServerEntry({ entry: `${url}?t=${Date.now()}` }); ``` + +The query is ignored when checking that the file exists, and is preserved in the returned `url`. + +> [!NOTE] +> Each reload leaves the previous module in the ESM cache for the lifetime of the process. For a long-running watch mode, restart a child process instead and let the runtime's own `--watch` handle reloads. + +### `LoadOptions` + +- **`entry`** — Path or `file://` URL of the server entry file. If omitted, the [default entries](#entry-resolution) are searched. +- **`dir`** — Base directory for resolving `entry` and for auto-discovery. Default is `"."`. +- **`nodeCompat`** — Set to `false` to disable upgrading a legacy Node.js `(req, res)` handler. The result then has no `fetch` when the entry only exports a Node.js handler. Default is `true`. +- **`interceptHttpListen`** — Set to `false` to import the entry without intercepting listen calls. Default is `true`. +- **`srvxServer`** — The srvx server instance to hand to the entry when its `serve()` call is intercepted. Define it as a getter when the instance is created after loading. +- **`nodeServer`** — The `node:http` server instance to return from an intercepted `listen()`. Defaults to `srvxServer`'s underlying Node.js server, or a stub that forwards to it once it exists. +- **`onLoad`** — Hook called with the imported module before the handler is resolved. Return a value to replace the module. + +### `LoadedServerEntry` + +- **`fetch`** — The resolved web fetch handler, or `undefined` if the entry exports none. +- **`module`** — The raw imported module. Use it to read options the entry exports next to its handler. +- **`url`** — The resolved `file://` URL of the loaded entry. +- **`notFound`** — `true` when no entry file could be located. `fetch` and `module` are then `undefined`. +- **`nodeCompat`** — `true` when the handler was upgraded from a legacy Node.js `(req, res)` handler. Serve it with `srvx/node`. +- **`srvxServer`** — The server instance the entry created via `serve()`, if that call was intercepted. + +### TypeScript entries + +The loader imports entries with a plain dynamic `import()`, so TypeScript support comes from the runtime: + +- **Node.js** — requires v22.18+ or v24+ for `.ts` entries. The loader throws a targeted error on older versions rather than a raw `ERR_UNKNOWN_FILE_EXTENSION`. +- **Deno** and **Bun** — supported natively. +- **JSX** — needs a loader such as [jiti](https://github.com/unjs/jiti), preloaded with the [`--import`](#usage) flag. diff --git a/docs/1.guide/2.handler.md b/docs/1.guide/2.handler.md index 66905912..e26c30e2 100644 --- a/docs/1.guide/2.handler.md +++ b/docs/1.guide/2.handler.md @@ -101,7 +101,7 @@ import { serve } from "srvx"; serve({ fetch: (request) => { if (request.runtime.node) { - console.log("Node.js req path:", request.runtime.node?.req.path); + console.log("Node.js req url:", request.runtime.node?.req.url); request.runtime.node.res.statusCode = 418; // I'm a teapot! } return new Response("ok"); diff --git a/docs/1.guide/3.server.md b/docs/1.guide/3.server.md index 77deb8a6..5e8ee16c 100644 --- a/docs/1.guide/3.server.md +++ b/docs/1.guide/3.server.md @@ -37,16 +37,6 @@ Access to the sever options set during initialization. Get the computed server listening URL. -### `server.addr` - -Listening address (hostname or ipv4/ipv6). - -### `server.port` - -Listening port number. - -:read-more{to="/guide/options#port-required"} - ### `server.waitUntil?` Register a background task that the server should await before closing. This is the same function as [`request.waitUntil`](/guide/handler#requestwaituntil) but available at the server level for use outside of request handlers. @@ -98,9 +88,6 @@ await server.close(true); ## Access to the Underlying Server -> [!NOTE] -> srvx tries to translate most common options to op level properties. This is only for advanced usage. - ### `server.bun.server` Access to the underlying Bun server instance when running in Bun. @@ -109,7 +96,7 @@ Access to the underlying Bun server instance when running in Bun. ### `server.deno.server` -Access to the underlying Bun server instance when running in Deno. +Access to the underlying Deno server instance when running in Deno. :read-more{to="https://docs.deno.com/api/deno/~/Deno.HttpServer"} diff --git a/docs/1.guide/4.middleware.md b/docs/1.guide/4.middleware.md index 8d0046c7..fb00ccb7 100644 --- a/docs/1.guide/4.middleware.md +++ b/docs/1.guide/4.middleware.md @@ -37,6 +37,98 @@ serve({ }); ``` -## Built-in plugins +## Order of execution -srvx ships an [`mtls()`](/guide/tls#mutual-tls-mtls) plugin from `srvx/mtls` that requests a client certificate (mutual TLS) and exposes it on [`request.tls`](/guide/handler#requesttls). It requires the [Node.js adapter](/guide/node). +Middleware run in the order they appear in the `middleware` array, each wrapping the next, with your `fetch` handler at the center. A middleware that returns a response **without** calling `next()` short-circuits the rest of the chain β€” nothing after it runs. + +Plugins are applied in `plugins` array order, before the server starts listening. A plugin that pushes middleware appends to the end of the array, so `middleware` entries always run first. + +## Built-in middleware and plugins + +srvx ships several optional extensions as separate subpath imports. All of them are opt-in β€” importing `srvx` alone pulls in none of them. + +| Import | Export | Kind | Runtimes | +| -------------- | ----------------- | ---------- | --------------- | +| `srvx/log` | `log()` | Middleware | All | +| `srvx/static` | `serveStatic()` | Middleware | Node, Deno, Bun | +| `srvx/mtls` | `mtls()` | Plugin | Node | +| `srvx/tracing` | `tracingPlugin()` | Plugin | Node, Deno, Bun | + +### Request logging + +`log()` from `srvx/log` prints one colored line per request with the method, URL, status, and duration. + +```ts [server.ts] +import { serve } from "srvx"; +import { log } from "srvx/log"; + +serve({ + middleware: [log()], + fetch: () => new Response("πŸ‘‹ Hello there."), +}); +``` + +```sh +[10:32:03 AM] GET http://localhost:3000/ [200] (1.42ms) +``` + +The duration is measured around `next()`, so place `log()` first in the array for it to cover the whole chain. The [CLI](/guide/cli) enables this middleware automatically. + +### Static files + +`serveStatic()` from `srvx/static` serves files from a directory, with `index.html` resolution, `.html` extension fallback (`/about` β†’ `about.html`), common MIME types, gzip/Brotli compression, and path-traversal protection. + +```ts [server.ts] +import { serve } from "srvx"; +import { serveStatic } from "srvx/static"; + +serve({ + middleware: [serveStatic({ dir: "public" })], + fetch: () => new Response("Not found", { status: 404 }), +}); +``` + +When no file matches the request, it calls `next()` β€” so your handler acts as the fallback for unmatched paths. + +**`serveStatic()` options:** + +- `dir`: The directory to serve files from (required). +- `methods`: HTTP methods to serve (default `["GET", "HEAD"]`). Other methods fall through to `next()`. +- `renderHTML`: A function receiving `{ request, html, filename }` for every `.html` file, returning the `Response` to send. Use it to inject or template markup before serving. + +> [!NOTE] +> Despite the runtime-neutral name, `srvx/static` is **Node-API-only** β€” it uses `node:fs` and `node:zlib` internally, so it works only on runtimes with Node.js compatibility (Node, Deno, Bun). + +See [Serving static files](/guide/cli#serving-static-files) for the equivalent CLI flag. + +### Mutual TLS + +[`mtls()`](/guide/tls#mutual-tls-mtls) from `srvx/mtls` requests a client certificate during the TLS handshake and exposes it on [`request.tls`](/guide/handler#requesttls). It requires the [Node.js adapter](/guide/node). + +### Tracing + +`tracingPlugin()` from `srvx/tracing` wraps your `fetch` handler and each middleware with [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html) instrumentation, publishing to the `srvx.request` and `srvx.middleware` [tracing channels](https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel). + +```ts [server.ts] +import { serve } from "srvx"; +import { tracingPlugin } from "srvx/tracing"; +import { tracingChannel } from "node:diagnostics_channel"; + +tracingChannel("srvx.request").subscribe({ + start: ({ request }) => console.log(`[start] ${request.url}`), + asyncEnd: ({ request }) => console.log(`[end] ${request.url}`), + error: ({ request, error }) => console.error(`[error] ${request.url}`, error), +}); + +serve({ + plugins: [tracingPlugin()], + fetch: () => new Response("πŸ‘‹ Hello there."), +}); +``` + +Each event carries `{ server, request }`, plus `{ middleware: { index, handler } }` on the `srvx.middleware` channel. Pass `{ fetch: false }` or `{ middleware: false }` to instrument only one of the two. + +Because plugins run in order, `tracingPlugin()` only wraps middleware registered before it β€” keep it **last** in the `plugins` array so it covers middleware added by earlier plugins. + +> [!IMPORTANT] +> `srvx/tracing` is **experimental**. diff --git a/docs/1.guide/5.options.md b/docs/1.guide/5.options.md index c57ec74c..5867612a 100644 --- a/docs/1.guide/5.options.md +++ b/docs/1.guide/5.options.md @@ -46,7 +46,7 @@ Default is value of `PORT` environment variable or `3000`. The hostname (IP or resolvable host) server listener should bound to. -When not provided, server will **listen to all network interfaces** by default. +Default is value of the `HOST` environment variable, if set. When neither `hostname` nor `HOST` is provided, the server will **listen to all network interfaces** by default. > [!IMPORTANT] > If you are running a server that should not be exposed to the network, use `localhost`. @@ -62,6 +62,63 @@ Enabling this option allows multiple processes to bind to the same port, which i If enabled, no server listening message will be printed (enabled by default when `TEST` environment variable is set). +### `manual` + +If set to `true`, the server will **not** start listening automatically. You must call [`server.serve()`](/guide/server#serverserve) yourself to begin accepting connections. Useful when you need to fully construct the server before it starts. + +```js +import { serve } from "srvx"; + +const server = serve({ + manual: true, + fetch: () => new Response("πŸ‘‹ Hello there!"), +}); + +// ...later +await server.serve(); +await server.ready(); +``` + +### `middleware` + +An array of [middleware](/guide/middleware) handlers to run before the main `fetch` handler. + +```js +import { serve } from "srvx"; + +serve({ + middleware: [ + (request, next) => { + console.log(`[${request.method}] ${request.url}`); + return next(); + }, + ], + fetch: () => new Response("πŸ‘‹ Hello there!"), +}); +``` + +:read-more{to="/guide/middleware" title="Middleware"} + +### `plugins` + +An array of [plugins](/guide/middleware) to extend the server. A plugin is a **synchronous** function that receives the server instance and can register middleware, hook into the lifecycle, or augment requests. + +```js +import { serve } from "srvx"; + +const myPlugin = (server) => { + server.options.middleware.push((request, next) => next()); +}; + +serve({ + plugins: [myPlugin], + fetch: () => new Response("πŸ‘‹ Hello there!"), +}); +``` + +> [!NOTE] +> Plugins are sync-only (they return `void`). All plugin-registered middleware is therefore in place before the first request is handled. + ### `protocol` The protocol to use for the server. @@ -193,7 +250,7 @@ import { serve } from "srvx"; serve({ node: { - maxHeadersize: 16384 * 2, // Double default + maxHeaderSize: 16384 * 2, // Double default ipv6Only: true, // Disable dual-stack support // http2: false // Disable http2 support (enabled by default in TLS mode) }, @@ -250,3 +307,36 @@ serve({ ::read-more{to=https://docs.deno.com/api/deno/~/Deno.ServeOptions} See Deno serve documentation for all available options. :: + +### Service Worker + +Options for the service worker adapter (`srvx/service-worker`). + +```js +import { serve } from "srvx/service-worker"; + +serve({ + serviceWorker: { + url: "/sw.js", // path to the service worker file to register + scope: "/", // service worker scope + }, + fetch: () => new Response("πŸ‘‹ Hello there!"), +}); +``` + +- `url`: The path to the service worker file to be registered. +- `scope`: The scope of the service worker. + +## Per-runtime Support + +srvx aims for identical behavior across runtimes, but a few options depend on capabilities the underlying runtime does not expose. The table below lists the intentional differences. + +| Option / feature | Behavior | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `close(true)` (force close) | Honored on Node and Bun. On **Deno** the force argument is currently ignored β€” `close(true)` behaves like a graceful `close()`. | +| `trustProxy` | Applies to the **Node, AWS Lambda, Bun and Deno** adapters only. Ignored on Cloudflare, Bunny and the generic/service-worker adapters. | +| `maxRequestBodySize` | Node/Deno enforced by srvx (unlimited by default). **Bun** forwards it to Bun's native option, which has its own **128 MiB** default even when unset. Dropped (not applied) when running under the CLI loader on any runtime. | +| `manual` | Meaningless on module-worker runtimes (Cloudflare module syntax, Bunny) β€” there is no listening step to defer. | +| `gracefulShutdown` | Supported on Node, Deno and Bun. No-op on Cloudflare, Bunny and service-worker runtimes. | +| Cloudflare `env` bindings | `request.runtime.cloudflare.env` is only populated in **module-worker syntax**. In service-worker syntax (the global `fetch` listener) bindings are unavailable. | +| **`upgrade` / WebSocket** | srvx does **not** handle HTTP `upgrade` requests by default. Use [crossws](https://crossws.h3.dev/guide) for WebSocket support. | diff --git a/docs/1.guide/8.node.md b/docs/1.guide/8.node.md index 04eff90c..9b386e0a 100644 --- a/docs/1.guide/8.node.md +++ b/docs/1.guide/8.node.md @@ -80,7 +80,7 @@ It also extends the global `Request` class provided by Node.js (via [undici](htt Due to undici’s internal implementation, some edge cases exist. For example, calling `new Request(req)` may throw `Cannot read private member #state from an object whose class did not declare it`. -To avoid these issues, you can use `req.clone()` or `req._request` to access an undici instance of request. +To avoid these issues, you can use `req.clone()` or `req._request` to access an undici instance of request. Alongside it, `req._url` exposes the already-parsed `URL` for the request. Alternatively, you can patch the global `Request` class in Node.js. diff --git a/src/cli/usage.ts b/src/cli/usage.ts index ab6aafb7..1c52ebc4 100644 --- a/src/cli/usage.ts +++ b/src/cli/usage.ts @@ -37,34 +37,38 @@ ${c.gray("$")} echo '{"name":"foo"}' | ${c.cyan(command)} fetch -d @- /api ${c.g ${c.bold("COMMON OPTIONS")} - ${c.green("--entry")} ${c.yellow("")} Server entry file to use - ${c.green("--dir")} ${c.yellow("")} Working directory for resolving entry file - ${c.green("-h, --help")} Show this help message - ${c.green("--version")} Show server and runtime versions + ${c.green("--entry")} ${c.yellow("")} Server entry file to use + ${c.green("--dir")} ${c.yellow("")} Working directory for resolving entry file + ${c.green("-h, --help")} Show this help message + ${c.green("--version")} Show server and runtime versions ${c.bold("SERVE OPTIONS")} - ${c.green("-p, --port")} ${c.yellow("")} Port to listen on (default: ${c.yellow("3000")}) - ${c.green("--host")} ${c.yellow("")} Host to bind to (default: all interfaces) - ${c.green("-s, --static")} ${c.yellow("")} Serve static files from the specified directory (default: ${c.yellow("public")}) - ${c.green("--prod")} Run in production mode (no watch, no debug) - ${c.green("--import")} ${c.yellow("")} ES module to preload - ${c.green("--tls")} Enable TLS (HTTPS/HTTP2) - ${c.green("--cert")} ${c.yellow("")} TLS certificate file - ${c.green("--key")} ${c.yellow("")} TLS private key file + ${c.green("-p, --port")} ${c.yellow("")} Port to listen on (default: ${c.yellow("3000")}) + ${c.green("--host, --hostname")} ${c.yellow("")} Host to bind to (default: all interfaces) + ${c.green("-s, --static")} ${c.yellow("")} Serve static files from the specified directory (default: ${c.yellow("public")}) + ${c.green("--prod")} Run in production mode (no watch, no debug) + ${c.green("--import")} ${c.yellow("")} ES module to preload + ${c.green("--tls")} Enable TLS (HTTPS/HTTP2) + ${c.green("--cert")} ${c.yellow("")} TLS certificate file + ${c.green("--key")} ${c.yellow("")} TLS private key file ${c.bold("FETCH OPTIONS")} - ${c.green("-X, --request")} ${c.yellow("")} HTTP method (default: ${c.yellow("GET")}, or ${c.yellow("POST")} if body is provided) - ${c.green("-H, --header")} ${c.yellow("
")} Add header (format: "Name: Value", can be used multiple times) - ${c.green("-d, --data")} ${c.yellow("")} Request body (use ${c.yellow("@-")} for stdin, ${c.yellow("@file")} for file) - ${c.green("-v, --verbose")} Show request and response headers + ${c.green("-X, --method")} ${c.yellow("")} HTTP method (default: ${c.yellow("GET")}, or ${c.yellow("POST")} if body is provided; ${c.green("--request")} is a curl alias) + ${c.green("-H, --header")} ${c.yellow("
")} Add header (format: "Name: Value", can be used multiple times) + ${c.green("-d, --data")} ${c.yellow("")} Request body (use ${c.yellow("@-")} for stdin, ${c.yellow("@file")} for file) + ${c.green("--host")} ${c.yellow("")} Host for a schemeless URL/path (default: ${c.yellow("localhost")}) + ${c.green("--tls")} Use ${c.yellow("https")} for a schemeless URL/path + ${c.green("-v, --verbose")} Show request and response headers + + Exits with code ${c.yellow("22")} on a non-2xx response (like ${c.cyan("curl --fail")}). ${c.bold("ENVIRONMENT")} - ${c.green("PORT")} Override port - ${c.green("HOST")} Override host - ${c.green("NODE_ENV")} Set to ${c.yellow("production")} for production mode. + ${c.green("PORT")} Default port to listen on + ${c.green("HOST")} Default host to bind to + ${c.green("NODE_ENV")} Set to ${c.yellow("production")} for production mode. ${mainOpts.usage?.docs ? `➀ ${c.url("Documentation", mainOpts.usage.docs)}` : ""} ${mainOpts.usage?.issues ? `➀ ${c.url("Report issues", mainOpts.usage.issues)}` : ""} diff --git a/src/types.ts b/src/types.ts index c671c5e0..6e395846 100644 --- a/src/types.ts +++ b/src/types.ts @@ -89,7 +89,8 @@ export interface ServerOptions { /** * The hostname (IP or resolvable host) server listener should bound to. * - * When not provided, server with listen to all network interfaces by default. + * Default is read from the `HOST` environment variable. When neither is + * provided, the server will listen to all network interfaces by default. * * **Important:** If you are running a server that is not expected to be exposed to the network, use `hostname: "localhost"`. */ diff --git a/test/_tests.ts b/test/_tests.ts index ec8cffb0..98e0bcf4 100644 --- a/test/_tests.ts +++ b/test/_tests.ts @@ -179,7 +179,7 @@ export function addTests(opts: { }); }); - // TODO: Investigate writing test for HTTP2/TLS + // TODO: Investigate writing test for HTTP2/TLS (https://github.com/h3js/srvx/issues/235) test.skipIf(opts.http2)("response stream error", async () => { const res = await fetch(url("/response/stream-error")); expect(res.status).toBe(200); @@ -374,7 +374,7 @@ export function addTests(opts: { expect(data.pathname).toBe("/bar/baz"); }); - // TODO: Write test to make sure it is forbidden for http2/tls + // TODO: Write test to make sure it is forbidden for http2/tls (https://github.com/h3js/srvx/issues/236) test.skipIf(opts.http2)("absolute path in request line", async () => { const _url = new URL(url("/")); diff --git a/test/node.test.ts b/test/node.test.ts index 7d6ce590..03efa327 100644 --- a/test/node.test.ts +++ b/test/node.test.ts @@ -40,7 +40,7 @@ const testConfigs = [ for (const config of testConfigs) { if ((isDeno || isBun) && config.http2) { - continue; // Not implemented yet in Deno, Bun fails somehow too! + continue; // Not implemented yet in Deno, Bun fails somehow too! (https://github.com/h3js/srvx/issues/237) } describe.sequential(`${runtime} (${config.name})`, () => { const client = getHttpClient(config.http2);