/** * Detects whether a thrown error indicates the requested port is already in * use. * * @remarks * Matches Node's `code: 'EADDRINUSE'` and Bun's surface, where the error * carries the same `code` plus a human-readable message such as * `"Failed to start server. Is port 3000 in use?"`. Older Bun builds that do * not set `.code` are covered by a fallback match on `syscall === 'listen'` * plus the message shape. */ export declare function isPortInUseError(error: unknown): boolean; /** * Probes whether a TCP port is currently bindable from this process. Uses a * short-lived Node `net.Server` so both Node and Bun can run it via their * Node-compatible core. * * @remarks * A successful probe only guarantees the port was free at probe time; callers * must still handle EADDRINUSE when they actually bind, since release races * can flip the answer between probe and bind. */ export declare function isPortAvailable(port: number, hostname?: string): Promise; /** * Default interactive prompt. Asks a yes/no question on stdin/stdout and * auto-approves (returns `true`) after `timeoutMs` with no input. * * @returns `true` when the user accepted (or timed out), `false` when they declined. */ export type PromptFunction = (message: string, timeoutMs: number) => Promise; export interface PortManagerOptions { /** * Bind factory. Should throw on EADDRINUSE (or return `null`/`undefined` * when it declines without a port collision). Returning a non-null port * marks the bind as successful. */ startOnPort: (port: number) => Promise; /** Emits the "moved to port X" warning when the bound port differs from the preferred port. */ warn?: (message: string) => void; /** Infers a free port to display in the prompt. Defaults to {@link isPortAvailable}. */ probePort?: (port: number) => Promise; /** Custom prompter. Defaults to a readline-based yes/no prompt on the process stdio. */ prompt?: PromptFunction; /** Enables the prompt flow at all. Defaults to `process.stdin.isTTY && process.stdout.isTTY`. */ interactive?: boolean; /** Auto-approve timeout (ms) for the prompt. Defaults to 10000. */ autoApproveMs?: number; /** Maximum port offset to scan when falling back. Defaults to 20. */ maxPortOffset?: number; /** Retry attempts for the same port before declaring it taken. Defaults to 2. */ releaseRaceRetries?: number; /** Backoff between same-port retries. Defaults to 100 ms. */ releaseRaceDelayMs?: number; } export interface BindPortOptions { /** Port the caller would prefer to bind. */ preferredPort: number; /** * Whether the caller permits moving to the next free port automatically when * the preferred port is busy and no interactive prompt is available * (non-TTY / CI). Explicit ports that fall back silently in CI would surprise * operators, so this is honoured by the non-interactive path only. */ allowPortFallback: boolean; } /** * Owns preview-port assignment: probing, race-release retries, fallback, and * an optional interactive auto-approve prompt. * * @remarks * Single-process API. The manager does not cache ports across calls; each * `bind()` runs fresh from the preferred port. */ export declare class PortManager { private readonly startOnPort; private readonly warn; private readonly probePort; private readonly prompt; private readonly interactive; private readonly autoApproveMs; private readonly maxPortOffset; private readonly releaseRaceRetries; private readonly releaseRaceDelayMs; constructor(options: PortManagerOptions); /** * Binds the preview server to the preferred port. * * @remarks * When the preferred port is free, binds it directly — no prompt, no * fallback. The interactive prompt only fires on a genuine EADDRINUSE * collision (TTY runs), and auto-approves after * {@link PortManagerOptions.autoApproveMs}. In non-interactive (CI) runs, * the manager falls back silently only when * {@link BindPortOptions.allowPortFallback} is true (the default-port * path); pinned ports rethrow EADDRINUSE so Docker-style fixed-port * deployments fail loudly instead of silently moving. */ bind(options: BindPortOptions): Promise; private shouldFallback; private fallForward; /** * Attempts to bind via {@link PortManagerOptions.startOnPort} on `port`, * retrying EADDRINUSE collisions up to * {@link PortManagerOptions.releaseRaceRetries} to ride through TIME_WAIT * race release. * * @remarks * Only EADDRINUSE is retryable. Any other error is rethrown synchronously * rather than swallowed as a soft "port taken" result, so a misconfigured * factory surfaces loudly instead of causing a silent fallback. A `null` * return from the factory (no throw) is reported as `factory-refused`, not * conflated with a real collision. */ private tryPort; private findNextFreePort; }