# All files / src/preview port-allocator.ts

95% Statements 19/20
100% Branches 4/4
100% Functions 6/6
94.44% Lines 17/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64

    9x

    9x

    9x

    4x
    4x

    4x
    8x

    5x
    4x
    4x

    5x
    5x

    5x
    1x

    5x
    4x
    4x

     /**
     * Port Allocator
     *
     * Finds the next available port for a preview deployment.
     * Queries running preview containers and performs a bind test.
     */

    import { createServer } from 'node:net';
    import { getPreviewPorts } from './docker-client.js';
    import { createModuleLogger } from '../logger/index.js';

    const log = createModuleLogger('preview:port');

    /** Starting port for preview deployments */
    const BASE_PORT = 9100;

    /** Maximum port to try before giving up */
    const MAX_PORT = 9200;

    /**
     * Allocate the next available port for a preview deployment.
     *
     * 1. Queries running preview containers for their cw.port labels
     * 2. Finds the next port >= BASE_PORT that isn't in use
     * 3. Performs a bind test to verify no external conflict
     *
     * @returns An available port number
     * @throws If no port is available in the range
     */
    export async function allocatePort(): Promise<number> {
      const usedPorts = new Set(await getPreviewPorts());
      log.debug({ usedPorts: Array.from(usedPorts) }, 'ports in use by previews');

      for (let port = BASE_PORT; port < MAX_PORT; port++) {
        if (usedPorts.has(port)) continue;

        if (await isPortAvailable(port)) {
          log.info({ port }, 'allocated port');
          return port;
        }
      }

      throw new Error(`No available ports in range ${BASE_PORT}-${MAX_PORT}`);
    }

    /**
     * Test if a port is available by attempting to bind to it.
     */
    async function isPortAvailable(port: number): Promise<boolean> {
      return new Promise((resolve) => {
        const server = createServer();

        server.once('error', () => {
          resolve(false);
        });

        server.listen(port, '127.0.0.1', () => {
          server.close(() => {
            resolve(true);
          });
        });
      });
    }