@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 +2 -0
- package/lib/error/report.js +27 -0
- package/lib/plugins/internal.js +10 -0
- package/lib/report/statsd.js +31 -0
- package/lib/steps/core_step.js +3 -1
- package/lib/steps/run_step.js +6 -5
- package/lib/time/aggregate.js +2 -13
- package/lib/time/main.js +2 -8
- package/lib/time/report.js +1 -24
- package/package.json +5 -4
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
|
+
};
|
package/lib/steps/core_step.js
CHANGED
|
@@ -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
|
};
|
package/lib/steps/run_step.js
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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
|
};
|
package/lib/time/aggregate.js
CHANGED
|
@@ -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
|
-
};
|
package/lib/time/report.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
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.
|
|
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.
|
|
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": "
|
|
154
|
+
"gitHead": "20503830a8814af5d5f8bfabaac38be846e131f4"
|
|
154
155
|
}
|