UNPKG

8.72 kBJavaScriptView Raw
1/* eslint-disable max-lines */
2'use strict'
3
4require('./utils/polyfills')
5
6const { getApiClient } = require('./api/client')
7const { getSiteInfo } = require('./api/site_info')
8const { getInitialBase, getBase, addBase } = require('./base')
9const { getBuildDir } = require('./build_dir')
10const { getCachedConfig } = require('./cached_config')
11const { normalizeContextProps, mergeContext } = require('./context')
12const { parseDefaultConfig } = require('./default')
13const { getEnv } = require('./env/main')
14const { EVENTS } = require('./events')
15const { resolveConfigPaths } = require('./files')
16const { getHeadersPath, addHeaders } = require('./headers')
17const { getInlineConfig } = require('./inline_config')
18const { cleanupConfig } = require('./log/cleanup')
19const { logResult } = require('./log/main')
20const { mergeConfigs } = require('./merge')
21const { normalizeBeforeConfigMerge, normalizeAfterConfigMerge } = require('./merge_normalize')
22const { updateConfig, restoreConfig } = require('./mutations/update')
23const { addDefaultOpts, normalizeOpts } = require('./options/main')
24const { UI_ORIGIN, CONFIG_ORIGIN, INLINE_ORIGIN } = require('./origin')
25const { parseConfig } = require('./parse')
26const { getConfigPath } = require('./path')
27// eslint-disable-next-line import/max-dependencies
28const { getRedirectsPath, addRedirects } = require('./redirects')
29
30// Load the configuration file.
31// Takes an optional configuration file path as input and return the resolved
32// `config` together with related properties such as the `configPath`.
33const resolveConfig = async function (opts) {
34 const { cachedConfig, cachedConfigPath, host, scheme, pathPrefix, testOpts, token, offline, ...optsA } =
35 addDefaultOpts(opts)
36 // `api` is not JSON-serializable, so we cannot cache it inside `cachedConfig`
37 const api = getApiClient({ token, offline, host, scheme, pathPrefix, testOpts })
38
39 const parsedCachedConfig = await getCachedConfig({ cachedConfig, cachedConfigPath, token, api })
40 if (parsedCachedConfig !== undefined) {
41 return parsedCachedConfig
42 }
43
44 const {
45 config: configOpt,
46 defaultConfig,
47 inlineConfig,
48 configMutations,
49 cwd,
50 context,
51 repositoryRoot,
52 base,
53 branch,
54 siteId,
55 deployId,
56 buildId,
57 baseRelDir,
58 mode,
59 debug,
60 logs,
61 } = await normalizeOpts(optsA)
62
63 const { siteInfo, accounts, addons } = await getSiteInfo({ api, siteId, mode, testOpts })
64
65 const { defaultConfig: defaultConfigA, baseRelDir: baseRelDirA } = parseDefaultConfig({
66 defaultConfig,
67 base,
68 baseRelDir,
69 siteInfo,
70 logs,
71 debug,
72 })
73 const inlineConfigA = getInlineConfig({ inlineConfig, configMutations, logs, debug })
74
75 const { configPath, config, buildDir, redirectsPath, headersPath } = await loadConfig({
76 configOpt,
77 cwd,
78 context,
79 repositoryRoot,
80 branch,
81 defaultConfig: defaultConfigA,
82 inlineConfig: inlineConfigA,
83 baseRelDir: baseRelDirA,
84 logs,
85 })
86
87 const env = await getEnv({
88 mode,
89 config,
90 siteInfo,
91 accounts,
92 addons,
93 buildDir,
94 branch,
95 deployId,
96 buildId,
97 context,
98 })
99
100 // @todo Remove in the next major version.
101 const configA = addLegacyFunctionsDirectory(config)
102
103 const result = {
104 siteInfo,
105 accounts,
106 addons,
107 env,
108 configPath,
109 redirectsPath,
110 headersPath,
111 buildDir,
112 repositoryRoot,
113 config: configA,
114 context,
115 branch,
116 token,
117 api,
118 logs,
119 }
120 logResult(result, { logs, debug })
121 return result
122}
123
124// Adds a `build.functions` property that mirrors `functionsDirectory`, for
125// backward compatibility.
126const addLegacyFunctionsDirectory = (config) => {
127 if (!config.functionsDirectory) {
128 return config
129 }
130
131 return {
132 ...config,
133 build: {
134 ...config.build,
135 functions: config.functionsDirectory,
136 },
137 }
138}
139
140// Try to load the configuration file in two passes.
141// The first pass uses the `defaultConfig`'s `build.base` (if defined).
142// The second pass uses the `build.base` from the first pass (if defined).
143const loadConfig = async function ({
144 configOpt,
145 cwd,
146 context,
147 repositoryRoot,
148 branch,
149 defaultConfig,
150 inlineConfig,
151 baseRelDir,
152 logs,
153}) {
154 const initialBase = getInitialBase({ repositoryRoot, defaultConfig, inlineConfig })
155 const { configPath, config, buildDir, base, redirectsPath, headersPath } = await getFullConfig({
156 configOpt,
157 cwd,
158 context,
159 repositoryRoot,
160 branch,
161 defaultConfig,
162 inlineConfig,
163 baseRelDir,
164 configBase: initialBase,
165 logs,
166 })
167
168 // No second pass needed if:
169 // - there is no `build.base` (in which case both `base` and `initialBase`
170 // are `undefined`)
171 // - `build.base` is the same as the `Base directory` UI setting (already
172 // used in the first round)
173 // - `baseRelDir` feature flag is not used. This feature flag was introduced
174 // to ensure backward compatibility.
175 if (!baseRelDir || base === initialBase) {
176 return { configPath, config, buildDir, redirectsPath, headersPath }
177 }
178
179 const {
180 configPath: configPathA,
181 config: configA,
182 buildDir: buildDirA,
183 redirectsPath: redirectsPathA,
184 headersPath: headersPathA,
185 } = await getFullConfig({
186 cwd,
187 context,
188 repositoryRoot,
189 branch,
190 defaultConfig,
191 inlineConfig,
192 baseRelDir,
193 configBase: base,
194 base,
195 logs,
196 })
197 return {
198 configPath: configPathA,
199 config: configA,
200 buildDir: buildDirA,
201 redirectsPath: redirectsPathA,
202 headersPath: headersPathA,
203 }
204}
205
206// Load configuration file and normalize it, merge contexts, etc.
207const getFullConfig = async function ({
208 configOpt,
209 cwd,
210 context,
211 repositoryRoot,
212 branch,
213 defaultConfig,
214 inlineConfig,
215 baseRelDir,
216 configBase,
217 base,
218 logs,
219}) {
220 const configPath = await getConfigPath({ configOpt, cwd, repositoryRoot, configBase })
221
222 try {
223 const config = await parseConfig(configPath)
224 const configA = mergeAndNormalizeConfig({
225 config,
226 defaultConfig,
227 inlineConfig,
228 context,
229 branch,
230 logs,
231 })
232 const {
233 config: configB,
234 buildDir,
235 base: baseA,
236 } = await resolveFiles({ config: configA, repositoryRoot, base, baseRelDir })
237 const headersPath = getHeadersPath(configB)
238 const configC = await addHeaders(configB, headersPath, logs)
239 const redirectsPath = getRedirectsPath(configC)
240 const configD = await addRedirects(configC, redirectsPath, logs)
241 return { configPath, config: configD, buildDir, base: baseA, redirectsPath, headersPath }
242 } catch (error) {
243 const configName = configPath === undefined ? '' : ` file ${configPath}`
244 error.message = `When resolving config${configName}:\n${error.message}`
245 throw error
246 }
247}
248
249// Merge:
250// - `--defaultConfig`: UI build settings and UI-installed plugins
251// - `inlineConfig`: Netlify CLI flags
252// Then merge context-specific configuration.
253// Before and after those steps, also performs validation and normalization.
254// Those need to be done at different stages depending on whether they should
255// happen before/after the merges mentioned above.
256const mergeAndNormalizeConfig = function ({ config, defaultConfig, inlineConfig, context, branch, logs }) {
257 const configA = normalizeConfigAndContext(config, CONFIG_ORIGIN)
258 const defaultConfigA = normalizeConfigAndContext(defaultConfig, UI_ORIGIN)
259 const inlineConfigA = normalizeConfigAndContext(inlineConfig, INLINE_ORIGIN)
260
261 const configB = mergeConfigs([defaultConfigA, configA])
262 const configC = mergeContext({ config: configB, context, branch, logs })
263 const configD = mergeConfigs([configC, inlineConfigA])
264
265 const configE = normalizeAfterConfigMerge(configD)
266 return configE
267}
268
269const normalizeConfigAndContext = function (config, origin) {
270 const configA = normalizeBeforeConfigMerge(config, origin)
271 const configB = normalizeContextProps({ config: configA, origin })
272 return configB
273}
274
275// Find base directory, build directory and resolve all paths to absolute paths
276const resolveFiles = async function ({ config, repositoryRoot, base, baseRelDir }) {
277 const baseA = getBase(base, repositoryRoot, config)
278 const buildDir = await getBuildDir(repositoryRoot, baseA)
279 const configA = await resolveConfigPaths({ config, repositoryRoot, buildDir, baseRelDir })
280 const configB = addBase(configA, baseA)
281 return { config: configB, buildDir, base: baseA }
282}
283
284module.exports = resolveConfig
285// TODO: on next major release, export a single object instead of mutating the
286// top-level function
287module.exports.cleanupConfig = cleanupConfig
288module.exports.updateConfig = updateConfig
289module.exports.restoreConfig = restoreConfig
290module.exports.EVENTS = EVENTS
291/* eslint-enable max-lines */