diff --git a/eslint_temporary_suppressions.js b/eslint_temporary_suppressions.js index 2ed0c0edec..c6b67f6ee4 100644 --- a/eslint_temporary_suppressions.js +++ b/eslint_temporary_suppressions.js @@ -749,6 +749,12 @@ export default [ '@typescript-eslint/no-unsafe-return': 'off', }, }, + { + files: ['packages/build/src/plugins/child/diff.js'], + rules: { + 'n/no-missing-import': 'off', + }, + }, { files: ['packages/build/src/plugins/child/error.js'], rules: { @@ -1062,6 +1068,12 @@ export default [ '@typescript-eslint/no-unsafe-assignment': 'off', }, }, + { + files: ['packages/build/src/plugins_core/functions_install/index.js'], + rules: { + 'n/no-missing-import': 'off', + }, + }, { files: ['packages/build/src/plugins_core/pre_cleanup/index.ts'], rules: { diff --git a/package-lock.json b/package-lock.json index b2f77257f0..d21a6aff1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8524,19 +8524,6 @@ "version": "2.2.0", "license": "MIT" }, - "node_modules/clean-stack": { - "version": "5.3.0", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "5.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/clean-yaml-object": { "version": "0.1.0", "dev": true, @@ -23031,30 +23018,21 @@ "@netlify/run-utils": "^7.1.0", "@netlify/zip-it-and-ship-it": "15.3.0", "@sindresorhus/slugify": "^2.0.0", - "ansi-escapes": "^7.0.0", "ansis": "^4.1.0", - "clean-stack": "^5.0.0", "execa": "^8.0.0", "fast-string-width": "^3.0.2", "fdir": "^6.0.1", "figures": "^6.0.0", - "filter-obj": "^6.0.0", "hot-shots": "11.4.0", "ignore": "^7.0.0", "indent-string": "^5.0.0", - "is-plain-obj": "^4.0.0", "keep-func-props": "^6.0.0", "log-process-errors": "^11.0.0", "memoize-one": "^6.0.0", "minimatch": "^10.2.4", "os-name": "^6.0.0", - "p-event": "^6.0.0", - "p-filter": "^4.0.0", - "p-locate": "^6.0.0", - "p-map": "^7.0.0", "p-reduce": "^3.0.0", "package-directory": "^8.0.0", - "path-exists": "^5.0.0", "pretty-ms": "^9.0.0", "ps-list": "^8.0.0", "read-package-up": "^11.0.0", diff --git a/packages/build/package.json b/packages/build/package.json index 913debfaa3..518cea5449 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -78,30 +78,21 @@ "@netlify/run-utils": "^7.1.0", "@netlify/zip-it-and-ship-it": "15.3.0", "@sindresorhus/slugify": "^2.0.0", - "ansi-escapes": "^7.0.0", "ansis": "^4.1.0", - "clean-stack": "^5.0.0", "execa": "^8.0.0", "fast-string-width": "^3.0.2", "fdir": "^6.0.1", "figures": "^6.0.0", - "filter-obj": "^6.0.0", "hot-shots": "11.4.0", "ignore": "^7.0.0", "indent-string": "^5.0.0", - "is-plain-obj": "^4.0.0", "keep-func-props": "^6.0.0", "log-process-errors": "^11.0.0", "memoize-one": "^6.0.0", "minimatch": "^10.2.4", "os-name": "^6.0.0", - "p-event": "^6.0.0", - "p-filter": "^4.0.0", - "p-locate": "^6.0.0", - "p-map": "^7.0.0", "p-reduce": "^3.0.0", "package-directory": "^8.0.0", - "path-exists": "^5.0.0", "pretty-ms": "^9.0.0", "ps-list": "^8.0.0", "read-package-up": "^11.0.0", diff --git a/packages/build/src/core/bin.js b/packages/build/src/core/bin.js index ac1fc6cee4..52cfec55e2 100755 --- a/packages/build/src/core/bin.js +++ b/packages/build/src/core/bin.js @@ -3,7 +3,6 @@ import { readFileSync } from 'fs' import process from 'process' -import { includeKeys } from 'filter-obj' import yargs from 'yargs' import { hideBin } from 'yargs/helpers' @@ -21,7 +20,7 @@ const packJson = JSON.parse(readFileSync(new URL('../../package.json', import.me // sense only in CLI, such as CLI flags parsing and exit code. const runCli = async function () { const flags = parseFlags() - const flagsA = includeKeys(flags, isUserFlag) + const flagsA = Object.fromEntries(Object.entries(flags).filter(isUserFlag)) const state = { done: false } process.on('exit', onExit.bind(undefined, state)) @@ -52,7 +51,7 @@ NETLIFY_BUILD_. For example the environment variable NETLIFY_BUILD_DRY=true can be used instead of the CLI flag --dry.` // Remove `yargs`-specific options, shortcuts, dash-cased and aliases -const isUserFlag = function (key, value) { +const isUserFlag = function ([key, value]) { return value !== undefined && !INTERNAL_KEYS.has(key) && key.length !== 1 && !key.includes('-') } diff --git a/packages/build/src/core/constants.ts b/packages/build/src/core/constants.ts index 219b493bcc..5a3ff8c517 100644 --- a/packages/build/src/core/constants.ts +++ b/packages/build/src/core/constants.ts @@ -1,7 +1,7 @@ import { relative, normalize, join } from 'path' import { getCacheDir } from '@netlify/cache-utils' -import { pathExists } from 'path-exists' +import { pathExists } from '../utils/path_exists.js' import { ROOT_PACKAGE_JSON } from '../utils/json.js' diff --git a/packages/build/src/core/dry.js b/packages/build/src/core/dry.js index 216a6e650c..94ba19dc81 100644 --- a/packages/build/src/core/dry.js +++ b/packages/build/src/core/dry.js @@ -1,5 +1,3 @@ -import pFilter from 'p-filter' - import { logDryRunStart, logDryRunStep, logDryRunEnd } from '../log/messages/dry.js' import { runsOnlyOnBuildFailure } from '../plugins/events.js' @@ -13,9 +11,21 @@ export const doDryRun = async function ({ logs, featureFlags, }) { - const successSteps = await pFilter(steps, ({ event, condition }) => - shouldIncludeStep({ buildDir, event, condition, netlifyConfig, constants, buildbotServerSocket, featureFlags }), + const includedSteps = await Promise.all( + steps.map(async (step) => { + const shouldInclude = await shouldIncludeStep({ + buildDir, + event: step.event, + condition: step.condition, + netlifyConfig, + constants, + buildbotServerSocket, + featureFlags, + }) + return shouldInclude ? step : null + }), ) + const successSteps = includedSteps.filter(Boolean) const eventWidth = Math.max(...successSteps.map(getEventLength)) const stepsCount = successSteps.length diff --git a/packages/build/src/core/missing_side_file.js b/packages/build/src/core/missing_side_file.js index f8875711d3..77fa404b28 100644 --- a/packages/build/src/core/missing_side_file.js +++ b/packages/build/src/core/missing_side_file.js @@ -1,8 +1,7 @@ import { relative } from 'path' -import { pathExists } from 'path-exists' - import { logMissingSideFile } from '../log/messages/core.js' +import { pathExists } from '../utils/path_exists.js' // Some files like `_headers` and `_redirects` must be copied to the publishing // directory to be used in production. When those are present in the repository diff --git a/packages/build/src/env/changes.js b/packages/build/src/env/changes.js index 9f7217bb69..64ce701df2 100644 --- a/packages/build/src/env/changes.js +++ b/packages/build/src/env/changes.js @@ -1,7 +1,5 @@ import { env } from 'process' -import { includeKeys } from 'filter-obj' - // If plugins modify `process.env`, this is propagated in other plugins and in // `build.command`. Since those are different processes, we figure out when they // do this and communicate the new `process.env` to other processes. @@ -12,8 +10,8 @@ export const getNewEnvChanges = function (envBefore, netlifyConfig, netlifyConfi } const diffEnv = function (envBefore, envAfter) { - const envChanges = includeKeys(envAfter, (name, value) => value !== envBefore[name]) - const deletedEnv = includeKeys(envBefore, (name) => envAfter[name] === undefined) + const envChanges = Object.fromEntries(Object.entries(envAfter).filter(([name, value]) => value !== envBefore[name])) + const deletedEnv = Object.fromEntries(Object.entries(envBefore).filter(([name]) => envAfter[name] === undefined)) const deletedEnvA = Object.fromEntries(Object.entries(deletedEnv).map(setToNull)) return { ...envChanges, ...deletedEnvA } } diff --git a/packages/build/src/env/main.ts b/packages/build/src/env/main.ts index 531b6b4a1f..874a12f33e 100644 --- a/packages/build/src/env/main.ts +++ b/packages/build/src/env/main.ts @@ -1,7 +1,5 @@ import { env } from 'process' -import { includeKeys } from 'filter-obj' - import { getParentColorEnv } from '../log/colors.js' // Retrieve the environment variables passed to plugins and `build.command` @@ -9,7 +7,7 @@ import { getParentColorEnv } from '../log/colors.js' export const getChildEnv = function ({ envOpt, env: allConfigEnv }) { const parentColorEnv = getParentColorEnv() const parentEnv = { ...env, ...allConfigEnv, ...envOpt, ...parentColorEnv } - return includeKeys(parentEnv, shouldKeepEnv) + return Object.fromEntries(Object.entries(parentEnv).filter(([key]) => shouldKeepEnv(key))) } const shouldKeepEnv = function (key: string) { diff --git a/packages/build/src/env/metadata.js b/packages/build/src/env/metadata.js index 8bec7581b8..6e13b962c0 100644 --- a/packages/build/src/env/metadata.js +++ b/packages/build/src/env/metadata.js @@ -1,10 +1,8 @@ import { env } from 'process' -import { includeKeys } from 'filter-obj' - // Retrieve environment variables used in error monitoring export const getEnvMetadata = function (childEnv = env) { - return includeKeys(childEnv, isEnvMetadata) + return Object.fromEntries(Object.entries(childEnv).filter(([name]) => isEnvMetadata(name))) } const isEnvMetadata = function (name) { diff --git a/packages/build/src/error/api.js b/packages/build/src/error/api.js index 9912732221..89b6e10e53 100644 --- a/packages/build/src/error/api.js +++ b/packages/build/src/error/api.js @@ -1,4 +1,4 @@ -import isPlainObj from 'is-plain-obj' +import { isPlainObject } from '../utils/is_plain_object.js' import { addErrorInfo } from './info.js' @@ -34,8 +34,8 @@ const apiMethodHandler = async function (endpoint, method, parameters, ...args) const redactError = function (error) { if ( error instanceof Error && - isPlainObj(error.data) && - isPlainObj(error.data.headers) && + isPlainObject(error.data) && + isPlainObject(error.data.headers) && typeof error.data.headers.Authorization === 'string' ) { error.data.headers.Authorization = error.data.headers.Authorization.replace(HEX_REGEXP, 'HEX') diff --git a/packages/build/src/error/handle.ts b/packages/build/src/error/handle.ts index 0a12a6f23a..b82ecb7d5c 100644 --- a/packages/build/src/error/handle.ts +++ b/packages/build/src/error/handle.ts @@ -1,6 +1,6 @@ import { cwd as getCwd } from 'process' -import { pathExists } from 'path-exists' +import { pathExists } from '../utils/path_exists.js' import { ErrorParam } from '../core/types.js' import { logBuildError } from '../log/messages/core.js' diff --git a/packages/build/src/error/parse/clean_stack.js b/packages/build/src/error/parse/clean_stack.js index 78825db485..2ecd2e21cf 100644 --- a/packages/build/src/error/parse/clean_stack.js +++ b/packages/build/src/error/parse/clean_stack.js @@ -1,8 +1,6 @@ import { stripVTControlCharacters } from 'node:util' import { cwd } from 'process' -import cleanStack from 'clean-stack' - // Clean stack traces: // - remove our internal code, e.g. the logic spawning plugins // - remove node modules and Node.js internals @@ -37,13 +35,7 @@ const cleanStackLine = function (lines, line) { return lines } - const lineC = cleanStack(lineB) - - if (lineC === '') { - return lines - } - - return `${lines}\n${lineC}` + return `${lines}\n${lineB}` } // `process.cwd()` can sometimes fail: directory name too long, current @@ -61,7 +53,12 @@ const STACK_LINE_REGEXP = /^\s+at / const shouldRemoveStackLine = function (line) { const lineA = normalizePathSlashes(line) - return INTERNAL_STACK_STRINGS.some((stackString) => lineA.includes(stackString)) || INTERNAL_STACK_REGEXP.test(lineA) + + return ( + INTERNAL_STACK_STRINGS.some((stackString) => lineA.includes(stackString)) || + INTERNAL_STACK_REGEXP.test(lineA) || + NODE_STACK_REGEXP.test(lineA) + ) } const INTERNAL_STACK_STRINGS = [ @@ -71,10 +68,10 @@ const INTERNAL_STACK_STRINGS = [ // nyc internal code 'node_modules/append-transform', 'node_modules/signal-exit', - // Node internals - '(node:', ] +const NODE_STACK_REGEXP = /\bnode:|\(native\)|\bat native\b/ + // This is only needed for local builds and tests const INTERNAL_STACK_REGEXP = /(lib\/|tests\/helpers\/|tests\/.*\/tests.js|node_modules)/ diff --git a/packages/build/src/install/main.js b/packages/build/src/install/main.js index 24ee4d3c5c..6d88926414 100644 --- a/packages/build/src/install/main.js +++ b/packages/build/src/install/main.js @@ -1,9 +1,9 @@ import { homedir } from 'os' import { execa } from 'execa' -import { pathExists } from 'path-exists' import { addErrorInfo } from '../error/info.js' +import { pathExists } from '../utils/path_exists.js' // Install Node.js dependencies in a specific directory export const installDependencies = function ({ packageRoot, isLocal }) { diff --git a/packages/build/src/install/missing.js b/packages/build/src/install/missing.js index 73f1816280..83d81723ef 100644 --- a/packages/build/src/install/missing.js +++ b/packages/build/src/install/missing.js @@ -2,9 +2,8 @@ import { promises as fs } from 'node:fs' import { normalize } from 'node:path' import { fileURLToPath } from 'node:url' -import { pathExists } from 'path-exists' - import { logInstallMissingPlugins, logInstallIntegrations } from '../log/messages/install.js' +import { pathExists } from '../utils/path_exists.js' import { addExactDependencies } from './main.js' diff --git a/packages/build/src/log/messages/core.ts b/packages/build/src/log/messages/core.ts index f3c4b08233..8396ab4e80 100644 --- a/packages/build/src/log/messages/core.ts +++ b/packages/build/src/log/messages/core.ts @@ -1,4 +1,3 @@ -import ansiEscapes from 'ansi-escapes' import prettyMs from 'pretty-ms' import { getFullErrorInfo } from '../../error/parse/parse.js' @@ -52,9 +51,8 @@ A "${sideFile}" file is present in the repository but is missing in the publish ) } -// @todo use `terminal-link` (https://github.com/sindresorhus/terminal-link) -// instead of `ansi-escapes` once -// https://github.com/jamestalmage/supports-hyperlinks/pull/12 is fixed +const ansiLink = (text: string, url: string) => `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007` + export const logLingeringProcesses = function (logs, commands) { logWarning( logs, @@ -66,9 +64,6 @@ The build completed successfully, but the following processes were still running logWarning( logs, ` -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.`, +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.`, ) } diff --git a/packages/build/src/log/messages/mutations.js b/packages/build/src/log/messages/mutations.js index 688b0afbf1..4162584dd6 100644 --- a/packages/build/src/log/messages/mutations.js +++ b/packages/build/src/log/messages/mutations.js @@ -1,8 +1,7 @@ import { promises as fs } from 'fs' import { inspect } from 'util' -import { pathExists } from 'path-exists' - +import { pathExists } from '../../utils/path_exists.js' import { log, logMessage, logSubHeader } from '../logger.js' export const logConfigMutations = function (logs, newConfigMutations, debug) { diff --git a/packages/build/src/plugins/child/diff.js b/packages/build/src/plugins/child/diff.js index d5cda92bdf..63e17a5798 100644 --- a/packages/build/src/plugins/child/diff.js +++ b/packages/build/src/plugins/child/diff.js @@ -1,8 +1,9 @@ import { isDeepStrictEqual } from 'util' -import isPlainObj from 'is-plain-obj' import rfdc from 'rfdc' +import { isPlainObject } from '../../utils/is_plain_object.js' + const clone = rfdc() // Copy `netlifyConfig` so we can compare before/after mutating it @@ -32,7 +33,7 @@ const diffObjects = function (objA, objB, parentKeys) { const valueB = objB[key] const keys = [...parentKeys, key] - if (isPlainObj(valueA) && isPlainObj(valueB)) { + if (isPlainObject(valueA) && isPlainObject(valueB)) { return diffObjects(valueA, valueB, keys) } diff --git a/packages/build/src/plugins/child/load.ts b/packages/build/src/plugins/child/load.ts index 55a9dd6283..6a3df7255b 100644 --- a/packages/build/src/plugins/child/load.ts +++ b/packages/build/src/plugins/child/load.ts @@ -1,5 +1,3 @@ -import { includeKeys } from 'filter-obj' - import { getLogic } from './logic.js' import { registerTypeScript } from './typescript.js' import { validatePlugin } from './validate.js' @@ -15,7 +13,9 @@ export const load = async function ({ pluginPath, inputs, packageJson, verbose, validatePlugin(logic) - const methods = includeKeys(logic, isEventHandler) + const methods = Object.fromEntries( + Object.entries(logic as Record).filter(([, value]) => isEventHandler(value)), + ) const events = Object.keys(methods) // Context passed to every event handler @@ -24,6 +24,6 @@ export const load = async function ({ pluginPath, inputs, packageJson, verbose, return { events, context } } -const isEventHandler = function (_event: unknown, value: unknown): boolean { +const isEventHandler = function (value: unknown): boolean { return typeof value === 'function' } diff --git a/packages/build/src/plugins/child/status.ts b/packages/build/src/plugins/child/status.ts index ad8cce793f..188496299f 100644 --- a/packages/build/src/plugins/child/status.ts +++ b/packages/build/src/plugins/child/status.ts @@ -1,6 +1,5 @@ -import isPlainObj from 'is-plain-obj' - import { addErrorInfo } from '../../error/info.js' +import { isPlainObject } from '../../utils/is_plain_object.js' // Report status information to the UI export const show = function (runState: Record, showArgs: Record) { @@ -33,7 +32,7 @@ function validateShowArgsObject(showArgs: unknown): asserts showArgs is Record> { - const compatibleEntry = await pLocate(versions, async ({ version, overridePinnedVersion, conditions }) => { + let compatibleEntry: PluginVersion | undefined + for (const entry of versions) { + const { version, overridePinnedVersion, conditions } = entry + // When there's a `pinnedVersion`, we typically pick the first version that // matches that range. The exception is if `overridePinnedVersion` is also // present. This property says that if the pinned version is within a given @@ -116,22 +118,27 @@ const getCompatibleEntry = async function ({ // If there's a pinned version and this entry doesn't satisfy that range, // discard it. The exception is if this entry overrides the pinned version. if (pinnedVersion && !overridesPin && !semver.satisfies(version, pinnedVersion, { includePrerelease: true })) { - return false + continue } // no conditions means nothing to filter if (conditions.length === 0 && pinnedVersion === undefined) { - return false + continue } - return ( + const isCompatible = ( await Promise.all( conditions.map(async ({ type, condition }) => CONDITIONS[type].test(condition as any, { nodeVersion, packageJson, packagePath, buildDir }), ), ) ).every(Boolean) - }) + + if (isCompatible) { + compatibleEntry = entry + break + } + } if (compatibleEntry) { systemLog( @@ -187,19 +194,28 @@ const getFirstCompatibleEntry = async function ({ packagePath?: string pinnedVersion?: string }): Promise> { - const compatibleEntry = await pLocate(versions, async ({ conditions }) => { + let compatibleEntry: PluginVersion | undefined + for (const entry of versions) { + const { conditions } = entry + if (conditions.length === 0) { - return true + compatibleEntry = entry + break } - return ( + const isCompatible = ( await Promise.all( conditions.map(async ({ type, condition }) => CONDITIONS[type].test(condition as any, { nodeVersion, packageJson, packagePath, buildDir }), ), ) ).every(Boolean) - }) + + if (isCompatible) { + compatibleEntry = entry + break + } + } if (compatibleEntry) { return compatibleEntry diff --git a/packages/build/src/plugins/ipc.js b/packages/build/src/plugins/ipc.js index e5e8458c5b..26e6194b4a 100644 --- a/packages/build/src/plugins/ipc.js +++ b/packages/build/src/plugins/ipc.js @@ -2,8 +2,6 @@ import crypto from 'crypto' import process from 'process' import { promisify } from 'util' -import { pEvent } from 'p-event' - import { jsonToError, errorToJson } from '../error/build.js' import { addErrorInfo } from '../error/info.js' import { @@ -33,43 +31,38 @@ export const callChild = async function ({ childProcess, eventName, payload, log // child process // - child process `exit` // In the later two cases, we propagate the error. -// We need to make `p-event` listeners are properly cleaned up too. export const getEventFromChild = async function (childProcess, callId) { if (childProcessHasExited(childProcess)) { throw getChildExitError('Could not receive event from child process because it already exited.') } - const messagePromise = pEvent(childProcess, 'message', { filter: (data) => data?.[0] === callId }) - const errorPromise = pEvent(childProcess, 'message', { filter: (data) => data?.[0] === 'error' }) - const exitPromise = pEvent(childProcess, 'exit', { multiArgs: true }) - try { - return await Promise.race([getMessage(messagePromise), getError(errorPromise), getExit(exitPromise)]) - } finally { - messagePromise.cancel() - errorPromise.cancel() - exitPromise.cancel() - } + return new Promise((resolve, reject) => { + const onMessage = function (data) { + if (data?.[0] === callId) { + cleanup() + resolve(data[1]) + } else if (data?.[0] === 'error') { + cleanup() + reject(jsonToError(data[1])) + } + } + const onExit = function (exitCode, signal) { + cleanup() + reject(getChildExitError(`Plugin exited with exit code ${exitCode} and signal ${signal}.`)) + } + const cleanup = function () { + childProcess.removeListener('message', onMessage) + childProcess.removeListener('exit', onExit) + } + childProcess.on('message', onMessage) + childProcess.on('exit', onExit) + }) } const childProcessHasExited = function (childProcess) { return !childProcess.connected || childProcess.signalCode !== null || childProcess.exitCode !== null } -const getMessage = async function (messagePromise) { - const [, response] = await messagePromise - return response -} - -const getError = async function (errorPromise) { - const [, error] = await errorPromise - throw jsonToError(error) -} - -const getExit = async function (exitPromise) { - const [exitCode, signal] = await exitPromise - throw getChildExitError(`Plugin exited with exit code ${exitCode} and signal ${signal}.`) -} - // Plugins should not terminate processes explicitly: // - It prevents specifying error messages to the end users // - It makes it impossible to distinguish between bugs (such as infinite loops) and user errors diff --git a/packages/build/src/plugins/list.ts b/packages/build/src/plugins/list.ts index 537ffc9de7..eb9a5622dd 100644 --- a/packages/build/src/plugins/list.ts +++ b/packages/build/src/plugins/list.ts @@ -1,5 +1,5 @@ import { pluginsUrl, pluginsList as oldPluginsList } from '@netlify/plugins-list' -import isPlainObj from 'is-plain-obj' +import { isPlainObject } from '../utils/is_plain_object.js' import { BufferedLogs } from '../log/logger.js' import { logPluginsList, logPluginsFetchError } from '../log/messages/plugins.js' @@ -116,7 +116,7 @@ const fetchPluginsList = async function ({ } const isValidPluginsList = function (pluginsList): pluginsList is PluginListEntry[] { - return Array.isArray(pluginsList) && pluginsList.every(isPlainObj) + return Array.isArray(pluginsList) && pluginsList.every(isPlainObject) } const normalizePluginsList = function (pluginsList: PluginListEntry[]) { diff --git a/packages/build/src/plugins/manifest/validate.js b/packages/build/src/plugins/manifest/validate.js index a93e4c9d4a..351d0045b7 100644 --- a/packages/build/src/plugins/manifest/validate.js +++ b/packages/build/src/plugins/manifest/validate.js @@ -1,6 +1,5 @@ -import isPlainObj from 'is-plain-obj' - import { THEME } from '../../log/theme.js' +import { isPlainObject } from '../../utils/is_plain_object.js' // Validate `manifest.yml` syntax export const validateManifest = function (manifest, rawManifest) { @@ -19,7 +18,7 @@ ${rawManifest.trim()}` } const validateBasic = function (manifest) { - if (!isPlainObj(manifest)) { + if (!isPlainObject(manifest)) { throw new Error('must be a plain object') } } @@ -56,7 +55,7 @@ const validateInputs = function ({ inputs }) { } const isArrayOfObjects = function (objects) { - return Array.isArray(objects) && objects.every(isPlainObj) + return Array.isArray(objects) && objects.every(isPlainObject) } const validateInput = function (input, index) { diff --git a/packages/build/src/plugins_core/blobs_upload/index.ts b/packages/build/src/plugins_core/blobs_upload/index.ts index f9b3f45133..96f50f6353 100644 --- a/packages/build/src/plugins_core/blobs_upload/index.ts +++ b/packages/build/src/plugins_core/blobs_upload/index.ts @@ -1,6 +1,5 @@ import { getDeployStore, type GetDeployStoreOptions } from '@netlify/blobs' import { inspect } from 'node:util' -import pMap from 'p-map' import { DEFAULT_API_HOST } from '../../core/normalize_flags.js' import { logError } from '../../log/logger.js' @@ -58,15 +57,25 @@ const coreStep: CoreStepFunction = async function ({ systemLog(`Uploading ${blobsToUpload.length} blobs to deploy store...`) try { - await pMap( - blobsToUpload, - async ({ key, contentPath, metadataPath }) => { - const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath) - // `data` is a `Buffer`, typed by @types/node as `Buffer`, which TS - // rejects as a `BlobPart`; the runtime value is a valid blob part. - await blobStore.set(key, new Blob([data as BlobPart]), { metadata }) - }, - { concurrency: 10 }, + const queue = blobsToUpload[Symbol.iterator]() + let stopQueue = false + await Promise.all( + Array.from({ length: 10 }, async () => { + while (!stopQueue) { + const next = queue.next() + if (next.done) { + return + } + const { key, contentPath, metadataPath } = next.value + try { + const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath) + await blobStore.set(key, new Blob([data]), { metadata }) + } catch (error) { + stopQueue = true + throw error + } + } + }), ) } catch (err) { logError(logs, `Error uploading blobs to deploy store: ${err.message}`) diff --git a/packages/build/src/plugins_core/db_setup/migrations.ts b/packages/build/src/plugins_core/db_setup/migrations.ts index b88c1a6d2b..b8c9f8ce4f 100644 --- a/packages/build/src/plugins_core/db_setup/migrations.ts +++ b/packages/build/src/plugins_core/db_setup/migrations.ts @@ -1,7 +1,7 @@ import { copyFile, mkdir } from 'node:fs/promises' import { join, resolve } from 'node:path' -import { pathExists } from 'path-exists' +import { pathExists } from '../../utils/path_exists.js' import { CoreStep, CoreStepCondition, CoreStepFunction } from '../types.js' import { readMigrationEntries, getMigrationsSrc } from './utils.js' diff --git a/packages/build/src/plugins_core/db_setup/utils.ts b/packages/build/src/plugins_core/db_setup/utils.ts index 74c59a3f9a..b3344c445b 100644 --- a/packages/build/src/plugins_core/db_setup/utils.ts +++ b/packages/build/src/plugins_core/db_setup/utils.ts @@ -1,7 +1,7 @@ import { readdir, stat } from 'node:fs/promises' import { join, resolve } from 'node:path' -import { pathExists } from 'path-exists' +import { pathExists } from '../../utils/path_exists.js' // TODO: Remove once we drop support for the legacy `netlify/db/migrations` directory. const LEGACY_DB_MIGRATIONS_SRC = 'netlify/db/migrations' diff --git a/packages/build/src/plugins_core/deploy/buildbot_client.ts b/packages/build/src/plugins_core/deploy/buildbot_client.ts index 5e7b0b0007..d55d0544e8 100644 --- a/packages/build/src/plugins_core/deploy/buildbot_client.ts +++ b/packages/build/src/plugins_core/deploy/buildbot_client.ts @@ -1,9 +1,8 @@ +import { once } from 'events' import net, { NetConnectOpts } from 'net' import { normalize, resolve, relative } from 'path' import { promisify } from 'util' -import { pEvent } from 'p-event' - import { addErrorInfo } from '../../error/info.js' import { runsAfterDeploy } from '../../plugins/events.js' import { addAsyncErrorMessage } from '../../utils/errors.js' @@ -79,7 +78,7 @@ const getConnectionOpts = function (buildbotServerSocket: string): NetConnectOpt * Emits the connect event */ export const connectBuildbotClient = addAsyncErrorMessage(async (client: net.Socket) => { - await pEvent(client, 'connect') + await once(client, 'connect') }, 'Could not connect to buildbot') /** @@ -98,7 +97,7 @@ const writePayload = addAsyncErrorMessage(async (buildbotClient: net.Socket, pay }, 'Could not send payload to buildbot') const getNextParsedResponsePromise = addAsyncErrorMessage(async (buildbotClient: net.Socket) => { - const data = await pEvent<'data', string>(buildbotClient, 'data') + const [data] = await once(buildbotClient, 'data') return JSON.parse(data) as BuildbotResponse }, 'Invalid response from buildbot') diff --git a/packages/build/src/plugins_core/dev_blobs_upload/index.ts b/packages/build/src/plugins_core/dev_blobs_upload/index.ts index b81af1e125..f7f443ee97 100644 --- a/packages/build/src/plugins_core/dev_blobs_upload/index.ts +++ b/packages/build/src/plugins_core/dev_blobs_upload/index.ts @@ -1,5 +1,4 @@ import { getDeployStore, type GetDeployStoreOptions } from '@netlify/blobs' -import pMap from 'p-map' import { log, logError } from '../../log/logger.js' import { getFileWithMetadata, getKeysToUpload, scanForBlobs } from '../../utils/blobs.js' @@ -61,18 +60,28 @@ const coreStep: CoreStepFunction = async function ({ } try { - await pMap( - blobsToUpload, - async ({ key, contentPath, metadataPath }) => { - if (debug && !quiet) { - log(logs, `- Uploading blob ${key}`, { indent: true }) + const queue = blobsToUpload[Symbol.iterator]() + let stopQueue = false + await Promise.all( + Array.from({ length: 10 }, async () => { + while (!stopQueue) { + const next = queue.next() + if (next.done) { + return + } + const { key, contentPath, metadataPath } = next.value + if (debug && !quiet) { + log(logs, `- Uploading blob ${key}`, { indent: true }) + } + try { + const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath) + await blobStore.set(key, new Blob([data]), { metadata }) + } catch (err) { + stopQueue = true + throw err + } } - const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath) - // `data` is a `Buffer`, typed by @types/node as `Buffer`, which TS - // rejects as a `BlobPart`; the runtime value is a valid blob part. - await blobStore.set(key, new Blob([data as BlobPart]), { metadata }) - }, - { concurrency: 10 }, + }), ) } catch (err) { logError(logs, `Error uploading blobs to deploy store: ${err.message}`) diff --git a/packages/build/src/plugins_core/edge_functions/index.ts b/packages/build/src/plugins_core/edge_functions/index.ts index e5dd15075e..8b78d846f6 100644 --- a/packages/build/src/plugins_core/edge_functions/index.ts +++ b/packages/build/src/plugins_core/edge_functions/index.ts @@ -2,7 +2,7 @@ import { promises as fs } from 'fs' import { dirname, join, resolve } from 'path' import { bundle, find } from '@netlify/edge-bundler' -import { pathExists } from 'path-exists' +import { pathExists } from '../../utils/path_exists.js' import { Metric } from '../../core/report_metrics.js' import { log, reduceLogLines } from '../../log/logger.js' diff --git a/packages/build/src/plugins_core/frameworks_api/util.ts b/packages/build/src/plugins_core/frameworks_api/util.ts index d2bb69ba97..1615a776c2 100644 --- a/packages/build/src/plugins_core/frameworks_api/util.ts +++ b/packages/build/src/plugins_core/frameworks_api/util.ts @@ -1,10 +1,9 @@ import { promises as fs } from 'fs' import { resolve } from 'path' -import isPlainObject from 'is-plain-obj' - import type { NetlifyConfig } from '../../index.js' import { FRAMEWORKS_API_CONFIG_PATH } from '../../utils/frameworks_api.js' +import { isPlainObject } from '../../utils/is_plain_object.js' import { SystemLogger } from '../types.js' export const loadConfigFile = async (buildDir: string, packagePath?: string) => { diff --git a/packages/build/src/plugins_core/functions/index.ts b/packages/build/src/plugins_core/functions/index.ts index ebdc41410a..f747a91136 100644 --- a/packages/build/src/plugins_core/functions/index.ts +++ b/packages/build/src/plugins_core/functions/index.ts @@ -1,7 +1,7 @@ import { resolve } from 'path' import { type NodeBundlerName, zipFunctions, type FunctionResult } from '@netlify/zip-it-and-ship-it' -import { pathExists } from 'path-exists' +import { pathExists } from '../../utils/path_exists.js' import { addErrorInfo } from '../../error/info.js' import { log } from '../../log/logger.js' diff --git a/packages/build/src/plugins_core/functions_install/index.js b/packages/build/src/plugins_core/functions_install/index.js index 8ece592d95..5007959d83 100644 --- a/packages/build/src/plugins_core/functions_install/index.js +++ b/packages/build/src/plugins_core/functions_install/index.js @@ -1,6 +1,5 @@ -import { pathExists } from 'path-exists' - import { installFunctionDependencies } from '../../install/functions.js' +import { pathExists } from '../../utils/path_exists.js' // Plugin to package Netlify functions with @netlify/zip-it-and-ship-it export const onPreBuild = async function ({ constants: { FUNCTIONS_SRC, IS_LOCAL } }) { diff --git a/packages/build/src/steps/update_config.js b/packages/build/src/steps/update_config.js index e449c86caf..4c376ed7b0 100644 --- a/packages/build/src/steps/update_config.js +++ b/packages/build/src/steps/update_config.js @@ -1,12 +1,10 @@ import { isDeepStrictEqual } from 'util' -import pFilter from 'p-filter' -import { pathExists } from 'path-exists' - import { resolveUpdatedConfig } from '../core/config.js' import { addErrorInfo } from '../error/info.js' import { logConfigOnUpdate } from '../log/messages/config.js' import { logConfigMutations, systemLogConfigMutations } from '../log/messages/mutations.js' +import { pathExists } from '../utils/path_exists.js' // If `netlifyConfig` was updated or `_redirects` was created, the configuration // is updated by calling `@netlify/config` again. @@ -81,9 +79,11 @@ const haveConfigSideFilesChanged = async function (configSideFiles, headersPath, // sometimes have higher priority and should therefore be deleted in order to // apply any configuration update on `netlify.toml`. export const listConfigSideFiles = async function (sideFiles) { - const configSideFiles = await pFilter(sideFiles, pathExists) + const existingSideFiles = await Promise.all( + sideFiles.map(async (sideFile) => ((await pathExists(sideFile)) ? sideFile : null)), + ) - return configSideFiles.sort() + return existingSideFiles.filter(Boolean).sort() } // Validate each new configuration change diff --git a/packages/build/src/utils/blobs.ts b/packages/build/src/utils/blobs.ts index 73a0494fcb..84c43a8bd5 100644 --- a/packages/build/src/utils/blobs.ts +++ b/packages/build/src/utils/blobs.ts @@ -136,7 +136,7 @@ export const getFileWithMetadata = async ( key: string, contentPath: string, metadataPath?: string, -): Promise<{ data: Buffer; metadata: Record }> => { +): Promise<{ data: Buffer; metadata: Record }> => { const [data, metadata] = await Promise.all([ readFile(contentPath), metadataPath ? readMetadata(metadataPath) : {}, diff --git a/packages/build/src/utils/is_plain_object.ts b/packages/build/src/utils/is_plain_object.ts new file mode 100644 index 0000000000..c52890fd45 --- /dev/null +++ b/packages/build/src/utils/is_plain_object.ts @@ -0,0 +1,8 @@ +export const isPlainObject = (value: unknown): value is Record => { + if (typeof value !== 'object' || value === null) { + return false + } + + const prototype = Object.getPrototypeOf(value) as object | null + return prototype === Object.prototype || prototype === null +} diff --git a/packages/build/src/utils/omit.ts b/packages/build/src/utils/omit.ts index d589868427..bc23da73ea 100644 --- a/packages/build/src/utils/omit.ts +++ b/packages/build/src/utils/omit.ts @@ -1,5 +1,3 @@ -import { excludeKeys } from 'filter-obj' - -// lodash.omit is 1400 lines of codes. filter-obj is much smaller and simpler. +// lodash.omit is 1400 lines of codes. This is much smaller and simpler. export const omit = (obj: T, keys: (keyof T)[]): Partial => - excludeKeys(obj, (key) => keys.includes(key)) + Object.fromEntries(Object.entries(obj).filter(([key]) => !keys.includes(key as keyof T))) as Partial diff --git a/packages/build/src/utils/path_exists.ts b/packages/build/src/utils/path_exists.ts new file mode 100644 index 0000000000..ab9f3303f9 --- /dev/null +++ b/packages/build/src/utils/path_exists.ts @@ -0,0 +1,10 @@ +import { access } from 'node:fs/promises' + +export const pathExists = async (path: string): Promise => { + try { + await access(path) + return true + } catch { + return false + } +} diff --git a/packages/build/src/utils/remove_falsy.ts b/packages/build/src/utils/remove_falsy.ts index fdbc7663a1..3c54ee441d 100644 --- a/packages/build/src/utils/remove_falsy.ts +++ b/packages/build/src/utils/remove_falsy.ts @@ -1,10 +1,8 @@ -import { includeKeys } from 'filter-obj' - // Remove falsy values from object export const removeFalsy = function >(obj: T): Partial { - return includeKeys(obj, isDefined) + return Object.fromEntries(Object.entries(obj).filter(([, value]) => isDefined(value))) as Partial } -const isDefined = function (_key: PropertyKey, value: unknown): boolean { +const isDefined = function (value: unknown): boolean { return value !== undefined && value !== '' } diff --git a/packages/build/tests/blobs_upload/tests.js b/packages/build/tests/blobs_upload/tests.js index d15e50804e..d428a0eadf 100644 --- a/packages/build/tests/blobs_upload/tests.js +++ b/packages/build/tests/blobs_upload/tests.js @@ -1,5 +1,5 @@ import { AsyncLocalStorage } from 'node:async_hooks' -import { access, readFile } from 'node:fs/promises' +import { access, mkdir, readFile, writeFile } from 'node:fs/promises' import { platform } from 'node:process' import { join } from 'path' @@ -204,6 +204,46 @@ test.serial('Blobs upload step uploads files to deploy store', async (t) => { t.deepEqual(blob3.metadata, { some: 'metadata' }) }) +test.serial('Blobs upload step uploads files to dev deploy store', async (t) => { + const fixture = await new Fixture(test.meta.file, './fixtures/src_with_blobs').withCopyRoot({ git: false }) + + const blobsDir = join(fixture.repositoryRoot, '.netlify', 'v1', 'blobs', 'deploy') + await mkdir(join(blobsDir, 'something.txt'), { recursive: true }) + await mkdir(join(blobsDir, 'with-metadata.txt'), { recursive: true }) + await mkdir(join(blobsDir, 'nested', 'blob'), { recursive: true }) + await Promise.all([ + writeFile(join(blobsDir, 'something.txt', 'blob'), 'some value'), + writeFile(join(blobsDir, 'with-metadata.txt', 'blob'), 'another value'), + writeFile(join(blobsDir, 'with-metadata.txt', 'blob.meta.json'), JSON.stringify({ meta: 'data', number: 1234 })), + writeFile(join(blobsDir, 'nested', 'blob', 'blob'), 'file value'), + writeFile(join(blobsDir, 'nested', 'blob', 'blob.meta.json'), JSON.stringify({ some: 'metadata' })), + ]) + + const output = await fixture + .withFlags({ deployId: 'abc123', siteId: 'test', token: TOKEN, offline: true, cwd: fixture.repositoryRoot }) + .runDev(() => Promise.resolve()) + + t.true(output.includes('Uploading 3 blobs to deploy store')) + + // 3 requests for getting pre-signed URLs + 3 requests for hitting them. + t.is(t.context.blobRequests.set?.length, 6) + + const storeOpts = { deployID: 'abc123', siteID: 'test', token: TOKEN } + const store = getDeployStore(storeOpts) + + const blob1 = await store.getWithMetadata('something.txt') + t.is(blob1.data, 'some value') + t.deepEqual(blob1.metadata, {}) + + const blob2 = await store.getWithMetadata('with-metadata.txt') + t.is(blob2.data, 'another value') + t.deepEqual(blob2.metadata, { meta: 'data', number: 1234 }) + + const blob3 = await store.getWithMetadata('nested/blob') + t.is(blob3.data, 'file value') + t.deepEqual(blob3.metadata, { some: 'metadata' }) +}) + test.serial('Blobs upload step cancels deploy if blob metadata is malformed', async (t) => { const fixture = await new Fixture(test.meta.file, './fixtures/src_with_malformed_blobs_metadata').withCopyRoot({ git: false, diff --git a/packages/build/tests/constants/fixtures/functions_src_missing/plugin.js b/packages/build/tests/constants/fixtures/functions_src_missing/plugin.js index d75bddcfb4..ebe536fb85 100644 --- a/packages/build/tests/constants/fixtures/functions_src_missing/plugin.js +++ b/packages/build/tests/constants/fixtures/functions_src_missing/plugin.js @@ -1,5 +1,5 @@ -import { pathExists } from 'path-exists' +import { existsSync } from 'fs' export const onPreBuild = async function ({ constants: { FUNCTIONS_SRC } }) { - console.log(FUNCTIONS_SRC, await pathExists(FUNCTIONS_SRC)) + console.log(FUNCTIONS_SRC, existsSync(FUNCTIONS_SRC)) } diff --git a/packages/build/tests/constants/fixtures/publish_missing/plugin.js b/packages/build/tests/constants/fixtures/publish_missing/plugin.js index a644d01f6f..c660cbc802 100644 --- a/packages/build/tests/constants/fixtures/publish_missing/plugin.js +++ b/packages/build/tests/constants/fixtures/publish_missing/plugin.js @@ -1,5 +1,5 @@ -import { pathExists } from 'path-exists' +import { existsSync } from 'fs' export const onPreBuild = async function ({ constants: { PUBLISH_DIR } }) { - console.log(PUBLISH_DIR, await pathExists(PUBLISH_DIR)) + console.log(PUBLISH_DIR, existsSync(PUBLISH_DIR)) } diff --git a/packages/build/tests/core/tests.js b/packages/build/tests/core/tests.js index 9f8cd35389..0ede3c0cd0 100644 --- a/packages/build/tests/core/tests.js +++ b/packages/build/tests/core/tests.js @@ -1,4 +1,4 @@ -import { promises as fs } from 'fs' +import { promises as fs, existsSync } from 'fs' import { join, resolve } from 'path' import { arch, kill, platform } from 'process' import { fileURLToPath } from 'url' @@ -7,7 +7,6 @@ import { Fixture, normalizeOutput, startServer, removeDir } from '@netlify/testi import test from 'ava' import getNode from 'get-node' import { memoize } from 'micro-memoize' -import { pathExists } from 'path-exists' import { spy, spyOn } from 'tinyspy' import { tmpName } from 'tmp-promise' @@ -716,7 +715,7 @@ test('Generates a `manifest.json` file when running outside of buildbot', async await new Fixture(test.meta.file, './fixtures/functions_internal_manifest').withFlags({ mode: 'cli' }).runWithBuild() const manifestPath = `${FIXTURES_DIR}/functions_internal_manifest/.netlify/functions/manifest.json` - t.true(await pathExists(manifestPath)) + t.true(existsSync(manifestPath)) const { functions, timestamp, version: manifestVersion } = await importJsonFile(manifestPath) @@ -734,7 +733,7 @@ test('Generates a `manifest.json` file when the `buildbot_create_functions_manif const manifestPath = `${FIXTURES_DIR}/functions_internal_manifest/.netlify/functions/manifest.json` - t.true(await pathExists(manifestPath)) + t.true(existsSync(manifestPath)) const { functions, timestamp, version: manifestVersion } = await importJsonFile(manifestPath) diff --git a/packages/build/tests/edge_functions/fixtures/src_missing/plugin.js b/packages/build/tests/edge_functions/fixtures/src_missing/plugin.js index 84f3f7b4e9..30ef40ad21 100644 --- a/packages/build/tests/edge_functions/fixtures/src_missing/plugin.js +++ b/packages/build/tests/edge_functions/fixtures/src_missing/plugin.js @@ -1,5 +1,5 @@ -import { pathExists } from 'path-exists' +import { existsSync } from 'fs' export const onPreBuild = async function ({ constants: { EDGE_FUNCTIONS_SRC } }) { - console.log(EDGE_FUNCTIONS_SRC, await pathExists(EDGE_FUNCTIONS_SRC)) + console.log(EDGE_FUNCTIONS_SRC, existsSync(EDGE_FUNCTIONS_SRC)) } diff --git a/packages/build/tests/edge_functions/tests.js b/packages/build/tests/edge_functions/tests.js index f615b68023..cb28baf063 100644 --- a/packages/build/tests/edge_functions/tests.js +++ b/packages/build/tests/edge_functions/tests.js @@ -1,4 +1,4 @@ -import { promises as fs } from 'fs' +import { promises as fs, existsSync } from 'fs' import { join } from 'path' import { platform } from 'process' import { fileURLToPath } from 'url' @@ -6,7 +6,6 @@ import { fileURLToPath } from 'url' import { DenoBridge } from '@netlify/edge-bundler' import { Fixture, normalizeOutput } from '@netlify/testing' import test from 'ava' -import { pathExists } from 'path-exists' import semver from 'semver' import tmp from 'tmp-promise' @@ -18,7 +17,7 @@ const assertManifest = async (t, fixtureName) => { const distPath = join(FIXTURES_DIR, fixtureName, '.netlify', 'edge-functions-dist') const manifestPath = join(distPath, 'manifest.json') - t.true(await pathExists(manifestPath)) + t.true(existsSync(manifestPath)) const manifestFile = await fs.readFile(manifestPath, 'utf8') const manifest = JSON.parse(manifestFile) @@ -27,7 +26,7 @@ const assertManifest = async (t, fixtureName) => { manifest.bundles.map(async (bundle) => { const bundlePath = join(distPath, bundle.asset) - t.true(await pathExists(bundlePath)) + t.true(existsSync(bundlePath)) }), ) @@ -257,8 +256,8 @@ for (const variant of FLAG_VARIANTS) { await fs.writeFile(oldBundlePath, 'some-data') await fs.writeFile(manifestPath, '{}') - t.true(await pathExists(oldBundlePath)) - t.true(await pathExists(manifestPath)) + t.true(existsSync(oldBundlePath)) + t.true(existsSync(manifestPath)) await fixture.withFlags({ ...variant.flags, mode: 'buildbot' }).runWithBuild() @@ -268,7 +267,7 @@ for (const variant of FLAG_VARIANTS) { const oldBundleAsset = manifest.bundles.find((bundle) => bundle.asset === 'old.eszip') t.is(oldBundleAsset, undefined) - t.false(await pathExists(oldBundlePath)) + t.false(existsSync(oldBundlePath)) }) test.serial(variant.id + ' - builds edge functions generated with the Frameworks API', async (t) => { @@ -414,7 +413,7 @@ for (const variant of FLAG_VARIANTS) { 'manifest.json', ) - t.false(await pathExists(manifestPath)) + t.false(existsSync(manifestPath)) }) test.serial( @@ -430,7 +429,7 @@ for (const variant of FLAG_VARIANTS) { 'manifest.json', ) - t.false(await pathExists(manifestPath)) + t.false(existsSync(manifestPath)) }, ) } diff --git a/packages/build/tests/functions/tests.js b/packages/build/tests/functions/tests.js index e76fff4572..3c29fb20b8 100644 --- a/packages/build/tests/functions/tests.js +++ b/packages/build/tests/functions/tests.js @@ -1,10 +1,10 @@ +import { existsSync } from 'fs' import { readdir, readFile, rm, stat, writeFile } from 'fs/promises' import { join, resolve } from 'path' import { fileURLToPath } from 'url' import { Fixture, normalizeOutput, removeDir, getTempName, unzipFile } from '@netlify/testing' import test from 'ava' -import { pathExists } from 'path-exists' import semver from 'semver' import { trackBundleResults } from '../../lib/log/messages/core_steps.js' @@ -63,7 +63,7 @@ test('Functions: --functionsDistDir', async (t) => { .withFlags({ mode: 'buildbot', functionsDistDir }) .runWithBuild() t.snapshot(normalizeOutput(output)) - t.true(await pathExists(functionsDistDir)) + t.true(existsSync(functionsDistDir)) const files = await readdir(functionsDistDir) // We're expecting two files: the function ZIP and the manifest. t.is(files.length, 2) diff --git a/packages/build/tests/install/tests.js b/packages/build/tests/install/tests.js index 8c694c294a..215bd7ec93 100644 --- a/packages/build/tests/install/tests.js +++ b/packages/build/tests/install/tests.js @@ -1,9 +1,9 @@ +import { existsSync } from 'fs' import { join } from 'path' import { fileURLToPath } from 'url' import { Fixture, normalizeOutput, removeDir } from '@netlify/testing' import test from 'ava' -import { pathExists } from 'path-exists' const FIXTURES_DIR = fileURLToPath(new URL('fixtures', import.meta.url)) @@ -24,7 +24,7 @@ const runInstallFixture = async (t, fixtureName, dirs = [], flags = {}, binary = await Promise.all( dirs.map(async (dir) => { - t.true(await pathExists(dir)) + t.true(existsSync(dir)) }), ) @@ -77,27 +77,27 @@ test('Functions: install dependencies with Yarn in CI', async (t) => { test('Functions: does not install dependencies unless opting in', async (t) => { await runInstallFixture(t, 'optional') - t.false(await pathExists(`${FIXTURES_DIR}/optional/functions/node_modules/`)) + t.false(existsSync(`${FIXTURES_DIR}/optional/functions/node_modules/`)) }) test('Functions: does not install dependencies unless opting in (with esbuild)', async (t) => { await runInstallFixture(t, 'optional-esbuild') - t.false(await pathExists(`${FIXTURES_DIR}/optional-esbuild/functions/node_modules/`)) + t.false(existsSync(`${FIXTURES_DIR}/optional-esbuild/functions/node_modules/`)) }) test('Functions: does not install dependencies unless opting in (with esbuild, many dependencies)', async (t) => { await runInstallFixture(t, 'optional-many-esbuild') - t.false(await pathExists(`${FIXTURES_DIR}/optional-many-esbuild/functions/node_modules/`)) + t.false(existsSync(`${FIXTURES_DIR}/optional-many-esbuild/functions/node_modules/`)) }) test('Functions: does not print warnings when dependency was mispelled', async (t) => { await runInstallFixture(t, 'mispelled_dep') - t.false(await pathExists(`${FIXTURES_DIR}/mispelled_dep/functions/node_modules/`)) + t.false(existsSync(`${FIXTURES_DIR}/mispelled_dep/functions/node_modules/`)) }) test('Functions: does not print warnings when dependency was local', async (t) => { await runInstallFixture(t, 'local_dep') - t.false(await pathExists(`${FIXTURES_DIR}/local_dep/functions/node_modules/`)) + t.false(existsSync(`${FIXTURES_DIR}/local_dep/functions/node_modules/`)) }) test('Functions: install dependencies handles errors', async (t) => { diff --git a/packages/build/tests/mutate_save/tests.js b/packages/build/tests/mutate_save/tests.js index f20d3841ae..3aceea3299 100644 --- a/packages/build/tests/mutate_save/tests.js +++ b/packages/build/tests/mutate_save/tests.js @@ -1,10 +1,10 @@ +import { existsSync } from 'fs' import { copyFile, readFile, rm } from 'fs/promises' import { platform } from 'process' import { fileURLToPath } from 'url' import { Fixture, normalizeOutput, startTcpServer } from '@netlify/testing' import test from 'ava' -import { pathExists } from 'path-exists' import tmp from 'tmp-promise' const FIXTURES_DIR = fileURLToPath(new URL('fixtures', import.meta.url)) @@ -288,7 +288,7 @@ test('--saveConfig creates netlify.toml if it does not exist', async (t) => { }) .runWithBuild() t.snapshot(normalizeOutput(output)) - t.false(await pathExists(configPath)) + t.false(existsSync(configPath)) } finally { await stopServer() }