UNPKG

@netlify/config

Version:
251 lines (250 loc) 10.7 kB
import { getApiClient } from './api/client.js'; import { getSiteInfo } from './api/site_info.js'; import { getInitialBase, getBase, addBase } from './base.js'; import { getBuildDir } from './build_dir.js'; import { getCachedConfig } from './cached_config.js'; import { normalizeContextProps, mergeContext } from './context.js'; import { parseDefaultConfig } from './default.js'; import { getEnv } from './env/main.js'; import { resolveConfigPaths } from './files.js'; import { getHeadersPath, addHeaders } from './headers.js'; import { getInlineConfig } from './inline_config.js'; import { EXTENSION_API_BASE_URL, EXTENSION_API_STAGING_BASE_URL, mergeIntegrations, NETLIFY_API_STAGING_BASE_URL, } from './integrations.js'; import { logResult } from './log/main.js'; import { mergeConfigs } from './merge.js'; import { normalizeBeforeConfigMerge, normalizeAfterConfigMerge } from './merge_normalize.js'; import { addDefaultOpts, normalizeOpts } from './options/main.js'; import { UI_ORIGIN, CONFIG_ORIGIN, INLINE_ORIGIN } from './origin.js'; import { parseConfig } from './parse.js'; import { getConfigPath } from './path.js'; import { getRedirectsPath, addRedirects } from './redirects.js'; /** * Load the configuration file. * Takes an optional configuration file path as input and return the resolved * `config` together with related properties such as the `configPath`. */ export const resolveConfig = async function (opts) { const { cachedConfig, cachedConfigPath, host, scheme, packagePath, pathPrefix, testOpts, token, offline, siteFeatureFlagPrefix, ...optsA } = addDefaultOpts(opts); // `api` is not JSON-serializable, so we cannot cache it inside `cachedConfig` const api = getApiClient({ token, offline, host, scheme, pathPrefix, testOpts }); const parsedCachedConfig = await getCachedConfig({ cachedConfig, cachedConfigPath, token, api }); // If there is a cached config, use it. The exception is when a default config, // which consumers like the CLI can set, is present. In those cases, let the // flow continue so that the default config is parsed and used. if (parsedCachedConfig !== undefined && opts.defaultConfig === undefined) { return parsedCachedConfig; } // TODO(kh): remove this mapping and get the extensionApiHost from the opts const extensionApiBaseUrl = host?.includes(NETLIFY_API_STAGING_BASE_URL) ? EXTENSION_API_STAGING_BASE_URL : EXTENSION_API_BASE_URL; const { config: configOpt, defaultConfig, inlineConfig, configMutations, cwd, context, repositoryRoot, base, branch, siteId, accountId, deployId, buildId, baseRelDir, mode, debug, logs, featureFlags, } = await normalizeOpts(optsA); let { siteInfo, accounts, addons, integrations } = parsedCachedConfig || {}; // If we have cached site info, we don't need to fetch it again const useCachedSiteInfo = Boolean(featureFlags?.use_cached_site_info && siteInfo && accounts && addons && integrations); // I'm adding some debug logging to see if the logic is working as expected if (featureFlags?.use_cached_site_info_logging) { console.log('Checking site information', { useCachedSiteInfo, siteInfo, accounts, addons, integrations }); } if (!useCachedSiteInfo) { const updatedSiteInfo = await getSiteInfo({ api, context, siteId, accountId, mode, siteFeatureFlagPrefix, offline, featureFlags, testOpts, token, extensionApiBaseUrl, }); siteInfo = updatedSiteInfo.siteInfo; accounts = updatedSiteInfo.accounts; addons = updatedSiteInfo.addons; integrations = updatedSiteInfo.integrations; } const { defaultConfig: defaultConfigA, baseRelDir: baseRelDirA } = parseDefaultConfig({ defaultConfig, base, baseRelDir, siteInfo, logs, debug, }); const inlineConfigA = getInlineConfig({ inlineConfig, configMutations, logs, debug }); const { configPath, config, buildDir, redirectsPath, headersPath } = await loadConfig({ configOpt, cwd, context, repositoryRoot, packagePath, branch, defaultConfig: defaultConfigA, inlineConfig: inlineConfigA, baseRelDir: baseRelDirA, logs, featureFlags, }); const env = await getEnv({ api, mode, config, siteInfo, accounts, addons, buildDir, branch, deployId, buildId, context, cachedEnv: parsedCachedConfig?.env || {}, }); // @todo Remove in the next major version. const configA = addLegacyFunctionsDirectory(config); const mergedIntegrations = await mergeIntegrations({ apiIntegrations: integrations, configIntegrations: configA.integrations, context: context, testOpts, offline, extensionApiBaseUrl, }); const result = { siteInfo, integrations: mergedIntegrations, accounts, addons, env, configPath, redirectsPath, headersPath, buildDir, repositoryRoot, config: configA, context, branch, token, api, logs, }; logResult(result, { logs, debug }); return result; }; /** * Adds a `build.functions` property that mirrors `functionsDirectory`, for * backward compatibility. */ const addLegacyFunctionsDirectory = (config) => { if (!config.functionsDirectory) { return config; } return { ...config, build: { ...config.build, functions: config.functionsDirectory } }; }; /** * Try to load the configuration file in two passes. * The first pass uses the `defaultConfig`'s `build.base` (if defined). * The second pass uses the `build.base` from the first pass (if defined). */ const loadConfig = async function ({ configOpt, cwd, context, repositoryRoot, packagePath, branch, defaultConfig, inlineConfig, baseRelDir, logs, featureFlags, }) { const initialBase = getInitialBase({ repositoryRoot, defaultConfig, inlineConfig }); const { configPath, config, buildDir, base, redirectsPath, headersPath } = await getFullConfig({ configOpt, cwd, context, repositoryRoot, branch, defaultConfig, inlineConfig, baseRelDir, packagePath, configBase: initialBase, logs, featureFlags, }); // No second pass needed if: // - there is no `build.base` (in which case both `base` and `initialBase` // are `undefined`) // - `build.base` is the same as the `Base directory` UI setting (already // used in the first round) // - `baseRelDir` feature flag is not used. This feature flag was introduced // to ensure backward compatibility. if (!baseRelDir || base === initialBase) { return { configPath, config, buildDir, redirectsPath, headersPath }; } const { configPath: configPathA, config: configA, buildDir: buildDirA, redirectsPath: redirectsPathA, headersPath: headersPathA, } = await getFullConfig({ cwd, context, repositoryRoot, branch, defaultConfig, inlineConfig, baseRelDir, configBase: base, base, logs, featureFlags, }); return { configPath: configPathA, config: configA, buildDir: buildDirA, redirectsPath: redirectsPathA, headersPath: headersPathA, }; }; /** * Load configuration file and normalize it, merge contexts, etc. */ const getFullConfig = async function ({ configOpt, cwd, context, repositoryRoot, packagePath, branch, defaultConfig, inlineConfig, baseRelDir, configBase, base, logs, featureFlags, }) { const configPath = await getConfigPath({ configOpt, cwd, repositoryRoot, packagePath, configBase }); try { const config = await parseConfig(configPath); const configA = mergeAndNormalizeConfig({ config, defaultConfig, inlineConfig, context, branch, logs, packagePath }); const { config: configB, buildDir, base: baseA, } = await resolveFiles({ packagePath, config: configA, repositoryRoot, base, baseRelDir }); const headersPath = getHeadersPath(configB); const configC = await addHeaders({ config: configB, headersPath, logs }); const redirectsPath = getRedirectsPath(configC); const configD = await addRedirects({ config: configC, redirectsPath, logs, featureFlags }); return { configPath, config: configD, buildDir, base: baseA, redirectsPath, headersPath }; } catch (error) { const configName = configPath === undefined ? '' : ` file ${configPath}`; error.message = `When resolving config${configName}:\n${error.message}`; throw error; } }; /** * Merge: * - `--defaultConfig`: UI build settings and UI-installed plugins * - `inlineConfig`: Netlify CLI flags * Then merge context-specific configuration. * Before and after those steps, also performs validation and normalization. * Those need to be done at different stages depending on whether they should * happen before/after the merges mentioned above. */ const mergeAndNormalizeConfig = function ({ config, defaultConfig, inlineConfig, context, branch, logs, packagePath }) { const configA = normalizeConfigAndContext(config, CONFIG_ORIGIN); const defaultConfigA = normalizeConfigAndContext(defaultConfig, UI_ORIGIN); const inlineConfigA = normalizeConfigAndContext(inlineConfig, INLINE_ORIGIN); const configB = mergeConfigs([defaultConfigA, configA]); const configC = mergeContext({ config: configB, context, branch, logs }); const configD = mergeConfigs([configC, inlineConfigA]); return normalizeAfterConfigMerge(configD, packagePath); }; const normalizeConfigAndContext = function (config, origin) { const configA = normalizeBeforeConfigMerge(config, origin); const configB = normalizeContextProps({ config: configA, origin }); return configB; }; /** * Find base directory, build directory and resolve all paths to absolute paths */ const resolveFiles = async function ({ config, repositoryRoot, base, packagePath, baseRelDir, }) { const baseA = getBase(base, repositoryRoot, config); const buildDir = await getBuildDir(repositoryRoot, baseA); const configA = resolveConfigPaths({ config, packagePath, repositoryRoot, buildDir, baseRelDir }); const configB = addBase(configA, baseA); return { config: configB, buildDir, base: baseA }; };