@netlify/config
Version:
Netlify config module
281 lines (258 loc) • 8.35 kB
JavaScript
/* eslint-disable max-lines */
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 { 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'
export { EVENTS } from './events.js'
export { cleanupConfig } from './log/cleanup.js'
// eslint-disable-next-line import/max-dependencies
export { updateConfig, restoreConfig } from './mutations/update.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, pathPrefix, testOpts, token, offline, ...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 (parsedCachedConfig !== undefined) {
return parsedCachedConfig
}
const {
config: configOpt,
defaultConfig,
inlineConfig,
configMutations,
cwd,
context,
repositoryRoot,
base,
branch,
siteId,
deployId,
buildId,
baseRelDir,
mode,
debug,
logs,
} = await normalizeOpts(optsA)
const { siteInfo, accounts, addons } = await getSiteInfo({ api, siteId, mode, testOpts })
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,
branch,
defaultConfig: defaultConfigA,
inlineConfig: inlineConfigA,
baseRelDir: baseRelDirA,
logs,
})
const env = await getEnv({
mode,
config,
siteInfo,
accounts,
addons,
buildDir,
branch,
deployId,
buildId,
context,
})
// @todo Remove in the next major version.
const configA = addLegacyFunctionsDirectory(config)
const result = {
siteInfo,
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,
branch,
defaultConfig,
inlineConfig,
baseRelDir,
logs,
}) {
const initialBase = getInitialBase({ repositoryRoot, defaultConfig, inlineConfig })
const { configPath, config, buildDir, base, redirectsPath, headersPath } = await getFullConfig({
configOpt,
cwd,
context,
repositoryRoot,
branch,
defaultConfig,
inlineConfig,
baseRelDir,
configBase: initialBase,
logs,
})
// 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,
})
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,
branch,
defaultConfig,
inlineConfig,
baseRelDir,
configBase,
base,
logs,
}) {
const configPath = await getConfigPath({ configOpt, cwd, repositoryRoot, configBase })
try {
const config = await parseConfig(configPath)
const configA = mergeAndNormalizeConfig({
config,
defaultConfig,
inlineConfig,
context,
branch,
logs,
})
const {
config: configB,
buildDir,
base: baseA,
} = await resolveFiles({ config: configA, repositoryRoot, base, baseRelDir })
const headersPath = getHeadersPath(configB)
const configC = await addHeaders(configB, headersPath, logs)
const redirectsPath = getRedirectsPath(configC)
const configD = await addRedirects(configC, redirectsPath, logs)
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 }) {
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])
const configE = normalizeAfterConfigMerge(configD)
return configE
}
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, baseRelDir }) {
const baseA = getBase(base, repositoryRoot, config)
const buildDir = await getBuildDir(repositoryRoot, baseA)
const configA = await resolveConfigPaths({ config, repositoryRoot, buildDir, baseRelDir })
const configB = addBase(configA, baseA)
return { config: configB, buildDir, base: baseA }
}
/* eslint-enable max-lines */