Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/next/src/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ import { MissingCanonicalInterceptionRoutesError } from '../shared/lib/errors/mi
import { IncompatibleParallelRouteSlotsError } from '../shared/lib/errors/incompatible-parallel-route-slots-error'
import { findMissingCanonicalInterceptionRoutes } from '../shared/lib/router/utils/interception-routes'
import { findPageFile } from '../server/lib/find-page-file'
import * as Log from './output/log'
import { getStrictRouteMatchingDefaultWarning } from '../server/lib/router-utils/strict-route-matching-config'

type ObjectValue<T> = T extends { [key: string]: infer V } ? V : never
import { getStaticInfoIncludingLayouts } from './get-static-info-including-layouts'
Expand Down Expand Up @@ -508,6 +510,12 @@ export async function createEntrypoints(
)
)
}
if (routeMatchingErrors.length > 0) {
const warning = getStrictRouteMatchingDefaultWarning(config)
if (warning) {
Log.warnOnce(warning)
}
}
if (routeMatchingErrors.length === 1) {
throw routeMatchingErrors[0]
}
Expand Down
32 changes: 30 additions & 2 deletions packages/next/src/build/print-build-errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { formatIssue, isRelevantWarning } from '../shared/lib/turbopack/utils'
import {
formatIssue,
isRelevantWarning,
renderStyledStringToErrorAnsi,
} from '../shared/lib/turbopack/utils'
import type { TurbopackResult } from './swc/types'
import * as Log from './output/log'

const STRICT_ROUTE_MATCHING_ISSUE_TITLES = new Set([
'Interception routes must have a canonical route',
'Parallel route slots cannot render the same URLs',
'Unmatched app pages',
])

