@netlify/build 19.0.5 → 20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/build",
3
- "version": "19.0.5",
3
+ "version": "20.0.0",
4
4
  "description": "Netlify build module",
5
5
  "main": "src/core/main.js",
6
6
  "types": "types/index.d.ts",
@@ -36,8 +36,8 @@
36
36
  "ci",
37
37
  "plugins",
38
38
  "continuous-integration",
39
- "continuous-delivery",
40
39
  "continuous-deployment",
40
+ "continuous-delivery",
41
41
  "continuous-testing",
42
42
  "netlify-plugin",
43
43
  "netlify"
@@ -61,7 +61,7 @@
61
61
  "@netlify/plugin-edge-handlers": "^2.0.0",
62
62
  "@netlify/plugins-list": "^5.0.0",
63
63
  "@netlify/run-utils": "^3.0.0",
64
- "@netlify/zip-it-and-ship-it": "^5.0.0",
64
+ "@netlify/zip-it-and-ship-it": "^5.1.0",
65
65
  "@sindresorhus/slugify": "^1.1.0",
66
66
  "ansi-escapes": "^4.3.2",
67
67
  "chalk": "^4.1.2",
@@ -5,7 +5,8 @@ const safeJsonStringify = require('safe-json-stringify')
5
5
  const { CUSTOM_ERROR_KEY } = require('./info')
6
6
 
7
7
  // Retrieve error information from child process and re-build it in current
