@netlify/build
Version:
Netlify build module
454 lines (453 loc) • 15.6 kB
JavaScript
import { supportedRuntimes } from '@netlify/framework-info';
import { addAttributesToActiveSpan } from '@netlify/opentelemetry-utils';
import { getErrorInfo } from '../error/info.js';
import { startErrorMonitor } from '../error/monitor/start.js';
import { getBufferLogs, getSystemLogger } from '../log/logger.js';
import { logBuildStart } from '../log/messages/core.js';
import { loadPlugins } from '../plugins/load.js';
import { getPluginsOptions } from '../plugins/options.js';
import { pinPlugins } from '../plugins/pinned_version.js';
import { startPlugins, stopPlugins } from '../plugins/spawn.js';
import { addCorePlugins } from '../plugins_core/add.js';
import { reportStatuses } from '../status/report.js';
import { getDevSteps, getSteps } from '../steps/get.js';
import { runSteps } from '../steps/run_steps.js';
import { initTimers, measureDuration } from '../time/main.js';
import { getBlobsEnvironmentContext } from '../utils/blobs.js';
import { getConfigOpts, loadConfig } from './config.js';
import { getConstants } from './constants.js';
import { doDryRun } from './dry.js';
import { warnOnLingeringProcesses } from './lingering.js';
import { warnOnMissingSideFiles } from './missing_side_file.js';
import { normalizeFlags } from './normalize_flags.js';
// Performed on build start. Must be kept small and unlikely to fail since it
// does not have proper error handling. Error handling relies on `errorMonitor`
// being built, which relies itself on flags being normalized.
export const startBuild = function (flags) {
const timers = initTimers();
const logs = getBufferLogs(flags);
if (!flags.quiet) {
logBuildStart(logs);
}
const { bugsnagKey, debug, systemLogFile, ...flagsA } = normalizeFlags(flags, logs);
const errorMonitor = startErrorMonitor({ flags: { debug, systemLogFile, ...flagsA }, logs, bugsnagKey });
return { ...flagsA, debug, systemLogFile, errorMonitor, logs, timers };
};
const tExecBuild = async function ({ config, defaultConfig, cachedConfig, cachedConfigPath, outputConfigPath, cwd, packagePath, repositoryRoot, apiHost, token, siteId, accountId, context, branch, baseRelDir, env: envOpt, debug, systemLogFile, verbose, nodePath, functionsDistDir, edgeFunctionsDistDir, cacheDir, dry, mode, offline, deployId, buildId, testOpts, errorMonitor, errorParams, logs, timers, buildbotServerSocket, sendStatus, saveConfig, featureFlags, timeline, devCommand, quiet, framework, explicitSecretKeys, edgeFunctionsBootstrapURL, eventHandlers, }) {
const configOpts = getConfigOpts({
config,
defaultConfig,
cwd,
repositoryRoot,
packagePath,
apiHost,
token,
siteId,
accountId,
context,
branch,
baseRelDir,
envOpt,
mode,
offline,
deployId,
buildId,
testOpts,
featureFlags,
});
const { netlifyConfig, configPath, headersPath, redirectsPath, buildDir, repositoryRoot: repositoryRootA, packageJson, userNodeVersion, childEnv, context: contextA, branch: branchA, token: tokenA, api, siteInfo, timers: timersA, integrations, } = await loadConfig({
configOpts,
cachedConfig,
defaultConfig,
cachedConfigPath,
envOpt,
debug,
logs,
nodePath,
timers,
quiet,
featureFlags,
});
if (featureFlags.build_automatic_runtime && framework) {
const runtime = supportedRuntimes[framework];
if (runtime !== undefined) {
const skip = childEnv[runtime.skipFlag] === 'true';
const installed = netlifyConfig.plugins.some((plugin) => plugin.package === runtime.package);
if (!installed && !skip) {
netlifyConfig.plugins.push({ package: runtime.package });
}
}
}
const constants = await getConstants({
configPath,
buildDir,
functionsDistDir,
edgeFunctionsDistDir,
cacheDir,
packagePath,
netlifyConfig,
siteInfo,
apiHost,
token: tokenA,
mode,
testOpts,
});
const systemLog = getSystemLogger(logs, debug, systemLogFile);
const pluginsOptions = addCorePlugins({ netlifyConfig, constants });
// `errorParams` is purposely stateful
Object.assign(errorParams, { netlifyConfig, pluginsOptions, siteInfo, childEnv, userNodeVersion });
const { pluginsOptions: pluginsOptionsA, netlifyConfig: netlifyConfigA, stepsCount, timers: timersB, configMutations, metrics, } = await runAndReportBuild({
pluginsOptions,
netlifyConfig,
defaultConfig,
configOpts,
siteInfo,
configPath,
outputConfigPath,
headersPath,
redirectsPath,
buildDir,
repositoryRoot: repositoryRootA,
packagePath,
nodePath,
packageJson,
userNodeVersion,
childEnv,
context: contextA,
branch: branchA,
dry,
mode,
api,
token,
errorMonitor,
deployId,
errorParams,
logs,
debug,
systemLog,
systemLogFile,
verbose,
timers: timersA,
sendStatus,
saveConfig,
testOpts,
buildbotServerSocket,
constants,
featureFlags,
timeline,
devCommand,
quiet,
integrations,
explicitSecretKeys,
edgeFunctionsBootstrapURL,
eventHandlers,
});
return {
pluginsOptions: pluginsOptionsA,
netlifyConfig: netlifyConfigA,
siteInfo,
userNodeVersion,
stepsCount,
timers: timersB,
configMutations,
metrics,
};
};
export const execBuild = measureDuration(tExecBuild, 'total', { parentTag: 'build_site' });
// Runs a build then report any plugin statuses
export const runAndReportBuild = async function ({ pluginsOptions, netlifyConfig, defaultConfig, configOpts, siteInfo, configPath, outputConfigPath, headersPath, redirectsPath, packagePath, buildDir, repositoryRoot, nodePath, packageJson, userNodeVersion, childEnv, context, branch, buildbotServerSocket, constants, dry, mode, api, token, errorMonitor, deployId, errorParams, logs, debug, systemLog, systemLogFile, verbose, timers, sendStatus, saveConfig, testOpts, featureFlags, timeline, devCommand, quiet, integrations, explicitSecretKeys, edgeFunctionsBootstrapURL, eventHandlers, }) {
try {
const { stepsCount, netlifyConfig: netlifyConfigA, statuses, pluginsOptions: pluginsOptionsA, failedPlugins, timers: timersA, configMutations, metrics, } = await initAndRunBuild({
pluginsOptions,
netlifyConfig,
defaultConfig,
configOpts,
siteInfo,
configPath,
outputConfigPath,
headersPath,
redirectsPath,
buildDir,
packagePath,
repositoryRoot,
nodePath,
packageJson,
userNodeVersion,
childEnv,
context,
branch,
dry,
mode,
api,
token,
errorMonitor,
deployId,
errorParams,
logs,
debug,
systemLog,
systemLogFile,
verbose,
timers,
sendStatus,
saveConfig,
testOpts,
buildbotServerSocket,
constants,
featureFlags,
timeline,
devCommand,
quiet,
integrations,
explicitSecretKeys,
edgeFunctionsBootstrapURL,
eventHandlers,
});
await Promise.all([
reportStatuses({
statuses,
childEnv,
api,
mode,
pluginsOptions: pluginsOptionsA,
netlifyConfig: netlifyConfigA,
errorMonitor,
deployId,
logs,
debug,
sendStatus,
testOpts,
}),
pinPlugins({
pluginsOptions: pluginsOptionsA,
failedPlugins,
api,
siteInfo,
childEnv,
mode,
netlifyConfig: netlifyConfigA,
errorMonitor,
logs,
debug,
testOpts,
sendStatus,
}),
]);
return {
pluginsOptions: pluginsOptionsA,
netlifyConfig: netlifyConfigA,
stepsCount,
timers: timersA,
configMutations,
metrics,
};
}
catch (error) {
const [{ statuses }] = getErrorInfo(error);
await reportStatuses({
statuses,
childEnv,
api,
mode,
pluginsOptions,
netlifyConfig,
errorMonitor,
deployId,
logs,
debug,
sendStatus,
testOpts,
});
throw error;
}
};
// Initialize plugin processes then runs a build
const initAndRunBuild = async function ({ pluginsOptions, netlifyConfig, defaultConfig, configOpts, siteInfo, configPath, outputConfigPath, headersPath, redirectsPath, buildDir, packagePath, repositoryRoot, nodePath, packageJson, userNodeVersion, childEnv, context, branch, dry, mode, api, token, errorMonitor, deployId, errorParams, logs, debug, systemLog, systemLogFile, verbose, sendStatus, saveConfig, timers, testOpts, buildbotServerSocket, constants, featureFlags, timeline, devCommand, quiet, integrations, explicitSecretKeys, edgeFunctionsBootstrapURL, eventHandlers, }) {
const pluginsEnv = {
...childEnv,
...getBlobsEnvironmentContext({ api, deployId: deployId, siteId: siteInfo?.id, token }),
};
const { pluginsOptions: pluginsOptionsA, timers: timersA } = await getPluginsOptions({
pluginsOptions,
netlifyConfig,
siteInfo,
buildDir,
nodePath,
packagePath,
packageJson,
userNodeVersion,
mode,
api,
logs,
debug,
sendStatus,
timers,
testOpts,
featureFlags,
integrations,
context,
systemLog,
pluginsEnv,
});
if (pluginsOptionsA?.length) {
const buildPlugins = {};
for (const plugin of pluginsOptionsA) {
if (plugin?.pluginPackageJson?.name) {
buildPlugins[`build.plugins['${plugin.pluginPackageJson.name}']`] = plugin?.pluginPackageJson?.version ?? 'N/A';
}
}
addAttributesToActiveSpan(buildPlugins);
}
errorParams.pluginsOptions = pluginsOptionsA;
const { childProcesses, timers: timersB } = await startPlugins({
pluginsOptions: pluginsOptionsA,
buildDir,
childEnv: pluginsEnv,
logs,
debug,
timers: timersA,
featureFlags,
quiet,
systemLog,
systemLogFile,
});
try {
const { stepsCount, netlifyConfig: netlifyConfigA, statuses, failedPlugins, timers: timersC, configMutations, metrics, } = await runBuild({
childProcesses,
pluginsOptions: pluginsOptionsA,
netlifyConfig,
defaultConfig,
configOpts,
packageJson,
configPath,
outputConfigPath,
userNodeVersion,
headersPath,
redirectsPath,
buildDir,
packagePath,
repositoryRoot,
nodePath,
childEnv,
context,
branch,
dry,
buildbotServerSocket,
constants,
mode,
api,
errorMonitor,
deployId,
errorParams,
logs,
debug,
systemLog,
verbose,
saveConfig,
timers: timersB,
testOpts,
featureFlags,
timeline,
devCommand,
quiet,
explicitSecretKeys,
edgeFunctionsBootstrapURL,
eventHandlers,
});
await Promise.all([
warnOnMissingSideFiles({ buildDir, netlifyConfig: netlifyConfigA, logs }),
warnOnLingeringProcesses({ mode, logs, testOpts }),
]);
return {
stepsCount,
netlifyConfig: netlifyConfigA,
statuses,
pluginsOptions: pluginsOptionsA,
failedPlugins,
timers: timersC,
configMutations,
metrics,
};
}
finally {
// Terminate the child processes of plugins so that they don't linger after
// the build is finished. The exception is when running in the dev timeline
// since those are long-running events by nature.
if (timeline !== 'dev') {
await stopPlugins({
childProcesses,
pluginOptions: pluginsOptionsA,
netlifyConfig,
logs,
verbose,
});
}
}
};
// Load plugin main files, retrieve their event handlers then runs them,
// together with the build command
const runBuild = async function ({ childProcesses, pluginsOptions, netlifyConfig, defaultConfig, configOpts, packageJson, configPath, outputConfigPath, userNodeVersion, headersPath, redirectsPath, buildDir, repositoryRoot, packagePath, nodePath, childEnv, context, branch, dry, buildbotServerSocket, constants, mode, api, errorMonitor, deployId, errorParams, logs, debug, systemLog, verbose, saveConfig, timers, testOpts, featureFlags, timeline, devCommand, quiet, explicitSecretKeys, edgeFunctionsBootstrapURL, eventHandlers, }) {
const { pluginsSteps, timers: timersA } = await loadPlugins({
pluginsOptions,
childProcesses,
packageJson,
timers,
logs,
debug,
verbose,
netlifyConfig,
featureFlags,
systemLog,
});
const { steps, events } = timeline === 'dev' ? getDevSteps(devCommand, pluginsSteps, eventHandlers) : getSteps(pluginsSteps, eventHandlers);
if (dry) {
await doDryRun({ buildDir, steps, netlifyConfig, constants, buildbotServerSocket, logs, featureFlags });
return { netlifyConfig };
}
const { stepsCount, netlifyConfig: netlifyConfigA, statuses, failedPlugins, timers: timersB, configMutations, metrics, } = await runSteps({
steps,
buildbotServerSocket,
events,
configPath,
outputConfigPath,
headersPath,
redirectsPath,
buildDir,
repositoryRoot,
packagePath,
nodePath,
childEnv,
context,
branch,
constants,
mode,
api,
errorMonitor,
deployId,
errorParams,
netlifyConfig,
defaultConfig,
configOpts,
logs,
debug,
systemLog,
verbose,
saveConfig,
timers: timersA,
testOpts,
featureFlags,
quiet,
userNodeVersion,
explicitSecretKeys,
edgeFunctionsBootstrapURL,
});
return {
stepsCount,
netlifyConfig: netlifyConfigA,
statuses,
failedPlugins,
timers: timersB,
configMutations,
metrics,
};
};