@netlify/build 36.4.0 → 36.4.2

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.
Files changed (45) hide show
  1. package/lib/core/bin.js +2 -3
  2. package/lib/core/constants.js +1 -1
  3. package/lib/core/dry.js +13 -2
  4. package/lib/core/missing_side_file.js +1 -1
  5. package/lib/env/changes.js +2 -3
  6. package/lib/env/main.d.ts +3 -1
  7. package/lib/env/main.js +1 -2
  8. package/lib/env/metadata.d.ts +3 -1
  9. package/lib/env/metadata.js +1 -2
  10. package/lib/error/api.js +3 -3
  11. package/lib/error/handle.js +1 -1
  12. package/lib/error/parse/clean_stack.js +5 -9
  13. package/lib/install/main.js +1 -1
  14. package/lib/install/missing.js +1 -1
  15. package/lib/log/messages/core.js +2 -5
  16. package/lib/log/messages/mutations.js +1 -1
  17. package/lib/plugins/child/diff.js +2 -2
  18. package/lib/plugins/child/load.d.ts +3 -1
  19. package/lib/plugins/child/load.js +2 -3
  20. package/lib/plugins/child/status.js +2 -2
  21. package/lib/plugins/compatibility.js +22 -10
  22. package/lib/plugins/ipc.js +22 -25
  23. package/lib/plugins/list.js +2 -2
  24. package/lib/plugins/manifest/validate.js +3 -3
  25. package/lib/plugins/spawn.js +25 -15
  26. package/lib/plugins_core/blobs_upload/index.js +19 -7
  27. package/lib/plugins_core/db_setup/migrations.js +1 -1
  28. package/lib/plugins_core/db_setup/utils.js +1 -1
  29. package/lib/plugins_core/deploy/buildbot_client.js +3 -3
  30. package/lib/plugins_core/dev_blobs_upload/index.js +21 -9
  31. package/lib/plugins_core/edge_functions/index.js +1 -1
  32. package/lib/plugins_core/frameworks_api/util.js +1 -1
  33. package/lib/plugins_core/functions/index.js +1 -1
  34. package/lib/plugins_core/functions/server_entry.js +1 -1
  35. package/lib/plugins_core/functions_install/index.js +1 -1
  36. package/lib/steps/update_config.d.ts +1 -1
  37. package/lib/steps/update_config.js +3 -4
  38. package/lib/utils/blobs.d.ts +1 -1
  39. package/lib/utils/is_plain_object.d.ts +1 -0
  40. package/lib/utils/is_plain_object.js +7 -0
  41. package/lib/utils/omit.js +2 -3
  42. package/lib/utils/path_exists.d.ts +1 -0
  43. package/lib/utils/path_exists.js +10 -0
  44. package/lib/utils/remove_falsy.js +2 -3
  45. package/package.json +5 -14
package/lib/core/bin.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from 'fs';
3
3
  import process from 'process';
4
- import { includeKeys } from 'filter-obj';
5
4
  import yargs from 'yargs';
6
5
  import { hideBin } from 'yargs/helpers';
7
6
  import { normalizeCliFeatureFlags } from './feature_flags.js';
