Skip to content

Commit 4858c86

Browse files
committed
refactor: added more strict app segment config parsing
1 parent 3e7ff6b commit 4858c86

File tree

6 files changed

+302
-106
lines changed

6 files changed

+302
-106
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { AppSegmentConfigSchema } from './app-segment-config'
2+
3+
describe('AppConfigSchema', () => {
4+
it('should only support zero, a positive number or false for revalidate', () => {
5+
const valid = [0, 1, 100, false]
6+
7+
for (const value of valid) {
8+
expect(
9+
AppSegmentConfigSchema.safeParse({ revalidate: value }).success
10+
).toBe(true)
11+
}
12+
13+
const invalid = [-1, -100, true]
14+
15+
for (const value of invalid) {
16+
expect(
17+
AppSegmentConfigSchema.safeParse({ revalidate: value }).success
18+
).toBe(false)
19+
}
20+
})
21+
22+
it('should support an empty config', () => {
23+
expect(AppSegmentConfigSchema.safeParse({}).success).toBe(true)
24+
})
25+
26+
it('should support a boolean for dynamicParams', () => {
27+
expect(
28+
AppSegmentConfigSchema.safeParse({ dynamicParams: true }).success
29+
).toBe(true)
30+
expect(
31+
AppSegmentConfigSchema.safeParse({ dynamicParams: false }).success
32+
).toBe(true)
33+
expect(
34+
AppSegmentConfigSchema.safeParse({ dynamicParams: 'foo' }).success
35+
).toBe(false)
36+
})
37+
38+
it('should support "auto" | "force-dynamic" | "error" | "force-static" for dynamic', () => {
39+
expect(AppSegmentConfigSchema.safeParse({ dynamic: 'auto' }).success).toBe(
40+
true
41+
)
42+
expect(
43+
AppSegmentConfigSchema.safeParse({ dynamic: 'force-dynamic' }).success
44+
).toBe(true)
45+
expect(AppSegmentConfigSchema.safeParse({ dynamic: 'error' }).success).toBe(
46+
true
47+
)
48+
expect(
49+
AppSegmentConfigSchema.safeParse({ dynamic: 'force-static' }).success
50+
).toBe(true)
51+
})
52+
53+
it('should support "edge" | "nodejs" for runtime', () => {
54+
expect(AppSegmentConfigSchema.safeParse({ runtime: 'edge' }).success).toBe(
55+
true
56+
)
57+
expect(
58+
AppSegmentConfigSchema.safeParse({ runtime: 'nodejs' }).success
59+
).toBe(true)
60+
expect(AppSegmentConfigSchema.safeParse({ runtime: 'foo' }).success).toBe(
61+
false
62+
)
63+
})
64+
65+
it('should support a positive number or zero for maxDuration', () => {
66+
expect(AppSegmentConfigSchema.safeParse({ maxDuration: 0 }).success).toBe(
67+
true
68+
)
69+
expect(AppSegmentConfigSchema.safeParse({ maxDuration: 100 }).success).toBe(
70+
true
71+
)
72+
expect(AppSegmentConfigSchema.safeParse({ maxDuration: -1 }).success).toBe(
73+
false
74+
)
75+
})
76+
77+
it('should support "force-cache" | "only-cache" for fetchCache', () => {
78+
expect(
79+
AppSegmentConfigSchema.safeParse({ fetchCache: 'force-cache' }).success
80+
).toBe(true)
81+
expect(
82+
AppSegmentConfigSchema.safeParse({ fetchCache: 'only-cache' }).success
83+
).toBe(true)
84+
expect(
85+
AppSegmentConfigSchema.safeParse({ fetchCache: 'foo' }).success
86+
).toBe(false)
87+
})
88+
89+
it('should support a string or an array of strings for preferredRegion', () => {
90+
expect(
91+
AppSegmentConfigSchema.safeParse({ preferredRegion: 'foo' }).success
92+
).toBe(true)
93+
expect(
94+
AppSegmentConfigSchema.safeParse({ preferredRegion: ['foo', 'bar'] })
95+
.success
96+
).toBe(true)
97+
})
98+
99+
it('should support a boolean for experimental_ppr', () => {
100+
expect(
101+
AppSegmentConfigSchema.safeParse({ experimental_ppr: true }).success
102+
).toBe(true)
103+
expect(
104+
AppSegmentConfigSchema.safeParse({ experimental_ppr: false }).success
105+
).toBe(true)
106+
expect(
107+
AppSegmentConfigSchema.safeParse({ experimental_ppr: 'foo' }).success
108+
).toBe(false)
109+
})
110+
})
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { z } from 'next/dist/compiled/zod'
2+
3+
/**
4+
* The schema for the dynamic behavior of a page.
5+
*/
6+
export const AppSegmentConfigDynamicSchema = z.enum([
7+
'auto',
8+
'error',
9+
'force-static',
10+
'force-dynamic',
11+
])
12+
13+
/**
14+
* The dynamic behavior of the page.
15+
*/
16+
export type AppSegmentConfigDynamic = z.infer<
17+
typeof AppSegmentConfigDynamicSchema
18+
>
19+
20+
/**
21+
* The schema for configuration for a page.
22+
*/
23+
export const AppSegmentConfigSchema = z.object({
24+
/**
25+
* The number of seconds to revalidate the page or false to disable revalidation.
26+
*/
27+
revalidate: z
28+
.union([z.number().int().nonnegative(), z.literal(false)])
29+
.optional(),
30+
31+
/**
32+
* Whether the page supports dynamic parameters.
33+
*/
34+
dynamicParams: z.boolean().optional(),
35+
36+
/**
37+
* The dynamic behavior of the page.
38+
*/
39+
dynamic: AppSegmentConfigDynamicSchema.optional(),
40+
41+
/**
42+
* The caching behavior of the page.
43+
*/
44+
fetchCache: z.enum(['force-cache', 'only-cache']).optional(),
45+
46+
/**
47+
* The preferred region for the page.
48+
*/
49+
preferredRegion: z.union([z.string(), z.array(z.string())]).optional(),
50+
51+
/**
52+
* Whether the page supports partial prerendering. When true, the page will be
53+
* served using partial prerendering. This setting will only take affect if
54+
* it's enabled via the `experimental.ppr = "incremental"` option.
55+
*/
56+
experimental_ppr: z.boolean().optional(),
57+
58+
/**
59+
* The runtime to use for the page.
60+
*/
61+
runtime: z.enum(['edge', 'nodejs']).optional(),
62+
63+
/**
64+
* The maximum duration for the page in seconds.
65+
*/
66+
maxDuration: z.number().int().nonnegative().optional(),
67+
})
68+
69+
/**
70+
* The configuration for a page.
71+
*/
72+
export type AppSegmentConfig = z.infer<typeof AppSegmentConfigSchema>
73+
74+
export const AppSegmentConfigSchemaKeys = AppSegmentConfigSchema.keyof().options

packages/next/src/build/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ import {
130130
collectMeta,
131131
// getSupportedBrowsers,
132132
} from './utils'
133-
import type { PageInfo, PageInfos, AppConfig, PrerenderedRoute } from './utils'
133+
import type { PageInfo, PageInfos, PrerenderedRoute } from './utils'
134+
import type { AppSegmentConfig } from './app-segment-config'
134135
import { writeBuildId } from './write-build-id'
135136
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
136137
import isError from '../lib/is-error'
@@ -1806,7 +1807,7 @@ export default async function build(
18061807
const staticPaths = new Map<string, PrerenderedRoute[]>()
18071808
const appNormalizedPaths = new Map<string, string>()
18081809
const fallbackModes = new Map<string, FallbackMode>()
1809-
const appDefaultConfigs = new Map<string, AppConfig>()
1810+
const appDefaultConfigs = new Map<string, AppSegmentConfig>()
18101811
const pageInfos: PageInfos = new Map<string, PageInfo>()
18111812
let pagesManifest = await readManifest<PagesManifest>(pagesManifestPath)
18121813
const buildManifest = await readManifest<BuildManifest>(buildManifestPath)

0 commit comments

Comments
 (0)