Repository: vercel/next.js
Pull request: use port zero for --inspect when forking, if used by parent


### Fixing a bug

- fixes #66189.

### What?

Fixes an issue where, when `NODE_OPTIONS="--inspect=0"` is set, the dev server crashes with `$(PATH_TO)/node: must be 0 or in range 1024 to 65535`. 

### Why?

The Next dev server forks and increments the port supplied by `--inspect` by 1, presumably to allow the dev server to be used with fixed port numbers.

### How?

If the `--inspect` is specified with a port of 0 (to use an ephemeral port), fork using 0 as well.


Fixes #66189


Changed files:
filename: packages/next/src/cli/next-dev.ts
patch: @@ -282,7 +282,7 @@ const nextDev = async (
 
       if (nodeDebugType) {
         const address = getParsedDebugAddress()
-        address.port = address.port + 1
+        address.port = address.port === 0 ? 0 : address.port + 1
         nodeOptions[nodeDebugType] = formatDebugAddress(address)
       }
 

filename: packages/next/src/lib/worker.ts
patch: @@ -85,10 +85,12 @@ export class Worker {
       if (nodeDebugType) {
         const address = getParsedDebugAddress()
         address.port =
-          address.port +
-          // current process runs on `address.port`
-          1 +
-          debuggerPortOffset
+          address.port === 0
+            ? 0
+            : address.port +
+              // current process runs on `address.port`
+              1 +
+              debuggerPortOffset
         nodeOptions[nodeDebugType] = formatDebugAddress(address)
       }
     }

filename: packages/next/src/server/lib/start-server.ts
patch: @@ -26,6 +26,7 @@ import {
   RESTART_EXIT_CODE,
   getFormattedDebugAddress,
   getNodeDebugType,
+  isDebugAddressEphemeral,
 } from './utils'
 import { formatHostname } from './format-hostname'
 import { initialize } from './router-server'
@@ -347,8 +348,12 @@ export async function startServer(
 
       if (nodeDebugType) {
         const formattedDebugAddress = getFormattedDebugAddress()
+        const isEphemeral = isDebugAddressEphemeral()
         Log.info(
-          `the --${nodeDebugType} option was detected, the Next.js router server should be inspected at ${formattedDebugAddress}.`
+          `the --${nodeDebugType} option was detected` +
+            (isEphemeral
+              ? ''
+              : `, the Next.js router server should be inspected at ${formattedDebugAddress}.`)
         )
       }
 

filename: packages/next/src/server/lib/utils.ts
patch: @@ -171,6 +171,13 @@ export const getParsedDebugAddress = (): DebugAddress => {
   return { host: undefined, port: parseInt(address, 10) }
 }
 
+/**
+ * Checks if the debug address from `NODE_OPTIONS` specifies to use a random port (e.g., the port set to 0).
+ *
+ * @returns A boolean indicating whether or not the debug address is assigned to a random port.
+ */
+export const isDebugAddressEphemeral = () => getParsedDebugAddress()?.port === 0
+
 /**
  * Get the debug address from the `NODE_OPTIONS` environment variable and format
  * it into a string.

filename: test/integration/cli/test/index.test.js
patch: @@ -571,6 +571,35 @@ describe('CLI Usage', () => {
       }
     })
 
+    test("NODE_OPTIONS='--inspect=:0'", async () => {
+      const port = await findPort()
+      let output = ''
+      let errOutput = ''
+      const app = await runNextCommandDev(
+        [dirBasic, '--port', port],
+        undefined,
+        {
+          onStdout(msg) {
+            output += stripAnsi(msg)
+          },
+          onStderr(msg) {
+            errOutput += stripAnsi(msg)
+          },
+          env: { NODE_OPTIONS: '--inspect=:0' },
+        }
+      )
+      try {
+        await check(() => output, new RegExp(`http://localhost:${port}`))
+        await check(() => errOutput, /Debugger listening on/)
+        expect(errOutput).not.toContain('address already in use')
+        expect(errOutput).toContain('Debugger listening on')
+        console.log(output)
+        expect(output).toContain('the --inspect option was detected')
+      } finally {
+        await killApp(app)
+      }
+    })
+
     test("NODE_OPTIONS='--require=file with spaces to-require-with-node-require-option.js'", async () => {
       const port = await findPort()
       let output = ''