@netlify/build 29.0.0-rc → 29.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.
Files changed (266) hide show
  1. package/bin.js +5 -0
  2. package/lib/core/bin.js +66 -0
  3. package/lib/core/build.js +356 -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 +21 -0
  9. package/lib/core/flags.js +201 -0
  10. package/lib/core/lingering.js +68 -0
  11. package/lib/core/main.js +110 -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/report.js +27 -0
  41. package/lib/error/type.js +177 -0
  42. package/lib/install/functions.js +20 -0
  43. package/lib/install/local.js +45 -0
  44. package/lib/install/main.js +67 -0
  45. package/lib/install/missing.js +54 -0
  46. package/lib/log/colors.js +28 -0
  47. package/lib/log/description.js +21 -0
  48. package/lib/log/header.js +14 -0
  49. package/lib/log/header_func.js +13 -0
  50. package/lib/log/logger.js +140 -0
  51. package/lib/log/messages/compatibility.js +146 -0
  52. package/lib/log/messages/config.js +91 -0
  53. package/lib/log/messages/core.js +51 -0
  54. package/lib/log/messages/core_steps.js +75 -0
  55. package/lib/log/messages/dry.js +41 -0
  56. package/lib/log/messages/install.js +25 -0
  57. package/lib/log/messages/ipc.js +29 -0
  58. package/lib/log/messages/mutations.js +62 -0
  59. package/{src → lib}/log/messages/plugins.js +18 -32
  60. package/lib/log/messages/status.js +14 -0
  61. package/lib/log/messages/steps.js +18 -0
  62. package/lib/log/old_version.js +32 -0
  63. package/lib/log/serialize.js +10 -0
  64. package/lib/log/stream.js +68 -0
  65. package/lib/log/theme.js +27 -0
  66. package/lib/plugins/child/diff.js +46 -0
  67. package/lib/plugins/child/error.js +26 -0
  68. package/lib/plugins/child/lazy.js +15 -0
  69. package/lib/plugins/child/load.js +22 -0
  70. package/lib/plugins/child/logic.js +57 -0
  71. package/lib/plugins/child/main.js +37 -0
  72. package/lib/plugins/child/run.js +19 -0
  73. package/lib/plugins/child/status.js +63 -0
  74. package/lib/plugins/child/typescript.js +28 -0
  75. package/lib/plugins/child/utils.js +42 -0
  76. package/lib/plugins/child/validate.js +31 -0
  77. package/lib/plugins/compatibility.js +104 -0
  78. package/{src → lib}/plugins/error.js +31 -35
  79. package/{src → lib}/plugins/events.js +7 -12
  80. package/lib/plugins/expected_version.js +81 -0
  81. package/lib/plugins/internal.js +10 -0
  82. package/lib/plugins/ipc.js +120 -0
  83. package/lib/plugins/list.js +73 -0
  84. package/lib/plugins/load.js +50 -0
  85. package/lib/plugins/manifest/check.js +85 -0
  86. package/lib/plugins/manifest/load.js +38 -0
  87. package/lib/plugins/manifest/main.js +19 -0
  88. package/lib/plugins/manifest/path.js +24 -0
  89. package/lib/plugins/manifest/validate.js +91 -0
  90. package/lib/plugins/node_version.js +35 -0
  91. package/lib/plugins/options.js +70 -0
  92. package/lib/plugins/pinned_version.js +83 -0
  93. package/lib/plugins/resolve.js +110 -0
  94. package/lib/plugins/spawn.js +58 -0
  95. package/lib/plugins_core/add.js +35 -0
  96. package/lib/plugins_core/build_command.js +50 -0
  97. package/lib/plugins_core/deploy/buildbot_client.js +87 -0
  98. package/lib/plugins_core/deploy/index.js +49 -0
  99. package/{src → lib}/plugins_core/deploy/manifest.yml +0 -0
  100. package/lib/plugins_core/edge_functions/index.js +76 -0
  101. package/{src → lib}/plugins_core/edge_functions/lib/error.js +13 -17
  102. package/lib/plugins_core/edge_functions/validate_manifest/validate_edge_functions_manifest.js +21 -0
  103. package/lib/plugins_core/functions/error.js +123 -0
  104. package/lib/plugins_core/functions/feature_flags.js +6 -0
  105. package/lib/plugins_core/functions/index.js +114 -0
  106. package/lib/plugins_core/functions/utils.js +45 -0
  107. package/lib/plugins_core/functions/zisi.js +39 -0
  108. package/{src → lib}/plugins_core/functions_install/index.js +8 -11
  109. package/{src → lib}/plugins_core/functions_install/manifest.yml +0 -0
  110. package/lib/plugins_core/list.js +20 -0
  111. package/lib/report/statsd.js +31 -0
  112. package/lib/status/add.js +30 -0
  113. package/lib/status/colors.js +18 -0
  114. package/lib/status/load_error.js +10 -0
  115. package/lib/status/report.js +83 -0
  116. package/lib/status/success.js +14 -0
  117. package/lib/steps/core_step.js +59 -0
  118. package/lib/steps/error.js +65 -0
  119. package/lib/steps/get.js +43 -0
  120. package/lib/steps/plugin.js +55 -0
  121. package/lib/steps/return.js +25 -0
  122. package/lib/steps/run_core_steps.js +117 -0
  123. package/lib/steps/run_step.js +190 -0
  124. package/lib/steps/run_steps.js +96 -0
  125. package/lib/steps/update_config.js +66 -0
  126. package/lib/telemetry/main.js +97 -0
  127. package/lib/time/aggregate.js +109 -0
  128. package/lib/time/main.js +31 -0
  129. package/lib/time/measure.js +16 -0
  130. package/lib/time/report.js +24 -0
  131. package/lib/utils/errors.js +13 -0
  132. package/lib/utils/json.js +15 -0
  133. package/lib/utils/omit.js +3 -0
  134. package/lib/utils/package.js +24 -0
  135. package/lib/utils/remove_falsy.js +8 -0
  136. package/lib/utils/resolve.js +41 -0
  137. package/lib/utils/runtime.js +5 -0
  138. package/lib/utils/semver.js +28 -0
  139. package/package.json +41 -24
  140. package/types/config/netlify_config.d.ts +4 -4
  141. package/types/netlify_plugin_constants.d.ts +8 -8
  142. package/src/core/bin.js +0 -83
  143. package/src/core/build.js +0 -554
  144. package/src/core/config.js +0 -186
  145. package/src/core/constants.js +0 -156
  146. package/src/core/dev.js +0 -31
  147. package/src/core/dry.js +0 -39
  148. package/src/core/feature_flags.js +0 -22
  149. package/src/core/flags.js +0 -204
  150. package/src/core/lingering.js +0 -85
  151. package/src/core/main.js +0 -165
  152. package/src/core/missing_side_file.js +0 -29
  153. package/src/core/normalize_flags.js +0 -70
  154. package/src/core/severity.js +0 -22
  155. package/src/core/user_node_version.js +0 -41
  156. package/src/env/changes.js +0 -52
  157. package/src/env/main.js +0 -19
  158. package/src/env/metadata.js +0 -81
  159. package/src/error/api.js +0 -46
  160. package/src/error/build.js +0 -50
  161. package/src/error/colors.js +0 -11
  162. package/src/error/handle.js +0 -57
  163. package/src/error/info.js +0 -46
  164. package/src/error/monitor/location.js +0 -21
  165. package/src/error/monitor/normalize.js +0 -96
  166. package/src/error/monitor/print.js +0 -42
  167. package/src/error/monitor/report.js +0 -138
  168. package/src/error/monitor/start.js +0 -69
  169. package/src/error/parse/clean_stack.js +0 -87
  170. package/src/error/parse/location.js +0 -62
  171. package/src/error/parse/normalize.js +0 -29
  172. package/src/error/parse/parse.js +0 -97
  173. package/src/error/parse/plugin.js +0 -70
  174. package/src/error/parse/properties.js +0 -23
  175. package/src/error/parse/serialize_log.js +0 -42
  176. package/src/error/parse/serialize_status.js +0 -23
  177. package/src/error/parse/stack.js +0 -43
  178. package/src/error/type.js +0 -189
  179. package/src/install/functions.js +0 -28
  180. package/src/install/local.js +0 -62
  181. package/src/install/main.js +0 -81
  182. package/src/install/missing.js +0 -67
  183. package/src/log/colors.js +0 -34
  184. package/src/log/description.js +0 -26
  185. package/src/log/header.js +0 -16
  186. package/src/log/header_func.js +0 -17
  187. package/src/log/logger.js +0 -161
  188. package/src/log/messages/compatibility.js +0 -164
  189. package/src/log/messages/config.js +0 -107
  190. package/src/log/messages/core.js +0 -70
  191. package/src/log/messages/core_steps.js +0 -104
  192. package/src/log/messages/dry.js +0 -63
  193. package/src/log/messages/install.js +0 -20
  194. package/src/log/messages/ipc.js +0 -38
  195. package/src/log/messages/mutations.js +0 -82
  196. package/src/log/messages/status.js +0 -16
  197. package/src/log/messages/steps.js +0 -22
  198. package/src/log/old_version.js +0 -41
  199. package/src/log/serialize.js +0 -13
  200. package/src/log/stream.js +0 -85
  201. package/src/log/theme.js +0 -26
  202. package/src/plugins/child/diff.js +0 -55
  203. package/src/plugins/child/error.js +0 -32
  204. package/src/plugins/child/lazy.js +0 -18
  205. package/src/plugins/child/load.js +0 -29
  206. package/src/plugins/child/logic.js +0 -57
  207. package/src/plugins/child/main.js +0 -51
  208. package/src/plugins/child/run.js +0 -28
  209. package/src/plugins/child/status.js +0 -74
  210. package/src/plugins/child/typescript.js +0 -45
  211. package/src/plugins/child/utils.js +0 -56
  212. package/src/plugins/child/validate.js +0 -34
  213. package/src/plugins/compatibility.js +0 -128
  214. package/src/plugins/expected_version.js +0 -119
  215. package/src/plugins/ipc.js +0 -145
  216. package/src/plugins/list.js +0 -86
  217. package/src/plugins/load.js +0 -70
  218. package/src/plugins/manifest/check.js +0 -106
  219. package/src/plugins/manifest/load.js +0 -41
  220. package/src/plugins/manifest/main.js +0 -22
  221. package/src/plugins/manifest/path.js +0 -31
  222. package/src/plugins/manifest/validate.js +0 -108
  223. package/src/plugins/node_version.js +0 -50
  224. package/src/plugins/options.js +0 -88
  225. package/src/plugins/pinned_version.js +0 -131
  226. package/src/plugins/resolve.js +0 -152
  227. package/src/plugins/spawn.js +0 -66
  228. package/src/plugins_core/add.js +0 -49
  229. package/src/plugins_core/build_command.js +0 -75
  230. package/src/plugins_core/deploy/buildbot_client.js +0 -102
  231. package/src/plugins_core/deploy/index.js +0 -73
  232. package/src/plugins_core/edge_functions/index.js +0 -123
  233. package/src/plugins_core/edge_functions/lib/internal_manifest.js +0 -54
  234. package/src/plugins_core/edge_functions/validate_manifest/validate_edge_functions_manifest.js +0 -89
  235. package/src/plugins_core/functions/error.js +0 -163
  236. package/src/plugins_core/functions/feature_flags.js +0 -6
  237. package/src/plugins_core/functions/index.js +0 -160
  238. package/src/plugins_core/functions/utils.js +0 -66
  239. package/src/plugins_core/functions/zisi.js +0 -53
  240. package/src/plugins_core/list.js +0 -27
  241. package/src/status/add.js +0 -36
  242. package/src/status/colors.js +0 -23
  243. package/src/status/load_error.js +0 -11
  244. package/src/status/report.js +0 -137
  245. package/src/status/success.js +0 -18
  246. package/src/steps/core_step.js +0 -92
  247. package/src/steps/error.js +0 -102
  248. package/src/steps/get.js +0 -51
  249. package/src/steps/plugin.js +0 -85
  250. package/src/steps/return.js +0 -52
  251. package/src/steps/run_core_steps.js +0 -200
  252. package/src/steps/run_step.js +0 -304
  253. package/src/steps/run_steps.js +0 -179
  254. package/src/steps/update_config.js +0 -93
  255. package/src/telemetry/main.js +0 -136
  256. package/src/time/aggregate.js +0 -146
  257. package/src/time/main.js +0 -48
  258. package/src/time/measure.js +0 -22
  259. package/src/time/report.js +0 -59
  260. package/src/utils/errors.js +0 -12
  261. package/src/utils/json.js +0 -19
  262. package/src/utils/omit.js +0 -6
  263. package/src/utils/package.js +0 -23
  264. package/src/utils/remove_falsy.js +0 -10
  265. package/src/utils/resolve.js +0 -46
  266. package/src/utils/semver.js +0 -34
