@netlify/config 18.2.4-rc → 18.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/lib/api/build_settings.js +41 -0
  2. package/lib/api/client.js +15 -0
  3. package/lib/api/site_info.js +67 -0
  4. package/lib/base.js +34 -0
  5. package/lib/bin/flags.js +181 -0
  6. package/lib/bin/main.js +73 -0
  7. package/lib/build_dir.js +26 -0
  8. package/lib/cached_config.js +29 -0
  9. package/lib/case.js +37 -0
  10. package/lib/context.js +108 -0
  11. package/lib/default.js +31 -0
  12. package/lib/env/envelope.js +24 -0
  13. package/lib/env/git.js +23 -0
  14. package/lib/env/main.js +202 -0
  15. package/lib/error.js +36 -0
  16. package/lib/events.js +22 -0
  17. package/lib/files.js +107 -0
  18. package/lib/functions_config.js +83 -0
  19. package/lib/headers.js +30 -0
  20. package/lib/inline_config.js +9 -0
  21. package/lib/log/cleanup.js +92 -0
  22. package/lib/log/logger.js +47 -0
  23. package/lib/log/main.js +48 -0
  24. package/lib/log/messages.js +126 -0
  25. package/lib/log/options.js +43 -0
  26. package/lib/log/serialize.js +5 -0
  27. package/lib/log/theme.js +14 -0
  28. package/lib/main.js +285 -0
  29. package/lib/merge.js +53 -0
  30. package/lib/merge_normalize.js +31 -0
  31. package/lib/mutations/apply.js +78 -0
  32. package/lib/mutations/config_prop_name.js +16 -0
  33. package/lib/mutations/update.js +117 -0
  34. package/lib/normalize.js +36 -0
  35. package/lib/options/base.js +66 -0
  36. package/lib/options/branch.js +34 -0
  37. package/lib/options/feature_flags.js +15 -0
  38. package/lib/options/main.js +111 -0
  39. package/lib/options/repository_root.js +21 -0
  40. package/lib/origin.js +38 -0
  41. package/lib/parse.js +69 -0
  42. package/lib/path.js +52 -0
  43. package/lib/redirects.js +29 -0
  44. package/lib/simplify.js +103 -0
  45. package/lib/utils/group.js +10 -0
  46. package/lib/utils/remove_falsy.js +18 -0
  47. package/lib/utils/set.js +33 -0
  48. package/lib/utils/toml.js +23 -0
  49. package/lib/validate/context.js +57 -0
  50. package/lib/validate/example.js +37 -0
  51. package/lib/validate/helpers.js +31 -0
  52. package/lib/validate/identical.js +20 -0
  53. package/lib/validate/main.js +143 -0
  54. package/lib/validate/validations.js +289 -0
  55. package/package.json +2 -1
