next.js/packages/next/src/bin/next.ts
next.ts704 lines22.3 KB
#!/usr/bin/env node

import '../server/require-hook'

import os from 'os'
import {
  Argument,
  Command,
  InvalidArgumentError,
  Option,
} from 'next/dist/compiled/commander'

import { warn } from '../build/output/log'
import semver from 'next/dist/compiled/semver'
import { bold, cyan, italic } from '../lib/picocolors'
import { formatCliHelpOutput } from '../lib/format-cli-help-output'
import { NON_STANDARD_NODE_ENV } from '../lib/constants'
import {
  getParsedDebugAddress,
  parseValidPositiveInteger,
  type DebugAddress,
} from '../server/lib/utils'
import {
  SUPPORTED_TEST_RUNNERS_LIST,
  type NextTestOptions,
} from '../cli/next-test.js'
import type { NextTelemetryOptions } from '../cli/next-telemetry.js'
import type { NextStartOptions } from '../cli/next-start.js'
import type { NextInfoOptions } from '../cli/next-info.js'
import type { NextDevOptions } from '../cli/next-dev.js'
import type { NextAnalyzeOptions } from '../cli/next-analyze.js'
import type { NextBuildOptions } from '../cli/next-build.js'
import type { NextTypegenOptions } from '../cli/next-typegen.js'
import type { NextPostBuildOptions } from '../cli/next-post-build.js'
import { mkdirSync } from 'fs'

if (process.env.NEXT_RSPACK) {
  // silent rspack's schema check
  process.env.RSPACK_CONFIG_VALIDATE = 'loose-silent'
}

if (
  !semver.satisfies(
    process.versions.node,
    process.env.__NEXT_REQUIRED_NODE_VERSION_RANGE!,
    { includePrerelease: true }
  )
) {
  console.error(
    `You are using Node.js ${process.versions.node}. For Next.js, Node.js version "${process.env.__NEXT_REQUIRED_NODE_VERSION_RANGE}" is required.`
  )
  process.exit(1)
}

process.env.NEXT_PRIVATE_START_TIME = Date.now().toString()

for (const dependency of ['react', 'react-dom']) {
  try {
    // When 'npm link' is used it checks the clone location. Not the project.
    require.resolve(dependency)
  } catch (err) {
    console.warn(
      `The module '${dependency}' was not found. Next.js requires that you include it in 'dependencies' of your 'package.json'. To add it, run 'npm install ${dependency}'`
    )
  }
}

class NextRootCommand extends Command {
  createCommand(name: string) {
    const command = new Command(name)

    command.hook('preAction', (event) => {
      const commandName = event.name()
      const defaultEnv = commandName === 'dev' ? 'development' : 'production'
      const standardEnv = ['production', 'development', 'test']

      if (process.env.NODE_ENV) {
        const isNotStandard = !standardEnv.includes(process.env.NODE_ENV)
        const shouldWarnCommands =
          process.env.NODE_ENV === 'development'
            ? ['start', 'build']
            : process.env.NODE_ENV === 'production'
              ? ['dev']
              : []

        if (isNotStandard || shouldWarnCommands.includes(commandName)) {
          warn(NON_STANDARD_NODE_ENV)
        }
      }

      ;(process.env as any).NODE_ENV = process.env.NODE_ENV || defaultEnv
      ;(process.env as any).NEXT_RUNTIME = 'nodejs'

      if (
        process.platform === 'darwin' &&
        process.arch === 'x64' &&
        os.cpus().some((cpu) => cpu.model.includes('Apple'))
      ) {
        warn(
          'You are running Next.js on an Apple Silicon Mac with Rosetta 2 ' +
            'translation, which may cause degraded performance. You may have ' +
            'accidentally installed an x86-64 version of Node.js.'
        )
      }

      if (
        commandName !== 'dev' &&
        commandName !== 'start' &&
        event.getOptionValue('inspect') === true
      ) {
        console.error(
          `\`--inspect\` flag is deprecated. Use env variable NODE_OPTIONS instead: NODE_OPTIONS='--inspect' next ${commandName}`
        )
        process.exit(1)
      }
    })

    return command
  }
}