@@ -0,0 +1,73 @@
1
+ import { pluginsUrl, pluginsList as oldPluginsList } from '@netlify/plugins-list';
2
+ import got from 'got';
3
+ import isPlainObj from 'is-plain-obj';
4
+ import { logPluginsList, logPluginsFetchError } from '../log/messages/plugins.js';
5
+ import { CONDITIONS } from './compatibility.js';
6
+ // Retrieve the list of plugins officially vetted by us and displayed in our
7
+ // plugins directory UI.
8
+ // We fetch this list during each build (no caching) because we want new
9
+ // versions of plugins to be available instantly to all users. The time to
10
+ // make this request is somewhat ok (in the 100ms range).
11
+ // We only fetch this plugins list when needed, i.e. we defer it as much as
12
+ // possible.
13
+ export const getPluginsList = async function ({ debug, logs, testOpts: { pluginsListUrl } }) {
14
+ // We try not to mock in integration tests. However, sending a request for
15
+ // each test would be too slow and make tests unreliable.
16
+ if (pluginsListUrl === 'test') {
17
+ return [];
18
+ }
19
+ const pluginsListUrlA = pluginsListUrl === undefined ? pluginsUrl : pluginsListUrl;
20
+ const pluginsList = await fetchPluginsList({ logs, pluginsListUrl: pluginsListUrlA });
21
+ const pluginsListA = normalizePluginsList(pluginsList);
22
+ logPluginsList({ pluginsList: pluginsListA, debug, logs });
23
+ return pluginsListA;
24
+ };
25
+ const fetchPluginsList = async function ({ logs, pluginsListUrl }) {
26
+ try {
27
+ const { body } = await got(pluginsListUrl, { responseType: 'json', timeout: PLUGINS_LIST_TIMEOUT });
28
+ if (!isValidPluginsList(body)) {
29
+ throw new Error(`Request succeeded but with an invalid response:\n${JSON.stringify(body, null, 2)}`);
30
+ }
31
+ return body;
32
+ // The Netlify Site should be up. This is a fallback.
33
+ // `oldPluginsList` might not contain the latest plugins versions:
34
+ // - We should do `npm publish` as soon as a PR is merged in
35
+ // `netlify/plugins` but it is possible we don't.
36
+ // - Releasing it requires a @netlify/buld release, which requires itself a
37
+ // buildbot release.
38
+ }
39
+ catch (error) {
40
+ logPluginsFetchError(logs, error.message);
41
+ return oldPluginsList;
42
+ }
43
+ };
44
+ // 1 minute HTTP request timeout
45
+ const PLUGINS_LIST_TIMEOUT = 6e4;
46
+ const isValidPluginsList = function (pluginsList) {
47
+ return Array.isArray(pluginsList) && pluginsList.every(isPlainObj);
48
+ };
49
+ const normalizePluginsList = function (pluginsList) {
50
+ return Object.fromEntries(pluginsList.map(normalizePluginItem));
51
+ };
52
+ // `version` in `plugins.json` is the latest version.
53
+ // A `compatibility` array of objects can be added to specify conditions to
54
+ // apply different versions.
55
+ // `netlify/plugins` ensures that `compatibility`:
56
+ // - Has the proper shape.
57
+ // - Is sorted from the highest to lowest version.
58
+ // - Does not include the latest `version`.
59
+ const normalizePluginItem = function ({ package: packageName, version, compatibility = [] }) {
60
+ const versions = compatibility.length === 0 ? [{ version }] : compatibility;
61
+ const versionsA = versions.map(normalizeCompatVersion);
62
+ return [packageName, versionsA];
63
+ };
64
+ const normalizeCompatVersion = function ({ version, migrationGuide, featureFlag, ...otherProperties }) {
65
+ const conditions = Object.entries(otherProperties).filter(isCondition).map(normalizeCondition);
66
+ return { version, migrationGuide, featureFlag, conditions };
67
+ };
68
+ const isCondition = function ([type]) {
69
+ return type in CONDITIONS;
70
+ };
71
+ const normalizeCondition = function ([type, condition]) {
72
+ return { type, condition };
73
+ };
@@ -0,0 +1,50 @@
1
+ import { addErrorInfo } from '../error/info.js';
2
+ import { addPluginLoadErrorStatus } from '../status/load_error.js';
3
+ import { measureDuration } from '../time/main.js';
4
+ import { callChild } from './ipc.js';
5
+ // Retrieve all plugins steps
6
+ // Can use either a module name or a file path to the plugin.
7
+ export const loadPlugins = async function ({ pluginsOptions, childProcesses, packageJson, timers, logs, debug, verbose, }) {
8
+ return pluginsOptions.length === 0
9
+ ? { pluginsSteps: [], timers }
10
+ : await loadAllPlugins({ pluginsOptions, childProcesses, packageJson, timers, logs, debug, verbose });
11
+ };
12
+ const tLoadAllPlugins = async function ({ pluginsOptions, childProcesses, packageJson, logs, debug, verbose }) {
13
+ const pluginsSteps = await Promise.all(pluginsOptions.map((pluginOptions, index) => loadPlugin(pluginOptions, { childProcesses, index, packageJson, logs, debug, verbose })));
14
+ const pluginsStepsA = pluginsSteps.flat();
15
+ return { pluginsSteps: pluginsStepsA };
16
+ };
17
+ // Only performed if there are some plugins
18
+ const loadAllPlugins = measureDuration(tLoadAllPlugins, 'load_plugins');
19
+ // Retrieve plugin steps for one plugin.
20
+ // Do it by executing the plugin `load` event handler.
21
+ const loadPlugin = async function ({ packageName, pluginPackageJson, pluginPackageJson: { version } = {}, pluginPath, inputs, loadedFrom, origin }, { childProcesses, index, packageJson, logs, debug, verbose }) {
22
+ const { childProcess } = childProcesses[index];
23
+ const loadEvent = 'load';
24
+ try {
25
+ const { events } = await callChild({
26
+ childProcess,
27
+ eventName: 'load',
28
+ payload: { pluginPath, inputs, packageJson, verbose },
29
+ logs,
30
+ verbose: false,
31
+ });
32
+ const pluginSteps = events.map((event) => ({
33
+ event,
34
+ packageName,
35
+ loadedFrom,
36
+ origin,
37
+ pluginPackageJson,
38
+ childProcess,
39
+ }));
40
+ return pluginSteps;
41
+ }
42
+ catch (error) {
43
+ addErrorInfo(error, {
44
+ plugin: { packageName, pluginPackageJson },
45
+ location: { event: loadEvent, packageName, loadedFrom, origin },
46
+ });
47
+ addPluginLoadErrorStatus({ error, packageName, version, debug });
48
+ throw error;
49
+ }
50
+ };
@@ -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,19 @@
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
+ /**
6
+ * Load plugin's `manifest.yml`
7
+ */
8
+ export const useManifest = async function ({ packageName, loadedFrom, origin, inputs, }, { pluginDir, packageDir, pluginPackageJson, pluginPackageJson: { version }, debug, }) {
9
+ const manifestPath = await getManifestPath({ pluginDir, packageDir, packageName });
10
+ try {
11
+ const manifest = await loadManifest({ manifestPath, packageName, pluginPackageJson, loadedFrom, origin });
12
+ const inputsA = checkInputs({ inputs, manifest, packageName, pluginPackageJson, loadedFrom, origin });
13
+ return { manifest, inputs: inputsA };
14
+ }
15
+ catch (error) {
16
+ addPluginLoadErrorStatus({ error, packageName, version, debug });
17
+ throw error;
18
+ }
19
+ };
@@ -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,35 @@
1
+ import { execPath, version as currentVersion } from 'process';
2
+ import semver from 'semver';
3
+ import link from 'terminal-link';
4
+ import { logWarning, logWarningSubHeader } from '../log/logger.js';
5
+ /**
6
+ * This node version is minimum required to run the plugins code.
7
+ * If the users preferred Node.js version is below that we have to fall back to the system node version
8
+ */
9
+ const MINIMUM_REQUIRED_NODE_VERSION = '^14.14.0 || >=16.0.0';
10
+ /**
11
+ * Local plugins and `package.json`-installed plugins use user's preferred Node.js version if higher than our minimum
12
+ * supported version. Else default to the system Node version.
13
+ * Local and programmatic builds use `@netlify/build` Node.js version, which is
14
+ * usually the system's Node.js version.
15
+ * If the user Node version does not satisfy our supported engine range use our own system Node version
16
+ */
17
+ export const addPluginsNodeVersion = function ({ pluginsOptions, nodePath, userNodeVersion, logs }) {
18
+ const currentNodeVersion = semver.clean(currentVersion);
19
+ return pluginsOptions.map((pluginOptions) => addPluginNodeVersion({ pluginOptions, currentNodeVersion, userNodeVersion, nodePath, logs }));
20
+ };
21
+ const addPluginNodeVersion = function ({ pluginOptions, pluginOptions: { loadedFrom, packageName }, currentNodeVersion, userNodeVersion, nodePath, logs, }) {
22
+ // if the plugin is a local one and the users node version does not satisfy
23
+ // our minimum required node version log a warning as we will default to the system node version
24
+ if ((loadedFrom === 'local' || loadedFrom === 'package.json') &&
25
+ !semver.satisfies(userNodeVersion, MINIMUM_REQUIRED_NODE_VERSION)) {
26
+ logWarningSubHeader(logs, `Warning: ${packageName} will be executed with Node.js version ${currentNodeVersion}`);
27
+ logWarning(logs, ` The plugin cannot be executed with your defined Node.js version ${userNodeVersion}
28
+
29
+ Read more about our minimum required version in our ${link('forums announcement', 'https://answers.netlify.com/t/build-plugins-dropping-support-for-node-js-12/79421')}`);
30
+ }
31
+ return (loadedFrom === 'local' || loadedFrom === 'package.json') &&
32
+ semver.satisfies(userNodeVersion, MINIMUM_REQUIRED_NODE_VERSION)
33
+ ? { ...pluginOptions, nodePath, nodeVersion: userNodeVersion }
34
+ : { ...pluginOptions, nodePath: execPath, nodeVersion: currentNodeVersion };
35
+ };
@@ -0,0 +1,70 @@
1
+ import { dirname } from 'path';
2
+ import semver from 'semver';
3
+ import { addErrorInfo } from '../error/info.js';
4
+ import { installLocalPluginsDependencies } from '../install/local.js';
5
+ import { measureDuration } from '../time/main.js';
6
+ import { ROOT_PACKAGE_JSON } from '../utils/json.js';
7
+ import { getPackageJson } from '../utils/package.js';
8
+ import { useManifest } from './manifest/main.js';
9
+ import { resolvePluginsPath } from './resolve.js';
10
+ // Load core plugins and user plugins
11
+ const tGetPluginsOptions = async function ({ pluginsOptions, netlifyConfig: { plugins }, siteInfo, buildDir, nodePath, packageJson, userNodeVersion, mode, api, logs, debug, sendStatus, testOpts, featureFlags, }) {
12
+ const pluginsOptionsA = await resolvePluginsPath({
13
+ pluginsOptions,
14
+ siteInfo,
15
+ buildDir,
16
+ nodePath,
17
+ packageJson,
18
+ userNodeVersion,
19
+ mode,
20
+ api,
21
+ logs,
22
+ debug,
23
+ sendStatus,
24
+ testOpts,
25
+ featureFlags,
26
+ });
27
+ const pluginsOptionsB = await Promise.all(pluginsOptionsA.map((pluginOptions) => loadPluginFiles({ pluginOptions, debug })));
28
+ const pluginsOptionsC = pluginsOptionsB.filter(isNotRedundantCorePlugin);
29
+ await installLocalPluginsDependencies({ plugins, pluginsOptions: pluginsOptionsC, buildDir, mode, logs });
30
+ return { pluginsOptions: pluginsOptionsC };
31
+ };
32
+ export const getPluginsOptions = measureDuration(tGetPluginsOptions, 'get_plugins_options');
33
+ // Retrieve plugin's main file path.
34
+ // Then load plugin's `package.json` and `manifest.yml`.
35
+ const loadPluginFiles = async function ({ pluginOptions, pluginOptions: { pluginPath, nodeVersion, packageName }, debug, }) {
36
+ const pluginDir = dirname(pluginPath);
37
+ const { packageDir, packageJson: pluginPackageJson } = await getPackageJson(pluginDir);
38
+ // Ensure Node.js version is compatible with plugin's `engines.node`
39
+ const pluginNodeVersionRange = pluginPackageJson?.engines?.node;
40
+ if (pluginNodeVersionRange && !semver.satisfies(nodeVersion, pluginNodeVersionRange)) {
41
+ throwUserError(`The Node.js version is ${nodeVersion} but the plugin "${packageName}" requires ${pluginNodeVersionRange}`);
42
+ }
43
+ const { manifest, inputs } = await useManifest(pluginOptions, { pluginDir, packageDir, pluginPackageJson, debug });
44
+ return { ...pluginOptions, pluginDir, packageDir, pluginPackageJson, manifest, inputs };
45
+ };
46
+ // Core plugins can only be included once.
47
+ // For example, when testing core plugins, they might be included as local plugins,
48
+ // in which case they should not be included twice.
49
+ const isNotRedundantCorePlugin = function (pluginOptionsA, index, pluginsOptions) {
50
+ return (pluginOptionsA.loadedFrom !== 'core' ||
51
+ pluginsOptions.every((pluginOptionsB) => pluginOptionsA.manifest.name !== pluginOptionsB.manifest.name || pluginOptionsA === pluginOptionsB));
52
+ };
53
+ /**
54
+ * Retrieve information about @netlify/build when an error happens there and not
55
+ * in a plugin
56
+ */
57
+ export const getSpawnInfo = () => {
58
+ // we know that this package.json has a name as it's ours
59
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
60
+ const packageName = ROOT_PACKAGE_JSON.name;
61
+ return {
62
+ plugin: { packageName, pluginPackageJson: ROOT_PACKAGE_JSON },
63
+ location: { event: 'load', packageName, loadedFrom: 'core', origin: 'core' },
64
+ };
65
+ };
66
+ const throwUserError = function (message) {
67
+ const error = new Error(message);
68
+ addErrorInfo(error, { type: 'resolveConfig' });
69
+ throw error;
70
+ };
@@ -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
+ };