Repository: vercel/next.js
Pull request: Tests: Mirror arguments and environment between `next.build()` and `next.start()`

## What?

Preparation for swapping the special logic in `start()` to using `build()` directly.

The build() method now respects buildCommand and `NEXT_SKIP_ISOLATE` settings that it previously ignored.

Closes PACK-5403

Changed files:
filename: test/lib/next-modes/next-start.ts
patch: @@ -8,7 +8,6 @@ import stripAnsi from 'strip-ansi'
 export class NextStartInstance extends NextInstance {
   private _buildId: string
   private _cliOutput: string = ''
-  private spawnOpts: import('child_process').SpawnOptions
 
   public get buildId() {
     return this._buildId
@@ -44,36 +43,10 @@ export class NextStartInstance extends NextInstance {
     }
 
     this._cliOutput = ''
-    this.spawnOpts = {
-      cwd: this.testDir,
-      stdio: ['ignore', 'pipe', 'pipe'],
-      shell: false,
-      env: {
-        ...process.env,
-        ...this.env,
-        NODE_ENV: this.env.NODE_ENV || ('' as any),
-        ...(this.forcedPort
-          ? {
-              PORT: this.forcedPort,
-            }
-          : {
-              PORT: '0',
-            }),
-        __NEXT_TEST_MODE: 'e2e',
-      },
-    }
+    const spawnOpts = this.getSpawnOpts()
 
-    let buildArgs = ['pnpm', 'next', 'build']
     let startArgs = ['pnpm', 'next', 'start']
 
-    if (this.buildCommand) {
-      buildArgs = this.buildCommand.split(' ')
-    }
-
-    if (this.buildArgs) {
-      buildArgs.push(...this.buildArgs)
-    }
-
     if (this.startCommand) {
       startArgs = this.startCommand.split(' ')
     }
@@ -84,23 +57,17 @@ export class NextStartInstance extends NextInstance {
 
     if (process.env.NEXT_SKIP_ISOLATE) {
       // without isolation yarn can't be used and pnpm must be used instead
-      if (buildArgs[0] === 'yarn') {
-        buildArgs[0] = 'pnpm'
-      }
       if (startArgs[0] === 'yarn') {
         startArgs[0] = 'pnpm'
       }
     }
 
     if (!options.skipBuild) {
+      const buildArgs = this.getBuildArgs()
       console.log('running', buildArgs.join(' '))
       await new Promise<void>((resolve, reject) => {
         try {
-          this.childProcess = spawn(
-            buildArgs[0],
-            buildArgs.slice(1),
-            this.spawnOpts
-          )
+          this.childProcess = spawn(buildArgs[0], buildArgs.slice(1), spawnOpts)
           this.handleStdio(this.childProcess)
           this.childProcess.on('exit', (code, signal) => {
             this.childProcess = undefined
@@ -135,11 +102,7 @@ export class NextStartInstance extends NextInstance {
     console.log('running', startArgs.join(' '))
     await new Promise<void>((resolve, reject) => {
       try {
-        this.childProcess = spawn(
-          startArgs[0],
-          startArgs.slice(1),
-          this.spawnOpts
-        )
+        this.childProcess = spawn(startArgs[0], startArgs.slice(1), spawnOpts)
         this.handleStdio(this.childProcess)
 
         this.childProcess.on('close', (code, signal) => {
@@ -184,50 +147,69 @@ export class NextStartInstance extends NextInstance {
     })
   }
 
-  public async build(
-    options: { env?: Record<string, string>; args?: string[] } = {}
-  ) {
-    this.spawnOpts = {
+  private getBuildArgs(args?: string[]) {
+    let buildArgs = ['pnpm', 'next', 'build']
+
+    if (this.buildCommand) {
+      buildArgs = this.buildCommand.split(' ')
+    }
+
+    if (this.buildArgs) {
+      buildArgs.push(...this.buildArgs)
+    }
+
+    if (args) {
+      buildArgs.push(...args)
+    }
+
+    if (process.env.NEXT_SKIP_ISOLATE) {
+      // without isolation yarn can't be used and pnpm must be used instead
+      if (buildArgs[0] === 'yarn') {
+        buildArgs[0] = 'pnpm'
+      }
+    }
+
+    return buildArgs
+  }
+
+  private getSpawnOpts(
+    env?: Record<string, string>
+  ): import('child_process').SpawnOptions {
+    return {
       cwd: this.testDir,
       stdio: ['ignore', 'pipe', 'pipe'],
       shell: false,
       env: {
         ...process.env,
         ...this.env,
-        ...options.env,
-        NODE_ENV: '' as any,
-        PORT: this.forcedPort || '0',
+        ...env,
+        NODE_ENV: this.env.NODE_ENV || ('' as any),
+        PORT: this.forcedPort ?? '0',
         __NEXT_TEST_MODE: 'e2e',
       },
     }
+  }
+
+  public async build(
+    options: { env?: Record<string, string>; args?: string[] } = {}
+  ) {
+    if (this.childProcess) {
+      throw new Error(
+        `can not run export while server is running, use next.stop() first`
+      )
+    }
+
     return new Promise<{
       exitCode: NodeJS.Signals | number | null
       cliOutput: string
     }>((resolve) => {
       const curOutput = this._cliOutput.length
-      const buildArgs = ['pnpm', 'next', 'build']
-
-      if (this.buildArgs) {
-        buildArgs.push(...this.buildArgs)
-      }
-
-      if (options.args) {
-        buildArgs.push(...options.args)
-      }
-
-      if (this.childProcess) {
-        throw new Error(
-          `can not run export while server is running, use next.stop() first`
-        )
-      }
+      const spawnOpts = this.getSpawnOpts(options.env)
+      const buildArgs = this.getBuildArgs(options.args)
 
       console.log('running', buildArgs.join(' '))
 
-      this.childProcess = spawn(
-        buildArgs[0],
-        buildArgs.slice(1),
-        this.spawnOpts
-      )
+      this.childProcess = spawn(buildArgs[0], buildArgs.slice(1), spawnOpts)
       this.handleStdio(this.childProcess)
 
       this.childProcess.on('exit', (code, signal) => {