function parseValidInspectAddress(value: string): DebugAddress {
  const address = getParsedDebugAddress(value)

  if (Number.isNaN(address.port)) {
    throw new InvalidArgumentError(
      'The given value is not a valid inspect address. ' +
        'Did you mean to pass an app path?\n' +
        `Try switching the order of the arguments or set the default address explicitly e.g.\n` +
        `next dev ${value} --inspect\n` +
        `next dev --inspect= ${value}`
    )
  }

  return address
}

const program = new NextRootCommand()

program
  .name('next')
  .description(
    'The Next.js CLI allows you to develop, build, start your application, and more.'
  )
  .configureHelp({
    formatHelp: (cmd, helper) => formatCliHelpOutput(cmd, helper),
    subcommandTerm: (cmd) => `${cmd.name()} ${cmd.usage()}`,
  })
  .helpCommand(false)
  .helpOption('-h, --help', 'Displays this message.')
  .version(
    `Next.js v${process.env.__NEXT_VERSION}`,
    '-v, --version',
    'Outputs the Next.js version.'
  )

program
  .command('build')
  .description(
    'Creates an optimized production build of your application. The output displays information about each route.'
  )
  .argument(
    '[directory]',
    `A directory on which to build the application. ${italic(
      'If no directory is provided, the current directory will be used.'
    )}`
  )
  .option(
    '--experimental-analyze',
    'Analyze bundle output. Only compatible with Turbopack.'
  )
  .option('-d, --debug', 'Enables a more verbose build output.')
  .option(
    '--debug-prerender',
    'Enables debug mode for prerendering. Not for production use!'
  )
  .option('--no-mangling', 'Disables mangling.')
  .option('--profile', 'Enables production profiling for React.')
  .option('--experimental-app-only', 'Builds only App Router routes.')
  .option('--turbo', 'Builds using Turbopack.')
  .option('--turbopack', 'Builds using Turbopack.')
  .option('--webpack', 'Builds using webpack.')
  .addOption(
    new Option(
      '--experimental-build-mode [mode]',
      'Uses an experimental build mode.'
    )
      .choices(['compile', 'generate', 'generate-env'])
      .default('default')
  )
  .option(
    '--experimental-debug-memory-usage',
    'Enables memory profiling features to debug memory consumption.'
  )
  .option(
    '--experimental-upload-trace, <traceUrl>',
    'Reports a subset of the debugging trace to a remote HTTP URL. Includes sensitive data.'
  )
  .option(
    '--experimental-next-config-strip-types',
    'Use Node.js native TypeScript resolution for next.config.(ts|mts)'
  )
  .option(
    '--debug-build-paths <patterns>',
    'Comma-separated glob patterns or explicit paths for selective builds. Use "!" prefix to exclude. Examples: "app/*", "app/page.tsx", "app/**/page.tsx", "app/**,!app/[slug]/**"'
  )
  .option(
    '--experimental-cpu-prof',
    'Enable CPU profiling. Profile is saved to .next-profiles/ on exit.'
  )
  .addOption(
    new Option(
      '--internal-trace [level]',
      'Enable Turbopack tracing. "all" (default) enables turbo-tasks level tracing, "overview" enables overview tracing.'
    )
      .choices(['all', 'overview'])
      .preset('all')
  )
  .action((directory: string, options: NextBuildOptions) => {
    if (options.debugPrerender) {
      // @ts-expect-error not readonly
      process.env.NODE_ENV = 'development'
    }
    if (options.experimentalNextConfigStripTypes) {
      process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'true'
    }
    if (options.experimentalCpuProf) {
      process.env.NEXT_CPU_PROF = '1'
      process.env.__NEXT_PRIVATE_CPU_PROFILE = 'build-main'
      const { join } = require('path') as typeof import('path')
      const dir = directory || process.cwd()
      const cpuProfileDir = join(dir, '.next-profiles')
      mkdirSync(cpuProfileDir, { recursive: true })
      process.env.NEXT_CPU_PROF_DIR = cpuProfileDir
    }
    if (options.internalTrace) {
      process.env.NEXT_TURBOPACK_TRACING =
        options.internalTrace === 'all'
          ? 'turbo-tasks'
          : String(options.internalTrace)
    }

    // ensure process exits after build completes so open handles/connections
    // don't cause process to hang
    return import('../cli/next-build.js').then((mod) =>
      mod.nextBuild(options, directory).then(async () => {
        // Save CPU profile before exiting if enabled
        if (options.experimentalCpuProf) {
          await mod.saveCpuProfile()
        }
        process.exit(0)
      })
    )
  })
  .usage('[directory] [options]')

