11import crypto from 'node:crypto' ;
22import net from 'node:net' ;
3+ import os from 'node:os' ;
34import { SharedContext } from '@ava/cooperate' ;
45
56const context = new SharedContext ( import . meta. url ) ;
67
8+ const localHosts = new Set ( [
9+ undefined , // Default interfaces,
10+ '0.0.0.0' , // Ensure we check IPv4,
11+ ...Object . values ( os . networkInterfaces ( ) ) . flatMap ( interfaces => interfaces ?. map ( info => info . address ) ) ,
12+ ] ) ;
13+
714// Reserve a range of 16 addresses at a random offset.
815const reserveRange = async ( ) : Promise < number [ ] > => {
916 let from : number ;
@@ -15,24 +22,69 @@ const reserveRange = async (): Promise<number[]> => {
1522 return context . reserve ( ...range ) ;
1623} ;
1724
25+ const enum Availability {
26+ AVAILABLE ,
27+ UNAVAILABLE ,
28+ UNKNOWN ,
29+ }
30+
1831// Listen on the port to make sure it's available.
19- const confirmAvailable = async ( port : number , options ?: net . ListenOptions ) : Promise < boolean > => new Promise ( ( resolve , reject ) => {
32+ const confirmAvailableForHost = async ( {
33+ host,
34+ listenOptions,
35+ port,
36+ unknowable,
37+ } : {
38+ host : string | undefined ;
39+ listenOptions ?: net . ListenOptions ;
40+ port : number ;
41+ unknowable : boolean ;
42+ } ) : Promise < Availability > => new Promise ( ( resolve , reject ) => {
2043 const server = net . createServer ( ) ;
2144 server . unref ( ) ;
2245 server . on ( 'error' , ( error : Error & { code : string } ) => {
2346 if ( error . code === 'EADDRINUSE' || error . code === 'EACCESS' ) {
24- resolve ( false ) ;
47+ resolve ( Availability . UNAVAILABLE ) ;
48+ } else if ( unknowable && ( error . code === 'EADDRNOTAVAIL' || error . code === 'EINVAL' ) ) { // https://github.com/sindresorhus/get-port/blob/0760c987c17581395d4e30432881dcb0ca6ca94a/index.js#L65
49+ resolve ( Availability . UNKNOWN ) ; // The address itself is not available, so we can't check.
2550 } else {
2651 reject ( error ) ;
2752 }
2853 } ) ;
29- server . listen ( { ...options , port} , ( ) => {
54+ server . listen ( { ...listenOptions , host , port} , ( ) => {
3055 server . close ( ( ) => {
31- resolve ( true ) ;
56+ resolve ( Availability . AVAILABLE ) ;
3257 } ) ;
3358 } ) ;
3459} ) ;
3560
61+ const confirmAvailable = async ( port : number , listenOptions ?: net . ListenOptions ) : Promise < boolean > => {
62+ if ( listenOptions ?. host !== undefined ) {
63+ const available = await confirmAvailableForHost ( {
64+ host : listenOptions . host ,
65+ listenOptions,
66+ port,
67+ unknowable : false ,
68+ } ) ;
69+ return available === Availability . AVAILABLE ;
70+ }
71+
72+ for await ( const host of localHosts ) {
73+ const available = await confirmAvailableForHost ( {
74+ host,
75+ listenOptions,
76+ port,
77+ unknowable : true ,
78+ } ) ;
79+
80+ if ( available === Availability . UNAVAILABLE ) {
81+ return false ;
82+ }
83+ }
84+
85+ return true ;
86+ } ;
87+
3688let available : Promise < number [ ] > = reserveRange ( ) ;
3789export default async function getPort ( options ?: Omit < net . ListenOptions , 'port' > ) : Promise < number > {
3890 const promise = available ;
0 commit comments