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
import process from 'node:process'
1111

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

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

2020
export interface ListenOptions {
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
const hostname = options.hostname ?? (options.public ? '' : 'localhost')
99101

100102
const requestedPort = options.port === undefined || options.port === '' ? undefined : Number(options.port)
101-
const port = requestedPort === 0
102-
? await getPort({ random: true, host: hostname || undefined })
103-
: await getPort({
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+
const port = await resolvePort(requestedPort, hostname, options.strictPort)
111104

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

224+
async function resolvePort(requestedPort: number | undefined, hostname: string, strictPort?: boolean): Promise<number> {
225+
if (requestedPort === 0) {
226+
return getPort({ random: true, host: hostname || undefined })
227+
}
228+
229+
if (strictPort) {
230+
const desiredPort = requestedPort ?? 3000
231+
if (await checkPort(desiredPort, hostname || undefined) === false) {
232+
throw new Error(`Port ${desiredPort} is already in use (\`--strictPort\` is enabled).`)
233+
}
234+
return desiredPort
235+
}
236+
237+
const port = await getPort({
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+
return port
246+
}
247+
231248
function centerBlock(block: string): string {
232249
const lines = block.split('\n')
233250
const width = 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+
import type { 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+
const listeners: Listener[] = []
70+
71+
afterEach(async () => {
72+
await Promise.all(listeners.splice(0).map(listener => listener.close()))
73+
})
74+
75+
async function start(options: Parameters<typeof listen>[1]) {
76+
const listener = await listen((_req, res) => res.end('ok'), { showURL: false, ...options })
77+
listeners.push(listener)
78+
return listener
79+
}
80+
81+
it('should fall back to another port by default', async () => {
82+
const first = await start({ port: 0 })
83+
const second = await start({ 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+
const first = await start({ port: 0 })
90+
91+
await expect(start({ port: first.address.port, strictPort: true })).rejects.toThrow(/already in use/)
92+
})
93+
94+
it('should use the requested port with `strictPort`', async () => {
95+
const { address } = await start({ port: 0 })
96+
const port = address.port
97+
await Promise.all(listeners.splice(0).map(listener => listener.close()))
98+
99+
const listener = await start({ port, strictPort: true })
100+
expect(listener.address.port).toBe(port)
101+
})
102+
})

0 commit comments

Comments
 (0)