program
  .command('experimental-analyze')
  .description(
    'Analyze production bundle output with an interactive web ui. Does not produce an application build. Only compatible with Turbopack.'
  )
  .argument(
    '[directory]',
    `A directory on which to analyze the application. ${italic(
      'If no directory is provided, the current directory will be used.'
    )}`
  )
  .option('--no-mangling', 'Disables mangling.')
  .option('--profile', 'Enables production profiling for React.')
  .option(
    '-o, --output',
    'Only write analysis files to disk. Does not start the server.'
  )
  .addOption(
    new Option(
      '--port <port>',
      'Specify a port number to serve the analyzer on.'
    )
      .implies({ serve: true })
      .argParser(parseValidPositiveInteger)
      .default(4000)
      .env('PORT')
  )
  .action((directory: string, options: NextAnalyzeOptions) => {
    return import('../cli/next-analyze.js')
      .then((mod) => mod.nextAnalyze(options, directory))
      .then(() => {
        if (options.output) {
          // The Next.js process is held open by something on the event loop. Exit manually like the `build` command does.
          // TODO: Fix the underlying issue so this is not necessary.
          process.exit(0)
        }
      })
  })

program
  .command('dev', { isDefault: true })
  .description(
    'Starts Next.js in development mode with hot-code reloading, error reporting, and more.'
  )
  .argument(
    '[directory]',
    `A directory on which to build the application. ${italic(
      'If no directory is provided, the current directory will be used.'
    )}`
  )
  .addOption(
    new Option(
      '--inspect [[host:]port]',
      'Allows inspecting server-side code. See https://nextjs.org/docs/app/guides/debugging#server-side-code'
    ).argParser(parseValidInspectAddress)
  )
  .option('--turbo', 'Starts development mode using Turbopack.')
  .option('--turbopack', 'Starts development mode using Turbopack.')
  .option('--webpack', 'Starts development mode using webpack.')
  .addOption(
    new Option(
      '-p, --port <port>',
      'Specify a port number on which to start the application.'
    )
      .argParser(parseValidPositiveInteger)
      .default(3000)
      .env('PORT')
  )
  .option(
    '-H, --hostname <hostname>',
    'Specify a hostname on which to start the application (default: 0.0.0.0).'
  )
  .option(
    '--disable-source-maps',
    "Don't start the Dev server with `--enable-source-maps`.",
    false
  )
  .option(
    '--experimental-https',
    'Starts the server with HTTPS and generates a self-signed certificate.'
  )
  .option('--experimental-https-key, <path>', 'Path to a HTTPS key file.')
  .option(
    '--experimental-https-cert, <path>',
    'Path to a HTTPS certificate file.'
  )
  .option(
    '--experimental-https-ca, <path>',
    'Path to a HTTPS certificate authority file.'
  )
  // `--server-fast-refresh` is hidden because it's the default behavior and
  // only needs to be explicitly passed to override a
  // `experimental.turbopackServerFastRefresh: false` in next.config. The
  // `--no-server-fast-refresh` negation is the meaningful user-facing flag.
  .addOption(new Option('--server-fast-refresh').default(undefined).hideHelp())
  .addOption(
    new Option('--no-server-fast-refresh', 'Disable server-side Fast Refresh')
  )
  .option(
    '--experimental-upload-trace, <traceUrl>',
    'Reports a subset of the debugging trace to a remote HTTP URL. Includes sensitive data.'
  )
  .option(
    '--experimental-next-config-strip-types',
    'Use Node.js native TypeScript resolution for next.config.(ts|mts)'
  )
  .option(
    '--experimental-cpu-prof',
    'Enable CPU profiling. Profiles are saved to .next-profiles/ on exit.'
  )
  .addOption(
    new Option(
      '--internal-trace [level]',
      'Enable Turbopack tracing. "all" (default) enables turbo-tasks level tracing, "overview" enables overview tracing.'
    )
      .choices(['all', 'overview'])
      .preset('all')
  )
  .action(
    (directory: string, options: NextDevOptions, { _optionValueSources }) => {
      if (options.experimentalNextConfigStripTypes) {
        process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'true'
      }
      if (options.experimentalCpuProf) {
        process.env.NEXT_CPU_PROF = '1'
        process.env.__NEXT_PRIVATE_CPU_PROFILE = 'dev-main'
        const { join } = require('path') as typeof import('path')
        const dir = directory || process.cwd()
        const cpuProfileDir = join(dir, '.next-profiles')
        mkdirSync(cpuProfileDir, { recursive: true })
        process.env.NEXT_CPU_PROF_DIR = cpuProfileDir
      }
      if (options.internalTrace) {
        process.env.NEXT_TURBOPACK_TRACING =
          options.internalTrace === 'all'
            ? 'turbo-tasks'
            : String(options.internalTrace)
      }
      const portSource = _optionValueSources.port
      import('../cli/next-dev.js').then((mod) =>
        mod.nextDev(options, portSource, directory)
      )
    }
  )
  .usage('[directory] [options]')

