@netlify/config 20.6.4 → 20.8.0-rc.0
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/site_info.js +16 -23
- package/lib/base.js +22 -14
- package/lib/bin/flags.js +4 -0
- package/lib/build_dir.js +13 -9
- package/lib/files.js +57 -33
- package/lib/main.js +49 -28
- package/lib/merge_normalize.js +11 -7
- package/lib/normalize.js +13 -9
- package/lib/path.js +8 -8
- package/lib/redirects.js +4 -3
- package/lib/types/api.js +1 -0
- package/lib/types/options.js +1 -0
- package/lib/utils/remove_falsy.js +7 -11
- package/package.json +3 -4
package/lib/api/site_info.js
CHANGED
|
@@ -2,28 +2,26 @@ import fetch from 'node-fetch';
|
|
|
2
2
|
import { getEnvelope } from '../env/envelope.js';
|
|
3
3
|
import { throwUserError } from '../error.js';
|
|
4
4
|
import { ERROR_CALL_TO_ACTION } from '../log/messages.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Retrieve Netlify Site information, if available.
|
|
7
|
+
* Used to retrieve local build environment variables and UI build settings.
|
|
8
|
+
* This is not used in production builds since the buildbot passes this
|
|
9
|
+
* information instead.
|
|
10
|
+
* Requires knowing the `siteId` and having the access `token`.
|
|
11
|
+
* Silently ignore API errors. For example the network connection might be down,
|
|
12
|
+
* but local builds should still work regardless.
|
|
13
|
+
*/
|
|
14
|
+
export const getSiteInfo = async function ({ api, siteId, mode, siteFeatureFlagPrefix, offline = false, featureFlags = {}, testOpts = {}, }) {
|
|
13
15
|
const { env: testEnv = false } = testOpts;
|
|
14
16
|
const fetchIntegrations = featureFlags.buildbot_fetch_integrations;
|
|
15
17
|
if (api === undefined || mode === 'buildbot' || testEnv) {
|
|
16
18
|
const siteInfo = siteId === undefined ? {} : { id: siteId };
|
|
17
|
-
|
|
18
|
-
if (fetchIntegrations && api !== undefined && !testEnv) {
|
|
19
|
-
// we still want to fetch integrations within buildbot
|
|
20
|
-
integrations = await getIntegrations({ api, ownerType: 'site', ownerId: siteId, testOpts });
|
|
21
|
-
}
|
|
19
|
+
const integrations = fetchIntegrations && mode === 'buildbot' && !offline ? await getIntegrations({ siteId, testOpts }) : [];
|
|
22
20
|
return { siteInfo, accounts: [], addons: [], integrations };
|
|
23
21
|
}
|
|
24
22
|
const promises = [getSite(api, siteId, siteFeatureFlagPrefix), getAccounts(api), getAddons(api, siteId)];
|
|
25
23
|
if (fetchIntegrations) {
|
|
26
|
-
promises.push(getIntegrations({
|
|
24
|
+
promises.push(getIntegrations({ siteId, testOpts }));
|
|
27
25
|
}
|
|
28
26
|
const [siteInfo, accounts, addons, integrations = []] = await Promise.all(promises);
|
|
29
27
|
if (siteInfo.use_envelope) {
|
|
@@ -65,19 +63,14 @@ const getAddons = async function (api, siteId) {
|
|
|
65
63
|
throwUserError(`Failed retrieving addons for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`);
|
|
66
64
|
}
|
|
67
65
|
};
|
|
68
|
-
const getIntegrations = async function ({
|
|
69
|
-
if (
|
|
66
|
+
const getIntegrations = async function ({ siteId, testOpts }) {
|
|
67
|
+
if (!siteId) {
|
|
70
68
|
return [];
|
|
71
69
|
}
|
|
72
70
|
const { host } = testOpts;
|
|
73
|
-
const baseUrl = host ? `http://${host}` : `https://api.netlifysdk.com
|
|
71
|
+
const baseUrl = new URL(host ? `http://${host}` : `https://api.netlifysdk.com`);
|
|
74
72
|
try {
|
|
75
|
-
const
|
|
76
|
-
const response = await fetch(`${baseUrl}/${ownerType}/${ownerId}/integrations`, {
|
|
77
|
-
headers: {
|
|
78
|
-
Authorization: `Bearer ${token}`,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
73
|
+
const response = await fetch(`${baseUrl}site/${siteId}/integrations/safe`);
|
|
81
74
|
const integrations = await response.json();
|
|
82
75
|
return Array.isArray(integrations) ? integrations : [];
|
|
83
76
|
}
|
package/lib/base.js
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import { resolvePath } from './files.js';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Retrieve the first `base` directory used to load the first config file.
|
|
4
|
+
*/
|
|
5
|
+
export const getInitialBase = function ({ repositoryRoot,
|
|
6
|
+
// @ts-expect-error TODO: enhance later
|
|
7
|
+
defaultConfig: { build: { base: defaultBase } = {} }, inlineConfig: { build: { base: initialBase = defaultBase } = {} }, }) {
|
|
4
8
|
return resolveBase(repositoryRoot, initialBase);
|
|
5
9
|
};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Two config files can be used:
|
|
12
|
+
* - The first one, using the `config` property or doing a default lookup
|
|
13
|
+
* of `netlify.toml`
|
|
14
|
+
* - If the first one has a `base` property pointing to a directory with
|
|
15
|
+
* another `netlify.toml`, that second config file is used instead.
|
|
16
|
+
* This retrieves the final `base` directory used:
|
|
17
|
+
* - To load the second config file
|
|
18
|
+
* - As the `buildDir`
|
|
19
|
+
* - To resolve file paths
|
|
20
|
+
* If the second file has a `base` property, it is ignored, i.e. it is not
|
|
21
|
+
* recursive.
|
|
22
|
+
*/
|
|
17
23
|
export const getBase = function (base, repositoryRoot, config) {
|
|
18
24
|
return base === undefined ? resolveBase(repositoryRoot, config.build.base) : base;
|
|
19
25
|
};
|
|
20
26
|
const resolveBase = function (repositoryRoot, base) {
|
|
21
27
|
return resolvePath(repositoryRoot, repositoryRoot, base, 'build.base');
|
|
22
28
|
};
|
|
23
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Also `config.build.base`.
|
|
31
|
+
*/
|
|
24
32
|
export const addBase = function (config, base) {
|
|
25
33
|
return { ...config, build: { ...config.build, base } };
|
|
26
34
|
};
|
package/lib/bin/flags.js
CHANGED
|
@@ -72,6 +72,10 @@ Default: empty array.`,
|
|
|
72
72
|
describe: `Current directory. Used to retrieve the configuration file.
|
|
73
73
|
Default: current directory`,
|
|
74
74
|
},
|
|
75
|
+
packagePath: {
|
|
76
|
+
string: true,
|
|
77
|
+
describe: `A relative path from the repository root to the package. Used inside monorepos to specify a package`,
|
|
78
|
+
},
|
|
75
79
|
repositoryRoot: {
|
|
76
80
|
string: true,
|
|
77
81
|
describe: `Git repository root directory. Used to retrieve the configuration file.
|
package/lib/build_dir.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { isDirectory } from 'path-type';
|
|
2
2
|
import { throwUserError } from './error.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Retrieve the build directory used to resolve most paths.
|
|
5
|
+
* This is (in priority order):
|
|
6
|
+
* - `build.base`
|
|
7
|
+
* - `--repositoryRoot`
|
|
8
|
+
* - the current directory (default value of `--repositoryRoot`)
|
|
9
|
+
*/
|
|
8
10
|
export const getBuildDir = async function (repositoryRoot, base) {
|
|
9
11
|
const buildDir = base === undefined ? repositoryRoot : base;
|
|
10
12
|
await checkBuildDir(buildDir, repositoryRoot);
|
|
11
13
|
return buildDir;
|
|
12
14
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
*/
|
|
17
21
|
const checkBuildDir = async function (buildDir, repositoryRoot) {
|
|
18
22
|
if (buildDir === repositoryRoot || (await isDirectory(buildDir))) {
|
|
19
23
|
return;
|
package/lib/files.js
CHANGED
|
@@ -1,63 +1,87 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
|
-
import { resolve, relative, parse } from 'path';
|
|
2
|
+
import { resolve, relative, parse, join } from 'path';
|
|
3
3
|
import { getProperty, setProperty, deleteProperty } from 'dot-prop';
|
|
4
4
|
import { throwUserError } from './error.js';
|
|
5
5
|
import { mergeConfigs } from './merge.js';
|
|
6
6
|
import { isTruthy } from './utils/remove_falsy.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// (if `baseRelDir` is `true`).
|
|
7
|
+
/**
|
|
8
|
+
* We allow paths in configuration file to start with /
|
|
9
|
+
* In that case, those are actually relative paths not absolute.
|
|
10
|
+
*/
|
|
11
|
+
const LEADING_SLASH_REGEXP = /^\/+/;
|
|
12
|
+
/**
|
|
13
|
+
* All file paths in the configuration file are relative to `buildDir`
|
|
14
|
+
* (if `baseRelDir` is `true`).
|
|
15
|
+
*/
|
|
17
16
|
const FILE_PATH_CONFIG_PROPS = [
|
|
18
17
|
'functionsDirectory',
|
|
19
18
|
'functions.*.deno_import_map',
|
|
20
19
|
'build.publish',
|
|
21
20
|
'build.edge_functions',
|
|
22
21
|
];
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Make configuration paths relative to `buildDir` and converts them to
|
|
24
|
+
* absolute paths
|
|
25
|
+
*/
|
|
26
|
+
export const resolveConfigPaths = function (options) {
|
|
27
|
+
const baseRel = options.baseRelDir ? options.buildDir : options.repositoryRoot;
|
|
28
|
+
const config = resolvePaths({
|
|
29
|
+
config: options.config,
|
|
30
|
+
packagePath: options.packagePath,
|
|
31
|
+
propNames: FILE_PATH_CONFIG_PROPS,
|
|
32
|
+
baseRel,
|
|
33
|
+
repositoryRoot: options.repositoryRoot,
|
|
34
|
+
});
|
|
35
|
+
return addDefaultPaths({
|
|
36
|
+
config,
|
|
37
|
+
repositoryRoot: options.repositoryRoot,
|
|
38
|
+
baseRel,
|
|
39
|
+
packagePath: options.packagePath,
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const resolvePaths = function ({ config, propNames, baseRel, packagePath, repositoryRoot, }) {
|
|
43
|
+
return propNames.reduce((configA, propName) => resolvePathProp(configA, propName, baseRel, repositoryRoot, packagePath), config);
|
|
25
44
|
};
|
|
26
|
-
const resolvePathProp = function (config, propName, baseRel, repositoryRoot) {
|
|
45
|
+
const resolvePathProp = function (config, propName, baseRel, repositoryRoot, packagePath) {
|
|
27
46
|
const path = getProperty(config, propName);
|
|
28
47
|
if (!isTruthy(path)) {
|
|
29
48
|
deleteProperty(config, propName);
|
|
30
49
|
return config;
|
|
31
50
|
}
|
|
32
|
-
return setProperty(config, propName, resolvePath(repositoryRoot, baseRel, path, propName));
|
|
51
|
+
return setProperty(config, propName, resolvePath(repositoryRoot, baseRel, path, propName, packagePath));
|
|
33
52
|
};
|
|
34
|
-
export const resolvePath =
|
|
53
|
+
export const resolvePath = (repositoryRoot, baseRel, originalPath, propName,
|
|
54
|
+
// @ts-expect-error depends on the survey outcome see comment below
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
56
|
+
packagePath) => {
|
|
35
57
|
if (!isTruthy(originalPath)) {
|
|
36
58
|
return;
|
|
37
59
|
}
|
|
38
60
|
const path = originalPath.replace(LEADING_SLASH_REGEXP, '');
|
|
61
|
+
// TODO: Based on survey outcome: https://netlify.slack.com/archives/C05556LEX28/p1691423437855069
|
|
62
|
+
// If we like option B then:
|
|
63
|
+
// const pathA = resolve(baseRel, packagePath || '', path)
|
|
39
64
|
const pathA = resolve(baseRel, path);
|
|
40
65
|
validateInsideRoot(originalPath, pathA, repositoryRoot, propName);
|
|
41
66
|
return pathA;
|
|
42
67
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const validateInsideRoot = function (originalPath, path, repositoryRoot, propName) {
|
|
68
|
+
/**
|
|
69
|
+
* We ensure all file paths are within the repository root directory.
|
|
70
|
+
* However, we allow file paths to be outside the build directory, since this
|
|
71
|
+
* can be convenient in monorepo setups.
|
|
72
|
+
*/
|
|
73
|
+
const validateInsideRoot = (originalPath, path, repositoryRoot, propName) => {
|
|
50
74
|
if (relative(repositoryRoot, path).startsWith('..') || getWindowsDrive(repositoryRoot) !== getWindowsDrive(path)) {
|
|
51
75
|
throwUserError(`Configuration property "${propName}" "${originalPath}" must be inside the repository root directory.`);
|
|
52
76
|
}
|
|
53
77
|
};
|
|
54
|
-
const getWindowsDrive =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const addDefaultPaths =
|
|
60
|
-
const defaultPathsConfigs = DEFAULT_PATHS.map(({ defaultPath, getConfig, propName }) => addDefaultPath({ repositoryRoot, baseRel, defaultPath, getConfig, propName })).filter(Boolean);
|
|
78
|
+
const getWindowsDrive = (path) => parse(path).root;
|
|
79
|
+
/**
|
|
80
|
+
* Some configuration properties have default values that are only set if a
|
|
81
|
+
* specific directory/file exists in the build directory
|
|
82
|
+
*/
|
|
83
|
+
const addDefaultPaths = ({ config, repositoryRoot, baseRel, packagePath, }) => {
|
|
84
|
+
const defaultPathsConfigs = DEFAULT_PATHS.map(({ defaultPath, getConfig, propName }) => addDefaultPath({ repositoryRoot, packagePath, baseRel, defaultPath, getConfig, propName })).filter(Boolean);
|
|
61
85
|
return mergeConfigs([...defaultPathsConfigs, config]);
|
|
62
86
|
};
|
|
63
87
|
const DEFAULT_PATHS = [
|
|
@@ -78,9 +102,9 @@ const DEFAULT_PATHS = [
|
|
|
78
102
|
propName: 'build.edge_functions',
|
|
79
103
|
},
|
|
80
104
|
];
|
|
81
|
-
const addDefaultPath =
|
|
82
|
-
const absolutePath = resolvePath(repositoryRoot, baseRel, defaultPath, propName);
|
|
83
|
-
if (!existsSync(absolutePath)) {
|
|
105
|
+
const addDefaultPath = ({ repositoryRoot, packagePath, baseRel, defaultPath, getConfig, propName, }) => {
|
|
106
|
+
const absolutePath = resolvePath(repositoryRoot, join(baseRel, packagePath || ''), defaultPath, propName);
|
|
107
|
+
if (!absolutePath || !existsSync(absolutePath)) {
|
|
84
108
|
return;
|
|
85
109
|
}
|
|
86
110
|
return getConfig(absolutePath);
|
package/lib/main.js
CHANGED
|
@@ -17,11 +17,13 @@ import { UI_ORIGIN, CONFIG_ORIGIN, INLINE_ORIGIN } from './origin.js';
|
|
|
17
17
|
import { parseConfig } from './parse.js';
|
|
18
18
|
import { getConfigPath } from './path.js';
|
|
19
19
|
import { getRedirectsPath, addRedirects } from './redirects.js';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Load the configuration file.
|
|
22
|
+
* Takes an optional configuration file path as input and return the resolved
|
|
23
|
+
* `config` together with related properties such as the `configPath`.
|
|
24
|
+
*/
|
|
23
25
|
export const resolveConfig = async function (opts) {
|
|
24
|
-
const { cachedConfig, cachedConfigPath, host, scheme, pathPrefix, testOpts, token, offline, siteFeatureFlagPrefix, ...optsA } = addDefaultOpts(opts);
|
|
26
|
+
const { cachedConfig, cachedConfigPath, host, scheme, packagePath, pathPrefix, testOpts, token, offline, siteFeatureFlagPrefix, ...optsA } = addDefaultOpts(opts);
|
|
25
27
|
// `api` is not JSON-serializable, so we cannot cache it inside `cachedConfig`
|
|
26
28
|
const api = getApiClient({ token, offline, host, scheme, pathPrefix, testOpts });
|
|
27
29
|
const parsedCachedConfig = await getCachedConfig({ cachedConfig, cachedConfigPath, token, api });
|
|
@@ -33,9 +35,10 @@ export const resolveConfig = async function (opts) {
|
|
|
33
35
|
api,
|
|
34
36
|
siteId,
|
|
35
37
|
mode,
|
|
36
|
-
|
|
38
|
+
offline,
|
|
37
39
|
siteFeatureFlagPrefix,
|
|
38
40
|
featureFlags,
|
|
41
|
+
testOpts,
|
|
39
42
|
});
|
|
40
43
|
const { defaultConfig: defaultConfigA, baseRelDir: baseRelDirA } = parseDefaultConfig({
|
|
41
44
|
defaultConfig,
|
|
@@ -51,6 +54,7 @@ export const resolveConfig = async function (opts) {
|
|
|
51
54
|
cwd,
|
|
52
55
|
context,
|
|
53
56
|
repositoryRoot,
|
|
57
|
+
packagePath,
|
|
54
58
|
branch,
|
|
55
59
|
defaultConfig: defaultConfigA,
|
|
56
60
|
inlineConfig: inlineConfigA,
|
|
@@ -94,8 +98,10 @@ export const resolveConfig = async function (opts) {
|
|
|
94
98
|
logResult(result, { logs, debug });
|
|
95
99
|
return result;
|
|
96
100
|
};
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Adds a `build.functions` property that mirrors `functionsDirectory`, for
|
|
103
|
+
* backward compatibility.
|
|
104
|
+
*/
|
|
99
105
|
const addLegacyFunctionsDirectory = (config) => {
|
|
100
106
|
if (!config.functionsDirectory) {
|
|
101
107
|
return config;
|
|
@@ -108,10 +114,12 @@ const addLegacyFunctionsDirectory = (config) => {
|
|
|
108
114
|
},
|
|
109
115
|
};
|
|
110
116
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Try to load the configuration file in two passes.
|
|
119
|
+
* The first pass uses the `defaultConfig`'s `build.base` (if defined).
|
|
120
|
+
* The second pass uses the `build.base` from the first pass (if defined).
|
|
121
|
+
*/
|
|
122
|
+
const loadConfig = async function ({ configOpt, cwd, context, repositoryRoot, packagePath, branch, defaultConfig, inlineConfig, baseRelDir, logs, featureFlags, }) {
|
|
115
123
|
const initialBase = getInitialBase({ repositoryRoot, defaultConfig, inlineConfig });
|
|
116
124
|
const { configPath, config, buildDir, base, redirectsPath, headersPath } = await getFullConfig({
|
|
117
125
|
configOpt,
|
|
@@ -122,6 +130,7 @@ const loadConfig = async function ({ configOpt, cwd, context, repositoryRoot, br
|
|
|
122
130
|
defaultConfig,
|
|
123
131
|
inlineConfig,
|
|
124
132
|
baseRelDir,
|
|
133
|
+
packagePath,
|
|
125
134
|
configBase: initialBase,
|
|
126
135
|
logs,
|
|
127
136
|
featureFlags,
|
|
@@ -157,9 +166,17 @@ const loadConfig = async function ({ configOpt, cwd, context, repositoryRoot, br
|
|
|
157
166
|
headersPath: headersPathA,
|
|
158
167
|
};
|
|
159
168
|
};
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Load configuration file and normalize it, merge contexts, etc.
|
|
171
|
+
*/
|
|
172
|
+
const getFullConfig = async function ({ configOpt, cwd, context, repositoryRoot, packagePath, branch, defaultConfig, inlineConfig, baseRelDir, configBase, base, logs, featureFlags, }) {
|
|
173
|
+
const configPath = await getConfigPath({
|
|
174
|
+
configOpt,
|
|
175
|
+
cwd,
|
|
176
|
+
repositoryRoot,
|
|
177
|
+
packagePath,
|
|
178
|
+
configBase,
|
|
179
|
+
});
|
|
163
180
|
try {
|
|
164
181
|
const config = await parseConfig(configPath);
|
|
165
182
|
const configA = mergeAndNormalizeConfig({
|
|
@@ -169,8 +186,9 @@ const getFullConfig = async function ({ configOpt, cwd, context, repositoryRoot,
|
|
|
169
186
|
context,
|
|
170
187
|
branch,
|
|
171
188
|
logs,
|
|
189
|
+
packagePath,
|
|
172
190
|
});
|
|
173
|
-
const { config: configB, buildDir, base: baseA, } = await resolveFiles({ config: configA, repositoryRoot, base, baseRelDir });
|
|
191
|
+
const { config: configB, buildDir, base: baseA, } = await resolveFiles({ packagePath, config: configA, repositoryRoot, base, baseRelDir });
|
|
174
192
|
const headersPath = getHeadersPath(configB);
|
|
175
193
|
const configC = await addHeaders({ config: configB, headersPath, logs, featureFlags });
|
|
176
194
|
const redirectsPath = getRedirectsPath(configC);
|
|
@@ -183,33 +201,36 @@ const getFullConfig = async function ({ configOpt, cwd, context, repositoryRoot,
|
|
|
183
201
|
throw error;
|
|
184
202
|
}
|
|
185
203
|
};
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Merge:
|
|
206
|
+
* - `--defaultConfig`: UI build settings and UI-installed plugins
|
|
207
|
+
* - `inlineConfig`: Netlify CLI flags
|
|
208
|
+
* Then merge context-specific configuration.
|
|
209
|
+
* Before and after those steps, also performs validation and normalization.
|
|
210
|
+
* Those need to be done at different stages depending on whether they should
|
|
211
|
+
* happen before/after the merges mentioned above.
|
|
212
|
+
*/
|
|
213
|
+
const mergeAndNormalizeConfig = function ({ config, defaultConfig, inlineConfig, context, branch, logs, packagePath }) {
|
|
194
214
|
const configA = normalizeConfigAndContext(config, CONFIG_ORIGIN);
|
|
195
215
|
const defaultConfigA = normalizeConfigAndContext(defaultConfig, UI_ORIGIN);
|
|
196
216
|
const inlineConfigA = normalizeConfigAndContext(inlineConfig, INLINE_ORIGIN);
|
|
197
217
|
const configB = mergeConfigs([defaultConfigA, configA]);
|
|
198
218
|
const configC = mergeContext({ config: configB, context, branch, logs });
|
|
199
219
|
const configD = mergeConfigs([configC, inlineConfigA]);
|
|
200
|
-
|
|
201
|
-
return configE;
|
|
220
|
+
return normalizeAfterConfigMerge(configD, packagePath);
|
|
202
221
|
};
|
|
203
222
|
const normalizeConfigAndContext = function (config, origin) {
|
|
204
223
|
const configA = normalizeBeforeConfigMerge(config, origin);
|
|
205
224
|
const configB = normalizeContextProps({ config: configA, origin });
|
|
206
225
|
return configB;
|
|
207
226
|
};
|
|
208
|
-
|
|
209
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Find base directory, build directory and resolve all paths to absolute paths
|
|
229
|
+
*/
|
|
230
|
+
const resolveFiles = async function ({ config, repositoryRoot, base, packagePath, baseRelDir, }) {
|
|
210
231
|
const baseA = getBase(base, repositoryRoot, config);
|
|
211
232
|
const buildDir = await getBuildDir(repositoryRoot, baseA);
|
|
212
|
-
const configA = resolveConfigPaths({ config, repositoryRoot, buildDir, baseRelDir });
|
|
233
|
+
const configA = resolveConfigPaths({ config, packagePath, repositoryRoot, buildDir, baseRelDir });
|
|
213
234
|
const configB = addBase(configA, baseA);
|
|
214
235
|
return { config: configB, buildDir, base: baseA };
|
|
215
236
|
};
|
package/lib/merge_normalize.js
CHANGED
|
@@ -3,10 +3,12 @@ import { normalizeConfig } from './normalize.js';
|
|
|
3
3
|
import { addOrigins } from './origin.js';
|
|
4
4
|
import { validateIdenticalPlugins } from './validate/identical.js';
|
|
5
5
|
import { validatePreCaseNormalize, validatePreMergeConfig, validatePreNormalizeConfig, validatePostNormalizeConfig, } from './validate/main.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Perform validation and normalization logic to apply to all of:
|
|
8
|
+
* - config, defaultConfig, inlineConfig
|
|
9
|
+
* - context-specific configs
|
|
10
|
+
* Therefore, this is performing before merging those together.
|
|
11
|
+
*/
|
|
10
12
|
export const normalizeBeforeConfigMerge = function (config, origin) {
|
|
11
13
|
validatePreCaseNormalize(config);
|
|
12
14
|
const configA = normalizeConfigCase(config);
|
|
@@ -15,10 +17,12 @@ export const normalizeBeforeConfigMerge = function (config, origin) {
|
|
|
15
17
|
validateIdenticalPlugins(configB);
|
|
16
18
|
return configB;
|
|
17
19
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Validation and normalization logic performed after merging
|
|
22
|
+
*/
|
|
23
|
+
export const normalizeAfterConfigMerge = function (config, packagePath) {
|
|
20
24
|
validatePreNormalizeConfig(config);
|
|
21
|
-
const configA = normalizeConfig(config);
|
|
25
|
+
const configA = normalizeConfig(config, packagePath);
|
|
22
26
|
validatePostNormalizeConfig(configA);
|
|
23
27
|
return configA;
|
|
24
28
|
};
|
package/lib/normalize.js
CHANGED
|
@@ -2,31 +2,35 @@ import { normalizeFunctionsProps, WILDCARD_ALL } from './functions_config.js';
|
|
|
2
2
|
import { mergeConfigs } from './merge.js';
|
|
3
3
|
import { DEFAULT_ORIGIN } from './origin.js';
|
|
4
4
|
import { removeFalsy } from './utils/remove_falsy.js';
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Normalize configuration object
|
|
7
|
+
*/
|
|
8
|
+
export const normalizeConfig = function (config, packagePath) {
|
|
7
9
|
const configA = removeEmpty(config);
|
|
8
|
-
const { build, functions, plugins, ...configB } = mergeConfigs([DEFAULT_CONFIG, configA]);
|
|
10
|
+
const { build, functions, plugins, ...configB } = mergeConfigs([DEFAULT_CONFIG(packagePath), configA]);
|
|
9
11
|
const { build: buildA, functions: functionsA, functionsDirectoryProps } = normalizeFunctionsProps(build, functions);
|
|
10
12
|
const pluginsA = plugins.map(normalizePlugin);
|
|
11
13
|
return { ...configB, build: buildA, functions: functionsA, plugins: pluginsA, ...functionsDirectoryProps };
|
|
12
14
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Remove empty strings.
|
|
17
|
+
* This notably ensures that empty strings in the build command are removed.
|
|
18
|
+
* Otherwise, those would be run during builds, making the build fail.
|
|
19
|
+
*/
|
|
16
20
|
const removeEmpty = function ({ build, ...config }) {
|
|
17
21
|
return removeFalsy({ ...config, build: removeFalsy(build) });
|
|
18
22
|
};
|
|
19
|
-
const DEFAULT_CONFIG = {
|
|
23
|
+
const DEFAULT_CONFIG = (packagePath) => ({
|
|
20
24
|
build: {
|
|
21
25
|
environment: {},
|
|
22
|
-
publish: '.',
|
|
26
|
+
publish: packagePath || '.',
|
|
23
27
|
publishOrigin: DEFAULT_ORIGIN,
|
|
24
28
|
processing: { css: {}, html: {}, images: {}, js: {} },
|
|
25
29
|
services: {},
|
|
26
30
|
},
|
|
27
31
|
functions: { [WILDCARD_ALL]: {} },
|
|
28
32
|
plugins: [],
|
|
29
|
-
};
|
|
33
|
+
});
|
|
30
34
|
const normalizePlugin = function ({ inputs = {}, ...plugin }) {
|
|
31
35
|
return removeFalsy({ ...plugin, inputs });
|
|
32
36
|
};
|
package/lib/path.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
|
-
import { resolve } from 'path';
|
|
2
|
+
import { join, resolve } from 'path';
|
|
3
3
|
import { findUp } from 'find-up';
|
|
4
4
|
import pLocate from 'p-locate';
|
|
5
|
+
const FILENAME = 'netlify.toml';
|
|
5
6
|
/**
|
|
6
7
|
* Configuration location can be:
|
|
7
8
|
* - a local path with the --config CLI flag
|
|
@@ -9,10 +10,10 @@ import pLocate from 'p-locate';
|
|
|
9
10
|
* - a `netlify.*` file in the `repositoryRoot`
|
|
10
11
|
* - a `netlify.*` file in the current directory or any parent
|
|
11
12
|
*/
|
|
12
|
-
export const getConfigPath = async function ({ configOpt, cwd, repositoryRoot, configBase }) {
|
|
13
|
+
export const getConfigPath = async function ({ configOpt, cwd, repositoryRoot, configBase, packagePath, }) {
|
|
13
14
|
const configPath = await pLocate([
|
|
14
15
|
searchConfigOpt(cwd, configOpt),
|
|
15
|
-
searchBaseConfigFile(configBase),
|
|
16
|
+
searchBaseConfigFile(repositoryRoot, configBase, packagePath),
|
|
16
17
|
searchConfigFile(repositoryRoot),
|
|
17
18
|
findUp(FILENAME, { cwd }),
|
|
18
19
|
], Boolean);
|
|
@@ -26,13 +27,13 @@ const searchConfigOpt = function (cwd, configOpt) {
|
|
|
26
27
|
return resolve(cwd, configOpt);
|
|
27
28
|
};
|
|
28
29
|
/**
|
|
29
|
-
* Look for `repositoryRoot/{base}/netlify.*`
|
|
30
|
+
* Look for `repositoryRoot/{base}/{packagePath || '}/netlify.*`
|
|
30
31
|
*/
|
|
31
|
-
const searchBaseConfigFile = function (
|
|
32
|
-
if (
|
|
32
|
+
const searchBaseConfigFile = function (repoRoot, base, packagePath) {
|
|
33
|
+
if (base === undefined && packagePath === undefined) {
|
|
33
34
|
return;
|
|
34
35
|
}
|
|
35
|
-
return searchConfigFile(
|
|
36
|
+
return searchConfigFile(join(base ? base : repoRoot, packagePath || ''));
|
|
36
37
|
};
|
|
37
38
|
/**
|
|
38
39
|
* Look for several file extensions for `netlify.*`
|
|
@@ -44,4 +45,3 @@ const searchConfigFile = function (cwd) {
|
|
|
44
45
|
}
|
|
45
46
|
return path;
|
|
46
47
|
};
|
|
47
|
-
const FILENAME = 'netlify.toml';
|
package/lib/redirects.js
CHANGED
|
@@ -6,13 +6,14 @@ export const getRedirectsPath = function ({ build: { publish } }) {
|
|
|
6
6
|
return resolve(publish, REDIRECTS_FILENAME);
|
|
7
7
|
};
|
|
8
8
|
const REDIRECTS_FILENAME = '_redirects';
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Add `config.redirects`
|
|
11
|
+
*/
|
|
12
|
+
export const addRedirects = async function ({ config: { redirects: configRedirects, ...config }, redirectsPath, logs, }) {
|
|
11
13
|
const { redirects, errors } = await parseAllRedirects({
|
|
12
14
|
redirectsFiles: [redirectsPath],
|
|
13
15
|
configRedirects,
|
|
14
16
|
minimal: true,
|
|
15
|
-
featureFlags,
|
|
16
17
|
});
|
|
17
18
|
warnRedirectsParsing(logs, errors);
|
|
18
19
|
return { ...config, redirects };
|
package/lib/types/api.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { includeKeys } from 'filter-obj';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Remove falsy values from object
|
|
4
|
+
*/
|
|
3
5
|
export const removeFalsy = function (obj) {
|
|
4
|
-
return includeKeys(obj, (
|
|
5
|
-
};
|
|
6
|
-
export const removeUndefined = function (obj) {
|
|
7
|
-
return includeKeys(obj, (key, value) => isDefined(value));
|
|
8
|
-
};
|
|
9
|
-
export const isTruthy = function (value) {
|
|
10
|
-
return isDefined(value) && (typeof value !== 'string' || value.trim() !== '');
|
|
11
|
-
};
|
|
12
|
-
export const isDefined = function (value) {
|
|
13
|
-
return value !== undefined && value !== null;
|
|
6
|
+
return includeKeys(obj, (_key, value) => isTruthy(value));
|
|
14
7
|
};
|
|
8
|
+
export const removeUndefined = (obj) => includeKeys(obj, (_key, value) => isDefined(value));
|
|
9
|
+
export const isTruthy = (value) => isDefined(value) && (typeof value !== 'string' || value.trim() !== '');
|
|
10
|
+
export const isDefined = (value) => value !== undefined && value !== null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/config",
|
|
3
|
-
"version": "20.
|
|
3
|
+
"version": "20.8.0-rc.0",
|
|
4
4
|
"description": "Netlify config module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./lib/index.js",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"map-obj": "^5.0.0",
|
|
73
73
|
"netlify": "^13.1.10",
|
|
74
74
|
"netlify-headers-parser": "^7.1.2",
|
|
75
|
-
"netlify-redirect-parser": "^14.1.3",
|
|
76
75
|
"node-fetch": "^3.3.1",
|
|
76
|
+
"netlify-redirect-parser": "^14.1.3",
|
|
77
77
|
"omit.js": "^2.0.2",
|
|
78
78
|
"p-locate": "^6.0.0",
|
|
79
79
|
"path-type": "^5.0.0",
|
|
@@ -93,6 +93,5 @@
|
|
|
93
93
|
},
|
|
94
94
|
"engines": {
|
|
95
95
|
"node": "^14.16.0 || >=16.0.0"
|
|
96
|
-
}
|
|
97
|
-
"gitHead": "5e684d0509bb094d0e11ff89559abf7eb2d806fc"
|
|
96
|
+
}
|
|
98
97
|
}
|