Skip to content

Commit

Permalink
Allow port 0 in next dev and next start (#40118)
Browse files Browse the repository at this point in the history
This addresses a bug where invoking `next dev` or `next start` with `--port 0` would fall back to the default port of 3000 instead of binding to port 0 (which typically results in the operating system assigning a free port).

I couldn't find a straightforward way of adding a test for next-start. It looks like we could add a similar test as for dev, but would need to generate a built project to serve. 

Manual test plan for `next start`:
```
$ ./packages/next/dist/bin/next start --port 0
ready - started server on 0.0.0.0:53508, url: http://localhost:53508
```

## Bug

- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [x] Make sure the linting passes by running `pnpm lint`
- [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)

Co-authored-by: Steven <steven@ceriously.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
Co-authored-by: Wyatt Johnson <accounts+github@wyattjoh.ca>
  • Loading branch information
4 people committed Aug 31, 2022
1 parent 92aafcb commit 643447e
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 19 deletions.
15 changes: 5 additions & 10 deletions packages/next/cli/next-dev.ts
Expand Up @@ -2,7 +2,7 @@
import arg from 'next/dist/compiled/arg/index.js'
import { existsSync, watchFile } from 'fs'
import { startServer } from '../server/lib/start-server'
import { printAndExit } from '../server/lib/utils'
import { getPort, printAndExit } from '../server/lib/utils'
import * as Log from '../build/output/log'
import { startedDevelopmentServer } from '../build/output'
import { cliCommand } from '../lib/commands'
Expand Down Expand Up @@ -75,16 +75,11 @@ const nextDev: cliCommand = (argv) => {
)
}
}
const allowRetry = !args['--port']
let port: number =
args['--port'] || (process.env.PORT && parseInt(process.env.PORT)) || 3000

// we allow the server to use a random port while testing
// instead of attempting to find a random port and then hope
// it doesn't become occupied before we leverage it
if (process.env.__NEXT_FORCED_PORT) {
port = parseInt(process.env.__NEXT_FORCED_PORT, 10) || 0
}
const port = getPort(args)
// If neither --port nor PORT were specified, it's okay to retry new ports.
const allowRetry =
args['--port'] === undefined && process.env.PORT === undefined

// We do not set a default host value here to prevent breaking
// some set-ups that rely on listening on other interfaces
Expand Down
9 changes: 2 additions & 7 deletions packages/next/cli/next-start.ts
Expand Up @@ -2,7 +2,7 @@

import arg from 'next/dist/compiled/arg/index.js'
import { startServer } from '../server/lib/start-server'
import { printAndExit } from '../server/lib/utils'
import { getPort, printAndExit } from '../server/lib/utils'
import * as Log from '../build/output/log'
import isError from '../lib/is-error'
import { getProjectDir } from '../lib/get-project-dir'
Expand Down Expand Up @@ -52,13 +52,8 @@ const nextStart: cliCommand = (argv) => {
}

const dir = getProjectDir(args._[0])
let port: number =
args['--port'] || (process.env.PORT && parseInt(process.env.PORT)) || 3000
const host = args['--hostname'] || '0.0.0.0'

if (process.env.__NEXT_FORCED_PORT) {
port = parseInt(process.env.__NEXT_FORCED_PORT, 10) || 0
}
const port = getPort(args)

const keepAliveTimeoutArg: number | undefined = args['--keepAliveTimeout']
if (
Expand Down
15 changes: 15 additions & 0 deletions packages/next/server/lib/utils.ts
@@ -1,3 +1,5 @@
import type arg from 'next/dist/compiled/arg/index.js'

export function printAndExit(message: string, code = 1) {
if (code === 0) {
console.log(message)
Expand All @@ -12,3 +14,16 @@ export function getNodeOptionsWithoutInspect() {
const NODE_INSPECT_RE = /--inspect(-brk)?(=\S+)?( |$)/
return (process.env.NODE_OPTIONS || '').replace(NODE_INSPECT_RE, '')
}

export function getPort(args: arg.Result<arg.Spec>): number {
if (typeof args['--port'] === 'number') {
return args['--port']
}

const parsed = process.env.PORT && parseInt(process.env.PORT, 10)
if (typeof parsed === 'number' && !Number.isNaN(parsed)) {
return parsed
}

return 3000
}
28 changes: 28 additions & 0 deletions test/integration/cli/test/index.test.js
Expand Up @@ -178,6 +178,34 @@ describe('CLI Usage', () => {
expect(output).toMatch(new RegExp(`http://localhost:${port}`))
})

test('--port 0', async () => {
const output = await runNextCommandDev([dir, '--port', '0'], true)
const matches = /on 0.0.0.0:(\d+)/.exec(output)
expect(matches).not.toBe(null)

const port = parseInt(matches[1])
// Regression test: port 0 was interpreted as if no port had been
// provided, falling back to 3000.
expect(port).not.toBe(3000)

expect(output).toMatch(new RegExp(`http://localhost:${port}`))
})

test('PORT=0', async () => {
const output = await runNextCommandDev([dir], true, {
env: { PORT: 0 },
})
const matches = /on 0.0.0.0:(\d+)/.exec(output)
expect(matches).not.toBe(null)

const port = parseInt(matches[1])
// Regression test: port 0 was interpreted as if no port had been
// provided, falling back to 3000.
expect(port).not.toBe(3000)

expect(output).toMatch(new RegExp(`http://localhost:${port}`))
})

test("NODE_OPTIONS='--inspect'", async () => {
// this test checks that --inspect works by launching a single debugger for the main Next.js process,
// not for its subprocesses
Expand Down
2 changes: 1 addition & 1 deletion test/lib/next-modes/next-dev.ts
Expand Up @@ -37,8 +37,8 @@ export class NextDevInstance extends NextInstance {
...process.env,
...this.env,
NODE_ENV: '' as any,
PORT: this.forcedPort || '0',
__NEXT_TEST_MODE: '1',
__NEXT_FORCED_PORT: this.forcedPort || '0',
__NEXT_TEST_WITH_DEVTOOL: '1',
},
})
Expand Down
2 changes: 1 addition & 1 deletion test/lib/next-modes/next-start.ts
Expand Up @@ -48,8 +48,8 @@ export class NextStartInstance extends NextInstance {
...process.env,
...this.env,
NODE_ENV: '' as any,
PORT: this.forcedPort || '0',
__NEXT_TEST_MODE: '1',
__NEXT_FORCED_PORT: this.forcedPort || '0',
},
}
let buildArgs = ['yarn', 'next', 'build']
Expand Down

0 comments on commit 643447e

Please sign in to comment.