Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[do-not-merge] checking test results for 2 PRs combined #2492

Closed
wants to merge 12 commits into from
Closed
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
1,419 changes: 1,081 additions & 338 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"memfs": "^4.9.2",
"mock-require": "^3.0.3",
"msw": "^2.0.7",
"next": "^14.0.4",
"next": "^15.0.0-canary.28",
"os": "^0.1.2",
"outdent": "^0.8.0",
"p-limit": "^5.0.0",
Expand Down
47 changes: 41 additions & 6 deletions src/build/content/prerendered.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { trace } from '@opentelemetry/api'
import { wrapTracer } from '@opentelemetry/api/experimental'
import { glob } from 'fast-glob'
import pLimit from 'p-limit'
import { satisfies } from 'semver'

import { encodeBlobKey } from '../../shared/blobkey.js'
import type {
CachedFetchValue,
NetlifyCachedAppPageValue,
NetlifyCachedPageValue,
NetlifyCachedRouteValue,
NetlifyCacheHandlerValue,
Expand Down Expand Up @@ -46,13 +48,29 @@ const buildPagesCacheValue = async (path: string): Promise<NetlifyCachedPageValu
kind: 'PAGE',
html: await readFile(`${path}.html`, 'utf-8'),
pageData: JSON.parse(await readFile(`${path}.json`, 'utf-8')),
postponed: undefined,
headers: undefined,
status: undefined,
})

