import findUp from 'next/dist/compiled/find-up' import path from 'path' import { CONFIG_FILE, PHASE_DEVELOPMENT_SERVER, PHASE_EXPORT, PHASE_PRODUCTION_BUILD, } from '../../next-server/lib/constants' import { normalizeConfig } from '../../next-server/server/config' const EVENT_VERSION = 'NEXT_CLI_SESSION_STARTED' type EventCliSessionStarted = { nextVersion: string nodeVersion: string cliCommand: string isSrcDir: boolean | null hasNowJson: boolean isCustomServer: boolean | null hasNextConfig: boolean buildTarget: string hasWebpackConfig: boolean hasBabelConfig: boolean basePathEnabled: boolean i18nEnabled: boolean imageEnabled: boolean locales: string | null localeDomainsCount: number | null localeDetectionEnabled: boolean | null imageDomainsCount: number | null imageSizes: string | null imageLoader: string | null trailingSlashEnabled: boolean reactStrictMode: boolean } function hasBabelConfig(dir: string): boolean { try { const noopFile = path.join(dir, 'noop.js') const res = require('@babel/core').loadPartialConfig({ cwd: dir, filename: noopFile, sourceFileName: noopFile, }) as any const isForTooling = res.options?.presets?.every( (e: any) => e?.file?.request === 'next/babel' ) && res.options?.plugins?.length === 0 return res.hasFilesystemConfig() && !isForTooling } catch { return false } } type NextConfigurationPhase = | typeof PHASE_DEVELOPMENT_SERVER | typeof PHASE_PRODUCTION_BUILD | typeof PHASE_EXPORT function getNextConfig( phase: NextConfigurationPhase, dir: string ): { [key: string]: any } | null { try { const configurationPath = findUp.sync(CONFIG_FILE, { cwd: dir, }) if (configurationPath) { // This should've already been loaded, and thus should be cached / won't // be re-evaluated. const configurationModule = require(configurationPath) // Re-normalize the configuration. return normalizeConfig( phase, configurationModule.default || configurationModule ) } } catch { // ignored } return null } export function eventCliSession( phase: NextConfigurationPhase, dir: string, event: Omit< EventCliSessionStarted, | 'nextVersion' | 'nodeVersion' | 'hasNextConfig' | 'buildTarget' | 'hasWebpackConfig' | 'hasBabelConfig' | 'basePathEnabled' | 'i18nEnabled' | 'imageEnabled' | 'locales' | 'localeDomainsCount' | 'localeDetectionEnabled' | 'imageDomainsCount' | 'imageSizes' | 'imageLoader' | 'trailingSlashEnabled' | 'reactStrictMode' > ): { eventName: string; payload: EventCliSessionStarted }[] { // This should be an invariant, if it fails our build tooling is broken. if (typeof process.env.__NEXT_VERSION !== 'string') { return [] } const userConfiguration = getNextConfig(phase, dir) const { images, experimental } = userConfiguration || {} const { i18n } = experimental || {} const payload: EventCliSessionStarted = { nextVersion: process.env.__NEXT_VERSION, nodeVersion: process.version, cliCommand: event.cliCommand, isSrcDir: event.isSrcDir, hasNowJson: event.hasNowJson, isCustomServer: event.isCustomServer, hasNextConfig: !!userConfiguration, buildTarget: userConfiguration?.target ?? 'default', hasWebpackConfig: typeof userConfiguration?.webpack === 'function', hasBabelConfig: hasBabelConfig(dir), imageEnabled: !!images, basePathEnabled: !!userConfiguration?.basePath, i18nEnabled: !!i18n, locales: i18n?.locales ? i18n.locales.join(',') : null, localeDomainsCount: i18n?.domains ? i18n.domains.length : null, localeDetectionEnabled: !i18n ? null : i18n.localeDetection !== false, imageDomainsCount: images?.domains ? images.domains.length : null, imageSizes: images?.sizes ? images.sizes.join(',') : null, imageLoader: images?.loader, trailingSlashEnabled: !!userConfiguration?.trailingSlash, reactStrictMode: !!userConfiguration?.reactStrictMode, } return [{ eventName: EVENT_VERSION, payload }] }