Skip to content

Commit b6ee3b9

Browse files
committed
feat(dev): add --strictPort flag
1 parent c1c6687 commit b6ee3b9

3 files changed

Lines changed: 75 additions & 14 deletions

File tree

‎packages/nuxt-cli/src/commands/dev.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ const command = defineCommand({
4949
description: 'Port to listen on (default: `NUXT_PORT || NITRO_PORT || PORT || nuxtOptions.devServer.port`)',
5050
alias: ['p'],
5151
},
52+
'strictPort': {
53+
type: 'boolean',
54+
description: 'Exit if the requested port is unavailable instead of using another one',
55+
default: false,
56+
},
5257
'host': {
5358
type: 'string',
5459
description: 'Host to listen on (default: `NUXT_HOST || NITRO_HOST || HOST || nuxtOptions.devServer?.host`)',
@@ -291,6 +296,7 @@ function resolveListenOverrides(args: ParsedArgs<ArgsT>): DevListenOverrides {
291296
||process.env.NITRO_PORT
292297
||process.env.PORT
293298
||undefined,
299+
strictPort: args.strictPort,
294300
hostname: host===true||host==='' ? '' : host||undefined,
295301
open: args.open,
296302
clipboard: args.clipboard,

‎packages/nuxt-cli/src/dev/listen.ts‎

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { createServer as createHttpsServer } from 'node:https'
99
import{networkInterfaces,release}from'node:os'
1010
importprocessfrom'node:process'
1111

12-
import{getPort}from'get-port-please'
12+
import{checkPort,getPort}from'get-port-please'
1313
importcolorsfrom'picocolors'
1414

1515
import{debug,logger}from'../utils/logger'
@@ -19,6 +19,8 @@ import { startTunnel } from './tunnel'
1919

2020
exportinterfaceListenOptions{
2121
port?: string|number
22+
/** Fail instead of falling back to another port when `port` is unavailable. */
23+
strictPort?: boolean
2224
hostname?: string
2325
baseURL?: string
2426
showURL?: boolean
@@ -98,16 +100,7 @@ export async function listen(handler: RequestListener, options: ListenOptions =
98100
consthostname=options.hostname??(options.public ? '' : 'localhost')
99101

100102
constrequestedPort=options.port===undefined||options.port==='' ? undefined : Number(options.port)
101-
constport=requestedPort===0
102-
? awaitgetPort({random: true,host: hostname||undefined})
103-
: awaitgetPort({
104-
port: requestedPort,
105-
alternativePortRange: [3000,3100],
106-
host: hostname||undefined,
107-
})
108-
if(requestedPort&&port!==requestedPort){
109-
logger.warn(`Port ${requestedPort} is in use, using port ${port} instead.`)
110-
}
103+
constport=awaitresolvePort(requestedPort,hostname,options.strictPort)
111104

112105
consthttpsOptions=options.https===true ? {} : options.https
113106
constcertificate=httpsOptions ? awaitresolveCertificate(httpsOptions) : false
@@ -228,6 +221,30 @@ export async function listen(handler: RequestListener, options: ListenOptions =
228221
}
229222
}
230223

224+
asyncfunctionresolvePort(requestedPort: number|undefined,hostname: string,strictPort?: boolean): Promise<number>{
225+
if(requestedPort===0){
226+
returngetPort({random: true,host: hostname||undefined})
227+
}
228+
229+
if(strictPort){
230+
constdesiredPort=requestedPort??3000
231+
if(awaitcheckPort(desiredPort,hostname||undefined)===false){
232+
thrownewError(`Port ${desiredPort} is already in use (\`--strictPort\` is enabled).`)
233+
}
234+
returndesiredPort
235+
}
236+
237+
constport=awaitgetPort({
238+
port: requestedPort,
239+
alternativePortRange: [3000,3100],
240+
host: hostname||undefined,
241+
})
242+
if(requestedPort&&port!==requestedPort){
243+
logger.warn(`Port ${requestedPort} is in use, using port ${port} instead.`)
244+
}
245+
returnport
246+
}
247+
231248
functioncenterBlock(block: string): string{
232249
constlines=block.split('\n')
233250
constwidth=Math.max(...lines.map(line=>line.length))

‎packages/nuxt-cli/test/unit/listen.spec.ts‎

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
importtype{Listener}from'../../src/dev/listen'
2+
13
import{networkInterfaces}from'node:os'
24

3-
import{describe,expect,it,vi}from'vitest'
5+
import{afterEach,describe,expect,it,vi}from'vitest'
46

5-
import{getNetworkAddresses,resolveOpenCommand}from'../../src/dev/listen'
7+
import{getNetworkAddresses,listen,resolveOpenCommand}from'../../src/dev/listen'
68

79
vi.mock('node:os',()=>({
8-
networkInterfaces: vi.fn(),
10+
networkInterfaces: vi.fn(()=>({})),
911
release: ()=>'test',
1012
}))
1113

@@ -62,3 +64,39 @@ describe('resolveOpenCommand', () => {
6264
expect(resolveOpenCommand(url,'darwin',{BROWSER: '/usr/local/bin/firefox'})).toEqual(['/usr/local/bin/firefox',[url]])
6365
})
6466
})
67+
68+
describe('listen',()=>{
69+
constlisteners: Listener[]=[]
70+
71+
afterEach(async()=>{
72+
awaitPromise.all(listeners.splice(0).map(listener=>listener.close()))
73+
})
74+
75+
asyncfunctionstart(options: Parameters<typeoflisten>[1]){
76+
constlistener=awaitlisten((_req,res)=>res.end('ok'),{showURL: false, ...options})
77+
listeners.push(listener)
78+
returnlistener
79+
}
80+
81+
it('should fall back to another port by default',async()=>{
82+
constfirst=awaitstart({port: 0})
83+
constsecond=awaitstart({port: first.address.port})
84+
85+
expect(second.address.port).not.toBe(first.address.port)
86+
})
87+
88+
it('should throw for a busy port with `strictPort`',async()=>{
89+
constfirst=awaitstart({port: 0})
90+
91+
awaitexpect(start({port: first.address.port,strictPort: true})).rejects.toThrow(/alreadyinuse/)
92+
})
93+
94+
it('should use the requested port with `strictPort`',async()=>{
95+
const{ address }=awaitstart({port: 0})
96+
constport=address.port
97+
awaitPromise.all(listeners.splice(0).map(listener=>listener.close()))
98+
99+
constlistener=awaitstart({ port,strictPort: true})
100+
expect(listener.address.port).toBe(port)
101+
})
102+
})

0 commit comments

Comments
 (0)