Repository: vercel/next.js
Pull request: bundle analyzer: remove custom output option

This functionality was added late in #87258 but was broken as we needed to pass this along to the Rust side. We'd also need to be able to write to arbitrary absolute paths there, so let's punt on it.

Test Plan: e2e tests


Changed files:
filename: packages/next/src/bin/next.ts
patch: @@ -213,8 +213,8 @@ program
   .option('--no-mangling', 'Disables mangling.')
   .option('--profile', 'Enables production profiling for React.')
   .option(
-    '-o, --output [path]',
-    'Only write analysis files to disk. Does not start the server. Optionally specify a custom output path (defaults to .next/diagnostics/analyze).'
+    '-o, --output',
+    'Only write analysis files to disk. Does not start the server.'
   )
   .addOption(
     new Option(

filename: packages/next/src/build/analyze/index.ts
patch: @@ -30,14 +30,12 @@ import { eventAnalyzeCompleted } from '../../telemetry/events'
 import { traceGlobals } from '../../trace/shared'
 import type { RoutesManifest } from '..'
 
-const ANALYZE_PATH = '.next/diagnostics/analyze'
-
 export type AnalyzeOptions = {
   dir: string
   reactProductionProfiling?: boolean
   noMangling?: boolean
   appDirOnly?: boolean
-  output?: boolean | string
+  output?: boolean
   port?: number
 }
 
@@ -77,36 +75,27 @@ export default async function analyze({
       await turbopackAnalyze(analyzeContext)
 
     const durationString = durationToString(analyzeDuration)
-
-    // Determine the output path: use custom path if provided, otherwise default
-    const outputPath = typeof output === 'string' ? output : ANALYZE_PATH
-    const resolvedOutputPath = path.isAbsolute(outputPath)
-      ? outputPath
-      : path.join(dir, outputPath)
-
-    let logMessage = `Analyze completed in ${durationString}.`
-    if (output) {
-      logMessage += ` Results written to ${outputPath}.\nTo explore the analyze results interactively, run \`next experimental-analyze\` without \`--output\`.`
-    }
-    Log.event(logMessage)
+    const analyzeDir = path.join(distDir, 'diagnostics/analyze')
 
     await shutdownPromise
 
-    await cp(
-      path.join(__dirname, '../../bundle-analyzer'),
-      resolvedOutputPath,
-      { recursive: true }
-    )
-
-    // Collect and write routes for the bundle analyzer
     const routes = await collectRoutesForAnalyze(dir, config, appDirOnly)
 
-    await mkdir(path.join(resolvedOutputPath, 'data'), { recursive: true })
+    await cp(path.join(__dirname, '../../bundle-analyzer'), analyzeDir, {
+      recursive: true,
+    })
+    await mkdir(path.join(analyzeDir, 'data'), { recursive: true })
     await writeFile(
-      path.join(resolvedOutputPath, 'data', 'routes.json'),
+      path.join(analyzeDir, 'data', 'routes.json'),
       JSON.stringify(routes, null, 2)
     )
 
+    let logMessage = `Analyze completed in ${durationString}.`
+    if (output) {
+      logMessage += ` Results written to ${analyzeDir}.\nTo explore the analyze results interactively, run \`next experimental-analyze\` without \`--output\`.`
+    }
+    Log.event(logMessage)
+
     telemetry.record(
       eventAnalyzeCompleted({
         success: true,
@@ -116,7 +105,7 @@ export default async function analyze({
     )
 
     if (!output) {
-      await startServer(resolvedOutputPath, port)
+      await startServer(analyzeDir, port)
     }
   } catch (e) {
     const telemetry = traceGlobals.get('telemetry') as Telemetry | undefined

filename: packages/next/src/cli/next-analyze.ts
patch: @@ -13,7 +13,7 @@ export type NextAnalyzeOptions = {
   profile?: boolean
   mangling: boolean
   port: number
-  output: boolean | string
+  output: boolean
   experimentalAppOnly?: boolean
 }
 

filename: test/e2e/next-analyze/next-analyze.test.ts
patch: @@ -49,7 +49,7 @@ describe('next experimental-analyze', () => {
   })
   ;['-o', '--output'].forEach((flag) => {
     describe(`with ${flag} flag`, () => {
-      it('writes output to default path when no path is specified', async () => {
+      it('writes output to .next/diagnostics/analyze path', async () => {
         const defaultOutputPath = path.join(
           next.testDir,
           '.next/diagnostics/analyze'
@@ -69,12 +69,14 @@ describe('next experimental-analyze', () => {
         expect(stdout).toContain('.next/diagnostics/analyze')
 
         expect(existsSync(defaultOutputPath)).toBe(true)
-        expect(existsSync(path.join(defaultOutputPath, 'index.html'))).toBe(
-          true
-        )
-        expect(
-          existsSync(path.join(defaultOutputPath, 'data', 'routes.json'))
-        ).toBe(true)
+        for (const file of [
+          'index.html',
+          'data/routes.json',
+          'data/modules.data',
+          'data/analyze.data',
+        ]) {
+          expect(existsSync(path.join(defaultOutputPath, file))).toBe(true)
+        }
 
         const routesJson = readFileSync(
           path.join(defaultOutputPath, 'data', 'routes.json'),
@@ -83,39 +85,6 @@ describe('next experimental-analyze', () => {
         const routes = JSON.parse(routesJson)
         expect(routes).toEqual(['/', '/_not-found'])
       })
-
-      it('writes output to custom path', async () => {
-        const customOutputPath = path.join(
-          next.testDir,
-          'nested/output/directory'
-        )
-
-        const { code, stderr, stdout } = await runNextCommand(
-          ['experimental-analyze', flag, 'nested/output/directory'],
-          {
-            cwd: next.testDir,
-            stderr: true,
-            stdout: true,
-          }
-        )
-
-        expect(code).toBe(0)
-        expect(stderr).not.toContain('Error')
-        expect(stdout).toContain('nested/output/directory')
-
-        expect(existsSync(customOutputPath)).toBe(true)
-        expect(existsSync(path.join(customOutputPath, 'index.html'))).toBe(true)
-        expect(
-          existsSync(path.join(customOutputPath, 'data', 'routes.json'))
-        ).toBe(true)
-
-        const routesJson = readFileSync(
-          path.join(customOutputPath, 'data', 'routes.json'),
-          'utf-8'
-        )
-        const routes = JSON.parse(routesJson)
-        expect(routes).toEqual(['/', '/_not-found'])
-      })
     })
   })
 })