Repository: netlify/build
Pull request #1814 (2020-09-15 12:30:13)

Fix exit code of build cancellation
The current `master` includes what happened to be a breaking change: cancelling the build with `utils.build.cancelBuild()` resulting in an exit code `0`. It turns out the buildbot relies on the exit code not being `0` in that case.

This PR fixes it by using exit code `0` for success, `1` for build cancellation, then incrementing all the other exit codes. All those exit codes have just been introduced, and not released to production yet, so this is not a breaking change.


Patch:
diff --git a/packages/build/src/core/main.js b/packages/build/src/core/main.js
index f6da2bec8..8737fb21f 100644
--- a/packages/build/src/core/main.js
+++ b/packages/build/src/core/main.js
@@ -40,7 +40,7 @@ const { getSeverity } = require('./severity')
  *
  * @returns {object} buildResult
  * @returns {boolean} buildResult.success - Whether build succeeded or failed
- * @returns {string} buildResult.severity - Build success/failure status among: 'none', 'info' (user error), 'warning' (plugin error), 'error' (system error)
+ * @returns {number} buildResult.severityCode - Build success/failure status among: 0 (success), 1 (build cancelled), 2 (user error), 3 (plugin error), 4 (system error). Can be used as exit code.
  * @returns {string[]} buildResult.logs - When using the `buffer` option, all log messages
  */
 const build = async function(flags = {}) {
@@ -74,7 +74,7 @@ const build = async function(flags = {}) {
       testOpts,
       statsdOpts,
     })
-    const { success, severityCode } = getSeverity('none')
+    const { success, severityCode } = getSeverity('success')
     return { success, severityCode, logs }
   } catch (error) {
     const { severity } = await handleBuildError(error, errorParams)
diff --git a/packages/build/src/core/severity.js b/packages/build/src/core/severity.js
index a287854ca..82c9cdcad 100644
--- a/packages/build/src/core/severity.js
+++ b/packages/build/src/core/severity.js
@@ -7,12 +7,13 @@ const getSeverity = function(severity = FALLBACK_SEVERITY) {
 }
 
 const SEVERITY_CODES = {
-  none: 0,
-  info: 1,
-  warning: 2,
-  error: 3,
+  success: 0,
+  none: 1,
+  info: 2,
+  warning: 3,
+  error: 4,
 }
-const SUCCESS_SEVERITY = 'none'
+const SUCCESS_SEVERITY = 'success'
 // Indicates a bug in our codebase
 const FALLBACK_SEVERITY = 'error'
 
diff --git a/packages/build/src/error/type.js b/packages/build/src/error/type.js
index 88afae75b..4712f8c72 100644
--- a/packages/build/src/error/type.js
+++ b/packages/build/src/error/type.js
@@ -20,6 +20,7 @@ const getTypeInfo = function({ type }) {
 //        Used when `error.stack` is not being correct due to the error being
 //        passed between different processes.
 //  - `severity`: error severity (also used by Bugsnag):
+//      - `success`: build success
 //      - `none`: not an error, e.g. build cancellation
 //      - `info`: user error
 //      - `warning`: plugin author error, or possible system error


Test patch:
diff --git a/packages/build/tests/core/tests.js b/packages/build/tests/core/tests.js
index d59b0aca2..249e4167b 100644
--- a/packages/build/tests/core/tests.js
+++ b/packages/build/tests/core/tests.js
@@ -23,19 +23,19 @@ test('Exit code is 0 on success', async t => {
   t.is(exitCode, 0)
 })
 
-test('Exit code is 0 on build cancellation', async t => {
+test('Exit code is 1 on build cancellation', async t => {
   const { exitCode } = await runFixture(t, 'cancel', { useBinary: true, snapshot: false })
-  t.is(exitCode, 0)
+  t.is(exitCode, 1)
 })
 
-test('Exit code is 1 on user error', async t => {
+test('Exit code is 2 on user error', async t => {
   const { exitCode } = await runFixture(t, '', { flags: { config: '/invalid' }, useBinary: true, snapshot: false })
-  t.is(exitCode, 1)
+  t.is(exitCode, 2)
 })
 
-test('Exit code is 2 on plugin error', async t => {
+test('Exit code is 3 on plugin error', async t => {
   const { exitCode } = await runFixture(t, 'plugin_error', { useBinary: true, snapshot: false })
-  t.is(exitCode, 2)
+  t.is(exitCode, 3)
 })
 
 test('Success is true on success', async t => {
@@ -45,11 +45,11 @@ test('Success is true on success', async t => {
   t.true(success)
 })
 
-test('Success is true on build cancellation', async t => {
+test('Success is false on build cancellation', async t => {
   const {
     returnValue: { success },
   } = await runFixture(t, 'cancel', { programmatic: true, snapshot: false })
-  t.true(success)
+  t.false(success)
 })
 
 test('Success is false on failure', async t => {
@@ -66,25 +66,25 @@ test('severityCode is 0 on success', async t => {
   t.is(severityCode, 0)
 })
 
-test('severityCode is 0 on build cancellation', async t => {
+test('severityCode is 1 on build cancellation', async t => {
   const {
     returnValue: { severityCode },
   } = await runFixture(t, 'cancel', { programmatic: true, snapshot: false })
-  t.is(severityCode, 0)
+  t.is(severityCode, 1)
 })
 
-test('severityCode is 1 on user error', async t => {
+test('severityCode is 2 on user error', async t => {
   const {
     returnValue: { severityCode },
   } = await runFixture(t, '', { flags: { config: '/invalid' }, programmatic: true, snapshot: false })
-  t.is(severityCode, 1)
+  t.is(severityCode, 2)
 })
 
-test('severityCode is 2 on plugin error', async t => {
+test('severityCode is 3 on plugin error', async t => {
   const {
     returnValue: { severityCode },
   } = await runFixture(t, 'plugin_error', { programmatic: true, snapshot: false })
-  t.is(severityCode, 2)
+  t.is(severityCode, 3)
 })
 
 test('--cwd', async t => {