@@ -20,85 +20,108 @@ export type GetPortInput = Partial<GetPortOptions> | number | string;
20
20
export type HostAddress = undefined | string ;
21
21
export type PortNumber = number ;
22
22
23
- function log ( ...arguments_ ) {
24
- // eslint-disable-next-line no-console
25
- console . log ( "[get-port]" , ...arguments_ ) ;
26
- }
23
+ const HOSTNAME_RE = / ^ (? ! - ) [ \d . A - Z a - z - ] { 1 , 63 } (?< ! - ) $ / ;
27
24
28
- export async function getPort ( config : GetPortInput = { } ) : Promise < PortNumber > {
29
- if ( typeof config === "number" || typeof config === "string" ) {
30
- config = { port : Number . parseInt ( config + "" ) || 0 } ;
25
+ export async function getPort (
26
+ _userOptions : GetPortInput = { } ,
27
+ ) : Promise < PortNumber > {
28
+ if ( typeof _userOptions === "number" || typeof _userOptions === "string" ) {
29
+ _userOptions = { port : Number . parseInt ( _userOptions + "" ) || 0 } ;
31
30
}
32
31
33
- const _port = Number ( config . port ?? process . env . PORT ?? 3000 ) ;
32
+ const _port = Number ( _userOptions . port ?? process . env . PORT ?? 3000 ) ;
34
33
35
34
const options = {
36
35
name : "default" ,
37
36
random : _port === 0 ,
38
37
ports : [ ] ,
39
38
portRange : [ ] ,
40
- alternativePortRange : config . port ? [ ] : [ 3000 , 3100 ] ,
39
+ alternativePortRange : _userOptions . port ? [ ] : [ 3000 , 3100 ] ,
41
40
host : undefined ,
42
41
verbose : false ,
43
- ...config ,
42
+ ..._userOptions ,
44
43
port : _port ,
45
44
} as GetPortOptions ;
46
45
46
+ if ( options . host && ! HOSTNAME_RE . test ( options . host ) ) {
47
+ throw new GetPortError ( `Invalid host: ${ JSON . stringify ( options . host ) } ` ) ;
48
+ }
49
+
47
50
if ( options . random ) {
48
51
return getRandomPort ( options . host ) ;
49
52
}
50
53
51
- // Ports to check
52
-
54
+ // Generate list of ports to check
53
55
const portsToCheck : PortNumber [ ] = [
54
56
options . port ,
55
57
...options . ports ,
56
- ...generateRange ( ...options . portRange ) ,
58
+ ..._generateRange ( ...options . portRange ) ,
57
59
] . filter ( ( port ) => {
58
60
if ( ! port ) {
59
61
return false ;
60
62
}
61
63
if ( ! isSafePort ( port ) ) {
62
- if ( options . verbose ) {
63
- log ( "Ignoring unsafe port:" , port ) ;
64
- }
64
+ _log ( options . verbose , `Ignoring unsafe port: ${ port } ` ) ;
65
65
return false ;
66
66
}
67
67
return true ;
68
68
} ) ;
69
69
70
70
// Try to find a port
71
- let availablePort = await findPort (
71
+ let availablePort = await _findPort (
72
72
portsToCheck ,
73
73
options . host ,
74
74
options . verbose ,
75
- false ,
76
75
) ;
77
76
78
77
// Try fallback port range
79
- if ( ! availablePort ) {
80
- availablePort = await findPort (
81
- generateRange ( ...options . alternativePortRange ) ,
78
+ if ( ! availablePort && options . alternativePortRange . length > 0 ) {
79
+ availablePort = await _findPort (
80
+ _generateRange ( ...options . alternativePortRange ) ,
82
81
options . host ,
83
82
options . verbose ,
84
83
) ;
85
- if ( options . verbose ) {
86
- log (
87
- `Unable to find an available port (tried ${
88
- portsToCheck . join ( ", " ) || "-"
89
- } ). Using alternative port:`,
90
- availablePort ,
91
- ) ;
84
+ _log (
85
+ options . verbose ,
86
+ `Unable to find an available port (tried ${ options . alternativePortRange . join (
87
+ "-" ,
88
+ ) } ${ _fmtOnHost ( options . host ) } ). Using alternative port ${ availablePort } `,
89
+ ) ;
90
+ }
91
+
92
+ // Try random port
93
+ if ( ! availablePort && _userOptions . random !== false ) {
94
+ availablePort = await getRandomPort ( options . host ) ;
95
+ if ( availablePort ) {
96
+ _log ( options . verbose , `Using random port ${ availablePort } ` ) ;
92
97
}
93
98
}
94
99
100
+ // Throw error if no port is available
101
+ if ( ! availablePort ) {
102
+ const triedRanges = [
103
+ options . port ,
104
+ options . portRange . join ( "-" ) ,
105
+ options . alternativePortRange . join ( "-" ) ,
106
+ ]
107
+ . filter ( Boolean )
108
+ . join ( ", " ) ;
109
+ throw new GetPortError (
110
+ `Unable to find find available port ${ _fmtOnHost (
111
+ options . host ,
112
+ ) } (tried ${ triedRanges } )`,
113
+ ) ;
114
+ }
115
+
95
116
return availablePort ;
96
117
}
97
118
98
119
export async function getRandomPort ( host ?: HostAddress ) {
99
120
const port = await checkPort ( 0 , host ) ;
100
121
if ( port === false ) {
101
- throw new Error ( "Unable to obtain an available random port number!" ) ;
122
+ throw new GetPortError (
123
+ `Unable to find any random port ${ _fmtOnHost ( host ) } ` ,
124
+ ) ;
102
125
}
103
126
return port ;
104
127
}
@@ -120,27 +143,32 @@ export async function waitForPort(
120
143
}
121
144
await new Promise ( ( resolve ) => setTimeout ( resolve , delay ) ) ;
122
145
}
123
- throw new Error (
146
+ throw new GetPortError (
124
147
`Timeout waiting for port ${ port } after ${ retries } retries with ${ delay } ms interval.` ,
125
148
) ;
126
149
}
127
150
128
151
export async function checkPort (
129
152
port : PortNumber ,
130
153
host : HostAddress | HostAddress [ ] = process . env . HOST ,
131
- _verbose ?: boolean ,
154
+ verbose ?: boolean ,
132
155
) : Promise < PortNumber | false > {
133
156
if ( ! host ) {
134
- host = getLocalHosts ( [ undefined /* default */ , "0.0.0.0" ] ) ;
157
+ host = _getLocalHosts ( [ undefined /* default */ , "0.0.0.0" ] ) ;
135
158
}
136
159
if ( ! Array . isArray ( host ) ) {
137
160
return _checkPort ( port , host ) ;
138
161
}
139
162
for ( const _host of host ) {
140
163
const _port = await _checkPort ( port , _host ) ;
141
164
if ( _port === false ) {
142
- if ( port < 1024 && _verbose ) {
143
- log ( "Unable to listen to priviliged port:" , `${ _host } :${ port } ` ) ;
165
+ if ( port < 1024 && verbose ) {
166
+ _log (
167
+ verbose ,
168
+ `Unable to listen to the priviliged port ${ port } ${ _fmtOnHost (
169
+ _host ,
170
+ ) } `,
171
+ ) ;
144
172
}
145
173
return false ;
146
174
}
@@ -153,7 +181,23 @@ export async function checkPort(
153
181
154
182
// ----- Internal -----
155
183
156
- function generateRange ( from : number , to : number ) : number [ ] {
184
+ class GetPortError extends Error {
185
+ name = "GetPortError" ;
186
+ constructor (
187
+ public message : string ,
188
+ opts ?: any ,
189
+ ) {
190
+ super ( message , opts ) ;
191
+ }
192
+ }
193
+
194
+ function _log ( showLogs : boolean , message : string ) {
195
+ if ( showLogs ) {
196
+ console . log ( "[get-port]" , message ) ;
197
+ }
198
+ }
199
+
200
+ function _generateRange ( from : number , to : number ) : number [ ] {
157
201
if ( to < from ) {
158
202
return [ ] ;
159
203
}
@@ -188,7 +232,7 @@ function _checkPort(
188
232
} ) ;
189
233
}
190
234
191
- function getLocalHosts ( additional ? : HostAddress [ ] ) : HostAddress [ ] {
235
+ function _getLocalHosts ( additional : HostAddress [ ] ) : HostAddress [ ] {
192
236
const hosts = new Set < HostAddress > ( additional ) ;
193
237
for ( const _interface of Object . values ( networkInterfaces ( ) ) ) {
194
238
for ( const config of _interface || [ ] ) {
@@ -198,30 +242,19 @@ function getLocalHosts(additional?: HostAddress[]): HostAddress[] {
198
242
return [ ...hosts ] ;
199
243
}
200
244
201
- async function findPort (
245
+ async function _findPort (
202
246
ports : number [ ] ,
203
- host ?: HostAddress ,
204
- _verbose = false ,
205
- _random = true ,
247
+ host : HostAddress ,
248
+ _verbose : boolean ,
206
249
) : Promise < PortNumber > {
207
250
for ( const port of ports ) {
208
251
const r = await checkPort ( port , host , _verbose ) ;
209
252
if ( r ) {
210
253
return r ;
211
254
}
212
255
}
213
- if ( _random ) {
214
- const randomPort = await getRandomPort ( host ) ;
215
- if ( _verbose ) {
216
- log (
217
- `Unable to find an available port (tried ${
218
- ports . join ( ", " ) || "-"
219
- } ). Using random port:`,
220
- randomPort ,
221
- ) ;
222
- }
223
- return randomPort ;
224
- } else {
225
- return 0 ;
226
- }
256
+ }
257
+
258
+ function _fmtOnHost ( hostname : string | undefined ) {
259
+ return hostname ? `on host ${ JSON . stringify ( hostname ) } ` : "on any host" ;
227
260
}
0 commit comments