Repository: unjs/get-port-please Branch: main Commit: 32a2f925b3f3 Files: 19 Total size: 35.4 KB Directory structure: gitextract_9ta45hy0/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── eslint.config.mjs ├── package.json ├── renovate.json ├── src/ │ ├── _internal.ts │ ├── get-port.ts │ ├── index.ts │ ├── socket.ts │ ├── types.ts │ └── unsafe-ports.ts ├── test/ │ ├── index.test.ts │ ├── socket.test.ts │ └── utils.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ci.yml ================================================ name: ci on: push: branches: - main pull_request: branches: - main jobs: ci: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v4 - run: npm i -g -f corepack && corepack enable - uses: actions/setup-node@v4 with: node-version: 22 cache: "pnpm" - run: pnpm install - run: pnpm lint if: ${{ matrix.os != 'windows-latest' }} - run: pnpm build if: ${{ matrix.os != 'windows-latest' }} - run: pnpm vitest --coverage - uses: codecov/codecov-action@v5 ================================================ FILE: .gitignore ================================================ .vscode node_modules *.log* dist coverage ================================================ FILE: .prettierrc ================================================ {} ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. ## v3.2.0 [compare changes](https://github.com/unjs/get-port-please/compare/v3.1.2...v3.2.0) ### 🚀 Enhancements - Add unix domain socket utils ([#110](https://github.com/unjs/get-port-please/pull/110)) ### 🩹 Fixes - **validateHostname:** Add `169.254.0.0/16` range to not allowed hostnames ([#101](https://github.com/unjs/get-port-please/pull/101)) ### 🏡 Chore - Update repo ([17df943](https://github.com/unjs/get-port-please/commit/17df943)) - Update deps ([20d20ec](https://github.com/unjs/get-port-please/commit/20d20ec)) ### ✅ Tests - Mock `console.log` implementation in error tests ([#104](https://github.com/unjs/get-port-please/pull/104)) ### 🤖 CI - Fix corepack ([9590839](https://github.com/unjs/get-port-please/commit/9590839)) ### ❤️ Contributors - Pooya Parsa ([@pi0](https://github.com/pi0)) - João Carmona ([@jpsc](https://github.com/jpsc)) - Sam Bostock ([@sambostock](https://github.com/sambostock)) ## v3.1.2 [compare changes](https://github.com/unjs/get-port-please/compare/v3.1.1...v3.1.2) ### 🩹 Fixes - Accept ipv6 as valid hostname ([d537a51](https://github.com/unjs/get-port-please/commit/d537a51)) - Use closed ranges as port ranges ([#66](https://github.com/unjs/get-port-please/pull/66)) - Ranges now work even if port is unset in options ([#72](https://github.com/unjs/get-port-please/pull/72)) - Don't use random port by default if user specified any port ([#65](https://github.com/unjs/get-port-please/pull/65)) ### 💅 Refactors - Correct typos in `GetPortError` message ([#77](https://github.com/unjs/get-port-please/pull/77)) - Fix typo in error message ([#73](https://github.com/unjs/get-port-please/pull/73)) - Improve log when using alternative port ([#78](https://github.com/unjs/get-port-please/pull/78)) ### 🏡 Chore - Update fallback message ([1f45050](https://github.com/unjs/get-port-please/commit/1f45050)) - Update dependencies and lockfile ([62735f6](https://github.com/unjs/get-port-please/commit/62735f6)) ### ✅ Tests - Simplify ([9dccfd9](https://github.com/unjs/get-port-please/commit/9dccfd9)) - Update snapshot ([59cdd8c](https://github.com/unjs/get-port-please/commit/59cdd8c)) ### ❤️ Contributors - Pooya Parsa ([@pi0](http://github.com/pi0)) - Filip Joelsson - Daniel Roe - Vladimir Vagaytsev ([@vvagaytsev](http://github.com/vvagaytsev)) - Yu Le ## v3.1.1 [compare changes](https://github.com/unjs/get-port-please/compare/v3.1.0...v3.1.1) ### 🩹 Fixes - Ignore ipv6 link-local addresses from hosts ([9e76e76](https://github.com/unjs/get-port-please/commit/9e76e76)) ### ❤️ Contributors - Pooya Parsa ([@pi0](http://github.com/pi0)) ## v3.1.0 [compare changes](https://github.com/unjs/get-port-please/compare/v3.0.2...v3.1.0) ### 🚀 Enhancements - Automatically fallback hostname for invalid hosts ([#62](https://github.com/unjs/get-port-please/pull/62)) ### 🩹 Fixes - Validate hostname and improve errors ([#59](https://github.com/unjs/get-port-please/pull/59)) - Skip all listen errors ([#61](https://github.com/unjs/get-port-please/pull/61)) ### 💅 Refactors - Split internals and types ([cf4317c](https://github.com/unjs/get-port-please/commit/cf4317c)) ### 🏡 Chore - Run tests on windows ([#60](https://github.com/unjs/get-port-please/pull/60)) - Use changelogen for releases ([7d4d6bb](https://github.com/unjs/get-port-please/commit/7d4d6bb)) - **release:** V3.0.3 ([86672c4](https://github.com/unjs/get-port-please/commit/86672c4)) ### ✅ Tests - Block host on all interfaces ([5a95184](https://github.com/unjs/get-port-please/commit/5a95184)) - Skip invalid host tests on windows ([e8b92ac](https://github.com/unjs/get-port-please/commit/e8b92ac)) ### 🎨 Styles - Lint ([1501204](https://github.com/unjs/get-port-please/commit/1501204)) ### ❤️ Contributors - Pooya Parsa ([@pi0](http://github.com/pi0)) ## v3.0.3 [compare changes](https://github.com/unjs/get-port-please/compare/v3.0.2...v3.0.3) ### 🩹 Fixes - Validate hostname and improve errors ([#59](https://github.com/unjs/get-port-please/pull/59)) - Skip all listen errors ([#61](https://github.com/unjs/get-port-please/pull/61)) ### 💅 Refactors - Split internals and types ([cf4317c](https://github.com/unjs/get-port-please/commit/cf4317c)) ### 🏡 Chore - Run tests on windows ([#60](https://github.com/unjs/get-port-please/pull/60)) - Use changelogen for releases ([7d4d6bb](https://github.com/unjs/get-port-please/commit/7d4d6bb)) ### ✅ Tests - Block host on all interfaces ([5a95184](https://github.com/unjs/get-port-please/commit/5a95184)) - Skip invalid host tests on windows ([e8b92ac](https://github.com/unjs/get-port-please/commit/e8b92ac)) ### 🎨 Styles - Lint ([1501204](https://github.com/unjs/get-port-please/commit/1501204)) ### ❤️ Contributors - Pooya Parsa ([@pi0](http://github.com/pi0)) ### [3.0.2](https://github.com/unjs/get-port-please/compare/v3.0.1...v3.0.2) (2023-08-27) ### Bug Fixes * use random port when `port: 0` is set ([081ed99](https://github.com/unjs/get-port-please/commit/081ed99661bb63e7910194e1c802ec0d33d75953)) ### [3.0.1](https://github.com/unjs/get-port-please/compare/v3.0.0...v3.0.1) (2023-01-03) ### Bug Fixes * use `process.env.PORT` as default value if `port` option is missing ([#27](https://github.com/unjs/get-port-please/issues/27)) ([57e1232](https://github.com/unjs/get-port-please/commit/57e1232274fe703d3b3039362d57c9897dae0e38)) ## [3.0.0](https://github.com/unjs/get-port-please/compare/v2.6.1...v3.0.0) (2022-11-14) ### ⚠ BREAKING CHANGES * no default value for `alternativePortRange` if explicit `port` provided * drop memo support ### Bug Fixes * no default value for `alternativePortRange` if explicit `port` provided ([00366ae](https://github.com/unjs/get-port-please/commit/00366aeae4226de8dd47532137dce39be6297050)), closes [#15](https://github.com/unjs/get-port-please/issues/15) * drop memo support ([bc6f5c5](https://github.com/unjs/get-port-please/commit/bc6f5c5baa99b72fe3136230ba995e6df06b27e9)) ### [2.6.1](https://github.com/unjs/get-port-please/compare/v2.6.0...v2.6.1) (2022-08-08) ### Bug Fixes * ignore falsy `config.port` option ([ca34365](https://github.com/unjs/get-port-please/commit/ca343657a02fbfa97fbc0822a18b6cd2f3766032)) ## [2.6.0](https://github.com/unjs/get-port-please/compare/v2.5.0...v2.6.0) (2022-08-08) ### Features * alternative port and verbose logging ([49200e6](https://github.com/unjs/get-port-please/commit/49200e6df6ca6ba9283cc3e7162b44b5ef0906e6)) * verbose logging (opt-in) ([779f3b7](https://github.com/unjs/get-port-please/commit/779f3b763c2d9ff44bba5594f944b4019d95b2b5)) ### Bug Fixes * generate range before passing to findPort ([848a8c2](https://github.com/unjs/get-port-please/commit/848a8c2bb588431861a0d29b243aad6c024f48cf)) ## [2.5.0](https://github.com/unjs/get-port-please/compare/v2.4.3...v2.5.0) (2022-04-11) ### Features * `portRange` support ([44c8acd](https://github.com/unjs/get-port-please/commit/44c8acdc7a26de5b7308ee409543a7f9e8fdb275)) * ignore unsafe ports ([#8](https://github.com/unjs/get-port-please/issues/8)) ([dd29cbd](https://github.com/unjs/get-port-please/commit/dd29cbd174c6676869cfb114c33671126c48ce86)) ### [2.4.3](https://github.com/unjs/get-port-please/compare/v2.4.2...v2.4.3) (2022-02-28) ### Bug Fixes * **waitForPort:** check for all available hosts by default ([ba5f3b0](https://github.com/unjs/get-port-please/commit/ba5f3b0dfe80b016719be2349bbf62914f9905c8)) ### [2.4.2](https://github.com/unjs/get-port-please/compare/v2.4.1...v2.4.2) (2022-02-25) ### Bug Fixes * **waitForPort:** wait for delay ([01497a8](https://github.com/unjs/get-port-please/commit/01497a80943116ff9a82aa516509044ae2a7d979)) ### [2.4.1](https://github.com/unjs/get-port-please/compare/v2.4.0...v2.4.1) (2022-02-25) ### Bug Fixes * **getRandomPort:** host argument is optional ([7081383](https://github.com/unjs/get-port-please/commit/70813834ba289c631df379b0ddd34eccbe54b0d4)) ## [2.4.0](https://github.com/unjs/get-port-please/compare/v2.3.0...v2.4.0) (2022-02-25) ### Features * check port against all available hosts ([bac49cc](https://github.com/unjs/get-port-please/commit/bac49cc4de6aea681314c794284cca684c25e0fa)) * export `waitForPort` ([1c42832](https://github.com/unjs/get-port-please/commit/1c4283292353e908fa3049e0b3f60f23338c6cff)) ## [2.3.0](https://github.com/unjs/get-port-please/compare/v2.2.0...v2.3.0) (2022-02-10) ### Features * export `checkPort` utils ([#6](https://github.com/unjs/get-port-please/issues/6)) ([c248dff](https://github.com/unjs/get-port-please/commit/c248dff81a7eb3acc20f0277d1eae00130e43d0d)) ## [2.2.0](https://github.com/unjs/get-port-please/compare/v2.1.0...v2.2.0) (2021-04-12) ### Features * add `host` option ([#4](https://github.com/unjs/get-port-please/issues/4)) ([f85d941](https://github.com/unjs/get-port-please/commit/f85d94154e3832e3cf854c2d631329c29e71df92)) ### Bug Fixes * resolve defaults at `getPort` ([dcab479](https://github.com/unjs/get-port-please/commit/dcab4795d49184c7e4df115372f43e4eec52fbe3)) ## [2.1.0](https://github.com/unjs/get-port-please/compare/v2.0.0...v2.1.0) (2020-12-04) ### Features * allow config to be string or number ([13f4275](https://github.com/unjs/get-port-please/commit/13f4275e021fe1cd69c3ac775c284d92cd6c601d)) ## [2.0.0](https://github.com/unjs/get-port-please/compare/v1.1.0...v2.0.0) (2020-12-04) ### ⚠ BREAKING CHANGES * use named exports ### Features * use named exports ([37e5aa2](https://github.com/unjs/get-port-please/commit/37e5aa2a485165c325f674951cef324889318304)) ## [1.1.0](https://github.com/unjs/get-port-please/compare/v1.0.0...v1.1.0) (2020-11-16) ### Features * update fs-memo and improve config handling ([5e4acee](https://github.com/unjs/get-port-please/commit/5e4acee1d7aa47c100815a25a43a508eafbacd6b)) ## [1.0.0](https://github.com/unjs/get-port-please/compare/v0.0.6...v1.0.0) (2020-06-16) ### [0.0.6](https://github.com/unjs/get-port-please/compare/v0.0.5...v0.0.6) (2020-06-01) ### [0.0.5](https://github.com/unjs/get-port-please/compare/v0.0.4...v0.0.5) (2020-06-01) ### Bug Fixes * name is not defined ([c1829f1](https://github.com/unjs/get-port-please/commit/c1829f12cfaf5304661ef16d744bbc66a2610a2d)) ### [0.0.4](https://github.com/unjs/get-port-please/compare/v0.0.3...v0.0.4) (2020-06-01) ### Features * name and random options ([ccea688](https://github.com/unjs/get-port-please/commit/ccea68889f440d0760412caff696dccfeac3144f)) ### [0.0.3](https://github.com/unjs/get-port-please/compare/v0.0.2...v0.0.3) (2020-06-01) ### Bug Fixes * **types:** use interface instead of type infering ([09135eb](https://github.com/unjs/get-port-please/commit/09135ebf0b7c96533b68cabdf8a9c512415e00b8)) ### [0.0.2](https://github.com/unjs/get-port-please/compare/v0.0.1...v0.0.2) (2020-06-01) ### 0.0.1 (2020-05-31) ### Bug Fixes * set version to 0 ([79c01ef](https://github.com/unjs/get-port-please/commit/79c01ef53e9425345bc0ec2cf58287b1fc940a7c)) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) Pooya Parsa Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # 🔌 get-port-please Get an available TCP port to listen [![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![License][license-src]][license-href] [![JSDocs][jsdocs-src]][jsdocs-href] ## Usage Install package: ```bash npm i get-port-please ``` ```js // ESM import { getPort, checkPort, getRandomPort, waitForPort, getSocketAddress, isSocketSupported, cleanSocket } from "get-port-please"; // CommonJS const { getPort, checkPort, getRandomPort, waitForPort, getSocketAddress, isSocketSupported, cleanSocket } = require("get-port-please"); ``` ``` getPort(options: GetPortOptions): Promise; checkPort(port: number, host?: string): Promise waitForPort(port: number, options): Promise ``` Try sequence is: port > ports > random ## Options ```ts interface GetPortOptions { name?: string; random?: boolean; port?: number; portRange?: [fromInclusive: number, toInclusive: number]; ports?: number[]; host?: string; memoDir?: string; memoName?: string; } ``` ### `name` Unique name for port memorizing. Default is `default`. ### `random` If enabled, `port` and `ports` will be ignored. Default is `false`. ### `port` First port to check. Default is `process.env.PORT || 3000` ### `ports` Extended ports to check. ### `portRange` Extended port range to check. The range's start and end are **inclusive**, i.e. it is `[start, end]` in the mathematical notion. Reversed port ranges are not supported. If `start > end`, then an empty range will be returned. ### `alternativePortRange` Alternative port range to check as fallback when none of the ports are available. The range's start and end are **inclusive**, i.e. it is `[start, end]` in the mathematical notion. Reversed port ranges are not supported. If `start > end`, then an empty range will be returned. The default range is `[3000, 3100]` (only when `port` is unspecified). ### `host` The host to check. Default is `process.env.HOST` otherwise all available hosts will be checked. ## License MIT [npm-version-src]: https://img.shields.io/npm/v/get-port-please?style=flat&colorA=18181B&colorB=F0DB4F [npm-version-href]: https://npmjs.com/package/get-port-please [npm-downloads-src]: https://img.shields.io/npm/dm/get-port-please?style=flat&colorA=18181B&colorB=F0DB4F [npm-downloads-href]: https://npmjs.com/package/get-port-please [codecov-src]: https://img.shields.io/codecov/c/gh/unjs/get-port-please/main?style=flat&colorA=18181B&colorB=F0DB4F [codecov-href]: https://codecov.io/gh/unjs/get-port-please [license-src]: https://img.shields.io/github/license/unjs/get-port-please.svg?style=flat&colorA=18181B&colorB=F0DB4F [license-href]: https://github.com/unjs/get-port-please/blob/main/LICENSE [jsdocs-src]: https://img.shields.io/badge/jsDocs.io-reference-18181B?style=flat&colorA=18181B&colorB=F0DB4F [jsdocs-href]: https://www.jsdocs.io/package/get-port-please ================================================ FILE: eslint.config.mjs ================================================ import unjs from "eslint-config-unjs"; // https://github.com/unjs/eslint-config export default unjs({ ignores: [], rules: {}, }); ================================================ FILE: package.json ================================================ { "name": "get-port-please", "version": "3.2.0", "description": "Get an available TCP port to listen", "repository": "unjs/get-port-please", "license": "MIT", "exports": { ".": { "import": "./dist/index.mjs", "types": "./dist/index.d.ts", "require": "./dist/index.cjs" } }, "main": "./dist/index.cjs", "types": "./dist/index.d.ts", "files": [ "dist" ], "scripts": { "build": "unbuild", "dev": "vitest", "lint": "eslint . && prettier -c src test", "lint:fix": "eslint --fix . && prettier -w src test", "prepack": "unbuild", "release": "pnpm test && pnpm build && changelogen --release --push && pnpm publish", "test": "pnpm lint && vitest run" }, "devDependencies": { "@types/node": "^24.3.0", "@vitest/coverage-v8": "^3.2.4", "changelogen": "^0.6.2", "eslint": "^9.34.0", "eslint-config-unjs": "^0.5.0", "jiti": "^2.5.1", "prettier": "^3.6.2", "typescript": "^5.9.2", "unbuild": "^3.6.1", "vitest": "^3.2.4" }, "packageManager": "pnpm@10.15.1" } ================================================ FILE: renovate.json ================================================ { "extends": ["github>unjs/renovate-config"] } ================================================ FILE: src/_internal.ts ================================================ import { createServer, AddressInfo } from "node:net"; import { networkInterfaces } from "node:os"; import { isSafePort } from "./unsafe-ports"; import type { PortNumber, HostAddress } from "./types"; export class GetPortError extends Error { name = "GetPortError"; constructor( public message: string, opts?: any, ) { super(message, opts); } } export function _log(verbose: boolean, message: string) { if (verbose) { console.log(`[get-port] ${message}`); } } export function _generateRange(from: number, to: number): number[] { if (to < from) { return []; } const r = []; for (let index = from; index <= to; index++) { r.push(index); } return r; } export function _tryPort( port: PortNumber, host: HostAddress, ): Promise { return new Promise((resolve) => { const server = createServer(); server.unref(); server.on("error", () => { resolve(false); }); server.listen({ port, host }, () => { const { port } = server.address() as AddressInfo; server.close(() => { resolve(isSafePort(port) && port); }); }); }); } export function _getLocalHosts(additional: HostAddress[]): HostAddress[] { const hosts = new Set(additional); for (const _interface of Object.values(networkInterfaces())) { for (const config of _interface || []) { if ( config.address && !config.internal && !config.address.startsWith("fe80::") && // Link-Local !config.address.startsWith("169.254") // reserved for Automatic Private IP Addressing ) { hosts.add(config.address); } } } return [...hosts]; } export async function _findPort( ports: number[], host: HostAddress, ): Promise { for (const port of ports) { const r = await _tryPort(port, host); if (r) { return r; } } } export function _fmtOnHost(hostname: string | undefined) { return hostname ? `on host ${JSON.stringify(hostname)}` : "on any host"; } const HOSTNAME_RE = /^(?!-)[\d.:A-Za-z-]{1,63}(? { if (typeof _userOptions === "number" || typeof _userOptions === "string") { _userOptions = { port: Number.parseInt(_userOptions + "") || 0 }; } const _port = Number(_userOptions.port ?? process.env.PORT); const _userSpecifiedAnyPort = Boolean( _userOptions.port || _userOptions.ports?.length || _userOptions.portRange?.length, ); const options = { name: "default", random: _port === 0, ports: [], portRange: [], alternativePortRange: _userSpecifiedAnyPort ? [] : [3000, 3100], verbose: false, ..._userOptions, port: _port, host: _validateHostname( _userOptions.host ?? process.env.HOST, _userOptions.public, _userOptions.verbose, ), } as GetPortOptions; if (options.random && !_userSpecifiedAnyPort) { return getRandomPort(options.host); } // Generate list of ports to check const portsToCheck: PortNumber[] = [ options.port, ...options.ports, ..._generateRange(...options.portRange), ].filter((port) => { if (!port) { return false; } if (!isSafePort(port)) { _log(options.verbose, `Ignoring unsafe port: ${port}`); return false; } return true; }); if (portsToCheck.length === 0) { portsToCheck.push(3000); } // Try to find a port let availablePort = await _findPort(portsToCheck, options.host); // Try fallback port range if (!availablePort && options.alternativePortRange.length > 0) { availablePort = await _findPort( _generateRange(...options.alternativePortRange), options.host, ); if (portsToCheck.length > 0) { let message = `Unable to find an available port (tried ${portsToCheck.join( "-", )} ${_fmtOnHost(options.host)}).`; if (availablePort) { message += ` Using alternative port ${availablePort}.`; } _log(options.verbose, message); } } // Try random port if (!availablePort && _userOptions.random !== false) { availablePort = await getRandomPort(options.host); if (availablePort) { _log(options.verbose, `Using random port ${availablePort}`); } } // Throw error if no port is available if (!availablePort) { const triedRanges = [ options.port, options.portRange.join("-"), options.alternativePortRange.join("-"), ] .filter(Boolean) .join(", "); throw new GetPortError( `Unable to find an available port ${_fmtOnHost( options.host, )} (tried ${triedRanges})`, ); } return availablePort; } export async function getRandomPort(host?: HostAddress) { const port = await checkPort(0, host); if (port === false) { throw new GetPortError(`Unable to find a random port ${_fmtOnHost(host)}`); } return port; } export async function waitForPort( port: PortNumber, options: WaitForPortOptions = {}, ) { const delay = options.delay || 500; const retries = options.retries || 4; for (let index = retries; index > 0; index--) { if ((await _tryPort(port, options.host)) === false) { return; } await new Promise((resolve) => setTimeout(resolve, delay)); } throw new GetPortError( `Timeout waiting for port ${port} after ${retries} retries with ${delay}ms interval.`, ); } export async function checkPort( port: PortNumber, host: HostAddress | HostAddress[] = process.env.HOST, verbose?: boolean, ): Promise { if (!host) { host = _getLocalHosts([undefined /* default */, "0.0.0.0"]); } if (!Array.isArray(host)) { return _tryPort(port, host); } for (const _host of host) { const _port = await _tryPort(port, _host); if (_port === false) { if (port < 1024 && verbose) { _log( verbose, `Unable to listen to the privileged port ${port} ${_fmtOnHost( _host, )}`, ); } return false; } if (port === 0 && _port !== 0) { port = _port; } } return port; } ================================================ FILE: src/index.ts ================================================ export * from "./types"; export * from "./get-port"; export * from "./unsafe-ports"; export * from "./socket"; ================================================ FILE: src/socket.ts ================================================ import { tmpdir } from "node:os"; import { join } from "node:path"; import { Server } from "node:net"; import { rm } from "node:fs/promises"; export interface GetSocketOptions { /* Human readable prefix for the socket name */ name: string; /** * Use process ID in the socket name. */ pid?: boolean; /** * Use a random number in the socket name. * */ random?: boolean; } let _nodeMajorVersion: number | undefined; let _isSocketSupported: boolean | undefined; /** * Generates a socket address based on the provided options. */ export function getSocketAddress(opts: GetSocketOptions): string { const parts = [ opts.name, opts.pid ? process.pid : undefined, opts.random ? Math.round(Math.random() * 10_000) : undefined, ].filter(Boolean); const socketName = `${parts.join("-")}.sock`; // Windows: pipe if (process.platform === "win32") { return join(String.raw`\\.\pipe`, socketName); } // Linux: abstract namespace // Fallback to a regular file socket on older Node.js versions to avoid issues if (process.platform === "linux") { if (_nodeMajorVersion === undefined) { _nodeMajorVersion = +process.versions.node.split(".")[0]; } if (_nodeMajorVersion >= 20) { return `\0${socketName}`; } } // Unix socket return join(tmpdir(), socketName); } /** * Test if the current environment supports sockets. */ export async function isSocketSupported(): Promise { if (_isSocketSupported !== undefined) { return _isSocketSupported; } if (globalThis.process?.versions?.webcontainer) { // Seems broken: https://stackblitz.com/edit/stackblitz-starters-1y68uhvu return false; } const socketAddress = getSocketAddress({ name: "get-port", random: true }); const server = new Server(); try { await new Promise((resolve, reject) => { server.on("error", reject); server.listen(socketAddress, resolve); }); _isSocketSupported = true; return true; } catch { _isSocketSupported = false; return false; } finally { if (server.listening) { server.close(); await cleanSocket(socketAddress); } } } export async function cleanSocket(path: string): Promise { if (!path || path[0] === "\0" || path.startsWith(String.raw`\\.\pipe`)) { // Abstract namespace sockets or invalid paths return; } return rm(path, { force: true, recursive: true }).catch(console.error); } ================================================ FILE: src/types.ts ================================================ export interface GetPortOptions { name: string; random: boolean; port: number; ports: number[]; portRange: [fromInclusive: number, toInclusive: number]; alternativePortRange: [fromInclusive: number, toInclusive: number]; host: string; verbose?: boolean; public?: boolean; } export interface WaitForPortOptions { host?: HostAddress; delay?: number; retries?: number; } export type GetPortInput = Partial | number | string; export type HostAddress = undefined | string; export type PortNumber = number; ================================================ FILE: src/unsafe-ports.ts ================================================ // https://chromium.googlesource.com/chromium/src.git/+/refs/heads/master/net/base/port_util.cc const unsafePorts = new Set([ 1, // tcpmux 7, // echo 9, // discard 11, // systat 13, // daytime 15, // netstat 17, // qotd 19, // chargen 20, // ftp data 21, // ftp access 22, // ssh 23, // telnet 25, // smtp 37, // time 42, // name 43, // nicname 53, // domain 69, // tftp 77, // priv-rjs 79, // finger 87, // ttylink 95, // supdup 101, // hostriame 102, // iso-tsap 103, // gppitnp 104, // acr-nema 109, // pop2 110, // pop3 111, // sunrpc 113, // auth 115, // sftp 117, // uucp-path 119, // nntp 123, // NTP 135, // loc-srv /epmap 137, // netbios 139, // netbios 143, // imap2 161, // snmp 179, // BGP 389, // ldap 427, // SLP (Also used by Apple Filing Protocol) 465, // smtp+ssl 512, // print / exec 513, // login 514, // shell 515, // printer 526, // tempo 530, // courier 531, // chat 532, // netnews 540, // uucp 548, // AFP (Apple Filing Protocol) 554, // rtsp 556, // remotefs 563, // nntp+ssl 587, // smtp (rfc6409) 601, // syslog-conn (rfc3195) 636, // ldap+ssl 989, // ftps-data 990, // ftps 993, // ldap+ssl 995, // pop3+ssl 1719, // h323gatestat 1720, // h323hostcall 1723, // pptp 2049, // nfs 3659, // apple-sasl / PasswordServer 4045, // lockd 5060, // sip 5061, // sips 6000, // X11 6566, // sane-port 6665, // Alternate IRC [Apple addition] 6666, // Alternate IRC [Apple addition] 6667, // Standard IRC [Apple addition] 6668, // Alternate IRC [Apple addition] 6669, // Alternate IRC [Apple addition] 6697, // IRC + TLS 10_080, // Amanda ]); export function isUnsafePort(port: number) { return unsafePorts.has(port); } export function isSafePort(port: number) { return !isUnsafePort(port); } ================================================ FILE: test/index.test.ts ================================================ import { Server } from "node:net"; import { networkInterfaces } from "node:os"; import { describe, test, expect, beforeEach, afterEach, vi } from "vitest"; import { getPort, getRandomPort } from "../src"; import { _generateRange, _getLocalHosts } from "../src/_internal"; import { blockPort } from "./utils"; const isWindows = process.platform === "win32"; describe("getPort", () => { let portBlocker: Server; afterEach(() => { portBlocker?.close(); }); describe("default host", () => { test("default port is not in use", async () => { const port = await getPort(); expect(port).toEqual(3000); }); test("default port is in use", async () => { portBlocker = await blockPort(3000); const port = await getPort(); expect(port).toEqual(3001); }); }); describe("order", () => { test("ports is preferred", async () => { const port = await getPort({ ports: [8080] }); expect(port).toEqual(8080); }); test("portRange is preferred over random", async () => { const port = await getPort({ random: true, portRange: [8081, 8085] }); expect(port).toEqual(8081); }); }); describe("localhost", () => { test("default port is not in use", async () => { const port = await getPort({ host: "localhost" }); expect(port).toEqual(3000); }); test("default port is in use", async () => { process.env.HOST = "localhost"; portBlocker = await blockPort(3000, "localhost"); const port1 = await getPort({ port: 3000, portRange: [3000, 3100] }); expect(port1).toEqual(3001); const port2 = await getPort(); expect(port2).toEqual(3001); const port3 = await getPort(3000); expect(port3).not.toEqual(3001); }); }); describe("ipv6", () => { test("get port on ::1", async () => { await blockPort(3000, "::1"); const port = await getPort({ host: "::1" }); expect(port).not.toBe(3000); }); }); }); describe("random port", () => { test("{ random: true }", async () => { const port = await getPort({ random: true }); expect(typeof port).toBe("number"); expect(port).not.toBe(3000); }); test("getRandomPort", async () => { const port = await getRandomPort(); expect(typeof port).toBe("number"); expect(port).not.toBe(3000); }); test("{ port: 0 }", async () => { let port = await getPort({ port: 0 }); expect(typeof port).toBe("number"); expect(port).not.toBe(3000); port = await getPort({ port: "0" as any }); expect(typeof port).toBe("number"); expect(port).not.toBe(3000); }); }); describe("errors", () => { beforeEach(() => { vi.spyOn(console, "log").mockImplementation(() => undefined); }); afterEach(() => { vi.resetAllMocks(); }); test("invalid hostname", async () => { await getPort({ host: "http://localhost:8080", verbose: true }).catch( (error) => error, ); expect(console.log).toHaveBeenCalledWith( '[get-port] Invalid hostname: "http://localhost:8080". Using "127.0.0.1" as fallback.', ); }); test("invalid hostname (public)", async () => { await getPort({ host: "http://localhost:8080", verbose: true, public: true, }).catch((error) => error); expect(console.log).toHaveBeenCalledWith( '[get-port] Invalid hostname: "http://localhost:8080". Using "0.0.0.0" as fallback.', ); }); test.skipIf(isWindows)("unavailable port", async () => { const error = await getPort({ host: "192.168.1.999", }).catch((error) => error); expect(error.toString()).toMatchInlineSnapshot( `"GetPortError: Unable to find a random port on host "192.168.1.999""`, ); }); test.skipIf(isWindows)("unavailable port (no random)", async () => { const error = await getPort({ host: "192.168.1.999", random: false, }).catch((error) => error); expect(error.toString()).toMatchInlineSnapshot( `"GetPortError: Unable to find an available port on host "192.168.1.999" (tried 3000-3100)"`, ); }); }); describe("internal tools", () => { describe("_generateRange", () => { test("returns a normal range [from, to) if from < to", () => { const from = 1; const to = 5; const range = _generateRange(from, to); expect(range).to.eql([1, 2, 3, 4, 5]); }); test("returns a singleton array if from = to", () => { const from = 1; const to = 1; const range = _generateRange(from, to); expect(range).to.eql([1]); }); test("returns an empty array if from > to", () => { const from = 5; const to = 1; const range = _generateRange(from, to); expect(range).to.eql([]); }); }); }); vi.mock("node:os", () => { return { networkInterfaces: vi.fn(), }; }); describe("_getLocalHosts", () => { test("should return the allowed host addresses", () => { vi.mocked(networkInterfaces).mockImplementation(() => ({ eth0: [ { address: "192.168.1.100", family: "IPv4", internal: false, netmask: "0", mac: "0", cidr: "", }, { address: "fe80::1", family: "IPv6", internal: false, scopeid: 1, netmask: "0", mac: "0", cidr: "", }, ], lo: [ { address: "127.0.0.1", family: "IPv4", internal: true, netmask: "0", mac: "0", cidr: "", }, { address: "169.254.0.1", family: "IPv4", internal: false, netmask: "0", mac: "0", cidr: "", }, ], })); // call the function with additional hosts const additionalHosts = ["192.168.1.200"]; const result = _getLocalHosts(additionalHosts); expect(result).toEqual(["192.168.1.200", "192.168.1.100"]); vi.clearAllMocks(); }); }); ================================================ FILE: test/socket.test.ts ================================================ import { describe, test, expect } from "vitest"; import { isSocketSupported, getSocketAddress } from "../src"; describe("socket", () => { test("isSocketSupported", async () => { expect(await isSocketSupported()).toBe(true); }); test("getSocketAddress", async () => { const addr = getSocketAddress({ name: "test", pid: true, random: true }); expect(addr).toMatch(/test-\d+-\d+\.sock/); }); }); ================================================ FILE: test/utils.ts ================================================ import { createServer, Server } from "node:net"; export function blockPort(port: number, host?: string): Promise { return new Promise((resolve) => { const blocker = createServer(); blocker.listen(port, host, () => { resolve(blocker); }); }); } export async function blockPorts( ports: number[], host?: string, ): Promise { const portBlockers: Server[] = []; for (const port of ports) { const blocker = await blockPort(port, host); portBlockers.push(blocker); } return portBlockers; } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "ESNext", "moduleResolution": "Node", "esModuleInterop": true }, "include": ["src"] }