@netlify/build 35.0.7 → 35.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/plugins_core/edge_functions/index.js +4 -4
- package/lib/plugins_core/frameworks_api/index.js +26 -0
- package/lib/plugins_core/frameworks_api/skew_protection.d.ts +48 -0
- package/lib/plugins_core/frameworks_api/skew_protection.js +37 -0
- package/lib/plugins_core/frameworks_api/util.js +2 -2
- package/lib/plugins_core/functions/index.js +3 -3
- package/lib/plugins_core/pre_cleanup/index.js +2 -2
- package/lib/utils/blobs.js +2 -2
- package/lib/utils/frameworks_api.d.ts +7 -5
- package/lib/utils/frameworks_api.js +7 -5
- package/package.json +5 -4
|
@@ -4,7 +4,7 @@ import { bundle, find } from '@netlify/edge-bundler';
|
|
|
4
4
|
import { pathExists } from 'path-exists';
|
|
5
5
|
import { log, reduceLogLines } from '../../log/logger.js';
|
|
6
6
|
import { logFunctionsToBundle } from '../../log/messages/core_steps.js';
|
|
7
|
-
import {
|
|
7
|
+
import { FRAMEWORKS_API_EDGE_FUNCTIONS_PATH, FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP, } from '../../utils/frameworks_api.js';
|
|
8
8
|
import { tagBundlingError } from './lib/error.js';
|
|
9
9
|
import { validateEdgeFunctionsManifest } from './validate_manifest/validate_edge_functions_manifest.js';
|
|
10
10
|
// TODO: Replace this with a custom cache directory.
|
|
@@ -18,12 +18,12 @@ const coreStep = async function ({ buildDir, packagePath, constants: { EDGE_FUNC
|
|
|
18
18
|
const internalSrcPath = resolve(buildDir, internalSrcDirectory);
|
|
19
19
|
const distImportMapPath = join(dirname(internalSrcPath), IMPORT_MAP_FILENAME);
|
|
20
20
|
const srcPath = srcDirectory ? resolve(buildDir, srcDirectory) : undefined;
|
|
21
|
-
const frameworksAPISrcPath = resolve(buildDir, packagePath || '',
|
|
21
|
+
const frameworksAPISrcPath = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_PATH);
|
|
22
22
|
const generatedFunctionPaths = [internalSrcPath];
|
|
23
23
|
if (await pathExists(frameworksAPISrcPath)) {
|
|
24
24
|
generatedFunctionPaths.push(frameworksAPISrcPath);
|
|
25
25
|
}
|
|
26
|
-
const frameworkImportMap = resolve(buildDir, packagePath || '',
|
|
26
|
+
const frameworkImportMap = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_PATH, FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP);
|
|
27
27
|
if (await pathExists(frameworkImportMap)) {
|
|
28
28
|
importMapPaths.push(frameworkImportMap);
|
|
29
29
|
}
|
|
@@ -104,7 +104,7 @@ const hasEdgeFunctionsDirectories = async function ({ buildDir, constants: { INT
|
|
|
104
104
|
if (await pathExists(internalFunctionsSrc)) {
|
|
105
105
|
return true;
|
|
106
106
|
}
|
|
107
|
-
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '',
|
|
107
|
+
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_EDGE_FUNCTIONS_PATH);
|
|
108
108
|
return await pathExists(frameworkFunctionsSrc);
|
|
109
109
|
};
|
|
110
110
|
const logFunctions = async ({ frameworksAPISrcPath, internalSrcDirectory, internalSrcPath, logs, srcDirectory: userFunctionsSrc, srcPath, }) => {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
1
3
|
import { mergeConfigs } from '@netlify/config';
|
|
2
4
|
import { getConfigMutations } from '../../plugins/child/diff.js';
|
|
5
|
+
import { DEPLOY_CONFIG_DIST_PATH, FRAMEWORKS_API_SKEW_PROTECTION_PATH } from '../../utils/frameworks_api.js';
|
|
6
|
+
import { loadSkewProtectionConfig } from './skew_protection.js';
|
|
3
7
|
import { filterConfig, loadConfigFile } from './util.js';
|
|
4
8
|
// The properties that can be set using this API. Each element represents a
|
|
5
9
|
// path using dot-notation — e.g. `["build", "functions"]` represents the
|
|
@@ -19,9 +23,31 @@ const ALLOWED_PROPERTIES = [
|
|
|
19
23
|
// values that should be evaluated before any user-defined values. They use
|
|
20
24
|
// a special notation where `redirects!` represents "forced redirects", etc.
|
|
21
25
|
const OVERRIDE_PROPERTIES = new Set(['redirects!']);
|
|
26
|
+
// Looks for a skew protection configuration file. If found, the file is loaded
|
|
27
|
+
// and validated against the schema, throwing a build error if validation
|
|
28
|
+
// fails. If valid, the contents are written to the deploy config file.
|
|
29
|
+
const handleSkewProtection = async (buildDir, packagePath) => {
|
|
30
|
+
const inputPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_SKEW_PROTECTION_PATH);
|
|
31
|
+
const outputPath = resolve(buildDir, packagePath ?? '', DEPLOY_CONFIG_DIST_PATH);
|
|
32
|
+
const skewProtectionConfig = await loadSkewProtectionConfig(inputPath);
|
|
33
|
+
if (!skewProtectionConfig) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const deployConfig = {
|
|
37
|
+
skew_protection: skewProtectionConfig,
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
await fs.mkdir(dirname(outputPath), { recursive: true });
|
|
41
|
+
await fs.writeFile(outputPath, JSON.stringify(deployConfig));
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new Error('Failed to process skew protection configuration', { cause: error });
|
|
45
|
+
}
|
|
46
|
+
};
|
|
22
47
|
const coreStep = async function ({ buildDir, netlifyConfig, packagePath, systemLog = () => {
|
|
23
48
|
// no-op
|
|
24
49
|
}, }) {
|
|
50
|
+
await handleSkewProtection(buildDir, packagePath);
|
|
25
51
|
let config;
|
|
26
52
|
try {
|
|
27
53
|
config = await loadConfigFile(buildDir, packagePath);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const deployIDSourceTypeSchema: z.ZodEnum<["cookie", "header", "query"]>;
|
|
3
|
+
declare const deployIDSourceSchema: z.ZodObject<{
|
|
4
|
+
type: z.ZodEnum<["cookie", "header", "query"]>;
|
|
5
|
+
name: z.ZodString;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
name: string;
|
|
8
|
+
type: "cookie" | "header" | "query";
|
|
9
|
+
}, {
|
|
10
|
+
name: string;
|
|
11
|
+
type: "cookie" | "header" | "query";
|
|
12
|
+
}>;
|
|
13
|
+
declare const skewProtectionConfigSchema: z.ZodObject<{
|
|
14
|
+
patterns: z.ZodArray<z.ZodString, "many">;
|
|
15
|
+
sources: z.ZodArray<z.ZodObject<{
|
|
16
|
+
type: z.ZodEnum<["cookie", "header", "query"]>;
|
|
17
|
+
name: z.ZodString;
|
|
18
|
+
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
name: string;
|
|
20
|
+
type: "cookie" | "header" | "query";
|
|
21
|
+
}, {
|
|
22
|
+
name: string;
|
|
23
|
+
type: "cookie" | "header" | "query";
|
|
24
|
+
}>, "many">;
|
|
25
|
+
}, "strip", z.ZodTypeAny, {
|
|
26
|
+
patterns: string[];
|
|
27
|
+
sources: {
|
|
28
|
+
name: string;
|
|
29
|
+
type: "cookie" | "header" | "query";
|
|
30
|
+
}[];
|
|
31
|
+
}, {
|
|
32
|
+
patterns: string[];
|
|
33
|
+
sources: {
|
|
34
|
+
name: string;
|
|
35
|
+
type: "cookie" | "header" | "query";
|
|
36
|
+
}[];
|
|
37
|
+
}>;
|
|
38
|
+
export type SkewProtectionConfig = z.infer<typeof skewProtectionConfigSchema>;
|
|
39
|
+
export type DeployIDSource = z.infer<typeof deployIDSourceSchema>;
|
|
40
|
+
export type DeployIDSourceType = z.infer<typeof deployIDSourceTypeSchema>;
|
|
41
|
+
export declare const loadSkewProtectionConfig: (configPath: string) => Promise<{
|
|
42
|
+
patterns: string[];
|
|
43
|
+
sources: {
|
|
44
|
+
name: string;
|
|
45
|
+
type: "cookie" | "header" | "query";
|
|
46
|
+
}[];
|
|
47
|
+
} | undefined>;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const deployIDSourceTypeSchema = z.enum(['cookie', 'header', 'query']);
|
|
4
|
+
const deployIDSourceSchema = z.object({
|
|
5
|
+
type: deployIDSourceTypeSchema,
|
|
6
|
+
name: z.string(),
|
|
7
|
+
});
|
|
8
|
+
const skewProtectionConfigSchema = z.object({
|
|
9
|
+
patterns: z.array(z.string()),
|
|
10
|
+
sources: z.array(deployIDSourceSchema),
|
|
11
|
+
});
|
|
12
|
+
const validateSkewProtectionConfig = (input) => {
|
|
13
|
+
const { data, error, success } = skewProtectionConfigSchema.safeParse(input);
|
|
14
|
+
if (success) {
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
throw new Error(`Invalid skew protection configuration:\n\n${formatSchemaError(error)}`);
|
|
18
|
+
};
|
|
19
|
+
export const loadSkewProtectionConfig = async (configPath) => {
|
|
20
|
+
let parsedData;
|
|
21
|
+
try {
|
|
22
|
+
const data = await fs.readFile(configPath, 'utf8');
|
|
23
|
+
parsedData = JSON.parse(data);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
// If the file doesn't exist, this is a non-error.
|
|
27
|
+
if (error.code === 'ENOENT') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
throw new Error('Invalid skew protection configuration', { cause: error });
|
|
31
|
+
}
|
|
32
|
+
return validateSkewProtectionConfig(parsedData);
|
|
33
|
+
};
|
|
34
|
+
const formatSchemaError = (error) => {
|
|
35
|
+
const lines = error.issues.map((issue) => `- ${issue.path.join('.')}: ${issue.message}`);
|
|
36
|
+
return lines.join('\n');
|
|
37
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import isPlainObject from 'is-plain-obj';
|
|
4
|
-
import {
|
|
4
|
+
import { FRAMEWORKS_API_CONFIG_PATH } from '../../utils/frameworks_api.js';
|
|
5
5
|
export const loadConfigFile = async (buildDir, packagePath) => {
|
|
6
|
-
const configPath = resolve(buildDir, packagePath ?? '',
|
|
6
|
+
const configPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_CONFIG_PATH);
|
|
7
7
|
try {
|
|
8
8
|
const data = await fs.readFile(configPath, 'utf8');
|
|
9
9
|
return JSON.parse(data);
|
|
@@ -5,7 +5,7 @@ import { addErrorInfo } from '../../error/info.js';
|
|
|
5
5
|
import { log } from '../../log/logger.js';
|
|
6
6
|
import { getGeneratedFunctions } from '../../steps/return_values.js';
|
|
7
7
|
import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle } from '../../log/messages/core_steps.js';
|
|
8
|
-
import {
|
|
8
|
+
import { FRAMEWORKS_API_FUNCTIONS_PATH } from '../../utils/frameworks_api.js';
|
|
9
9
|
import { getZipError } from './error.js';
|
|
10
10
|
import { getUserAndInternalFunctions, validateFunctionsSrc } from './utils.js';
|
|
11
11
|
import { getZisiParameters } from './zisi.js';
|
|
@@ -88,7 +88,7 @@ const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC
|
|
|
88
88
|
const functionsDist = resolve(buildDir, relativeFunctionsDist);
|
|
89
89
|
const internalFunctionsSrc = resolve(buildDir, relativeInternalFunctionsSrc);
|
|
90
90
|
const internalFunctionsSrcExists = await pathExists(internalFunctionsSrc);
|
|
91
|
-
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '',
|
|
91
|
+
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_PATH);
|
|
92
92
|
const frameworkFunctionsSrcExists = await pathExists(frameworkFunctionsSrc);
|
|
93
93
|
const functionsSrcExists = await validateFunctionsSrc({ functionsSrc, relativeFunctionsSrc });
|
|
94
94
|
const [userFunctions = [], internalFunctions = [], frameworkFunctions = []] = await getUserAndInternalFunctions({
|
|
@@ -161,7 +161,7 @@ const hasFunctionsDirectories = async function ({ buildDir, constants: { INTERNA
|
|
|
161
161
|
if (await pathExists(internalFunctionsSrc)) {
|
|
162
162
|
return true;
|
|
163
163
|
}
|
|
164
|
-
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '',
|
|
164
|
+
const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_PATH);
|
|
165
165
|
if (await pathExists(frameworkFunctionsSrc)) {
|
|
166
166
|
return true;
|
|
167
167
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { rm } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { getBlobsDirs } from '../../utils/blobs.js';
|
|
4
|
-
import {
|
|
4
|
+
import { FRAMEWORKS_API_PATH } from '../../utils/frameworks_api.js';
|
|
5
5
|
const coreStep = async ({ buildDir, packagePath }) => {
|
|
6
|
-
const dirs = [...getBlobsDirs(buildDir, packagePath), resolve(buildDir, packagePath || '',
|
|
6
|
+
const dirs = [...getBlobsDirs(buildDir, packagePath), resolve(buildDir, packagePath || '', FRAMEWORKS_API_PATH)];
|
|
7
7
|
try {
|
|
8
8
|
await Promise.all(dirs.map((dir) => rm(dir, { recursive: true, force: true })));
|
|
9
9
|
}
|
package/lib/utils/blobs.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fdir } from 'fdir';
|
|
4
4
|
import { DEFAULT_API_HOST } from '../core/normalize_flags.js';
|
|
5
|
-
import {
|
|
5
|
+
import { FRAMEWORKS_API_BLOBS_PATH } from './frameworks_api.js';
|
|
6
6
|
const LEGACY_BLOBS_PATH = '.netlify/blobs/deploy';
|
|
7
7
|
const DEPLOY_CONFIG_BLOBS_PATH = '.netlify/deploy/v1/blobs/deploy';
|
|
8
8
|
/** Retrieve the absolute path of the deploy scoped internal blob directories */
|
|
@@ -36,7 +36,7 @@ export const getBlobsEnvironmentContext = ({ api = { host: DEFAULT_API_HOST, sch
|
|
|
36
36
|
*/
|
|
37
37
|
export const scanForBlobs = async function (buildDir, packagePath) {
|
|
38
38
|
// We start by looking for files using the Frameworks API.
|
|
39
|
-
const frameworkBlobsDir = path.resolve(buildDir, packagePath || '',
|
|
39
|
+
const frameworkBlobsDir = path.resolve(buildDir, packagePath || '', FRAMEWORKS_API_BLOBS_PATH, 'deploy');
|
|
40
40
|
const frameworkBlobsDirScan = await new fdir().onlyCounts().crawl(frameworkBlobsDir).withPromise();
|
|
41
41
|
if (frameworkBlobsDirScan.files > 0) {
|
|
42
42
|
return {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
1
|
+
export declare const FRAMEWORKS_API_PATH = ".netlify/v1";
|
|
2
|
+
export declare const FRAMEWORKS_API_BLOBS_PATH = ".netlify/v1/blobs";
|
|
3
|
+
export declare const FRAMEWORKS_API_CONFIG_PATH = ".netlify/v1/config.json";
|
|
4
|
+
export declare const FRAMEWORKS_API_EDGE_FUNCTIONS_PATH = ".netlify/v1/edge-functions";
|
|
5
5
|
export declare const FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP = "import_map.json";
|
|
6
|
-
export declare const
|
|
6
|
+
export declare const FRAMEWORKS_API_FUNCTIONS_PATH = ".netlify/v1/functions";
|
|
7
|
+
export declare const FRAMEWORKS_API_SKEW_PROTECTION_PATH = ".netlify/v1/skew-protection.json";
|
|
8
|
+
export declare const DEPLOY_CONFIG_DIST_PATH = ".netlify/deploy-config/deploy-config.json";
|
|
7
9
|
type DirectoryTreeFiles = Map<string, string[]>;
|
|
8
10
|
/**
|
|
9
11
|
* Traverses a directory tree in search of leaf files. The key of each leaf
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { basename, dirname, resolve, sep } from 'node:path';
|
|
2
2
|
import { fdir } from 'fdir';
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
3
|
+
export const FRAMEWORKS_API_PATH = '.netlify/v1';
|
|
4
|
+
export const FRAMEWORKS_API_BLOBS_PATH = `${FRAMEWORKS_API_PATH}/blobs`;
|
|
5
|
+
export const FRAMEWORKS_API_CONFIG_PATH = `${FRAMEWORKS_API_PATH}/config.json`;
|
|
6
|
+
export const FRAMEWORKS_API_EDGE_FUNCTIONS_PATH = `${FRAMEWORKS_API_PATH}/edge-functions`;
|
|
7
7
|
export const FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP = 'import_map.json';
|
|
8
|
-
export const
|
|
8
|
+
export const FRAMEWORKS_API_FUNCTIONS_PATH = `${FRAMEWORKS_API_PATH}/functions`;
|
|
9
|
+
export const FRAMEWORKS_API_SKEW_PROTECTION_PATH = `${FRAMEWORKS_API_PATH}/skew-protection.json`;
|
|
10
|
+
export const DEPLOY_CONFIG_DIST_PATH = '.netlify/deploy-config/deploy-config.json';
|
|
9
11
|
/**
|
|
10
12
|
* Traverses a directory tree in search of leaf files. The key of each leaf
|
|
11
13
|
* file is determined by its path relative to the base directory.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/build",
|
|
3
|
-
"version": "35.0
|
|
3
|
+
"version": "35.1.0",
|
|
4
4
|
"description": "Netlify build module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./lib/index.js",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@bugsnag/js": "^8.0.0",
|
|
70
70
|
"@netlify/blobs": "^10.0.8",
|
|
71
71
|
"@netlify/cache-utils": "^6.0.4",
|
|
72
|
-
"@netlify/config": "^24.0.
|
|
72
|
+
"@netlify/config": "^24.0.3",
|
|
73
73
|
"@netlify/edge-bundler": "14.5.2",
|
|
74
74
|
"@netlify/functions-utils": "^6.2.2",
|
|
75
75
|
"@netlify/git-utils": "^6.0.2",
|
|
@@ -115,7 +115,8 @@
|
|
|
115
115
|
"typescript": "^5.0.0",
|
|
116
116
|
"uuid": "^11.0.0",
|
|
117
117
|
"yaml": "^2.8.0",
|
|
118
|
-
"yargs": "^17.6.0"
|
|
118
|
+
"yargs": "^17.6.0",
|
|
119
|
+
"zod": "^3.25.76"
|
|
119
120
|
},
|
|
120
121
|
"devDependencies": {
|
|
121
122
|
"@netlify/nock-udp": "^5.0.1",
|
|
@@ -151,5 +152,5 @@
|
|
|
151
152
|
"engines": {
|
|
152
153
|
"node": ">=18.14.0"
|
|
153
154
|
},
|
|
154
|
-
"gitHead": "
|
|
155
|
+
"gitHead": "27ed650158778bb4c3fd917f505b1676551013c8"
|
|
155
156
|
}
|