@netlify/build 28.0.1-beta → 28.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.
Files changed (259) hide show
  1. package/bin.js +5 -0
  2. package/lib/core/bin.js +66 -0
  3. package/lib/core/build.js +355 -0
  4. package/lib/core/config.js +121 -0
  5. package/lib/core/constants.js +116 -0
  6. package/lib/core/dev.js +27 -0
  7. package/lib/core/dry.js +21 -0
  8. package/lib/core/feature_flags.js +19 -0
  9. package/lib/core/flags.js +201 -0
  10. package/lib/core/lingering.js +68 -0
  11. package/lib/core/main.js +105 -0
  12. package/lib/core/missing_side_file.js +17 -0
  13. package/lib/core/normalize_flags.js +59 -0
  14. package/lib/core/severity.js +21 -0
  15. package/lib/core/types.js +8 -0
  16. package/lib/core/user_node_version.js +32 -0
  17. package/lib/env/changes.js +43 -0
  18. package/lib/env/main.js +14 -0
  19. package/lib/env/metadata.js +68 -0
  20. package/lib/error/api.js +37 -0
  21. package/lib/error/build.js +36 -0
  22. package/{src → lib}/error/cancel.js +5 -6
  23. package/lib/error/colors.js +9 -0
  24. package/lib/error/handle.js +46 -0
  25. package/lib/error/info.js +37 -0
  26. package/lib/error/monitor/location.js +16 -0
  27. package/lib/error/monitor/normalize.js +86 -0
  28. package/lib/error/monitor/print.js +20 -0
  29. package/lib/error/monitor/report.js +120 -0
  30. package/lib/error/monitor/start.js +61 -0
  31. package/lib/error/parse/clean_stack.js +70 -0
  32. package/lib/error/parse/location.js +50 -0
  33. package/lib/error/parse/normalize.js +24 -0
  34. package/lib/error/parse/parse.js +67 -0
  35. package/lib/error/parse/plugin.js +55 -0
  36. package/lib/error/parse/properties.js +16 -0
  37. package/lib/error/parse/serialize_log.js +34 -0
  38. package/lib/error/parse/serialize_status.js +18 -0
  39. package/lib/error/parse/stack.js +34 -0
  40. package/lib/error/type.js +171 -0
  41. package/lib/install/functions.js +20 -0
  42. package/lib/install/local.js +45 -0
  43. package/lib/install/main.js +67 -0
  44. package/lib/install/missing.js +54 -0
  45. package/{src → lib}/log/colors.js +15 -22
  46. package/lib/log/description.js +21 -0
  47. package/lib/log/header.js +14 -0
  48. package/lib/log/header_func.js +13 -0
  49. package/lib/log/logger.js +140 -0
  50. package/lib/log/messages/compatibility.js +120 -0
  51. package/lib/log/messages/config.js +91 -0
  52. package/lib/log/messages/core.js +50 -0
  53. package/lib/log/messages/core_steps.js +75 -0
  54. package/lib/log/messages/dry.js +41 -0
  55. package/lib/log/messages/install.js +25 -0
  56. package/lib/log/messages/ipc.js +29 -0
  57. package/lib/log/messages/mutations.js +62 -0
  58. package/{src → lib}/log/messages/plugins.js +18 -32
  59. package/lib/log/messages/status.js +14 -0
  60. package/lib/log/messages/steps.js +18 -0
  61. package/lib/log/old_version.js +32 -0
  62. package/lib/log/serialize.js +10 -0
  63. package/lib/log/stream.js +68 -0
  64. package/lib/log/theme.js +25 -0
  65. package/lib/plugins/child/diff.js +46 -0
  66. package/lib/plugins/child/error.js +26 -0
  67. package/lib/plugins/child/lazy.js +15 -0
  68. package/lib/plugins/child/load.js +22 -0
  69. package/lib/plugins/child/logic.js +57 -0
  70. package/lib/plugins/child/main.js +37 -0
  71. package/lib/plugins/child/run.js +19 -0
  72. package/lib/plugins/child/status.js +63 -0
  73. package/lib/plugins/child/typescript.js +28 -0
  74. package/lib/plugins/child/utils.js +42 -0
  75. package/lib/plugins/child/validate.js +31 -0
  76. package/lib/plugins/compatibility.js +104 -0
  77. package/{src → lib}/plugins/error.js +31 -35
  78. package/{src → lib}/plugins/events.js +7 -12
  79. package/lib/plugins/expected_version.js +81 -0
  80. package/lib/plugins/ipc.js +120 -0
  81. package/lib/plugins/list.js +73 -0
  82. package/lib/plugins/load.js +50 -0
  83. package/lib/plugins/manifest/check.js +85 -0
  84. package/lib/plugins/manifest/load.js +38 -0
  85. package/lib/plugins/manifest/main.js +17 -0
  86. package/lib/plugins/manifest/path.js +24 -0
  87. package/lib/plugins/manifest/validate.js +91 -0
  88. package/lib/plugins/node_version.js +30 -0
  89. package/lib/plugins/options.js +55 -0
  90. package/lib/plugins/pinned_version.js +83 -0
  91. package/lib/plugins/resolve.js +110 -0
  92. package/lib/plugins/spawn.js +54 -0
  93. package/lib/plugins_core/add.js +35 -0
  94. package/lib/plugins_core/build_command.js +50 -0
  95. package/lib/plugins_core/deploy/buildbot_client.js +87 -0
  96. package/lib/plugins_core/deploy/index.js +49 -0
  97. package/{src → lib}/plugins_core/deploy/manifest.yml +0 -0
  98. package/lib/plugins_core/edge_functions/index.js +79 -0
  99. package/lib/plugins_core/edge_functions/lib/error.js +17 -0
  100. package/lib/plugins_core/edge_functions/lib/internal_manifest.js +50 -0
  101. package/lib/plugins_core/edge_functions/validate_manifest/validate_edge_functions_manifest.js +75 -0
  102. package/lib/plugins_core/functions/error.js +123 -0
  103. package/lib/plugins_core/functions/feature_flags.js +6 -0
  104. package/lib/plugins_core/functions/index.js +114 -0
  105. package/lib/plugins_core/functions/utils.js +45 -0
  106. package/lib/plugins_core/functions/zisi.js +39 -0
  107. package/{src → lib}/plugins_core/functions_install/index.js +8 -11
  108. package/{src → lib}/plugins_core/functions_install/manifest.yml +0 -0
  109. package/lib/plugins_core/list.js +20 -0
  110. package/lib/status/add.js +30 -0
  111. package/lib/status/colors.js +18 -0
  112. package/lib/status/load_error.js +10 -0
  113. package/lib/status/report.js +83 -0
  114. package/lib/status/success.js +14 -0
  115. package/lib/steps/core_step.js +57 -0
  116. package/lib/steps/error.js +65 -0
  117. package/lib/steps/get.js +43 -0
  118. package/lib/steps/plugin.js +55 -0
  119. package/lib/steps/return.js +25 -0
  120. package/lib/steps/run_core_steps.js +126 -0
  121. package/lib/steps/run_step.js +188 -0
  122. package/lib/steps/run_steps.js +96 -0
  123. package/lib/steps/update_config.js +66 -0
  124. package/lib/telemetry/main.js +97 -0
  125. package/lib/time/aggregate.js +120 -0
  126. package/lib/time/main.js +37 -0
  127. package/lib/time/measure.js +18 -0
  128. package/lib/time/report.js +47 -0
  129. package/lib/utils/errors.js +13 -0
  130. package/lib/utils/json.js +15 -0
  131. package/{src → lib}/utils/omit.js +3 -4
  132. package/lib/utils/package.js +22 -0
  133. package/lib/utils/remove_falsy.js +8 -0
  134. package/lib/utils/resolve.js +41 -0
  135. package/lib/utils/runtime.js +5 -0
  136. package/lib/utils/semver.js +28 -0
  137. package/package.json +40 -20
  138. package/types/config/netlify_config.d.ts +4 -4
  139. package/types/netlify_plugin_constants.d.ts +8 -8
  140. package/src/core/bin.js +0 -83
  141. package/src/core/config.js +0 -186
  142. package/src/core/constants.js +0 -156
  143. package/src/core/dry.js +0 -39
  144. package/src/core/feature_flags.js +0 -21
  145. package/src/core/flags.js +0 -194
  146. package/src/core/lingering.js +0 -85
  147. package/src/core/main.js +0 -692
  148. package/src/core/missing_side_file.js +0 -29
  149. package/src/core/normalize_flags.js +0 -69
  150. package/src/core/severity.js +0 -22
  151. package/src/core/user_node_version.js +0 -41
  152. package/src/env/changes.js +0 -52
  153. package/src/env/main.js +0 -19
  154. package/src/env/metadata.js +0 -81
  155. package/src/error/api.js +0 -46
  156. package/src/error/build.js +0 -50
  157. package/src/error/colors.js +0 -11
  158. package/src/error/handle.js +0 -57
  159. package/src/error/info.js +0 -46
  160. package/src/error/monitor/location.js +0 -21
  161. package/src/error/monitor/normalize.js +0 -77
  162. package/src/error/monitor/print.js +0 -42
  163. package/src/error/monitor/report.js +0 -133
  164. package/src/error/monitor/start.js +0 -69
  165. package/src/error/parse/clean_stack.js +0 -87
  166. package/src/error/parse/location.js +0 -58
  167. package/src/error/parse/normalize.js +0 -29
  168. package/src/error/parse/parse.js +0 -97
  169. package/src/error/parse/plugin.js +0 -70
  170. package/src/error/parse/properties.js +0 -23
  171. package/src/error/parse/serialize_log.js +0 -42
  172. package/src/error/parse/serialize_status.js +0 -23
  173. package/src/error/parse/stack.js +0 -43
  174. package/src/error/type.js +0 -182
  175. package/src/install/functions.js +0 -28
  176. package/src/install/local.js +0 -62
  177. package/src/install/main.js +0 -81
  178. package/src/install/missing.js +0 -67
  179. package/src/log/description.js +0 -26
  180. package/src/log/header.js +0 -16
  181. package/src/log/header_func.js +0 -17
  182. package/src/log/logger.js +0 -107
  183. package/src/log/messages/compatibility.js +0 -164
  184. package/src/log/messages/config.js +0 -105
  185. package/src/log/messages/core.js +0 -70
  186. package/src/log/messages/core_steps.js +0 -104
  187. package/src/log/messages/dry.js +0 -63
  188. package/src/log/messages/install.js +0 -20
  189. package/src/log/messages/ipc.js +0 -38
  190. package/src/log/messages/mutations.js +0 -82
  191. package/src/log/messages/status.js +0 -16
  192. package/src/log/messages/steps.js +0 -22
  193. package/src/log/old_version.js +0 -41
  194. package/src/log/serialize.js +0 -13
  195. package/src/log/stream.js +0 -85
  196. package/src/log/theme.js +0 -26
  197. package/src/plugins/child/diff.js +0 -55
  198. package/src/plugins/child/error.js +0 -32
  199. package/src/plugins/child/lazy.js +0 -18
  200. package/src/plugins/child/load.js +0 -29
  201. package/src/plugins/child/logic.js +0 -57
  202. package/src/plugins/child/main.js +0 -51
  203. package/src/plugins/child/run.js +0 -28
  204. package/src/plugins/child/status.js +0 -67
  205. package/src/plugins/child/typescript.js +0 -45
  206. package/src/plugins/child/utils.js +0 -56
  207. package/src/plugins/child/validate.js +0 -34
  208. package/src/plugins/compatibility.js +0 -128
  209. package/src/plugins/expected_version.js +0 -119
  210. package/src/plugins/ipc.js +0 -145
  211. package/src/plugins/list.js +0 -86
  212. package/src/plugins/load.js +0 -70
  213. package/src/plugins/manifest/check.js +0 -106
  214. package/src/plugins/manifest/load.js +0 -41
  215. package/src/plugins/manifest/main.js +0 -22
  216. package/src/plugins/manifest/path.js +0 -31
  217. package/src/plugins/manifest/validate.js +0 -108
  218. package/src/plugins/node_version.js +0 -50
  219. package/src/plugins/options.js +0 -88
  220. package/src/plugins/pinned_version.js +0 -131
  221. package/src/plugins/resolve.js +0 -152
  222. package/src/plugins/spawn.js +0 -66
  223. package/src/plugins_core/add.js +0 -49
  224. package/src/plugins_core/build_command.js +0 -75
  225. package/src/plugins_core/deploy/buildbot_client.js +0 -102
  226. package/src/plugins_core/deploy/index.js +0 -73
  227. package/src/plugins_core/edge_functions/index.js +0 -107
  228. package/src/plugins_core/edge_functions/lib/internal_manifest.js +0 -54
  229. package/src/plugins_core/functions/error.js +0 -163
  230. package/src/plugins_core/functions/feature_flags.js +0 -6
  231. package/src/plugins_core/functions/index.js +0 -160
  232. package/src/plugins_core/functions/utils.js +0 -66
  233. package/src/plugins_core/functions/zisi.js +0 -53
  234. package/src/plugins_core/list.js +0 -27
  235. package/src/status/add.js +0 -36
  236. package/src/status/colors.js +0 -23
  237. package/src/status/load_error.js +0 -11
  238. package/src/status/report.js +0 -128
  239. package/src/status/success.js +0 -18
  240. package/src/steps/core_step.js +0 -90
  241. package/src/steps/error.js +0 -102
  242. package/src/steps/get.js +0 -32
  243. package/src/steps/plugin.js +0 -85
  244. package/src/steps/return.js +0 -52
  245. package/src/steps/run_core_steps.js +0 -194
  246. package/src/steps/run_step.js +0 -300
  247. package/src/steps/run_steps.js +0 -179
  248. package/src/steps/update_config.js +0 -93
  249. package/src/telemetry/main.js +0 -136
  250. package/src/time/aggregate.js +0 -146
  251. package/src/time/main.js +0 -48
  252. package/src/time/measure.js +0 -22
  253. package/src/time/report.js +0 -59
  254. package/src/utils/errors.js +0 -12
  255. package/src/utils/json.js +0 -19
  256. package/src/utils/package.js +0 -23
  257. package/src/utils/remove_falsy.js +0 -10
  258. package/src/utils/resolve.js +0 -46
  259. package/src/utils/semver.js +0 -34
