@netlify/build 29.4.3-rc → 29.4.3

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/bin.js CHANGED
@@ -3,10 +3,10 @@ import process from 'process';
3
3
  import filterObj from 'filter-obj';
4
4
  import yargs from 'yargs';
5
5
  import { hideBin } from 'yargs/helpers';
6
- import { normalizeCliFeatureFlags } from '../../lib/core/feature_flags.js';
7
- import { FLAGS } from '../../lib/core/flags.js';
8
- import build from '../../lib/core/main.js';
9
- import { FALLBACK_SEVERITY_ENTRY } from '../../lib/core/severity.js';
6
+ import { normalizeCliFeatureFlags } from './feature_flags.js';
7
+ import { FLAGS } from './flags.js';
8
+ import build from './main.js';
9
+ import { FALLBACK_SEVERITY_ENTRY } from './severity.js';
10
10
  // CLI entry point.
11
11
  // Before adding logic to this file, please consider adding it to the main
12
12
  // programmatic command instead, so that the new logic is available when run
package/lib/core/build.js CHANGED
@@ -30,7 +30,7 @@ export const startBuild = function (flags) {
30
30
  const errorMonitor = startErrorMonitor({ flags: flagsA, logs, bugsnagKey });
31
31
  return { ...flagsA, errorMonitor, logs, timers };
32
32
  };
33
- 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, }) {
33
+ 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, }) {
34
34
  const configOpts = getConfigOpts({
35
35
  config,
36
36
  defaultConfig,
@@ -61,14 +61,6 @@ const tExecBuild = async function ({ config, defaultConfig, cachedConfig, cached
61
61
  timers,
62
62
  quiet,
63
63
  });
64
- if (framework) {
65
- const runtime = supportedRuntimes[framework];
66
- const skip = childEnv[runtime.skipFlag];
67
- const installedPlugins = netlifyConfig.plugins.map((plugin) => plugin.package);
68
- if (runtime !== undefined && skip !== 'true' && installedPlugins.contains(runtime.package)) {
69
- netlifyConfig.plugins.push({ package: runtime.package });
70
- }
71
- }
72
64
  const constants = await getConstants({
73
65
  configPath,
74
66
  buildDir,
@@ -374,7 +366,3 @@ const runBuild = async function ({ childProcesses, pluginsOptions, netlifyConfig
374
366
  });
375
367
  return { stepsCount, netlifyConfig: netlifyConfigA, statuses, failedPlugins, timers: timersB, configMutations };
376
368
  };
377
- const supportedRuntimes = {
378
- next: { package: '@netlify/plugin-nextjs', skipFlag: 'NETLIFY_NEXT_PLUGIN_SKIP' },
379
- gatsby: { package: '@netlify/plugin-gatsby', skipFlag: 'NETLIFY_GATSBY_PLUGIN_SKIP' },
380
- };
package/lib/core/main.js CHANGED
@@ -31,7 +31,6 @@ export default async function buildSite(flags = {}) {
31
31
  debug,
32
32
  testOpts,
33
33
  errorParams,
34
- framework,
35
34
  });
36
35
  await handleBuildSuccess({
37
36
  framework,
@@ -75,7 +74,7 @@ export default async function buildSite(flags = {}) {
75
74
  testOpts,
76
75
  errorParams,
77
76
  });
78
- await reportError({ error, framework, statsdOpts });
77
+ await reportError(error, statsdOpts, framework);
79
78
  return { success, severityCode, logs };
80
79
  }
81
80
  }
@@ -86,7 +85,7 @@ const handleBuildSuccess = async function ({ framework, dry, logs, timers, durat
86
85
  }
87
86
  logBuildSuccess(logs);
88
87
  logTimer(logs, durationNs, 'Netlify Build', systemLog);
89
- await reportTimers({ timers, statsdOpts, framework });
88
+ await reportTimers(timers, statsdOpts, framework);
90
89
  };
91
90
  // Handles the calls and errors of telemetry reports
92
91
  const telemetryReport = async function ({ deployId, buildId, status, stepsCount, pluginsOptions, durationNs, siteInfo, telemetry, userNodeVersion, framework, testOpts, errorParams, }) {
@@ -1,11 +1,13 @@
1
1
  import { isNetlifyMaintainedPlugin } from '../plugins/internal.js';
2
- import { closeClient, normalizeTagName, startClient } from '../report/statsd.js';
2
+ import { closeClient, formatTags, normalizeTagName, startClient, validateStatsDOptions, } from '../report/statsd.js';
3
3
  import { getErrorInfo } from './info.js';
4
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) {
5
+ /**
6
+ * Record error rates of the build phase for monitoring.
7
+ * Sends to statsd daemon.
8
+ */
9
+ export const reportError = async function (error, statsdOpts, framework) {
10
+ if (!validateStatsDOptions(statsdOpts)) {
9
11
  return;
10
12
  }
11
13
  const [errorInfo] = getErrorInfo(error);
@@ -16,12 +18,12 @@ export const reportError = async function ({ error, statsdOpts: { host, port },
16
18
  }
17
19
  const parent = pluginName ? pluginName : TOP_PARENT_TAG;
18
20
  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
- });
21
+ const statsDTags = { stage: stage ?? 'system', parent };
22
+ // Do not add a framework tag if empty string or null/undefined
23
+ if (framework) {
24
+ statsDTags.framework = framework;
25
+ }
26
+ const client = await startClient(statsdOpts);
27
+ client.increment('buildbot.build.stage.error', 1, formatTags(statsDTags));
26
28
  await closeClient(client);
27
29
  };
@@ -1,31 +1,56 @@
1
1
  import { promisify } from 'util';
2
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;
3
+ import { StatsD } from 'hot-shots';
4
+ export const validateStatsDOptions = function (statsdOpts) {
5
+ return !!(statsdOpts && statsdOpts.host && statsdOpts.port);
17
6
  };
18
- // UDP packets are buffered and flushed at regular intervals by statsd-client.
19
- // Closing force flushing all of them.
7
+ /**
8
+ * Start a new StatsD Client and a new UDP socket
9
+ */
10
+ export const startClient = async function (statsdOpts) {
11
+ const { host, port } = statsdOpts;
12
+ return new StatsD({
13
+ host,
14
+ port,
15
+ // This caches the dns resolution for subsequent sends of metrics for this instance
16
+ // Because we only try to send the metrics on close, this comes only into effect if `bufferFlushInterval` time is exceeded
17
+ cacheDns: true,
18
+ // set the maxBufferSize to infinite and the bufferFlushInterval very high, so that we only
19
+ // send the metrics on close or if more than 10 seconds past by
20
+ maxBufferSize: Infinity,
21
+ bufferFlushInterval: 10_000,
22
+ });
23
+ };
24
+ /**
25
+ * Close the StatsD Client
26
+ *
27
+ * UDP packets are buffered and flushed every 10 seconds and
28
+ * closing the client forces all buffered metrics to be flushed.
29
+ */
20
30
  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);
31
+ await promisify(client.close.bind(client))();
26
32
  };
