@netlify/build 29.12.8 → 29.14.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/core/build.js CHANGED
@@ -12,6 +12,7 @@ import { reportStatuses } from '../status/report.js';
12
12
  import { getDevSteps, getSteps } from '../steps/get.js';
13
13
  import { runSteps } from '../steps/run_steps.js';
14
14
  import { initTimers, measureDuration } from '../time/main.js';
15
+ import { startTracing } from '../tracing/main.js';
15
16
  import { getConfigOpts, loadConfig } from './config.js';
16
17
  import { getConstants } from './constants.js';
17
18
  import { doDryRun } from './dry.js';
@@ -27,11 +28,12 @@ export const startBuild = function (flags) {
27
28
  if (!flags.quiet) {
28
29
  logBuildStart(logs);
29
30
  }
30
- const { bugsnagKey, ...flagsA } = normalizeFlags(flags, logs);
31
- const errorMonitor = startErrorMonitor({ flags: flagsA, logs, bugsnagKey });
31
+ const { bugsnagKey, tracing, ...flagsA } = normalizeFlags(flags, logs);
32
+ const errorMonitor = startErrorMonitor({ flags: { tracing, ...flagsA }, logs, bugsnagKey });
33
+ startTracing(tracing);
32
34
  return { ...flagsA, errorMonitor, logs, timers };
33
35
  };
