From 865e7a15f0ef717c7b65a7b9103e6138882cab4f Mon Sep 17 00:00:00 2001 From: Lukas Holzer Date: Wed, 19 Apr 2023 12:54:25 +0200 Subject: [PATCH] feat: add bugsnag reporting to the CLI application (#5640) * feat: add bugsnag reporting to the CLI application * chore: only report error if it exits --- .github/workflows/unit-tests.yml | 1 + functions/error-reporting.ts | 43 ++++++++ netlify.toml | 6 ++ package-lock.json | 151 +++++++++++++++------------ package.json | 2 + src/utils/command-helpers.mjs | 5 +- src/utils/execa.mjs | 4 + src/utils/telemetry/index.mjs | 1 + src/utils/telemetry/report-error.mjs | 44 ++++++++ src/utils/telemetry/request.mjs | 16 ++- src/utils/telemetry/telemetry.mjs | 8 +- src/utils/telemetry/utils.mjs | 7 ++ 12 files changed, 213 insertions(+), 75 deletions(-) create mode 100755 functions/error-reporting.ts create mode 100644 src/utils/telemetry/report-error.mjs create mode 100644 src/utils/telemetry/utils.mjs diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 4fcbb1eb601..af1d47b5636 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -14,6 +14,7 @@ jobs: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] node-version: ['14.18.0', '*'] + fail-fast: false steps: # Sets an output parameter if this is a release PR - name: Check for release diff --git a/functions/error-reporting.ts b/functions/error-reporting.ts new file mode 100755 index 00000000000..226a78d19c0 --- /dev/null +++ b/functions/error-reporting.ts @@ -0,0 +1,43 @@ +import process from 'process' + +import Bugsnag from '@bugsnag/js' +import { Handler } from '@netlify/functions' + +Bugsnag.start({ + apiKey: `${process.env.NETLIFY_BUGSNAG_API_KEY}`, +}) + +export const handler: Handler = async ({ body }) => { + try { + if (typeof body !== 'string') { + return { statusCode: 200 } + } + const { + cause, + cliVersion, + message, + name, + nodejsVersion, + osName, + severity = 'error', + stack, + user, + } = JSON.parse(body) + Bugsnag.notify({ name, message, stack, cause }, (event) => { + event.setUser(user.id, user.email, user.name) + event.severity = severity + event.device = { + osName, + runtimeVersions: { + nodejs: nodejsVersion, + cli: cliVersion, + }, + } + }) + } catch (error) { + Bugsnag.notify(error) + } + return { + statusCode: 200, + } +} diff --git a/netlify.toml b/netlify.toml index bd246877932..16d6ee59fc7 100644 --- a/netlify.toml +++ b/netlify.toml @@ -25,6 +25,12 @@ to = "/.netlify/functions/telemetry" status = 200 force = true +[[redirects]] + # Bugsnag link + from = "/report-error" + to = "/.netlify/functions/error-reporting" + status = 200 + force = true [[redirects]] # Old CLI download links. from = "/download/latest/mac" diff --git a/package-lock.json b/package-lock.json index 456115977fc..66c8cf5141b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@bugsnag/js": "^7.20.0", "@fastify/static": "^6.6.0", "@netlify/build": "^29.9.2", "@netlify/config": "^20.3.7", @@ -124,6 +125,7 @@ "devDependencies": { "@babel/preset-react": "^7.12.13", "@netlify/eslint-config-node": "^7.0.0", + "@netlify/functions": "^1.4.0", "@types/fs-extra": "^11.0.1", "@types/prettyjson": "^0.0.30", "@vitest/coverage-c8": "^0.30.1", @@ -695,17 +697,17 @@ "dev": true }, "node_modules/@bugsnag/browser": { - "version": "7.16.2", - "resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.16.2.tgz", - "integrity": "sha512-iBbAmjTDe0I6WPTHi3wIcmKu3ykydtT6fc8atJA65rzgDLMlTM1Wnwz4Ny1cn0bVouLGa48BRiOJ27Rwy7QRYA==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.20.0.tgz", + "integrity": "sha512-LzZWI6q5cWYQSXvfJDcSl287d2xXESVn0L20lK+K5nwo/jXcK9IVZr9L+CYZ40HVXaC9jOmQbqZ18hsbO2QNIw==", "dependencies": { - "@bugsnag/core": "^7.16.1" + "@bugsnag/core": "^7.19.0" } }, "node_modules/@bugsnag/core": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.16.1.tgz", - "integrity": "sha512-zuBnL7B329VldItRqhXYrp1hjmjZnltJwNXMysi9WtY4t29WKk5LVwgWb1mPM9clJ0FoObZ7kvvQMUTKh3ezFQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.19.0.tgz", + "integrity": "sha512-2KGwdaLD9PhR7Wk7xPi3jGuGsKTatc/28U4TOZIDU3CgC2QhGjubwiXSECel5gwxhZ3jACKcMKSV2ovHhv1NrA==", "dependencies": { "@bugsnag/cuid": "^3.0.0", "@bugsnag/safe-json-stringify": "^6.0.0", @@ -715,25 +717,25 @@ } }, "node_modules/@bugsnag/cuid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.0.0.tgz", - "integrity": "sha512-LOt8aaBI+KvOQGneBtpuCz3YqzyEAehd1f3nC5yr9TIYW1+IzYKa2xWS4EiMz5pPOnRPHkyyS5t/wmSmN51Gjg==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.0.2.tgz", + "integrity": "sha512-cIwzC93r3PQ/INeuwtZwkZIG2K8WWN0rRLZQhu+mr48Ay+i6sEki4GYfTsflse7hZ1BeDWrNb/Q9vgY3B31xHQ==" }, "node_modules/@bugsnag/js": { - "version": "7.16.2", - "resolved": "https://registry.npmjs.org/@bugsnag/js/-/js-7.16.2.tgz", - "integrity": "sha512-AzV0PtG3SZt+HnA2JmRJeI60aDNZsIJbEEAZIWZeATvWBt5RdVdsWKllM1SkTvURfxfdAVd4Xry3BgVrh8nEbg==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@bugsnag/js/-/js-7.20.0.tgz", + "integrity": "sha512-lhUUSOveE8fP10RagAINqBmuH+eoOpyUOiTN1WRkjHUevWG0LZjRRUWEGN3AA+ZyTphmC6ljd2qE3/64qfOSGQ==", "dependencies": { - "@bugsnag/browser": "^7.16.2", - "@bugsnag/node": "^7.16.2" + "@bugsnag/browser": "^7.20.0", + "@bugsnag/node": "^7.19.0" } }, "node_modules/@bugsnag/node": { - "version": "7.16.2", - "resolved": "https://registry.npmjs.org/@bugsnag/node/-/node-7.16.2.tgz", - "integrity": "sha512-V5pND701cIYGzjjTwt0tuvAU1YyPB9h7vo5F/DzrDHRPmCINA/oVbc0Twco87knc2VPe8ntGFqTicTY65iOWzg==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@bugsnag/node/-/node-7.19.0.tgz", + "integrity": "sha512-c4snyxx5d/fsMogmgehFBGc//daH6+4XCplia4zrEQYltjaQ+l8ud0dPx623DgJl/2j1+2zlRc7y7IHSd7Gm5w==", "dependencies": { - "@bugsnag/core": "^7.16.1", + "@bugsnag/core": "^7.19.0", "byline": "^5.0.0", "error-stack-parser": "^2.0.2", "iserror": "^0.0.2", @@ -4126,6 +4128,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@netlify/functions": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-1.4.0.tgz", + "integrity": "sha512-gy7ULTIRroc2/jyFVGx1djCmmBMVisIwrvkqggq5B6iDcInRSy2Tpkm+V5C63hKJVkNRskKWtLQKm9ecCaQTjA==", + "dev": true, + "dependencies": { + "is-promise": "^4.0.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, "node_modules/@netlify/functions-utils": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@netlify/functions-utils/-/functions-utils-5.2.2.tgz", @@ -8059,7 +8073,7 @@ "node_modules/byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", + "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==", "engines": { "node": ">=0.10.0" } @@ -11022,11 +11036,11 @@ } }, "node_modules/error-stack-parser": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.7.tgz", - "integrity": "sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "dependencies": { - "stackframe": "^1.1.1" + "stackframe": "^1.3.4" } }, "node_modules/es-abstract": { @@ -15423,7 +15437,7 @@ "node_modules/iserror": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/iserror/-/iserror-0.0.2.tgz", - "integrity": "sha1-vVNFH+L2aLnyQCwZZnh6qix8C/U=" + "integrity": "sha512-oKGGrFVaWwETimP3SiWwjDeY27ovZoyZPHtxblC4hCq9fXxed/jasx+ATWFFjCVSRZng8VTMsN1nDnGo6zMBSw==" }, "node_modules/isexe": { "version": "2.0.0", @@ -21509,11 +21523,11 @@ } }, "node_modules/stack-generator": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", - "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", "dependencies": { - "stackframe": "^1.1.1" + "stackframe": "^1.3.4" } }, "node_modules/stack-trace": { @@ -21552,9 +21566,9 @@ "dev": true }, "node_modules/stackframe": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", - "integrity": "sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg==" + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, "node_modules/static-extend": { "version": "0.1.2", @@ -24947,17 +24961,17 @@ "dev": true }, "@bugsnag/browser": { - "version": "7.16.2", - "resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.16.2.tgz", - "integrity": "sha512-iBbAmjTDe0I6WPTHi3wIcmKu3ykydtT6fc8atJA65rzgDLMlTM1Wnwz4Ny1cn0bVouLGa48BRiOJ27Rwy7QRYA==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@bugsnag/browser/-/browser-7.20.0.tgz", + "integrity": "sha512-LzZWI6q5cWYQSXvfJDcSl287d2xXESVn0L20lK+K5nwo/jXcK9IVZr9L+CYZ40HVXaC9jOmQbqZ18hsbO2QNIw==", "requires": { - "@bugsnag/core": "^7.16.1" + "@bugsnag/core": "^7.19.0" } }, "@bugsnag/core": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.16.1.tgz", - "integrity": "sha512-zuBnL7B329VldItRqhXYrp1hjmjZnltJwNXMysi9WtY4t29WKk5LVwgWb1mPM9clJ0FoObZ7kvvQMUTKh3ezFQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@bugsnag/core/-/core-7.19.0.tgz", + "integrity": "sha512-2KGwdaLD9PhR7Wk7xPi3jGuGsKTatc/28U4TOZIDU3CgC2QhGjubwiXSECel5gwxhZ3jACKcMKSV2ovHhv1NrA==", "requires": { "@bugsnag/cuid": "^3.0.0", "@bugsnag/safe-json-stringify": "^6.0.0", @@ -24967,25 +24981,25 @@ } }, "@bugsnag/cuid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.0.0.tgz", - "integrity": "sha512-LOt8aaBI+KvOQGneBtpuCz3YqzyEAehd1f3nC5yr9TIYW1+IzYKa2xWS4EiMz5pPOnRPHkyyS5t/wmSmN51Gjg==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@bugsnag/cuid/-/cuid-3.0.2.tgz", + "integrity": "sha512-cIwzC93r3PQ/INeuwtZwkZIG2K8WWN0rRLZQhu+mr48Ay+i6sEki4GYfTsflse7hZ1BeDWrNb/Q9vgY3B31xHQ==" }, "@bugsnag/js": { - "version": "7.16.2", - "resolved": "https://registry.npmjs.org/@bugsnag/js/-/js-7.16.2.tgz", - "integrity": "sha512-AzV0PtG3SZt+HnA2JmRJeI60aDNZsIJbEEAZIWZeATvWBt5RdVdsWKllM1SkTvURfxfdAVd4Xry3BgVrh8nEbg==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@bugsnag/js/-/js-7.20.0.tgz", + "integrity": "sha512-lhUUSOveE8fP10RagAINqBmuH+eoOpyUOiTN1WRkjHUevWG0LZjRRUWEGN3AA+ZyTphmC6ljd2qE3/64qfOSGQ==", "requires": { - "@bugsnag/browser": "^7.16.2", - "@bugsnag/node": "^7.16.2" + "@bugsnag/browser": "^7.20.0", + "@bugsnag/node": "^7.19.0" } }, "@bugsnag/node": { - "version": "7.16.2", - "resolved": "https://registry.npmjs.org/@bugsnag/node/-/node-7.16.2.tgz", - "integrity": "sha512-V5pND701cIYGzjjTwt0tuvAU1YyPB9h7vo5F/DzrDHRPmCINA/oVbc0Twco87knc2VPe8ntGFqTicTY65iOWzg==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@bugsnag/node/-/node-7.19.0.tgz", + "integrity": "sha512-c4snyxx5d/fsMogmgehFBGc//daH6+4XCplia4zrEQYltjaQ+l8ud0dPx623DgJl/2j1+2zlRc7y7IHSd7Gm5w==", "requires": { - "@bugsnag/core": "^7.16.1", + "@bugsnag/core": "^7.19.0", "byline": "^5.0.0", "error-stack-parser": "^2.0.2", "iserror": "^0.0.2", @@ -27196,6 +27210,15 @@ } } }, + "@netlify/functions": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-1.4.0.tgz", + "integrity": "sha512-gy7ULTIRroc2/jyFVGx1djCmmBMVisIwrvkqggq5B6iDcInRSy2Tpkm+V5C63hKJVkNRskKWtLQKm9ecCaQTjA==", + "dev": true, + "requires": { + "is-promise": "^4.0.0" + } + }, "@netlify/functions-utils": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@netlify/functions-utils/-/functions-utils-5.2.2.tgz", @@ -30106,7 +30129,7 @@ "byline": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" + "integrity": "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==" }, "bytes": { "version": "3.1.2", @@ -32404,11 +32427,11 @@ } }, "error-stack-parser": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.7.tgz", - "integrity": "sha512-chLOW0ZGRf4s8raLrDxa5sdkvPec5YdvwbFnqJme4rk0rFajP8mPtrDL1+I+CwrQDCjswDA5sREX7jYQDQs9vA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "requires": { - "stackframe": "^1.1.1" + "stackframe": "^1.3.4" } }, "es-abstract": { @@ -35612,7 +35635,7 @@ "iserror": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/iserror/-/iserror-0.0.2.tgz", - "integrity": "sha1-vVNFH+L2aLnyQCwZZnh6qix8C/U=" + "integrity": "sha512-oKGGrFVaWwETimP3SiWwjDeY27ovZoyZPHtxblC4hCq9fXxed/jasx+ATWFFjCVSRZng8VTMsN1nDnGo6zMBSw==" }, "isexe": { "version": "2.0.0", @@ -40221,11 +40244,11 @@ } }, "stack-generator": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", - "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", "requires": { - "stackframe": "^1.1.1" + "stackframe": "^1.3.4" } }, "stack-trace": { @@ -40257,9 +40280,9 @@ "dev": true }, "stackframe": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", - "integrity": "sha512-h88QkzREN/hy8eRdyNhhsO7RSJ5oyTqxxmmn0dzBIMUclZsjpfmrsg81vp8mjjAs2vAZ72nyWxRUwSwmh0e4xg==" + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, "static-extend": { "version": "0.1.2", diff --git a/package.json b/package.json index 127445c8a1c..b2ca3f991e4 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "prettier": "--ignore-path .eslintignore --loglevel=warn \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,yml,json,html}\" \"*.{mjs,cjs,js,yml,json,html}\" \".*.{mjs,cjs,js,yml,json,html}\" \"!CHANGELOG.md\" \"!**/*/package-lock.json\" \"!.github/**/*.md\"" }, "dependencies": { + "@bugsnag/js": "^7.20.0", "@fastify/static": "^6.6.0", "@netlify/build": "^29.9.2", "@netlify/config": "^20.3.7", @@ -186,6 +187,7 @@ "devDependencies": { "@babel/preset-react": "^7.12.13", "@netlify/eslint-config-node": "^7.0.0", + "@netlify/functions": "^1.4.0", "@types/fs-extra": "^11.0.1", "@types/prettyjson": "^0.0.30", "@vitest/coverage-c8": "^0.30.1", diff --git a/src/utils/command-helpers.mjs b/src/utils/command-helpers.mjs index 48a94034331..3c20cf0f9fe 100644 --- a/src/utils/command-helpers.mjs +++ b/src/utils/command-helpers.mjs @@ -16,6 +16,7 @@ import { clearSpinner, startSpinner } from '../lib/spinner.mjs' import getGlobalConfig from './get-global-config.mjs' import getPackageJson from './get-package-json.mjs' +import { reportError } from './telemetry/report-error.mjs' /** The parsed process argv without the binary only arguments and flags */ const argv = process.argv.slice(2) @@ -179,14 +180,16 @@ export const warn = (message = '') => { */ export const error = (message = '', options = {}) => { const err = message instanceof Error ? message : new Error(message) + if (options.exit === false) { const bang = chalk.red(BANG) if (process.env.DEBUG) { - process.stderr.write(` ${bang} Warning: ${err.stack.split('\n').join(`\n ${bang} `)}\n`) + process.stderr.write(` ${bang} Warning: ${err.stack?.split('\n').join(`\n ${bang} `)}\n`) } else { process.stderr.write(` ${bang} ${chalk.red(`${err.name}:`)} ${err.message}\n`) } } else { + reportError(err, { severity: 'error' }) throw err } } diff --git a/src/utils/execa.mjs b/src/utils/execa.mjs index 1dde8a3578b..e23d75c7716 100644 --- a/src/utils/execa.mjs +++ b/src/utils/execa.mjs @@ -2,6 +2,10 @@ import { env } from 'process' // This is a thin layer on top of `execa` that allows consumers to provide an // alternative path to the module location, making it easier to mock its logic // in tests (see `tests/utils/mock-execa.js`). + +/** + * @type {import('execa')} + */ // eslint-disable-next-line import/no-mutable-exports let execa diff --git a/src/utils/telemetry/index.mjs b/src/utils/telemetry/index.mjs index 3f07e161bb6..e9e72a044ba 100644 --- a/src/utils/telemetry/index.mjs +++ b/src/utils/telemetry/index.mjs @@ -1 +1,2 @@ export { track, identify } from './telemetry.mjs' +export { reportError } from './report-error.mjs' diff --git a/src/utils/telemetry/report-error.mjs b/src/utils/telemetry/report-error.mjs new file mode 100644 index 00000000000..e29bda8bd6a --- /dev/null +++ b/src/utils/telemetry/report-error.mjs @@ -0,0 +1,44 @@ +import { dirname, join } from 'path' +import process, { version as nodejsVersion } from 'process' +import { fileURLToPath } from 'url' + +import execa from '../execa.mjs' +import getGlobalConfig from '../get-global-config.mjs' + +import { cliVersion } from './utils.mjs' + +const dirPath = dirname(fileURLToPath(import.meta.url)) + +/** + * + * @param {import('@bugsnag/js').NotifiableError} error + * @param {object} config + * @param {import('@bugsnag/js').Event['severity']} config.severity + * @returns {Promise} + */ +export const reportError = async function (error, config = {}) { + const globalConfig = await getGlobalConfig() + + const options = JSON.stringify({ + type: 'error', + data: { + message: error.message, + name: error.name, + stack: error.stack, + cause: error.cause, + severity: config.severity, + user: { + id: globalConfig.get('userId'), + }, + osName: process.platform, + cliVersion, + nodejsVersion, + }, + }) + + // spawn detached child process to handle send + execa(process.execPath, [join(dirPath, 'request.mjs'), options], { + detached: true, + stdio: 'ignore', + }).unref() +} diff --git a/src/utils/telemetry/request.mjs b/src/utils/telemetry/request.mjs index 665b75eb9d9..bca3c0ff693 100644 --- a/src/utils/telemetry/request.mjs +++ b/src/utils/telemetry/request.mjs @@ -14,13 +14,23 @@ const options = JSON.parse(process.argv[2]) const CLIENT_ID = 'NETLIFY_CLI' const TRACK_URL = process.env.NETLIFY_TEST_TRACK_URL || 'https://cli.netlify.com/telemetry/track' const IDENTIFY_URL = process.env.NETLIFY_TEST_IDENTIFY_URL || 'https://cli.netlify.com/telemetry/identify' - -const API_URL = options.type && options.type === 'track' ? TRACK_URL : IDENTIFY_URL +const REPORT_ERROR_URL = process.env.NETLIFY_TEST_ERROR_REPORT_URL || 'https://cli.netlify.com/report-error' + +const getApiUrl = () => { + switch (options.type) { + case 'track': + return TRACK_URL + case 'error': + return REPORT_ERROR_URL + default: + return IDENTIFY_URL + } +} // Make telemetry call const makeRequest = async function () { try { - await fetch(API_URL, { + await fetch(getApiUrl(), { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/utils/telemetry/telemetry.mjs b/src/utils/telemetry/telemetry.mjs index 1f288be4518..cbc1e59069b 100644 --- a/src/utils/telemetry/telemetry.mjs +++ b/src/utils/telemetry/telemetry.mjs @@ -7,16 +7,10 @@ import { isCI } from 'ci-info' import execa from '../execa.mjs' import getGlobalConfig from '../get-global-config.mjs' -import getPackageJson from '../get-package-json.mjs' +import { isTelemetryDisabled, cliVersion } from './utils.mjs' import isValidEventName from './validation.mjs' -const { version: cliVersion } = await getPackageJson() - -const isTelemetryDisabled = function (config) { - return config.get('telemetryDisabled') -} - const dirPath = dirname(fileURLToPath(import.meta.url)) const send = function (type, payload) { diff --git a/src/utils/telemetry/utils.mjs b/src/utils/telemetry/utils.mjs new file mode 100644 index 00000000000..de9fc8f42ea --- /dev/null +++ b/src/utils/telemetry/utils.mjs @@ -0,0 +1,7 @@ +import getPackageJson from '../get-package-json.mjs' + +export const { version: cliVersion } = await getPackageJson() + +export const isTelemetryDisabled = function (config) { + return config.get('telemetryDisabled') +}