program
  .command('export', { hidden: true })
  .action(() => import('../cli/next-export.js').then((mod) => mod.nextExport()))
  .helpOption(false)

program
  .command('info')
  .description(
    'Prints relevant details about the current system which can be used to report Next.js bugs.'
  )
  .addHelpText(
    'after',
    `\nLearn more: ${cyan('https://nextjs.org/docs/api-reference/cli#info')}`
  )
  .option('--verbose', 'Collects additional information for debugging.')
  .action((options: NextInfoOptions) =>
    import('../cli/next-info.js').then((mod) => mod.nextInfo(options))
  )

program
  .command('start')
  .description(
    'Starts Next.js in production mode. The application should be compiled with `next build` first.'
  )
  .argument(
    '[directory]',
    `A directory on which to start the application. ${italic(
      'If no directory is provided, the current directory will be used.'
    )}`
  )
  .addOption(
    new Option(
      '-p, --port <port>',
      'Specify a port number on which to start the application.'
    )
      .argParser(parseValidPositiveInteger)
      .default(3000)
      .env('PORT')
  )
  .option(
    '-H, --hostname <hostname>',
    'Specify a hostname on which to start the application (default: 0.0.0.0).'
  )
  .addOption(
    new Option(
      '--inspect [[host:]port]',
      'Allows inspecting server-side code. See https://nextjs.org/docs/app/guides/debugging#server-side-code'
    ).argParser(parseValidInspectAddress)
  )
  .addOption(
    new Option(
      '--keepAliveTimeout <keepAliveTimeout>',
      'Specify the maximum amount of milliseconds to wait before closing inactive connections.'
    ).argParser(parseValidPositiveInteger)
  )
  .option(
    '--experimental-next-config-strip-types',
    'Use Node.js native TypeScript resolution for next.config.(ts|mts)'
  )
  .option(
    '--experimental-cpu-prof',
    'Enable CPU profiling. Profiles are saved to .next-profiles/ on exit.'
  )
  .action((directory: string, options: NextStartOptions) => {
    if (options.experimentalNextConfigStripTypes) {
      process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'true'
    }
    if (options.experimentalCpuProf) {
      process.env.NEXT_CPU_PROF = '1'
      process.env.__NEXT_PRIVATE_CPU_PROFILE = 'start-main'
      const { join } = require('path') as typeof import('path')
      const dir = directory || process.cwd()
      const cpuProfileDir = join(dir, '.next-profiles')
      mkdirSync(cpuProfileDir, { recursive: true })
      process.env.NEXT_CPU_PROF_DIR = cpuProfileDir
    }
    return import('../cli/next-start.js').then((mod) =>
      mod.nextStart(options, directory)
    )
  })
  .usage('[directory] [options]')