27
- // Make sure the timer name does not include special characters.
28
- // For example, the `packageName` of local plugins includes dots.
33
+ /**
34
+ * Make sure the timer name does not include special characters.
35
+ * For example, the `packageName` of local plugins includes dots.
36
+ */
29
37
  export const normalizeTagName = function (name) {
30
38
  return slugify(name, { separator: '_' });
31
39
  };
40
+ /**
41
+ * Formats and flattens the tags as array
42
+ * We do this because we might send the same tag with different values `{ tag: ['a', 'b'] }`
43
+ * which cannot be represented in an flattened object and `hot-shots` also supports tags as array in the format `['tag:a', 'tag:b']`
44
+ */
45
+ export const formatTags = function (tags) {
46
+ return Object.entries(tags)
47
+ .map(([key, value]) => {
48
+ if (Array.isArray(value)) {
49
+ return value.map((subValue) => `${key}:${subValue}`);
50
+ }
51
+ else {
52
+ return `${key}:${value}`;
53
+ }
54
+ })
55
+ .flat();
56
+ };
@@ -1,24 +1,30 @@
1
- import { closeClient, startClient } from '../report/statsd.js';
1
+ import { closeClient, formatTags, startClient, validateStatsDOptions, } from '../report/statsd.js';
2
2
  import { addAggregatedTimers } from './aggregate.js';
3
3
  import { roundTimerToMillisecs } from './measure.js';
