Skip to content

Commit ed31239

Browse files
authored
Merge branch 'main' into JakeChampion-patch-1
2 parents 6ef9255 + b26f7b6 commit ed31239

File tree

40 files changed

+703
-416
lines changed

40 files changed

+703
-416
lines changed

.release-please-manifest.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
{
22
"packages/build-info": "10.0.7",
3-
"packages/build": "35.0.4",
4-
"packages/edge-bundler": "14.5.0",
5-
"packages/cache-utils": "6.0.3",
6-
"packages/config": "24.0.1",
7-
"packages/functions-utils": "6.2.1",
3+
"packages/build": "35.1.2",
4+
"packages/edge-bundler": "14.5.2",
5+
"packages/cache-utils": "6.0.4",
6+
"packages/config": "24.0.3",
7+
"packages/functions-utils": "6.2.4",
88
"packages/git-utils": "6.0.2",
9-
"packages/headers-parser": "9.0.1",
10-
"packages/js-client": "14.0.3",
9+
"packages/headers-parser": "9.0.2",
10+
"packages/js-client": "14.0.4",
1111
"packages/nock-udp": "5.0.1",
1212
"packages/redirect-parser": "15.0.3",
1313
"packages/run-utils": "6.0.2",
1414
"packages/opentelemetry-sdk-setup": "2.0.2",
1515
"packages/opentelemetry-utils": "2.0.1",
16-
"packages/zip-it-and-ship-it": "14.1.1"
16+
"packages/zip-it-and-ship-it": "14.1.4"
1717
}

package-lock.json

Lines changed: 243 additions & 299 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/build/CHANGELOG.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,80 @@
105105
* dependencies
106106
* @netlify/config bumped from ^20.8.0 to ^20.8.1
107107

