@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.
- package/lib/api/build_settings.js +41 -0
- package/lib/api/client.js +15 -0
- package/lib/api/site_info.js +67 -0
- package/lib/base.js +34 -0
- package/lib/bin/flags.js +181 -0
- package/lib/bin/main.js +73 -0
- package/lib/build_dir.js +26 -0
- package/lib/cached_config.js +29 -0
- package/lib/case.js +37 -0
- package/lib/context.js +108 -0
- package/lib/default.js +31 -0
- package/lib/env/envelope.js +24 -0
- package/lib/env/git.js +23 -0
- package/lib/env/main.js +202 -0
- package/lib/error.js +36 -0
- package/lib/events.js +22 -0
- package/lib/files.js +107 -0
- package/lib/functions_config.js +83 -0
- package/lib/headers.js +30 -0
- package/lib/inline_config.js +9 -0
- package/lib/log/cleanup.js +92 -0
- package/lib/log/logger.js +47 -0
- package/lib/log/main.js +48 -0
- package/lib/log/messages.js +126 -0
- package/lib/log/options.js +43 -0
- package/lib/log/serialize.js +5 -0
- package/lib/log/theme.js +14 -0
- package/lib/main.js +285 -0
- package/lib/merge.js +53 -0
- package/lib/merge_normalize.js +31 -0
- package/lib/mutations/apply.js +78 -0
- package/lib/mutations/config_prop_name.js +16 -0
- package/lib/mutations/update.js +117 -0
- package/lib/normalize.js +36 -0
- package/lib/options/base.js +66 -0
- package/lib/options/branch.js +34 -0
- package/lib/options/feature_flags.js +15 -0
- package/lib/options/main.js +111 -0
- package/lib/options/repository_root.js +21 -0
- package/lib/origin.js +38 -0
- package/lib/parse.js +69 -0
- package/lib/path.js +52 -0
- package/lib/redirects.js +29 -0
- package/lib/simplify.js +103 -0
- package/lib/utils/group.js +10 -0
- package/lib/utils/remove_falsy.js +18 -0
- package/lib/utils/set.js +33 -0
- package/lib/utils/toml.js +23 -0
- package/lib/validate/context.js +57 -0
- package/lib/validate/example.js +37 -0
- package/lib/validate/helpers.js +31 -0
- package/lib/validate/identical.js +20 -0
- package/lib/validate/main.js +143 -0
- package/lib/validate/validations.js +289 -0
- package/package.json +2 -1
package/lib/env/git.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
|
|
3
|
+
import { removeFalsy } from '../utils/remove_falsy.js'
|
|
4
|
+
|
|
5
|
+
// Retrieve git-related information for use in environment variables.
|
|
6
|
+
// git is optional and there might be not git repository.
|
|
7
|
+
// We purposely keep this decoupled from the git utility.
|
|
8
|
+
export const getGitEnv = async function (buildDir, branch) {
|
|
9
|
+
const [COMMIT_REF, CACHED_COMMIT_REF] = await Promise.all([
|
|
10
|
+
git(['rev-parse', 'HEAD'], buildDir),
|
|
11
|
+
git(['rev-parse', 'HEAD^'], buildDir),
|
|
12
|
+
])
|
|
13
|
+
const gitEnv = { BRANCH: branch, HEAD: branch, COMMIT_REF, CACHED_COMMIT_REF, PULL_REQUEST: 'false' }
|
|
14
|
+
const gitEnvA = removeFalsy(gitEnv)
|
|
15
|
+
return gitEnvA
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const git = async function (args, cwd) {
|
|
19
|
+
try {
|
|
20
|
+
const { stdout } = await execa('git', args, { cwd })
|
|
21
|
+
return stdout
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
package/lib/env/main.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import omit from 'omit.js'
|
|
2
|
+
|
|
3
|
+
import { removeFalsy } from '../utils/remove_falsy.js'
|
|
4
|
+
|
|
5
|
+
import { getEnvelope } from './envelope.js'
|
|
6
|
+
import { getGitEnv } from './git.js'
|
|
7
|
+
|
|
8
|
+
// Retrieve this site's environment variable. Also take into account team-wide
|
|
9
|
+
// environment variables and addons.
|
|
10
|
+
// The buildbot already has the right environment variables. This is mostly
|
|
11
|
+
// meant so that local builds can mimic production builds
|
|
12
|
+
// TODO: add `netlify.toml` `build.environment`, after normalization
|
|
13
|
+
// TODO: add `CONTEXT` and others
|
|
14
|
+
export const getEnv = async function ({
|
|
15
|
+
api,
|
|
16
|
+
mode,
|
|
17
|
+
config,
|
|
18
|
+
siteInfo,
|
|
19
|
+
accounts,
|
|
20
|
+
addons,
|
|
21
|
+
buildDir,
|
|
22
|
+
branch,
|
|
23
|
+
deployId,
|
|
24
|
+
buildId,
|
|
25
|
+
context,
|
|
26
|
+
}) {
|
|
27
|
+
if (mode === 'buildbot') {
|
|
28
|
+
return {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const generalEnv = await getGeneralEnv({ siteInfo, buildDir, branch, deployId, buildId, context })
|
|
32
|
+
const [accountEnv, addonsEnv, uiEnv, configFileEnv] = await getUserEnv({ api, config, siteInfo, accounts, addons })
|
|
33
|
+
|
|
34
|
+
// Sources of environment variables, in descending order of precedence.
|
|
35
|
+
const sources = [
|
|
36
|
+
{ key: 'configFile', values: configFileEnv },
|
|
37
|
+
{ key: 'ui', values: uiEnv },
|
|
38
|
+
{ key: 'addons', values: addonsEnv },
|
|
39
|
+
{ key: 'account', values: accountEnv },
|
|
40
|
+
{ key: 'general', values: generalEnv },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
// A hash mapping names of environment variables to objects containing the following properties:
|
|
44
|
+
// - sources: List of sources where the environment variable was found. The first element is the source that
|
|
45
|
+
// actually provided the variable (i.e. the one with the highest precedence).
|
|
46
|
+
// - value: The value of the environment variable.
|
|
47
|
+
const env = new Map()
|
|
48
|
+
|
|
49
|
+
sources.forEach((source) => {
|
|
50
|
+
Object.keys(source.values).forEach((key) => {
|
|
51
|
+
if (env.has(key)) {
|
|
52
|
+
const { sources: envSources, value } = env.get(key)
|
|
53
|
+
|
|
54
|
+
env.set(key, {
|
|
55
|
+
sources: [...envSources, source.key],
|
|
56
|
+
value: convertToString(value),
|
|
57
|
+
})
|
|
58
|
+
} else {
|
|
59
|
+
env.set(key, {
|
|
60
|
+
sources: [source.key],
|
|
61
|
+
value: convertToString(source.values[key]),
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return Object.fromEntries(env)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const convertToString = (value) => {
|
|
71
|
+
if (value === null || value === undefined) {
|
|
72
|
+
return value
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (typeof value === 'string') {
|
|
76
|
+
return value
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return value.toString()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Environment variables not set by users, but meant to mimic the production
|
|
83
|
+
// environment.
|
|
84
|
+
const getGeneralEnv = async function ({
|
|
85
|
+
siteInfo,
|
|
86
|
+
siteInfo: { id, name },
|
|
87
|
+
buildDir,
|
|
88
|
+
branch,
|
|
89
|
+
deployId,
|
|
90
|
+
buildId,
|
|
91
|
+
context,
|
|
92
|
+
}) {
|
|
93
|
+
const gitEnv = await getGitEnv(buildDir, branch)
|
|
94
|
+
const deployUrls = getDeployUrls({ siteInfo, branch, deployId })
|
|
95
|
+
return removeFalsy({
|
|
96
|
+
SITE_ID: id,
|
|
97
|
+
SITE_NAME: name,
|
|
98
|
+
DEPLOY_ID: deployId,
|
|
99
|
+
BUILD_ID: buildId,
|
|
100
|
+
...deployUrls,
|
|
101
|
+
CONTEXT: context,
|
|
102
|
+
NETLIFY_LOCAL: 'true',
|
|
103
|
+
...gitEnv,
|
|
104
|
+
// Localization
|
|
105
|
+
LANG: 'en_US.UTF-8',
|
|
106
|
+
LANGUAGE: 'en_US:en',
|
|
107
|
+
LC_ALL: 'en_US.UTF-8',
|
|
108
|
+
// Disable telemetry of some tools
|
|
109
|
+
GATSBY_TELEMETRY_DISABLED: '1',
|
|
110
|
+
NEXT_TELEMETRY_DISABLED: '1',
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const getDeployUrls = function ({
|
|
115
|
+
siteInfo: { name = DEFAULT_SITE_NAME, ssl_url: sslUrl, build_settings: { repo_url: REPOSITORY_URL } = {} },
|
|
116
|
+
branch,
|
|
117
|
+
deployId,
|
|
118
|
+
}) {
|
|
119
|
+
return {
|
|
120
|
+
URL: sslUrl,
|
|
121
|
+
REPOSITORY_URL,
|
|
122
|
+
DEPLOY_PRIME_URL: `https://${branch}--${name}${NETLIFY_DEFAULT_DOMAIN}`,
|
|
123
|
+
DEPLOY_URL: `https://${deployId}--${name}${NETLIFY_DEFAULT_DOMAIN}`,
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const NETLIFY_DEFAULT_DOMAIN = '.netlify.app'
|
|
128
|
+
// `site.name` is `undefined` when there is no token or siteId
|
|
129
|
+
const DEFAULT_SITE_NAME = 'site-name'
|
|
130
|
+
|
|
131
|
+
// Environment variables specified by the user
|
|
132
|
+
const getUserEnv = async function ({ api, config, siteInfo, accounts, addons }) {
|
|
133
|
+
const accountEnv = await getAccountEnv({ api, siteInfo, accounts })
|
|
134
|
+
const addonsEnv = getAddonsEnv(addons)
|
|
135
|
+
const uiEnv = getUiEnv({ siteInfo })
|
|
136
|
+
const configFileEnv = getConfigFileEnv({ config })
|
|
137
|
+
return [accountEnv, addonsEnv, uiEnv, configFileEnv].map(cleanUserEnv)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Account-wide environment variables
|
|
141
|
+
const getAccountEnv = async function ({ api, siteInfo, accounts }) {
|
|
142
|
+
if (siteInfo.use_envelope) {
|
|
143
|
+
const envelope = await getEnvelope({ api, accountId: siteInfo.account_slug })
|
|
144
|
+
return envelope
|
|
145
|
+
}
|
|
146
|
+
const { site_env: siteEnv = {} } = accounts.find(({ slug }) => slug === siteInfo.account_slug) || {}
|
|
147
|
+
return siteEnv
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Environment variables from addons
|
|
151
|
+
const getAddonsEnv = function (addons) {
|
|
152
|
+
return Object.assign({}, ...addons.map(getAddonEnv))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const getAddonEnv = function ({ env }) {
|
|
156
|
+
return env
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Site-specific environment variables set in the UI
|
|
160
|
+
const getUiEnv = function ({ siteInfo: { build_settings: { env = {} } = {} } }) {
|
|
161
|
+
return env
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Site-specific environment variables set in netlify.toml
|
|
165
|
+
const getConfigFileEnv = function ({
|
|
166
|
+
config: {
|
|
167
|
+
build: { environment = {} },
|
|
168
|
+
},
|
|
169
|
+
}) {
|
|
170
|
+
return environment
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Some environment variables cannot be overridden by configuration
|
|
174
|
+
const cleanUserEnv = function (userEnv) {
|
|
175
|
+
return omit.default(userEnv, READONLY_ENV)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const READONLY_ENV = [
|
|
179
|
+
// Set in local builds
|
|
180
|
+
'BRANCH',
|
|
181
|
+
'CACHED_COMMIT_REF',
|
|
182
|
+
'COMMIT_REF',
|
|
183
|
+
'CONTEXT',
|
|
184
|
+
'HEAD',
|
|
185
|
+
'REPOSITORY_URL',
|
|
186
|
+
'URL',
|
|
187
|
+
|
|
188
|
+
// CI builds set NETLIFY=true while CLI and programmatic builds set
|
|
189
|
+
// NETLIFY_LOCAL=true
|
|
190
|
+
'NETLIFY',
|
|
191
|
+
'NETLIFY_LOCAL',
|
|
192
|
+
|
|
193
|
+
// Not set in local builds because there is no CI build/deploy, incoming hooks nor PR
|
|
194
|
+
'INCOMING_HOOK_BODY',
|
|
195
|
+
'INCOMING_HOOK_TITLE',
|
|
196
|
+
'INCOMING_HOOK_URL',
|
|
197
|
+
'NETLIFY_BUILD_BASE',
|
|
198
|
+
'NETLIFY_BUILD_LIFECYCLE_TRIAL',
|
|
199
|
+
'NETLIFY_IMAGES_CDN_DOMAIN',
|
|
200
|
+
'PULL_REQUEST',
|
|
201
|
+
'REVIEW_ID',
|
|
202
|
+
]
|
package/lib/error.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// We distinguish between errors thrown intentionally and uncaught exceptions
|
|
2
|
+
// (such as bugs) with a `customErrorInfo.type` property.
|
|
3
|
+
export const throwUserError = function (messageOrError, error) {
|
|
4
|
+
const errorA = getError(messageOrError, error)
|
|
5
|
+
errorA[CUSTOM_ERROR_KEY] = { type: USER_ERROR_TYPE }
|
|
6
|
+
throw errorA
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Can pass either `message`, `error` or `message, error`
|
|
10
|
+
const getError = function (messageOrError, error) {
|
|
11
|
+
if (messageOrError instanceof Error) {
|
|
12
|
+
return messageOrError
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (error === undefined) {
|
|
16
|
+
return new Error(messageOrError)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
error.message = `${messageOrError}\n${error.message}`
|
|
20
|
+
return error
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const isUserError = function (error) {
|
|
24
|
+
return (
|
|
25
|
+
canHaveErrorInfo(error) && error[CUSTOM_ERROR_KEY] !== undefined && error[CUSTOM_ERROR_KEY].type === USER_ERROR_TYPE
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Exceptions that are not objects (including `Error` instances) cannot have an
|
|
30
|
+
// `CUSTOM_ERROR_KEY` property
|
|
31
|
+
const canHaveErrorInfo = function (error) {
|
|
32
|
+
return error != null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const CUSTOM_ERROR_KEY = 'customErrorInfo'
|
|
36
|
+
const USER_ERROR_TYPE = 'resolveConfig'
|
package/lib/events.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// List of build plugins events
|
|
2
|
+
export const EVENTS = [
|
|
3
|
+
// Before build command
|
|
4
|
+
'onPreBuild',
|
|
5
|
+
// After build command, before Functions bundling
|
|
6
|
+
'onBuild',
|
|
7
|
+
// After Functions bundling
|
|
8
|
+
'onPostBuild',
|
|
9
|
+
// After build success
|
|
10
|
+
'onSuccess',
|
|
11
|
+
// After build error
|
|
12
|
+
'onError',
|
|
13
|
+
// After build error or success
|
|
14
|
+
'onEnd',
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export const DEV_EVENTS = [
|
|
18
|
+
// Before dev command
|
|
19
|
+
'onPreDev',
|
|
20
|
+
// The dev command
|
|
21
|
+
'onDev',
|
|
22
|
+
]
|
package/lib/files.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { resolve, relative, parse } from 'path'
|
|
2
|
+
|
|
3
|
+
import { getProperty, setProperty, deleteProperty } from 'dot-prop'
|
|
4
|
+
import { pathExists } from 'path-exists'
|
|
5
|
+
|
|
6
|
+
import { throwUserError } from './error.js'
|
|
7
|
+
import { mergeConfigs } from './merge.js'
|
|
8
|
+
import { isTruthy } from './utils/remove_falsy.js'
|
|
9
|
+
|
|
10
|
+
// Make configuration paths relative to `buildDir` and converts them to
|
|
11
|
+
// absolute paths
|
|
12
|
+
export const resolveConfigPaths = async function ({ config, repositoryRoot, buildDir, baseRelDir }) {
|
|
13
|
+
const baseRel = baseRelDir ? buildDir : repositoryRoot
|
|
14
|
+
const configA = resolvePaths(config, FILE_PATH_CONFIG_PROPS, baseRel, repositoryRoot)
|
|
15
|
+
const configB = await addDefaultPaths(configA, repositoryRoot, baseRel)
|
|
16
|
+
return configB
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// All file paths in the configuration file are are relative to `buildDir`
|
|
20
|
+
// (if `baseRelDir` is `true`).
|
|
21
|
+
const FILE_PATH_CONFIG_PROPS = ['functionsDirectory', 'build.publish', 'build.edge_functions']
|
|
22
|
+
|
|
23
|
+
const resolvePaths = function (config, propNames, baseRel, repositoryRoot) {
|
|
24
|
+
return propNames.reduce((configA, propName) => resolvePathProp(configA, propName, baseRel, repositoryRoot), config)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const resolvePathProp = function (config, propName, baseRel, repositoryRoot) {
|
|
28
|
+
const path = getProperty(config, propName)
|
|
29
|
+
|
|
30
|
+
if (!isTruthy(path)) {
|
|
31
|
+
deleteProperty(config, propName)
|
|
32
|
+
return config
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return setProperty(config, propName, resolvePath(repositoryRoot, baseRel, path, propName))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const resolvePath = function (repositoryRoot, baseRel, originalPath, propName) {
|
|
39
|
+
if (!isTruthy(originalPath)) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const path = originalPath.replace(LEADING_SLASH_REGEXP, '')
|
|
44
|
+
const pathA = resolve(baseRel, path)
|
|
45
|
+
validateInsideRoot(originalPath, pathA, repositoryRoot, propName)
|
|
46
|
+
return pathA
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// We allow paths in configuration file to start with /
|
|
50
|
+
// In that case, those are actually relative paths not absolute.
|
|
51
|
+
const LEADING_SLASH_REGEXP = /^\/+/
|
|
52
|
+
|
|
53
|
+
// We ensure all file paths are within the repository root directory.
|
|
54
|
+
// However we allow file paths to be outside of the build directory, since this
|
|
55
|
+
// can be convenient in monorepo setups.
|
|
56
|
+
const validateInsideRoot = function (originalPath, path, repositoryRoot, propName) {
|
|
57
|
+
if (relative(repositoryRoot, path).startsWith('..') || getWindowsDrive(repositoryRoot) !== getWindowsDrive(path)) {
|
|
58
|
+
throwUserError(
|
|
59
|
+
`Configuration property "${propName}" "${originalPath}" must be inside the repository root directory.`,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const getWindowsDrive = function (path) {
|
|
65
|
+
return parse(path).root
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Some configuration properties have default values that are only set if a
|
|
69
|
+
// specific directory/file exists in the build directory
|
|
70
|
+
const addDefaultPaths = async function (config, repositoryRoot, baseRel) {
|
|
71
|
+
const defaultPathsConfigs = await Promise.all(
|
|
72
|
+
DEFAULT_PATHS.map(({ defaultPath, getConfig, propName }) =>
|
|
73
|
+
addDefaultPath({ repositoryRoot, baseRel, defaultPath, getConfig, propName }),
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
const defaultPathsConfigsA = defaultPathsConfigs.filter(Boolean)
|
|
77
|
+
return mergeConfigs([...defaultPathsConfigsA, config])
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const DEFAULT_PATHS = [
|
|
81
|
+
// @todo Remove once we drop support for the legacy default functions directory.
|
|
82
|
+
{
|
|
83
|
+
getConfig: (directory) => ({ functionsDirectory: directory, functionsDirectoryOrigin: 'default-v1' }),
|
|
84
|
+
defaultPath: 'netlify-automatic-functions',
|
|
85
|
+
propName: 'functions.directory',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
getConfig: (directory) => ({ functionsDirectory: directory, functionsDirectoryOrigin: 'default' }),
|
|
89
|
+
defaultPath: 'netlify/functions',
|
|
90
|
+
propName: 'functions.directory',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
getConfig: (directory) => ({ build: { edge_functions: directory } }),
|
|
94
|
+
defaultPath: 'netlify/edge-functions',
|
|
95
|
+
propName: 'build.edge_functions',
|
|
96
|
+
},
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
const addDefaultPath = async function ({ repositoryRoot, baseRel, defaultPath, getConfig, propName }) {
|
|
100
|
+
const absolutePath = resolvePath(repositoryRoot, baseRel, defaultPath, propName)
|
|
101
|
+
|
|
102
|
+
if (!(await pathExists(absolutePath))) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return getConfig(absolutePath)
|
|
107
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import isPlainObj from 'is-plain-obj'
|
|
2
|
+
|
|
3
|
+
import { isDefined } from './utils/remove_falsy.js'
|
|
4
|
+
|
|
5
|
+
export const bundlers = ['esbuild', 'nft', 'zisi']
|
|
6
|
+
export const WILDCARD_ALL = '*'
|
|
7
|
+
|
|
8
|
+
// Removing the legacy `functions` from the `build` block.
|
|
9
|
+
// Looking for a default directory in the `functions` block, separating it
|
|
10
|
+
// from the rest of the configuration if it exists.
|
|
11
|
+
export const normalizeFunctionsProps = function (
|
|
12
|
+
{ functions: v1FunctionsDirectory, ...build },
|
|
13
|
+
{ [WILDCARD_ALL]: wildcardProps, ...functions },
|
|
14
|
+
) {
|
|
15
|
+
const functionsA = Object.entries(functions).reduce(normalizeFunctionsProp, { [WILDCARD_ALL]: wildcardProps })
|
|
16
|
+
const { directory: functionsDirectory, functions: functionsB } = extractFunctionsDirectory(functionsA)
|
|
17
|
+
const functionsDirectoryProps = getFunctionsDirectoryProps({ functionsDirectory, v1FunctionsDirectory })
|
|
18
|
+
return { build, functions: functionsB, functionsDirectoryProps }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Normalizes a functions configuration object, so that the first level of keys
|
|
22
|
+
// represents function expressions mapped to a configuration object.
|
|
23
|
+
//
|
|
24
|
+
// Example input:
|
|
25
|
+
// {
|
|
26
|
+
// "external_node_modules": ["one"],
|
|
27
|
+
// "my-function": { "external_node_modules": ["two"] }
|
|
28
|
+
// }
|
|
29
|
+
//
|
|
30
|
+
// Example output:
|
|
31
|
+
// {
|
|
32
|
+
// "*": { "external_node_modules": ["one"] },
|
|
33
|
+
// "my-function": { "external_node_modules": ["two"] }
|
|
34
|
+
// }
|
|
35
|
+
// If `prop` is one of `configProperties``, we might be dealing with a
|
|
36
|
+
// top-level property (i.e. one that targets all functions). We consider
|
|
37
|
+
// that to be the case if the value is an object where all keys are also
|
|
38
|
+
// config properties. If not, it's simply a function with the same name
|
|
39
|
+
// as one of the config properties.
|
|
40
|
+
const normalizeFunctionsProp = (functions, [propName, propValue]) =>
|
|
41
|
+
isConfigProperty(propName) && !isConfigLeaf(propValue)
|
|
42
|
+
? { ...functions, [WILDCARD_ALL]: { [propName]: propValue, ...functions[WILDCARD_ALL] } }
|
|
43
|
+
: { ...functions, [propName]: propValue }
|
|
44
|
+
|
|
45
|
+
const isConfigLeaf = (functionConfig) =>
|
|
46
|
+
isPlainObj(functionConfig) && Object.keys(functionConfig).every(isConfigProperty)
|
|
47
|
+
|
|
48
|
+
const isConfigProperty = (propName) => FUNCTION_CONFIG_PROPERTIES.has(propName)
|
|
49
|
+
|
|
50
|
+
export const FUNCTION_CONFIG_PROPERTIES = new Set([
|
|
51
|
+
'directory',
|
|
52
|
+
'external_node_modules',
|
|
53
|
+
'ignored_node_modules',
|
|
54
|
+
'included_files',
|
|
55
|
+
'node_bundler',
|
|
56
|
+
'schedule',
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
// Takes a functions configuration object and looks for the functions directory
|
|
60
|
+
// definition, returning it under the `directory` key. The rest of the config
|
|
61
|
+
// object is returned under the `functions` key.
|
|
62
|
+
const extractFunctionsDirectory = ({ [WILDCARD_ALL]: { directory, ...wildcardFunctionsConfig }, ...functions }) => ({
|
|
63
|
+
directory,
|
|
64
|
+
functions: { ...functions, [WILDCARD_ALL]: wildcardFunctionsConfig },
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const getFunctionsDirectoryProps = ({ functionsDirectory, v1FunctionsDirectory }) => {
|
|
68
|
+
if (isDefined(functionsDirectory)) {
|
|
69
|
+
return {
|
|
70
|
+
functionsDirectory,
|
|
71
|
+
functionsDirectoryOrigin: 'config',
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isDefined(v1FunctionsDirectory)) {
|
|
76
|
+
return {
|
|
77
|
+
functionsDirectory: v1FunctionsDirectory,
|
|
78
|
+
functionsDirectoryOrigin: 'config-v1',
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {}
|
|
83
|
+
}
|
package/lib/headers.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { resolve } from 'path'
|
|
2
|
+
|
|
3
|
+
import { parseAllHeaders } from 'netlify-headers-parser'
|
|
4
|
+
|
|
5
|
+
import { warnHeadersParsing, warnHeadersCaseSensitivity } from './log/messages.js'
|
|
6
|
+
|
|
7
|
+
// Retrieve path to `_headers` file (even if it does not exist yet)
|
|
8
|
+
export const getHeadersPath = function ({ build: { publish } }) {
|
|
9
|
+
return resolve(publish, HEADERS_FILENAME)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const HEADERS_FILENAME = '_headers'
|
|
13
|
+
|
|
14
|
+
// Add `config.headers`
|
|
15
|
+
export const addHeaders = async function ({
|
|
16
|
+
config: { headers: configHeaders, ...config },
|
|
17
|
+
headersPath,
|
|
18
|
+
logs,
|
|
19
|
+
featureFlags,
|
|
20
|
+
}) {
|
|
21
|
+
const { headers, errors } = await parseAllHeaders({
|
|
22
|
+
headersFiles: [headersPath],
|
|
23
|
+
configHeaders,
|
|
24
|
+
minimal: true,
|
|
25
|
+
featureFlags,
|
|
26
|
+
})
|
|
27
|
+
warnHeadersParsing(logs, errors)
|
|
28
|
+
warnHeadersCaseSensitivity(logs, headers)
|
|
29
|
+
return { ...config, headers }
|
|
30
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { logInlineConfig } from './log/main.js'
|
|
2
|
+
import { applyMutations } from './mutations/apply.js'
|
|
3
|
+
|
|
4
|
+
// Retrieve the `--inlineConfig` CLI flag
|
|
5
|
+
export const getInlineConfig = function ({ inlineConfig, configMutations, logs, debug }) {
|
|
6
|
+
const mutatedInlineConfig = applyMutations(inlineConfig, configMutations)
|
|
7
|
+
logInlineConfig(mutatedInlineConfig, { logs, debug })
|
|
8
|
+
return mutatedInlineConfig
|
|
9
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import filterObj from 'filter-obj'
|
|
2
|
+
|
|
3
|
+
import { simplifyConfig } from '../simplify.js'
|
|
4
|
+
|
|
5
|
+
// Make sure we are not printing secret values. Use an allow list.
|
|
6
|
+
export const cleanupConfig = function ({
|
|
7
|
+
build: {
|
|
8
|
+
base,
|
|
9
|
+
command,
|
|
10
|
+
commandOrigin,
|
|
11
|
+
environment = {},
|
|
12
|
+
edge_functions: edgeFunctions,
|
|
13
|
+
ignore,
|
|
14
|
+
processing,
|
|
15
|
+
publish,
|
|
16
|
+
publishOrigin,
|
|
17
|
+
} = {},
|
|
18
|
+
headers,
|
|
19
|
+
headersOrigin,
|
|
20
|
+
plugins = [],
|
|
21
|
+
redirects,
|
|
22
|
+
redirectsOrigin,
|
|
23
|
+
baseRelDir,
|
|
24
|
+
functions,
|
|
25
|
+
functionsDirectory,
|
|
26
|
+
}) {
|
|
27
|
+
const environmentA = cleanupEnvironment(environment)
|
|
28
|
+
const build = {
|
|
29
|
+
base,
|
|
30
|
+
command,
|
|
31
|
+
commandOrigin,
|
|
32
|
+
environment: environmentA,
|
|
33
|
+
edge_functions: edgeFunctions,
|
|
34
|
+
ignore,
|
|
35
|
+
processing,
|
|
36
|
+
publish,
|
|
37
|
+
publishOrigin,
|
|
38
|
+
}
|
|
39
|
+
const pluginsA = plugins.map(cleanupPlugin)
|
|
40
|
+
const netlifyConfig = simplifyConfig({
|
|
41
|
+
build,
|
|
42
|
+
plugins: pluginsA,
|
|
43
|
+
headers,
|
|
44
|
+
headersOrigin,
|
|
45
|
+
redirects,
|
|
46
|
+
redirectsOrigin,
|
|
47
|
+
baseRelDir,
|
|
48
|
+
functions,
|
|
49
|
+
functionsDirectory,
|
|
50
|
+
})
|
|
51
|
+
const netlifyConfigA = truncateArray(netlifyConfig, 'headers')
|
|
52
|
+
const netlifyConfigB = truncateArray(netlifyConfigA, 'redirects')
|
|
53
|
+
return netlifyConfigB
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const cleanupEnvironment = function (environment) {
|
|
57
|
+
return Object.keys(environment).filter((key) => !BUILDBOT_ENVIRONMENT.has(key))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Added by the buildbot. We only want to print environment variables specified
|
|
61
|
+
// by the user.
|
|
62
|
+
const BUILDBOT_ENVIRONMENT = new Set([
|
|
63
|
+
'BRANCH',
|
|
64
|
+
'CONTEXT',
|
|
65
|
+
'DEPLOY_PRIME_URL',
|
|
66
|
+
'DEPLOY_URL',
|
|
67
|
+
'GO_VERSION',
|
|
68
|
+
'NETLIFY_IMAGES_CDN_DOMAIN',
|
|
69
|
+
'SITE_ID',
|
|
70
|
+
'SITE_NAME',
|
|
71
|
+
'URL',
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
const cleanupPlugin = function ({ package: packageName, origin, inputs = {} }) {
|
|
75
|
+
const inputsA = filterObj(inputs, isPublicInput)
|
|
76
|
+
return { package: packageName, origin, inputs: inputsA }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const isPublicInput = function (key, input) {
|
|
80
|
+
return typeof input === 'boolean'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// `headers` and `redirects` can be very long, which can take several minutes
|
|
84
|
+
// to print in the build logs. We truncate them before logging.
|
|
85
|
+
const truncateArray = function (netlifyConfig, propName) {
|
|
86
|
+
const array = netlifyConfig[propName]
|
|
87
|
+
return Array.isArray(array) && array.length > MAX_ARRAY_LENGTH
|
|
88
|
+
? { ...netlifyConfig, [propName]: array.slice(0, MAX_ARRAY_LENGTH) }
|
|
89
|
+
: netlifyConfig
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const MAX_ARRAY_LENGTH = 100
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import figures from 'figures'
|
|
2
|
+
|
|
3
|
+
import { serializeObject } from './serialize.js'
|
|
4
|
+
import { THEME } from './theme.js'
|
|
5
|
+
|
|
6
|
+
// When the `buffer` option is true, we return logs instead of printing them
|
|
7
|
+
// on the console. The logs are accumulated in a `logs` array variable.
|
|
8
|
+
export const getBufferLogs = function ({ buffer }) {
|
|
9
|
+
if (!buffer) {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return { stdout: [], stderr: [] }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// This should be used instead of `console.log()`
|
|
17
|
+
// Printed on stderr because stdout is reserved for the JSON output
|
|
18
|
+
export const log = function (logs, string, { color } = {}) {
|
|
19
|
+
const stringA = String(string).replace(EMPTY_LINES_REGEXP, EMPTY_LINE)
|
|
20
|
+
const stringB = color === undefined ? stringA : color(stringA)
|
|
21
|
+
|
|
22
|
+
if (logs !== undefined) {
|
|
23
|
+
// `logs` is a stateful variable
|
|
24
|
+
// eslint-disable-next-line fp/no-mutating-methods
|
|
25
|
+
logs.stderr.push(stringB)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.warn(stringB)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// We need to add a zero width space character in empty lines. Otherwise the
|
|
33
|
+
// buildbot removes those due to a bug: https://github.com/netlify/buildbot/issues/595
|
|
34
|
+
const EMPTY_LINES_REGEXP = /^\s*$/gm
|
|
35
|
+
const EMPTY_LINE = '\u{200B}'
|
|
36
|
+
|
|
37
|
+
export const logWarning = function (logs, string, opts) {
|
|
38
|
+
log(logs, string, { color: THEME.warningLine, ...opts })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const logObject = function (logs, object, opts) {
|
|
42
|
+
log(logs, serializeObject(object), opts)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const logSubHeader = function (logs, string, opts) {
|
|
46
|
+
log(logs, `\n${figures.pointer} ${string}`, { color: THEME.subHeader, ...opts })
|
|
47
|
+
}
|