34
- const tExecBuild = async function ({ config, defaultConfig, cachedConfig, cachedConfigPath, outputConfigPath, cwd, repositoryRoot, apiHost, token, siteId, context, branch, baseRelDir, env: envOpt, debug, systemLogFile, verbose, nodePath, functionsDistDir, edgeFunctionsDistDir, cacheDir, dry, mode, offline, deployId, buildId, testOpts, errorMonitor, errorParams, logs, timers, buildbotServerSocket, sendStatus, saveConfig, featureFlags, timeline, devCommand, quiet, framework, }) {
36
+ const tExecBuild = async function ({ config, defaultConfig, cachedConfig, cachedConfigPath, outputConfigPath, cwd, repositoryRoot, apiHost, token, siteId, context, branch, baseRelDir, env: envOpt, debug, systemLogFile, verbose, nodePath, functionsDistDir, edgeFunctionsDistDir, cacheDir, dry, mode, offline, deployId, buildId, testOpts, errorMonitor, errorParams, logs, timers, buildbotServerSocket, sendStatus, saveConfig, featureFlags, timeline, devCommand, quiet, framework, explicitSecretKeys, }) {
35
37
  const configOpts = getConfigOpts({
36
38
  config,
37
39
  defaultConfig,
@@ -126,6 +128,7 @@ const tExecBuild = async function ({ config, defaultConfig, cachedConfig, cached
126
128
  timeline,
127
129
  devCommand,
128
130
  quiet,
131
+ explicitSecretKeys,
129
132
  });
130
133
  return {
131
134
  pluginsOptions: pluginsOptionsA,
@@ -140,7 +143,7 @@ const tExecBuild = async function ({ config, defaultConfig, cachedConfig, cached
140
143
  };
141
144
  export const execBuild = measureDuration(tExecBuild, 'total', { parentTag: 'build_site' });
142
145
  // Runs a build then report any plugin statuses
143
- export const runAndReportBuild = async function ({ pluginsOptions, netlifyConfig, configOpts, siteInfo, configPath, outputConfigPath, headersPath, redirectsPath, buildDir, repositoryRoot, nodePath, packageJson, userNodeVersion, childEnv, context, branch, buildbotServerSocket, constants, dry, mode, api, errorMonitor, deployId, errorParams, logs, debug, systemLog, verbose, timers, sendStatus, saveConfig, testOpts, featureFlags, timeline, devCommand, quiet, }) {
146
+ export const runAndReportBuild = async function ({ pluginsOptions, netlifyConfig, configOpts, siteInfo, configPath, outputConfigPath, headersPath, redirectsPath, buildDir, repositoryRoot, nodePath, packageJson, userNodeVersion, childEnv, context, branch, buildbotServerSocket, constants, dry, mode, api, errorMonitor, deployId, errorParams, logs, debug, systemLog, verbose, timers, sendStatus, saveConfig, testOpts, featureFlags, timeline, devCommand, quiet, explicitSecretKeys, }) {
144
147
  try {
145
148
  const { stepsCount, netlifyConfig: netlifyConfigA, statuses, pluginsOptions: pluginsOptionsA, failedPlugins, timers: timersA, configMutations, metrics, } = await initAndRunBuild({
146
149
  pluginsOptions,
@@ -179,6 +182,7 @@ export const runAndReportBuild = async function ({ pluginsOptions, netlifyConfig
179
182
  timeline,
180
183
  devCommand,
181
184
  quiet,
185
+ explicitSecretKeys,
182
186
  });
183
187
  await Promise.all([
184
188
  reportStatuses({
@@ -239,7 +243,7 @@ export const runAndReportBuild = async function ({ pluginsOptions, netlifyConfig
239
243
  }
240
244
  };
241
245
  // Initialize plugin processes then runs a build
242
- const initAndRunBuild = async function ({ pluginsOptions, netlifyConfig, configOpts, siteInfo, configPath, outputConfigPath, headersPath, redirectsPath, buildDir, repositoryRoot, nodePath, packageJson, userNodeVersion, childEnv, context, branch, dry, mode, api, errorMonitor, deployId, errorParams, logs, debug, systemLog, verbose, sendStatus, saveConfig, timers, testOpts, buildbotServerSocket, constants, featureFlags, timeline, devCommand, quiet, }) {
246
+ const initAndRunBuild = async function ({ pluginsOptions, netlifyConfig, configOpts, siteInfo, configPath, outputConfigPath, headersPath, redirectsPath, buildDir, repositoryRoot, nodePath, packageJson, userNodeVersion, childEnv, context, branch, dry, mode, api, errorMonitor, deployId, errorParams, logs, debug, systemLog, verbose, sendStatus, saveConfig, timers, testOpts, buildbotServerSocket, constants, featureFlags, timeline, devCommand, quiet, explicitSecretKeys, }) {
243
247
  const { pluginsOptions: pluginsOptionsA, timers: timersA } = await getPluginsOptions({
244
248
  pluginsOptions,
245
249
  netlifyConfig,
@@ -305,6 +309,7 @@ const initAndRunBuild = async function ({ pluginsOptions, netlifyConfig, configO
305
309
  timeline,
306
310
  devCommand,
307
311
  quiet,
312
+ explicitSecretKeys,
308
313
  });
309
314
  await Promise.all([
310
315
  warnOnMissingSideFiles({ buildDir, netlifyConfig: netlifyConfigA, logs }),
@@ -332,7 +337,7 @@ const initAndRunBuild = async function ({ pluginsOptions, netlifyConfig, configO
332
337
  };
333
338
  // Load plugin main files, retrieve their event handlers then runs them,
334
339
  // together with the build command
335
- const runBuild = async function ({ childProcesses, pluginsOptions, netlifyConfig, configOpts, packageJson, configPath, outputConfigPath, userNodeVersion, headersPath, redirectsPath, buildDir, repositoryRoot, nodePath, childEnv, context, branch, dry, buildbotServerSocket, constants, mode, api, errorMonitor, deployId, errorParams, logs, debug, systemLog, verbose, saveConfig, timers, testOpts, featureFlags, timeline, devCommand, quiet, }) {
340
+ const runBuild = async function ({ childProcesses, pluginsOptions, netlifyConfig, configOpts, packageJson, configPath, outputConfigPath, userNodeVersion, headersPath, redirectsPath, buildDir, repositoryRoot, nodePath, childEnv, context, branch, dry, buildbotServerSocket, constants, mode, api, errorMonitor, deployId, errorParams, logs, debug, systemLog, verbose, saveConfig, timers, testOpts, featureFlags, timeline, devCommand, quiet, explicitSecretKeys, }) {
336
341
  const { pluginsSteps, timers: timersA } = await loadPlugins({
337
342
  pluginsOptions,
338
343
  childProcesses,
@@ -379,6 +384,7 @@ const runBuild = async function ({ childProcesses, pluginsOptions, netlifyConfig
379
384
  featureFlags,
380
385
  quiet,
381
386
  userNodeVersion,
387
+ explicitSecretKeys,
382
388
  });
383
389
  return {
384
390
  stepsCount,
package/lib/core/flags.js CHANGED
@@ -189,6 +189,40 @@ Default: false`,
189
189
  describe: 'Statsd port',
190
190
  hidden: true,
191
191
  },
192
+ tracing: {
193
+ describe: 'Tracing related options',
194
+ hidden: true,
195
+ },
196
+ 'tracing.enabled': {
197
+ boolean: true,
198
+ describe: 'Enable distributed tracing for build',
199
+ hidden: true,
200
+ },
201
+ 'tracing.host': {
202
+ string: true,
203
+ describe: 'Traces backend host',
204
+ hidden: true,
205
+ },
206
+ 'tracing.port': {
207
+ number: true,
208
+ describe: 'Traces backend port',
209
+ hidden: true,
210
+ },
211
+ 'tracing.traceId': {
212
+ string: true,
213
+ describe: 'Trace ID used to stitch the emited traces to',
214
+ hidden: true,
215
+ },
216
+ 'tracing.parentSpanId': {
217
+ string: true,
218
+ describe: 'Parent Span ID used to stitch the root parent span to',
219
+ hidden: true,
220
+ },
221
+ 'tracing.traceFlags': {
222
+ number: true,
223
+ describe: 'Trace flags containing the trace settings for the given trace ID',
224
+ hidden: true,
225
+ },
192
226
  offline: {
193
227
  boolean: true,
194
228
  describe: `Do not send requests to the Netlify API to retrieve site settings.
@@ -203,4 +237,9 @@ Default: false`,
203
237
  describe: 'The sequence of lifecycle events to run',
204
238
  hidden: true,
205
239
  },
240
+ explicitSecretKeys: {
241
+ type: 'string',
242
+ describe: 'Env var keys that are marked as secret explicitly.',
243
+ hidden: true,
244
+ },
206
245
  };
package/lib/core/main.js CHANGED
@@ -1,14 +1,17 @@
1
+ import { trace } from '@opentelemetry/api';
1
2
  import { handleBuildError } from '../error/handle.js';
2
3
  import { reportError } from '../error/report.js';
3
4
  import { getSystemLogger } from '../log/logger.js';
4
5
  import { logTimer, logBuildSuccess } from '../log/messages/core.js';
5
6
  import { trackBuildComplete } from '../telemetry/main.js';
6
7
  import { reportTimers } from '../time/report.js';
8
+ import { stopTracing, setMultiSpanAttributes } from '../tracing/main.js';
7
9
  import { execBuild, startBuild } from './build.js';
8
10
  import { reportMetrics } from './report_metrics.js';
9
11
  import { getSeverity } from './severity.js';
10
12
  export { startDev } from './dev.js';
11
13
  export { runCoreSteps } from '../steps/run_core_steps.js';
14
+ const tracer = trace.getTracer('core');
12
15
  /**
13
16
  * Main entry point of Netlify Build.
14
17
  * Runs a builds and returns whether it succeeded or not.
@@ -19,67 +22,91 @@ export default async function buildSite(flags = {}) {
19
22
  const { errorMonitor, framework, mode, logs, debug, systemLogFile, testOpts, statsdOpts, dry, telemetry, buildId, deployId, ...flagsA } = startBuild(flags);
20
23
  const errorParams = { errorMonitor, mode, logs, debug, testOpts };
21
24
  const systemLog = getSystemLogger(logs, debug, systemLogFile);
22
- try {
23
- const { pluginsOptions, netlifyConfig: netlifyConfigA, siteInfo, userNodeVersion, stepsCount, timers, durationNs, configMutations, metrics, } = await execBuild({
24
- ...flagsA,
25
- buildId,
26
- systemLogFile,
27
- deployId,
28
- dry,
29
- errorMonitor,
30
- mode,
31
- logs,
32
- debug,
33
- testOpts,
34
- errorParams,
35
- framework,
36
- });
37
- await handleBuildSuccess({
38
- framework,
39
- dry,
40
- logs,
41
- timers,
42
- durationNs,
43
- statsdOpts,
44
- systemLog,
45
- metrics,
46
- });
47
- const { success, severityCode, status } = getSeverity('success');
48
- await telemetryReport({
49
- buildId,
50
- deployId,
51
- status,
52
- stepsCount,
53
- pluginsOptions,
54
- durationNs,
55
- siteInfo,
56
- telemetry,
57
- userNodeVersion,
58
- framework,
59
- testOpts,
60
- errorParams,
61
- });
62
- return { success, severityCode, netlifyConfig: netlifyConfigA, logs, configMutations };
63
- }
64
- catch (error) {
65
- const { severity } = await handleBuildError(error, errorParams);
66
- const { pluginsOptions, siteInfo, userNodeVersion } = errorParams;
67
- const { success, severityCode, status } = getSeverity(severity);
68
- await telemetryReport({
69
- buildId,
70
- deployId,
71
- status,
72
- pluginsOptions,
73
- siteInfo,
74
- telemetry,
75
- userNodeVersion,
76
- framework,
77
- testOpts,
78
- errorParams,
79
- });
80
- await reportError(error, statsdOpts, framework);
81
- return { success, severityCode, logs };
82
- }
25
+ const attributes = {
26
+ 'deploy.id': deployId,
27
+ 'build.id': buildId,
28
+ 'deploy.context': flagsA.context,
29
+ 'site.id': flagsA.siteId,
30
+ };
31
+ const rootCtx = setMultiSpanAttributes(attributes);
32
+ return await tracer.startActiveSpan('exec-build', {}, rootCtx, async (span) => {
33
+ try {
34
+ const { pluginsOptions, netlifyConfig: netlifyConfigA, siteInfo, userNodeVersion, stepsCount, timers, durationNs, configMutations, metrics, } = await execBuild({
35
+ ...flagsA,
36
+ buildId,
37
+ systemLogFile,
38
+ deployId,
39
+ dry,
40
+ errorMonitor,
41
+ mode,
42
+ logs,
43
+ debug,
44
+ testOpts,
45
+ errorParams,
46
+ framework,
47
+ });
48
+ await handleBuildSuccess({
49
+ framework,
50
+ dry,
51
+ logs,
52
+ timers,
53
+ durationNs,
54
+ statsdOpts,
55
+ systemLog,
56
+ metrics,
57
+ });
58
+ const { success, severityCode, status } = getSeverity('success');
59
+ span.setAttributes({
60
+ 'build.execution.success': success,
61
+ 'build.execution.code': severityCode,
62
+ 'build.execution.status': status,
63
+ });
64
+ await telemetryReport({
65
+ buildId,
66
+ deployId,
67
+ status,
68
+ stepsCount,
69
+ pluginsOptions,
70
+ durationNs,
71
+ siteInfo,
72
+ telemetry,
73
+ userNodeVersion,
74
+ framework,
75
+ testOpts,
76
+ errorParams,
77
+ });
78
+ return { success, severityCode, netlifyConfig: netlifyConfigA, logs, configMutations };
79
+ }
80
+ catch (error) {
81
+ const { severity } = await handleBuildError(error, errorParams);
82
+ const { pluginsOptions, siteInfo, userNodeVersion } = errorParams;
83
+ const { success, severityCode, status } = getSeverity(severity);
84
+ span.setAttributes({
85
+ 'build.execution.success': success,
86
+ 'build.execution.code': severityCode,
87
+ 'build.execution.status': status,
88
+ });
89
+ await telemetryReport({
90
+ buildId,
91
+ deployId,
92
+ status,
93
+ pluginsOptions,
94
+ siteInfo,
95
+ telemetry,
96
+ userNodeVersion,
97
+ framework,
98
+ testOpts,
99
+ errorParams,
100
+ });
101
+ await reportError(error, statsdOpts, framework);
102
+ return { success, severityCode, logs };
103
+ }
104
+ finally {
105
+ span.end();
106
+ // Ensure we flush the resulting spans
107
+ await stopTracing();
108
+ }
109
+ });
83
110
  }
84
111
  // Logs and reports that a build successfully ended
85
112
  const handleBuildSuccess = async function ({ framework, dry, logs, timers, durationNs, statsdOpts, systemLog, metrics, }) {
@@ -7,6 +7,7 @@ const DEFAULT_EDGE_FUNCTIONS_DIST = '.netlify/edge-functions-dist/';
7
7
  const DEFAULT_FUNCTIONS_DIST = '.netlify/functions/';
8
8
  const DEFAULT_CACHE_DIR = '.netlify/cache/';
9
9
  const DEFAULT_STATSD_PORT = 8125;
10
+ const DEFAULT_OTEL_TRACING_PORT = 4317;
10
11
  /** Normalize CLI flags */
11
12
  export const normalizeFlags = function (flags, logs) {
12
13
  const rawFlags = removeFalsy(flags);
@@ -20,6 +21,7 @@ export const normalizeFlags = function (flags, logs) {
20
21
  ...rawFlags,
21
22
  ...telemetryFlag,
22
23
  statsdOpts: { ...defaultFlags.statsd, ...rawFlags.statsd },
24
+ tracingOpts: { ...defaultFlags.tracing, ...rawFlags.tracing },
23
25
  featureFlags: { ...defaultFlags.featureFlags, ...rawFlags.featureFlags },
24
26
  };
25
27
  const normalizedFlags = removeFalsy(mergedFlags);
@@ -51,6 +53,7 @@ const getDefaultFlags = function ({ env: envOpt = {} }, combinedEnv) {
51
53
  testOpts: {},
52
54
  featureFlags: DEFAULT_FEATURE_FLAGS,
53
55
  statsd: { port: DEFAULT_STATSD_PORT },
56
+ tracing: { enabled: false, port: DEFAULT_OTEL_TRACING_PORT },
54
57
  timeline: 'build',
55
58
  quiet: false,
56
59
  };
package/lib/core/types.js CHANGED
@@ -5,4 +5,4 @@ export var SeverityCode;
5
5
  SeverityCode[SeverityCode["userError"] = 3] = "userError";
6
6
  SeverityCode[SeverityCode["pluginError"] = 4] = "pluginError";
7
7
  SeverityCode[SeverityCode["systemError"] = 5] = "systemError";
8
- })(SeverityCode = SeverityCode || (SeverityCode = {}));
8
+ })(SeverityCode || (SeverityCode = {}));
@@ -1,5 +1,6 @@
1
1
  import { isNetlifyMaintainedPlugin } from '../plugins/internal.js';
2
2
  import { closeClient, formatTags, normalizeTagName, startClient, validateStatsDOptions, } from '../report/statsd.js';
3
+ import { addErrorToActiveSpan } from '../tracing/main.js';
3
4
  import { getErrorInfo } from './info.js';
4
5
  const TOP_PARENT_TAG = 'run_netlify_build';
5
6
  /**
@@ -7,6 +8,7 @@ const TOP_PARENT_TAG = 'run_netlify_build';
7
8
  * Sends to statsd daemon.
8
9
  */
9
10
  export const reportError = async function (error, statsdOpts, framework) {
11
+ addErrorToActiveSpan(error);
10
12
  if (!validateStatsDOptions(statsdOpts)) {
11
13
  return;
12
14
  }
@@ -49,6 +49,7 @@ const INTERNAL_FLAGS = [
49
49
  'cacheDir',
50
50
  'systemLogFile',
51
51
  'timeline',
52
+ 'explicitSecretKeys',
52
53
  ];
53
54
  const HIDDEN_FLAGS = [...SECURE_FLAGS, ...TEST_FLAGS, ...INTERNAL_FLAGS];
54
55
  const HIDDEN_DEBUG_FLAGS = [...SECURE_FLAGS, ...TEST_FLAGS];
@@ -2,7 +2,7 @@ import { setEnvChanges } from '../env/changes.js';
2
2
  import { addErrorInfo, isBuildError } from '../error/info.js';
3
3
  import { updateNetlifyConfig, listConfigSideFiles } from './update_config.js';
4
4
  // Fire a core step
5
- export const fireCoreStep = async function ({ coreStep, coreStepId, coreStepName, configPath, outputConfigPath, buildDir, repositoryRoot, constants, buildbotServerSocket, events, logs, nodePath, childEnv, context, branch, envChanges, errorParams, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, featureFlags, debug, systemLog, saveConfig, userNodeVersion, }) {
5
+ export const fireCoreStep = async function ({ coreStep, coreStepId, coreStepName, configPath, outputConfigPath, buildDir, repositoryRoot, constants, buildbotServerSocket, events, logs, nodePath, childEnv, context, branch, envChanges, errorParams, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, featureFlags, debug, systemLog, saveConfig, userNodeVersion, explicitSecretKeys, }) {
6
6
  try {
7
7
  const configSideFiles = await listConfigSideFiles([headersPath, redirectsPath]);
8
8
  const childEnvA = setEnvChanges(envChanges, { ...childEnv });
@@ -28,6 +28,7 @@ export const fireCoreStep = async function ({ coreStep, coreStepId, coreStepName
28
28
  systemLog,
29
29
  saveConfig,
30
30
  userNodeVersion,
31
+ explicitSecretKeys,
31
32
  });
32
33
  const { netlifyConfig: netlifyConfigA, configMutations: configMutationsA, headersPath: headersPathA, redirectsPath: redirectsPathA, } = await updateNetlifyConfig({
33
34
  configOpts,
@@ -3,6 +3,7 @@ import { handleBuildError } from '../error/handle.js';
3
3
  import { getFullErrorInfo, parseErrorInfo } from '../error/parse/parse.js';
4
4
  import { serializeErrorStatus } from '../error/parse/serialize_status.js';
5
5
  import { isSoftFailEvent } from '../plugins/events.js';
6
+ import { addErrorToActiveSpan } from '../tracing/main.js';
6
7
  // Handle build command errors and plugin errors:
7
8
  // - usually, propagate the error to make the build stop.
8
9
  // - `utils.build.cancelBuild()` also cancels the build by calling the API
@@ -11,6 +12,7 @@ import { isSoftFailEvent } from '../plugins/events.js';
11
12
  // plugin.
12
13
  // This also computes error statuses that are sent to the API.
13
14
  export const handleStepError = function ({ event, newError, childEnv, mode, api, errorMonitor, deployId, coreStep, netlifyConfig, logs, debug, testOpts, }) {
15
+ addErrorToActiveSpan(newError);
14
16
  // Core steps do not report error statuses
15
17
  if (coreStep !== undefined) {
16
18
  return { newError };
@@ -1,98 +1,120 @@
1
+ import { trace } from '@opentelemetry/api';
1
2
  import { addMutableConstants } from '../core/constants.js';
2
3
  import { logStepStart } from '../log/messages/steps.js';
3
4
  import { runsAlsoOnBuildFailure, runsOnlyOnBuildFailure } from '../plugins/events.js';
4
5
  import { normalizeTagName } from '../report/statsd.js';
5
6
  import { measureDuration } from '../time/main.js';
7
+ import { setMultiSpanAttributes } from '../tracing/main.js';
6
8
  import { fireCoreStep } from './core_step.js';
7
9
  import { firePluginStep } from './plugin.js';
8
10
  import { getStepReturn } from './return.js';
11
+ const tracer = trace.getTracer('steps');
9
12
  // Run a step (core, build command or plugin)
10
- export const runStep = async function ({ event, childProcess, packageName, coreStep, coreStepId, coreStepName, coreStepDescription, pluginPackageJson, loadedFrom, origin, condition, configPath, outputConfigPath, buildDir, repositoryRoot, nodePath, index, childEnv, context, branch, envChanges, constants, steps, buildbotServerSocket, events, mode, api, errorMonitor, deployId, errorParams, error, failedPlugins, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, logs, debug, systemLog, verbose, saveConfig, timers, testOpts, featureFlags, quiet, userNodeVersion, }) {
11
- const constantsA = await addMutableConstants({ constants, buildDir, netlifyConfig });
12
- if (!(await shouldRunStep({
13
- event,
14
- packageName,
15
- error,
16
- failedPlugins,
17
- netlifyConfig,
18
- condition,
19
- constants: constantsA,
20
- buildbotServerSocket,
21
- buildDir,
22
- saveConfig,
23
- }))) {
24
- return {};
25
- }
26
- if (!quiet) {
27
- logStepStart({ logs, event, packageName, coreStepDescription, error, netlifyConfig });
28
- }
29
- const fireStep = getFireStep(packageName, coreStepId, event);
30
- const { newEnvChanges, netlifyConfig: netlifyConfigA = netlifyConfig, configMutations: configMutationsA = configMutations, headersPath: headersPathA = headersPath, redirectsPath: redirectsPathA = redirectsPath, newError, newStatus, timers: timersA, durationNs, metrics, } = await fireStep({
31
- event,
32
- childProcess,
33
- packageName,
34
- pluginPackageJson,
35
- loadedFrom,
36
- origin,
37
- coreStep,
38
- coreStepId,
39
- coreStepName,
40
- configPath,
41
- outputConfigPath,
42
- buildDir,
43
- repositoryRoot,
44
- nodePath,
45
- childEnv,
46
- context,
47
- branch,
48
- envChanges,
49
- constants: constantsA,
50
- steps,
51
- buildbotServerSocket,
52
- events,
53
- error,
54
- logs,
55
- debug,
56
- systemLog,
57
- verbose,
58
- saveConfig,
59
- timers,
60
- errorParams,
61
- configOpts,
62
- netlifyConfig,
63
- configMutations,
64
- headersPath,
65
- redirectsPath,
66
- featureFlags,
67
- userNodeVersion,
68
- });
69
- const newValues = await getStepReturn({
70
- event,
71
- packageName,
72
- newError,
73
- newEnvChanges,
74
- newStatus,
75
- coreStep,
76
- coreStepName,
77
- childEnv,
78
- mode,
79
- api,
80
- errorMonitor,
81
- deployId,
82
- netlifyConfig: netlifyConfigA,
83
- configMutations: configMutationsA,
84
- headersPath: headersPathA,
85
- redirectsPath: redirectsPathA,
86
- logs,
87
- debug,
88
- timers: timersA,
89
- durationNs,
90
- testOpts,
91
- systemLog,
92
- quiet,
93
- metrics,
13
+ export const runStep = async function ({ event, childProcess, packageName, coreStep, coreStepId, coreStepName, coreStepDescription, pluginPackageJson, loadedFrom, origin, condition, configPath, outputConfigPath, buildDir, repositoryRoot, nodePath, index, childEnv, context, branch, envChanges, constants, steps, buildbotServerSocket, events, mode, api, errorMonitor, deployId, errorParams, error, failedPlugins, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, logs, debug, systemLog, verbose, saveConfig, timers, testOpts, featureFlags, quiet, userNodeVersion, explicitSecretKeys, }) {
14
+ // Add relevant attributes to the upcoming span context
15
+ const attributes = {
16
+ 'build.execution.step.name': coreStepName,
17
+ 'build.execution.step.description': coreStepDescription,
18
+ 'build.execution.step.package_name': packageName,
19
+ 'build.execution.step.id': coreStepId,
20
+ 'build.execution.step.loaded_from': loadedFrom,
21
+ 'build.execution.step.origin': origin,
22
+ 'build.execution.step.event': event,
23
+ };
24
+ const spanCtx = setMultiSpanAttributes(attributes);
25
+ return tracer.startActiveSpan(`run-step-${coreStepId}`, {}, spanCtx, async (span) => {
26
+ const constantsA = await addMutableConstants({ constants, buildDir, netlifyConfig });
27
+ const shouldRun = await shouldRunStep({
28
+ event,
29
+ packageName,
30
+ error,
31
+ failedPlugins,
32
+ netlifyConfig,
33
+ condition,
34
+ constants: constantsA,
35
+ buildbotServerSocket,
36
+ buildDir,
37
+ saveConfig,
38
+ explicitSecretKeys,
39
+ });
40
+ span.setAttribute('build.execution.step.should_run', shouldRun);
41
+ if (!shouldRun) {
42
+ span.end();
43
+ return {};
44
+ }
45
+ if (!quiet) {
46
+ logStepStart({ logs, event, packageName, coreStepDescription, error, netlifyConfig });
47
+ }
48
+ const fireStep = getFireStep(packageName, coreStepId, event);
49
+ const { newEnvChanges, netlifyConfig: netlifyConfigA = netlifyConfig, configMutations: configMutationsA = configMutations, headersPath: headersPathA = headersPath, redirectsPath: redirectsPathA = redirectsPath, newError, newStatus, timers: timersA, durationNs, metrics, } = await fireStep({
50
+ event,
51
+ childProcess,
52
+ packageName,
53
+ pluginPackageJson,
54
+ loadedFrom,
55
+ origin,
56
+ coreStep,
57
+ coreStepId,
58
+ coreStepName,
59
+ configPath,
60
+ outputConfigPath,
61
+ buildDir,
62
+ repositoryRoot,
63
+ nodePath,
64
+ childEnv,
65
+ context,
66
+ branch,
67
+ envChanges,
68
+ constants: constantsA,
69
+ steps,
70
+ buildbotServerSocket,
71
+ events,
72
+ error,
73
+ logs,
74
+ debug,
75
+ systemLog,
76
+ verbose,
77
+ saveConfig,
78
+ timers,
79
+ errorParams,
80
+ configOpts,
81
+ netlifyConfig,
82
+ configMutations,
83
+ headersPath,
84
+ redirectsPath,
85
+ featureFlags,
86
+ userNodeVersion,
87
+ explicitSecretKeys,
88
+ });
89
+ const newValues = await getStepReturn({
90
+ event,
91
+ packageName,
92
+ newError,
93
+ newEnvChanges,
94
+ newStatus,
95
+ coreStep,
96
+ coreStepName,
97
+ childEnv,
98
+ mode,
99
+ api,
100
+ errorMonitor,
101
+ deployId,
102
+ netlifyConfig: netlifyConfigA,
103
+ configMutations: configMutationsA,
104
+ headersPath: headersPathA,
105
+ redirectsPath: redirectsPathA,
106
+ logs,
107
+ debug,
108
+ timers: timersA,
109
+ durationNs,
110
+ testOpts,
111
+ systemLog,
112
+ quiet,
113
+ metrics,
114
+ });
115
+ span.end();
116
+ return { ...newValues, newIndex: index + 1 };
94
117
  });
95
- return { ...newValues, newIndex: index + 1 };
96
118
  };
97
119
  // A plugin fails _without making the build fail_ when either:
98
120
  // - using `utils.build.failPlugin()`
@@ -125,10 +147,10 @@ export const runStep = async function ({ event, childProcess, packageName, coreS
125
147
  // or available. However, one might be created by a build plugin, in which case,
126
148
  // those core plugins should be triggered. We use a dynamic `condition()` to
127
149
  // model this behavior.
128
- const shouldRunStep = async function ({ event, packageName, error, failedPlugins, netlifyConfig, condition, constants, buildbotServerSocket, buildDir, saveConfig, }) {
150
+ const shouldRunStep = async function ({ event, packageName, error, failedPlugins, netlifyConfig, condition, constants, buildbotServerSocket, buildDir, saveConfig, explicitSecretKeys, }) {
129
151
  if (failedPlugins.includes(packageName) ||
130
152
  (condition !== undefined &&
131
- !(await condition({ buildDir, constants, buildbotServerSocket, netlifyConfig, saveConfig })))) {
153
+ !(await condition({ buildDir, constants, buildbotServerSocket, netlifyConfig, saveConfig, explicitSecretKeys })))) {
132
154
  return false;
133
155
  }
134
156
  if (error !== undefined) {
@@ -144,7 +166,7 @@ const getFireStep = function (packageName, coreStepId, event) {
144
166
  const parentTag = normalizeTagName(packageName);
145
167
  return measureDuration(tFireStep, event, { parentTag, category: 'pluginEvent' });
146
168
  };
147
- const tFireStep = function ({ event, childProcess, packageName, pluginPackageJson, loadedFrom, origin, coreStep, coreStepId, coreStepName, configPath, outputConfigPath, buildDir, repositoryRoot, nodePath, childEnv, context, branch, envChanges, constants, steps, buildbotServerSocket, events, error, logs, debug, systemLog, verbose, saveConfig, errorParams, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, featureFlags, userNodeVersion, }) {
169
+ const tFireStep = function ({ event, childProcess, packageName, pluginPackageJson, loadedFrom, origin, coreStep, coreStepId, coreStepName, configPath, outputConfigPath, buildDir, repositoryRoot, nodePath, childEnv, context, branch, envChanges, constants, steps, buildbotServerSocket, events, error, logs, debug, systemLog, verbose, saveConfig, errorParams, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, featureFlags, userNodeVersion, explicitSecretKeys, }) {
148
170
  if (coreStep !== undefined) {
149
171
  return fireCoreStep({
150
172
  coreStep,
@@ -174,6 +196,7 @@ const tFireStep = function ({ event, childProcess, packageName, pluginPackageJso
174
196
  systemLog,
175
197
  saveConfig,
176
198
  userNodeVersion,
199
+ explicitSecretKeys,
177
200
  });
178
201
  }
179
202
  return firePluginStep({
@@ -7,7 +7,7 @@ import { runStep } from './run_step.js';
7
7
  // list of `failedPlugins` (that ran `utils.build.failPlugin()`).
8
8
  // If an error arises, runs `onError` events.
9
9
  // Runs `onEnd` events at the end, whether an error was thrown or not.
10
- export const runSteps = async function ({ steps, buildbotServerSocket, events, configPath, outputConfigPath, headersPath, redirectsPath, buildDir, repositoryRoot, nodePath, childEnv, context, branch, constants, mode, api, errorMonitor, deployId, errorParams, netlifyConfig, configOpts, logs, debug, systemLog, verbose, saveConfig, timers, testOpts, featureFlags, quiet, userNodeVersion, }) {
10
+ export const runSteps = async function ({ steps, buildbotServerSocket, events, configPath, outputConfigPath, headersPath, redirectsPath, buildDir, repositoryRoot, nodePath, childEnv, context, branch, constants, mode, api, errorMonitor, deployId, errorParams, netlifyConfig, configOpts, logs, debug, systemLog, verbose, saveConfig, timers, testOpts, featureFlags, quiet, userNodeVersion, explicitSecretKeys, }) {
11
11
  const { index: stepsCount, error: errorA, netlifyConfig: netlifyConfigC, statuses: statusesB, failedPlugins: failedPluginsA, timers: timersC, configMutations: configMutationsB, metrics: metricsC, } = await pReduce(steps, async ({ index, error, failedPlugins, envChanges, netlifyConfig: netlifyConfigA, configMutations, headersPath: headersPathA, redirectsPath: redirectsPathA, statuses, timers: timersA, metrics: metricsA, }, { event, childProcess, packageName, coreStep, coreStepId, coreStepName, coreStepDescription, pluginPackageJson, loadedFrom, origin, condition, }) => {
12
12
  const { newIndex = index, newError = error, failedPlugin = [], newEnvChanges = {}, netlifyConfig: netlifyConfigB = netlifyConfigA, configMutations: configMutationsA = configMutations, headersPath: headersPathB = headersPathA, redirectsPath: redirectsPathB = redirectsPathA, newStatus, timers: timersB = timersA, metrics: metricsB = [], } = await runStep({
13
13
  event,
@@ -57,6 +57,7 @@ export const runSteps = async function ({ steps, buildbotServerSocket, events, c
57
57
  featureFlags,
58
58
  quiet,
59
59
  userNodeVersion,
60
+ explicitSecretKeys,
60
61
  });
61
62
  const statusesA = addStatus({ newStatus, statuses, event, packageName, pluginPackageJson });
62
63
  return {
@@ -0,0 +1,52 @@
1
+ import { HoneycombSDK } from '@honeycombio/opentelemetry-node';
2
+ import { context, trace, propagation, SpanStatusCode } from '@opentelemetry/api';
3
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
4
+ import { ROOT_PACKAGE_JSON } from '../utils/json.js';
5
+ let sdk;
6
+ /** Starts the tracing SDK, if there's already a tracing service this will be a no-op */
7
+ export const startTracing = function (options) {
8
+ if (!options.enabled)
9
+ return;
10
+ if (sdk)
11
+ return;
12
+ sdk = new HoneycombSDK({
13
+ serviceName: ROOT_PACKAGE_JSON.name,
14
+ endpoint: `http://${options.host}:${options.port}`,
15
+ instrumentations: [new HttpInstrumentation()],
16
+ });
17
+ sdk.start();
18
+ // Sets the current trace ID and span ID based on the options received
19
+ // this is used as a way to propagate trace context from Buildbot
20
+ trace.setSpanContext(context.active(), {
21
+ traceId: options.traceId,
22
+ spanId: options.parentSpanId,
23
+ traceFlags: options.traceFlags,
24
+ isRemote: true,
25
+ });
26
+ };
27
+ /** Stops the tracing service if there's one running. This will flush any ongoing events */
28
+ export const stopTracing = async function () {
29
+ if (!sdk)
30
+ return;
31
+ return sdk.shutdown();
32
+ };
33
+ /** Sets attributes to be propagated across child spans under the current context */
34
+ export const setMultiSpanAttributes = function (attributes) {
35
+ const currentBaggage = propagation.getBaggage(context.active());
36
+ let baggage = currentBaggage === undefined ? propagation.createBaggage() : currentBaggage;
37
+ Object.entries(attributes).forEach((entry) => {
38
+ baggage = baggage.setEntry(entry[0], { value: entry[1] });
39
+ });
40
+ return propagation.setBaggage(context.active(), baggage);
41
+ };
42
+ /** Add error information to the current active span (if any) */
43
+ export const addErrorToActiveSpan = function (error) {
44
+ const span = trace.getActiveSpan();
45
+ if (!span)
46
+ return;
47
+ span.recordException(error);
48
+ span.setStatus({
49
+ code: SpanStatusCode.ERROR,
50
+ message: error.message,
51
+ });
52
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/build",
3
- "version": "29.12.8",
3
+ "version": "29.14.0",
4
4
  "description": "Netlify build module",
5
5
  "type": "module",
6
6
  "exports": "./lib/core/main.js",
@@ -64,6 +64,7 @@
64
64
  "license": "MIT",
65
65
  "dependencies": {
66
66
  "@bugsnag/js": "^7.0.0",
67
+ "@honeycombio/opentelemetry-node": "^0.4.0",
67
68
  "@netlify/cache-utils": "^5.1.5",
68
69
  "@netlify/config": "^20.5.1",
69
70
  "@netlify/edge-bundler": "8.16.2",
@@ -73,6 +74,8 @@
73
74
  "@netlify/plugins-list": "^6.68.0",
74
75
  "@netlify/run-utils": "^5.1.1",
75
76
  "@netlify/zip-it-and-ship-it": "9.10.0",
77
+ "@opentelemetry/api": "^1.4.1",
78
+ "@opentelemetry/instrumentation-http": "^0.40.0",
76
79
  "@sindresorhus/slugify": "^2.0.0",
77
80
  "ansi-escapes": "^6.0.0",
78
81
  "chalk": "^5.0.0",
@@ -146,5 +149,5 @@
146
149
  "module": "commonjs"
147
150
  }
148
151
  },
149
- "gitHead": "d0f8ab39846e14532655694264da61301cd1d4c2"
152
+ "gitHead": "3ec21c7c8e5c221d96b32e739331dc44b18fe05c"
150
153
  }