4
- // Record the duration of a build phase, for monitoring.
5
- // Sends to statsd daemon.
6
- export const reportTimers = async function ({ timers, statsdOpts: { host, port }, framework }) {
7
- if (host === undefined) {
4
+ /**
5
+ * Record the duration of a build phase, for monitoring.
6
+ * Sends to statsd daemon.
7
+ */
8
+ export const reportTimers = async function (timers, statsdOpts, framework) {
9
+ if (!validateStatsDOptions(statsdOpts)) {
8
10
  return;
9
11
  }
10
- const timersA = addAggregatedTimers(timers);
11
- await sendTimers({ timers: timersA, host, port, framework });
12
+ await sendTimers(addAggregatedTimers(timers), statsdOpts, framework);
12
13
  };
13
- const sendTimers = async function ({ timers, host, port, framework }) {
14
- const client = await startClient(host, port);
14
+ const sendTimers = async function (timers, statsdOpts, framework) {
15
+ const client = await startClient(statsdOpts);
15
16
  timers.forEach((timer) => {
16
- sendTimer({ timer, client, framework });
17
+ sendTimer(timer, client, framework);
17
18
  });
18
19
  await closeClient(client);
19
20
  };
20
- const sendTimer = function ({ timer: { metricName, stageTag, parentTag, durationNs, tags }, client, framework }) {
21
+ const sendTimer = function (timer, client, framework) {
22
+ const { metricName, stageTag, parentTag, durationNs, tags } = timer;
21
23
  const durationMs = roundTimerToMillisecs(durationNs);
22
- const frameworkTag = framework === undefined ? {} : { framework };
23
- client.distribution(metricName, durationMs, { stage: stageTag, parent: parentTag, ...tags, ...frameworkTag });
24
+ const statsDTags = { stage: stageTag, parent: parentTag, ...tags };
25
+ // Do not add a framework tag if empty string or null/undefined
26
+ if (framework) {
27
+ statsDTags.framework = framework;
28
+ }
29
+ client.distribution(metricName, durationMs, formatTags(statsDTags));
24
30
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/build",
3
- "version": "29.4.3-rc",
3
+ "version": "29.4.3",
4
4
  "description": "Netlify build module",
5
5
  "type": "module",
6
6
  "exports": "./lib/core/main.js",
@@ -66,12 +66,12 @@
66
66
  "@bugsnag/js": "^7.0.0",
67
67
  "@netlify/cache-utils": "^5.1.0",
68
68
  "@netlify/config": "^20.3.0",
69
- "@netlify/edge-bundler": "8.0.0",
70
- "@netlify/functions-utils": "^5.1.1",
69
+ "@netlify/edge-bundler": "8.1.2",
70
+ "@netlify/functions-utils": "^5.1.2",
71
71
  "@netlify/git-utils": "^5.1.0",
72
- "@netlify/plugins-list": "^6.59.0",
72
+ "@netlify/plugins-list": "^6.61.0",
73
73
  "@netlify/run-utils": "^5.1.0",
74
- "@netlify/zip-it-and-ship-it": "^8.2.0",
74
+ "@netlify/zip-it-and-ship-it": "^8.3.0",
75
75
  "@sindresorhus/slugify": "^2.0.0",
76
76
  "ansi-escapes": "^5.0.0",
77
77
  "chalk": "^5.0.0",
@@ -80,6 +80,7 @@
80
80
  "figures": "^4.0.0",
81
81
  "filter-obj": "^3.0.0",
82
82
  "got": "^10.0.0",
83
+ "hot-shots": "9.3.0",
83
84
  "indent-string": "^5.0.0",
84
85
  "is-plain-obj": "^4.0.0",
85
86
  "js-yaml": "^4.0.0",
@@ -105,7 +106,6 @@
105
106
  "rfdc": "^1.3.0",
106
107
  "safe-json-stringify": "^1.2.0",
107
108
  "semver": "^7.0.0",
108
- "statsd-client": "0.4.7",
109
109
  "string-width": "^5.0.0",
110
110
  "strip-ansi": "^7.0.0",
111
111
  "supports-color": "^9.0.0",
@@ -120,7 +120,6 @@
120
120
  "devDependencies": {
121
121
  "@netlify/nock-udp": "^3.1.0",
122
122
  "@types/node": "^14.18.31",
123
- "@types/statsd-client": "^0.4.3",
124
123
  "atob": "^2.1.2",
125
124
  "ava": "^4.0.0",
126
125
  "c8": "^7.12.0",
@@ -148,5 +147,6 @@
148
147
  "compilerOptions": {
149
148
  "module": "commonjs"
150
149
  }
151
- }
150
+ },
151
+ "gitHead": "c534df8433e85724d77a38c288a2a5cb55eb59b8"
152
152
  }