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