Skip to content

Commit

Permalink
feat: check port against all available hosts
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Feb 25, 2022
1 parent 41e94e6 commit bac49cc
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 298 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Alternative ports to check. Default is `[4000, 5000, 6000, 7000]`

### `host`

The host to check. Default is `process.env.HOST || '0.0.0.0'`
The host to check. Default is `process.env.HOST` otherwise all available hosts will be checked.

### `memoDir` / `memoName`

Expand Down
49 changes: 45 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createServer, AddressInfo } from 'net'
import { networkInterfaces } from 'os'
import { getMemo, setMemo } from 'fs-memo'

export interface GetPortOptions {
Expand All @@ -23,7 +24,7 @@ export async function getPort (config?: GetPortInput): Promise<number> {
random: false,
port: parseInt(process.env.PORT || '') || 3000,
ports: [4000, 5000, 6000, 7000],
host: process.env.HOST || '0.0.0.0',
host: undefined,
memoName: 'port',
...config
} as GetPortOptions
Expand Down Expand Up @@ -69,14 +70,54 @@ export async function checkPorts (ports: number[], host?: string): Promise<numbe
return checkPort(0, host) as unknown as number
}

export function checkPort (port: number, host: string = process.env.HOST || '0.0.0.0'): Promise<number|false> {
export type HostAddress = undefined | string

export async function checkPort (port: number, host: HostAddress | HostAddress[] | undefined = process.env.HOST): Promise<number|false> {
if (!host) {
host = getLocalHosts([undefined /* default */, '0.0.0.0'])
}
if (!Array.isArray(host)) {
return _checkPort(port, host)
}
for (const _host of host) {
const _port = await _checkPort(port, _host)
if (_port === false) {
return false
}
if (port === 0 && _port !== 0) {
port = _port
}
}
return port
}

// ----- Internal -----

function _checkPort (port: number, host: HostAddress): Promise<number|false> {
return new Promise((resolve) => {
const server = createServer()
server.unref()
server.on('error', () => { resolve(false) })
server.listen(port, host, () => {
server.on('error', (err: Error & { code: string }) => {
// Ignore invalid host
if (err.code === 'EINVAL' || err.code === 'EADDRNOTAVAIL') {
resolve(port !== 0 ? port : false)
} else {
resolve(false)
}
})
server.listen({ port, host }, () => {
const { port } = server.address() as AddressInfo
server.close(() => { resolve(port) })
})
})
}

function getLocalHosts (additional?: HostAddress[]): HostAddress[] {
const hosts = new Set<HostAddress>(additional)
for (const _interface of Object.values(networkInterfaces())) {
for (const config of _interface!) {
hosts.add(config.address)
}
}
return Array.from(hosts)
}
15 changes: 15 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,18 @@ describe('getPort (host)', () => {
})
})
})

describe('getPort (host)', () => {
let portBlocker: Server

afterEach(() => {
portBlocker?.close()
})

test('default port is in use', async () => {
process.env.HOST = 'localhost'
portBlocker = await blockPort(3000, 'localhost')
const port = await getPort()
expect(port).toEqual(4000)
})
})
31 changes: 0 additions & 31 deletions test/process.env.test.ts

This file was deleted.

Loading

0 comments on commit bac49cc

Please sign in to comment.