program
  .command('telemetry')
  .description(
    `Allows you to enable or disable Next.js' ${bold(
      'completely anonymous'
    )} telemetry collection.`
  )
  .addArgument(new Argument('[arg]').choices(['disable', 'enable', 'status']))
  .addHelpText('after', `\nLearn more: ${cyan('https://nextjs.org/telemetry')}`)
  .addOption(
    new Option('--enable', `Enables Next.js' telemetry collection.`).conflicts(
      'disable'
    )
  )
  .option('--disable', `Disables Next.js' telemetry collection.`)
  .action((arg: string, options: NextTelemetryOptions) =>
    import('../cli/next-telemetry.js').then((mod) =>
      mod.nextTelemetry(options, arg)
    )
  )

program
  .command('typegen')
  .description(
    'Generate TypeScript definitions for routes, pages, and layouts without running a full build.'
  )
  .argument(
    '[directory]',
    `A directory on which to generate types. ${italic(
      'If no directory is provided, the current directory will be used.'
    )}`
  )
  .action((directory: string, options: NextTypegenOptions) =>
    // ensure process exits after typegen completes so open handles/connections
    // don't cause process to hang
    import('../cli/next-typegen.js').then((mod) =>
      mod.nextTypegen(options, directory).then(() => process.exit(0))
    )
  )
  .usage('[directory] [options]')

const nextVersion = process.env.__NEXT_VERSION || 'unknown'
program
  .command('upgrade')
  .description(
    'Upgrade Next.js apps to desired versions with a single command.'
  )
  .argument(
    '[directory]',
    `A Next.js project directory to upgrade. ${italic(
      'If no directory is provided, the current directory will be used.'
    )}`
  )
  .usage('[directory] [options]')
  .option(
    '--revision <revision>',
    'Specify the target Next.js version using an NPM dist tag (e.g. "latest", "canary", "rc", "beta") or an exact version number (e.g. "15.0.0").',
    nextVersion.includes('-canary.')
      ? 'canary'
      : nextVersion.includes('-rc.')
        ? 'rc'
        : nextVersion.includes('-beta.')
          ? 'beta'
          : 'latest'
  )
  .option('--verbose', 'Verbose output', false)
  .action(async (directory, options) => {
    const mod = await import('../cli/next-upgrade.js')
    mod.spawnNextUpgrade(directory, options)
  })

