# core/adapters/server.adapter.ts

> 140 lines of code and 36 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-adapters-server-adapter-ts
Source text: https://banes-lab.com/source/build-scripts/core/adapters/server.adapter.ts.txt

Listed in [core/adapters](https://banes-lab.com/api/source/build-scripts/core/adapters.md), after [core/adapters/security.adapter.ts](https://banes-lab.com/source/build-scripts/core/adapters/security.adapter.ts.md) and before [core/adapters/surface.adapter.ts](https://banes-lab.com/source/build-scripts/core/adapters/surface.adapter.ts.md).

## Definitions

- `terminateTree` (lexical_declaration, line 121, exported)
- `signaled` (lexical_declaration, line 54)
- `killTaskTree` (lexical_declaration, line 109)
- `holdersOf` (lexical_declaration, line 93, exported)
- `terminate` (lexical_declaration, line 125)
- `commandOutput` (lexical_declaration, line 38)
- `windowsHolders` (lexical_declaration, line 66)
- `alive` (lexical_declaration, line 97)
- `posixHolders` (lexical_declaration, line 81)
- `freePort` (lexical_declaration, line 140, exported)
- `awaitRelease` (lexical_declaration, line 129, exported)
- `WINDOWS` (lexical_declaration, line 13)
- `LISTENING` (lexical_declaration, line 14)
- `SYSTEM_PIDS` (lexical_declaration, line 15)
- `RELEASE_ATTEMPTS` (lexical_declaration, line 16)
- `RELEASE_INTERVAL_MS` (lexical_declaration, line 17)
- `LOCAL_ADDRESS_COLUMN` (lexical_declaration, line 19)
- `STATE_COLUMN` (lexical_declaration, line 20)
- `PID_COLUMN` (lexical_declaration, line 21)
- `NO_MATCH_STATUS` (lexical_declaration, line 23)
- `TASK_GONE_STATUS` (lexical_declaration, line 24)
- `PROCESS_GONE` (lexical_declaration, line 25)
- `tokensOf` (lexical_declaration, line 27)
- `exitStatusOf` (lexical_declaration, line 34)
- `status` (lexical_declaration, line 46)
- `suffix` (lexical_declaration, line 67)
- `pids` (lexical_declaration, line 68)
- `tokens` (lexical_declaration, line 70)
- `local` (lexical_declaration, line 71)
- `state` (lexical_declaration, line 72)
- `pid` (lexical_declaration, line 73)
- `listed` (lexical_declaration, line 82)
- `output` (lexical_declaration, line 111)
- `held` (lexical_declaration, line 141, exported)
- `released` (lexical_declaration, line 148, exported)
- `label` (lexical_declaration, line 149, exported)

## Contained in

- [core/adapters](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-adapters.md)

## Uses

- [configuration/strings/server.strings.ts](https://banes-lab.com/source/build-scripts/configuration/strings/server.strings.ts.md)
- [core/timers/base.timer.ts](https://banes-lab.com/source/build-scripts/core/timers/base.timer.ts.md)

## Used by

- [core/coordinators/server.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/server.coordinator.ts.md)
- [core/factories/browser.factory.ts](https://banes-lab.com/source/build-scripts/core/factories/browser.factory.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-configuration-strings.md)
- [core/adapters](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-adapters.md)
- [core/coordinators](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-coordinators.md)
- [core/factories](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-factories.md)
- [core/timers](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-timers.md)

## Source

```typescript
import {
    commandFailed,
    portHeldLine,
    portReleasedLine,
    probeFailed,
    signalFailed,
} from "#configuration/strings/server.strings";
import { execFileSync } from "node:child_process";
import { isRecord } from "#core/selectors/base.selector";
import process from "node:process";
import { wait } from "#core/timers/base.timer";

const WINDOWS = "win32";
const LISTENING = "LISTENING";
const SYSTEM_PIDS: ReadonlySet<string> = new Set(["0", "4"]);
const RELEASE_ATTEMPTS = 20;
const RELEASE_INTERVAL_MS = 50;

const LOCAL_ADDRESS_COLUMN = 1;
const STATE_COLUMN = 3;
const PID_COLUMN = 4;

const NO_MATCH_STATUS = 1;
const TASK_GONE_STATUS = 128;
const PROCESS_GONE = "ESRCH";

const tokensOf = function tokensOf(line: string): string[] {
    return line
        .split(" ")
        .map((token) => token.trim())
        .filter((token) => token.length > 0);
};

const exitStatusOf = function exitStatusOf(error: unknown): unknown {
    return isRecord(error) ? error["status"] : null;
};

const commandOutput = function commandOutput(
    file: string,
    args: readonly string[],
    quiet: ReadonlySet<number>,
): string {
    try {
        return execFileSync(file, [...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
    } catch (error: unknown) {
        const status = exitStatusOf(error);
        if (typeof status === "number" && quiet.has(status)) {
            return "";
        }
        throw new Error(commandFailed(file), { cause: error });
    }
};

const signaled = function signaled(pid: number): boolean {
    try {
        process.kill(pid, "SIGKILL");
        return true;
    } catch (error: unknown) {
        if (isRecord(error) && error["code"] === PROCESS_GONE) {
            return false;
        }
        throw new Error(signalFailed(pid), { cause: error });
    }
};

const windowsHolders = function windowsHolders(port: number): string[] {
    const suffix = `:${String(port)}`;
    const pids = new Set<string>();
    for (const line of commandOutput("netstat", ["-ano", "-p", "TCP"], new Set()).split("\n")) {
        const tokens = tokensOf(line);
        const local = tokens[LOCAL_ADDRESS_COLUMN] ?? "";
        const state = tokens[STATE_COLUMN] ?? "";
        const pid = tokens[PID_COLUMN] ?? "";
        if (local.endsWith(suffix) && state === LISTENING && pid.length > 0 && !SYSTEM_PIDS.has(pid)) {
            pids.add(pid);
        }
    }
    return [...pids];
};

const posixHolders = function posixHolders(port: number): string[] {
    const listed = commandOutput("lsof", ["-ti", `tcp:${String(port)}`, "-sTCP:LISTEN"], new Set([NO_MATCH_STATUS]));
    return [
        ...new Set(
            listed
                .split("\n")
                .map((line) => line.trim())
                .filter((pid) => pid.length > 0 && !SYSTEM_PIDS.has(pid)),
        ),
    ];
};

export const holdersOf = function holdersOf(port: number): string[] {
    return process.platform === WINDOWS ? windowsHolders(port) : posixHolders(port);
};

const alive = function alive(pid: number): boolean {
    try {
        process.kill(pid, 0);
        return true;
    } catch (error: unknown) {
        if (isRecord(error) && error["code"] === PROCESS_GONE) {
            return false;
        }
        throw new Error(probeFailed(pid), { cause: error });
    }
};

const killTaskTree = function killTaskTree(pid: number): boolean {
    try {
        const output = commandOutput("taskkill", ["/PID", String(pid), "/F", "/T"], new Set([TASK_GONE_STATUS]));
        return output.length > 0;
    } catch (error: unknown) {
        if (alive(pid)) {
            throw error;
        }
        return true;
    }
};

export const terminateTree = function terminateTree(pid: number): boolean {
    return process.platform === WINDOWS ? killTaskTree(pid) : signaled(-pid);
};

const terminate = function terminate(pid: string): boolean {
    return process.platform === WINDOWS ? killTaskTree(Number(pid)) : signaled(Number(pid));
};

export const awaitRelease = async function awaitRelease(port: number, attempt = 0): Promise<boolean> {
    if (holdersOf(port).length === 0) {
        return true;
    }
    if (attempt >= RELEASE_ATTEMPTS) {
        return false;
    }
    await wait(RELEASE_INTERVAL_MS);
    return awaitRelease(port, attempt + 1);
};

export const freePort = async function freePort(port: number): Promise<string[]> {
    const held = holdersOf(port);
    if (held.length === 0) {
        return [];
    }
    held.forEach((pid) => {
        terminate(pid);
    });
    const released = await awaitRelease(port);
    const label = held.join(", ");
    if (!released) {
        process.stderr.write(portHeldLine(port, label));
        return held;
    }
    process.stdout.write(portReleasedLine(port, label));
    return held;
};
```
