mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 04:00:21 +00:00
96 lines
3.0 KiB
TypeScript
96 lines
3.0 KiB
TypeScript
import { danger, info, shouldLogVerbose, warn } from "../globals.js";
|
|
import { logDebug } from "../logger.js";
|
|
import type { RuntimeEnv } from "../runtime.js";
|
|
import { defaultRuntime } from "../runtime.js";
|
|
import { isErrno } from "./errors.js";
|
|
import { formatPortDiagnostics } from "./ports-format.js";
|
|
import { inspectPortUsage } from "./ports-inspect.js";
|
|
import { tryListenOnPort } from "./ports-probe.js";
|
|
import type { PortListener, PortListenerKind, PortUsage, PortUsageStatus } from "./ports-types.js";
|
|
|
|
class PortInUseError extends Error {
|
|
port: number;
|
|
details?: string;
|
|
|
|
constructor(port: number, details?: string) {
|
|
super(`Port ${port} is already in use.`);
|
|
this.name = "PortInUseError";
|
|
this.port = port;
|
|
this.details = details;
|
|
}
|
|
}
|
|
|
|
export async function describePortOwner(port: number): Promise<string | undefined> {
|
|
const diagnostics = await inspectPortUsage(port);
|
|
if (diagnostics.listeners.length === 0) {
|
|
return undefined;
|
|
}
|
|
return formatPortDiagnostics(diagnostics).join("\n");
|
|
}
|
|
|
|
export async function ensurePortAvailable(port: number): Promise<void> {
|
|
// Detect EADDRINUSE early with a friendly message.
|
|
try {
|
|
await tryListenOnPort({ port });
|
|
} catch (err) {
|
|
if (isErrno(err) && err.code === "EADDRINUSE") {
|
|
throw new PortInUseError(port);
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
export async function handlePortError(
|
|
err: unknown,
|
|
port: number,
|
|
context: string,
|
|
runtime: RuntimeEnv = defaultRuntime,
|
|
): Promise<never> {
|
|
// Uniform messaging for EADDRINUSE with optional owner details.
|
|
if (err instanceof PortInUseError || (isErrno(err) && err.code === "EADDRINUSE")) {
|
|
const details =
|
|
err instanceof PortInUseError
|
|
? (err.details ?? (await describePortOwner(port)))
|
|
: await describePortOwner(port);
|
|
runtime.error(danger(`${context} failed: port ${port} is already in use.`));
|
|
if (details) {
|
|
runtime.error(info("Port listener details:"));
|
|
runtime.error(details);
|
|
if (/openclaw|src\/index\.ts|dist\/index\.js/.test(details)) {
|
|
runtime.error(
|
|
warn(
|
|
"It looks like another OpenClaw instance is already running. Stop it or pick a different port.",
|
|
),
|
|
);
|
|
}
|
|
}
|
|
runtime.error(
|
|
info("Resolve by stopping the process using the port or passing --port <free-port>."),
|
|
);
|
|
runtime.exit(1);
|
|
}
|
|
runtime.error(danger(`${context} failed: ${String(err)}`));
|
|
if (shouldLogVerbose()) {
|
|
const stdout = (err as { stdout?: string })?.stdout;
|
|
const stderr = (err as { stderr?: string })?.stderr;
|
|
if (stdout?.trim()) {
|
|
logDebug(`stdout: ${stdout.trim()}`);
|
|
}
|
|
if (stderr?.trim()) {
|
|
logDebug(`stderr: ${stderr.trim()}`);
|
|
}
|
|
}
|
|
runtime.exit(1);
|
|
throw new Error("unreachable");
|
|
}
|
|
|
|
export { PortInUseError };
|
|
export type { PortListener, PortListenerKind, PortUsage, PortUsageStatus };
|
|
export {
|
|
buildPortHints,
|
|
classifyPortListener,
|
|
formatPortDiagnostics,
|
|
isDualStackLoopbackGatewayListeners,
|
|
} from "./ports-format.js";
|
|
export { inspectPortUsage } from "./ports-inspect.js";
|