diff --git a/packages/next/cli/next-dev.ts b/packages/next/cli/next-dev.ts index 9048fd028aed..f0c0cd25e3d5 100755 --- a/packages/next/cli/next-dev.ts +++ b/packages/next/cli/next-dev.ts @@ -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' @@ -75,16 +75,10 @@ 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'] === null && process.env.PORT === null // We do not set a default host value here to prevent breaking // some set-ups that rely on listening on other interfaces diff --git a/packages/next/cli/next-start.ts b/packages/next/cli/next-start.ts index fc7cfe39369b..4f6ddf84e656 100755 --- a/packages/next/cli/next-start.ts +++ b/packages/next/cli/next-start.ts @@ -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' @@ -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 ( diff --git a/packages/next/server/lib/utils.ts b/packages/next/server/lib/utils.ts index 578992683c8e..10f8ec20165f 100644 --- a/packages/next/server/lib/utils.ts +++ b/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) @@ -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): number { + if (args['--port'] != null) { + return args['--port'] + } + + const parsed = process.env.PORT && parseInt(process.env.PORT, 10) + if (typeof parsed === 'number' && !Number.isNaN(parsed)) { + return parsed + } + + return 3000 +} diff --git a/test/integration/cli/test/index.test.js b/test/integration/cli/test/index.test.js index 6e5aeb0451fd..4d228a31f569 100644 --- a/test/integration/cli/test/index.test.js +++ b/test/integration/cli/test/index.test.js @@ -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 diff --git a/test/lib/next-modes/next-dev.ts b/test/lib/next-modes/next-dev.ts index f7161c7b7a9c..067d8632a08c 100644 --- a/test/lib/next-modes/next-dev.ts +++ b/test/lib/next-modes/next-dev.ts @@ -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', }, }) diff --git a/test/lib/next-modes/next-start.ts b/test/lib/next-modes/next-start.ts index 11515fce56ed..51672583a40e 100644 --- a/test/lib/next-modes/next-start.ts +++ b/test/lib/next-modes/next-start.ts @@ -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']