program
  .command('experimental-test')
  .description(
    `Execute \`next/experimental/testmode\` tests using a specified test runner. The test runner defaults to 'playwright' if the \`experimental.defaultTestRunner\` configuration option or the \`--test-runner\` option are not set.`
  )
  .argument(
    '[directory]',
    `A Next.js project directory to execute the test runner on. ${italic(
      'If no directory is provided, the current directory will be used.'
    )}`
  )
  .argument(
    '[test-runner-args...]',
    'Any additional arguments or options to pass down to the test runner `test` command.'
  )
  .option(
    '--test-runner [test-runner]',
    `Any supported test runner. Options: ${bold(
      SUPPORTED_TEST_RUNNERS_LIST.join(', ')
    )}. ${italic(
      "If no test runner is provided, the Next.js config option `experimental.defaultTestRunner`, or 'playwright' will be used."
    )}`
  )
  .allowUnknownOption()
  .action(
    (directory: string, testRunnerArgs: string[], options: NextTestOptions) => {
      return import('../cli/next-test.js').then((mod) => {
        mod.nextTest(directory, testRunnerArgs, options)
      })
    }
  )
  .usage('[directory] [options]')

const internal = program
  .command('internal')
  .description(
    'Internal debugging commands. Use with caution. Not covered by semver.'
  )

internal
  .command('trace')
  .alias('turbo-trace-server')
  .argument('file', 'Trace file to serve.')
  .addOption(
    new Option('-p, --port <port>', 'Override the port.').argParser(
      parseValidPositiveInteger
    )
  )
  .addOption(
    new Option(
      '--mcp-port <mcpPort>',
      'Port for the MCP (Model Context Protocol) server. Defaults to --port + 1.'
    ).argParser(parseValidPositiveInteger)
  )
  .action(
    (
      file: string,
      options: { port: number | undefined; mcpPort: number | undefined }
    ) => {
      return import('../cli/internal/turbo-trace-server.js').then((mod) =>
        mod.startTurboTraceServerCli(file, options.port, options.mcpPort)
      )
    }
  )

internal
  .command('query-trace')
  .description(
    'Query a running turbopack trace server (started with `next internal trace --mcp-port <port>`).'
  )
  .addOption(
    new Option(
      '--port <port>',
      'MCP port of the running trace server. Defaults to 5748.'
    ).argParser(parseValidPositiveInteger)
  )
  .addOption(
    new Option(
      '--parent <parent>',
      'Span ID to enumerate children of. Omit for root level.'
    )
  )
  .addOption(
    new Option(
      '--no-aggregated',
      'Disable aggregation of spans by name (aggregated by default).'
    )
  )
  .addOption(
    new Option(
      '--sort <mode>',
      'Sort mode: "value" for corrected duration descending, "name" for alphabetical.'
    ).choices(['value', 'name'])
  )
  .addOption(
    new Option('--search <search>', 'Substring filter on span name/category.')
  )
  .addOption(new Option('--json', 'Output as JSON instead of markdown.'))
  .addOption(
    new Option('--page <page>', 'Page number (1-based, default 1).').argParser(
      parseValidPositiveInteger
    )
  )
  .action((options) =>
    import('../cli/internal/query-trace.js').then((mod) =>
      mod.queryTraceCli(options)
    )
  )

internal
  .command('post-build')
  .description(
    'Runs post-build optimization steps (e.g. Turbopack database compaction).'
  )
  .argument(
    '[directory]',
    `A directory on which to run post-build steps. ${italic(
      'If no directory is provided, the current directory will be used.'
    )}`
  )
  .action((directory: string, options: NextPostBuildOptions) => {
    return (
      require('../cli/next-post-build.js') as typeof import('../cli/next-post-build.js')
    )
      .nextPostBuild(options, directory)
      .then(() => process.exit(0))
  })
  .usage('[directory] [options]')

internal
  .command('upload-trace')
  .description(
    'Upload CPU profiles from .next-profiles/ to Vercel Blob storage.'
  )
  .argument(
    '[directory]',
    `The project directory containing .next-profiles/. ${italic(
      'If no directory is provided, the current directory will be used.'
    )}`
  )
  .action((directory: string) => {
    return import('../cli/internal/upload-trace.js').then((mod) =>
      mod.uploadTraceToBlob({ directory })
    )
  })
  .usage('[directory] [options]')

program.parse(process.argv)
Quest for Codev2.0.0
/
SIGN IN