From b77cb56cd04ba633f204640ec0b16e1edd18a993 Mon Sep 17 00:00:00 2001 From: Paulo Castro <paulo@balena.io> Date: Wed, 4 Mar 2020 19:03:43 +0000 Subject: [PATCH 1/4] Fix occasionally missed command tracking request (oclif commands) Change-type: patch --- lib/hooks/prerun/track.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/hooks/prerun/track.ts b/lib/hooks/prerun/track.ts index 11722017..d5863ea4 100644 --- a/lib/hooks/prerun/track.ts +++ b/lib/hooks/prerun/track.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Balena Ltd. + * Copyright 2019-2020 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,12 @@ */ import { Hook } from '@oclif/config'; +let trackResolve: (result: Promise<any>) => void; + // note: trackPromise is subject to a Bluebird.timeout, defined in events.ts -export let trackPromise: PromiseLike<void>; +export const trackPromise = new Promise(resolve => { + trackResolve = resolve; +}); /** * This is an oclif 'prerun' hook. This hook runs after the command line is @@ -38,7 +42,7 @@ const hook: Hook<'prerun'> = async function(options) { // Intentionally do not await for the track promise here, in order to // run the command tracking and the command itself in parallel. - trackPromise = events.trackCommand(cmdSignature); + trackResolve(events.trackCommand(cmdSignature)); }; export default hook; From 36d3d1256ebd851e2edeab4380a97646126fb5ee Mon Sep 17 00:00:00 2001 From: Paulo Castro <paulo@balena.io> Date: Thu, 5 Mar 2020 16:24:00 +0000 Subject: [PATCH 2/4] Don't send the full command line to Sentry.io Resolves: #703 Change-type: patch --- lib/app-common.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/app-common.ts b/lib/app-common.ts index baaa5b78..b38fe781 100644 --- a/lib/app-common.ts +++ b/lib/app-common.ts @@ -58,7 +58,6 @@ function setupRaven() { Raven.setContext({ extra: { - args: process.argv, node_version: process.version, }, }); From d2df2c7b60e6024f3452c002184f7824f5b952f0 Mon Sep 17 00:00:00 2001 From: Paulo Castro <paulo@balena.io> Date: Wed, 4 Mar 2020 16:31:54 +0000 Subject: [PATCH 3/4] Fix occasional "CLI prints 'null' and exits" (replace old Raven/Sentry SDK) Resolves: #1523 Connects-to: #1333 Connects-to: #1193 Change-type: patch --- lib/app-common.ts | 35 +++++----- lib/errors.ts | 22 ++---- lib/events.ts | 61 +++++++++-------- lib/global.d.ts | 5 +- npm-shrinkwrap.json | 163 +++++++++++++++++++++++++++++++------------- package.json | 6 +- 6 files changed, 178 insertions(+), 114 deletions(-) diff --git a/lib/app-common.ts b/lib/app-common.ts index b38fe781..c59ec66e 100644 --- a/lib/app-common.ts +++ b/lib/app-common.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Balena Ltd. + * Copyright 2019-2020 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ * limitations under the License. */ +import * as packageJSON from '../package.json'; + class CliSettings { public readonly settings: any; constructor() { @@ -42,29 +44,26 @@ class CliSettings { /** * Sentry.io setup - * @see https://docs.sentry.io/clients/node/ + * @see https://docs.sentry.io/error-reporting/quickstart/?platform=node */ -function setupRaven() { - const Raven = require('raven'); - Raven.disableConsoleAlerts(); - Raven.config(require('./config').sentryDsn, { - captureUnhandledRejections: true, - autoBreadcrumbs: true, - release: require('../package.json').version, - }).install(function(_logged: any, error: Error) { - console.error(error); - process.exit(1); +async function setupSentry() { + const config = await import('./config'); + const Sentry = await import('@sentry/node'); + Sentry.init({ + dsn: config.sentryDsn, + release: packageJSON.version, }); - - Raven.setContext({ - extra: { + Sentry.configureScope(scope => { + scope.setExtras({ + is_pkg: !!(process as any).pkg, node_version: process.version, - }, + platform: process.platform, + }); }); } function checkNodeVersion() { - const validNodeVersions = require('../package.json').engines.node; + const validNodeVersions = packageJSON.engines.node; if (!require('semver').satisfies(process.version, validNodeVersions)) { const { stripIndent } = require('common-tags'); console.warn(stripIndent` @@ -243,7 +242,7 @@ export function setMaxListeners(maxListeners: number) { } export async function globalInit() { - setupRaven(); + await setupSentry(); checkNodeVersion(); configureBluebird(); diff --git a/lib/errors.ts b/lib/errors.ts index d8c8daf4..a4e01a01 100644 --- a/lib/errors.ts +++ b/lib/errors.ts @@ -1,5 +1,5 @@ /* -Copyright 2016-2019 Balena +Copyright 2016-2020 Balena Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,21 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as Bluebird from 'bluebird'; import { stripIndent } from 'common-tags'; import * as _ from 'lodash'; import * as os from 'os'; -import * as Raven from 'raven'; export class ExpectedError extends Error {} export class NotLoggedInError extends ExpectedError {} -const captureException = Bluebird.promisify<string, Error>( - Raven.captureException, - { context: Raven }, -); - function hasCode(error: any): error is Error & { code: string } { return error.code != null; } @@ -127,11 +120,10 @@ export async function handleError(error: any) { } // Report "unexpected" errors via Sentry.io - await captureException(error) - .timeout(1000) - .catch(function() { - // Ignore any errors (from error logging, or timeouts) - }) - // exit with the process.exitCode set earlier - .finally(() => process.exit()); + const Sentry = await import('@sentry/node'); + Sentry.captureException(error); + await Sentry.close(1000); + // Unhandled/unexpected error: ensure that the process terminates. + // The exit error code was set above through `process.exitCode`. + process.exit(); } diff --git a/lib/events.ts b/lib/events.ts index a5bd167d..5438de8b 100644 --- a/lib/events.ts +++ b/lib/events.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Balena Ltd. + * Copyright 2019-2020 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Promise = require('bluebird'); -import _ = require('lodash'); -import Mixpanel = require('mixpanel'); -import Raven = require('raven'); +import * as Sentry from '@sentry/node'; +import * as Bluebird from 'bluebird'; +import * as _ from 'lodash'; +import * as Mixpanel from 'mixpanel'; -import packageJSON = require('../package.json'); +import * as packageJSON from '../package.json'; import { getBalenaSdk } from './utils/lazy'; const getMixpanel = _.once<any>(() => { @@ -31,36 +31,43 @@ const getMixpanel = _.once<any>(() => { }); }); +/** + * Mixpanel.com analytics tracking (information on balena CLI usage). + * + * @param commandSignature A string like, for example: + * "push <applicationOrDevice>" + * That's literally so: "applicationOrDevice" is NOT replaced with the actual + * application ID or device ID. The purpose is to find out the most / least + * used command verbs, so we can focus our development effort where it is most + * beneficial to end users. + * + * The username and command signature are also added as extra context + * information in Sentry.io error reporting, for CLI debugging purposes + * (mainly unexpected/unhandled exceptions -- see also `lib/errors.ts`). + */ export function trackCommand(commandSignature: string) { const balena = getBalenaSdk(); - return Promise.props({ + return Bluebird.props({ balenaUrl: balena.settings.get('balenaUrl'), username: balena.auth.whoami().catchReturn(undefined), mixpanel: getMixpanel(), }) .then(({ username, balenaUrl, mixpanel }) => { - return Promise.try(() => { - Raven.mergeContext({ - user: { - id: username, - username, - }, - }); - // commandSignature is a string like, for example: - // "push <applicationOrDevice>" - // That's literally so: "applicationOrDevice" is NOT replaced with - // the actual application ID or device ID. The purpose is find out the - // most / least used command verbs, so we can focus our development - // effort where it is most beneficial to end users. - return mixpanel.track(`[CLI] ${commandSignature}`, { - distinct_id: username, - version: packageJSON.version, - node: process.version, - arch: process.arch, - balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com' - platform: process.platform, + Sentry.configureScope(scope => { + scope.setExtra('command', commandSignature); + scope.setUser({ + id: username, + username, }); }); + return mixpanel.track(`[CLI] ${commandSignature}`, { + distinct_id: username, + version: packageJSON.version, + node: process.version, + arch: process.arch, + balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com' + platform: process.platform, + }); }) .timeout(100) .catchReturn(undefined); diff --git a/lib/global.d.ts b/lib/global.d.ts index 5d702e1a..18a74982 100644 --- a/lib/global.d.ts +++ b/lib/global.d.ts @@ -1,6 +1,6 @@ /** * @license - * Copyright 2019 Balena Ltd. + * Copyright 2019-2020 Balena Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,9 @@ interface Dictionary<T> { } declare module '*/package.json' { + export const engines: { + node: string; + }; export const name: string; export const version: string; } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f1b82a9e..c96556f0 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -553,6 +553,113 @@ "resolved": "https://registry.npmjs.org/@resin.io/valid-email/-/valid-email-0.1.0.tgz", "integrity": "sha1-DnUwmoQ8AUqAqhSC+WmQYvL6UV0=" }, + "@sentry/apm": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@sentry/apm/-/apm-5.13.2.tgz", + "integrity": "sha512-Pv6PRVkcmmYYIT422gXm968F8YQyf5uN1RSHOFBjWsxI3Ke/uRgeEdIVKPDo78GklBfETyRN6GyLEZ555jRe6g==", + "requires": { + "@sentry/browser": "5.13.2", + "@sentry/hub": "5.13.2", + "@sentry/minimal": "5.13.2", + "@sentry/types": "5.13.2", + "@sentry/utils": "5.13.2", + "tslib": "^1.9.3" + } + }, + "@sentry/browser": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.13.2.tgz", + "integrity": "sha512-4MeauHs8Rf1c2FF6n84wrvA4LexEL1K/Tg3r+1vigItiqyyyYBx1sPjHGZeKeilgBi+6IEV5O8sy30QIrA/NsQ==", + "requires": { + "@sentry/core": "5.13.2", + "@sentry/types": "5.13.2", + "@sentry/utils": "5.13.2", + "tslib": "^1.9.3" + } + }, + "@sentry/core": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.13.2.tgz", + "integrity": "sha512-iB7CQSt9e0EJhSmcNOCjzJ/u7E7qYJ3mI3h44GO83n7VOmxBXKSvtUl9FpKFypbWrsdrDz8HihLgAZZoMLWpPA==", + "requires": { + "@sentry/hub": "5.13.2", + "@sentry/minimal": "5.13.2", + "@sentry/types": "5.13.2", + "@sentry/utils": "5.13.2", + "tslib": "^1.9.3" + } + }, + "@sentry/hub": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.13.2.tgz", + "integrity": "sha512-/U7yq3DTuRz8SRpZVKAaenW9sD2F5wbj12kDVPxPnGspyqhy0wBWKs9j0YJfBiDXMKOwp3HX964O3ygtwjnfAw==", + "requires": { + "@sentry/types": "5.13.2", + "@sentry/utils": "5.13.2", + "tslib": "^1.9.3" + } + }, + "@sentry/minimal": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.13.2.tgz", + "integrity": "sha512-VV0eA3HgrnN3mac1XVPpSCLukYsU+QxegbmpnZ8UL8eIQSZ/ZikYxagDNlZbdnmXHUpOEUeag2gxVntSCo5UcA==", + "requires": { + "@sentry/hub": "5.13.2", + "@sentry/types": "5.13.2", + "tslib": "^1.9.3" + } + }, + "@sentry/node": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.13.2.tgz", + "integrity": "sha512-LwNOUvc0+28jYfI0o4HmkDTEYdY3dWvSCnL5zggO12buon7Wc+jirXZbEQAx84HlXu7sGSjtKCTzUQOphv7sPw==", + "requires": { + "@sentry/apm": "5.13.2", + "@sentry/core": "5.13.2", + "@sentry/hub": "5.13.2", + "@sentry/types": "5.13.2", + "@sentry/utils": "5.13.2", + "cookie": "^0.3.1", + "https-proxy-agent": "^4.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "dependencies": { + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "requires": { + "agent-base": "5", + "debug": "4" + } + } + } + }, + "@sentry/types": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.13.2.tgz", + "integrity": "sha512-mgAEQyc77PYBnAjnslSXUz6aKgDlunlg2c2qSK/ivKlEkTgTWWW/dE76++qVdrqM8SupnqQoiXyPDL0wUNdB3g==" + }, + "@sentry/utils": { + "version": "5.13.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.13.2.tgz", + "integrity": "sha512-LwPQl6WRMKEnd16kg35HS3yE+VhBc8vN4+BBIlrgs7X0aoT+AbEd/sQLMisDgxNboCF44Ho3RCKtztiPb9blqg==", + "requires": { + "@sentry/types": "5.13.2", + "tslib": "^1.9.3" + } + }, "@sinonjs/commons": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.0.tgz", @@ -905,15 +1012,6 @@ "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", "dev": true }, - "@types/raven": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/@types/raven/-/raven-2.5.3.tgz", - "integrity": "sha512-k6vxiX5I6/GEqJS9mMYPVIgMJf/X26n09NfzuqBpdcEp684RIwpdrwCgSyJGuy8EaSG1wc2rFP7xVEAixPzw7Q==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/request": { "version": "2.48.4", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz", @@ -2850,11 +2948,6 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -3630,11 +3723,6 @@ } } }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" - }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -8523,6 +8611,11 @@ "es5-ext": "~0.10.2" } }, + "lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=" + }, "lzma-native": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-4.0.6.tgz", @@ -8697,16 +8790,6 @@ "chs": "^1.1.0" } }, - "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", - "requires": { - "charenc": "~0.0.1", - "crypt": "~0.0.1", - "is-buffer": "~1.1.1" - } - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -14814,25 +14897,6 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, - "raven": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/raven/-/raven-2.6.4.tgz", - "integrity": "sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw==", - "requires": { - "cookie": "0.3.1", - "md5": "^2.2.1", - "stack-trace": "0.0.10", - "timed-out": "4.0.1", - "uuid": "3.3.2" - }, - "dependencies": { - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - } - } - }, "raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", @@ -16884,7 +16948,8 @@ "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true }, "static-extend": { "version": "0.1.2", diff --git a/package.json b/package.json index 0602091a..35bcb05b 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,7 @@ "scripts": [ "node_modules/balena-sync/build/capitano/*.js", "node_modules/balena-sync/build/sync/*.js", - "node_modules/resin-compose-parse/build/schemas/*.json", - "node_modules/raven/lib/instrumentation/*.js" + "node_modules/resin-compose-parse/build/schemas/*.json" ], "assets": [ "build/**/*.js", @@ -121,7 +120,6 @@ "@types/nock": "^11.0.7", "@types/node": "^10.17.17", "@types/prettyjson": "0.0.29", - "@types/raven": "^2.5.3", "@types/request": "^2.48.4", "@types/rewire": "^2.5.28", "@types/rimraf": "^2.0.3", @@ -160,6 +158,7 @@ "@oclif/command": "^1.5.19", "@oclif/errors": "^1.2.2", "@resin.io/valid-email": "^0.1.0", + "@sentry/node": "^5.13.1", "@zeit/dockerignore": "0.0.3", "JSONStream": "^1.0.3", "ansi-escapes": "^2.0.0", @@ -221,7 +220,6 @@ "patch-package": "6.1.2", "prettyjson": "^1.1.3", "progress-stream": "^2.0.0", - "raven": "^2.5.0", "reconfix": "^0.1.0", "request": "^2.88.2", "resin-cli-form": "^2.0.1", From 5a806543057338d617dbddcaf965bd46fe168347 Mon Sep 17 00:00:00 2001 From: Paulo Castro <paulo@balena.io> Date: Thu, 5 Mar 2020 01:02:46 +0000 Subject: [PATCH 4/4] Avoid Sentry reporting of selected common "expected" errors Change-type: patch --- lib/errors.ts | 15 +++++++++++++-- lib/utils/remote-build.ts | 8 ++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/errors.ts b/lib/errors.ts index a4e01a01..49af2d2c 100644 --- a/lib/errors.ts +++ b/lib/errors.ts @@ -17,8 +17,9 @@ limitations under the License. import { stripIndent } from 'common-tags'; import * as _ from 'lodash'; import * as os from 'os'; +import { TypedError } from 'typed-error'; -export class ExpectedError extends Error {} +export class ExpectedError extends TypedError {} export class NotLoggedInError extends ExpectedError {} @@ -115,7 +116,17 @@ export async function handleError(error: any) { } printErrorMessage(message.join('\n')); - if (error instanceof ExpectedError) { + const expectedErrorREs = [ + /^BalenaApplicationNotFound:/, // balena-sdk + /^BalenaDeviceNotFound:/, // balena-sdk + /^Missing \w+$/, // Capitano's command line parsing error + /^Unexpected arguments?:/, // oclif's command line parsing error + ]; + + if ( + error instanceof ExpectedError || + expectedErrorREs.some(re => re.test(message[0])) + ) { return; } diff --git a/lib/utils/remote-build.ts b/lib/utils/remote-build.ts index b619092d..527163fb 100644 --- a/lib/utils/remote-build.ts +++ b/lib/utils/remote-build.ts @@ -22,12 +22,12 @@ import { RegistrySecrets } from 'resin-multibuild'; import * as Stream from 'stream'; import streamToPromise = require('stream-to-promise'); import { Pack } from 'tar-stream'; -import { TypedError } from 'typed-error'; -import Logger = require('./logger'); +import { ExpectedError } from '../errors'; import { exitWithExpectedError } from '../utils/patterns'; import { tarDirectory } from './compose'; import { getVisuals } from './lazy'; +import Logger = require('./logger'); const globalLogger = Logger.getLogger(); @@ -77,7 +77,7 @@ interface HeadlessBuilderMessage { releaseId?: number; } -export class RemoteBuildFailedError extends TypedError { +export class RemoteBuildFailedError extends ExpectedError { public constructor(message = 'Remote build failed') { super(message); } @@ -138,10 +138,10 @@ export async function startRemoteBuild(build: RemoteBuild): Promise<void> { stream.on('end', resolve); stream.on('error', reject); }).then(() => { + globalLogger.outputDeferredMessages(); if (build.hadError) { throw new RemoteBuildFailedError(); } - globalLogger.outputDeferredMessages(); }); }