@@ -16,7 +15,7 @@ const packJson = JSON.parse(readFileSync(new URL('../../package.json', import.me
16
15
  // sense only in CLI, such as CLI flags parsing and exit code.
17
16
  const runCli = async function () {
18
17
  const flags = parseFlags();
19
- const flagsA = includeKeys(flags, isUserFlag);
18
+ const flagsA = Object.fromEntries(Object.entries(flags).filter(isUserFlag));
20
19
  const state = { done: false };
21
20
  process.on('exit', onExit.bind(undefined, state));
22
21
  const { severityCode, logs } = await buildSite(flagsA);
@@ -41,7 +40,7 @@ Options can also be specified as environment variables prefixed with
41
40
  NETLIFY_BUILD_. For example the environment variable NETLIFY_BUILD_DRY=true can
42
41
  be used instead of the CLI flag --dry.`;
43
42
  // Remove `yargs`-specific options, shortcuts, dash-cased and aliases
44
- const isUserFlag = function (key, value) {
43
+ const isUserFlag = function ([key, value]) {
45
44
  return value !== undefined && !INTERNAL_KEYS.has(key) && key.length !== 1 && !key.includes('-');
46
45
  };
47
46
  const INTERNAL_KEYS = new Set(['help', 'version', '_', '$0', 'dryRun']);
@@ -1,6 +1,6 @@
1
1
  import { relative, normalize, join } from 'path';
2
2
  import { getCacheDir } from '@netlify/cache-utils';
3
- import { pathExists } from 'path-exists';
3
+ import { pathExists } from '../utils/path_exists.js';
4
4
  import { ROOT_PACKAGE_JSON } from '../utils/json.js';
5
5
  /**
6
6
  * Retrieve constants passed to plugins
package/lib/core/dry.js CHANGED
@@ -1,9 +1,20 @@
1
- import pFilter from 'p-filter';
2
1
  import { logDryRunStart, logDryRunStep, logDryRunEnd } from '../log/messages/dry.js';
3
2
  import { runsOnlyOnBuildFailure } from '../plugins/events.js';
4
3
  // If the `dry` flag is specified, do a dry run
5
4
  export const doDryRun = async function ({ buildDir, steps, netlifyConfig, constants, buildbotServerSocket, logs, featureFlags, }) {
6
- const successSteps = await pFilter(steps, ({ event, condition }) => shouldIncludeStep({ buildDir, event, condition, netlifyConfig, constants, buildbotServerSocket, featureFlags }));
5
+ const includedSteps = await Promise.all(steps.map(async (step) => {
6
+ const shouldInclude = await shouldIncludeStep({
7
+ buildDir,
8
+ event: step.event,
9
+ condition: step.condition,
10
+ netlifyConfig,
11
+ constants,
12
+ buildbotServerSocket,
13
+ featureFlags,
14
+ });
15
+ return shouldInclude ? step : null;
16
+ }));
17
+ const successSteps = includedSteps.filter(Boolean);
7
18
  const eventWidth = Math.max(...successSteps.map(getEventLength));
8
19
  const stepsCount = successSteps.length;
9
20
  logDryRunStart({ logs, eventWidth, stepsCount });
@@ -1,6 +1,6 @@
1
1
  import { relative } from 'path';
2
- import { pathExists } from 'path-exists';
3
2
  import { logMissingSideFile } from '../log/messages/core.js';
3
+ import { pathExists } from '../utils/path_exists.js';
4
4
  // Some files like `_headers` and `_redirects` must be copied to the publishing
5
5
  // directory to be used in production. When those are present in the repository
6
6
  // but not in the publish directory, this most likely indicates that the build
@@ -1,5 +1,4 @@
1
1
  import { env } from 'process';
2
- import { includeKeys } from 'filter-obj';
3
2
  // If plugins modify `process.env`, this is propagated in other plugins and in
4
3
  // `build.command`. Since those are different processes, we figure out when they
5
4
  // do this and communicate the new `process.env` to other processes.
@@ -9,8 +8,8 @@ export const getNewEnvChanges = function (envBefore, netlifyConfig, netlifyConfi
9
8
  return { ...processEnvChanges, ...netlifyConfigEnvChanges };
10
9
  };
11
10
  const diffEnv = function (envBefore, envAfter) {
12
- const envChanges = includeKeys(envAfter, (name, value) => value !== envBefore[name]);
13
- const deletedEnv = includeKeys(envBefore, (name) => envAfter[name] === undefined);
11
+ const envChanges = Object.fromEntries(Object.entries(envAfter).filter(([name, value]) => value !== envBefore[name]));
12
+ const deletedEnv = Object.fromEntries(Object.entries(envBefore).filter(([name]) => envAfter[name] === undefined));
14
13
  const deletedEnvA = Object.fromEntries(Object.entries(deletedEnv).map(setToNull));
15
14
  return { ...envChanges, ...deletedEnvA };
16
15
  };
package/lib/env/main.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export declare const getChildEnv: ({ envOpt, env: allConfigEnv }: {
2
2
  envOpt: any;
3
3
  env: any;
4
- }) => Partial<any>;
4
+ }) => {
5
+ [k: string]: unknown;
6
+ };
package/lib/env/main.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import { env } from 'process';
2
- import { includeKeys } from 'filter-obj';
3
2
  import { getParentColorEnv } from '../log/colors.js';
4
3
  // Retrieve the environment variables passed to plugins and `build.command`
5
4
  // When run locally, this tries to emulate the production environment.
6
5
  export const getChildEnv = function ({ envOpt, env: allConfigEnv }) {
7
6
  const parentColorEnv = getParentColorEnv();
8
7
  const parentEnv = { ...env, ...allConfigEnv, ...envOpt, ...parentColorEnv };
9
- return includeKeys(parentEnv, shouldKeepEnv);
8
+ return Object.fromEntries(Object.entries(parentEnv).filter(([key]) => shouldKeepEnv(key)));
10
9
  };
11
10
  const shouldKeepEnv = function (key) {
12
11
  return !REMOVED_PARENT_ENV.has(key.toLowerCase());
@@ -1 +1,3 @@
1
- export function getEnvMetadata(childEnv?: NodeJS.ProcessEnv): Partial<NodeJS.ProcessEnv>;
1
+ export function getEnvMetadata(childEnv?: NodeJS.ProcessEnv): {
2
+ [k: string]: string | undefined;
3
+ };
@@ -1,8 +1,7 @@
1
1
  import { env } from 'process';
2
- import { includeKeys } from 'filter-obj';
3
2
  // Retrieve environment variables used in error monitoring
4
3
  export const getEnvMetadata = function (childEnv = env) {
5
- return includeKeys(childEnv, isEnvMetadata);
4
+ return Object.fromEntries(Object.entries(childEnv).filter(([name]) => isEnvMetadata(name)));
6
5
  };
7
6
  const isEnvMetadata = function (name) {
8
7
  return ENVIRONMENT_VARIABLES.has(name);
package/lib/error/api.js CHANGED
@@ -1,4 +1,4 @@
1
- import isPlainObj from 'is-plain-obj';
1
+ import { isPlainObject } from '../utils/is_plain_object.js';
2
2
  import { addErrorInfo } from './info.js';
3
3
  // Wrap `api.*` methods so that they add more error information
4
4
  export const addApiErrorHandlers = function (api) {
@@ -27,8 +27,8 @@ const apiMethodHandler = async function (endpoint, method, parameters, ...args)
27
27
  // Redact API token from the build logs
28
28
  const redactError = function (error) {
29
29
  if (error instanceof Error &&
30
- isPlainObj(error.data) &&
31
- isPlainObj(error.data.headers) &&
30
+ isPlainObject(error.data) &&
31
+ isPlainObject(error.data.headers) &&
32
32
  typeof error.data.headers.Authorization === 'string') {
33
33
  error.data.headers.Authorization = error.data.headers.Authorization.replace(HEX_REGEXP, 'HEX');
34
34
  }
@@ -1,5 +1,5 @@
1
1
  import { cwd as getCwd } from 'process';
2
- import { pathExists } from 'path-exists';
2
+ import { pathExists } from '../utils/path_exists.js';
3
3
  import { logBuildError } from '../log/messages/core.js';
4
4
  import { removeErrorColors } from './colors.js';
5
5
  import { getErrorInfo } from './info.js';
@@ -1,6 +1,5 @@
1
1
  import { stripVTControlCharacters } from 'node:util';
2
2
  import { cwd } from 'process';
3
- import cleanStack from 'clean-stack';
4
3
  // Clean stack traces:
5
4
  // - remove our internal code, e.g. the logic spawning plugins
6
5
  // - remove node modules and Node.js internals
@@ -29,11 +28,7 @@ const cleanStackLine = function (lines, line) {
29
28
  if (shouldRemoveStackLine(lineB)) {
30
29
  return lines;
31
30
  }
32
- const lineC = cleanStack(lineB);
33
- if (lineC === '') {
34
- return lines;
35
- }
36
- return `${lines}\n${lineC}`;
31
+ return `${lines}\n${lineB}`;
37
32
  };
38
33
  // `process.cwd()` can sometimes fail: directory name too long, current
39
34
  // directory has been removed, access denied.
@@ -49,7 +44,9 @@ const getCwd = function () {
49
44
  const STACK_LINE_REGEXP = /^\s+at /;
50
45
  const shouldRemoveStackLine = function (line) {
51
46
  const lineA = normalizePathSlashes(line);
52
- return INTERNAL_STACK_STRINGS.some((stackString) => lineA.includes(stackString)) || INTERNAL_STACK_REGEXP.test(lineA);
47
+ return (INTERNAL_STACK_STRINGS.some((stackString) => lineA.includes(stackString)) ||
48
+ INTERNAL_STACK_REGEXP.test(lineA) ||
49
+ NODE_STACK_REGEXP.test(lineA));
53
50
  };
54
51
  const INTERNAL_STACK_STRINGS = [
55
52
  // Anonymous function
@@ -58,9 +55,8 @@ const INTERNAL_STACK_STRINGS = [
58
55
  // nyc internal code
59
56
  'node_modules/append-transform',
60
57
  'node_modules/signal-exit',
61
- // Node internals
62
- '(node:',
63
58
  ];
59
+ const NODE_STACK_REGEXP = /\bnode:|\(native\)|\bat native\b/;
64
60
  // This is only needed for local builds and tests
65
61
  const INTERNAL_STACK_REGEXP = /(lib\/|tests\/helpers\/|tests\/.*\/tests.js|node_modules)/;
66
62
  const INITIAL_NEWLINES = /^\n+/;
@@ -1,7 +1,7 @@
1
1
  import { homedir } from 'os';
2
2
  import { execa } from 'execa';
3
- import { pathExists } from 'path-exists';
4
3
  import { addErrorInfo } from '../error/info.js';
4
+ import { pathExists } from '../utils/path_exists.js';
5
5
  // Install Node.js dependencies in a specific directory
6
6
  export const installDependencies = function ({ packageRoot, isLocal }) {
7
7
  return runCommand({ packageRoot, isLocal, type: 'install' });
@@ -1,8 +1,8 @@
1
1
  import { promises as fs } from 'node:fs';
2
2
  import { normalize } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { pathExists } from 'path-exists';
5
4
  import { logInstallMissingPlugins, logInstallIntegrations } from '../log/messages/install.js';
5
+ import { pathExists } from '../utils/path_exists.js';
6
6
  import { addExactDependencies } from './main.js';
7
7
  // Automatically install plugins if not already installed.
8
8
  // Since this is done under the hood, we always use `npm` with specific `npm`
@@ -1,4 +1,3 @@
1
- import ansiEscapes from 'ansi-escapes';
2
1
  import prettyMs from 'pretty-ms';
3
2
  import { getFullErrorInfo } from '../../error/parse/parse.js';
4
3
  import { serializeLogError } from '../../error/parse/serialize_log.js';
@@ -38,14 +37,12 @@ export const logMissingSideFile = function (logs, sideFile, publish) {
38
37
  logWarning(logs, `
39
38
  A "${sideFile}" file is present in the repository but is missing in the publish directory "${publish}".`);
40
39
  };
41
- // @todo use `terminal-link` (https://github.com/sindresorhus/terminal-link)
42
- // instead of `ansi-escapes` once
43
- // https://github.com/jamestalmage/supports-hyperlinks/pull/12 is fixed
40
+ const ansiLink = (text, url) => `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
44
41
  export const logLingeringProcesses = function (logs, commands) {
45
42
  logWarning(logs, `
46
43
  The build completed successfully, but the following processes were still running:
47
44
  `);
48
45
  logWarningArray(logs, commands);
49
46
  logWarning(logs, `
50
- These processes have been terminated. In case this creates a problem for your build, refer to this ${ansiEscapes.link('article', 'https://answers.netlify.com/t/support-guide-how-to-address-the-warning-message-related-to-terminating-processes-in-builds/35277')} for details about why this process termination happens and how to fix it.`);
47
+ These processes have been terminated. In case this creates a problem for your build, refer to this ${ansiLink('article', 'https://answers.netlify.com/t/support-guide-how-to-address-the-warning-message-related-to-terminating-processes-in-builds/35277')} for details about why this process termination happens and how to fix it.`);
51
48
  };
@@ -1,6 +1,6 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import { inspect } from 'util';
3
- import { pathExists } from 'path-exists';
3
+ import { pathExists } from '../../utils/path_exists.js';
4
4
  import { log, logMessage, logSubHeader } from '../logger.js';
5
5
  export const logConfigMutations = function (logs, newConfigMutations, debug) {
6
6
  const configMutationsToLog = debug ? newConfigMutations : newConfigMutations.filter(shouldLogConfigMutation);
@@ -1,6 +1,6 @@
1
1
  import { isDeepStrictEqual } from 'util';
2
- import isPlainObj from 'is-plain-obj';
3
2
  import rfdc from 'rfdc';
3
+ import { isPlainObject } from '../../utils/is_plain_object.js';
4
4
  const clone = rfdc();
5
5
  // Copy `netlifyConfig` so we can compare before/after mutating it
6
6
  export function cloneNetlifyConfig(netlifyConfig) {
@@ -26,7 +26,7 @@ function diffObjects(objA, objB, parentKeys) {
26
26
  const valueA = objA[key];
27
27
  const valueB = objB[key];
28
28
  const keys = [...parentKeys, key];
29
- if (isPlainObj(valueA) && isPlainObj(valueB)) {
29
+ if (isPlainObject(valueA) && isPlainObject(valueB)) {
30
30
  return diffObjects(valueA, valueB, keys);
31
31
  }
32
32
  if (isDeepStrictEqual(valueA, valueB)) {
@@ -7,7 +7,9 @@ export declare const load: ({ pluginPath, inputs, packageJson, verbose, netlifyC
7
7
  }) => Promise<{
8
8
  events: string[];
9
9
  context: {
10
- methods: Partial<any>;
10
+ methods: {
11
+ [k: string]: unknown;
12
+ };
11
13
  inputs: any;
12
14
  packageJson: any;
13
15
  verbose: any;
@@ -1,4 +1,3 @@
1
- import { includeKeys } from 'filter-obj';
2
1
  import { getLogic } from './logic.js';
3
2
  import { registerTypeScript } from './typescript.js';
4
3
  import { validatePlugin } from './validate.js';
@@ -11,12 +10,12 @@ export const load = async function ({ pluginPath, inputs, packageJson, verbose,
11
10
  const tsNodeService = registerTypeScript(pluginPath);
12
11
  const logic = await getLogic({ pluginPath, inputs, tsNodeService, netlifyConfig });
13
12
  validatePlugin(logic);
14
- const methods = includeKeys(logic, isEventHandler);
13
+ const methods = Object.fromEntries(Object.entries(logic).filter(([, value]) => isEventHandler(value)));
15
14
  const events = Object.keys(methods);
16
15
  // Context passed to every event handler
17
16
  const context = { methods, inputs, packageJson, verbose };
18
17
  return { events, context };
19
18
  };
20
- const isEventHandler = function (_event, value) {
19
+ const isEventHandler = function (value) {
21
20
  return typeof value === 'function';
22
21
  };
@@ -1,5 +1,5 @@
1
- import isPlainObj from 'is-plain-obj';
2
1
  import { addErrorInfo } from '../../error/info.js';
2
+ import { isPlainObject } from '../../utils/is_plain_object.js';
3
3
  // Report status information to the UI
4
4
  export const show = function (runState, showArgs) {
5
5
  validateShowArgs(showArgs);
@@ -29,7 +29,7 @@ function validateShowArgsObject(showArgs) {
29
29
  if (showArgs === undefined) {
30
30
  throw new Error('requires an argument');
31
31
  }
32
- if (!isPlainObj(showArgs)) {
32
+ if (!isPlainObject(showArgs)) {
33
33
  throw new Error('argument must be a plain object');
34
34
  }
35
35
  }
@@ -1,4 +1,3 @@
1
- import pLocate from 'p-locate';
2
1
  import semver from 'semver';
3
2
  import { CONDITIONS } from './plugin_conditions.js';
4
3
  /**
@@ -47,7 +46,9 @@ export const getExpectedVersion = async function ({ versions, nodeVersion, packa
47
46
  const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson, packageName, packagePath, buildDir, pinnedVersion, featureFlags, systemLog = () => {
48
47
  // no-op
49
48
  }, }) {
50
- const compatibleEntry = await pLocate(versions, async ({ version, overridePinnedVersion, conditions }) => {
49
+ let compatibleEntry;
50
+ for (const entry of versions) {
51
+ const { version, overridePinnedVersion, conditions } = entry;
51
52
  // When there's a `pinnedVersion`, we typically pick the first version that
52
53
  // matches that range. The exception is if `overridePinnedVersion` is also
53
54
  // present. This property says that if the pinned version is within a given
@@ -57,14 +58,18 @@ const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson,
57
58
  // If there's a pinned version and this entry doesn't satisfy that range,
58
59
  // discard it. The exception is if this entry overrides the pinned version.
59
60
  if (pinnedVersion && !overridesPin && !semver.satisfies(version, pinnedVersion, { includePrerelease: true })) {
60
- return false;
61
+ continue;
61
62
  }
62
63
  // no conditions means nothing to filter
63
64
  if (conditions.length === 0 && pinnedVersion === undefined) {
64
- return false;
65
+ continue;
65
66
  }
66
- return (await Promise.all(conditions.map(async ({ type, condition }) => CONDITIONS[type].test(condition, { nodeVersion, packageJson, packagePath, buildDir })))).every(Boolean);
67
- });
67
+ const isCompatible = (await Promise.all(conditions.map(async ({ type, condition }) => CONDITIONS[type].test(condition, { nodeVersion, packageJson, packagePath, buildDir })))).every(Boolean);
68
+ if (isCompatible) {
69
+ compatibleEntry = entry;
70
+ break;
71
+ }
72
+ }
68
73
  if (compatibleEntry) {
69
74
  systemLog(`Used compatible version '${compatibleEntry.version}' for plugin '${packageName}' (pinned version is ${pinnedVersion})`);
70
75
  return compatibleEntry;
@@ -91,12 +96,19 @@ const getCompatibleEntry = async function ({ versions, nodeVersion, packageJson,
91
96
  * the conditions (if any), without taking into account the pinned version.
92
97
  */
93
98
  const getFirstCompatibleEntry = async function ({ versions, nodeVersion, packageJson, packagePath, buildDir, }) {
94
- const compatibleEntry = await pLocate(versions, async ({ conditions }) => {
99
+ let compatibleEntry;
100
+ for (const entry of versions) {
101
+ const { conditions } = entry;
95
102
  if (conditions.length === 0) {
96
- return true;
103
+ compatibleEntry = entry;
104
+ break;
97
105
  }
98
- return (await Promise.all(conditions.map(async ({ type, condition }) => CONDITIONS[type].test(condition, { nodeVersion, packageJson, packagePath, buildDir })))).every(Boolean);
99
- });
106
+ const isCompatible = (await Promise.all(conditions.map(async ({ type, condition }) => CONDITIONS[type].test(condition, { nodeVersion, packageJson, packagePath, buildDir })))).every(Boolean);
107
+ if (isCompatible) {
108
+ compatibleEntry = entry;
109
+ break;
110
+ }
111
+ }
100
112
  if (compatibleEntry) {
101
113
  return compatibleEntry;
102
114
  }
@@ -1,7 +1,6 @@
1
1
  import crypto from 'crypto';
2
2
  import process from 'process';
3
3
  import { promisify } from 'util';
4
- import { pEvent } from 'p-event';
5
4
  import { jsonToError, errorToJson } from '../error/build.js';
6
5
  import { addErrorInfo } from '../error/info.js';
7
6
  import { logSendingEventToChild, logSentEventToChild, logReceivedEventFromChild, logSendingEventToParent, } from '../log/messages/ipc.js';
@@ -24,38 +23,36 @@ export const callChild = async function ({ childProcess, eventName, payload, log
24
23
  // child process
25
24
  // - child process `exit`
26
25
  // In the later two cases, we propagate the error.
27
- // We need to make `p-event` listeners are properly cleaned up too.
28
26
  export const getEventFromChild = async function (childProcess, callId) {
29
27
  if (childProcessHasExited(childProcess)) {
30
28
  throw getChildExitError('Could not receive event from child process because it already exited.');
31
29
  }
32
- const messagePromise = pEvent(childProcess, 'message', { filter: (data) => data?.[0] === callId });
33
- const errorPromise = pEvent(childProcess, 'message', { filter: (data) => data?.[0] === 'error' });
34
- const exitPromise = pEvent(childProcess, 'exit', { multiArgs: true });
35
- try {
36
- return await Promise.race([getMessage(messagePromise), getError(errorPromise), getExit(exitPromise)]);
37
- }
38
- finally {
39
- messagePromise.cancel();
40
- errorPromise.cancel();
41
- exitPromise.cancel();
42
- }
30
+ return new Promise((resolve, reject) => {
31
+ const onMessage = function (data) {
32
+ if (data?.[0] === callId) {
33
+ cleanup();
34
+ resolve(data[1]);
35
+ }
36
+ else if (data?.[0] === 'error') {
37
+ cleanup();
38
+ reject(jsonToError(data[1]));
39
+ }
40
+ };
41
+ const onExit = function (exitCode, signal) {
42
+ cleanup();
43
+ reject(getChildExitError(`Plugin exited with exit code ${exitCode} and signal ${signal}.`));
44
+ };
45
+ const cleanup = function () {
46
+ childProcess.removeListener('message', onMessage);
47
+ childProcess.removeListener('exit', onExit);
48
+ };
49
+ childProcess.on('message', onMessage);
50
+ childProcess.on('exit', onExit);
51
+ });
43
52
  };
44
53
  const childProcessHasExited = function (childProcess) {
45
54
  return !childProcess.connected || childProcess.signalCode !== null || childProcess.exitCode !== null;
46
55
  };
47
- const getMessage = async function (messagePromise) {
48
- const [, response] = await messagePromise;
49
- return response;
50
- };
51
- const getError = async function (errorPromise) {
52
- const [, error] = await errorPromise;
53
- throw jsonToError(error);
54
- };
55
- const getExit = async function (exitPromise) {
56
- const [exitCode, signal] = await exitPromise;
57
- throw getChildExitError(`Plugin exited with exit code ${exitCode} and signal ${signal}.`);
58
- };
59
56
  // Plugins should not terminate processes explicitly:
60
57
  // - It prevents specifying error messages to the end users
61
58
  // - It makes it impossible to distinguish between bugs (such as infinite loops) and user errors
@@ -1,5 +1,5 @@
1
1
  import { pluginsUrl, pluginsList as oldPluginsList } from '@netlify/plugins-list';
2
- import isPlainObj from 'is-plain-obj';
2
+ import { isPlainObject } from '../utils/is_plain_object.js';
3
3
  import { logPluginsList, logPluginsFetchError } from '../log/messages/plugins.js';
4
4
  import { CONDITIONS } from './plugin_conditions.js';
5
5
  /** 1 minute HTTP request timeout */
@@ -49,7 +49,7 @@ const fetchPluginsList = async function ({ logs, pluginsListUrl, }) {
49
49
  }
50
50
  };
51
51
  const isValidPluginsList = function (pluginsList) {
52
- return Array.isArray(pluginsList) && pluginsList.every(isPlainObj);
52
+ return Array.isArray(pluginsList) && pluginsList.every(isPlainObject);
53
53
  };
54
54
  const normalizePluginsList = function (pluginsList) {
55
55
  return Object.fromEntries(pluginsList.map(normalizePluginItem));
@@ -1,5 +1,5 @@
1
- import isPlainObj from 'is-plain-obj';
2
1
  import { THEME } from '../../log/theme.js';
2
+ import { isPlainObject } from '../../utils/is_plain_object.js';
3
3
  // Validate `manifest.yml` syntax
4
4
  export const validateManifest = function (manifest, rawManifest) {
5
5
  try {
@@ -17,7 +17,7 @@ ${rawManifest.trim()}`;
17
17
  }
18
18
  };
19
19
  const validateBasic = function (manifest) {
20
- if (!isPlainObj(manifest)) {
20
+ if (!isPlainObject(manifest)) {
21
21
  throw new Error('must be a plain object');
22
22
  }
23
23
  };
@@ -46,7 +46,7 @@ const validateInputs = function ({ inputs }) {
46
46
  inputs.forEach(validateInput);
47
47
  };
48
48
  const isArrayOfObjects = function (objects) {
49
- return Array.isArray(objects) && objects.every(isPlainObj);
49
+ return Array.isArray(objects) && objects.every(isPlainObject);
50
50
  };
51
51
  const validateInput = function (input, index) {
52
52
  try {
@@ -111,22 +111,32 @@ export const stopPlugins = async function ({ childProcesses, logs, verbose, plug
111
111
  };
112
112
  const stopPlugin = async function ({ childProcess, logs, pluginOptions: { packageName, inputs, pluginPath, pluginPackageJson: packageJson = {} }, netlifyConfig, verbose, }) {
113
113
  if (childProcess.connected) {
114
- // reliable stop tracing inside child processes
115
- await callChild({
116
- childProcess,
117
- eventName: 'shutdown',
118
- payload: {
119
- packageName,
120
- pluginPath,
121
- inputs,
122
- packageJson,
114
+ try {
115
+ // reliable stop tracing inside child processes
116
+ await callChild({
117
+ childProcess,
118
+ eventName: 'shutdown',
119
+ payload: {
120
+ packageName,
121
+ pluginPath,
122
+ inputs,
123
+ packageJson,
124
+ verbose,
125
+ netlifyConfig,
126
+ },
127
+ logs,
123
128
  verbose,
124
- netlifyConfig,
125
- },
126
- logs,
127
- verbose,
128
- });
129
- childProcess.disconnect();
129
+ });
130
+ }
131
+ catch {
132
+ // The child process may exit before responding, e.g. when it already
133
+ // failed. It is being terminated anyway, so ignore the error
134
+ }
135
+ finally {
136
+ if (childProcess.connected) {
137
+ childProcess.disconnect();
138
+ }
139
+ }
130
140
  }
131
141
  // On Windows with Node 21+, there's a bug where attempting to kill a child process
132
142
  // results in an EPERM error. Ignore the error in that case.
@@ -1,6 +1,5 @@
1
1
  import { getDeployStore } from '@netlify/blobs';
2
2
  import { inspect } from 'node:util';
3
- import pMap from 'p-map';
4
3
  import { DEFAULT_API_HOST } from '../../core/normalize_flags.js';
5
4
  import { logError } from '../../log/logger.js';
6
5
  import { getFileWithMetadata, getKeysToUpload, scanForBlobs } from '../../utils/blobs.js';
@@ -38,12 +37,25 @@ const coreStep = async function ({ logs, deployId, buildDir, packagePath, consta
38
37
  }
39
38
  systemLog(`Uploading ${blobsToUpload.length} blobs to deploy store...`);
40
39
  try {
41
- await pMap(blobsToUpload, async ({ key, contentPath, metadataPath }) => {
42
- const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath);
43
- // `data` is a `Buffer`, typed by @types/node as `Buffer<ArrayBufferLike>`, which TS
44
- // rejects as a `BlobPart`; the runtime value is a valid blob part.
45
- await blobStore.set(key, new Blob([data]), { metadata });
46
- }, { concurrency: 10 });
40
+ const queue = blobsToUpload[Symbol.iterator]();
41
+ let stopQueue = false;
42
+ await Promise.all(Array.from({ length: 10 }, async () => {
43
+ while (!stopQueue) {
44
+ const next = queue.next();
45
+ if (next.done) {
46
+ return;
47
+ }
48
+ const { key, contentPath, metadataPath } = next.value;
49
+ try {
50
+ const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath);
51
+ await blobStore.set(key, new Blob([data]), { metadata });
52
+ }
53
+ catch (error) {
54
+ stopQueue = true;
55
+ throw error;
56
+ }
57
+ }
58
+ }));
47
59
  }
48
60
  catch (err) {
49
61
  logError(logs, `Error uploading blobs to deploy store: ${err.message}`);
@@ -1,6 +1,6 @@
1
1
  import { copyFile, mkdir } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
- import { pathExists } from 'path-exists';
3
+ import { pathExists } from '../../utils/path_exists.js';
4
4
  import { readMigrationEntries, getMigrationsSrc } from './utils.js';
5
5
  import { validateMigrations, formatValidationErrors } from './validation.js';
6
6
  const condition = async ({ featureFlags, constants, buildDir }) => {
@@ -1,6 +1,6 @@
1
1
  import { readdir, stat } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
- import { pathExists } from 'path-exists';
3
+ import { pathExists } from '../../utils/path_exists.js';
4
4
  // TODO: Remove once we drop support for the legacy `netlify/db/migrations` directory.
5
5
  const LEGACY_DB_MIGRATIONS_SRC = 'netlify/db/migrations';
6
6
  /**
@@ -1,7 +1,7 @@
1
+ import { once } from 'events';
1
2
  import net from 'net';
2
3
  import { normalize, resolve, relative } from 'path';
3
4
  import { promisify } from 'util';
4
- import { pEvent } from 'p-event';
5
5
  import { addErrorInfo } from '../../error/info.js';
6
6
  import { runsAfterDeploy } from '../../plugins/events.js';
7
7
  import { addAsyncErrorMessage } from '../../utils/errors.js';
@@ -41,7 +41,7 @@ const getConnectionOpts = function (buildbotServerSocket) {
41
41
  * Emits the connect event
42
42
  */
43
43
  export const connectBuildbotClient = addAsyncErrorMessage(async (client) => {
44
- await pEvent(client, 'connect');
44
+ await once(client, 'connect');
45
45
  }, 'Could not connect to buildbot');
46
46
  /**
47
47
  * Closes the buildbot client and its connection
@@ -56,7 +56,7 @@ const writePayload = addAsyncErrorMessage(async (buildbotClient, payload) => {
56
56
  await promisify(buildbotClient.write.bind(buildbotClient))(JSON.stringify(payload));
57
57
  }, 'Could not send payload to buildbot');
58
58
  const getNextParsedResponsePromise = addAsyncErrorMessage(async (buildbotClient) => {
59
- const data = await pEvent(buildbotClient, 'data');
59
+ const [data] = await once(buildbotClient, 'data');
60
60
  return JSON.parse(data);
61
61
  }, 'Invalid response from buildbot');
62
62
  /**
@@ -1,5 +1,4 @@
1
1
  import { getDeployStore } from '@netlify/blobs';
2
- import pMap from 'p-map';
3
2
  import { log, logError } from '../../log/logger.js';
4
3
  import { getFileWithMetadata, getKeysToUpload, scanForBlobs } from '../../utils/blobs.js';
5
4
  import { getBlobs } from '../../utils/frameworks_api.js';
@@ -42,15 +41,28 @@ const coreStep = async function ({ debug, logs, deployId, buildDir, quiet, packa
42
41
  log(logs, `Uploading ${blobsToUpload.length} blobs to deploy store...`);
43
42
  }
44
43
  try {
45
- await pMap(blobsToUpload, async ({ key, contentPath, metadataPath }) => {
46
- if (debug && !quiet) {
47
- log(logs, `- Uploading blob ${key}`, { indent: true });
44
+ const queue = blobsToUpload[Symbol.iterator]();
45
+ let stopQueue = false;
46
+ await Promise.all(Array.from({ length: 10 }, async () => {
47
+ while (!stopQueue) {
48
+ const next = queue.next();
49
+ if (next.done) {
50
+ return;
51
+ }
52
+ const { key, contentPath, metadataPath } = next.value;
53
+ if (debug && !quiet) {
54
+ log(logs, `- Uploading blob ${key}`, { indent: true });
55
+ }
56
+ try {
57
+ const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath);
58
+ await blobStore.set(key, new Blob([data]), { metadata });
59
+ }
60
+ catch (err) {
61
+ stopQueue = true;
62
+ throw err;
63
+ }
48
64
  }
49
- const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath);
50
- // `data` is a `Buffer`, typed by @types/node as `Buffer<ArrayBufferLike>`, which TS
51
- // rejects as a `BlobPart`; the runtime value is a valid blob part.
52
- await blobStore.set(key, new Blob([data]), { metadata });
53
- }, { concurrency: 10 });
65
+ }));
54
66
  }
55
67
  catch (err) {
56
68
  logError(logs, `Error uploading blobs to deploy store: ${err.message}`);
@@ -1,7 +1,7 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import { dirname, join, resolve } from 'path';
3
3
  import { bundle, find } from '@netlify/edge-bundler';
4
- import { pathExists } from 'path-exists';
4
+ import { pathExists } from '../../utils/path_exists.js';
5
5
  import { log, reduceLogLines } from '../../log/logger.js';
6
6
  import { logFunctionsToBundle } from '../../log/messages/core_steps.js';
7
7
  import { FRAMEWORKS_API_EDGE_FUNCTIONS_PATH, FRAMEWORKS_API_EDGE_FUNCTIONS_IMPORT_MAP, } from '../../utils/frameworks_api.js';
@@ -1,7 +1,7 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import { resolve } from 'path';
3
- import isPlainObject from 'is-plain-obj';
4
3
  import { FRAMEWORKS_API_CONFIG_PATH } from '../../utils/frameworks_api.js';
4
+ import { isPlainObject } from '../../utils/is_plain_object.js';
5
5
  export const loadConfigFile = async (buildDir, packagePath) => {
6
6
  const configPath = resolve(buildDir, packagePath ?? '', FRAMEWORKS_API_CONFIG_PATH);
7
7
  try {
@@ -1,6 +1,6 @@
1
1
  import { resolve } from 'path';
2
2
  import { zipFunctions } from '@netlify/zip-it-and-ship-it';
3
- import { pathExists } from 'path-exists';
3
+ import { pathExists } from '../../utils/path_exists.js';
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';
@@ -1,6 +1,6 @@
1
1
  import { mkdir, readdir, writeFile } from 'fs/promises';
2
2
  import { join, resolve } from 'path';
3
- import { pathExists } from 'path-exists';
3
+ import { pathExists } from '../../utils/path_exists.js';
4
4
  import { addErrorInfo } from '../../error/info.js';
5
5
  export const SERVER_ENTRY_FUNCTION_NAME = '___netlify-server';
6
6
  const SERVER_ENTRY_DIR = 'netlify/server';
@@ -1,5 +1,5 @@
1
- import { pathExists } from 'path-exists';
2
1
  import { installFunctionDependencies } from '../../install/functions.js';
2
+ import { pathExists } from '../../utils/path_exists.js';
3
3
  // Plugin to package Netlify functions with @netlify/zip-it-and-ship-it
4
4
  export const onPreBuild = async function ({ constants: { FUNCTIONS_SRC, IS_LOCAL } }) {
5
5
  if (!(await pathExists(FUNCTIONS_SRC))) {
@@ -23,4 +23,4 @@ export function updateNetlifyConfig({ configOpts, netlifyConfig, defaultConfig,
23
23
  headersPath: any;
24
24
  redirectsPath: any;
25
25
  }>;
26
- export function listConfigSideFiles(sideFiles: any): Promise<string[]>;
26
+ export function listConfigSideFiles(sideFiles: any): Promise<any[]>;
@@ -1,10 +1,9 @@
1
1
  import { isDeepStrictEqual } from 'util';
2
- import pFilter from 'p-filter';
3
- import { pathExists } from 'path-exists';
4
2
  import { resolveUpdatedConfig } from '../core/config.js';
5
3
  import { addErrorInfo } from '../error/info.js';
6
4
  import { logConfigOnUpdate } from '../log/messages/config.js';
7
5
  import { logConfigMutations, systemLogConfigMutations } from '../log/messages/mutations.js';
6
+ import { pathExists } from '../utils/path_exists.js';
8
7
  // If `netlifyConfig` was updated or `_redirects` was created, the configuration
9
8
  // is updated by calling `@netlify/config` again.
10
9
  export const updateNetlifyConfig = async function ({ configOpts, netlifyConfig, defaultConfig, headersPath, redirectsPath, configMutations, newConfigMutations, configSideFiles, errorParams, logs, systemLog, debug, source = '', }) {
@@ -51,8 +50,8 @@ const haveConfigSideFilesChanged = async function (configSideFiles, headersPath,
51
50
  // sometimes have higher priority and should therefore be deleted in order to
52
51
  // apply any configuration update on `netlify.toml`.
53
52
  export const listConfigSideFiles = async function (sideFiles) {
54
- const configSideFiles = await pFilter(sideFiles, pathExists);
55
- return configSideFiles.sort();
53
+ const existingSideFiles = await Promise.all(sideFiles.map(async (sideFile) => ((await pathExists(sideFile)) ? sideFile : null)));
54
+ return existingSideFiles.filter(Boolean).sort();
56
55
  };
57
56
  // Validate each new configuration change
58
57
  const validateConfigMutations = function (newConfigMutations) {
@@ -38,7 +38,7 @@ export declare const getKeysToUpload: (blobsDir: string) => Promise<{
38
38
  }[]>;
39
39
  /** Read a file and its metadata file from the blobs directory */
40
40
  export declare const getFileWithMetadata: (key: string, contentPath: string, metadataPath?: string) => Promise<{
41
- data: Buffer;
41
+ data: Buffer<ArrayBuffer>;
42
42
  metadata: Record<string, string>;
43
43
  }>;
44
44
  export {};
@@ -0,0 +1 @@
1
+ export declare const isPlainObject: (value: unknown) => value is Record<string, unknown>;
@@ -0,0 +1,7 @@
1
+ export const isPlainObject = (value) => {
2
+ if (typeof value !== 'object' || value === null) {
3
+ return false;
4
+ }
5
+ const prototype = Object.getPrototypeOf(value);
6
+ return prototype === Object.prototype || prototype === null;
7
+ };
package/lib/utils/omit.js CHANGED
@@ -1,3 +1,2 @@
1
- import { excludeKeys } from 'filter-obj';
2
- // lodash.omit is 1400 lines of codes. filter-obj is much smaller and simpler.
3
- export const omit = (obj, keys) => excludeKeys(obj, (key) => keys.includes(key));
1
+ // lodash.omit is 1400 lines of codes. This is much smaller and simpler.
2
+ export const omit = (obj, keys) => Object.fromEntries(Object.entries(obj).filter(([key]) => !keys.includes(key)));
@@ -0,0 +1 @@
1
+ export declare const pathExists: (path: string) => Promise<boolean>;
@@ -0,0 +1,10 @@
1
+ import { access } from 'node:fs/promises';
2
+ export const pathExists = async (path) => {
3
+ try {
4
+ await access(path);
5
+ return true;
6
+ }
7
+ catch {
8
+ return false;
9
+ }
10
+ };
@@ -1,8 +1,7 @@
1
- import { includeKeys } from 'filter-obj';
2
1
  // Remove falsy values from object
3
2
  export const removeFalsy = function (obj) {
4
- return includeKeys(obj, isDefined);
3
+ return Object.fromEntries(Object.entries(obj).filter(([, value]) => isDefined(value)));
5
4
  };
6
- const isDefined = function (_key, value) {
5
+ const isDefined = function (value) {
7
6
  return value !== undefined && value !== '';
8
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/build",
3
- "version": "36.4.0",
3
+ "version": "36.4.2",
4
4
  "description": "Netlify build module",
5
5
  "type": "module",
6
6
  "exports": "./lib/index.js",
@@ -68,40 +68,31 @@
68
68
  "dependencies": {
69
69
  "@bugsnag/js": "^8.0.0",
70
70
  "@netlify/blobs": "^10.4.4",
71
- "@netlify/cache-utils": "^7.1.1",
71
+ "@netlify/cache-utils": "^7.1.2",
72
72
  "@netlify/config": "^25.2.3",
73
- "@netlify/edge-bundler": "16.0.3",
73
+ "@netlify/edge-bundler": "16.0.4",
74
74
  "@netlify/functions-utils": "^7.1.5",
75
- "@netlify/git-utils": "^7.1.0",
75
+ "@netlify/git-utils": "^7.1.1",
76
76
  "@netlify/opentelemetry-utils": "^3.1.0",
77
77
  "@netlify/plugins-list": "^6.81.6",
78
78
  "@netlify/run-utils": "^7.1.0",
79
79
  "@netlify/zip-it-and-ship-it": "15.4.0",
80
80
  "@sindresorhus/slugify": "^2.0.0",
81
- "ansi-escapes": "^7.0.0",
82
81
  "ansis": "^4.1.0",
83
- "clean-stack": "^5.0.0",
84
82
  "execa": "^8.0.0",
85
83
  "fast-string-width": "^3.0.2",
86
84
  "fdir": "^6.0.1",
87
85
  "figures": "^6.0.0",
88
- "filter-obj": "^6.0.0",
89
86
  "hot-shots": "11.4.0",
90
87
  "ignore": "^7.0.0",
91
88
  "indent-string": "^5.0.0",
92
- "is-plain-obj": "^4.0.0",
93
89
  "keep-func-props": "^6.0.0",
94
90
  "log-process-errors": "^11.0.0",
95
91
  "memoize-one": "^6.0.0",
96
92
  "minimatch": "^10.2.4",
97
93
  "os-name": "^6.0.0",
98
- "p-event": "^6.0.0",
99
- "p-filter": "^4.0.0",
100
- "p-locate": "^6.0.0",
101
- "p-map": "^7.0.0",
102
94
  "p-reduce": "^3.0.0",
103
95
  "package-directory": "^8.0.0",
104
- "path-exists": "^5.0.0",
105
96
  "pretty-ms": "^9.0.0",
106
97
  "ps-list": "^8.0.0",
107
98
  "read-package-up": "^11.0.0",
@@ -152,5 +143,5 @@
152
143
  "engines": {
153
144
  "node": ">=22.12.0"
154
145
  },
155
- "gitHead": "aaee524cfed2fc082e6cb7268ce7230071b39068"
146
+ "gitHead": "b858a683acc2054b39d923c45ca9fe8d87bd7d78"
156
147
  }