@netlify/build 36.0.0 → 36.1.0-node24
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/env/changes.d.ts +0 -1
- package/lib/log/logger.js +1 -1
- package/lib/log/messages/core_steps.d.ts +1 -2
- package/lib/log/messages/core_steps.js +18 -29
- package/lib/plugins_core/blobs_upload/index.js +2 -0
- package/lib/plugins_core/db_setup/index.js +0 -4
- package/lib/plugins_core/dev_blobs_upload/index.js +2 -0
- package/lib/plugins_core/secrets_scanning/index.js +8 -21
- package/package.json +6 -7
package/lib/env/changes.d.ts
CHANGED
package/lib/log/logger.js
CHANGED
|
@@ -33,7 +33,7 @@ export const getBufferLogs = (config) => {
|
|
|
33
33
|
export const log = function (logs, string, config = {}) {
|
|
34
34
|
const { indent = false, color } = config;
|
|
35
35
|
const stringA = indent ? indentString(string, INDENT_SIZE) : string;
|
|
36
|
-
const stringB =
|
|
36
|
+
const stringB = stringA.replace(EMPTY_LINES_REGEXP, EMPTY_LINE);
|
|
37
37
|
const stringC = color === undefined ? stringB : color(stringB);
|
|
38
38
|
if (logs && logs.outputFlusher) {
|
|
39
39
|
logs.outputFlusher.flush();
|
|
@@ -34,9 +34,8 @@ export function logDbMigrations({ logs, migrations, srcDir }: {
|
|
|
34
34
|
}): void;
|
|
35
35
|
export function logSecretsScanSkipMessage(logs: any, msg: any): void;
|
|
36
36
|
export function logSecretsScanSuccessMessage(logs: any, msg: any): void;
|
|
37
|
-
export function logSecretsScanFailBuildMessage({ logs, scanResults, groupedResults
|
|
37
|
+
export function logSecretsScanFailBuildMessage({ logs, scanResults, groupedResults }: {
|
|
38
38
|
logs: any;
|
|
39
39
|
scanResults: any;
|
|
40
40
|
groupedResults: any;
|
|
41
|
-
enhancedScanShouldRunInActiveMode: any;
|
|
42
41
|
}): void;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { RUNTIME } from '@netlify/zip-it-and-ship-it';
|
|
3
|
-
import { trace } from '@opentelemetry/api';
|
|
4
3
|
import { log, logArray, logError, logErrorSubHeader, logWarningSubHeader } from '../logger.js';
|
|
5
4
|
import { THEME } from '../theme.js';
|
|
6
5
|
const logBundleResultFunctions = ({ functions, headerMessage, logs, error }) => {
|
|
@@ -68,6 +67,8 @@ export const trackBundleResults = ({ results = [], systemLog }) => {
|
|
|
68
67
|
name: result.name,
|
|
69
68
|
runtime: result.runtime,
|
|
70
69
|
bundler: result.runtime === RUNTIME.JAVASCRIPT ? result.bundler : null,
|
|
70
|
+
bundlerReason: result.runtime === RUNTIME.JAVASCRIPT ? (result.bundlerReason ?? null) : null,
|
|
71
|
+
sizeBytes: result.size ?? null,
|
|
71
72
|
hadFallback: (result.bundlerErrors?.length ?? 0) > 0,
|
|
72
73
|
hadWarnings: (result.bundlerWarnings?.length ?? 0) > 0,
|
|
73
74
|
}));
|
|
@@ -79,23 +80,13 @@ export const trackBundleResults = ({ results = [], systemLog }) => {
|
|
|
79
80
|
const fallbackCount = perFunction.filter((p) => p.hadFallback).length;
|
|
80
81
|
const warningsCount = perFunction.filter((p) => p.hadWarnings).length;
|
|
81
82
|
systemLog({
|
|
82
|
-
msg: 'Functions bundling completed',
|
|
83
|
+
msg: 'Functions bundling completed successfully',
|
|
83
84
|
bundlers,
|
|
84
85
|
bundlerCounts,
|
|
85
86
|
fallbackCount,
|
|
86
87
|
warningsCount,
|
|
87
88
|
functions: perFunction,
|
|
88
89
|
});
|
|
89
|
-
const span = trace.getActiveSpan();
|
|
90
|
-
if (span) {
|
|
91
|
-
span.setAttribute('build.execution.step.bundler', bundlers);
|
|
92
|
-
span.setAttribute('build.execution.step.functions_count', perFunction.length);
|
|
93
|
-
span.setAttribute('build.execution.step.bundler.fallback_count', fallbackCount);
|
|
94
|
-
span.setAttribute('build.execution.step.bundler.warnings_count', warningsCount);
|
|
95
|
-
for (const [bundler, count] of Object.entries(bundlerCounts)) {
|
|
96
|
-
span.setAttribute(`build.execution.step.bundler.${bundler}.count`, count);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
90
|
return { bundlers, fallbackCount, warningsCount };
|
|
100
91
|
};
|
|
101
92
|
export const logFunctionsNonExistingDir = function (logs, relativeFunctionsSrc) {
|
|
@@ -180,11 +171,11 @@ export const logSecretsScanSkipMessage = function (logs, msg) {
|
|
|
180
171
|
export const logSecretsScanSuccessMessage = function (logs, msg) {
|
|
181
172
|
log(logs, msg, { color: THEME.highlightWords });
|
|
182
173
|
};
|
|
183
|
-
export const logSecretsScanFailBuildMessage = function ({ logs, scanResults, groupedResults
|
|
174
|
+
export const logSecretsScanFailBuildMessage = function ({ logs, scanResults, groupedResults }) {
|
|
184
175
|
const { secretMatches, enhancedSecretMatches } = groupedResults;
|
|
185
176
|
const secretMatchesKeys = Object.keys(secretMatches);
|
|
186
177
|
const enhancedSecretMatchesKeys = Object.keys(enhancedSecretMatches);
|
|
187
|
-
logErrorSubHeader(logs, `Scanning complete. ${scanResults.scannedFilesCount} file(s) scanned. Secrets scanning found ${secretMatchesKeys.length} instance(s) of secrets${enhancedSecretMatchesKeys.length > 0
|
|
178
|
+
logErrorSubHeader(logs, `Scanning complete. ${scanResults.scannedFilesCount} file(s) scanned. Secrets scanning found ${secretMatchesKeys.length} instance(s) of secrets${enhancedSecretMatchesKeys.length > 0 ? ` and ${enhancedSecretMatchesKeys.length} instance(s) of likely secrets` : ''} in build output or repo code.\n`);
|
|
188
179
|
// Explicit secret matches
|
|
189
180
|
secretMatchesKeys.forEach((key) => {
|
|
190
181
|
logError(logs, `Secret env var "${key}"'s value detected:`);
|
|
@@ -200,22 +191,20 @@ export const logSecretsScanFailBuildMessage = function ({ logs, scanResults, gro
|
|
|
200
191
|
logError(logs, `\nTo prevent exposing secrets, the build will fail until these secret values are not found in build output or repo files.`);
|
|
201
192
|
logError(logs, `\nIf these are expected, use SECRETS_SCAN_OMIT_PATHS, SECRETS_SCAN_OMIT_KEYS, or SECRETS_SCAN_ENABLED to prevent detecting.`);
|
|
202
193
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
logError(logs, `found value at line ${lineNumber} in ${file}`, { indent: true });
|
|
213
|
-
});
|
|
194
|
+
// Likely secret matches from enhanced scan
|
|
195
|
+
enhancedSecretMatchesKeys.forEach((key, index) => {
|
|
196
|
+
logError(logs, `${index === 0 && secretMatchesKeys.length ? '\n' : ''}"${key}***" detected as a likely secret:`);
|
|
197
|
+
enhancedSecretMatches[key]
|
|
198
|
+
.sort((a, b) => {
|
|
199
|
+
return a.file > b.file ? 0 : 1;
|
|
200
|
+
})
|
|
201
|
+
.forEach(({ lineNumber, file }) => {
|
|
202
|
+
logError(logs, `found value at line ${lineNumber} in ${file}`, { indent: true });
|
|
214
203
|
});
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
204
|
+
});
|
|
205
|
+
if (enhancedSecretMatchesKeys.length) {
|
|
206
|
+
logError(logs, `\nTo prevent exposing secrets, the build will fail until these likely secret values are not found in build output or repo files.`);
|
|
207
|
+
logError(logs, `\nIf these are expected, use SECRETS_SCAN_SMART_DETECTION_OMIT_VALUES, or SECRETS_SCAN_SMART_DETECTION_ENABLED to prevent detecting.`);
|
|
219
208
|
}
|
|
220
209
|
logError(logs, `\nFor more information on secrets scanning, see the Netlify Docs: https://ntl.fyi/configure-secrets-scanning`);
|
|
221
210
|
};
|
|
@@ -40,6 +40,8 @@ const coreStep = async function ({ logs, deployId, buildDir, packagePath, consta
|
|
|
40
40
|
try {
|
|
41
41
|
await pMap(blobsToUpload, async ({ key, contentPath, metadataPath }) => {
|
|
42
42
|
const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath);
|
|
43
|
+
// `data` is a `Buffer`, typed by @types/node as `Buffer<ArrayBufferLike>`, which TS
|
|
44
|
+
// rejects as a `BlobPart`; the runtime value is a valid blob part.
|
|
43
45
|
await blobStore.set(key, new Blob([data]), { metadata });
|
|
44
46
|
}, { concurrency: 10 });
|
|
45
47
|
}
|
|
@@ -30,13 +30,9 @@ const coreStep = async ({ api, branch, buildDir, constants, context, logs }) =>
|
|
|
30
30
|
if (migrationNames.length > 0 && migrationsSrc) {
|
|
31
31
|
logDbMigrations({ logs, migrations: migrationNames, srcDir: migrationsSrc });
|
|
32
32
|
}
|
|
33
|
-
// @ts-expect-error This is an internal method for now so it isn't typed yet.
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
35
33
|
const database = (await api.createSiteDatabase({ site_id: siteId }));
|
|
36
34
|
let connectionString = database.connection_string;
|
|
37
35
|
if (context !== 'production') {
|
|
38
|
-
// @ts-expect-error This is an internal method for now so it isn't typed yet.
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
40
36
|
const databaseBranch = (await api.createSiteDatabaseBranch({
|
|
41
37
|
site_id: siteId,
|
|
42
38
|
body: { branch_id: branch },
|
|
@@ -47,6 +47,8 @@ const coreStep = async function ({ debug, logs, deployId, buildDir, quiet, packa
|
|
|
47
47
|
log(logs, `- Uploading blob ${key}`, { indent: true });
|
|
48
48
|
}
|
|
49
49
|
const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath);
|
|
50
|
+
// `data` is a `Buffer`, typed by @types/node as `Buffer<ArrayBufferLike>`, which TS
|
|
51
|
+
// rejects as a `BlobPart`; the runtime value is a valid blob part.
|
|
50
52
|
await blobStore.set(key, new Blob([data]), { metadata });
|
|
51
53
|
}, { concurrency: 10 });
|
|
52
54
|
}
|
|
@@ -5,13 +5,10 @@ import { logSecretsScanFailBuildMessage, logSecretsScanSkipMessage, logSecretsSc
|
|
|
5
5
|
import { reportValidations } from '../../status/validations.js';
|
|
6
6
|
import { getFilePathsToScan, getOmitValuesFromEnhancedScanForEnhancedScanFromEnv, getSecretKeysToScanFor, groupScanResultsByKeyAndScanType, isEnhancedSecretsScanningEnabled, isSecretsScanningEnabled, scanFilesForKeyValues, } from './utils.js';
|
|
7
7
|
const tracer = trace.getTracer('secrets-scanning');
|
|
8
|
-
const coreStep = async function ({ buildDir, logs, netlifyConfig, explicitSecretKeys, enhancedSecretScan,
|
|
8
|
+
const coreStep = async function ({ buildDir, logs, netlifyConfig, explicitSecretKeys, enhancedSecretScan, systemLog, deployId, api, }) {
|
|
9
9
|
const stepResults = {};
|
|
10
10
|
const passedSecretKeys = (explicitSecretKeys || '').split(',');
|
|
11
11
|
const envVars = netlifyConfig.build.environment;
|
|
12
|
-
// When the flag is disabled, we may still run the scan if a secrets scan would otherwise take place anyway
|
|
13
|
-
// In this case, we hide any output to the user and simply gather the information in our logs
|
|
14
|
-
const enhancedScanShouldRunInActiveMode = featureFlags?.enhanced_secret_scan_impacts_builds ?? false;
|
|
15
12
|
systemLog?.({ passedSecretKeys, buildDir });
|
|
16
13
|
if (!isSecretsScanningEnabled(envVars)) {
|
|
17
14
|
logSecretsScanSkipMessage(logs, 'Secrets scanning disabled via SECRETS_SCAN_ENABLED flag set to false.');
|
|
@@ -26,20 +23,14 @@ const coreStep = async function ({ buildDir, logs, netlifyConfig, explicitSecret
|
|
|
26
23
|
}
|
|
27
24
|
const enhancedScanningEnabledInEnv = isEnhancedSecretsScanningEnabled(envVars);
|
|
28
25
|
const enhancedScanConfigured = enhancedSecretScan && enhancedScanningEnabledInEnv;
|
|
29
|
-
if (enhancedSecretScan &&
|
|
26
|
+
if (enhancedSecretScan && !enhancedScanningEnabledInEnv) {
|
|
30
27
|
logSecretsScanSkipMessage(logs, 'Enhanced secrets detection disabled via SECRETS_SCAN_SMART_DETECTION_ENABLED flag set to false.');
|
|
31
28
|
}
|
|
32
|
-
if (
|
|
33
|
-
enhancedScanConfigured &&
|
|
34
|
-
envVars['SECRETS_SCAN_SMART_DETECTION_OMIT_VALUES'] !== undefined) {
|
|
29
|
+
if (enhancedScanConfigured && envVars['SECRETS_SCAN_SMART_DETECTION_OMIT_VALUES'] !== undefined) {
|
|
35
30
|
log(logs, `SECRETS_SCAN_SMART_DETECTION_OMIT_VALUES override option set to: ${envVars['SECRETS_SCAN_SMART_DETECTION_OMIT_VALUES']}\n`);
|
|
36
31
|
}
|
|
37
32
|
const keysToSearchFor = getSecretKeysToScanFor(envVars, passedSecretKeys);
|
|
38
|
-
|
|
39
|
-
const enhancedScanShouldRun = enhancedScanShouldRunInActiveMode
|
|
40
|
-
? enhancedScanConfigured
|
|
41
|
-
: enhancedScanConfigured && keysToSearchFor.length > 0;
|
|
42
|
-
if (keysToSearchFor.length === 0 && !enhancedScanShouldRun) {
|
|
33
|
+
if (keysToSearchFor.length === 0 && !enhancedScanConfigured) {
|
|
43
34
|
logSecretsScanSkipMessage(logs, 'Secrets scanning skipped because no env vars marked as secret are set to non-empty/non-trivial values or they are all omitted with SECRETS_SCAN_OMIT_KEYS env var setting.');
|
|
44
35
|
return stepResults;
|
|
45
36
|
}
|
|
@@ -60,7 +51,7 @@ const coreStep = async function ({ buildDir, logs, netlifyConfig, explicitSecret
|
|
|
60
51
|
keys: keysToSearchFor,
|
|
61
52
|
base: buildDir,
|
|
62
53
|
filePaths,
|
|
63
|
-
enhancedScanning:
|
|
54
|
+
enhancedScanning: enhancedScanConfigured,
|
|
64
55
|
omitValuesFromEnhancedScan: getOmitValuesFromEnhancedScanForEnhancedScanFromEnv(envVars),
|
|
65
56
|
});
|
|
66
57
|
secretMatches = scanResults.matches.filter((match) => !match.enhancedMatch);
|
|
@@ -73,8 +64,7 @@ const coreStep = async function ({ buildDir, logs, netlifyConfig, explicitSecret
|
|
|
73
64
|
secretsFilesCount: scanResults.scannedFilesCount,
|
|
74
65
|
keysToSearchFor,
|
|
75
66
|
enhancedPrefixMatches: enhancedSecretMatches.length ? enhancedSecretMatches.map((match) => match.key) : [],
|
|
76
|
-
enhancedScanning:
|
|
77
|
-
enhancedScanActiveMode: enhancedScanShouldRunInActiveMode,
|
|
67
|
+
enhancedScanning: enhancedScanConfigured,
|
|
78
68
|
};
|
|
79
69
|
systemLog?.(attributesForLogsAndSpan);
|
|
80
70
|
span.setAttributes(attributesForLogsAndSpan);
|
|
@@ -84,13 +74,11 @@ const coreStep = async function ({ buildDir, logs, netlifyConfig, explicitSecret
|
|
|
84
74
|
const secretScanResult = {
|
|
85
75
|
scannedFilesCount: scanResults?.scannedFilesCount ?? 0,
|
|
86
76
|
secretsScanMatches: secretMatches ?? [],
|
|
87
|
-
enhancedSecretsScanMatches:
|
|
77
|
+
enhancedSecretsScanMatches: enhancedSecretMatches ? enhancedSecretMatches : [],
|
|
88
78
|
};
|
|
89
79
|
reportValidations({ api, secretScanResult, deployId, systemLog });
|
|
90
80
|
}
|
|
91
|
-
if (!scanResults ||
|
|
92
|
-
scanResults.matches.length === 0 ||
|
|
93
|
-
(!enhancedScanShouldRunInActiveMode && !secretMatches?.length)) {
|
|
81
|
+
if (!scanResults || scanResults.matches.length === 0) {
|
|
94
82
|
logSecretsScanSuccessMessage(logs, `Secrets scanning complete. ${scanResults?.scannedFilesCount} file(s) scanned. No secrets detected in build output or repo code!`);
|
|
95
83
|
return stepResults;
|
|
96
84
|
}
|
|
@@ -100,7 +88,6 @@ const coreStep = async function ({ buildDir, logs, netlifyConfig, explicitSecret
|
|
|
100
88
|
logs,
|
|
101
89
|
scanResults,
|
|
102
90
|
groupedResults: groupScanResultsByKeyAndScanType(scanResults),
|
|
103
|
-
enhancedScanShouldRunInActiveMode,
|
|
104
91
|
});
|
|
105
92
|
const error = new Error(`Secrets scanning found secrets in build.`);
|
|
106
93
|
addErrorInfo(error, { type: 'secretScanningFoundSecrets' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/build",
|
|
3
|
-
"version": "36.
|
|
3
|
+
"version": "36.1.0-node24",
|
|
4
4
|
"description": "Netlify build module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./lib/index.js",
|
|
@@ -70,13 +70,13 @@
|
|
|
70
70
|
"@netlify/blobs": "^10.4.4",
|
|
71
71
|
"@netlify/cache-utils": "^7.0.0",
|
|
72
72
|
"@netlify/config": "^25.0.0",
|
|
73
|
-
"@netlify/edge-bundler": "15.0.
|
|
74
|
-
"@netlify/functions-utils": "^7.0.
|
|
73
|
+
"@netlify/edge-bundler": "15.0.1",
|
|
74
|
+
"@netlify/functions-utils": "^7.0.2",
|
|
75
75
|
"@netlify/git-utils": "^7.0.0",
|
|
76
76
|
"@netlify/opentelemetry-utils": "^3.0.0",
|
|
77
77
|
"@netlify/plugins-list": "^6.81.6",
|
|
78
78
|
"@netlify/run-utils": "^7.0.0",
|
|
79
|
-
"@netlify/zip-it-and-ship-it": "15.
|
|
79
|
+
"@netlify/zip-it-and-ship-it": "15.1.0",
|
|
80
80
|
"@sindresorhus/slugify": "^2.0.0",
|
|
81
81
|
"ansi-escapes": "^7.0.0",
|
|
82
82
|
"ansis": "^4.1.0",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"@netlify/nock-udp": "^6.0.0",
|
|
123
123
|
"@opentelemetry/api": "~1.8.0",
|
|
124
124
|
"@opentelemetry/sdk-trace-base": "~1.24.0",
|
|
125
|
-
"@types/node": "^
|
|
125
|
+
"@types/node": "^22.12.0",
|
|
126
126
|
"atob": "^2.1.2",
|
|
127
127
|
"ava": "^5.0.0",
|
|
128
128
|
"c8": "^10.0.0",
|
|
@@ -151,6 +151,5 @@
|
|
|
151
151
|
},
|
|
152
152
|
"engines": {
|
|
153
153
|
"node": ">=22.12.0"
|
|
154
|
-
}
|
|
155
|
-
"gitHead": "bdaf7cd5e14049c31a236c9432f192f78c6c31c8"
|
|
154
|
+
}
|
|
156
155
|
}
|