Skip to content

Commit

Permalink
refactor: added more strict app segment config parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
wyattjoh committed Sep 26, 2024
1 parent 07a55e0 commit ba16a69
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 247 deletions.
110 changes: 110 additions & 0 deletions packages/next/src/build/app-segment-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { AppSegmentConfigSchema } from './app-segment-config'

describe('AppConfigSchema', () => {
it('should only support zero, a positive number or false for revalidate', () => {
const valid = [0, 1, 100, false]

for (const value of valid) {
expect(
AppSegmentConfigSchema.safeParse({ revalidate: value }).success
).toBe(true)
}

const invalid = [-1, -100, true]

for (const value of invalid) {
expect(
AppSegmentConfigSchema.safeParse({ revalidate: value }).success
).toBe(false)
}
})

it('should support an empty config', () => {
expect(AppSegmentConfigSchema.safeParse({}).success).toBe(true)
})

it('should support a boolean for dynamicParams', () => {
expect(
AppSegmentConfigSchema.safeParse({ dynamicParams: true }).success
).toBe(true)
expect(
AppSegmentConfigSchema.safeParse({ dynamicParams: false }).success
).toBe(true)
expect(
AppSegmentConfigSchema.safeParse({ dynamicParams: 'foo' }).success
).toBe(false)
})

it('should support "auto" | "force-dynamic" | "error" | "force-static" for dynamic', () => {
expect(AppSegmentConfigSchema.safeParse({ dynamic: 'auto' }).success).toBe(
true
)
expect(
AppSegmentConfigSchema.safeParse({ dynamic: 'force-dynamic' }).success
).toBe(true)
expect(AppSegmentConfigSchema.safeParse({ dynamic: 'error' }).success).toBe(
true
)
expect(
AppSegmentConfigSchema.safeParse({ dynamic: 'force-static' }).success
).toBe(true)
})

it('should support "edge" | "nodejs" for runtime', () => {
expect(AppSegmentConfigSchema.safeParse({ runtime: 'edge' }).success).toBe(
true
)
expect(
AppSegmentConfigSchema.safeParse({ runtime: 'nodejs' }).success
).toBe(true)
expect(AppSegmentConfigSchema.safeParse({ runtime: 'foo' }).success).toBe(
false
)
})

it('should support a positive number or zero for maxDuration', () => {
expect(AppSegmentConfigSchema.safeParse({ maxDuration: 0 }).success).toBe(
true
)
expect(AppSegmentConfigSchema.safeParse({ maxDuration: 100 }).success).toBe(
true
)
expect(AppSegmentConfigSchema.safeParse({ maxDuration: -1 }).success).toBe(
false
)
})

it('should support "force-cache" | "only-cache" for fetchCache', () => {
expect(
AppSegmentConfigSchema.safeParse({ fetchCache: 'force-cache' }).success
).toBe(true)
expect(
AppSegmentConfigSchema.safeParse({ fetchCache: 'only-cache' }).success
).toBe(true)
expect(
AppSegmentConfigSchema.safeParse({ fetchCache: 'foo' }).success
).toBe(false)
})

it('should support a string or an array of strings for preferredRegion', () => {
expect(
AppSegmentConfigSchema.safeParse({ preferredRegion: 'foo' }).success
).toBe(true)
expect(
AppSegmentConfigSchema.safeParse({ preferredRegion: ['foo', 'bar'] })
.success
).toBe(true)
})

it('should support a boolean for experimental_ppr', () => {
expect(
AppSegmentConfigSchema.safeParse({ experimental_ppr: true }).success
).toBe(true)
expect(
AppSegmentConfigSchema.safeParse({ experimental_ppr: false }).success
).toBe(true)
expect(
AppSegmentConfigSchema.safeParse({ experimental_ppr: 'foo' }).success
).toBe(false)
})
})
87 changes: 87 additions & 0 deletions packages/next/src/build/app-segment-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { z } from 'next/dist/compiled/zod'

/**
* The schema for the dynamic behavior of a page.
*
* @internal
*/
export const AppSegmentConfigDynamicSchema = z.enum([
'auto',
'error',
'force-static',
'force-dynamic',
])

/**
* The dynamic behavior of the page.
*
* @internal
*/
export type AppSegmentConfigDynamic = z.infer<
typeof AppSegmentConfigDynamicSchema
>

/**
* The schema for configuration for a page.
*
* @internal
*/
export const AppSegmentConfigSchema = z.object({
/**
* The number of seconds to revalidate the page or false to disable revalidation.
*/
revalidate: z
.union([z.number().int().nonnegative(), z.literal(false)])
.optional(),

/**
* Whether the page supports dynamic parameters.
*/
dynamicParams: z.boolean().optional(),

/**
* The dynamic behavior of the page.
*/
dynamic: AppSegmentConfigDynamicSchema.optional(),

/**
* The caching behavior of the page.
*/
fetchCache: z.enum(['force-cache', 'only-cache']).optional(),

/**
* The preferred region for the page.
*/
preferredRegion: z.union([z.string(), z.array(z.string())]).optional(),

/**
* Whether the page supports partial prerendering. When true, the page will be
* served using partial prerendering. This setting will only take affect if
* it's enabled via the `experimental.ppr = "incremental"` option.
*/
experimental_ppr: z.boolean().optional(),

/**
* The runtime to use for the page.
*/
runtime: z.enum(['edge', 'nodejs']).optional(),

/**
* The maximum duration for the page in seconds.
*/
maxDuration: z.number().int().nonnegative().optional(),
})

/**
* The configuration for a page.
*
* @internal
*/
export type AppSegmentConfig = z.infer<typeof AppSegmentConfigSchema>

/**
* The keys of the configuration for a page.
*
* @internal
*/
export const AppSegmentConfigSchemaKeys = AppSegmentConfigSchema.keyof().options
5 changes: 3 additions & 2 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ import {
collectMeta,
// getSupportedBrowsers,
} from './utils'
import type { PageInfo, PageInfos, AppConfig, PrerenderedRoute } from './utils'
import type { PageInfo, PageInfos, PrerenderedRoute } from './utils'
import type { AppSegmentConfig } from './app-segment-config'
import { writeBuildId } from './write-build-id'
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
import isError from '../lib/is-error'
Expand Down Expand Up @@ -1815,7 +1816,7 @@ export default async function build(
const staticPaths = new Map<string, PrerenderedRoute[]>()
const appNormalizedPaths = new Map<string, string>()
const fallbackModes = new Map<string, FallbackMode>()
const appDefaultConfigs = new Map<string, AppConfig>()
const appDefaultConfigs = new Map<string, AppSegmentConfig>()
const pageInfos: PageInfos = new Map<string, PageInfo>()
let pagesManifest = await readManifest<PagesManifest>(pagesManifestPath)
const buildManifest = await readManifest<BuildManifest>(buildManifestPath)
Expand Down
Loading

0 comments on commit ba16a69

Please sign in to comment.