@netlify/build 35.13.8 → 35.14.0-node22eos

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.
@@ -2,6 +2,14 @@ export function logBundleResults({ logs, results }: {
2
2
  logs: any;
3
3
  results: import("@netlify/zip-it-and-ship-it").FunctionResult[];
4
4
  }): void;
5
+ export function trackBundleResults({ results, systemLog }: {
6
+ results: import("@netlify/zip-it-and-ship-it").FunctionResult[];
7
+ systemLog: (...args: unknown[]) => void;
8
+ }): {
9
+ bundlers: import("@netlify/zip-it-and-ship-it").NodeBundlerName[];
10
+ fallbackCount: number;
11
+ warningsCount: number;
12
+ };
5
13
  export function logFunctionsNonExistingDir(logs: any, relativeFunctionsSrc: any): void;
6
14
  export function logFunctionsToBundle({ logs, userFunctions, userFunctionsSrc, userFunctionsSrcExists, internalFunctions, internalFunctionsSrc, frameworkFunctions, generatedFunctions, type, }: {
7
15
  logs: any;
@@ -1,4 +1,6 @@
1
1
  import path from 'path';
2
+ import { RUNTIME } from '@netlify/zip-it-and-ship-it';
3
+ import { trace } from '@opentelemetry/api';
2
4
  import { log, logArray, logError, logErrorSubHeader, logWarningSubHeader } from '../logger.js';
3
5
  import { THEME } from '../theme.js';
4
6
  const logBundleResultFunctions = ({ functions, headerMessage, logs, error }) => {
@@ -12,7 +14,7 @@ const logBundleResultFunctions = ({ functions, headerMessage, logs, error }) =>
12
14
  logArray(logs, functionNames);
13
15
  };
14
16
  /**
15
- * Logs the result of bundling functions
17
+ * Logs the result of bundling functions (user facing)
16
18
  *
17
19
  * @param {object} options
18
20
  * @param {any} options.logs
@@ -44,6 +46,58 @@ export const logBundleResults = ({ logs, results = [] }) => {
44
46
  logModulesWithDynamicImports({ logs, modulesWithDynamicImports });
45
47
  }
46
48
  };
49
+ /**
50
+ * Sibling of `logBundleResults`. Derives structured telemetry from the same
51
+ * `results` array and emits it to the system log and the active span. Returns
52
+ * summary stats the caller can use for metric tags.
53
+ *
54
+ * @param {object} options
55
+ * @param {import("@netlify/zip-it-and-ship-it").FunctionResult[]} options.results
56
+ * @param {(...args: unknown[]) => void} options.systemLog
57
+ * @returns {{
58
+ * bundlers: import("@netlify/zip-it-and-ship-it").NodeBundlerName[],
59
+ * fallbackCount: number,
60
+ * warningsCount: number,
61
+ * }}
62
+ */
63
+ export const trackBundleResults = ({ results = [], systemLog }) => {
64
+ // `bundlerErrors` is only set when the user requested `esbuild_zisi` (esbuild
65
+ // with zisi fallback), esbuild failed, and zisi succeeded. The final
66
+ // `bundler` reflects the fallback, so this is our "silent fallback" signal.
67
+ const perFunction = results.map((result) => ({
68
+ name: result.name,
69
+ runtime: result.runtime,
70
+ bundler: result.runtime === RUNTIME.JAVASCRIPT ? result.bundler : null,
71
+ hadFallback: (result.bundlerErrors?.length ?? 0) > 0,
72
+ hadWarnings: (result.bundlerWarnings?.length ?? 0) > 0,
73
+ }));
74
+ // Exclude both `null` (non-JS runtimes) and `undefined` (prebuilt `.zip`
75
+ // JS functions, which zip-it-and-ship-it passes through with no bundler).
76
+ const jsResults = perFunction.filter((p) => p.bundler != null);
77
+ const bundlers = [...new Set(jsResults.map((p) => p.bundler))];
78
+ const bundlerCounts = jsResults.reduce((acc, p) => ({ ...acc, [p.bundler]: (acc[p.bundler] ?? 0) + 1 }), {});
79
+ const fallbackCount = perFunction.filter((p) => p.hadFallback).length;
80
+ const warningsCount = perFunction.filter((p) => p.hadWarnings).length;
81
+ systemLog({
82
+ msg: 'Functions bundling completed',
83
+ bundlers,
84
+ bundlerCounts,
85
+ fallbackCount,
86
+ warningsCount,
87
+ functions: perFunction,
88
+ });
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
+ return { bundlers, fallbackCount, warningsCount };
100
+ };
47
101
  export const logFunctionsNonExistingDir = function (logs, relativeFunctionsSrc) {
48
102
  log(logs, `The Netlify Functions setting targets a non-existing directory: ${relativeFunctionsSrc}`);
49
103
  };
@@ -15,9 +15,11 @@ export type PluginsOptions = {
15
15
  * usually the system's Node.js version.
16
16
  * If the user Node version does not satisfy our supported engine range use our own system Node version
17
17
  */
18
- export declare const addPluginsNodeVersion: ({ pluginsOptions, nodePath, userNodeVersion, logs }: {
18
+ export declare const addPluginsNodeVersion: ({ featureFlags, pluginsOptions, nodePath, userNodeVersion, logs, systemLog, }: {
19
+ featureFlags: any;
19
20
  pluginsOptions: any;
20
21
  nodePath: any;
21
22
  userNodeVersion: any;
22
23
  logs: any;
24
+ systemLog: any;
23
25
  }) => Promise<any[]>;
@@ -1,12 +1,15 @@
1
+ import { dirname } from 'path';
1
2
  import { execPath, version as currentVersion } from 'process';
2
3
  import semver from 'semver';
3
4
  import link from 'terminal-link';
4
5
  import { logWarning, logWarningSubHeader } from '../log/logger.js';
6
+ import { getPackageJson } from '../utils/package.js';
5
7
  /**
6
8
  * This node version is minimum required to run the plugins code.
7
9
  * If the users preferred Node.js version is below that we have to fall back to the system node version
8
10
  */
9
11
  const MINIMUM_REQUIRED_NODE_VERSION = '>=18.14.0';
12
+ const UPCOMING_MINIMUM_REQUIRED_NODE_VERSION = '>=22.12.0';
10
13
  /**
11
14
  * Local plugins and `package.json`-installed plugins use user's preferred Node.js version if higher than our minimum
12
15
  * supported version. Else default to the system Node version.
@@ -14,17 +17,19 @@ const MINIMUM_REQUIRED_NODE_VERSION = '>=18.14.0';
14
17
  * usually the system's Node.js version.
15
18
  * If the user Node version does not satisfy our supported engine range use our own system Node version
16
19
  */
17
- export const addPluginsNodeVersion = function ({ pluginsOptions, nodePath, userNodeVersion, logs }) {
20
+ export const addPluginsNodeVersion = function ({ featureFlags, pluginsOptions, nodePath, userNodeVersion, logs, systemLog, }) {
18
21
  const currentNodeVersion = semver.clean(currentVersion);
19
22
  return Promise.all(pluginsOptions.map((pluginOptions) => addPluginNodeVersion({
23
+ featureFlags,
20
24
  pluginOptions,
21
25
  currentNodeVersion,
22
26
  userNodeVersion,
23
27
  nodePath,
24
28
  logs,
29
+ systemLog,
25
30
  })));
26
31
  };
27
- const addPluginNodeVersion = async function ({ pluginOptions, pluginOptions: { loadedFrom, packageName }, currentNodeVersion, userNodeVersion, nodePath, logs, }) {
32
+ const addPluginNodeVersion = async function ({ featureFlags, pluginOptions, pluginOptions: { loadedFrom, packageName, pluginPath }, currentNodeVersion, userNodeVersion, nodePath, logs, systemLog, }) {
28
33
  const systemNode = { ...pluginOptions, nodePath: execPath, nodeVersion: currentNodeVersion };
29
34
  const userNode = { ...pluginOptions, nodePath, nodeVersion: userNodeVersion };
30
35
  const isLocalPlugin = loadedFrom === 'local' || loadedFrom === 'package.json';
@@ -32,12 +37,50 @@ const addPluginNodeVersion = async function ({ pluginOptions, pluginOptions: { l
32
37
  if (isUIOrAutoInstalledPlugin) {
33
38
  return systemNode;
34
39
  }
40
+ if (featureFlags.build_warn_upcoming_system_version_change &&
41
+ !semver.satisfies(userNodeVersion, UPCOMING_MINIMUM_REQUIRED_NODE_VERSION)) {
42
+ logWarningSubHeader(logs, `Warning: Starting June 16, 2026 plugin "${packageName}" will be executed with Node.js version 22.`);
43
+ logWarning(logs, ` We're upgrading our system Node.js minimum on that day, which means the plugin cannot be executed with your defined Node.js version ${userNodeVersion}.
44
+
45
+ Please make sure your plugin supports being run on Node.js 22.
46
+
47
+ Read more about our minimum required version in our ${link('forums announcement', 'https://answers.netlify.com/t/build-plugins-end-of-support-for-node-js-18-node-js-20/162662')}`);
48
+ if (pluginPath) {
49
+ const pluginDir = dirname(pluginPath);
50
+ const { packageJson: pluginPackageJson, packageDir } = await getPackageJson(pluginDir);
51
+ // `getPackageJson` walks up to the nearest `package.json`. For a `package.json`-installed
52
+ // plugin that's the plugin's own manifest, but for a local single-file plugin
53
+ // (e.g. `./plugins/foo.js`) it resolves an ancestor — typically the *site's*
54
+ // `package.json`, whose `engines.node` describes the site rather than the plugin. Only
55
+ // trust the resolved range when the manifest belongs to the plugin: an installed package,
56
+ // or a local plugin shipping its own `package.json` alongside its entry file.
57
+ const pluginOwnsPackageJson = loadedFrom === 'package.json' || packageDir === pluginDir;
58
+ const pluginNodeVersionRange = pluginOwnsPackageJson ? pluginPackageJson.engines?.node : undefined;
59
+ // Ensure Node.js version is compatible with plugin's `engines.node`
60
+ if (!pluginOwnsPackageJson) {
61
+ systemLog(`plugin "${packageName}" node support range could not be determined (no own package.json)`);
62
+ }
63
+ else if (!pluginNodeVersionRange) {
64
+ systemLog(`plugin "${packageName}" does not specify node support range`);
65
+ }
66
+ else if (semver.satisfies('22.12.0', pluginNodeVersionRange)) {
67
+ systemLog(`plugin "${packageName}" node support range includes v22`);
68
+ }
69
+ else {
70
+ logWarning(logs, ` In its package.json, the plugin "${packageName}" declares a Node.js version range ("${pluginNodeVersionRange}") that does not include Node.js 22. Please upgrade the plugin so it can be run on Node.js 22.`);
71
+ systemLog(`plugin "${packageName}" node support range does NOT include v22`);
72
+ }
73
+ }
74
+ else {
75
+ systemLog(`plugin "${packageName}" pluginPath not available`);
76
+ }
77
+ }
35
78
  if (semver.satisfies(userNodeVersion, MINIMUM_REQUIRED_NODE_VERSION)) {
36
79
  return userNode;
37
80
  }
38
81
  logWarningSubHeader(logs, `Warning: ${packageName} will be executed with Node.js version ${currentNodeVersion}`);
39
82
  logWarning(logs, ` The plugin cannot be executed with your defined Node.js version ${userNodeVersion}
40
83
 
41
- Read more about our minimum required version in our ${link('forums announcement', 'https://answers.netlify.com/t/build-plugins-end-of-support-for-node-js-14-node-js-16/136405')}`);
84
+ Read more about our minimum required version in our ${link('forums announcement', 'https://answers.netlify.com/t/build-plugins-end-of-support-for-node-js-18-node-js-20/162662')}`);
42
85
  return systemNode;
43
86
  };
@@ -15,10 +15,12 @@ export const resolvePluginsPath = async function ({ pluginsOptions, siteInfo, bu
15
15
  const autoPluginsDir = getAutoPluginsDir(buildDir, packagePath);
16
16
  const pluginsOptionsA = await Promise.all(pluginsOptions.map((pluginOptions) => resolvePluginPath({ pluginOptions, buildDir, packagePath, autoPluginsDir })));
17
17
  const pluginsOptionsB = await addPluginsNodeVersion({
18
+ featureFlags,
18
19
  pluginsOptions: pluginsOptionsA,
19
20
  nodePath,
20
21
  userNodeVersion,
21
22
  logs,
23
+ systemLog,
22
24
  });
23
25
  const pluginsOptionsC = await addPinnedVersions({ pluginsOptions: pluginsOptionsB, api, siteInfo, sendStatus });
24
26
  const pluginsOptionsD = await addExpectedVersions({
@@ -1,40 +1,8 @@
1
1
  import { zipFunctions, type FunctionResult } from '@netlify/zip-it-and-ship-it';
2
+ import type { CoreStepFunction } from '../types.js';
2
3
  export declare const bundleFunctions: {
3
4
  event: string;
4
- coreStep: ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC: relativeInternalFunctionsSrc, IS_LOCAL: isRunningLocally, FUNCTIONS_SRC: relativeFunctionsSrc, FUNCTIONS_DIST: relativeFunctionsDist, }, buildDir, branch, packagePath, logs, netlifyConfig, featureFlags, repositoryRoot, userNodeVersion, systemLog, returnValues, }: {
5
- childEnv: any;
6
- constants: {
7
- INTERNAL_FUNCTIONS_SRC: any;
8
- IS_LOCAL: any;
9
- FUNCTIONS_SRC: any;
10
- FUNCTIONS_DIST: any;
11
- };
12
- buildDir: any;
13
- branch: any;
14
- packagePath: any;
15
- logs: any;
16
- netlifyConfig: any;
17
- featureFlags: any;
18
- repositoryRoot: any;
19
- userNodeVersion: any;
20
- systemLog: any;
21
- returnValues: any;
22
- }) => Promise<{
23
- tags?: undefined;
24
- metrics?: undefined;
25
- } | {
26
- tags: {
27
- bundler: ("zisi" | "nft" | "esbuild" | "none" | "esbuild_zisi")[];
28
- };
29
- metrics: {
30
- type: string;
31
- name: string;
32
- value: number;
33
- tags: {
34
- type: string;
35
- };
36
- }[];
37
- }>;
5
+ coreStep: CoreStepFunction;
38
6
  coreStepId: string;
39
7
  coreStepName: string;
40
8
  coreStepDescription: () => string;
@@ -1,20 +1,14 @@
1
1
  import { resolve } from 'path';
2
- import { RUNTIME, zipFunctions } from '@netlify/zip-it-and-ship-it';
2
+ import { zipFunctions } from '@netlify/zip-it-and-ship-it';
3
3
  import { pathExists } from 'path-exists';
4
4
  import { addErrorInfo } from '../../error/info.js';
5
5
  import { log } from '../../log/logger.js';
6
6
  import { getGeneratedFunctions } from '../../steps/return_values.js';
7
- import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle } from '../../log/messages/core_steps.js';
7
+ import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle, trackBundleResults, } from '../../log/messages/core_steps.js';
8
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';
12
- // Get a list of all unique bundlers in this run
13
- const getBundlers = (results = []) =>
14
- // using a Set to filter duplicates
15
- new Set(results
16
- .map((bundle) => (bundle.runtime === RUNTIME.JAVASCRIPT ? bundle.bundler : null))
17
- .filter(Boolean));
18
12
  // see https://docs.netlify.com/functions/trigger-on-events/#available-triggers
19
13
  const eventTriggeredFunctions = new Set([
20
14
  'deploy-building',
@@ -74,9 +68,9 @@ const zipFunctionsAndLogResults = async ({ branch, buildDir, childEnv, featureFl
74
68
  },
75
69
  }, functionsDist, zisiParameters);
76
70
  validateCustomRoutes(results);
77
- const bundlers = Array.from(getBundlers(results));
78
71
  logBundleResults({ logs, results });
79
- return { bundlers };
72
+ const summary = trackBundleResults({ results, systemLog });
73
+ return summary;
80
74
  }
81
75
  catch (error) {
82
76
  throw await getZipError(error, functionsSrc);
@@ -86,7 +80,7 @@ const zipFunctionsAndLogResults = async ({ branch, buildDir, childEnv, featureFl
86
80
  const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC: relativeInternalFunctionsSrc, IS_LOCAL: isRunningLocally, FUNCTIONS_SRC: relativeFunctionsSrc, FUNCTIONS_DIST: relativeFunctionsDist, }, buildDir, branch, packagePath, logs, netlifyConfig, featureFlags, repositoryRoot, userNodeVersion, systemLog, returnValues, }) {
87
81
  const functionsSrc = relativeFunctionsSrc === undefined ? undefined : resolve(buildDir, relativeFunctionsSrc);
88
82
  const functionsDist = resolve(buildDir, relativeFunctionsDist);
89
- const internalFunctionsSrc = resolve(buildDir, relativeInternalFunctionsSrc);
83
+ const internalFunctionsSrc = resolve(buildDir, relativeInternalFunctionsSrc ?? '');
90
84
  const internalFunctionsSrcExists = await pathExists(internalFunctionsSrc);
91
85
  const frameworkFunctionsSrc = resolve(buildDir, packagePath || '', FRAMEWORKS_API_FUNCTIONS_PATH);
92
86
  const frameworkFunctionsSrcExists = await pathExists(frameworkFunctionsSrc);
@@ -123,7 +117,7 @@ const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC
123
117
  generatedFunctions.length === 0) {
124
118
  return {};
125
119
  }
126
- const { bundlers } = await zipFunctionsAndLogResults({
120
+ const { bundlers, fallbackCount, warningsCount } = await zipFunctionsAndLogResults({
127
121
  branch,
128
122
  buildDir,
129
123
  childEnv,
@@ -140,10 +134,14 @@ const coreStep = async function ({ childEnv, constants: { INTERNAL_FUNCTIONS_SRC
140
134
  systemLog,
141
135
  generatedFunctions: generatedFunctions.map((func) => func.path),
142
136
  });
143
- const metrics = getMetrics(internalFunctions, userFunctions);
137
+ const fallback = fallbackCount > 0 ? 'true' : 'false';
138
+ const warnings = warningsCount > 0 ? 'true' : 'false';
139
+ const metrics = getMetrics(internalFunctions, userFunctions, { bundlers, fallback, warnings });
144
140
  return {
145
141
  tags: {
146
142
  bundler: bundlers,
143
+ fallback,
144
+ warnings,
147
145
  },
148
146
  metrics,
149
147
  };
@@ -203,19 +201,19 @@ export const zipItAndShipIt = {
203
201
  return await zipFunctions(...args);
204
202
  },
205
203
  };
206
- const getMetrics = (internalFunctions, userFunctions) => {
204
+ const getMetrics = (internalFunctions, userFunctions, { bundlers, fallback, warnings }) => {
207
205
  return [
208
206
  {
209
207
  type: 'increment',
210
208
  name: 'buildbot.build.functions',
211
209
  value: internalFunctions.length,
212
- tags: { type: 'lambda:generated' },
210
+ tags: { type: 'lambda:generated', bundler: bundlers, fallback, warnings },
213
211
  },
214
212
  {
215
213
  type: 'increment',
216
214
  name: 'buildbot.build.functions',
217
215
  value: userFunctions.length,
218
- tags: { type: 'lambda:user' },
216
+ tags: { type: 'lambda:user', bundler: bundlers, fallback, warnings },
219
217
  },
220
218
  ];
221
219
  };
@@ -1,7 +1,8 @@
1
1
  import { type DynamicMethods } from 'packages/js-client/lib/types.js';
2
- import { NetlifyPluginConstants } from '../core/constants.js';
3
- import { BufferedLogs } from '../log/logger.js';
4
- import { NetlifyConfig } from '../types/config/netlify_config.js';
2
+ import type { NetlifyPluginConstants } from '../core/constants.js';
3
+ import type { BufferedLogs } from '../log/logger.js';
4
+ import type { NetlifyConfig } from '../types/config/netlify_config.js';
5
+ import type { ReturnValue } from '../steps/return_values.js';
5
6
  type $TSFixme = any;
6
7
  export type CoreStepFunctionArgs = {
7
8
  /**
@@ -46,6 +47,9 @@ export type CoreStepFunctionArgs = {
46
47
  isSecret: boolean;
47
48
  scopes: string[];
48
49
  }[];
50
+ userNodeVersion?: string;
51
+ childEnv: $TSFixme;
52
+ returnValues: Record<string, ReturnValue>;
49
53
  buildbotServerSocket?: string;
50
54
  api: DynamicMethods;
51
55
  };
@@ -1,9 +1,25 @@
1
1
  type GlobPattern = string;
2
2
  type FunctionsObject = {
3
+ /**
4
+ * marks the function as a [background function](https://docs.netlify.com/functions/background-functions/), which is invoked asynchronously and can run for up to 15 minutes.
5
+ */
6
+ background?: boolean;
3
7
  /**
4
8
  * a list of additional paths to include in the function bundle. Although our build system includes statically referenced files (like `import * from "./some-file.js"`) by default, `included_files` lets you specify additional files or directories and reference them dynamically in function code. You can use `*` to match any character or prefix an entry with `!` to exclude files. Paths are relative to the [base directory](https://docs.netlify.com/configure-builds/get-started/#definitions-1).
5
9
  */
6
10
  included_files?: string[];
11
+ /**
12
+ * the amount of memory allocated to the function, expressed either as a number of MB or as a string with a unit (e.g. `"2gb"`). Mutually exclusive with `vcpu`.
13
+ */
14
+ memory?: number | string;
15
+ /**
16
+ * the [region](https://docs.netlify.com/functions/optional-configuration/#region) the function should run in, identified by its airport code (e.g. `"cmh"`).
17
+ */
18
+ region?: string;
19
+ /**
20
+ * the number of vCPUs allocated to the function (between `0.5` and `2`). Mutually exclusive with `memory`.
21
+ */
22
+ vcpu?: number;
7
23
  } & ({
8
24
  /**
9
25
  * the function bundling method used in [`@netlify/zip-it-and-ship-it`](https://github.com/netlify/zip-it-and-ship-it).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/build",
3
- "version": "35.13.8",
3
+ "version": "35.14.0-node22eos",
4
4
  "description": "Netlify build module",
5
5
  "type": "module",
6
6
  "exports": "./lib/index.js",
@@ -70,7 +70,7 @@
70
70
  "@netlify/blobs": "^10.4.4",
71
71
  "@netlify/cache-utils": "^6.0.5",
72
72
  "@netlify/config": "^24.6.0",
73
- "@netlify/edge-bundler": "14.10.2",
73
+ "@netlify/edge-bundler": "14.10.3",
74
74
  "@netlify/functions-utils": "^6.2.34",
75
75
  "@netlify/git-utils": "^6.0.4",
76
76
  "@netlify/opentelemetry-utils": "^2.0.2",
@@ -151,6 +151,5 @@
151
151
  },
152
152
  "engines": {
153
153
  "node": ">=18.14.0"
154
- },
155
- "gitHead": "0e2b212c4ffae1e07159374df9991584e5b4bb0b"
154
+ }
156
155
  }