@@ -0,0 +1,85 @@
1
+ import { addErrorInfo } from '../../error/info.js';
2
+ import { serializeObject } from '../../log/serialize.js';
3
+ import { THEME } from '../../log/theme.js';
4
+ // Check that plugin inputs match the validation specified in "manifest.yml"
5
+ // Also assign default values
6
+ export const checkInputs = function ({ inputs, manifest: { inputs: rules = [] }, packageName, pluginPackageJson, loadedFrom, origin, }) {
7
+ try {
8
+ const inputsA = addDefaults(inputs, rules);
9
+ checkRequiredInputs({ inputs: inputsA, rules, packageName, pluginPackageJson, loadedFrom, origin });
10
+ checkUnknownInputs({ inputs: inputsA, rules, packageName, pluginPackageJson, loadedFrom, origin });
11
+ return inputsA;
12
+ }
13
+ catch (error) {
14
+ error.message = `${error.message}
15
+
16
+ ${THEME.errorSubHeader('Plugin inputs')}
17
+ ${serializeObject(inputs)}`;
18
+ throw error;
19
+ }
20
+ };
21
+ // Add "inputs[*].default"
22
+ const addDefaults = function (inputs, rules) {
23
+ const defaults = rules.filter(hasDefault).map(getDefault);
24
+ return Object.assign({}, ...defaults, inputs);
25
+ };
26
+ const hasDefault = function (rule) {
27
+ return rule.default !== undefined;
28
+ };
29
+ const getDefault = function ({ name, default: defaultValue }) {
30
+ return { [name]: defaultValue };
31
+ };
32
+ // Check "inputs[*].required"
33
+ const checkRequiredInputs = function ({ inputs, rules, packageName, pluginPackageJson, loadedFrom, origin }) {
34
+ const missingInputs = rules.filter((rule) => isMissingRequired(inputs, rule));
35
+ if (missingInputs.length === 0) {
36
+ return;
37
+ }
38
+ const names = missingInputs.map(getName);
39
+ const error = new Error(`Required inputs for plugin "${packageName}": ${names.join(', ')}`);
40
+ addInputError({ error, name: names[0], packageName, pluginPackageJson, loadedFrom, origin });
41
+ throw error;
42
+ };
43
+ const isMissingRequired = function (inputs, { name, required }) {
44
+ return required && inputs[name] === undefined;
45
+ };
46
+ const getName = function ({ name }) {
47
+ return name;
48
+ };
49
+ // Check each "inputs[*].*" property for a specific input
50
+ const checkUnknownInputs = function ({ inputs, rules, packageName, pluginPackageJson, loadedFrom, origin }) {
51
+ const knownInputs = rules.map(getName);
52
+ const unknownInputs = Object.keys(inputs).filter((name) => !knownInputs.includes(name));
53
+ if (unknownInputs.length === 0) {
54
+ return;
55
+ }
56
+ const unknownInputsMessage = getUnknownInputsMessage({ packageName, knownInputs, unknownInputs });
57
+ const error = new Error(`${unknownInputsMessage}
58
+ Check your plugin configuration to be sure that:
59
+ - the input name is spelled correctly
60
+ - the input is included in the plugin's available configuration options
61
+ - the plugin's input requirements have not changed`);
62
+ const [name] = unknownInputs;
63
+ addInputError({ error, name, packageName, pluginPackageJson, loadedFrom, origin });
64
+ throw error;
65
+ };
66
+ const getUnknownInputsMessage = function ({ packageName, knownInputs, unknownInputs }) {
67
+ const unknownInputsStr = unknownInputs.map(quoteWord).join(', ');
68
+ if (knownInputs.length === 0) {
69
+ return `Plugin "${packageName}" does not accept any inputs but you specified: ${unknownInputsStr}`;
70
+ }
71
+ const knownInputsStr = knownInputs.map(quoteWord).join(', ');
72
+ return `Unknown inputs for plugin "${packageName}": ${unknownInputsStr}
73
+ Plugin inputs should be one of: ${knownInputsStr}`;
74
+ };
75
+ const quoteWord = function (word) {
76
+ return `"${word}"`;
77
+ };
78
+ // Add error information
79
+ const addInputError = function ({ error, name, packageName, pluginPackageJson, loadedFrom, origin }) {
80
+ addErrorInfo(error, {
81
+ type: 'pluginInput',
82
+ plugin: { packageName, pluginPackageJson },
83
+ location: { event: 'load', packageName, input: name, loadedFrom, origin },
84
+ });
85
+ };
@@ -0,0 +1,38 @@
1
+ import { promises as fs } from 'fs';
2
+ import { load as loadYaml, JSON_SCHEMA } from 'js-yaml';
3
+ import { addErrorInfo } from '../../error/info.js';
4
+ import { validateManifest } from './validate.js';
5
+ // Load "manifest.yml" using its file path
6
+ export const loadManifest = async function ({ manifestPath, packageName, pluginPackageJson, loadedFrom, origin }) {
7
+ try {
8
+ const rawManifest = await loadRawManifest(manifestPath);
9
+ const manifest = await parseManifest(rawManifest);
10
+ validateManifest(manifest, rawManifest);
11
+ return manifest;
12
+ }
13
+ catch (error) {
14
+ addErrorInfo(error, {
15
+ type: 'pluginValidation',
16
+ plugin: { packageName, pluginPackageJson },
17
+ location: { event: 'load', packageName, loadedFrom, origin },
18
+ });
19
+ throw error;
20
+ }
21
+ };
22
+ const loadRawManifest = async function (manifestPath) {
23
+ try {
24
+ return await fs.readFile(manifestPath, 'utf8');
25
+ }
26
+ catch (error) {
27
+ error.message = `Could not load plugin's "manifest.yml"\n${error.message}`;
28
+ throw error;
29
+ }
30
+ };
31
+ const parseManifest = async function (rawManifest) {
32
+ try {
33
+ return await loadYaml(rawManifest, { schema: JSON_SCHEMA, json: true });
34
+ }
35
+ catch (error) {
36
+ throw new Error(`Could not parse plugin's "manifest.yml"\n${error.message}`);
37
+ }
38
+ };
@@ -0,0 +1,17 @@
1
+ import { addPluginLoadErrorStatus } from '../../status/load_error.js';
2
+ import { checkInputs } from './check.js';
3
+ import { loadManifest } from './load.js';
4
+ import { getManifestPath } from './path.js';
5
+ // Load plugin's `manifest.yml`
6
+ export const useManifest = async function ({ packageName, loadedFrom, origin, inputs }, { pluginDir, packageDir, pluginPackageJson, pluginPackageJson: { version }, debug }) {
7
+ const manifestPath = await getManifestPath({ pluginDir, packageDir, packageName });
8
+ try {
9
+ const manifest = await loadManifest({ manifestPath, packageName, pluginPackageJson, loadedFrom, origin });
10
+ const inputsA = checkInputs({ inputs, manifest, packageName, pluginPackageJson, loadedFrom, origin });
11
+ return { manifest, inputs: inputsA };
12
+ }
13
+ catch (error) {
14
+ addPluginLoadErrorStatus({ error, packageName, version, debug });
15
+ throw error;
16
+ }
17
+ };
@@ -0,0 +1,24 @@
1
+ import { locatePath } from 'locate-path';
2
+ import { addErrorInfo } from '../../error/info.js';
3
+ // Retrieve "manifest.yml" path for a specific plugin
4
+ export const getManifestPath = async function ({ pluginDir, packageDir, packageName }) {
5
+ const dirs = [pluginDir, packageDir]
6
+ .filter(Boolean)
7
+ .flatMap((dir) => MANIFEST_FILENAMES.map((filename) => `${dir}/${filename}`));
8
+ const manifestPath = await locatePath(dirs);
9
+ validateManifestExists(manifestPath, packageName);
10
+ return manifestPath;
11
+ };
12
+ const validateManifestExists = function (manifestPath, packageName) {
13
+ if (manifestPath !== undefined) {
14
+ return;
15
+ }
16
+ const error = new Error(`The plugin "${packageName}" is missing a "manifest.yml".
17
+ This might mean:
18
+ - The plugin "package" name is misspelled
19
+ - The plugin "package" points to a Node module that is not a Netlify Build plugin
20
+ - If you're developing a plugin, please see the documentation at https://docs.netlify.com/configure-builds/build-plugins/create-plugins/#anatomy-of-a-plugin`);
21
+ addErrorInfo(error, { type: 'resolveConfig' });
22
+ throw error;
23
+ };
24
+ const MANIFEST_FILENAMES = ['manifest.yml', 'manifest.yaml'];
@@ -0,0 +1,91 @@
1
+ import isPlainObj from 'is-plain-obj';
2
+ import { THEME } from '../../log/theme.js';
3
+ // Validate `manifest.yml` syntax
4
+ export const validateManifest = function (manifest, rawManifest) {
5
+ try {
6
+ validateBasic(manifest);
7
+ validateUnknownProps(manifest);
8
+ validateName(manifest);
9
+ validateInputs(manifest);
10
+ }
11
+ catch (error) {
12
+ error.message = `Plugin's "manifest.yml" ${error.message}
13
+
14
+ ${THEME.errorSubHeader('manifest.yml')}
15
+ ${rawManifest.trim()}`;
16
+ throw error;
17
+ }
18
+ };
19
+ const validateBasic = function (manifest) {
20
+ if (!isPlainObj(manifest)) {
21
+ throw new Error('must be a plain object');
22
+ }
23
+ };
24
+ const validateUnknownProps = function (manifest) {
25
+ const unknownProp = Object.keys(manifest).find((key) => !VALID_PROPS.has(key));
26
+ if (unknownProp !== undefined) {
27
+ throw new Error(`unknown property "${unknownProp}"`);
28
+ }
29
+ };
30
+ const VALID_PROPS = new Set(['name', 'inputs']);
31
+ const validateName = function ({ name }) {
32
+ if (name === undefined) {
33
+ throw new Error('must contain a "name" property');
34
+ }
35
+ if (typeof name !== 'string') {
36
+ throw new TypeError('"name" property must be a string');
37
+ }
38
+ };
39
+ const validateInputs = function ({ inputs }) {
40
+ if (inputs === undefined) {
41
+ return;
42
+ }
43
+ if (!isArrayOfObjects(inputs)) {
44
+ throw new Error('"inputs" property must be an array of objects');
45
+ }
46
+ inputs.forEach(validateInput);
47
+ };
48
+ const isArrayOfObjects = function (objects) {
49
+ return Array.isArray(objects) && objects.every(isPlainObj);
50
+ };
51
+ const validateInput = function (input, index) {
52
+ try {
53
+ validateUnknownInputProps(input);
54
+ validateInputName(input);
55
+ validateInputDescription(input);
56
+ validateInputRequired(input);
57
+ }
58
+ catch (error) {
59
+ error.message = `"inputs" property is invalid.
60
+ Input at position ${index} ${error.message}.`;
61
+ throw error;
62
+ }
63
+ };
64
+ const validateUnknownInputProps = function (input) {
65
+ const unknownProp = Object.keys(input).find((key) => !VALID_INPUT_PROPS.has(key));
66
+ if (unknownProp !== undefined) {
67
+ throw new Error(`has an unknown property "${unknownProp}"`);
68
+ }
69
+ };
70
+ const VALID_INPUT_PROPS = new Set(['name', 'description', 'required', 'default']);
71
+ const validateInputName = function ({ name }) {
72
+ if (name === undefined) {
73
+ throw new Error('must contain a "name" property');
74
+ }
75
+ if (typeof name !== 'string') {
76
+ throw new TypeError('"name" property must be a string');
77
+ }
78
+ };
79
+ const validateInputDescription = function ({ description }) {
80
+ if (description === undefined) {
81
+ return;
82
+ }
83
+ if (typeof description !== 'string') {
84
+ throw new TypeError('"description" property must be a string');
85
+ }
86
+ };
87
+ const validateInputRequired = function ({ required }) {
88
+ if (required !== undefined && typeof required !== 'boolean') {
89
+ throw new Error('"required" property must be a boolean');
90
+ }
91
+ };
@@ -0,0 +1,30 @@
1
+ import { version as currentVersion, execPath } from 'process';
2
+ import semver from 'semver';
3
+ import { addErrorInfo } from '../error/info.js';
4
+ import { ROOT_PACKAGE_JSON } from '../utils/json.js';
5
+ // Local plugins and `package.json`-installed plugins use user's preferred Node.js version if higher than our minimum
6
+ // supported version. Else default to the system Node version.
7
+ // Local and programmatic builds use `@netlify/build` Node.js version, which is
8
+ // usually the system's Node.js version.
9
+ // If the user Node version does not satisfy our supported engine range use our own system Node version
10
+ export const addPluginsNodeVersion = function ({ pluginsOptions, nodePath, userNodeVersion }) {
11
+ const currentNodeVersion = semver.clean(currentVersion);
12
+ return pluginsOptions.map((pluginOptions) => addPluginNodeVersion({ pluginOptions, currentNodeVersion, userNodeVersion, nodePath }));
13
+ };
14
+ const addPluginNodeVersion = function ({ pluginOptions, pluginOptions: { loadedFrom }, currentNodeVersion, userNodeVersion, nodePath, }) {
15
+ return (loadedFrom === 'local' || loadedFrom === 'package.json') &&
16
+ semver.satisfies(userNodeVersion, ROOT_PACKAGE_JSON.engines.node)
17
+ ? { ...pluginOptions, nodePath, nodeVersion: userNodeVersion }
18
+ : { ...pluginOptions, nodePath: execPath, nodeVersion: currentNodeVersion };
19
+ };
20
+ // Ensure Node.js version is compatible with plugin's `engines.node`
21
+ export const checkNodeVersion = function ({ nodeVersion, packageName, pluginPackageJson: { engines: { node: pluginNodeVersionRange } = {} } = {}, }) {
22
+ if (pluginNodeVersionRange && !semver.satisfies(nodeVersion, pluginNodeVersionRange)) {
23
+ throwUserError(`The Node.js version is ${nodeVersion} but the plugin "${packageName}" requires ${pluginNodeVersionRange}`);
24
+ }
25
+ };
26
+ const throwUserError = function (message) {
27
+ const error = new Error(message);
28
+ addErrorInfo(error, { type: 'resolveConfig' });
29
+ throw error;
30
+ };
@@ -0,0 +1,55 @@
1
+ import { dirname } from 'path';
2
+ import { installLocalPluginsDependencies } from '../install/local.js';
3
+ import { measureDuration } from '../time/main.js';
4
+ import { ROOT_PACKAGE_JSON } from '../utils/json.js';
5
+ import { getPackageJson } from '../utils/package.js';
6
+ import { useManifest } from './manifest/main.js';
7
+ import { checkNodeVersion } from './node_version.js';
8
+ import { resolvePluginsPath } from './resolve.js';
9
+ // Load core plugins and user plugins
10
+ const tGetPluginsOptions = async function ({ pluginsOptions, netlifyConfig: { plugins }, siteInfo, buildDir, nodePath, packageJson, userNodeVersion, mode, api, logs, debug, sendStatus, testOpts, featureFlags, }) {
11
+ const pluginsOptionsA = await resolvePluginsPath({
12
+ pluginsOptions,
13
+ siteInfo,
14
+ buildDir,
15
+ nodePath,
16
+ packageJson,
17
+ userNodeVersion,
18
+ mode,
19
+ api,
20
+ logs,
21
+ debug,
22
+ sendStatus,
23
+ testOpts,
24
+ featureFlags,
25
+ });
26
+ const pluginsOptionsB = await Promise.all(pluginsOptionsA.map((pluginOptions) => loadPluginFiles({ pluginOptions, debug })));
27
+ const pluginsOptionsC = pluginsOptionsB.filter(isNotRedundantCorePlugin);
28
+ await installLocalPluginsDependencies({ plugins, pluginsOptions: pluginsOptionsC, buildDir, mode, logs });
29
+ return { pluginsOptions: pluginsOptionsC };
30
+ };
31
+ export const getPluginsOptions = measureDuration(tGetPluginsOptions, 'get_plugins_options');
32
+ // Retrieve plugin's main file path.
33
+ // Then load plugin's `package.json` and `manifest.yml`.
34
+ const loadPluginFiles = async function ({ pluginOptions, pluginOptions: { pluginPath, nodeVersion, packageName }, debug, }) {
35
+ const pluginDir = dirname(pluginPath);
36
+ const { packageDir, packageJson: pluginPackageJson } = await getPackageJson(pluginDir);
37
+ checkNodeVersion({ nodeVersion, packageName, pluginPackageJson });
38
+ const { manifest, inputs } = await useManifest(pluginOptions, { pluginDir, packageDir, pluginPackageJson, debug });
39
+ return { ...pluginOptions, pluginDir, packageDir, pluginPackageJson, manifest, inputs };
40
+ };
41
+ // Core plugins can only be included once.
42
+ // For example, when testing core plugins, they might be included as local plugins,
43
+ // in which case they should not be included twice.
44
+ const isNotRedundantCorePlugin = function (pluginOptionsA, index, pluginsOptions) {
45
+ return (pluginOptionsA.loadedFrom !== 'core' ||
46
+ pluginsOptions.every((pluginOptionsB) => pluginOptionsA.manifest.name !== pluginOptionsB.manifest.name || pluginOptionsA === pluginOptionsB));
47
+ };
48
+ // Retrieve information about @netlify/build when an error happens there and not
49
+ // in a plugin
50
+ export const getSpawnInfo = function () {
51
+ return {
52
+ plugin: { packageName: ROOT_PACKAGE_JSON.name, pluginPackageJson: ROOT_PACKAGE_JSON },
53
+ location: { event: 'load', packageName: ROOT_PACKAGE_JSON.name, loadedFrom: 'core', origin: 'core' },
54
+ };
55
+ };
@@ -0,0 +1,83 @@
1
+ import { handleBuildError } from '../error/handle.js';
2
+ import { getMajorVersion } from '../utils/semver.js';
3
+ // Retrieve plugin's pinned major versions by fetching the latest `PluginRun`
4
+ // Only applies to `netlify.toml`-only installed plugins.
5
+ export const addPinnedVersions = async function ({ pluginsOptions, api, siteInfo: { id: siteId }, sendStatus }) {
6
+ if (!sendStatus || api === undefined || !siteId) {
7
+ return pluginsOptions;
8
+ }
9
+ const packages = pluginsOptions.filter(shouldFetchPinVersion).map(getPackageName);
10
+ if (packages.length === 0) {
11
+ return pluginsOptions;
12
+ }
13
+ const pluginRuns = await api.getLatestPluginRuns({ site_id: siteId, packages, state: 'success' });
14
+ const pluginsOptionsA = pluginsOptions.map((pluginOption) => addPinnedVersion(pluginOption, pluginRuns));
15
+ return pluginsOptionsA;
16
+ };
17
+ const shouldFetchPinVersion = function ({ pinnedVersion, loadedFrom, origin }) {
18
+ return pinnedVersion === undefined && loadedFrom === 'auto_install' && origin === 'config';
19
+ };
20
+ const getPackageName = function ({ packageName }) {
21
+ return packageName;
22
+ };
23
+ const addPinnedVersion = function (pluginOptions, pluginRuns) {
24
+ const foundPluginRun = pluginRuns.find((pluginRun) => pluginRun.package === pluginOptions.packageName);
25
+ if (foundPluginRun === undefined) {
26
+ return pluginOptions;
27
+ }
28
+ const pinnedVersion = getMajorVersion(foundPluginRun.version);
29
+ return pinnedVersion === undefined ? pluginOptions : { ...pluginOptions, pinnedVersion };
30
+ };
31
+ // Send an API request to pin plugins' major versions.
32
+ // Only applies to UI-installed plugins.
33
+ export const pinPlugins = async function ({ pluginsOptions, failedPlugins, api, siteInfo: { id: siteId }, childEnv, mode, netlifyConfig, errorMonitor, logs, debug, testOpts, sendStatus, }) {
34
+ if ((mode !== 'buildbot' && !sendStatus) || api === undefined || !siteId) {
35
+ return;
36
+ }
37
+ const pluginsOptionsA = pluginsOptions.filter((pluginOptions) => shouldPinVersion({ pluginOptions, failedPlugins }));
38
+ await Promise.all(pluginsOptionsA.map((pluginOptions) => pinPlugin({
39
+ pluginOptions,
40
+ api,
41
+ childEnv,
42
+ mode,
43
+ netlifyConfig,
44
+ errorMonitor,
45
+ logs,
46
+ debug,
47
+ testOpts,
48
+ siteId,
49
+ })));
50
+ };
51
+ // Only pin version if:
52
+ // - the plugin's version has not been pinned yet
53
+ // - the plugin was installed in the UI
54
+ // - both the build and the plugin succeeded
55
+ const shouldPinVersion = function ({ pluginOptions: { packageName, pinnedVersion, loadedFrom, origin }, failedPlugins, }) {
56
+ return (pinnedVersion === undefined &&
57
+ loadedFrom === 'auto_install' &&
58
+ origin === 'ui' &&
59
+ !failedPlugins.includes(packageName));
60
+ };
61
+ const pinPlugin = async function ({ pluginOptions: { packageName, pluginPackageJson: { version }, }, api, childEnv, mode, netlifyConfig, errorMonitor, logs, debug, testOpts, siteId, }) {
62
+ const pinnedVersion = getMajorVersion(version);
63
+ try {
64
+ await api.updatePlugin({
65
+ package: encodeURIComponent(packageName),
66
+ site_id: siteId,
67
+ body: { pinned_version: pinnedVersion },
68
+ });
69
+ // Bitballoon API randomly fails with 502.
70
+ // Builds should be successful when this API call fails, but we still want
71
+ // to report the error both in logs and in error monitoring.
72
+ }
73
+ catch (error) {
74
+ if (shouldIgnoreError(error)) {
75
+ return;
76
+ }
77
+ await handleBuildError(error, { errorMonitor, netlifyConfig, childEnv, mode, logs, debug, testOpts });
78
+ }
79
+ };
80
+ // Status is 404 if the plugin is uninstalled while the build is ongoing.
81
+ const shouldIgnoreError = function ({ status }) {
82
+ return status === 404;
83
+ };
@@ -0,0 +1,110 @@
1
+ import { addErrorInfo } from '../error/info.js';
2
+ import { installMissingPlugins } from '../install/missing.js';
3
+ import { resolvePath, tryResolvePath } from '../utils/resolve.js';
4
+ import { addExpectedVersions } from './expected_version.js';
5
+ import { addPluginsNodeVersion } from './node_version.js';
6
+ import { addPinnedVersions } from './pinned_version.js';
7
+ // Try to find plugins in four places, by priority order:
8
+ // - already loaded (core plugins)
9
+ // - local plugin
10
+ // - external plugin already installed in `node_modules`, most likely through `package.json`
11
+ // - automatically installed by us, to `.netlify/plugins/`
12
+ export const resolvePluginsPath = async function ({ pluginsOptions, siteInfo, buildDir, nodePath, packageJson, userNodeVersion, mode, api, logs, debug, sendStatus, testOpts, featureFlags, }) {
13
+ const autoPluginsDir = getAutoPluginsDir(buildDir);
14
+ const pluginsOptionsA = await Promise.all(pluginsOptions.map((pluginOptions) => resolvePluginPath({ pluginOptions, buildDir, autoPluginsDir })));
15
+ const pluginsOptionsB = addPluginsNodeVersion({
16
+ pluginsOptions: pluginsOptionsA,
17
+ nodePath,
18
+ userNodeVersion,
19
+ logs,
20
+ });
21
+ const pluginsOptionsC = await addPinnedVersions({ pluginsOptions: pluginsOptionsB, api, siteInfo, sendStatus });
22
+ const pluginsOptionsD = await addExpectedVersions({
23
+ pluginsOptions: pluginsOptionsC,
24
+ autoPluginsDir,
25
+ packageJson,
26
+ debug,
27
+ logs,
28
+ buildDir,
29
+ testOpts,
30
+ featureFlags,
31
+ });
32
+ const pluginsOptionsE = await handleMissingPlugins({
33
+ pluginsOptions: pluginsOptionsD,
34
+ autoPluginsDir,
35
+ mode,
36
+ logs,
37
+ });
38
+ return pluginsOptionsE;
39
+ };
40
+ // Find the path to the directory used to install plugins automatically.
41
+ // It is a subdirectory of `buildDir`, so that the plugin can require the
42
+ // project's dependencies (peer dependencies).
43
+ const getAutoPluginsDir = function (buildDir) {
44
+ return `${buildDir}/${AUTO_PLUGINS_DIR}`;
45
+ };
46
+ const AUTO_PLUGINS_DIR = '.netlify/plugins/';
47
+ const resolvePluginPath = async function ({ pluginOptions, pluginOptions: { packageName, loadedFrom }, buildDir, autoPluginsDir, }) {
48
+ // Core plugins
49
+ if (loadedFrom !== undefined) {
50
+ return pluginOptions;
51
+ }
52
+ const localPackageName = normalizeLocalPackageName(packageName);
53
+ // Local plugins
54
+ if (localPackageName.startsWith('.')) {
55
+ const { path: localPath, error } = await tryResolvePath(localPackageName, buildDir);
56
+ validateLocalPluginPath(error, localPackageName);
57
+ return { ...pluginOptions, pluginPath: localPath, loadedFrom: 'local' };
58
+ }
59
+ // Plugin added to `package.json`
60
+ const { path: manualPath } = await tryResolvePath(packageName, buildDir);
61
+ if (manualPath !== undefined) {
62
+ return { ...pluginOptions, pluginPath: manualPath, loadedFrom: 'package.json' };
63
+ }
64
+ // Previously automatically installed
65
+ const { path: automaticPath } = await tryResolvePath(packageName, autoPluginsDir);
66
+ if (automaticPath !== undefined) {
67
+ return { ...pluginOptions, pluginPath: automaticPath, loadedFrom: 'auto_install' };
68
+ }
69
+ // Happens if the plugin:
70
+ // - name is mispelled
71
+ // - is not in our official list
72
+ // - is in our official list but has not been installed by this site yet
73
+ return { ...pluginOptions, loadedFrom: 'auto_install' };
74
+ };
75
+ // `packageName` starting with `/` are relative to the build directory
76
+ const normalizeLocalPackageName = function (packageName) {
77
+ if (packageName.startsWith('/')) {
78
+ return `.${packageName}`;
79
+ }
80
+ return packageName;
81
+ };
82
+ // When requiring a local plugin with an invalid file path
83
+ const validateLocalPluginPath = function (error, localPackageName) {
84
+ if (error !== undefined) {
85
+ error.message = `Plugin could not be found using local path: ${localPackageName}\n${error.message}`;
86
+ addErrorInfo(error, { type: 'resolveConfig' });
87
+ throw error;
88
+ }
89
+ };
90
+ // Install plugins from the official list that have not been previously installed.
91
+ // Print a warning if they have not been installed through the UI.
92
+ const handleMissingPlugins = async function ({ pluginsOptions, autoPluginsDir, mode, logs }) {
93
+ const missingPlugins = pluginsOptions.filter(isMissingPlugin);
94
+ if (missingPlugins.length === 0) {
95
+ return pluginsOptions;
96
+ }
97
+ await installMissingPlugins({ missingPlugins, autoPluginsDir, mode, logs });
98
+ return await Promise.all(pluginsOptions.map((pluginOptions) => resolveMissingPluginPath({ pluginOptions, autoPluginsDir })));
99
+ };
100
+ // Resolve the plugins that just got automatically installed
101
+ const resolveMissingPluginPath = async function ({ pluginOptions, pluginOptions: { packageName }, autoPluginsDir }) {
102
+ if (!isMissingPlugin(pluginOptions)) {
103
+ return pluginOptions;
104
+ }
105
+ const pluginPath = await resolvePath(packageName, autoPluginsDir);
106
+ return { ...pluginOptions, pluginPath };
107
+ };
108
+ const isMissingPlugin = function ({ isMissing }) {
109
+ return isMissing;
110
+ };
@@ -0,0 +1,54 @@
1
+ import { fileURLToPath } from 'url';
2
+ import { execaNode } from 'execa';
3
+ import { addErrorInfo } from '../error/info.js';
4
+ import { logRuntime, logLoadingPlugins, logOutdatedPlugins, logIncompatiblePlugins, } from '../log/messages/compatibility.js';
5
+ import { measureDuration } from '../time/main.js';
6
+ import { getEventFromChild } from './ipc.js';
7
+ import { getSpawnInfo } from './options.js';
8
+ const CHILD_MAIN_FILE = fileURLToPath(new URL('child/main.js', import.meta.url));
9
+ // Start child processes used by all plugins
10
+ // We fire plugins through child processes so that:
11
+ // - each plugin is sandboxed, e.g. cannot access/modify its parent `process`
12
+ // (for both security and safety reasons)
13
+ // - logs can be buffered which allows manipulating them for log shipping,
14
+ // transforming and parallel plugins
15
+ const tStartPlugins = async function ({ pluginsOptions, buildDir, childEnv, logs, debug }) {
16
+ logRuntime(logs, pluginsOptions);
17
+ logLoadingPlugins(logs, pluginsOptions, debug);
18
+ logOutdatedPlugins(logs, pluginsOptions);
19
+ logIncompatiblePlugins(logs, pluginsOptions);
20
+ const childProcesses = await Promise.all(pluginsOptions.map(({ pluginDir, nodePath }) => startPlugin({ pluginDir, nodePath, buildDir, childEnv })));
21
+ return { childProcesses };
22
+ };
23
+ export const startPlugins = measureDuration(tStartPlugins, 'start_plugins');
24
+ const startPlugin = async function ({ pluginDir, nodePath, buildDir, childEnv }) {
25
+ const childProcess = execaNode(CHILD_MAIN_FILE, {
26
+ cwd: buildDir,
27
+ preferLocal: true,
28
+ localDir: pluginDir,
29
+ nodePath,
30
+ execPath: nodePath,
31
+ env: childEnv,
32
+ extendEnv: false,
33
+ serialization: 'advanced',
34
+ });
35
+ try {
36
+ await getEventFromChild(childProcess, 'ready');
37
+ return { childProcess };
38
+ }
39
+ catch (error) {
40
+ const spawnInfo = getSpawnInfo();
41
+ addErrorInfo(error, spawnInfo);
42
+ throw error;
43
+ }
44
+ };
45
+ // Stop all plugins child processes
46
+ export const stopPlugins = function (childProcesses) {
47
+ childProcesses.forEach(stopPlugin);
48
+ };
49
+ const stopPlugin = function ({ childProcess }) {
50
+ if (childProcess.connected) {
51
+ childProcess.disconnect();
52
+ }
53
+ childProcess.kill();
54
+ };
@@ -0,0 +1,35 @@
1
+ import { listCorePlugins, isCorePlugin } from './list.js';
2
+ // Add core plugins and user plugins together.
3
+ // Do not allow user override of core plugins.
4
+ export const addCorePlugins = function ({ netlifyConfig: { plugins }, constants }) {
5
+ const corePlugins = listCorePlugins(constants);
6
+ const allCorePlugins = corePlugins
7
+ .map((corePlugin) => addCoreProperties(corePlugin, plugins))
8
+ .filter((corePlugin) => !isOptionalCore(corePlugin, plugins));
9
+ const userPlugins = plugins.filter(isUserPlugin);
10
+ const allPlugins = [...userPlugins, ...allCorePlugins];
11
+ const pluginsOptions = allPlugins.map(normalizePluginOptions);
12
+ return pluginsOptions;
13
+ };
14
+ const addCoreProperties = function (corePlugin, plugins) {
15
+ const inputs = getCorePluginInputs(corePlugin, plugins);
16
+ return { ...corePlugin, inputs, loadedFrom: 'core', origin: 'core' };
17
+ };
18
+ // Core plugins can get inputs too
19
+ const getCorePluginInputs = function (corePlugin, plugins) {
20
+ const configuredCorePlugin = plugins.find((plugin) => plugin.package === corePlugin.package);
21
+ if (configuredCorePlugin === undefined) {
22
+ return {};
23
+ }
24
+ return configuredCorePlugin.inputs;
25
+ };
26
+ // Optional core plugins requires user opt-in
27
+ const isOptionalCore = function (pluginA, plugins) {
28
+ return pluginA.optional && plugins.every((pluginB) => pluginB.package !== pluginA.package);
29
+ };
30
+ const isUserPlugin = function (plugin) {
31
+ return !isCorePlugin(plugin.package);
32
+ };
33
+ const normalizePluginOptions = function ({ package: packageName, pluginPath, pinned_version: pinnedVersion, loadedFrom, origin, inputs, }) {
34
+ return { packageName, pluginPath, pinnedVersion, loadedFrom, origin, inputs };
35
+ };