8
- // process. We need this since errors are not JSON-serializable.
8
+ // process. We need this since errors static properties are not kept by
9
+ // `v8.serialize()`.
9
10
  const jsonToError = function ({ name, message, stack, ...errorProps }) {
10
11
  // eslint-disable-next-line unicorn/error-message
11
12
  const error = new Error('')
@@ -35,8 +36,6 @@ const assignErrorProp = function (error, name, value) {
35
36
  }
36
37
 
37
38
  // Inverse of `jsonToError()`.
38
- // IPC uses JSON for the payload. We ensure there are not circular references
39
- // as those would make the message sending fail.
40
39
  const errorToJson = function ({ name, message, stack, [CUSTOM_ERROR_KEY]: customError, ...errorProps }) {
41
40
  return {
42
41
  ...safeJsonStringify.ensureProperties(errorProps),
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const filterObj = require('filter-obj')
4
+
3
5
  const { getLogic } = require('./logic')
4
6
  const { registerTypeScript } = require('./typescript')
5
7
  const { validatePlugin } = require('./validate')
@@ -9,27 +11,22 @@ const { validatePlugin } = require('./validate')
9
11
  // This also validates the plugin.
10
12
  // Do it when parent requests it using the `load` event.
11
13
  // Also figure out the list of plugin steps. This is also passed to the parent.
12
- const load = function ({ pluginPath, inputs, packageJson }) {
14
+ const load = async function ({ pluginPath, inputs, packageJson }) {
13
15
  const tsNodeService = registerTypeScript(pluginPath)
14
- const logic = getLogic({ pluginPath, inputs, tsNodeService })
16
+ const logic = await getLogic({ pluginPath, inputs, tsNodeService })
15
17
 
16
18
  validatePlugin(logic)
17
19
 
18
- const pluginSteps = getPluginSteps(logic)
20
+ const methods = filterObj(logic, isEventHandler)
21
+ const events = Object.keys(methods)
19
22
 
20
23
  // Context passed to every event handler
21
- const context = { pluginSteps, inputs, packageJson }
22
-
23
- return { pluginSteps, context }
24
- }
24
+ const context = { methods, inputs, packageJson }
25
25
 
26
- const getPluginSteps = function (logic) {
27
- return Object.entries(logic)
28
- .filter(isEventHandler)
29
- .map(([event, method]) => ({ event, method }))
26
+ return { events, context }
30
27
  }
31
28
 
32
- const isEventHandler = function ([, value]) {
29
+ const isEventHandler = function (event, value) {
33
30
  return typeof value === 'function'
34
31
  }
35
32
 
@@ -1,22 +1,43 @@
1
1
  'use strict'
2
2
 
3
+ const { pathToFileURL } = require('url')
4
+
3
5
  const { addTsErrorInfo } = require('./typescript')
4
6
 
5
7
  // Require the plugin file and fire its top-level function.
6
8
  // The returned object is the `logic` which includes all event handlers.
7
- const getLogic = function ({ pluginPath, inputs, tsNodeService }) {
8
- const logic = requireLogic(pluginPath, tsNodeService)
9
+ const getLogic = async function ({ pluginPath, inputs, tsNodeService }) {
10
+ const logic = await importLogic(pluginPath, tsNodeService)
9
11
  const logicA = loadLogic({ logic, inputs })
10
12
  return logicA
11
13
  }
12
14
 
13
- const requireLogic = function (pluginPath, tsNodeService) {
15
+ const importLogic = async function (pluginPath, tsNodeService) {
14
16
  try {
15
- // eslint-disable-next-line node/global-require, import/no-dynamic-require
16
- return require(pluginPath)
17
+ // `ts-node` is not available programmatically for pure ES modules yet,
18
+ // which is currently making it impossible for local plugins to use both
19
+ // pure ES modules and TypeScript.
20
+ if (tsNodeService !== undefined) {
21
+ // eslint-disable-next-line node/global-require, import/no-dynamic-require
22
+ return require(pluginPath)
23
+ }
24
+
25
+ // `pluginPath` is an absolute file path but `import()` needs URLs.
26
+ // Converting those with `pathToFileURL()` is needed especially on Windows
27
+ // where the drive letter would not work with `import()`.
28
+ const returnValue = await import(pathToFileURL(pluginPath))
29
+ // Plugins should use named exports, but we still support default exports
30
+ // for backward compatibility with CommonJS
31
+ return returnValue.default === undefined ? returnValue : returnValue.default
17
32
  } catch (error) {
18
33
  addTsErrorInfo(error, tsNodeService)
19
- error.message = `Could not import plugin:\n${error.message}`
34
+ // We must change `error.stack` instead of `error.message` because some
35
+ // errors thrown from `import()` access `error.stack` before throwing.
36
+ // `error.stack` is lazily instantiated by Node.js, so changing
37
+ // `error.message` afterwards would not modify `error.stack`. Therefore, the
38
+ // resulting stack trace, which is printed in the build logs, would not
39
+ // include the additional message prefix.
40
+ error.stack = `Could not import plugin:\n${error.stack}`
20
41
  throw error
21
42
  }
22
43
  }
@@ -6,11 +6,8 @@ const { cloneNetlifyConfig, getConfigMutations } = require('./diff')
6
6
  const { getUtils } = require('./utils')
7
7
 
8
8
  // Run a specific plugin event handler
9
- const run = async function (
10
- { event, error, constants, envChanges, netlifyConfig },
11
- { pluginSteps, inputs, packageJson },
12
- ) {
13
- const { method } = pluginSteps.find((pluginStep) => pluginStep.event === event)
9
+ const run = async function ({ event, error, constants, envChanges, netlifyConfig }, { methods, inputs, packageJson }) {
10
+ const method = methods[event]
14
11
  const runState = {}
15
12
  const utils = getUtils({ event, constants, runState })
16
13
  const netlifyConfigCopy = cloneNetlifyConfig(netlifyConfig)
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const isPlainObj = require('is-plain-obj')
4
-
5
3
  const { addErrorInfo } = require('../../error/info')
6
4
  const { serializeArray } = require('../../log/serialize')
7
5
  const { EVENTS } = require('../events')
@@ -9,7 +7,9 @@ const { EVENTS } = require('../events')
9
7
  // Validate the shape of a plugin return value
10
8
  const validatePlugin = function (logic) {
11
9
  try {
12
- if (!isPlainObj(logic)) {
10
+ // This validation must work with the return value of `import()` which has
11
+ // a `Module` prototype, not `Object`
12
+ if (typeof logic !== 'object' || logic === null) {
13
13
  throw new Error('Plugin must be an object or a function')
14
14
  }
15
15
 
@@ -113,10 +113,9 @@ const sendEventToParent = async function (callId, payload) {
113
113
  await promisify(process.send.bind(process))([callId, payload])
114
114
  }
115
115
 
116
- // Errors are not serializable through `child_process` `ipc` so we need to
117
- // convert from/to plain objects.
118
- // TODO: use `child_process.spawn()` `serialization: 'advanced'` option after
119
- // dropping support for Node.js <=12.6.0
116
+ // Error static properties are not serializable through `child_process`
117
+ // (which uses `v8.serialize()` under the hood) so we need to convert from/to
118
+ // plain objects.
120
119
  const serializePayload = function ({ error = {}, error: { name } = {}, ...payload }) {
121
120
  if (name === undefined) {
122
121
  return payload
@@ -37,8 +37,8 @@ const loadPlugin = async function (
37
37
  const loadEvent = 'load'
38
38
 
39
39
  try {
40
- const { pluginSteps } = await callChild(childProcess, 'load', { pluginPath, inputs, packageJson })
41
- const pluginStepsA = pluginSteps.map(({ event }) => ({
40
+ const { events } = await callChild(childProcess, 'load', { pluginPath, inputs, packageJson })
41
+ const pluginSteps = events.map((event) => ({
42
42
  event,
43
43
  packageName,
44
44
  loadedFrom,
@@ -46,7 +46,7 @@ const loadPlugin = async function (
46
46
  pluginPackageJson,
47
47
  childProcess,
48
48
  }))
49
- return pluginStepsA
49
+ return pluginSteps
50
50
  } catch (error) {
51
51
  addErrorInfo(error, {
52
52
  plugin: { packageName, pluginPackageJson },
@@ -39,6 +39,7 @@ const startPlugin = async function ({ pluginDir, nodePath, buildDir, childEnv })
39
39
  execPath: nodePath,
40
40
  env: childEnv,
41
41
  extendEnv: false,
42
+ serialization: 'advanced',
42
43
  })
43
44
 
44
45
  try {
@@ -14,7 +14,7 @@ interface Redirect {
14
14
  signed?: string
15
15
  query?: Partial<Record<string, string>>
16
16
  headers?: Partial<Record<string, string>>
17
- conditions?: Record<'language' | 'role' | 'country', readonly string[]>
17
+ conditions?: Partial<Record<'Language' | 'Role' | 'Country' | 'Cookie', readonly string[]>>
18
18
  }
19
19
 
20
20
  interface Header {