@netlify/build 28.0.1 → 28.1.1

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/main.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { handleBuildError } from '../error/handle.js';
2
+ import { reportError } from '../error/report.js';
2
3
  import { logTimer, logBuildSuccess } from '../log/messages/core.js';
3
4
  import { trackBuildComplete } from '../telemetry/main.js';
4
5
  import { reportTimers } from '../time/report.js';
@@ -70,6 +71,7 @@ export default async function buildSite(flags = {}) {
70
71
  testOpts,
71
72
  errorParams,
72
73
  });
74
+ await reportError({ error, framework, statsdOpts });
73
75
  return { success, severityCode, logs };
74
76
  }
75
77
  }
@@ -0,0 +1,27 @@
1
+ import { isNetlifyMaintainedPlugin } from '../plugins/internal.js';
2
+ import { closeClient, normalizeTagName, startClient } from '../report/statsd.js';
3
+ import { getErrorInfo } from './info.js';
4
+ const TOP_PARENT_TAG = 'run_netlify_build';
5
+ // Record error rates of the build phase for monitoring.
6
+ // Sends to statsd daemon.
7
+ export const reportError = async function ({ error, statsdOpts: { host, port }, framework, }) {
8
+ if (host === undefined) {
9
+ return;
10
+ }
11
+ const [errorInfo] = getErrorInfo(error);
12
+ const pluginName = errorInfo.plugin ? normalizeTagName(errorInfo.plugin.packageName) : null;
13
+ // only send tracking if it is a known plugin
14
+ if (pluginName && !isNetlifyMaintainedPlugin(pluginName)) {
15
+ return;
16
+ }
17
+ const parent = pluginName ? pluginName : TOP_PARENT_TAG;
18
+ const stage = pluginName ? errorInfo.location?.event : errorInfo.stage;
19
+ const client = await startClient(host, port);
20
+ const frameworkTag = framework === undefined ? {} : { framework };
21
+ client.increment('buildbot.build.stage.error', 1, {
22
+ stage: stage ?? 'system',
23
+ parent,
24
+ ...frameworkTag,
25
+ });
26
+ await closeClient(client);
27
+ };
@@ -0,0 +1,10 @@
1
+ const NETLIFY_MAINTAINED_PLUGINS = new Set([
2
+ 'netlify_plugin_gatsby_cache',
3
+ 'netlify_plugin_sitemap',
4
+ 'netlify_plugin_debug_cache',
5
+ 'netlify_plugin_is_website_vulnerable',
6
+ 'netlify_plugin_lighthouse',
7
+ 'netlify_plugin_nextjs',
8
+ 'netlify_plugin_gatsby',
9
+ ]);
10
+ export const isNetlifyMaintainedPlugin = (pluginPackage) => NETLIFY_MAINTAINED_PLUGINS.has(pluginPackage);
@@ -0,0 +1,31 @@
1
+ import { promisify } from 'util';
2
+ import slugify from '@sindresorhus/slugify';
3
+ import StatsdClient from 'statsd-client';
4
+ // TODO: replace with `timers/promises` after dropping Node < 15.0.0
5
+ const pSetTimeout = promisify(setTimeout);
6
+ // See https://github.com/msiebuhr/node-statsd-client/blob/45a93ee4c94ca72f244a40b06cb542d4bd7c3766/lib/EphemeralSocket.js#L81
7
+ const CLOSE_TIMEOUT = 11;
8
+ // The socket creation is delayed until the first packet is sent. In our
9
+ // case, this happens just before `client.close()` is called, which is too
10
+ // late and make it not send anything. We need to manually create it using
11
+ // the internal API.
12
+ export const startClient = async function (host, port) {
13
+ const client = new StatsdClient({ host, port, socketTimeout: 0 });
14
+ // @ts-expect-error using internals :D
15
+ await promisify(client._socket._createSocket.bind(client._socket))();
16
+ return client;
17
+ };
18
+ // UDP packets are buffered and flushed at regular intervals by statsd-client.
19
+ // Closing force flushing all of them.
20
+ export const closeClient = async function (client) {
21
+ client.close();
22
+ // statsd-client does not provide a way of knowing when the socket is done
23
+ // closing, so we need to use the following hack.
24
+ await pSetTimeout(CLOSE_TIMEOUT);
25
+ await pSetTimeout(CLOSE_TIMEOUT);
26
+ };
27
+ // Make sure the timer name does not include special characters.
28
+ // For example, the `packageName` of local plugins includes dots.
29
+ export const normalizeTagName = function (name) {
30
+ return slugify(name, { separator: '_' });
31
+ };
@@ -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, coreStepName, configPath, buildDir, repositoryRoot, constants, buildbotServerSocket, events, logs, nodePath, childEnv, context, branch, envChanges, errorParams, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, featureFlags, debug, systemLog, saveConfig, }) {
5
+ export const fireCoreStep = async function ({ coreStep, coreStepId, coreStepName, configPath, buildDir, repositoryRoot, constants, buildbotServerSocket, events, logs, nodePath, childEnv, context, branch, envChanges, errorParams, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, featureFlags, debug, systemLog, saveConfig, }) {
6
6
  try {
7
7
  const configSideFiles = await listConfigSideFiles([headersPath, redirectsPath]);
8
8
  const childEnvA = setEnvChanges(envChanges, { ...childEnv });
@@ -52,6 +52,8 @@ export const fireCoreStep = async function ({ coreStep, coreStepName, configPath
52
52
  if (!isBuildError(newError)) {
53
53
  addErrorInfo(newError, { type: 'coreStep', location: { coreStepName } });
54
54
  }
55
+ // always add the current stage
56
+ addErrorInfo(newError, { stage: coreStepId });
55
57
  return { newError };
56
58
  }
57
59
  };
@@ -1,7 +1,8 @@
1
1
  import { addMutableConstants } from '../core/constants.js';
2
2
  import { logStepStart } from '../log/messages/steps.js';
3
3
  import { runsAlsoOnBuildFailure, runsOnlyOnBuildFailure } from '../plugins/events.js';
4
- import { measureDuration, normalizeTimerName } from '../time/main.js';
4
+ import { normalizeTagName } from '../report/statsd.js';
5
+ import { measureDuration } from '../time/main.js';
5
6
  import { fireCoreStep } from './core_step.js';
6
7
  import { firePluginStep } from './plugin.js';
7
8
  import { getStepReturn } from './return.js';
@@ -31,6 +32,7 @@ export const runStep = async function ({ event, childProcess, packageName, coreS
31
32
  loadedFrom,
32
33
  origin,
33
34
  coreStep,
35
+ coreStepId,
34
36
  coreStepName,
35
37
  configPath,
36
38
  buildDir,
@@ -78,7 +80,6 @@ export const runStep = async function ({ event, childProcess, packageName, coreS
78
80
  redirectsPath: redirectsPathA,
79
81
  logs,
80
82
  debug,
81
- systemLog,
82
83
  timers: timersA,
83
84
  durationNs,
84
85
  testOpts,
@@ -131,13 +132,14 @@ const getFireStep = function (packageName, coreStepId, event) {
131
132
  if (coreStepId !== undefined) {
132
133
  return measureDuration(tFireStep, coreStepId);
133
134
  }
134
- const parentTag = normalizeTimerName(packageName);
135
+ const parentTag = normalizeTagName(packageName);
135
136
  return measureDuration(tFireStep, event, { parentTag, category: 'pluginEvent' });
136
137
  };
137
- const tFireStep = function ({ event, childProcess, packageName, pluginPackageJson, loadedFrom, origin, coreStep, coreStepName, configPath, buildDir, repositoryRoot, nodePath, childEnv, context, branch, envChanges, constants, steps, buildbotServerSocket, events, error, logs, debug, systemLog, verbose, saveConfig, errorParams, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, featureFlags, }) {
138
+ const tFireStep = function ({ event, childProcess, packageName, pluginPackageJson, loadedFrom, origin, coreStep, coreStepId, coreStepName, configPath, buildDir, repositoryRoot, nodePath, childEnv, context, branch, envChanges, constants, steps, buildbotServerSocket, events, error, logs, debug, systemLog, verbose, saveConfig, errorParams, configOpts, netlifyConfig, configMutations, headersPath, redirectsPath, featureFlags, }) {
138
139
  if (coreStep !== undefined) {
139
140
  return fireCoreStep({
140
141
  coreStep,
142
+ coreStepId,
141
143
  coreStepName,
142
144
  configPath,
143
145
  buildDir,
@@ -182,7 +184,6 @@ const tFireStep = function ({ event, childProcess, packageName, pluginPackageJso
182
184
  error,
183
185
  logs,
184
186
  debug,
185
- systemLog,
186
187
  verbose,
187
188
  });
188
189
  };
@@ -1,3 +1,4 @@
1
+ import { isNetlifyMaintainedPlugin } from '../plugins/internal.js';
1
2
  import { createTimer, TOP_PARENT_TAG } from './main.js';
2
3
  // Some timers are computed based on others:
3
4
  // - `others` is `total` minus the other timers
@@ -53,18 +54,6 @@ const addPluginTimers = function (timers) {
53
54
  const isCommunityPluginTimer = function (timer) {
54
55
  return isPluginTimer(timer) && !isNetlifyMaintainedPlugin(getPluginTimerPackage(timer));
55
56
  };
56
- const isNetlifyMaintainedPlugin = function (pluginPackage) {
57
- return NETLIFY_MAINTAINED_PLUGINS.has(pluginPackage);
58
- };
59
- const NETLIFY_MAINTAINED_PLUGINS = new Set([
60
- 'netlify_plugin_gatsby_cache',
61
- 'netlify_plugin_sitemap',
62
- 'netlify_plugin_debug_cache',
63
- 'netlify_plugin_is_website_vulnerable',
64
- 'netlify_plugin_lighthouse',
65
- 'netlify_plugin_nextjs',
66
- 'netlify_plugin_gatsby',
67
- ]);
68
57
  const getPluginPackages = function (pluginsTimers) {
69
58
  const pluginPackages = pluginsTimers.map(getPluginTimerPackage);
70
59
  return [...new Set(pluginPackages)];
@@ -90,7 +79,7 @@ const addTypeTimers = function (timers) {
90
79
  const TYPE_TIMERS = [
91
80
  { name: 'system', stages: ['resolve_config', 'get_plugins_options', 'start_plugins', 'others'] },
92
81
  { name: 'plugin', stages: ['load_plugins', 'run_plugins'] },
93
- { name: 'user', stages: ['build_command', 'functions_bundling', 'deploy_site'] },
82
+ { name: 'user', stages: ['build_command', 'edge_functions_bundling', 'functions_bundling', 'deploy_site'] },
94
83
  ];
95
84
  const getTypeTimer = function (name, stages, topTimers) {
96
85
  const topTimersA = topTimers.filter(({ stageTag }) => stages.includes(stageTag));
package/lib/time/main.js CHANGED
@@ -1,4 +1,3 @@
1
- import slugify from '@sindresorhus/slugify';
2
1
  import keepFuncProps from 'keep-func-props';
3
2
  import { startTimer, endTimer } from './measure.js';
4
3
  // Initialize the `timers` array
@@ -11,7 +10,7 @@ export const initTimers = function () {
11
10
  // - return a plain object. This may or may not contain a modified `timers`.
12
11
  // The `durationNs` will be returned by the function. A new `timers` with the
13
12
  // additional duration timer will be returned as well.
14
- const kMeasureDuration = function (func, stageTag, { parentTag, category } = {}) {
13
+ const kMeasureDuration = function (func, stageTag, { parentTag = undefined, category = undefined } = {}) {
15
14
  return async function measuredFunc({ timers, ...opts }, ...args) {
16
15
  const timerNs = startTimer();
17
16
  const { timers: timersA = timers, ...returnObject } = await func({ timers, ...opts }, ...args);
@@ -25,13 +24,8 @@ const kMeasureDuration = function (func, stageTag, { parentTag, category } = {})
25
24
  // Ensure the wrapped function `name` is not `anonymous` in stack traces
26
25
  export const measureDuration = keepFuncProps(kMeasureDuration);
27
26
  // Create a new object representing a completed timer
28
- export const createTimer = function (stageTag, durationNs, { metricName = DEFAULT_METRIC_NAME, parentTag = TOP_PARENT_TAG, category, tags } = {}) {
27
+ export const createTimer = function (stageTag, durationNs, { metricName = DEFAULT_METRIC_NAME, parentTag = TOP_PARENT_TAG, category = undefined, tags = undefined } = {}) {
29
28
  return { metricName, stageTag, parentTag, durationNs, category, tags };
30
29
  };
31
30
  const DEFAULT_METRIC_NAME = 'buildbot.build.stage.duration';
32
31
  export const TOP_PARENT_TAG = 'run_netlify_build';
33
- // Make sure the timer name does not include special characters.
34
- // For example, the `packageName` of local plugins includes dots.
35
- export const normalizeTimerName = function (name) {
36
- return slugify(name, { separator: '_' });
37
- };
@@ -1,9 +1,6 @@
1
- import { promisify } from 'util';
2
- import StatsdClient from 'statsd-client';
1
+ import { closeClient, startClient } from '../report/statsd.js';
3
2
  import { addAggregatedTimers } from './aggregate.js';
4
3
  import { roundTimerToMillisecs } from './measure.js';
5
- // TODO: replace with `timers/promises` after dropping Node < 15.0.0
6
- const pSetTimeout = promisify(setTimeout);
7
4
  // Record the duration of a build phase, for monitoring.
8
5
  // Sends to statsd daemon.
9
6
  export const reportTimers = async function ({ timers, statsdOpts: { host, port }, framework }) {
@@ -20,28 +17,8 @@ const sendTimers = async function ({ timers, host, port, framework }) {
20
17
  });
21
18
  await closeClient(client);
22
19
  };
23
- // The socket creation is delayed until the first packet is sent. In our
24
- // case, this happens just before `client.close()` is called, which is too
25
- // late and make it not send anything. We need to manually create it using
26
- // the internal API.
27
- const startClient = async function (host, port) {
28
- const client = new StatsdClient({ host, port, socketTimeout: 0 });
29
- await promisify(client._socket._createSocket.bind(client._socket))();
30
- return client;
31
- };
32
20
  const sendTimer = function ({ timer: { metricName, stageTag, parentTag, durationNs, tags }, client, framework }) {
33
21
  const durationMs = roundTimerToMillisecs(durationNs);
34
22
  const frameworkTag = framework === undefined ? {} : { framework };
35
23
  client.distribution(metricName, durationMs, { stage: stageTag, parent: parentTag, ...tags, ...frameworkTag });
36
24
  };
37
- // UDP packets are buffered and flushed at regular intervals by statsd-client.
38
- // Closing force flushing all of them.
39
- const closeClient = async function (client) {
40
- client.close();
41
- // statsd-clent does not provide with a way of knowing when the socket is done
42
- // closing, so we need to use the following hack.
43
- await pSetTimeout(CLOSE_TIMEOUT);
44
- await pSetTimeout(CLOSE_TIMEOUT);
45
- };
46
- // See https://github.com/msiebuhr/node-statsd-client/blob/45a93ee4c94ca72f244a40b06cb542d4bd7c3766/lib/EphemeralSocket.js#L81
47
- const CLOSE_TIMEOUT = 11;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/build",
3
- "version": "28.0.1",
3
+ "version": "28.1.1",
4
4
  "description": "Netlify build module",
5
5
  "type": "module",
6
6
  "exports": "./lib/core/main.js",
@@ -65,7 +65,7 @@
65
65
  "dependencies": {
66
66
  "@bugsnag/js": "^7.0.0",
67
67
  "@netlify/cache-utils": "^5.0.1",
68
- "@netlify/config": "^19.0.1",
68
+ "@netlify/config": "^19.0.2",
69
69
  "@netlify/edge-bundler": "^2.8.0",
70
70
  "@netlify/functions-utils": "^5.0.1",
71
71
  "@netlify/git-utils": "^5.0.1",
@@ -116,11 +116,12 @@
116
116
  "typescript": "^4.5.4",
117
117
  "update-notifier": "^5.0.0",
118
118
  "uuid": "^8.0.0",
119
- "yargs": "^17.3.1"
119
+ "yargs": "^17.6.0"
120
120
  },
121
121
  "devDependencies": {
122
122
  "@netlify/nock-udp": "^3.0.0",
123
123
  "@types/node": "^14.18.31",
124
+ "@types/statsd-client": "^0.4.3",
124
125
  "atob": "^2.1.2",
125
126
  "ava": "^4.0.0",
126
127
  "c8": "^7.12.0",
@@ -150,5 +151,5 @@
150
151
  "module": "commonjs"
151
152
  }
152
153
  },
153
- "gitHead": "701e883d4e19c048e8de9801e49e21de5c891a18"
154
+ "gitHead": "20503830a8814af5d5f8bfabaac38be846e131f4"
154
155
  }