@@ -0,0 +1,41 @@
1
+ import { removeFalsy } from '../utils/remove_falsy.js'
2
+
3
+ // Netlify UI build settings are used as default configuration values in
4
+ // production. In local builds, we retrieve them with `getSite` (`siteInfo`)
5
+ // instead.
6
+ // This also includes UI-installed plugins.
7
+ export const addBuildSettings = function ({
8
+ defaultConfig,
9
+ baseRelDir,
10
+ siteInfo: { build_settings: buildSettings, plugins },
11
+ }) {
12
+ if (buildSettings === undefined) {
13
+ return { defaultConfig, baseRelDir }
14
+ }
15
+
16
+ const defaultConfigA = getDefaultConfig(buildSettings, defaultConfig, plugins)
17
+ const baseRelDirA = getBaseRelDir(buildSettings, baseRelDir)
18
+ return { defaultConfig: defaultConfigA, baseRelDir: baseRelDirA }
19
+ }
20
+
21
+ // From the `getSite` API response to the corresponding configuration properties
22
+ const getDefaultConfig = function (
23
+ { cmd: command, dir: publish, functions_dir: functionsDirectory, base },
24
+ { build, plugins = [], ...defaultConfig },
25
+ uiPlugins = [],
26
+ ) {
27
+ const siteBuild = removeFalsy({ command, publish, base })
28
+ const functions = functionsDirectory ? { functionsDirectory, functionsDirectoryOrigin: 'ui' } : {}
29
+ const uiPluginsA = uiPlugins.map(normalizeUiPlugin)
30
+ const pluginsA = [...uiPluginsA, ...plugins]
31
+ return { ...defaultConfig, build: { ...siteBuild, ...build }, plugins: pluginsA, ...functions }
32
+ }
33
+
34
+ // Make sure we know which fields we are picking from the API
35
+ const normalizeUiPlugin = function ({ package: packageName, inputs, pinned_version: pinnedVersion }) {
36
+ return { package: packageName, inputs, pinned_version: pinnedVersion }
37
+ }
38
+
39
+ const getBaseRelDir = function ({ base_rel_dir: siteBaseRelDir }, baseRelDir = siteBaseRelDir) {
40
+ return Boolean(baseRelDir)
41
+ }
@@ -0,0 +1,15 @@
1
+ import { NetlifyAPI } from 'netlify'
2
+
3
+ import { removeUndefined } from '../utils/remove_falsy.js'
4
+
5
+ // Retrieve Netlify API client, if an access token was passed
6
+ export const getApiClient = function ({ token, offline, testOpts = {}, host, scheme, pathPrefix }) {
7
+ if (!token || offline) {
8
+ return
9
+ }
10
+
11
+ // TODO: find less intrusive way to mock HTTP requests
12
+ const parameters = removeUndefined({ scheme: testOpts.scheme || scheme, host: testOpts.host || host, pathPrefix })
13
+ const api = new NetlifyAPI(token, parameters)
14
+ return api
15
+ }
@@ -0,0 +1,67 @@
1
+ import { getEnvelope } from '../env/envelope.js'
2
+ import { throwUserError } from '../error.js'
3
+ import { ERROR_CALL_TO_ACTION } from '../log/messages.js'
4
+
5
+ // Retrieve Netlify Site information, if available.
6
+ // Used to retrieve local build environment variables and UI build settings.
7
+ // This is not used in production builds since the buildbot passes this
8
+ // information instead.
9
+ // Requires knowing the `siteId` and having the access `token`.
10
+ // Silently ignore API errors. For example the network connection might be down,
11
+ // but local builds should still work regardless.
12
+ // eslint-disable-next-line complexity
13
+ export const getSiteInfo = async function ({ api, siteId, mode, testOpts: { env: testEnv = true } = {} }) {
14
+ if (api === undefined || mode === 'buildbot' || !testEnv) {
15
+ const siteInfo = siteId === undefined ? {} : { id: siteId }
16
+ return { siteInfo, accounts: [], addons: [] }
17
+ }
18
+
19
+ const [siteInfo, accounts, addons] = await Promise.all([
20
+ getSite(api, siteId),
21
+ getAccounts(api),
22
+ getAddons(api, siteId),
23
+ ])
24
+
25
+ if (siteInfo.use_envelope) {
26
+ const envelope = await getEnvelope({ api, accountId: siteInfo.account_slug, siteId })
27
+ // eslint-disable-next-line fp/no-mutation
28
+ siteInfo.build_settings.env = envelope
29
+ }
30
+
31
+ return { siteInfo, accounts, addons }
32
+ }
33
+
34
+ const getSite = async function (api, siteId) {
35
+ if (siteId === undefined) {
36
+ return {}
37
+ }
38
+
39
+ try {
40
+ const site = await api.getSite({ siteId })
41
+ return { ...site, id: siteId }
42
+ } catch (error) {
43
+ throwUserError(`Failed retrieving site data for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`)
44
+ }
45
+ }
46
+
47
+ const getAccounts = async function (api) {
48
+ try {
49
+ const accounts = await api.listAccountsForUser()
50
+ return Array.isArray(accounts) ? accounts : []
51
+ } catch (error) {
52
+ throwUserError(`Failed retrieving user account: ${error.message}. ${ERROR_CALL_TO_ACTION}`)
53
+ }
54
+ }
55
+
56
+ const getAddons = async function (api, siteId) {
57
+ if (siteId === undefined) {
58
+ return []
59
+ }
60
+
61
+ try {
62
+ const addons = await api.listServiceInstancesForSite({ siteId })
63
+ return Array.isArray(addons) ? addons : []
64
+ } catch (error) {
65
+ throwUserError(`Failed retrieving addons for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`)
66
+ }
67
+ }
package/lib/base.js ADDED
@@ -0,0 +1,34 @@
1
+ import { resolvePath } from './files.js'
2
+
3
+ // Retrieve the first `base` directory used to load the first config file.
4
+ export const getInitialBase = function ({
5
+ repositoryRoot,
6
+ defaultConfig: { build: { base: defaultBase } = {} },
7
+ inlineConfig: { build: { base: initialBase = defaultBase } = {} },
8
+ }) {
9
+ return resolveBase(repositoryRoot, initialBase)
10
+ }
11
+
12
+ // Two config files can be used:
13
+ // - The first one, using the `config` property or doing a default lookup
14
+ // of `netlify.toml`
15
+ // - If the first one has a `base` property pointing to a directory with
16
+ // another `netlify.toml`, that second config file is used instead.
17
+ // This retrieves the final `base` directory used:
18
+ // - To load the second config file
19
+ // - As the `buildDir`
20
+ // - To resolve file paths
21
+ // If the second file has a `base` property, it is ignored, i.e. it is not
22
+ // recursive.
23
+ export const getBase = function (base, repositoryRoot, config) {
24
+ return base === undefined ? resolveBase(repositoryRoot, config.build.base) : base
25
+ }
26
+
27
+ const resolveBase = function (repositoryRoot, base) {
28
+ return resolvePath(repositoryRoot, repositoryRoot, base, 'build.base')
29
+ }
30
+
31
+ // Also `config.build.base`.
32
+ export const addBase = function (config, base) {
33
+ return { ...config, build: { ...config.build, base } }
34
+ }
@@ -0,0 +1,181 @@
1
+ import process from 'process'
2
+
3
+ import filterObj from 'filter-obj'
4
+ import yargs from 'yargs'
5
+ import { hideBin } from 'yargs/helpers'
6
+
7
+ import { normalizeCliFeatureFlags } from '../options/feature_flags.js'
8
+
9
+ // Parse CLI flags
10
+ export const parseFlags = function () {
11
+ const { featureFlags: cliFeatureFlags = '', ...flags } = yargs(hideBin(process.argv))
12
+ .options(FLAGS)
13
+ .usage(USAGE)
14
+ .parse()
15
+ const featureFlags = normalizeCliFeatureFlags(cliFeatureFlags)
16
+ const flagsA = { ...flags, featureFlags }
17
+ const flagsB = filterObj(flagsA, isUserFlag)
18
+ return flagsB
19
+ }
20
+
21
+ const jsonParse = function (value) {
22
+ return value === undefined ? undefined : JSON.parse(value)
23
+ }
24
+
25
+ // List of CLI flags
26
+ const FLAGS = {
27
+ config: {
28
+ string: true,
29
+ describe: `Path to the configuration file.
30
+ Defaults to any netlify.toml in the git repository root directory or the base directory`,
31
+ },
32
+ defaultConfig: {
33
+ string: true,
34
+ describe: `JSON configuration object containing default values.
35
+ Each configuration default value is used unless overriden through the main configuration file.
36
+ Default: none.`,
37
+ coerce: jsonParse,
38
+ hidden: true,
39
+ },
40
+ cachedConfig: {
41
+ string: true,
42
+ describe: `JSON configuration object returned by @netlify/config when --output=/ is used
43
+ or when using @netlify/config programmatically.
44
+ This is done as a performance optimization to cache the configuration loading logic.
45
+ Default: none.`,
46
+ coerce: jsonParse,
47
+ hidden: true,
48
+ },
49
+ cachedConfigPath: {
50
+ string: true,
51
+ describe: `File path to the JSON configuration object returned by @netlify/config
52
+ when --output=/path is used.
53
+ This is done as a performance optimization to cache the configuration loading logic.
54
+ Default: none.`,
55
+ hidden: true,
56
+ },
57
+ inlineConfig: {
58
+ string: true,
59
+ describe: `JSON configuration object overriding the configuration file and other settings.
60
+ Default: none.`,
61
+ coerce: jsonParse,
62
+ hidden: true,
63
+ },
64
+ configMutations: {
65
+ array: true,
66
+ describe: `Array of changes to apply to the configuration.
67
+ Each change must be an object with three properties:
68
+ - "keys": array of keys targetting the property to change
69
+ - "value": new value of that property
70
+ - "event": build event when this change was applied, e.g. "onPreBuild"
71
+ Default: empty array.`,
72
+ coerce: jsonParse,
73
+ hidden: true,
74
+ },
75
+ cwd: {
76
+ string: true,
77
+ describe: `Current directory. Used to retrieve the configuration file.
78
+ Default: current directory`,
79
+ },
80
+ repositoryRoot: {
81
+ string: true,
82
+ describe: `Git repository root directory. Used to retrieve the configuration file.
83
+ Default: automatically guessed`,
84
+ },
85
+ output: {
86
+ string: true,
87
+ describe: `Where to output the JSON result.
88
+ Default: "-" (stdout)`,
89
+ },
90
+ stable: {
91
+ boolean: true,
92
+ describe: `Sort keys printed in the output.
93
+ Default: false`,
94
+ default: false,
95
+ },
96
+ token: {
97
+ string: true,
98
+ describe: `Netlify API token for authentication.
99
+ The NETLIFY_AUTH_TOKEN environment variable can be used as well.`,
100
+ },
101
+ host: {
102
+ string: true,
103
+ describe: `Host of the Netlify API.`,
104
+ hidden: true,
105
+ },
106
+ scheme: {
107
+ string: true,
108
+ describe: `Scheme/protocol of the Netlify API.`,
109
+ hidden: true,
110
+ },
111
+ pathPrefix: {
112
+ string: true,
113
+ describe: `Base path prefix of the Netlify API.`,
114
+ hidden: true,
115
+ },
116
+ siteId: {
117
+ string: true,
118
+ describe: `Netlify Site ID.`,
119
+ },
120
+ context: {
121
+ string: true,
122
+ describe: `Build context.
123
+ Default: 'production'`,
124
+ },
125
+ branch: {
126
+ string: true,
127
+ describe: `Repository branch.
128
+ Default: automatically guessed`,
129
+ },
130
+ baseRelDir: {
131
+ boolean: true,
132
+ describe: `Feature flag meant for backward compatibility.
133
+ When enabled, if the 'build.base' configuration property is defined, it is used
134
+ to try to retrieve a second configuration file and discard the first one.
135
+ Default: true`,
136
+ hidden: true,
137
+ },
138
+ mode: {
139
+ string: true,
140
+ describe: `Environment in which this is loaded. Can be:
141
+ - 'buildbot': within Netlify Buildbot
142
+ - 'cli': within Netlify CLI
143
+ - 'require': through import('@netlify/config')`,
144
+ hidden: true,
145
+ },
146
+ debug: {
147
+ boolean: true,
148
+ describe: 'Print debugging information',
149
+ hidden: true,
150
+ },
151
+ testOpts: {
152
+ describe: 'Options for testing only',
153
+ hidden: true,
154
+ },
155
+ featureFlags: {
156
+ describe: 'Comma-separated list of feature flags to enable unreleased features',
157
+ hidden: true,
158
+ },
159
+ offline: {
160
+ boolean: true,
161
+ describe: `Do not send requests to the Netlify API to retrieve site settings.
162
+ Default: false`,
163
+ },
164
+ buffer: {
165
+ boolean: true,
166
+ describe: 'Buffer output instead of streaming it',
167
+ hidden: true,
168
+ },
169
+ }
170
+
171
+ const USAGE = `netlify-config [OPTIONS...]
172
+
173
+ Retrieve and resolve the Netlify configuration.
174
+ The result is printed as a JSON object on stdout.`
175
+
176
+ // Remove `yargs`-specific options, shortcuts, dash-cased and aliases
177
+ const isUserFlag = function (key, value) {
178
+ return value !== undefined && !INTERNAL_KEYS.has(key) && key.length !== 1 && !key.includes('-')
179
+ }
180
+
181
+ const INTERNAL_KEYS = new Set(['help', 'version', '_', '$0'])
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { promises as fs } from 'fs'
4
+ import { dirname } from 'path'
5
+ import process from 'process'
6
+
7
+ import fastSafeStringify from 'fast-safe-stringify'
8
+ import omit from 'omit.js'
9
+
10
+ import { isUserError } from '../error.js'
11
+ import { resolveConfig } from '../main.js'
12
+
13
+ import { parseFlags } from './flags.js'
14
+
15
+ // CLI entry point
16
+ const runCli = async function () {
17
+ try {
18
+ const { stable, output = DEFAULT_OUTPUT, ...flags } = parseFlags()
19
+ const result = await resolveConfig(flags)
20
+ await handleCliSuccess(result, stable, output)
21
+ } catch (error) {
22
+ handleCliError(error)
23
+ }
24
+ }
25
+
26
+ const DEFAULT_OUTPUT = '-'
27
+
28
+ // The result is output as JSON on success (exit code 0)
29
+ const handleCliSuccess = async function (result, stable, output) {
30
+ const resultA = serializeApi(result)
31
+ const resultB = omit.default(resultA, SECRET_PROPERTIES)
32
+ const stringifyFunc = stable ? fastSafeStringify.stableStringify : JSON.stringify
33
+ const resultJson = stringifyFunc(resultB, null, 2)
34
+ await outputResult(resultJson, output)
35
+ process.exitCode = 0
36
+ }
37
+
38
+ const outputResult = async function (resultJson, output) {
39
+ if (output === '-') {
40
+ console.log(resultJson)
41
+ return
42
+ }
43
+
44
+ await fs.mkdir(dirname(output), { recursive: true })
45
+ await fs.writeFile(output, resultJson)
46
+ }
47
+
48
+ // `api` is not JSON-serializable, so we remove it
49
+ // We still indicate it as a boolean
50
+ const serializeApi = function ({ api, ...result }) {
51
+ if (api === undefined) {
52
+ return result
53
+ }
54
+
55
+ return { ...result, hasApi: true }
56
+ }
57
+
58
+ const SECRET_PROPERTIES = ['token']
59
+
60
+ const handleCliError = function (error) {
61
+ // Errors caused by users do not show stack traces and have exit code 1
62
+ if (isUserError(error)) {
63
+ console.error(error.message)
64
+ process.exitCode = 1
65
+ return
66
+ }
67
+
68
+ // Internal errors / bugs have exit code 2
69
+ console.error(error.stack)
70
+ process.exitCode = 2
71
+ }
72
+
73
+ runCli()
@@ -0,0 +1,26 @@
1
+ import { isDirectory } from 'path-type'
2
+
3
+ import { throwUserError } from './error.js'
4
+
5
+ // Retrieve the build directory used to resolve most paths.
6
+ // This is (in priority order):
7
+ // - `build.base`
8
+ // - `--repositoryRoot`
9
+ // - the current directory (default value of `--repositoryRoot`)
10
+ export const getBuildDir = async function (repositoryRoot, base) {
11
+ const buildDir = base === undefined ? repositoryRoot : base
12
+ await checkBuildDir(buildDir, repositoryRoot)
13
+ return buildDir
14
+ }
15
+
16
+ // The build directory is used as the current directory of build commands and
17
+ // build plugins. Therefore it must exist.
18
+ // We already check `repositoryRoot` earlier in the code, so only need to check
19
+ // `buildDir` when it is the base directory instead.
20
+ const checkBuildDir = async function (buildDir, repositoryRoot) {
21
+ if (buildDir === repositoryRoot || (await isDirectory(buildDir))) {
22
+ return
23
+ }
24
+
25
+ throwUserError(`Base directory does not exist: ${buildDir}`)
26
+ }
@@ -0,0 +1,29 @@
1
+ import { promises as fs } from 'fs'
2
+
3
+ // Performance optimization when @netlify/config caller has already previously
4
+ // called it and cached the result.
5
+ // This is used by the buildbot which:
6
+ // - first calls @netlify/config since it needs configuration property
7
+ // - later calls @netlify/build, which runs @netlify/config under the hood
8
+ // This is also used by Netlify CLI.
9
+ export const getCachedConfig = async function ({ cachedConfig, cachedConfigPath, token, api }) {
10
+ const parsedCachedConfig = await parseCachedConfig(cachedConfig, cachedConfigPath)
11
+ // The CLI does not print some properties when they are not serializable or
12
+ // are confidential. Those will be missing from `cachedConfig`. The caller
13
+ // must supply them again when using `cachedConfig`.
14
+ return parsedCachedConfig === undefined ? undefined : { token, ...parsedCachedConfig, api }
15
+ }
16
+
17
+ // `cachedConfig` is a plain object while `cachedConfigPath` is a file path to
18
+ // a JSON file. The former is useful in programmatic usage while the second one
19
+ // is useful in CLI usage, to avoid hitting the OS limits if the configuration
20
+ // file is too big.
21
+ const parseCachedConfig = async function (cachedConfig, cachedConfigPath) {
22
+ if (cachedConfig !== undefined) {
23
+ return cachedConfig
24
+ }
25
+
26
+ if (cachedConfigPath !== undefined) {
27
+ return JSON.parse(await fs.readFile(cachedConfigPath))
28
+ }
29
+ }
package/lib/case.js ADDED
@@ -0,0 +1,37 @@
1
+ // Some properties can be optionally capitalized. We normalize them to lowercase
2
+ export const normalizeConfigCase = function ({ Build, build = Build, ...config }) {
3
+ const buildA = normalizeBuildCase(build)
4
+ return { ...config, build: buildA }
5
+ }
6
+
7
+ const normalizeBuildCase = function ({
8
+ Base,
9
+ base = Base,
10
+ Command,
11
+ command = Command,
12
+ Edge_functions: EdgeFunctions,
13
+ edge_functions: edgeFunctions = EdgeFunctions,
14
+ Environment,
15
+ environment = Environment,
16
+ Functions,
17
+ functions = Functions,
18
+ Ignore,
19
+ ignore = Ignore,
20
+ Processing,
21
+ processing = Processing,
22
+ Publish,
23
+ publish = Publish,
24
+ ...build
25
+ } = {}) {
26
+ return {
27
+ ...build,
28
+ base,
29
+ command,
30
+ edge_functions: edgeFunctions,
31
+ environment,
32
+ functions,
33
+ ignore,
34
+ processing,
35
+ publish,
36
+ }
37
+ }
package/lib/context.js ADDED
@@ -0,0 +1,108 @@
1
+ import isPlainObj from 'is-plain-obj'
2
+ import mapObj from 'map-obj'
3
+
4
+ import { mergeConfigs } from './merge.js'
5
+ import { normalizeBeforeConfigMerge } from './merge_normalize.js'
6
+ import { validateContextsPluginsConfig } from './validate/context.js'
7
+ import { validatePreContextConfig } from './validate/main.js'
8
+
9
+ // Validate and normalize `config.context.*`
10
+ export const normalizeContextProps = function ({ config, config: { context: contextProps }, origin }) {
11
+ if (contextProps === undefined) {
12
+ return config
13
+ }
14
+
15
+ validatePreContextConfig(config)
16
+
17
+ const allContextProps = mapObj(contextProps, (key, contextConfig) => [key, addNamespace(contextConfig)])
18
+ const normalizedContextProps = mapObj(allContextProps, (key, contextConfig) => [
19
+ key,
20
+ normalizeBeforeConfigMerge(contextConfig, origin),
21
+ ])
22
+ return { ...config, context: normalizedContextProps }
23
+ }
24
+
25
+ // Merge `config.context.{CONTEXT|BRANCH}.*` to `config.build.*` or `config.*`
26
+ // CONTEXT is the `--context` CLI flag.
27
+ // BRANCH is the `--branch` CLI flag.
28
+ export const mergeContext = function ({
29
+ config: { context: contextProps, ...config },
30
+ config: { plugins },
31
+ context,
32
+ branch,
33
+ logs,
34
+ }) {
35
+ if (contextProps === undefined) {
36
+ return config
37
+ }
38
+
39
+ const contexts = [context, branch]
40
+ validateContextsPluginsConfig({ contextProps, plugins, contexts, logs })
41
+ const filteredContextProps = contexts.map((key) => contextProps[key]).filter(Boolean)
42
+ return mergeConfigs([config, ...filteredContextProps])
43
+ }
44
+
45
+ // `config.context.{context}.*` properties are merged either to `config.*` or
46
+ // to `config.build.*`. We distinguish between both by checking the property
47
+ // name.
48
+ const addNamespace = (contextConfig) => Object.entries(contextConfig).reduce(addNamespacedProperty, {})
49
+
50
+ const addNamespacedProperty = function (contextConfig, [key, value]) {
51
+ return isBuildProperty(key, value)
52
+ ? { ...contextConfig, build: { ...contextConfig.build, [key]: value } }
53
+ : { ...contextConfig, [key]: value }
54
+ }
55
+
56
+ const isBuildProperty = function (key, value) {
57
+ return BUILD_PROPERTIES.has(key) && !isFunctionsConfig(key, value) && !isEdgeFunctionsConfig(key, value)
58
+ }
59
+
60
+ // All properties in `config.build.*`
61
+ const BUILD_PROPERTIES = new Set([
62
+ 'base',
63
+ 'command',
64
+ 'edge_functions',
65
+ 'environment',
66
+ 'functions',
67
+ 'ignore',
68
+ 'processing',
69
+ 'publish',
70
+ ])
71
+
72
+ // `config.functions` is a plain object while `config.build.functions` is a
73
+ // string.
74
+ const isFunctionsConfig = function (key, value) {
75
+ return key === 'functions' && isPlainObj(value)
76
+ }
77
+
78
+ // `config.edge_functions` is an array of objects while
79
+ // `config.build.edge_functions` is a string.
80
+ const isEdgeFunctionsConfig = function (key, value) {
81
+ return key === 'edge_functions' && Array.isArray(value)
82
+ }
83
+
84
+ // Ensure that `inlineConfig` has higher priority than context properties by
85
+ // assigining it to `context.*`. Still keep it at the top-level as well since
86
+ // some properties are not handled context-sensitively by the API.
87
+ // Takes into account that `context.{context}.build.*` is the same as
88
+ // `context.{context}.*`
89
+ export const ensureConfigPriority = function ({ build = {}, ...config }, context, branch) {
90
+ const base = {
91
+ ...config,
92
+ build,
93
+ }
94
+
95
+ // remove the redirects to not have context specific redirects.
96
+ // The redirects should be only on the root level.
97
+ // eslint-disable-next-line fp/no-delete, no-param-reassign
98
+ delete config.redirects
99
+
100
+ return {
101
+ ...base,
102
+ context: {
103
+ ...config.context,
104
+ [context]: { ...config, ...build, build },
105
+ [branch]: { ...config, ...build, build },
106
+ },
107
+ }
108
+ }
package/lib/default.js ADDED
@@ -0,0 +1,31 @@
1
+ import { addBuildSettings } from './api/build_settings.js'
2
+ import { logDefaultConfig } from './log/main.js'
3
+
4
+ // Retrieve default configuration file. It has less priority and it also does
5
+ // not get normalized, merged with contexts, etc.
6
+ export const parseDefaultConfig = function ({ defaultConfig, base, baseRelDir, siteInfo, logs, debug }) {
7
+ const defaultConfigB = addDefaultConfigBase(defaultConfig, base)
8
+ const { defaultConfig: defaultConfigC, baseRelDir: baseRelDirA = DEFAULT_BASE_REL_DIR } = addBuildSettings({
9
+ defaultConfig: defaultConfigB,
10
+ baseRelDir,
11
+ siteInfo,
12
+ })
13
+ logDefaultConfig(defaultConfigC, { logs, debug, baseRelDir: baseRelDirA })
14
+ return { defaultConfig: defaultConfigC, baseRelDir: baseRelDirA }
15
+ }
16
+
17
+ // When the `base` was overridden, add it to `defaultConfig` so it behaves
18
+ // as if it had been specified in the UI settings
19
+ const addDefaultConfigBase = function (defaultConfig, base) {
20
+ if (base === undefined) {
21
+ return defaultConfig
22
+ }
23
+
24
+ const { build = {} } = defaultConfig
25
+ return { ...defaultConfig, build: { ...build, base } }
26
+ }
27
+
28
+ // `baseRelDir` should default to `true` only if the option was not passed and
29
+ // it could be retrieved from the `siteInfo`, which is why the default value
30
+ // is assigned later than other properties.
31
+ const DEFAULT_BASE_REL_DIR = true
@@ -0,0 +1,24 @@
1
+ export const getEnvelope = async function ({ api, accountId, siteId }) {
2
+ if (accountId === undefined) {
3
+ return {}
4
+ }
5
+ try {
6
+ const environmentVariables = await api.getEnvVars({ accountId, siteId })
7
+ // eslint-disable-next-line fp/no-mutating-methods
8
+ const sortedEnvVarsFromDevContext = environmentVariables
9
+ .sort((left, right) => (left.key.toLowerCase() < right.key.toLowerCase() ? -1 : 1))
10
+ .reduce((acc, cur) => {
11
+ const envVar = cur.values.find((val) => ['dev', 'all'].includes(val.context))
12
+ if (envVar && envVar.value) {
13
+ return {
14
+ ...acc,
15
+ [cur.key]: envVar.value,
16
+ }
17
+ }
18
+ return acc
19
+ }, {})
20
+ return sortedEnvVarsFromDevContext
21
+ } catch {
22
+ return {}
23
+ }
24
+ }