export function formatWarningsHeader(count: number): string {
return `Turbopack build encountered ${count} ${count === 1 ? 'warning' : 'warnings'}:`
Expand All @@ -19,7 +30,10 @@ export function formatWarningsHeader(count: number): string {
export function printBuildErrors<T>(
result: TurbopackResult<T>,
isDev: boolean,
opts?: { deferWarnings?: boolean }
opts?: {
deferWarnings?: boolean
strictRouteMatchingDefaultWarning?: string
}
): { warnings: string[] } {
// Issues that we want to stop the server from executing
const topLevelFatalIssues = []
Expand All @@ -32,8 +46,18 @@ export function printBuildErrors<T>(
const seenFatalIssues = new Set<string>()
const seenErrors = new Set<string>()
const seenWarnings = new Set<string>()
let hasStrictRouteMatchingIssue = false

for (const issue of result.issues) {
if (
issue.severity === 'error' &&
STRICT_ROUTE_MATCHING_ISSUE_TITLES.has(
renderStyledStringToErrorAnsi(issue.title)
)
) {
hasStrictRouteMatchingIssue = true
}

// We only want to completely shut down the server
if (issue.severity === 'fatal' || issue.severity === 'bug') {
const formatted = formatIssue(issue)
Expand Down Expand Up @@ -83,6 +107,10 @@ export function printBuildErrors<T>(
)
}

if (hasStrictRouteMatchingIssue && opts?.strictRouteMatchingDefaultWarning) {
Log.warnOnce(opts.strictRouteMatchingDefaultWarning)
}

if (topLevelFatalIssues.length > 0) {
throw new Error(
`Turbopack build failed with ${
Expand Down
7 changes: 4 additions & 3 deletions packages/next/src/build/static-paths/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import type { NormalizedAppRoute } from '../../shared/lib/router/routes/app'
import { interceptionPrefixFromParamType } from '../../shared/lib/router/utils/interception-prefix-from-param-type'
import { isPlainObject } from '../../shared/lib/is-plain-object'
import {
type GenerateStaticParamsStore,
type BuildTimeGeneratorStore,
workUnitAsyncStorage,
} from '../../server/app-render/work-unit-async-storage.external'
import type { ImplicitTags } from '../../server/lib/implicit-tags'
Expand Down Expand Up @@ -625,8 +625,9 @@ async function callGenerateStaticParams(
}
}

const workUnitStore: GenerateStaticParamsStore = {
type: 'generate-static-params',
const workUnitStore: BuildTimeGeneratorStore = {
type: 'build-time-generator',
functionName: 'generateStaticParams',
phase: 'render',
implicitTags,
rootParams,
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/build/turbopack-build/impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { printBuildErrors } from '../print-build-errors'
import { normalizePath } from '../../lib/normalize-path'
import type { ProjectOptions, RawEntrypoints } from '../swc/types'
import { Bundler } from '../../lib/bundler'
import { getStrictRouteMatchingDefaultWarning } from '../../server/lib/router-utils/strict-route-matching-config'

export async function turbopackBuild(telemetry: Telemetry): Promise<{
duration: number
Expand Down Expand Up @@ -184,6 +185,8 @@ export async function turbopackBuild(telemetry: Telemetry): Promise<{
// keeping SSG errors more prominent than compile warnings.
const { warnings } = printBuildErrors(entrypoints, dev, {
deferWarnings: true,
strictRouteMatchingDefaultWarning:
getStrictRouteMatchingDefaultWarning(config),
})

// Skip when telemetry is fully off — featureUsage() isn't free.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { loadAgentFeedbackInstructions } from './agent-feedback-instructions'
import {
agentFeedbackInstructionsCli,
loadAgentFeedbackInstructions,
} from './agent-feedback-instructions'

describe('loadAgentFeedbackInstructions', () => {
it('returns the protocol when feedback is enabled', async () => {
Expand Down Expand Up @@ -29,4 +32,36 @@ describe('loadAgentFeedbackInstructions', () => {
)
).resolves.toBeNull()
})

it('propagates feedback status errors', async () => {
await expect(
loadAgentFeedbackInstructions(async () => {
throw new Error('network unavailable')
})
).rejects.toThrow('network unavailable')
})
})

describe('agentFeedbackInstructionsCli', () => {
const originalExitCode = process.exitCode

afterEach(() => {
process.exitCode = originalExitCode
jest.restoreAllMocks()
})

it('reports feedback status errors and exits with a failure', async () => {
const writeError = jest
.spyOn(process.stderr, 'write')
.mockImplementation(() => true)

await agentFeedbackInstructionsCli(async () => {
throw new Error('network unavailable')
})

expect(writeError).toHaveBeenCalledWith(
'Unable to check whether Next.js agent feedback is enabled. Rerun this command with network access.\n'
)
expect(process.exitCode).toBe(1)
})
})
26 changes: 18 additions & 8 deletions packages/next/src/cli/internal/agent-feedback-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,36 @@ const AGENT_FEEDBACK_PROTOCOL_PATH = path.join(

type IsEnabled = () => Promise<boolean>
type ReadProtocol = () => Promise<string>
type LoadInstructions = () => Promise<string | null>

export async function loadAgentFeedbackInstructions(
isEnabled: IsEnabled = isAgentFeedbackEnabled,
readProtocol: ReadProtocol = () =>
readFile(AGENT_FEEDBACK_PROTOCOL_PATH, 'utf8')
): Promise<string | null> {
try {
if (!(await isEnabled())) {
return null
}
if (!(await isEnabled())) {
return null
}

try {
return await readProtocol()
} catch {
return null
}
}

export async function agentFeedbackInstructionsCli(): Promise<void> {
const instructions = await loadAgentFeedbackInstructions()
if (instructions) {
process.stdout.write(instructions)
export async function agentFeedbackInstructionsCli(
loadInstructions: LoadInstructions = loadAgentFeedbackInstructions
): Promise<void> {
try {
const instructions = await loadInstructions()
if (instructions) {
process.stdout.write(instructions)
}
} catch {
process.stderr.write(
'Unable to check whether Next.js agent feedback is enabled. Rerun this command with network access.\n'
)
process.exitCode = 1
}
}
12 changes: 8 additions & 4 deletions packages/next/src/cli/internal/agent-feedback-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ describe('isAgentFeedbackEnabled', () => {
await expect(isAgentFeedbackEnabled(fetchImpl)).resolves.toBe(false)
})

it('fails closed when the request rejects', async () => {
it('rejects when the request fails', async () => {
const fetchImpl: typeof fetch = async () => {
throw new Error('network unavailable')
}

await expect(isAgentFeedbackEnabled(fetchImpl)).resolves.toBe(false)
await expect(isAgentFeedbackEnabled(fetchImpl)).rejects.toThrow(
'network unavailable'
)
})

it('fails closed when the request times out', async () => {
it('rejects when the request times out', async () => {
const fetchImpl: typeof fetch = (_input, init) => {
return new Promise<Response>((_resolve, reject) => {
init?.signal?.addEventListener('abort', () => {
Expand All @@ -40,6 +42,8 @@ describe('isAgentFeedbackEnabled', () => {
})
}

await expect(isAgentFeedbackEnabled(fetchImpl, 1)).resolves.toBe(false)
await expect(isAgentFeedbackEnabled(fetchImpl, 1)).rejects.toThrow(
'aborted'
)
})
})
2 changes: 0 additions & 2 deletions packages/next/src/cli/internal/agent-feedback-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ export async function isAgentFeedbackEnabled(
})

return response.ok && (await response.text()) === 'true'
} catch {
return false
} finally {
clearTimeout(timeout)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/client/components/handle-isr-error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function handleISRError({ error }: { error: any }) {
case 'cache':
case 'private-cache':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
case undefined:
return
default:
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/client/components/instant-samples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function instrumentParamsForClientValidation<TPArams extends Params>(
case 'request':
case 'private-cache':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
break
default:
workUnitStore satisfies never
Expand Down Expand Up @@ -76,7 +76,7 @@ export function expectCompleteParamsInClientValidation(
case 'request':
case 'private-cache':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
break
default:
workUnitStore satisfies never
Expand Down Expand Up @@ -112,7 +112,7 @@ export function instrumentSearchParamsForClientValidation(
case 'request':
case 'private-cache':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
break
default:
workUnitStore satisfies never
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function hasFallbackRouteParams(): boolean {
case 'cache':
case 'private-cache':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
break
default:
workUnitStore satisfies never
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2599,7 +2599,7 @@ function installGlobalModuleLoadingHandlers(
case 'prerender-legacy':
case 'request':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
return false
default:
workUnitStore satisfies never
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/server/app-render/create-component-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ async function createComponentTreeInternal(
case 'prerender-client':
case 'validation-client':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
break
default:
workUnitStore satisfies never
Expand Down Expand Up @@ -412,7 +412,7 @@ async function createComponentTreeInternal(
case 'prerender-client':
case 'validation-client':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
break
default:
workUnitStore satisfies never
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ export function useDynamicRouteParams(expression: string) {
throw new InvariantError(
`\`${expression}\` was called inside a cache scope. Next.js should be preventing ${expression} from being included in server components statically, but did not in this case.`
)
case 'generate-static-params':
case 'build-time-generator':
throw new InvariantError(
`\`${expression}\` was called in \`generateStaticParams\`. Next.js should be preventing ${expression} from being included in server component files statically, but did not in this case.`
`\`${expression}\` was called in \`${workUnitStore.functionName}\`. Next.js should be preventing ${expression} from being included in server component files statically, but did not in this case.`
)
case 'prerender-legacy':
case 'request':
Expand Down Expand Up @@ -127,9 +127,9 @@ export function useDynamicSearchParams(expression: string) {
throw new InvariantError(
`\`${expression}\` was called inside a cache scope. Next.js should be preventing ${expression} from being included in server components statically, but did not in this case.`
)
case 'generate-static-params':
case 'build-time-generator':
throw new InvariantError(
`\`${expression}\` was called in \`generateStaticParams\`. Next.js should be preventing ${expression} from being included in server component files statically, but did not in this case.`
`\`${expression}\` was called in \`${workUnitStore.functionName}\`. Next.js should be preventing ${expression} from being included in server component files statically, but did not in this case.`
)
case 'request':
return
Expand Down
8 changes: 4 additions & 4 deletions packages/next/src/server/app-render/dynamic-rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export function markCurrentScopeAsDynamic(
return
case 'prerender-legacy':
case 'request':
case 'generate-static-params':
case 'build-time-generator':
break
default:
workUnitStore satisfies never
Expand Down Expand Up @@ -219,7 +219,7 @@ export function markCurrentScopeAsDynamic(
workUnitStore.usedDynamic = true
}
break
case 'generate-static-params':
case 'build-time-generator':
break
default:
workUnitStore satisfies never
Expand Down Expand Up @@ -275,7 +275,7 @@ export function trackDynamicDataInDynamicRender(workUnitStore: WorkUnitStore) {
case 'prerender-legacy':
case 'prerender-client':
case 'validation-client':
case 'generate-static-params':
case 'build-time-generator':
break
case 'request':
if (process.env.NODE_ENV !== 'production') {
Expand Down Expand Up @@ -515,7 +515,7 @@ export function createHangingInputAbortSignal(
case 'cache':
case 'private-cache':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
return undefined
default:
workUnitStore satisfies never
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/app-render/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export async function decryptActionBoundArgs(
case 'cache':
case 'private-cache':
case 'unstable-cache':
case 'generate-static-params':
case 'build-time-generator':
case undefined:
return controller.close()
default:
Expand Down
Loading
Loading