const buildAppCacheValue = async (path: string): Promise<NetlifyCachedPageValue> => {
const buildAppCacheValue = async (
path: string,
shouldUseAppPageKind: boolean,
): Promise<NetlifyCachedAppPageValue | NetlifyCachedPageValue> => {
const meta = JSON.parse(await readFile(`${path}.meta`, 'utf-8'))
const html = await readFile(`${path}.html`, 'utf-8')

// supporting both old and new cache kind for App Router pages - https://github.com/vercel/next.js/pull/65988
if (shouldUseAppPageKind) {
return {
kind: 'APP_PAGE',
html,
rscData: await readFile(`${path}.rsc`, 'base64').catch(() =>
readFile(`${path}.prefetch.rsc`, 'base64'),
),
...meta,
}
}

const rsc = await readFile(`${path}.rsc`, 'utf-8').catch(() =>
readFile(`${path}.prefetch.rsc`, 'utf-8'),
)
Expand All @@ -66,10 +84,9 @@ const buildAppCacheValue = async (path: string): Promise<NetlifyCachedPageValue>
) {
meta.status = 404
}

return {
kind: 'PAGE',
html: await readFile(`${path}.html`, 'utf-8'),
html,
pageData: rsc,
...meta,
}
Expand Down Expand Up @@ -103,6 +120,18 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void>

const limitConcurrentPrerenderContentHandling = pLimit(10)

// https://github.com/vercel/next.js/pull/65988 introduced Cache kind specific to pages in App Router (`APP_PAGE`).
// Before this change there was common kind for both Pages router and App router pages
// so we check Next.js version to decide how to generate cache values for App Router pages.
// Note: at time of writing this code, released 15@rc uses old kind for App Router pages, while [email protected] and newer canaries use new kind.
// Looking at 15@rc release branch it was merging `canary` branch in, so the version constraint assumes that future 15@rc (and 15@latest) versions
// will use new kind for App Router pages.
const shouldUseAppPageKind = ctx.nextVersion
? satisfies(ctx.nextVersion, '>=15.0.0-canary.13 <15.0.0-d || >15.0.0-rc.0', {
includePrerelease: true,
})
: false

await Promise.all(
Object.entries(manifest.routes).map(
([route, meta]): Promise<void> =>
Expand All @@ -125,7 +154,10 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void>
value = await buildPagesCacheValue(join(ctx.publishDir, 'server/pages', key))
break
case meta.dataRoute?.endsWith('.rsc'):
value = await buildAppCacheValue(join(ctx.publishDir, 'server/app', key))
value = await buildAppCacheValue(
join(ctx.publishDir, 'server/app', key),
shouldUseAppPageKind,
)
break
case meta.dataRoute === null:
value = await buildRouteCacheValue(
Expand All @@ -147,7 +179,10 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise<void>
if (existsSync(join(ctx.publishDir, `server/app/_not-found.html`))) {
const lastModified = Date.now()
const key = '/404'
const value = await buildAppCacheValue(join(ctx.publishDir, 'server/app/_not-found'))
const value = await buildAppCacheValue(
join(ctx.publishDir, 'server/app/_not-found'),
shouldUseAppPageKind,
)
await writeCacheEntry(key, value, lastModified, ctx)
}
} catch (error) {
Expand Down
20 changes: 3 additions & 17 deletions src/build/content/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { prerelease, lt as semverLowerThan, lte as semverLowerThanOrEqual } from

import { RUN_CONFIG } from '../../run/constants.js'
import { PluginContext } from '../plugin-context.js'
import { verifyNextVersion } from '../verification.js'

const tracer = wrapTracer(trace.getTracer('Next runtime'))

Expand Down Expand Up @@ -292,26 +291,13 @@ export const copyNextDependencies = async (ctx: PluginContext): Promise<void> =>

await Promise.all(promises)

// detect if it might lead to a runtime issue and throw an error upfront on build time instead of silently failing during runtime
const serverHandlerRequire = createRequire(posixJoin(ctx.serverHandlerDir, ':internal:'))

let nextVersion: string | undefined
try {
const { version } = serverHandlerRequire('next/package.json')
if (version) {
nextVersion = version as string
}
} catch {
// failed to resolve package.json - currently this is resolvable in all known next versions, but if next implements
// exports map it still might be a problem in the future, so we are not breaking here
}

if (nextVersion) {
verifyNextVersion(ctx, nextVersion)

await patchNextModules(ctx, nextVersion, serverHandlerRequire.resolve)
if (ctx.nextVersion) {
await patchNextModules(ctx, ctx.nextVersion, serverHandlerRequire.resolve)
}

// detect if it might lead to a runtime issue and throw an error upfront on build time instead of silently failing during runtime
try {
const nextEntryAbsolutePath = serverHandlerRequire.resolve('next')
const nextRequire = createRequire(nextEntryAbsolutePath)
Expand Down
21 changes: 21 additions & 0 deletions src/build/plugin-context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { existsSync, readFileSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { join, relative, resolve } from 'node:path'
import { join as posixJoin } from 'node:path/posix'
import { fileURLToPath } from 'node:url'

import type {
Expand Down Expand Up @@ -313,6 +315,25 @@ export class PluginContext {
return JSON.parse(await readFile(join(this.publishDir, 'routes-manifest.json'), 'utf-8'))
}

#nextVersion: string | null | undefined = undefined

/**
* Get Next.js version that was used to build the site
*/
get nextVersion(): string | null {
if (this.#nextVersion === undefined) {
try {
const serverHandlerRequire = createRequire(posixJoin(this.standaloneRootDir, ':internal:'))
const { version } = serverHandlerRequire('next/package.json')
this.#nextVersion = version as string
} catch {
this.#nextVersion = null
}
}

return this.#nextVersion
}

/** Fails a build with a message and an optional error */
failBuild(message: string, error?: unknown): never {
return this.utils.build.failBuild(message, error instanceof Error ? { error } : undefined)
Expand Down
17 changes: 9 additions & 8 deletions src/build/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ export function verifyPublishDir(ctx: PluginContext) {
`Your publish directory does not contain expected Next.js build output. Please make sure you are using Next.js version (${SUPPORTED_NEXT_VERSIONS})`,
)
}

if (
ctx.nextVersion &&
!satisfies(ctx.nextVersion, SUPPORTED_NEXT_VERSIONS, { includePrerelease: true })
) {
ctx.failBuild(
`@netlify/plugin-next@5 requires Next.js version ${SUPPORTED_NEXT_VERSIONS}, but found ${ctx.nextVersion}. Please upgrade your project's Next.js version.`,
)
}
}
if (ctx.buildConfig.output === 'export') {
if (!ctx.exportDetail?.success) {
Expand All @@ -60,14 +69,6 @@ export function verifyPublishDir(ctx: PluginContext) {
}
}

export function verifyNextVersion(ctx: PluginContext, nextVersion: string): void | never {
if (!satisfies(nextVersion, SUPPORTED_NEXT_VERSIONS, { includePrerelease: true })) {
ctx.failBuild(
`@netlify/plugin-next@5 requires Next.js version ${SUPPORTED_NEXT_VERSIONS}, but found ${nextVersion}. Please upgrade your project's Next.js version.`,
)
}
}

export async function verifyNoAdvancedAPIRoutes(ctx: PluginContext) {
const apiRoutesConfigs = await getAPIRoutesConfigs(ctx)

Expand Down
2 changes: 2 additions & 0 deletions src/run/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const setRunConfig = (config: NextConfigComplete) => {
// set the path to the cache handler
config.experimental = {
...config.experimental,
// @ts-expect-error incrementalCacheHandlerPath was removed from config type
// but we still need to set it for older Next.js versions
incrementalCacheHandlerPath: cacheHandler,
}

Expand Down
Loading
Loading