import { loadEnvConfig } from '@next/env'
import * as inspector from 'inspector'
import * as Log from '../../build/output/log'
import { bold, purple, strikethrough } from '../../lib/picocolors'
import type { ConfiguredExperimentalFeature } from '../config'
import { experimentalSchema } from '../config-schema'
import { detectAgent } from '../../telemetry/detect-agent'
import {
hasAgentRulesInstalled,
writeAgentFiles,
type AgentFilesResult,
} from './generate-agent-files'
// Re-export the type for consumers
export type { ConfiguredExperimentalFeature }
/**
* Logs basic startup info that doesn't require config.
* Called before "Ready in X" to show immediate feedback.
*/
export function logStartInfo({
networkUrl,
appUrl,
envInfo,
logBundler,
}: {
networkUrl: string | null
appUrl: string | null
envInfo?: string[]
logBundler: boolean
}) {
let versionSuffix = ''
const parts = []
if (logBundler) {
if (process.env.TURBOPACK) {
parts.push('Turbopack')
} else if (process.env.NEXT_RSPACK) {
parts.push('Rspack')
} else {
parts.push('webpack')
}
}
if (parts.length > 0) {
versionSuffix = ` (${parts.join(', ')})`
}
Log.bootstrap(
`${bold(
purple(`${Log.prefixes.ready} Next.js ${process.env.__NEXT_VERSION}`)
)}${versionSuffix}`
)
if (appUrl) {
Log.bootstrap(`- Local: ${appUrl}`)
}
if (networkUrl) {
Log.bootstrap(`- Network: ${networkUrl}`)
}
const inspectorUrl = inspector.url()
if (inspectorUrl) {
// Could also parse this port from the inspector URL.
// process.debugPort will always be defined even if the process is not being inspected.
// The full URL seems noisy as far as I can tell.
// Node.js will print the full URL anyway.
const debugPort = process.debugPort
Log.bootstrap(`- Debugger port: ${debugPort}`)
}
if (envInfo?.length) Log.bootstrap(`- Environments: ${envInfo.join(', ')}`)
}
/**
* Logs experimental features and config-dependent info.
* Called after getRequestHandlers completes.
*/
export function logExperimentalInfo({
experimentalFeatures,
cacheComponents,
}: {
experimentalFeatures?: ConfiguredExperimentalFeature[]
cacheComponents?: boolean
}) {
if (cacheComponents) {
Log.bootstrap(`- Cache Components enabled`)
}
if (experimentalFeatures?.length) {
Log.bootstrap(`- Experiments (use with caution):`)
for (const exp of experimentalFeatures) {
const isValid = Object.prototype.hasOwnProperty.call(
experimentalSchema,
exp.key
)
if (isValid) {
const symbol =
typeof exp.value === 'boolean'
? exp.value === true
? bold('✓')
: bold('⨯')
: '·'
const suffix =
typeof exp.value === 'number' || typeof exp.value === 'string'
? `: ${JSON.stringify(exp.value)}`
: ''
const reason = exp.reason ? ` (${exp.reason})` : ''
Log.bootstrap(` ${symbol} ${exp.key}${suffix}${reason}`)
} else {
Log.bootstrap(
` ? ${strikethrough(exp.key)} (invalid experimental key)`
)
}
}
}
// New line after the bootstrap info
Log.info('')
}
/**
* When `next dev` detects an AI coding agent but the managed
* agent-rules block is missing from AGENTS.md / CLAUDE.md,
* auto-generate the files so the agent has access to version-matched
* docs. Returns the write result when files were generated, or `null`
* when no action was needed.
*
* Callers gate this on `config.agentRules !== false` — opt-out is
* declarative in next.config, not inside this function.
*/
export function ensureAgentRulesForDev(dir: string): AgentFilesResult | null {
if (detectAgent() === null) return null
if (hasAgentRulesInstalled(dir)) return null
return writeAgentFiles(dir)
}
/**
* Gets environment info for logging. Fast operation that doesn't require config.
*/
export function getEnvInfo(dir: string): string[] {
const { loadedEnvFiles } = loadEnvConfig(dir, true, console, false)
return loadedEnvFiles.map((f) => f.path)
}