1
1
import crypto from 'node:crypto' ;
2
2
import net from 'node:net' ;
3
+ import os from 'node:os' ;
3
4
import { SharedContext } from '@ava/cooperate' ;
4
5
5
6
const context = new SharedContext ( import . meta. url ) ;
6
7
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
+
7
14
// Reserve a range of 16 addresses at a random offset.
8
15
const reserveRange = async ( ) : Promise < number [ ] > => {
9
16
let from : number ;
@@ -15,24 +22,69 @@ const reserveRange = async (): Promise<number[]> => {
15
22
return context . reserve ( ...range ) ;
16
23
} ;
17
24
25
+ const enum Availability {
26
+ AVAILABLE ,
27
+ UNAVAILABLE ,
28
+ UNKNOWN ,
29
+ }
30
+
18
31
// 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 ) => {
20
43
const server = net . createServer ( ) ;
21
44
server . unref ( ) ;
22
45
server . on ( 'error' , ( error : Error & { code : string } ) => {
23
46
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.
25
50
} else {
26
51
reject ( error ) ;
27
52
}
28
53
} ) ;
29
- server . listen ( { ...options , port} , ( ) => {
54
+ server . listen ( { ...listenOptions , host , port} , ( ) => {
30
55
server . close ( ( ) => {
31
- resolve ( true ) ;
56
+ resolve ( Availability . AVAILABLE ) ;
32
57
} ) ;
33
58
} ) ;
34
59
} ) ;
35
60
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
+
36
88
let available : Promise < number [ ] > = reserveRange ( ) ;
37
89
export default async function getPort ( options ?: Omit < net . ListenOptions , 'port' > ) : Promise < number > {
38
90
const promise = available ;
0 commit comments