diff --git a/packages/build/src/core/build.ts b/packages/build/src/core/build.ts index 1cb2149cdb..5697f80734 100644 --- a/packages/build/src/core/build.ts +++ b/packages/build/src/core/build.ts @@ -166,6 +166,7 @@ const tExecBuild = async function ({ timers: timersB, configMutations, metrics, + returnValues, } = await runAndReportBuild({ pluginsOptions, netlifyConfig, @@ -222,6 +223,7 @@ const tExecBuild = async function ({ timers: timersB, configMutations, metrics, + returnValues, } } @@ -285,6 +287,7 @@ export const runAndReportBuild = async function ({ timers: timersA, configMutations, metrics, + returnValues, } = await initAndRunBuild({ pluginsOptions, netlifyConfig, @@ -370,6 +373,7 @@ export const runAndReportBuild = async function ({ timers: timersA, configMutations, metrics, + returnValues, } } catch (error) { const [{ statuses }] = getErrorInfo(error) @@ -502,6 +506,7 @@ const initAndRunBuild = async function ({ timers: timersC, configMutations, metrics, + returnValues, } = await runBuild({ childProcesses, pluginsOptions: pluginsOptionsA, @@ -560,6 +565,7 @@ const initAndRunBuild = async function ({ timers: timersC, configMutations, metrics, + returnValues, } } finally { // Terminate the child processes of plugins so that they don't linger after @@ -651,6 +657,7 @@ const runBuild = async function ({ timers: timersB, configMutations, metrics, + returnValues, } = await runSteps({ steps, buildbotServerSocket, @@ -698,5 +705,6 @@ const runBuild = async function ({ timers: timersB, configMutations, metrics, + returnValues, } } diff --git a/packages/build/src/core/dev.js b/packages/build/src/core/dev.js index 82dd9999f9..ea40954f71 100644 --- a/packages/build/src/core/dev.js +++ b/packages/build/src/core/dev.js @@ -1,4 +1,5 @@ import { handleBuildError } from '../error/handle.js' +import { getGeneratedFunctions } from '../steps/return_values.js' import { execBuild, startBuild } from './build.js' import { getSeverity } from './severity.js' @@ -8,7 +9,11 @@ export const startDev = async (devCommand, flags = {}) => { const errorParams = { errorMonitor, mode, logs, debug, testOpts } try { - const { netlifyConfig: netlifyConfigA, configMutations } = await execBuild({ + const { + netlifyConfig: netlifyConfigA, + configMutations, + returnValues, + } = await execBuild({ ...normalizedFlags, errorMonitor, errorParams, @@ -21,7 +26,14 @@ export const startDev = async (devCommand, flags = {}) => { }) const { success, severityCode } = getSeverity('success') - return { success, severityCode, netlifyConfig: netlifyConfigA, logs, configMutations } + return { + success, + severityCode, + netlifyConfig: netlifyConfigA, + logs, + configMutations, + generatedFunctions: getGeneratedFunctions(returnValues), + } } catch (error) { const { severity, message, stack } = await handleBuildError(error, errorParams) const { success, severityCode } = getSeverity(severity) diff --git a/packages/build/src/core/main.ts b/packages/build/src/core/main.ts index 767f8b260b..7c7c4e5b3a 100644 --- a/packages/build/src/core/main.ts +++ b/packages/build/src/core/main.ts @@ -6,6 +6,7 @@ import { reportError } from '../error/report.js' import { getSystemLogger } from '../log/logger.js' import type { BufferedLogs } from '../log/logger.js' import { logTimer, logBuildSuccess } from '../log/messages/core.js' +import { getGeneratedFunctions } from '../steps/return_values.js' import { trackBuildComplete } from '../telemetry/main.js' import { reportTimers } from '../time/report.js' import { RootExecutionAttributes } from '../tracing/main.js' @@ -72,6 +73,7 @@ export async function buildSite(flags: Partial = {}): Promise<{ durationNs, configMutations, metrics, + returnValues, } = await execBuild({ ...flagsA, buildId, @@ -117,7 +119,14 @@ export async function buildSite(flags: Partial = {}): Promise<{ testOpts, errorParams, }) - return { success, severityCode, netlifyConfig: netlifyConfigA, logs, configMutations } + return { + success, + severityCode, + netlifyConfig: netlifyConfigA, + logs, + configMutations, + generatedFunctions: getGeneratedFunctions(returnValues), + } } catch (error) { const { severity } = await handleBuildError(error, errorParams as any) const { pluginsOptions, siteInfo, userNodeVersion }: any = errorParams diff --git a/packages/build/src/log/messages/core_steps.js b/packages/build/src/log/messages/core_steps.js index 319447eaf2..c34b64eba4 100644 --- a/packages/build/src/log/messages/core_steps.js +++ b/packages/build/src/log/messages/core_steps.js @@ -72,17 +72,22 @@ export const logFunctionsToBundle = function ({ }) { let needsSpace = false - if (generatedFunctions.length !== 0) { - for (const id in generatedFunctions) { - const { displayName, generatorType, functionNames } = generatedFunctions[id] + for (const id in generatedFunctions) { + if (generatedFunctions[id].length === 0) { + continue + } - if (needsSpace) log(logs, '') + // Getting the generator block from the first function, since it will be + // the same for all of them. + const { generator } = generatedFunctions[id][0] + const functionNames = generatedFunctions[id].map((func) => path.basename(func.path)) - log(logs, `Packaging ${type} generated by ${THEME.highlightWords(displayName)} ${generatorType}:`) - logArray(logs, functionNames, { indent: false }) + if (needsSpace) log(logs, '') - needsSpace = true - } + log(logs, `Packaging ${type} generated by ${THEME.highlightWords(generator.displayName)} ${generator.type}:`) + logArray(logs, functionNames, { indent: false }) + + needsSpace = true } if (internalFunctions.length !== 0) { diff --git a/packages/build/src/plugins_core/edge_functions/index.ts b/packages/build/src/plugins_core/edge_functions/index.ts index d2771c4b5c..46884c0959 100644 --- a/packages/build/src/plugins_core/edge_functions/index.ts +++ b/packages/build/src/plugins_core/edge_functions/index.ts @@ -206,7 +206,7 @@ const logFunctions = async ({ internalFunctionsSrc: internalSrcDirectory, frameworkFunctions: frameworkFunctions.map(({ name }) => name), type: 'Edge Functions', - generatedFunctions: [], + generatedFunctions: {}, }) } diff --git a/packages/build/src/plugins_core/functions/index.ts b/packages/build/src/plugins_core/functions/index.ts index bb0f9cce6d..c502d0c5af 100644 --- a/packages/build/src/plugins_core/functions/index.ts +++ b/packages/build/src/plugins_core/functions/index.ts @@ -1,11 +1,11 @@ -import { basename, resolve } from 'path' +import { resolve } from 'path' import { type NodeBundlerName, RUNTIME, zipFunctions, type FunctionResult } from '@netlify/zip-it-and-ship-it' import { pathExists } from 'path-exists' import { addErrorInfo } from '../../error/info.js' import { log } from '../../log/logger.js' -import type { ReturnValue } from '../../types/step.js' +import { type GeneratedFunction, getGeneratedFunctions } from '../../steps/return_values.js' import { logBundleResults, logFunctionsNonExistingDir, logFunctionsToBundle } from '../../log/messages/core_steps.js' import { FRAMEWORKS_API_FUNCTIONS_ENDPOINT } from '../../utils/frameworks_api.js' @@ -168,7 +168,7 @@ const coreStep = async function ({ } } - const generatedFunctions = getGeneratedFunctionPaths(returnValues) + const generatedFunctions = getGeneratedFunctions(returnValues) logFunctionsToBundle({ logs, @@ -178,7 +178,7 @@ const coreStep = async function ({ internalFunctions, internalFunctionsSrc: relativeInternalFunctionsSrc, frameworkFunctions, - generatedFunctions: getGeneratedFunctionsByGenerator(returnValues), + generatedFunctions: getGeneratedFunctionsByGenerator(generatedFunctions), }) if ( @@ -205,7 +205,7 @@ const coreStep = async function ({ repositoryRoot, userNodeVersion, systemLog, - generatedFunctions, + generatedFunctions: generatedFunctions.map((func) => func.path), }) const metrics = getMetrics(internalFunctions, userFunctions) @@ -257,31 +257,17 @@ const hasFunctionsDirectories = async function ({ return false } -// Takes a list of return values and produces an array with the paths of all -// generated functions. -const getGeneratedFunctionPaths = (returnValues: Record) => { - return Object.values(returnValues).flatMap( - (returnValue) => returnValue.generatedFunctions?.map((func) => func.path) || [], - ) -} - // Takes a list of return values and produces an object with the names of the // generated functions for each generator. This is used for printing logs only. -const getGeneratedFunctionsByGenerator = (returnValues: Record) => { - const result: Record = {} - - for (const id in returnValues) { - const { displayName, generatedFunctions, generatorType } = returnValues[id] - - if (!generatedFunctions || generatedFunctions.length === 0) { - continue - } - - result[id] = { - displayName, - generatorType, - functionNames: generatedFunctions.map((func) => basename(func.path)), - } +const getGeneratedFunctionsByGenerator = ( + generatedFunctions: GeneratedFunction[], +): Record => { + const result: Record = {} + + for (const func of generatedFunctions) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + result[func.generator.name] = result[func.generator.name] || [] + result[func.generator.name].push(func) } return result diff --git a/packages/build/src/steps/return_values.ts b/packages/build/src/steps/return_values.ts new file mode 100644 index 0000000000..82c6f973a3 --- /dev/null +++ b/packages/build/src/steps/return_values.ts @@ -0,0 +1,33 @@ +type GeneratorType = 'build plugin' | 'extension' + +export interface GeneratedFunction { + generator: { + displayName: string + name: string + type: GeneratorType + } + path: string +} + +export interface ReturnValue { + displayName?: string + generatedFunctions?: { path: string }[] + generatorType: GeneratorType +} + +export const getGeneratedFunctions = (returnValues?: Record): GeneratedFunction[] => { + return Object.entries(returnValues ?? {}).flatMap(([name, returnValue]) => { + const generator = { + displayName: returnValue.displayName ?? name, + name, + type: returnValue.generatorType, + } + + return ( + returnValue.generatedFunctions?.map((func) => ({ + generator, + path: func.path, + })) ?? [] + ) + }) +} diff --git a/packages/build/src/steps/run_steps.js b/packages/build/src/steps/run_steps.js index 8c5e26ae36..9f9eedaa5c 100644 --- a/packages/build/src/steps/run_steps.js +++ b/packages/build/src/steps/run_steps.js @@ -164,7 +164,7 @@ export const runSteps = async function ({ const statusesA = addStatus({ newStatus, statuses, event, packageName, pluginPackageJson }) - /** @type import('../types/step.js').ReturnValue */ + /** @type import('../steps/return_values.js').ReturnValue */ const augmentedReturnValue = returnValue ? { ...returnValue, diff --git a/packages/build/src/types/step.ts b/packages/build/src/types/step.ts deleted file mode 100644 index cb6d5fb38a..0000000000 --- a/packages/build/src/types/step.ts +++ /dev/null @@ -1,9 +0,0 @@ -interface GeneratedFunction { - path: string -} - -export interface ReturnValue { - displayName: string - generatedFunctions?: GeneratedFunction[] - generatorType: 'build plugin' | 'extension' -} diff --git a/packages/build/tests/core/tests.js b/packages/build/tests/core/tests.js index 93cff70b39..07716988c9 100644 --- a/packages/build/tests/core/tests.js +++ b/packages/build/tests/core/tests.js @@ -460,15 +460,18 @@ test.serial('Passes the right properties to zip-it-and-ship-it', async (t) => { }) test.serial('Passes functions generated by build plugins to zip-it-and-ship-it', async (t) => { - const mockZipFunctions = sinon.stub().resolves() + const mockZipFunctions = sinon.stub().resolves([]) const stub = sinon.stub(zipItAndShipIt, 'zipFunctions').get(() => mockZipFunctions) const fixtureName = 'functions_generated_from_steps' const fixtureDir = join(FIXTURES_DIR, fixtureName) - await new Fixture(`./fixtures/${fixtureName}`).withFlags({ mode: 'buildbot' }).runWithBuild() + const { success, generatedFunctions } = await new Fixture(`./fixtures/${fixtureName}`) + .withFlags({ mode: 'buildbot' }) + .runWithBuildAndIntrospect() stub.restore() + t.true(success) t.is(mockZipFunctions.callCount, 1) const { generated, user } = mockZipFunctions.firstCall.args[0] @@ -487,6 +490,14 @@ test.serial('Passes functions generated by build plugins to zip-it-and-ship-it', t.is(user.directories.length, 1) t.true(user.directories.includes(resolve(fixtureDir, 'netlify/functions'))) t.is(user.functions, undefined) + + t.is(generatedFunctions.length, 1) + t.deepEqual(generatedFunctions[0].generator, { + displayName: './.netlify/plugins/node_modules/plugin/plugin.mjs', + name: './.netlify/plugins/node_modules/plugin/plugin.mjs', + type: 'build plugin', + }) + t.is(generatedFunctions[0].path, join(fixtureDir, '.netlify/plugins/node_modules/plugin/functions/plugin-func1.mjs')) }) test.serial('Passes the right feature flags to zip-it-and-ship-it', async (t) => {