From 8fbe9edd22b392d1fb25ebe41b2120b8fe545c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Sun, 3 Aug 2025 09:49:58 +0100 Subject: [PATCH 1/6] feat: add skew protection to Frameworks API --- package-lock.json | 9 ++-- packages/build/package.json | 3 +- .../src/plugins_core/frameworks_api/index.ts | 30 +++++++++++++ .../frameworks_api/skew_protection.ts | 45 +++++++++++++++++++ packages/build/src/utils/frameworks_api.ts | 3 ++ .../skew_protection_invalid/build.mjs | 8 ++++ .../skew_protection_invalid/netlify.toml | 2 + .../skew_protection_missing/build.mjs | 1 + .../skew_protection_missing/netlify.toml | 2 + .../fixtures/skew_protection_valid/build.mjs | 12 +++++ .../skew_protection_valid/netlify.toml | 2 + packages/build/tests/frameworks_api/tests.js | 43 ++++++++++++++++++ .../yarn/plugin/node_modules/.yarn-integrity | 28 ++++++++++++ .../plugin/node_modules/math-avg/index.js | 13 ++++++ .../yarn/plugin/node_modules/math-avg/license | 21 +++++++++ .../plugin/node_modules/math-avg/package.json | 35 +++++++++++++++ .../plugin/node_modules/math-avg/readme.md | 33 ++++++++++++++ .../plugin/node_modules/.yarn-integrity | 28 ++++++++++++ .../plugin/node_modules/math-avg/index.js | 13 ++++++ .../plugin/node_modules/math-avg/license | 21 +++++++++ .../plugin/node_modules/math-avg/package.json | 35 +++++++++++++++ .../plugin/node_modules/math-avg/readme.md | 33 ++++++++++++++ 22 files changed, 415 insertions(+), 5 deletions(-) create mode 100644 packages/build/src/plugins_core/frameworks_api/skew_protection.ts create mode 100644 packages/build/tests/frameworks_api/fixtures/skew_protection_invalid/build.mjs create mode 100644 packages/build/tests/frameworks_api/fixtures/skew_protection_invalid/netlify.toml create mode 100644 packages/build/tests/frameworks_api/fixtures/skew_protection_missing/build.mjs create mode 100644 packages/build/tests/frameworks_api/fixtures/skew_protection_missing/netlify.toml create mode 100644 packages/build/tests/frameworks_api/fixtures/skew_protection_valid/build.mjs create mode 100644 packages/build/tests/frameworks_api/fixtures/skew_protection_valid/netlify.toml create mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/.yarn-integrity create mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/index.js create mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/license create mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/package.json create mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/readme.md create mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/.yarn-integrity create mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/index.js create mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/license create mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/package.json create mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/readme.md diff --git a/package-lock.json b/package-lock.json index 97c8ac0a59..670b86161c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23708,9 +23708,9 @@ } }, "node_modules/zod": { - "version": "3.25.75", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.75.tgz", - "integrity": "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -23770,7 +23770,8 @@ "typescript": "^5.0.0", "uuid": "^11.0.0", "yaml": "^2.8.0", - "yargs": "^17.6.0" + "yargs": "^17.6.0", + "zod": "^3.25.76" }, "bin": { "netlify-build": "bin.js" diff --git a/packages/build/package.json b/packages/build/package.json index 0a52e5a506..ebc187f93a 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -115,7 +115,8 @@ "typescript": "^5.0.0", "uuid": "^11.0.0", "yaml": "^2.8.0", - "yargs": "^17.6.0" + "yargs": "^17.6.0", + "zod": "^3.25.76" }, "devDependencies": { "@netlify/nock-udp": "^5.0.1", diff --git a/packages/build/src/plugins_core/frameworks_api/index.ts b/packages/build/src/plugins_core/frameworks_api/index.ts index eead513884..d2219f9e9b 100644 --- a/packages/build/src/plugins_core/frameworks_api/index.ts +++ b/packages/build/src/plugins_core/frameworks_api/index.ts @@ -1,9 +1,14 @@ +import { promises as fs } from 'node:fs' +import { dirname, resolve } from 'node:path' + import { mergeConfigs } from '@netlify/config' import type { NetlifyConfig } from '../../index.js' import { getConfigMutations } from '../../plugins/child/diff.js' +import { EDGE_REDIRECTS_DIST_PATH, FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT } from '../../utils/frameworks_api.js' import { CoreStep, CoreStepFunction } from '../types.js' +import { loadSkewProtectionConfig } from './skew_protection.js' import { filterConfig, loadConfigFile } from './util.js' // The properties that can be set using this API. Each element represents a @@ -26,6 +31,30 @@ const ALLOWED_PROPERTIES = [ // a special notation where `redirects!` represents "forced redirects", etc. const OVERRIDE_PROPERTIES = new Set(['redirects!']) +// Looks for a skew protection configuration file. If found, the file is loaded +// and validated against the schema, throwing a build error if validation +// fails. If valid, the contents are written to the edge redirects file. +const handleSkewProtection = async (buildDir: string, packagePath?: string) => { + const inputPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT) + const outputPath = resolve(buildDir, packagePath ?? '', EDGE_REDIRECTS_DIST_PATH) + + const skewProtectionConfig = await loadSkewProtectionConfig(inputPath) + if (!skewProtectionConfig) { + return + } + + const edgeRedirects = { + skew_protection: skewProtectionConfig, + } + + try { + await fs.mkdir(dirname(outputPath), { recursive: true }) + await fs.writeFile(outputPath, JSON.stringify(edgeRedirects)) + } catch (error) { + throw new Error('Failed to process skew protection configuration', { cause: error }) + } +} + const coreStep: CoreStepFunction = async function ({ buildDir, netlifyConfig, @@ -34,6 +63,7 @@ const coreStep: CoreStepFunction = async function ({ // no-op }, }) { + await handleSkewProtection(buildDir, packagePath) let config: Partial | undefined try { diff --git a/packages/build/src/plugins_core/frameworks_api/skew_protection.ts b/packages/build/src/plugins_core/frameworks_api/skew_protection.ts new file mode 100644 index 0000000000..7c7c8c8060 --- /dev/null +++ b/packages/build/src/plugins_core/frameworks_api/skew_protection.ts @@ -0,0 +1,45 @@ +import { promises as fs } from 'node:fs' + +import { z } from 'zod' + +const deployIDSourceTypeSchema = z.enum(['cookie', 'header', 'query']) + +const deployIDSourceSchema = z.object({ + type: deployIDSourceTypeSchema, + name: z.string(), +}) + +const skewProtectionConfigSchema = z.object({ + patterns: z.array(z.string()), + sources: z.array(deployIDSourceSchema), +}) + +export type SkewProtectionConfig = z.infer +export type DeployIDSource = z.infer +export type DeployIDSourceType = z.infer + +const validateSkewProtectionConfig = (data: unknown): SkewProtectionConfig => { + try { + return skewProtectionConfigSchema.parse(data) + } catch (error) { + if (error instanceof z.ZodError) { + throw new Error(`Invalid skew protection configuration: ${error.message}`) + } + + throw error + } +} + +export const loadSkewProtectionConfig = async (configPath: string) => { + try { + const data = await fs.readFile(configPath, 'utf8') + const config = validateSkewProtectionConfig(JSON.parse(data)) + + return config + } catch (err) { + // If the file doesn't exist, this is a non-error. + if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { + throw err + } + } +} diff --git a/packages/build/src/utils/frameworks_api.ts b/packages/build/src/utils/frameworks_api.ts index af930bc79a..2873e22c5d 100644 --- a/packages/build/src/utils/frameworks_api.ts +++ b/packages/build/src/utils/frameworks_api.ts @@ -8,6 +8,9 @@ export const FRAMEWORKS_API_CONFIG_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/config export const FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/edge-functions` export const FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP = 'import_map.json' export const FRAMEWORKS_API_FUNCTIONS_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/functions` +export const FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/skew-protection.json` + +export const EDGE_REDIRECTS_DIST_PATH = '.netlify/deploy-config/edge-redirects.json' type DirectoryTreeFiles = Map diff --git a/packages/build/tests/frameworks_api/fixtures/skew_protection_invalid/build.mjs b/packages/build/tests/frameworks_api/fixtures/skew_protection_invalid/build.mjs new file mode 100644 index 0000000000..1a0265a4d6 --- /dev/null +++ b/packages/build/tests/frameworks_api/fixtures/skew_protection_invalid/build.mjs @@ -0,0 +1,8 @@ +import { mkdir, writeFile } from 'node:fs/promises' + +await mkdir('.netlify/v1', { recursive: true }) + +await writeFile('.netlify/v1/skew-protection.json', JSON.stringify({ + patterns: ["api"], + sources: [{ type: "invalid_type", name: "test" }] +})) \ No newline at end of file diff --git a/packages/build/tests/frameworks_api/fixtures/skew_protection_invalid/netlify.toml b/packages/build/tests/frameworks_api/fixtures/skew_protection_invalid/netlify.toml new file mode 100644 index 0000000000..5bc55bdb75 --- /dev/null +++ b/packages/build/tests/frameworks_api/fixtures/skew_protection_invalid/netlify.toml @@ -0,0 +1,2 @@ +[build] +command = "node build.mjs" \ No newline at end of file diff --git a/packages/build/tests/frameworks_api/fixtures/skew_protection_missing/build.mjs b/packages/build/tests/frameworks_api/fixtures/skew_protection_missing/build.mjs new file mode 100644 index 0000000000..d51b631677 --- /dev/null +++ b/packages/build/tests/frameworks_api/fixtures/skew_protection_missing/build.mjs @@ -0,0 +1 @@ +// No skew protection file created \ No newline at end of file diff --git a/packages/build/tests/frameworks_api/fixtures/skew_protection_missing/netlify.toml b/packages/build/tests/frameworks_api/fixtures/skew_protection_missing/netlify.toml new file mode 100644 index 0000000000..5bc55bdb75 --- /dev/null +++ b/packages/build/tests/frameworks_api/fixtures/skew_protection_missing/netlify.toml @@ -0,0 +1,2 @@ +[build] +command = "node build.mjs" \ No newline at end of file diff --git a/packages/build/tests/frameworks_api/fixtures/skew_protection_valid/build.mjs b/packages/build/tests/frameworks_api/fixtures/skew_protection_valid/build.mjs new file mode 100644 index 0000000000..e1da750bb7 --- /dev/null +++ b/packages/build/tests/frameworks_api/fixtures/skew_protection_valid/build.mjs @@ -0,0 +1,12 @@ +import { mkdir, writeFile } from 'node:fs/promises' + +await mkdir('.netlify/v1', { recursive: true }) + +await writeFile('.netlify/v1/skew-protection.json', JSON.stringify({ + patterns: ["/api/*", "/dashboard/*"], + sources: [ + { type: "cookie", name: "nf_deploy_id" }, + { type: "header", name: "x-nf-deploy-id" }, + { type: "query", name: "deploy_id" } + ] +})) \ No newline at end of file diff --git a/packages/build/tests/frameworks_api/fixtures/skew_protection_valid/netlify.toml b/packages/build/tests/frameworks_api/fixtures/skew_protection_valid/netlify.toml new file mode 100644 index 0000000000..5bc55bdb75 --- /dev/null +++ b/packages/build/tests/frameworks_api/fixtures/skew_protection_valid/netlify.toml @@ -0,0 +1,2 @@ +[build] +command = "node build.mjs" \ No newline at end of file diff --git a/packages/build/tests/frameworks_api/tests.js b/packages/build/tests/frameworks_api/tests.js index 3a3108001b..a5345c48c5 100644 --- a/packages/build/tests/frameworks_api/tests.js +++ b/packages/build/tests/frameworks_api/tests.js @@ -151,3 +151,46 @@ test('Removes any leftover files from a previous build', async (t) => { remote_images: ['domain1.from-toml.netlify', 'domain2.from-toml.netlify'], }) }) + +test('Throws an error if the skew protection configuration file is invalid', async (t) => { + const { output, success } = await new Fixture('./fixtures/skew_protection_invalid').runWithBuildAndIntrospect() + t.false(success) + t.true(output.includes('Invalid skew protection configuration')) +}) + +test('Does not create dist file when skew protection file is missing', async (t) => { + const fixture = new Fixture('./fixtures/skew_protection_missing') + const { success } = await fixture.runWithBuildAndIntrospect() + const distPath = resolve(fixture.repositoryRoot, '.netlify/deploy-config/edge-redirects.json') + + t.true(success) + + try { + await fs.access(distPath) + t.fail('Dist file should not exist when skew protection file is missing') + } catch (error) { + t.is(error.code, 'ENOENT') + } +}) + +test('Creates dist file when valid skew protection configuration is provided', async (t) => { + const fixture = new Fixture('./fixtures/skew_protection_valid') + const { success } = await fixture.runWithBuildAndIntrospect() + const distPath = resolve(fixture.repositoryRoot, '.netlify/deploy-config/edge-redirects.json') + + t.true(success) + + const distContent = await fs.readFile(distPath, 'utf8') + const config = JSON.parse(distContent) + + t.deepEqual(config, { + skew_protection: { + patterns: ['/api/*', '/dashboard/*'], + sources: [ + { type: 'cookie', name: 'nf_deploy_id' }, + { type: 'header', name: 'x-nf-deploy-id' }, + { type: 'query', name: 'deploy_id' }, + ], + }, + }) +}) diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/.yarn-integrity b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/.yarn-integrity new file mode 100644 index 0000000000..aea2886657 --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/.yarn-integrity @@ -0,0 +1,28 @@ +{ + "systemParams": "darwin-arm64-127", + "modulesFolders": [ + "node_modules" + ], + "flags": [], + "linkedModules": [ + "@netlify/blobs", + "@netlify/build", + "@netlify/config", + "@netlify/dev", + "@netlify/edge-bundler", + "@netlify/edge-functions", + "@netlify/serverless-functions-api", + "@netlify/vite-plugin", + "@netlify/zip-it-and-ship-it", + "netlify", + "netlify-cli" + ], + "topLevelPatterns": [ + "math-avg@^1.0.0" + ], + "lockfileEntries": { + "math-avg@^1.0.0": "https://registry.yarnpkg.com/math-avg/-/math-avg-1.0.0.tgz#a78d31c58b2b951078d51124440f7d81b6cd8460" + }, + "files": [], + "artifacts": {} +} \ No newline at end of file diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/index.js b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/index.js new file mode 100644 index 0000000000..59367cef66 --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/index.js @@ -0,0 +1,13 @@ +'use strict'; +module.exports = function (x) { + x = Array.isArray(x) ? x : arguments; + + var sum = 0; + var len = x.length; + + for (var i = 0; i < len; i++) { + sum += x[i]; + } + + return sum / len; +}; diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/license b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/license new file mode 100644 index 0000000000..654d0bfe94 --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/package.json b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/package.json new file mode 100644 index 0000000000..a638225dba --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/package.json @@ -0,0 +1,35 @@ +{ + "name": "math-avg", + "version": "1.0.0", + "description": "Get the average of numbers", + "license": "MIT", + "repository": "sindresorhus/math-avg", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "node test.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "math", + "avg", + "average", + "number", + "numbers", + "num", + "add", + "count", + "array" + ], + "devDependencies": { + "ava": "0.0.4" + } +} diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/readme.md b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/readme.md new file mode 100644 index 0000000000..d8a943c6ce --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/readme.md @@ -0,0 +1,33 @@ +# math-avg [![Build Status](https://travis-ci.org/sindresorhus/math-avg.svg?branch=master)](https://travis-ci.org/sindresorhus/math-avg) + +> Get the average of numbers + + +## Install + +``` +$ npm install --save math-avg +``` + + +## Usage + +```js +var mathAvg = require('math-avg'); + +mathAvg(1, 3); +//=> 2 + +mathAvg([1, 3]); +//=> 2 +``` + + +## Related + +- [math-sum](https://github.com/sindresorhus/math-sum) - Sum numbers + + +## License + +MIT © [Sindre Sorhus](http://sindresorhus.com) diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/.yarn-integrity b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/.yarn-integrity new file mode 100644 index 0000000000..aea2886657 --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/.yarn-integrity @@ -0,0 +1,28 @@ +{ + "systemParams": "darwin-arm64-127", + "modulesFolders": [ + "node_modules" + ], + "flags": [], + "linkedModules": [ + "@netlify/blobs", + "@netlify/build", + "@netlify/config", + "@netlify/dev", + "@netlify/edge-bundler", + "@netlify/edge-functions", + "@netlify/serverless-functions-api", + "@netlify/vite-plugin", + "@netlify/zip-it-and-ship-it", + "netlify", + "netlify-cli" + ], + "topLevelPatterns": [ + "math-avg@^1.0.0" + ], + "lockfileEntries": { + "math-avg@^1.0.0": "https://registry.yarnpkg.com/math-avg/-/math-avg-1.0.0.tgz#a78d31c58b2b951078d51124440f7d81b6cd8460" + }, + "files": [], + "artifacts": {} +} \ No newline at end of file diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/index.js b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/index.js new file mode 100644 index 0000000000..59367cef66 --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/index.js @@ -0,0 +1,13 @@ +'use strict'; +module.exports = function (x) { + x = Array.isArray(x) ? x : arguments; + + var sum = 0; + var len = x.length; + + for (var i = 0; i < len; i++) { + sum += x[i]; + } + + return sum / len; +}; diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/license b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/license new file mode 100644 index 0000000000..654d0bfe94 --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/package.json b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/package.json new file mode 100644 index 0000000000..a638225dba --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/package.json @@ -0,0 +1,35 @@ +{ + "name": "math-avg", + "version": "1.0.0", + "description": "Get the average of numbers", + "license": "MIT", + "repository": "sindresorhus/math-avg", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "node test.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "math", + "avg", + "average", + "number", + "numbers", + "num", + "add", + "count", + "array" + ], + "devDependencies": { + "ava": "0.0.4" + } +} diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/readme.md b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/readme.md new file mode 100644 index 0000000000..d8a943c6ce --- /dev/null +++ b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/readme.md @@ -0,0 +1,33 @@ +# math-avg [![Build Status](https://travis-ci.org/sindresorhus/math-avg.svg?branch=master)](https://travis-ci.org/sindresorhus/math-avg) + +> Get the average of numbers + + +## Install + +``` +$ npm install --save math-avg +``` + + +## Usage + +```js +var mathAvg = require('math-avg'); + +mathAvg(1, 3); +//=> 2 + +mathAvg([1, 3]); +//=> 2 +``` + + +## Related + +- [math-sum](https://github.com/sindresorhus/math-sum) - Sum numbers + + +## License + +MIT © [Sindre Sorhus](http://sindresorhus.com) From 1b69150cda314e798c7be35d29a3fb0f98179c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Sun, 3 Aug 2025 11:20:59 +0100 Subject: [PATCH 2/6] chore: remove extraneous files --- .../yarn/plugin/node_modules/.yarn-integrity | 28 --------------- .../plugin/node_modules/math-avg/index.js | 13 ------- .../yarn/plugin/node_modules/math-avg/license | 21 ----------- .../plugin/node_modules/math-avg/package.json | 35 ------------------- .../plugin/node_modules/math-avg/readme.md | 33 ----------------- .../plugin/node_modules/.yarn-integrity | 28 --------------- .../plugin/node_modules/math-avg/index.js | 13 ------- .../plugin/node_modules/math-avg/license | 21 ----------- .../plugin/node_modules/math-avg/package.json | 35 ------------------- .../plugin/node_modules/math-avg/readme.md | 33 ----------------- 10 files changed, 260 deletions(-) delete mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/.yarn-integrity delete mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/index.js delete mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/license delete mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/package.json delete mode 100644 packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/readme.md delete mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/.yarn-integrity delete mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/index.js delete mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/license delete mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/package.json delete mode 100644 packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/readme.md diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/.yarn-integrity b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/.yarn-integrity deleted file mode 100644 index aea2886657..0000000000 --- a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/.yarn-integrity +++ /dev/null @@ -1,28 +0,0 @@ -{ - "systemParams": "darwin-arm64-127", - "modulesFolders": [ - "node_modules" - ], - "flags": [], - "linkedModules": [ - "@netlify/blobs", - "@netlify/build", - "@netlify/config", - "@netlify/dev", - "@netlify/edge-bundler", - "@netlify/edge-functions", - "@netlify/serverless-functions-api", - "@netlify/vite-plugin", - "@netlify/zip-it-and-ship-it", - "netlify", - "netlify-cli" - ], - "topLevelPatterns": [ - "math-avg@^1.0.0" - ], - "lockfileEntries": { - "math-avg@^1.0.0": "https://registry.yarnpkg.com/math-avg/-/math-avg-1.0.0.tgz#a78d31c58b2b951078d51124440f7d81b6cd8460" - }, - "files": [], - "artifacts": {} -} \ No newline at end of file diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/index.js b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/index.js deleted file mode 100644 index 59367cef66..0000000000 --- a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/index.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; -module.exports = function (x) { - x = Array.isArray(x) ? x : arguments; - - var sum = 0; - var len = x.length; - - for (var i = 0; i < len; i++) { - sum += x[i]; - } - - return sum / len; -}; diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/license b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/license deleted file mode 100644 index 654d0bfe94..0000000000 --- a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/license +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/package.json b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/package.json deleted file mode 100644 index a638225dba..0000000000 --- a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "math-avg", - "version": "1.0.0", - "description": "Get the average of numbers", - "license": "MIT", - "repository": "sindresorhus/math-avg", - "author": { - "name": "Sindre Sorhus", - "email": "sindresorhus@gmail.com", - "url": "sindresorhus.com" - }, - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "node test.js" - }, - "files": [ - "index.js" - ], - "keywords": [ - "math", - "avg", - "average", - "number", - "numbers", - "num", - "add", - "count", - "array" - ], - "devDependencies": { - "ava": "0.0.4" - } -} diff --git a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/readme.md b/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/readme.md deleted file mode 100644 index d8a943c6ce..0000000000 --- a/packages/build/tests/install/fixtures/yarn/plugin/node_modules/math-avg/readme.md +++ /dev/null @@ -1,33 +0,0 @@ -# math-avg [![Build Status](https://travis-ci.org/sindresorhus/math-avg.svg?branch=master)](https://travis-ci.org/sindresorhus/math-avg) - -> Get the average of numbers - - -## Install - -``` -$ npm install --save math-avg -``` - - -## Usage - -```js -var mathAvg = require('math-avg'); - -mathAvg(1, 3); -//=> 2 - -mathAvg([1, 3]); -//=> 2 -``` - - -## Related - -- [math-sum](https://github.com/sindresorhus/math-sum) - Sum numbers - - -## License - -MIT © [Sindre Sorhus](http://sindresorhus.com) diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/.yarn-integrity b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/.yarn-integrity deleted file mode 100644 index aea2886657..0000000000 --- a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/.yarn-integrity +++ /dev/null @@ -1,28 +0,0 @@ -{ - "systemParams": "darwin-arm64-127", - "modulesFolders": [ - "node_modules" - ], - "flags": [], - "linkedModules": [ - "@netlify/blobs", - "@netlify/build", - "@netlify/config", - "@netlify/dev", - "@netlify/edge-bundler", - "@netlify/edge-functions", - "@netlify/serverless-functions-api", - "@netlify/vite-plugin", - "@netlify/zip-it-and-ship-it", - "netlify", - "netlify-cli" - ], - "topLevelPatterns": [ - "math-avg@^1.0.0" - ], - "lockfileEntries": { - "math-avg@^1.0.0": "https://registry.yarnpkg.com/math-avg/-/math-avg-1.0.0.tgz#a78d31c58b2b951078d51124440f7d81b6cd8460" - }, - "files": [], - "artifacts": {} -} \ No newline at end of file diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/index.js b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/index.js deleted file mode 100644 index 59367cef66..0000000000 --- a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/index.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; -module.exports = function (x) { - x = Array.isArray(x) ? x : arguments; - - var sum = 0; - var len = x.length; - - for (var i = 0; i < len; i++) { - sum += x[i]; - } - - return sum / len; -}; diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/license b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/license deleted file mode 100644 index 654d0bfe94..0000000000 --- a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/license +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/package.json b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/package.json deleted file mode 100644 index a638225dba..0000000000 --- a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "math-avg", - "version": "1.0.0", - "description": "Get the average of numbers", - "license": "MIT", - "repository": "sindresorhus/math-avg", - "author": { - "name": "Sindre Sorhus", - "email": "sindresorhus@gmail.com", - "url": "sindresorhus.com" - }, - "engines": { - "node": ">=0.10.0" - }, - "scripts": { - "test": "node test.js" - }, - "files": [ - "index.js" - ], - "keywords": [ - "math", - "avg", - "average", - "number", - "numbers", - "num", - "add", - "count", - "array" - ], - "devDependencies": { - "ava": "0.0.4" - } -} diff --git a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/readme.md b/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/readme.md deleted file mode 100644 index d8a943c6ce..0000000000 --- a/packages/build/tests/install/fixtures/yarn_ci/plugin/node_modules/math-avg/readme.md +++ /dev/null @@ -1,33 +0,0 @@ -# math-avg [![Build Status](https://travis-ci.org/sindresorhus/math-avg.svg?branch=master)](https://travis-ci.org/sindresorhus/math-avg) - -> Get the average of numbers - - -## Install - -``` -$ npm install --save math-avg -``` - - -## Usage - -```js -var mathAvg = require('math-avg'); - -mathAvg(1, 3); -//=> 2 - -mathAvg([1, 3]); -//=> 2 -``` - - -## Related - -- [math-sum](https://github.com/sindresorhus/math-sum) - Sum numbers - - -## License - -MIT © [Sindre Sorhus](http://sindresorhus.com) From cadd32d2e8bebac9091929aaeedea1d591b87e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 14 Aug 2025 10:16:02 +0100 Subject: [PATCH 3/6] refactor: use deploy config manifest --- packages/build/src/plugins_core/frameworks_api/index.ts | 4 ++-- packages/build/src/utils/frameworks_api.ts | 2 +- packages/build/tests/frameworks_api/tests.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/build/src/plugins_core/frameworks_api/index.ts b/packages/build/src/plugins_core/frameworks_api/index.ts index d2219f9e9b..53cff442c8 100644 --- a/packages/build/src/plugins_core/frameworks_api/index.ts +++ b/packages/build/src/plugins_core/frameworks_api/index.ts @@ -5,7 +5,7 @@ import { mergeConfigs } from '@netlify/config' import type { NetlifyConfig } from '../../index.js' import { getConfigMutations } from '../../plugins/child/diff.js' -import { EDGE_REDIRECTS_DIST_PATH, FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT } from '../../utils/frameworks_api.js' +import { DEPLOY_CONFIG_DIST_PATH, FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT } from '../../utils/frameworks_api.js' import { CoreStep, CoreStepFunction } from '../types.js' import { loadSkewProtectionConfig } from './skew_protection.js' @@ -36,7 +36,7 @@ const OVERRIDE_PROPERTIES = new Set(['redirects!']) // fails. If valid, the contents are written to the edge redirects file. const handleSkewProtection = async (buildDir: string, packagePath?: string) => { const inputPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT) - const outputPath = resolve(buildDir, packagePath ?? '', EDGE_REDIRECTS_DIST_PATH) + const outputPath = resolve(buildDir, packagePath ?? '', DEPLOY_CONFIG_DIST_PATH) const skewProtectionConfig = await loadSkewProtectionConfig(inputPath) if (!skewProtectionConfig) { diff --git a/packages/build/src/utils/frameworks_api.ts b/packages/build/src/utils/frameworks_api.ts index 2873e22c5d..58f1d64fd0 100644 --- a/packages/build/src/utils/frameworks_api.ts +++ b/packages/build/src/utils/frameworks_api.ts @@ -10,7 +10,7 @@ export const FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP = 'import_map.json' export const FRAMEWORKS_API_FUNCTIONS_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/functions` export const FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/skew-protection.json` -export const EDGE_REDIRECTS_DIST_PATH = '.netlify/deploy-config/edge-redirects.json' +export const DEPLOY_CONFIG_DIST_PATH = '.netlify/deploy-config/deploy-config.json' type DirectoryTreeFiles = Map diff --git a/packages/build/tests/frameworks_api/tests.js b/packages/build/tests/frameworks_api/tests.js index a5345c48c5..5bb4d986ce 100644 --- a/packages/build/tests/frameworks_api/tests.js +++ b/packages/build/tests/frameworks_api/tests.js @@ -161,7 +161,7 @@ test('Throws an error if the skew protection configuration file is invalid', asy test('Does not create dist file when skew protection file is missing', async (t) => { const fixture = new Fixture('./fixtures/skew_protection_missing') const { success } = await fixture.runWithBuildAndIntrospect() - const distPath = resolve(fixture.repositoryRoot, '.netlify/deploy-config/edge-redirects.json') + const distPath = resolve(fixture.repositoryRoot, '.netlify/deploy-config/deploy-config.json') t.true(success) @@ -176,7 +176,7 @@ test('Does not create dist file when skew protection file is missing', async (t) test('Creates dist file when valid skew protection configuration is provided', async (t) => { const fixture = new Fixture('./fixtures/skew_protection_valid') const { success } = await fixture.runWithBuildAndIntrospect() - const distPath = resolve(fixture.repositoryRoot, '.netlify/deploy-config/edge-redirects.json') + const distPath = resolve(fixture.repositoryRoot, '.netlify/deploy-config/deploy-config.json') t.true(success) From 687a92c7a357e7d43fa5b9440588c43126748cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 14 Aug 2025 10:16:57 +0100 Subject: [PATCH 4/6] refactor: rename consts --- .../build/src/plugins_core/edge_functions/index.ts | 8 ++++---- .../build/src/plugins_core/frameworks_api/index.ts | 4 ++-- .../build/src/plugins_core/frameworks_api/util.ts | 4 ++-- packages/build/src/plugins_core/functions/index.ts | 6 +++--- packages/build/src/plugins_core/pre_cleanup/index.ts | 4 ++-- packages/build/src/utils/blobs.ts | 4 ++-- packages/build/src/utils/frameworks_api.ts | 12 ++++++------ 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/build/src/plugins_core/edge_functions/index.ts b/packages/build/src/plugins_core/edge_functions/index.ts index 46884c0959..e28bcbec21 100644 --- a/packages/build/src/plugins_core/edge_functions/index.ts +++ b/packages/build/src/plugins_core/edge_functions/index.ts @@ -8,7 +8,7 @@ import { Metric } from '../../core/report_metrics.js' import { log, reduceLogLines } from '../../log/logger.js' import { logFunctionsToBundle } from '../../log/messages/core_steps.js' import { - FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT, + FRAMEWORKS_API_EDGE_FUNCTIONS_PATH, FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP, } from '../../utils/frameworks_api.js' @@ -52,7 +52,7 @@ const coreStep = async function ({ const internalSrcPath = resolve(buildDir, internalSrcDirectory) const distImportMapPath = join(dirname(internalSrcPath), IMPORT_MAP_FILENAME) const srcPath = srcDirectory ? resolve(buildDir, srcDirectory) : undefined - const frameworksAPISrcPath = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT) + const frameworksAPISrcPath = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_PATH) const generatedFunctionPaths = [internalSrcPath] if (await pathExists(frameworksAPISrcPath)) { @@ -62,7 +62,7 @@ const coreStep = async function ({ const frameworkImportMap = resolve( buildDir, packagePath || '', - FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT, + FRAMEWORKS_API_EDGE_FUNCTIONS_PATH, FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP, ) @@ -170,7 +170,7 @@ const hasEdgeFunctionsDirectories = async function ({ return true } - const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT) + const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_PATH) return await pathExists(frameworkFunctionsSrc) } diff --git a/packages/build/src/plugins_core/frameworks_api/index.ts b/packages/build/src/plugins_core/frameworks_api/index.ts index 53cff442c8..9310091d8a 100644 --- a/packages/build/src/plugins_core/frameworks_api/index.ts +++ b/packages/build/src/plugins_core/frameworks_api/index.ts @@ -5,7 +5,7 @@ import { mergeConfigs } from '@netlify/config' import type { NetlifyConfig } from '../../index.js' import { getConfigMutations } from '../../plugins/child/diff.js' -import { DEPLOY_CONFIG_DIST_PATH, FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT } from '../../utils/frameworks_api.js' +import { DEPLOY_CONFIG_DIST_PATH, FRAMEWORKS_API_SKEW_PROTECTION_PATH } from '../../utils/frameworks_api.js' import { CoreStep, CoreStepFunction } from '../types.js' import { loadSkewProtectionConfig } from './skew_protection.js' @@ -35,7 +35,7 @@ const OVERRIDE_PROPERTIES = new Set(['redirects!']) // and validated against the schema, throwing a build error if validation // fails. If valid, the contents are written to the edge redirects file. const handleSkewProtection = async (buildDir: string, packagePath?: string) => { - const inputPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT) + const inputPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_SKEW_PROTECTION_PATH) const outputPath = resolve(buildDir, packagePath ?? '', DEPLOY_CONFIG_DIST_PATH) const skewProtectionConfig = await loadSkewProtectionConfig(inputPath) diff --git a/packages/build/src/plugins_core/frameworks_api/util.ts b/packages/build/src/plugins_core/frameworks_api/util.ts index 860c5d7a7d..d2bb69ba97 100644 --- a/packages/build/src/plugins_core/frameworks_api/util.ts +++ b/packages/build/src/plugins_core/frameworks_api/util.ts @@ -4,11 +4,11 @@ import { resolve } from 'path' import isPlainObject from 'is-plain-obj' import type { NetlifyConfig } from '../../index.js' -import { FRAMEWORKS_API_CONFIG_ENDPOINT } from '../../utils/frameworks_api.js' +import { FRAMEWORKS_API_CONFIG_PATH } from '../../utils/frameworks_api.js' import { SystemLogger } from '../types.js' export const loadConfigFile = async (buildDir: string, packagePath?: string) => { - const configPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_CONFIG_ENDPOINT) + const configPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_CONFIG_PATH) try { const data = await fs.readFile(configPath, 'utf8') diff --git a/packages/build/src/plugins_core/functions/index.ts b/packages/build/src/plugins_core/functions/index.ts index c502d0c5af..34f80ebe6f 100644 --- a/packages/build/src/plugins_core/functions/index.ts +++ b/packages/build/src/plugins_core/functions/index.ts @@ -7,7 +7,7 @@ import { addErrorInfo } from '../../error/info.js' import { log } from '../../log/logger.js' import { type GeneratedFunction, getGeneratedFunctions } from '../../steps/return_values.js' import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle } from '../../log/messages/core_steps.js' -import { FRAMEWORKS_API_FUNCTIONS_ENDPOINT } from '../../utils/frameworks_api.js' +import { FRAMEWORKS_API_FUNCTIONS_PATH } from '../../utils/frameworks_api.js' import { getZipError } from './error.js' import { getUserAndInternalFunctions, validateFunctionsSrc } from './utils.js' @@ -147,7 +147,7 @@ const coreStep = async function ({ const functionsDist = resolve(buildDir, relativeFunctionsDist) const internalFunctionsSrc = resolve(buildDir, relativeInternalFunctionsSrc) const internalFunctionsSrcExists = await pathExists(internalFunctionsSrc) - const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_ENDPOINT) + const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_PATH) const frameworkFunctionsSrcExists = await pathExists(frameworkFunctionsSrc) const functionsSrcExists = await validateFunctionsSrc({ functionsSrc, relativeFunctionsSrc }) const [userFunctions = [], internalFunctions = [], frameworkFunctions = []] = await getUserAndInternalFunctions({ @@ -240,7 +240,7 @@ const hasFunctionsDirectories = async function ({ return true } - const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_ENDPOINT) + const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_PATH) if (await pathExists(frameworkFunctionsSrc)) { return true diff --git a/packages/build/src/plugins_core/pre_cleanup/index.ts b/packages/build/src/plugins_core/pre_cleanup/index.ts index 3fa1035c7f..61d5ed3769 100644 --- a/packages/build/src/plugins_core/pre_cleanup/index.ts +++ b/packages/build/src/plugins_core/pre_cleanup/index.ts @@ -2,11 +2,11 @@ import { rm } from 'node:fs/promises' import { resolve } from 'node:path' import { getBlobsDirs } from '../../utils/blobs.js' -import { FRAMEWORKS_API_ENDPOINT } from '../../utils/frameworks_api.js' +import { FRAMEWORKS_API_PATH } from '../../utils/frameworks_api.js' import { CoreStep, CoreStepFunction } from '../types.js' const coreStep: CoreStepFunction = async ({ buildDir, packagePath }) => { - const dirs = [...getBlobsDirs(buildDir, packagePath), resolve(buildDir, packagePath || '', FRAMEWORKS_API_ENDPOINT)] + const dirs = [...getBlobsDirs(buildDir, packagePath), resolve(buildDir, packagePath || '', FRAMEWORKS_API_PATH)] try { await Promise.all(dirs.map((dir) => rm(dir, { recursive: true, force: true }))) diff --git a/packages/build/src/utils/blobs.ts b/packages/build/src/utils/blobs.ts index 5b9a38f017..73a0494fcb 100644 --- a/packages/build/src/utils/blobs.ts +++ b/packages/build/src/utils/blobs.ts @@ -5,7 +5,7 @@ import { fdir } from 'fdir' import { DEFAULT_API_HOST } from '../core/normalize_flags.js' -import { FRAMEWORKS_API_BLOBS_ENDPOINT } from './frameworks_api.js' +import { FRAMEWORKS_API_BLOBS_PATH } from './frameworks_api.js' const LEGACY_BLOBS_PATH = '.netlify/blobs/deploy' const DEPLOY_CONFIG_BLOBS_PATH = '.netlify/deploy/v1/blobs/deploy' @@ -60,7 +60,7 @@ export const getBlobsEnvironmentContext = ({ */ export const scanForBlobs = async function (buildDir: string, packagePath?: string) { // We start by looking for files using the Frameworks API. - const frameworkBlobsDir = path.resolve(buildDir, packagePath || '', FRAMEWORKS_API_BLOBS_ENDPOINT, 'deploy') + const frameworkBlobsDir = path.resolve(buildDir, packagePath || '', FRAMEWORKS_API_BLOBS_PATH, 'deploy') const frameworkBlobsDirScan = await new fdir().onlyCounts().crawl(frameworkBlobsDir).withPromise() if (frameworkBlobsDirScan.files > 0) { diff --git a/packages/build/src/utils/frameworks_api.ts b/packages/build/src/utils/frameworks_api.ts index 58f1d64fd0..2da37a4466 100644 --- a/packages/build/src/utils/frameworks_api.ts +++ b/packages/build/src/utils/frameworks_api.ts @@ -2,13 +2,13 @@ import { basename, dirname, resolve, sep } from 'node:path' import { fdir } from 'fdir' -export const FRAMEWORKS_API_ENDPOINT = '.netlify/v1' -export const FRAMEWORKS_API_BLOBS_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/blobs` -export const FRAMEWORKS_API_CONFIG_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/config.json` -export const FRAMEWORKS_API_EDGE_FUNCTIONS_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/edge-functions` +export const FRAMEWORKS_API_PATH = '.netlify/v1' +export const FRAMEWORKS_API_BLOBS_PATH = `${FRAMEWORKS_API_PATH}/blobs` +export const FRAMEWORKS_API_CONFIG_PATH = `${FRAMEWORKS_API_PATH}/config.json` +export const FRAMEWORKS_API_EDGE_FUNCTIONS_PATH = `${FRAMEWORKS_API_PATH}/edge-functions` export const FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP = 'import_map.json' -export const FRAMEWORKS_API_FUNCTIONS_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/functions` -export const FRAMEWORKS_API_SKEW_PROTECTION_ENDPOINT = `${FRAMEWORKS_API_ENDPOINT}/skew-protection.json` +export const FRAMEWORKS_API_FUNCTIONS_PATH = `${FRAMEWORKS_API_PATH}/functions` +export const FRAMEWORKS_API_SKEW_PROTECTION_PATH = `${FRAMEWORKS_API_PATH}/skew-protection.json` export const DEPLOY_CONFIG_DIST_PATH = '.netlify/deploy-config/deploy-config.json' From a4f84c06703cde4235273293d3b22c87447f47d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 14 Aug 2025 10:51:35 +0100 Subject: [PATCH 5/6] refactor: update validation --- .../frameworks_api/skew_protection.ts | 35 ++++++++++++------- .../skew_protection_malformed/build.mjs | 5 +++ .../skew_protection_malformed/netlify.toml | 2 ++ packages/build/tests/frameworks_api/tests.js | 12 ++++++- 4 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 packages/build/tests/frameworks_api/fixtures/skew_protection_malformed/build.mjs create mode 100644 packages/build/tests/frameworks_api/fixtures/skew_protection_malformed/netlify.toml diff --git a/packages/build/src/plugins_core/frameworks_api/skew_protection.ts b/packages/build/src/plugins_core/frameworks_api/skew_protection.ts index 7c7c8c8060..09003a1275 100644 --- a/packages/build/src/plugins_core/frameworks_api/skew_protection.ts +++ b/packages/build/src/plugins_core/frameworks_api/skew_protection.ts @@ -18,28 +18,37 @@ export type SkewProtectionConfig = z.infer export type DeployIDSource = z.infer export type DeployIDSourceType = z.infer -const validateSkewProtectionConfig = (data: unknown): SkewProtectionConfig => { - try { - return skewProtectionConfigSchema.parse(data) - } catch (error) { - if (error instanceof z.ZodError) { - throw new Error(`Invalid skew protection configuration: ${error.message}`) - } +const validateSkewProtectionConfig = (input: unknown): SkewProtectionConfig => { + const { data, error, success } = skewProtectionConfigSchema.safeParse(input) - throw error + if (success) { + return data } + + throw new Error(`Invalid skew protection configuration:\n\n${formatSchemaError(error)}`) } export const loadSkewProtectionConfig = async (configPath: string) => { + let parsedData: unknown + try { const data = await fs.readFile(configPath, 'utf8') - const config = validateSkewProtectionConfig(JSON.parse(data)) - return config - } catch (err) { + parsedData = JSON.parse(data) + } catch (error) { // If the file doesn't exist, this is a non-error. - if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { - throw err + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return } + + throw new Error('Invalid skew protection configuration', { cause: error }) } + + return validateSkewProtectionConfig(parsedData) +} + +const formatSchemaError = (error: z.ZodError) => { + const lines = error.issues.map((issue) => `- ${issue.path.join('.')}: ${issue.message}`) + + return lines.join('\n') } diff --git a/packages/build/tests/frameworks_api/fixtures/skew_protection_malformed/build.mjs b/packages/build/tests/frameworks_api/fixtures/skew_protection_malformed/build.mjs new file mode 100644 index 0000000000..2a01946465 --- /dev/null +++ b/packages/build/tests/frameworks_api/fixtures/skew_protection_malformed/build.mjs @@ -0,0 +1,5 @@ +import { mkdir, writeFile } from 'node:fs/promises' + +await mkdir('.netlify/v1', { recursive: true }) + +await writeFile('.netlify/v1/skew-protection.json', `{"patterns":`) \ No newline at end of file diff --git a/packages/build/tests/frameworks_api/fixtures/skew_protection_malformed/netlify.toml b/packages/build/tests/frameworks_api/fixtures/skew_protection_malformed/netlify.toml new file mode 100644 index 0000000000..5bc55bdb75 --- /dev/null +++ b/packages/build/tests/frameworks_api/fixtures/skew_protection_malformed/netlify.toml @@ -0,0 +1,2 @@ +[build] +command = "node build.mjs" \ No newline at end of file diff --git a/packages/build/tests/frameworks_api/tests.js b/packages/build/tests/frameworks_api/tests.js index 5bb4d986ce..09b27b61be 100644 --- a/packages/build/tests/frameworks_api/tests.js +++ b/packages/build/tests/frameworks_api/tests.js @@ -156,13 +156,23 @@ test('Throws an error if the skew protection configuration file is invalid', asy const { output, success } = await new Fixture('./fixtures/skew_protection_invalid').runWithBuildAndIntrospect() t.false(success) t.true(output.includes('Invalid skew protection configuration')) + t.true( + output.includes( + `sources.0.type: Invalid enum value. Expected 'cookie' | 'header' | 'query', received 'invalid_type'`, + ), + ) +}) + +test('Throws an error if the skew protection configuration file is malformed', async (t) => { + const { output, success } = await new Fixture('./fixtures/skew_protection_malformed').runWithBuildAndIntrospect() + t.false(success) + t.true(output.includes('Invalid skew protection configuration')) }) test('Does not create dist file when skew protection file is missing', async (t) => { const fixture = new Fixture('./fixtures/skew_protection_missing') const { success } = await fixture.runWithBuildAndIntrospect() const distPath = resolve(fixture.repositoryRoot, '.netlify/deploy-config/deploy-config.json') - t.true(success) try { From eba572b09516c8b0fb7c6571522f6894c4c01f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Bou=C3=A7as?= Date: Thu, 14 Aug 2025 13:48:23 +0100 Subject: [PATCH 6/6] refactor: update names --- packages/build/src/plugins_core/frameworks_api/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/build/src/plugins_core/frameworks_api/index.ts b/packages/build/src/plugins_core/frameworks_api/index.ts index 9310091d8a..b4ae6a2f11 100644 --- a/packages/build/src/plugins_core/frameworks_api/index.ts +++ b/packages/build/src/plugins_core/frameworks_api/index.ts @@ -33,7 +33,7 @@ const OVERRIDE_PROPERTIES = new Set(['redirects!']) // Looks for a skew protection configuration file. If found, the file is loaded // and validated against the schema, throwing a build error if validation -// fails. If valid, the contents are written to the edge redirects file. +// fails. If valid, the contents are written to the deploy config file. const handleSkewProtection = async (buildDir: string, packagePath?: string) => { const inputPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_SKEW_PROTECTION_PATH) const outputPath = resolve(buildDir, packagePath ?? '', DEPLOY_CONFIG_DIST_PATH) @@ -43,13 +43,13 @@ const handleSkewProtection = async (buildDir: string, packagePath?: string) => { return } - const edgeRedirects = { + const deployConfig = { skew_protection: skewProtectionConfig, } try { await fs.mkdir(dirname(outputPath), { recursive: true }) - await fs.writeFile(outputPath, JSON.stringify(edgeRedirects)) + await fs.writeFile(outputPath, JSON.stringify(deployConfig)) } catch (error) { throw new Error('Failed to process skew protection configuration', { cause: error }) }