@netlify/config 19.0.0-rc → 19.0.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/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 +21 -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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { warnContextPluginConfig, throwContextPluginsConfig } from '../log/messages.js';
|
|
2
|
+
import { UI_ORIGIN } from '../origin.js';
|
|
3
|
+
// The only reason to specify both `[[plugins]]` and
|
|
4
|
+
// `[[contexts.{context}.plugins]]` is to configure context-specific plugin
|
|
5
|
+
// inputs.
|
|
6
|
+
// The second cannot be used to enable a plugin for a specific context, because
|
|
7
|
+
// it would be overridden by `[[plugins]]`.
|
|
8
|
+
// We prevent against that mistake by printing a warning message when both are
|
|
9
|
+
// used. We only do this when `[[plugins]]` is due to plugin being UI-installed.
|
|
10
|
+
// We also fail the build when the following additional conditions apply:
|
|
11
|
+
// - `package` is the only property in `[[contexts.{context}.plugins]]`, i.e.
|
|
12
|
+
// there are no context-specific `inputs`
|
|
13
|
+
// - The current build is not in `context`
|
|
14
|
+
export const validateContextsPluginsConfig = function ({ contextProps, plugins, contexts, logs }) {
|
|
15
|
+
Object.entries(contextProps).forEach(([givenContext, givenContextProps]) => {
|
|
16
|
+
validateContextPluginsConfig({ givenContextProps, plugins, givenContext, contexts, logs });
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
const validateContextPluginsConfig = function ({ givenContextProps: { plugins: contextPlugins = [] }, plugins, givenContext, contexts, logs, }) {
|
|
20
|
+
contextPlugins.forEach((pluginConfig) => {
|
|
21
|
+
validateContextPluginConfig({ pluginConfig, plugins, givenContext, contexts, logs });
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
const validateContextPluginConfig = function ({ pluginConfig: { package: packageName, inputs = {} }, plugins = [], givenContext, contexts, logs, }) {
|
|
25
|
+
if (!isContextFreePlugin(plugins, packageName)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (isPluginConfigError(contexts, givenContext, inputs)) {
|
|
29
|
+
throwContextPluginsConfig(packageName, givenContext);
|
|
30
|
+
}
|
|
31
|
+
warnContextPluginConfig(logs, packageName, givenContext);
|
|
32
|
+
};
|
|
33
|
+
const isContextFreePlugin = function (plugins, packageName) {
|
|
34
|
+
return plugins.some((plugin) => plugin.package === packageName && plugin.origin === UI_ORIGIN);
|
|
35
|
+
};
|
|
36
|
+
const isPluginConfigError = function (contexts, givenContext, inputs) {
|
|
37
|
+
return contexts.every((context) => context !== givenContext) && Object.keys(inputs).length === 0;
|
|
38
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import indentString from 'indent-string';
|
|
2
|
+
import { THEME } from '../log/theme.js';
|
|
3
|
+
import { serializeToml } from '../utils/toml.js';
|
|
4
|
+
// Print invalid value and example netlify.toml
|
|
5
|
+
export const getExample = function ({ value, key, prevPath, example, formatInvalid }) {
|
|
6
|
+
const exampleA = typeof example === 'function' ? example(value, key, prevPath) : example;
|
|
7
|
+
return `
|
|
8
|
+
${THEME.errorSubHeader('Invalid syntax')}
|
|
9
|
+
|
|
10
|
+
${indentString(getInvalidValue(value, prevPath, formatInvalid), 2)}
|
|
11
|
+
|
|
12
|
+
${THEME.subHeader('Valid syntax')}
|
|
13
|
+
|
|
14
|
+
${indentString(serializeToml(exampleA), 2)}`;
|
|
15
|
+
};
|
|
16
|
+
const getInvalidValue = function (value, prevPath, formatInvalid) {
|
|
17
|
+
// slice() is temporary, so it does not mutate
|
|
18
|
+
const invalidValue = [...prevPath].reverse().reduce(setInvalidValuePart, value);
|
|
19
|
+
// If `formatInvalid` is supplied, we use it to format the invalid value
|
|
20
|
+
// before serializing it to TOML and printing it.
|
|
21
|
+
const formattedInvalidValue = typeof formatInvalid === 'function' ? formatInvalid(invalidValue) : invalidValue;
|
|
22
|
+
const invalidValueA = serializeToml(formattedInvalidValue);
|
|
23
|
+
return invalidValueA;
|
|
24
|
+
};
|
|
25
|
+
const setInvalidValuePart = function (value, part) {
|
|
26
|
+
if (Number.isInteger(part)) {
|
|
27
|
+
return [value];
|
|
28
|
+
}
|
|
29
|
+
return value === undefined ? {} : { [part]: value };
|
|
30
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import isPlainObj from 'is-plain-obj';
|
|
2
|
+
export const isArrayOfObjects = function (value) {
|
|
3
|
+
return Array.isArray(value) && value.every(isPlainObj);
|
|
4
|
+
};
|
|
5
|
+
export const isArrayOfStrings = function (value) {
|
|
6
|
+
return Array.isArray(value) && value.every(isString);
|
|
7
|
+
};
|
|
8
|
+
export const isString = function (value) {
|
|
9
|
+
return typeof value === 'string';
|
|
10
|
+
};
|
|
11
|
+
// Check an object valid properties, including legacy ones
|
|
12
|
+
export const validProperties = function (propNames, legacyPropNames) {
|
|
13
|
+
return {
|
|
14
|
+
check: (value) => checkValidProperty(value, [...propNames, ...legacyPropNames]),
|
|
15
|
+
message: `has unknown properties. Valid properties are:
|
|
16
|
+
${propNames.map((propName) => ` - ${propName}`).join('\n')}`,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
const checkValidProperty = function (value, propNames) {
|
|
20
|
+
return Object.keys(value).every((propName) => propNames.includes(propName));
|
|
21
|
+
};
|
|
22
|
+
export const functionsDirectoryCheck = {
|
|
23
|
+
formatInvalid: ({ functionsDirectory } = {}) => ({ functions: { directory: functionsDirectory } }),
|
|
24
|
+
propertyName: 'functions.directory',
|
|
25
|
+
};
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import { throwUserError } from '../error.js'
|
|
2
|
-
|
|
1
|
+
import { throwUserError } from '../error.js';
|
|
3
2
|
// Validate that plugin are configured only once per origin
|
|
4
3
|
// (`netlify.toml` or UI).
|
|
5
4
|
// Exception: context-specific configuration since we allow context-specific
|
|
6
5
|
// overrides. This does not validate them since contexts have not been merged
|
|
7
6
|
// yet.
|
|
8
7
|
export const validateIdenticalPlugins = function ({ plugins = [] }) {
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
8
|
+
plugins.filter(hasIdenticalPlugin).forEach(throwIndenticalPlugin);
|
|
9
|
+
};
|
|
12
10
|
const hasIdenticalPlugin = function ({ package: packageName, origin }, index, plugins) {
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
11
|
+
return plugins.slice(index + 1).some((pluginA) => packageName === pluginA.package && origin === pluginA.origin);
|
|
12
|
+
};
|
|
16
13
|
const throwIndenticalPlugin = function ({ package: packageName, origin }) {
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const ORIGINS = { config: 'netlify.toml', ui: 'the app' }
|
|
14
|
+
throwUserError(`Plugin "${packageName}" must not be specified twice in ${ORIGINS[origin]}`);
|
|
15
|
+
};
|
|
16
|
+
const ORIGINS = { config: 'netlify.toml', ui: 'the app' };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { throwUserError } from '../error.js';
|
|
2
|
+
import { THEME } from '../log/theme.js';
|
|
3
|
+
import { getExample } from './example.js';
|
|
4
|
+
import { PRE_CASE_NORMALIZE_VALIDATIONS, PRE_MERGE_VALIDATIONS, PRE_CONTEXT_VALIDATIONS, PRE_NORMALIZE_VALIDATIONS, POST_NORMALIZE_VALIDATIONS, } from './validations.js';
|
|
5
|
+
// Validate the configuration file, before case normalization.
|
|
6
|
+
export const validatePreCaseNormalize = function (config) {
|
|
7
|
+
validateConfig(config, PRE_CASE_NORMALIZE_VALIDATIONS);
|
|
8
|
+
};
|
|
9
|
+
// Validate the configuration file, before `defaultConfig` merge.
|
|
10
|
+
export const validatePreMergeConfig = function (config) {
|
|
11
|
+
validateConfig(config, PRE_MERGE_VALIDATIONS);
|
|
12
|
+
};
|
|
13
|
+
// Validate the configuration file, before context merge.
|
|
14
|
+
export const validatePreContextConfig = function (config) {
|
|
15
|
+
validateConfig(config, PRE_CONTEXT_VALIDATIONS);
|
|
16
|
+
};
|
|
17
|
+
// Validate the configuration file, before normalization.
|
|
18
|
+
export const validatePreNormalizeConfig = function (config) {
|
|
19
|
+
validateConfig(config, PRE_NORMALIZE_VALIDATIONS);
|
|
20
|
+
};
|
|
21
|
+
// Validate the configuration file, after normalization.
|
|
22
|
+
export const validatePostNormalizeConfig = function (config) {
|
|
23
|
+
validateConfig(config, POST_NORMALIZE_VALIDATIONS);
|
|
24
|
+
};
|
|
25
|
+
const validateConfig = function (config, validations) {
|
|
26
|
+
try {
|
|
27
|
+
validations.forEach(({ property, ...validation }) => {
|
|
28
|
+
validateProperty(config, { ...validation, nextPath: property.split('.') });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throwUserError(error);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
// Validate a single property in the configuration file.
|
|
36
|
+
const validateProperty = function (parent, { nextPath: [propName, nextPropName, ...nextPath], prevPath = [propName], propPath = propName, key = propName, check, message, example, formatInvalid, propertyName, }) {
|
|
37
|
+
const value = parent[propName];
|
|
38
|
+
if (nextPropName !== undefined) {
|
|
39
|
+
return validateChild({
|
|
40
|
+
value,
|
|
41
|
+
nextPropName,
|
|
42
|
+
prevPath,
|
|
43
|
+
nextPath,
|
|
44
|
+
propPath,
|
|
45
|
+
key,
|
|
46
|
+
check,
|
|
47
|
+
message,
|
|
48
|
+
example,
|
|
49
|
+
formatInvalid,
|
|
50
|
+
propertyName,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (value === undefined || (check !== undefined && check(value, key, prevPath))) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
reportError({ prevPath, propPath, message, example, value, key, formatInvalid, propertyName });
|
|
57
|
+
};
|
|
58
|
+
const reportError = function ({ prevPath, propPath, message, example, value, key, formatInvalid, propertyName = propPath, }) {
|
|
59
|
+
throwUserError(`${THEME.highlightWords('Configuration property')} ${propertyName} ${message}
|
|
60
|
+
${getExample({ value, key, prevPath, example, formatInvalid })}`);
|
|
61
|
+
};
|
|
62
|
+
// Recurse over children (each part of the `property` array).
|
|
63
|
+
const validateChild = function ({ value, nextPropName, prevPath, nextPath, propPath, ...rest }) {
|
|
64
|
+
if (value === undefined) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (nextPropName !== '*') {
|
|
68
|
+
return validateProperty(value, {
|
|
69
|
+
...rest,
|
|
70
|
+
prevPath: [...prevPath, nextPropName],
|
|
71
|
+
nextPath: [nextPropName, ...nextPath],
|
|
72
|
+
propPath: `${propPath}.${nextPropName}`,
|
|
73
|
+
key: nextPropName,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return Object.keys(value).forEach((childProp) => {
|
|
77
|
+
validateChildProp({ childProp, value, nextPath, propPath, prevPath, ...rest });
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
// Can use * to recurse over array|object elements.
|
|
81
|
+
const validateChildProp = function ({ childProp, value, nextPath, propPath, prevPath, ...rest }) {
|
|
82
|
+
if (Array.isArray(value)) {
|
|
83
|
+
const key = Number(childProp);
|
|
84
|
+
return validateProperty(value, {
|
|
85
|
+
...rest,
|
|
86
|
+
prevPath: [...prevPath, key],
|
|
87
|
+
nextPath: [key, ...nextPath],
|
|
88
|
+
propPath: `${propPath}[${childProp}]`,
|
|
89
|
+
key,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
validateProperty(value, {
|
|
93
|
+
...rest,
|
|
94
|
+
prevPath: [...prevPath, childProp],
|
|
95
|
+
nextPath: [childProp, ...nextPath],
|
|
96
|
+
propPath: `${propPath}.${childProp}`,
|
|
97
|
+
key: childProp,
|
|
98
|
+
});
|
|
99
|
+
};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import CronParser from 'cron-parser';
|
|
2
|
+
import isPlainObj from 'is-plain-obj';
|
|
3
|
+
import validateNpmPackageName from 'validate-npm-package-name';
|
|
4
|
+
import { bundlers, WILDCARD_ALL as FUNCTIONS_CONFIG_WILDCARD_ALL } from '../functions_config.js';
|
|
5
|
+
import { functionsDirectoryCheck, isArrayOfObjects, isArrayOfStrings, isString, validProperties } from './helpers.js';
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} cron
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
const isValidCronExpression = (cron) => {
|
|
11
|
+
try {
|
|
12
|
+
CronParser.parseExpression(cron);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
// List of validations performed on the configuration file.
|
|
20
|
+
// Validation are performed in order: parent should be before children.
|
|
21
|
+
// Each validation is an object with the following properties:
|
|
22
|
+
// - `property` {string}: dot-delimited path to the property.
|
|
23
|
+
// Can contain `*` providing a previous check validates the parent is an
|
|
24
|
+
// object or an array.
|
|
25
|
+
// - `propertyName` {string}: human-friendly property name; overrides the
|
|
26
|
+
// value of `property` when displaying an error message
|
|
27
|
+
// - `check` {(value, key, prevPath) => boolean}: validation check function
|
|
28
|
+
// - `message` {string}: error message
|
|
29
|
+
// - `example` {string}: example of correct code
|
|
30
|
+
// - `formatInvalid` {(object) => object}: formats the invalid value when
|
|
31
|
+
// displaying an error message
|
|
32
|
+
// We use this instead of JSON schema (or others) to get nicer error messages.
|
|
33
|
+
// Validations done before case normalization
|
|
34
|
+
export const PRE_CASE_NORMALIZE_VALIDATIONS = [
|
|
35
|
+
{
|
|
36
|
+
property: 'build',
|
|
37
|
+
check: isPlainObj,
|
|
38
|
+
message: 'must be a plain object.',
|
|
39
|
+
example: () => ({ build: { command: 'npm run build' } }),
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
// Properties with an `origin` property need to be validated twice:
|
|
43
|
+
// - Before the `origin` property is added
|
|
44
|
+
// - After `context.*` is merged, since they might contain that property
|
|
45
|
+
const ORIGIN_VALIDATIONS = [
|
|
46
|
+
{
|
|
47
|
+
property: 'build.command',
|
|
48
|
+
check: isString,
|
|
49
|
+
message: 'must be a string',
|
|
50
|
+
example: () => ({ build: { command: 'npm run build' } }),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
property: 'plugins',
|
|
54
|
+
check: isArrayOfObjects,
|
|
55
|
+
message: 'must be an array of objects.',
|
|
56
|
+
example: () => ({ plugins: [{ package: 'netlify-plugin-one' }, { package: 'netlify-plugin-two' }] }),
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
// Validations done before `defaultConfig` merge
|
|
60
|
+
export const PRE_MERGE_VALIDATIONS = [...ORIGIN_VALIDATIONS];
|
|
61
|
+
// Validations done before context merge
|
|
62
|
+
export const PRE_CONTEXT_VALIDATIONS = [
|
|
63
|
+
{
|
|
64
|
+
property: 'context',
|
|
65
|
+
check: isPlainObj,
|
|
66
|
+
message: 'must be a plain object.',
|
|
67
|
+
example: () => ({ context: { production: { publish: 'dist' } } }),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
property: 'context.*',
|
|
71
|
+
check: isPlainObj,
|
|
72
|
+
message: 'must be a plain object.',
|
|
73
|
+
example: (contextProps, key) => ({ context: { [key]: { publish: 'dist' } } }),
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
// Validations done before normalization
|
|
77
|
+
export const PRE_NORMALIZE_VALIDATIONS = [
|
|
78
|
+
...ORIGIN_VALIDATIONS,
|
|
79
|
+
{
|
|
80
|
+
property: 'functions',
|
|
81
|
+
check: isPlainObj,
|
|
82
|
+
message: 'must be an object.',
|
|
83
|
+
example: () => ({
|
|
84
|
+
functions: { external_node_modules: ['module-one', 'module-two'] },
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
property: 'functions',
|
|
89
|
+
check: isPlainObj,
|
|
90
|
+
message: 'must be an object.',
|
|
91
|
+
example: () => ({
|
|
92
|
+
functions: { ignored_node_modules: ['module-one', 'module-two'] },
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
property: 'edge_functions',
|
|
97
|
+
check: isArrayOfObjects,
|
|
98
|
+
message: 'must be an array of objects.',
|
|
99
|
+
example: () => ({
|
|
100
|
+
edge_functions: [
|
|
101
|
+
{ path: '/hello', function: 'hello' },
|
|
102
|
+
{ path: '/auth', function: 'auth' },
|
|
103
|
+
],
|
|
104
|
+
}),
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
const EXAMPLE_PORT = 80;
|
|
108
|
+
// Validations done after normalization
|
|
109
|
+
export const POST_NORMALIZE_VALIDATIONS = [
|
|
110
|
+
{
|
|
111
|
+
property: 'plugins.*',
|
|
112
|
+
...validProperties(['package', 'pinned_version', 'inputs'], ['origin']),
|
|
113
|
+
example: { plugins: [{ package: 'netlify-plugin-one', inputs: { port: EXAMPLE_PORT } }] },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
property: 'plugins.*',
|
|
117
|
+
check: (plugin) => plugin.package !== undefined,
|
|
118
|
+
message: '"package" property is required.',
|
|
119
|
+
example: () => ({ plugins: [{ package: 'netlify-plugin-one' }] }),
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
property: 'plugins.*.package',
|
|
123
|
+
check: isString,
|
|
124
|
+
message: 'must be a string.',
|
|
125
|
+
example: () => ({ plugins: [{ package: 'netlify-plugin-one' }] }),
|
|
126
|
+
},
|
|
127
|
+
// We don't allow `package@tag|version` nor `git:...`, `github:...`,
|
|
128
|
+
// `https://...`, etc.
|
|
129
|
+
// We skip this validation for local plugins.
|
|
130
|
+
// We ensure @scope/plugin still work.
|
|
131
|
+
{
|
|
132
|
+
property: 'plugins.*.package',
|
|
133
|
+
check: (packageName) => packageName.startsWith('.') ||
|
|
134
|
+
packageName.startsWith('/') ||
|
|
135
|
+
validateNpmPackageName(packageName).validForOldPackages,
|
|
136
|
+
message: 'must be a npm package name only.',
|
|
137
|
+
example: () => ({ plugins: [{ package: 'netlify-plugin-one' }] }),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
property: 'plugins.*.pinned_version',
|
|
141
|
+
check: isString,
|
|
142
|
+
message: 'must be a string.',
|
|
143
|
+
example: () => ({ plugins: [{ package: 'netlify-plugin-one', pinned_version: '1' }] }),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
property: 'plugins.*.inputs',
|
|
147
|
+
check: isPlainObj,
|
|
148
|
+
message: 'must be a plain object.',
|
|
149
|
+
example: () => ({ plugins: [{ package: 'netlify-plugin-one', inputs: { port: EXAMPLE_PORT } }] }),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
property: 'build.base',
|
|
153
|
+
check: isString,
|
|
154
|
+
message: 'must be a string.',
|
|
155
|
+
example: () => ({ build: { base: 'packages/project' } }),
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
property: 'build.publish',
|
|
159
|
+
check: isString,
|
|
160
|
+
message: 'must be a string.',
|
|
161
|
+
example: () => ({ build: { publish: 'dist' } }),
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
property: 'build.functions',
|
|
165
|
+
check: isString,
|
|
166
|
+
message: 'must be a string.',
|
|
167
|
+
example: () => ({ build: { functions: 'functions' } }),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
property: 'build.edge_functions',
|
|
171
|
+
check: isString,
|
|
172
|
+
message: 'must be a string.',
|
|
173
|
+
example: () => ({ build: { edge_functions: 'edge-functions' } }),
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
property: 'functions.*',
|
|
177
|
+
check: isPlainObj,
|
|
178
|
+
message: 'must be an object.',
|
|
179
|
+
example: (value, key, prevPath) => ({
|
|
180
|
+
functions: { [prevPath[1]]: { external_node_modules: ['module-one', 'module-two'] } },
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
property: 'functions.*.external_node_modules',
|
|
185
|
+
check: isArrayOfStrings,
|
|
186
|
+
message: 'must be an array of strings.',
|
|
187
|
+
example: (value, key, prevPath) => ({
|
|
188
|
+
functions: { [prevPath[1]]: { external_node_modules: ['module-one', 'module-two'] } },
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
property: 'functions.*.ignored_node_modules',
|
|
193
|
+
check: isArrayOfStrings,
|
|
194
|
+
message: 'must be an array of strings.',
|
|
195
|
+
example: (value, key, prevPath) => ({
|
|
196
|
+
functions: { [prevPath[1]]: { ignored_node_modules: ['module-one', 'module-two'] } },
|
|
197
|
+
}),
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
property: 'functions.*.included_files',
|
|
201
|
+
check: isArrayOfStrings,
|
|
202
|
+
message: 'must be an array of strings.',
|
|
203
|
+
example: (value, key, prevPath) => ({
|
|
204
|
+
functions: { [prevPath[1]]: { included_files: ['directory-one/file1', 'directory-two/**/*.jpg'] } },
|
|
205
|
+
}),
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
property: 'functions.*.node_bundler',
|
|
209
|
+
check: (value) => bundlers.includes(value),
|
|
210
|
+
message: `must be one of: ${bundlers.join(', ')}`,
|
|
211
|
+
example: (value, key, prevPath) => ({
|
|
212
|
+
functions: { [prevPath[1]]: { node_bundler: bundlers[0] } },
|
|
213
|
+
}),
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
property: 'functions.*.directory',
|
|
217
|
+
check: (value, key, prevPath) => prevPath[1] === FUNCTIONS_CONFIG_WILDCARD_ALL,
|
|
218
|
+
message: 'must be defined on the main `functions` object.',
|
|
219
|
+
example: () => ({
|
|
220
|
+
functions: { directory: 'my-functions' },
|
|
221
|
+
}),
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
property: 'functions.*.schedule',
|
|
225
|
+
check: isValidCronExpression,
|
|
226
|
+
message: 'must be a valid cron expression (see https://ntl.fyi/cron-syntax).',
|
|
227
|
+
example: (value, key, prevPath) => ({
|
|
228
|
+
functions: { [prevPath[1]]: { schedule: '5 4 * * *' } },
|
|
229
|
+
}),
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
property: 'functionsDirectory',
|
|
233
|
+
check: isString,
|
|
234
|
+
message: 'must be a string.',
|
|
235
|
+
...functionsDirectoryCheck,
|
|
236
|
+
example: () => ({
|
|
237
|
+
functions: { directory: 'my-functions' },
|
|
238
|
+
}),
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
property: 'edge_functions.*',
|
|
242
|
+
...validProperties(['path', 'function'], []),
|
|
243
|
+
example: () => ({ edge_functions: [{ path: '/hello', function: 'hello' }] }),
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
property: 'edge_functions.*',
|
|
247
|
+
check: (edgeFunction) => edgeFunction.path !== undefined,
|
|
248
|
+
message: '"path" property is required.',
|
|
249
|
+
example: () => ({ edge_functions: [{ path: '/hello', function: 'hello' }] }),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
property: 'edge_functions.*',
|
|
253
|
+
check: (edgeFunction) => edgeFunction.function !== undefined,
|
|
254
|
+
message: '"function" property is required.',
|
|
255
|
+
example: () => ({ edge_functions: [{ path: '/hello', function: 'hello' }] }),
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
property: 'edge_functions.*.path',
|
|
259
|
+
check: isString,
|
|
260
|
+
message: 'must be a string.',
|
|
261
|
+
example: () => ({ edge_functions: [{ path: '/hello', function: 'hello' }] }),
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
property: 'edge_functions.*.function',
|
|
265
|
+
check: isString,
|
|
266
|
+
message: 'must be a string.',
|
|
267
|
+
example: () => ({ edge_functions: [{ path: '/hello', function: 'hello' }] }),
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
property: 'edge_functions.*.path',
|
|
271
|
+
check: (pathName) => pathName.startsWith('/'),
|
|
272
|
+
message: 'must be a valid path.',
|
|
273
|
+
example: () => ({ edge_functions: [{ path: '/hello', function: 'hello' }] }),
|
|
274
|
+
},
|
|
275
|
+
];
|
package/package.json
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/config",
|
|
3
|
-
"version": "19.0.0
|
|
3
|
+
"version": "19.0.0",
|
|
4
4
|
"description": "Netlify config module",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"exports": "./
|
|
7
|
-
"main": "./
|
|
6
|
+
"exports": "./lib/main.js",
|
|
7
|
+
"main": "./lib/main.js",
|
|
8
|
+
"types": "./lib/main.d.ts",
|
|
8
9
|
"bin": {
|
|
9
|
-
"netlify-config": "
|
|
10
|
+
"netlify-config": "./bin.js"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
|
-
"
|
|
13
|
+
"bin.js",
|
|
14
|
+
"lib/**/*.js"
|
|
13
15
|
],
|
|
14
16
|
"author": "Netlify Inc.",
|
|
15
17
|
"contributors": [
|
|
16
18
|
"David Wells <hello@davidwells.io> (https://davidwells.io/)"
|
|
17
19
|
],
|
|
18
20
|
"scripts": {
|
|
19
|
-
"
|
|
21
|
+
"prebuild": "rm -rf lib",
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"test": "ava",
|
|
24
|
+
"test:ci": "c8 -r lcovonly -r text -r json ava"
|
|
20
25
|
},
|
|
21
26
|
"keywords": [
|
|
22
27
|
"javascript",
|
|
@@ -64,9 +69,9 @@
|
|
|
64
69
|
"is-plain-obj": "^4.0.0",
|
|
65
70
|
"js-yaml": "^4.0.0",
|
|
66
71
|
"map-obj": "^5.0.0",
|
|
67
|
-
"netlify": "^
|
|
68
|
-
"netlify-headers-parser": "^
|
|
69
|
-
"netlify-redirect-parser": "
|
|
72
|
+
"netlify": "^13.0.0",
|
|
73
|
+
"netlify-headers-parser": "^7.0.0",
|
|
74
|
+
"netlify-redirect-parser": "^14.0.0",
|
|
70
75
|
"omit.js": "^2.0.2",
|
|
71
76
|
"p-locate": "^6.0.0",
|
|
72
77
|
"path-exists": "^5.0.0",
|
|
@@ -77,13 +82,17 @@
|
|
|
77
82
|
"yargs": "^17.3.1"
|
|
78
83
|
},
|
|
79
84
|
"devDependencies": {
|
|
85
|
+
"@types/node": "^14.18.31",
|
|
80
86
|
"ava": "^4.0.0",
|
|
87
|
+
"c8": "^7.12.0",
|
|
81
88
|
"del": "^6.0.0",
|
|
82
89
|
"has-ansi": "^5.0.0",
|
|
83
90
|
"is-ci": "^3.0.0",
|
|
84
|
-
"tmp-promise": "^3.0.2"
|
|
91
|
+
"tmp-promise": "^3.0.2",
|
|
92
|
+
"typescript": "^4.8.4"
|
|
85
93
|
},
|
|
86
94
|
"engines": {
|
|
87
|
-
"node": "^
|
|
88
|
-
}
|
|
95
|
+
"node": "^14.16.0 || >=16.0.0"
|
|
96
|
+
},
|
|
97
|
+
"gitHead": "c4502f1c112e7f33d5e6fa053bfbe7dabdfe0160"
|
|
89
98
|
}
|
|
@@ -1,41 +0,0 @@
|
|
|
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
|
-
}
|
package/src/api/client.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
}
|