108+
## [35.1.2](https://github.com/netlify/build/compare/build-v35.1.1...build-v35.1.2) (2025-08-19)
109+
110+
111+
### Dependencies
112+
113+
* The following workspace dependencies were updated
114+
* dependencies
115+
* @netlify/functions-utils bumped from ^6.2.3 to ^6.2.4
116+
* @netlify/zip-it-and-ship-it bumped from 14.1.3 to 14.1.4
117+
118+
## [35.1.1](https://github.com/netlify/build/compare/build-v35.1.0...build-v35.1.1) (2025-08-18)
119+
120+
121+
### Dependencies
122+
123+
* The following workspace dependencies were updated
124+
* dependencies
125+
* @netlify/functions-utils bumped from ^6.2.2 to ^6.2.3
126+
* @netlify/zip-it-and-ship-it bumped from 14.1.2 to 14.1.3
127+
128+
## [35.1.0](https://github.com/netlify/build/compare/build-v35.0.7...build-v35.1.0) (2025-08-14)
129+
130+
131+
### Features
132+
133+
* add skew protection to Frameworks API ([#6601](https://github.com/netlify/build/issues/6601)) ([6cf3065](https://github.com/netlify/build/commit/6cf306535d8083797a140ecaa05b0778c33e1052))
134+
135+
136+
### Dependencies
137+
138+
* The following workspace dependencies were updated
139+
* dependencies
140+
* @netlify/config bumped from ^24.0.2 to ^24.0.3
141+
142+
## [35.0.7](https://github.com/netlify/build/compare/build-v35.0.6...build-v35.0.7) (2025-08-12)
143+
144+
145+
### Dependencies
146+
147+
* The following workspace dependencies were updated
148+
* dependencies
149+
* @netlify/edge-bundler bumped from 14.5.1 to 14.5.2
150+
151+
## [35.0.6](https://github.com/netlify/build/compare/build-v35.0.5...build-v35.0.6) (2025-08-11)
152+
153+
154+
### Bug Fixes
155+
156+
* **deps:** update dependency @netlify/blobs to ^10.0.8 ([#6584](https://github.com/netlify/build/issues/6584)) ([1e7332a](https://github.com/netlify/build/commit/1e7332ad332f8b35e5ce48cbfda389e22658b618))
157+
158+
159+
### Dependencies
160+
161+
* The following workspace dependencies were updated
162+
* dependencies
163+
* @netlify/edge-bundler bumped from 14.5.0 to 14.5.1
164+
165+
## [35.0.5](https://github.com/netlify/build/compare/build-v35.0.4...build-v35.0.5) (2025-08-08)
166+
167+
168+
### Bug Fixes
169+
170+
* remove code supporting versions of Node < v15 ([#6599](https://github.com/netlify/build/issues/6599)) ([d9f55d3](https://github.com/netlify/build/commit/d9f55d35fbf4c1f37f2a6632a43566fb6d295ca1))
171+
172+
173+
### Dependencies
174+
175+
* The following workspace dependencies were updated
176+
* dependencies
177+
* @netlify/cache-utils bumped from ^6.0.3 to ^6.0.4
178+
* @netlify/config bumped from ^24.0.1 to ^24.0.2
179+
* @netlify/functions-utils bumped from ^6.2.1 to ^6.2.2
180+
* @netlify/zip-it-and-ship-it bumped from 14.1.1 to 14.1.2
181+
108182
## [35.0.4](https://github.com/netlify/build/compare/build-v35.0.3...build-v35.0.4) (2025-08-06)
109183

110184

packages/build/package.json

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@netlify/build",
3-
"version": "35.0.4",
3+
"version": "35.1.2",
44
"description": "Netlify build module",
55
"type": "module",
66
"exports": "./lib/index.js",
@@ -67,16 +67,16 @@
6767
"license": "MIT",
6868
"dependencies": {
6969
"@bugsnag/js": "^8.0.0",
70-
"@netlify/blobs": "^10.0.7",
71-
"@netlify/cache-utils": "^6.0.3",
72-
"@netlify/config": "^24.0.1",
73-
"@netlify/edge-bundler": "14.5.0",
74-
"@netlify/functions-utils": "^6.2.1",
70+
"@netlify/blobs": "^10.0.8",
71+
"@netlify/cache-utils": "^6.0.4",
72+
"@netlify/config": "^24.0.3",
73+
"@netlify/edge-bundler": "14.5.2",
74+
"@netlify/functions-utils": "^6.2.4",
7575
"@netlify/git-utils": "^6.0.2",
7676
"@netlify/opentelemetry-utils": "^2.0.1",
7777
"@netlify/plugins-list": "^6.80.0",
7878
"@netlify/run-utils": "^6.0.2",
79-
"@netlify/zip-it-and-ship-it": "14.1.1",
79+
"@netlify/zip-it-and-ship-it": "14.1.4",
8080
"@sindresorhus/slugify": "^2.0.0",
8181
"ansi-escapes": "^7.0.0",
8282
"ansis": "^4.1.0",
@@ -115,7 +115,8 @@
115115
"typescript": "^5.0.0",
116116
"uuid": "^11.0.0",
117117
"yaml": "^2.8.0",
118-
"yargs": "^17.6.0"
118+
"yargs": "^17.6.0",
119+
"zod": "^3.25.76"
119120
},
120121
"devDependencies": {
121122
"@netlify/nock-udp": "^5.0.1",

packages/build/src/plugins_core/edge_functions/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Metric } from '../../core/report_metrics.js'
88
import { log, reduceLogLines } from '../../log/logger.js'
99
import { logFunctionsToBundle } from '../../log/messages/core_steps.js'
1010
import {
11-
FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT,
11+
FRAMEWORKS_API_EDGE_FUNCTIONS_PATH,
1212
FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP,
1313
} from '../../utils/frameworks_api.js'
1414

@@ -52,7 +52,7 @@ const coreStep = async function ({
5252
const internalSrcPath = resolve(buildDir, internalSrcDirectory)
5353
const distImportMapPath = join(dirname(internalSrcPath), IMPORT_MAP_FILENAME)
5454
const srcPath = srcDirectory ? resolve(buildDir, srcDirectory) : undefined
55-
const frameworksAPISrcPath = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT)
55+
const frameworksAPISrcPath = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_PATH)
5656
const generatedFunctionPaths = [internalSrcPath]
5757

5858
if (await pathExists(frameworksAPISrcPath)) {
@@ -62,7 +62,7 @@ const coreStep = async function ({
6262
const frameworkImportMap = resolve(
6363
buildDir,
6464
packagePath || '',
65-
FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT,
65+
FRAMEWORKS_API_EDGE_FUNCTIONS_PATH,
6666
FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP,
6767
)
6868

@@ -170,7 +170,7 @@ const hasEdgeFunctionsDirectories = async function ({
170170
return true
171171
}
172172

173-
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT)
173+
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_PATH)
174174

175175
return await pathExists(frameworkFunctionsSrc)
176176
}

packages/build/src/plugins_core/frameworks_api/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import { promises as fs } from 'node:fs'
2+
import { dirname, resolve } from 'node:path'
3+
14
import { mergeConfigs } from '@netlify/config'
25

36
import type { NetlifyConfig } from '../../index.js'
47
import { getConfigMutations } from '../../plugins/child/diff.js'
8+
import { DEPLOY_CONFIG_DIST_PATH, FRAMEWORKS_API_SKEW_PROTECTION_PATH } from '../../utils/frameworks_api.js'
59
import { CoreStep, CoreStepFunction } from '../types.js'
610

11+
import { loadSkewProtectionConfig } from './skew_protection.js'
712
import { filterConfig, loadConfigFile } from './util.js'
813

914
// The properties that can be set using this API. Each element represents a
@@ -26,6 +31,30 @@ const ALLOWED_PROPERTIES = [
2631
// a special notation where `redirects!` represents "forced redirects", etc.
2732
const OVERRIDE_PROPERTIES = new Set(['redirects!'])
2833

34+
// Looks for a skew protection configuration file. If found, the file is loaded
35+
// and validated against the schema, throwing a build error if validation
36+
// fails. If valid, the contents are written to the deploy config file.
37+
const handleSkewProtection = async (buildDir: string, packagePath?: string) => {
38+
const inputPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_SKEW_PROTECTION_PATH)
39+
const outputPath = resolve(buildDir, packagePath ?? '', DEPLOY_CONFIG_DIST_PATH)
40+
41+
const skewProtectionConfig = await loadSkewProtectionConfig(inputPath)
42+
if (!skewProtectionConfig) {
43+
return
44+
}
45+
46+
const deployConfig = {
47+
skew_protection: skewProtectionConfig,
48+
}
49+
50+
try {
51+
await fs.mkdir(dirname(outputPath), { recursive: true })
52+
await fs.writeFile(outputPath, JSON.stringify(deployConfig))
53+
} catch (error) {
54+
throw new Error('Failed to process skew protection configuration', { cause: error })
55+
}
56+
}
57+
2958
const coreStep: CoreStepFunction = async function ({
3059
buildDir,
3160
netlifyConfig,
@@ -34,6 +63,7 @@ const coreStep: CoreStepFunction = async function ({
3463
// no-op
3564
},
3665
}) {
66+
await handleSkewProtection(buildDir, packagePath)
3767
let config: Partial<NetlifyConfig> | undefined
3868

3969
try {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { promises as fs } from 'node:fs'
2+
3+
import { z } from 'zod'
4+
5+
const deployIDSourceTypeSchema = z.enum(['cookie', 'header', 'query'])
6+
7+
const deployIDSourceSchema = z.object({
8+
type: deployIDSourceTypeSchema,
9+
name: z.string(),
10+
})
11+
12+
const skewProtectionConfigSchema = z.object({
13+
patterns: z.array(z.string()),
14+
sources: z.array(deployIDSourceSchema),
15+
})
16+
17+
export type SkewProtectionConfig = z.infer<typeof skewProtectionConfigSchema>
18+
export type DeployIDSource = z.infer<typeof deployIDSourceSchema>
19+
export type DeployIDSourceType = z.infer<typeof deployIDSourceTypeSchema>
20+
21+
const validateSkewProtectionConfig = (input: unknown): SkewProtectionConfig => {
22+
const { data, error, success } = skewProtectionConfigSchema.safeParse(input)
23+
24+
if (success) {
25+
return data
26+
}
27+
28+
throw new Error(`Invalid skew protection configuration:\n\n${formatSchemaError(error)}`)
29+
}
30+
31+
export const loadSkewProtectionConfig = async (configPath: string) => {
32+
let parsedData: unknown
33+
34+
try {
35+
const data = await fs.readFile(configPath, 'utf8')
36+
37+
parsedData = JSON.parse(data)
38+
} catch (error) {
39+
// If the file doesn't exist, this is a non-error.
40+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
41+
return
42+
}
43+
44+
throw new Error('Invalid skew protection configuration', { cause: error })
45+
}
46+
47+
return validateSkewProtectionConfig(parsedData)
48+
}
49+
50+
const formatSchemaError = (error: z.ZodError) => {
51+
const lines = error.issues.map((issue) => `- ${issue.path.join('.')}: ${issue.message}`)
52+
53+
return lines.join('\n')
54+
}

packages/build/src/plugins_core/frameworks_api/util.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { resolve } from 'path'
44
import isPlainObject from 'is-plain-obj'
55

66
import type { NetlifyConfig } from '../../index.js'
7-
import { FRAMEWORKS_API_CONFIG_ENDPOINT } from '../../utils/frameworks_api.js'
7+
import { FRAMEWORKS_API_CONFIG_PATH } from '../../utils/frameworks_api.js'
88
import { SystemLogger } from '../types.js'
99

1010
export const loadConfigFile = async (buildDir: string, packagePath?: string) => {
11-
const configPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_CONFIG_ENDPOINT)
11+
const configPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_CONFIG_PATH)
1212

1313
try {
1414
const data = await fs.readFile(configPath, 'utf8')

packages/build/src/plugins_core/functions/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { addErrorInfo } from '../../error/info.js'
77
import { log } from '../../log/logger.js'
88
import { type GeneratedFunction, getGeneratedFunctions } from '../../steps/return_values.js'
99
import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle } from '../../log/messages/core_steps.js'
10-
import { FRAMEWORKS_API_FUNCTIONS_ENDPOINT } from '../../utils/frameworks_api.js'
10+
import { FRAMEWORKS_API_FUNCTIONS_PATH } from '../../utils/frameworks_api.js'
1111

1212
import { getZipError } from './error.js'
1313
import { getUserAndInternalFunctions, validateFunctionsSrc } from './utils.js'
@@ -147,7 +147,7 @@ const coreStep = async function ({
147147
const functionsDist = resolve(buildDir, relativeFunctionsDist)
148148
const internalFunctionsSrc = resolve(buildDir, relativeInternalFunctionsSrc)
149149
const internalFunctionsSrcExists = await pathExists(internalFunctionsSrc)
150-
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_ENDPOINT)
150+
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_PATH)
151151
const frameworkFunctionsSrcExists = await pathExists(frameworkFunctionsSrc)
152152
const functionsSrcExists = await validateFunctionsSrc({ functionsSrc, relativeFunctionsSrc })
153153
const [userFunctions = [], internalFunctions = [], frameworkFunctions = []] = await getUserAndInternalFunctions({
@@ -240,7 +240,7 @@ const hasFunctionsDirectories = async function ({
240240
return true
241241
}
242242

243-
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_ENDPOINT)
243+
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_PATH)
244244

245245
if (await pathExists(frameworkFunctionsSrc)) {
246246
return true

packages/build/src/plugins_core/pre_cleanup/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { rm } from 'node:fs/promises'
22
import { resolve } from 'node:path'
33

44
import { getBlobsDirs } from '../../utils/blobs.js'
5-
import { FRAMEWORKS_API_ENDPOINT } from '../../utils/frameworks_api.js'
5+
import { FRAMEWORKS_API_PATH } from '../../utils/frameworks_api.js'
66
import { CoreStep, CoreStepFunction } from '../types.js'
77

88
const coreStep: CoreStepFunction = async ({ buildDir, packagePath }) => {
9-
const dirs = [...getBlobsDirs(buildDir, packagePath), resolve(buildDir, packagePath || '', FRAMEWORKS_API_ENDPOINT)]
9+
const dirs = [...getBlobsDirs(buildDir, packagePath), resolve(buildDir, packagePath || '', FRAMEWORKS_API_PATH)]
1010

1111
try {
1212
await Promise.all(dirs.map((dir) => rm(dir, { recursive: true, force: true })))

0 commit comments

Comments
 (0)