From 0d7d7227c3d2b550b30455823256859dfb585f4d Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Thu, 7 Jul 2022 13:35:40 -0400 Subject: [PATCH 1/4] Move unhandled error/rejection tracking to root app --- app/discord/gateway.ts | 2 -- app/index.ts | 20 +++++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/discord/gateway.ts b/app/discord/gateway.ts index 8a3ebe8..a5efd25 100644 --- a/app/discord/gateway.ts +++ b/app/discord/gateway.ts @@ -52,6 +52,4 @@ export default function init() { }; client.on("error", errorHandler); - process.on("uncaughtException", errorHandler); - process.on("unhandledRejection", errorHandler); } diff --git a/app/index.ts b/app/index.ts index d64acef..bda0602 100644 --- a/app/index.ts +++ b/app/index.ts @@ -8,9 +8,11 @@ const app = express(); app.use(express.static(path.join(__dirname, "..", "public"))); -app.get("/butts", (req, res) => { - res.send("butts"); -}); +/** +Route handlers and static hosting +*/ + +app.use(express.static(path.join(__dirname, "..", "public"))); // needs to handle all verbs (GET, POST, etc.) app.all( @@ -32,3 +34,15 @@ app.all( app.listen(process.env.PORT || "3000"); discordBot(); + +const errorHandler = (error: unknown) => { + Sentry.captureException(error); + if (error instanceof Error) { + console.log("ERROR", error.message); + } else if (typeof error === "string") { + console.log("ERROR", error); + } +}; + +process.on("uncaughtException", errorHandler); +process.on("unhandledRejection", errorHandler); From 5d7dd198ae6775d4462b7a183176fe723bb316fa Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Thu, 7 Jul 2022 13:36:24 -0400 Subject: [PATCH 2/4] Use Sentry error and release tracking --- .github/workflows/node.js.yml | 7 + app/discord/gateway.ts | 3 + app/helpers/sentry.server.ts | 24 ++++ app/index.ts | 14 +- package-lock.json | 252 ++++++++++++++++++++++++++++++++++ package.json | 2 + 6 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 app/helpers/sentry.server.ts diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 94405f5..8929c57 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -165,3 +165,10 @@ jobs: --from-literal=DISCORD_SECRET=${{ secrets.DISCORD_SECRET }} \ --from-literal=DISCORD_HASH=${{ secrets.DISCORD_HASH }} kubectl apply -k . + + - name: Set Sentry release + run: | + curl ${{secrets.SENTRY_RELEASES}} \ + -X POST \ + -H 'Content-Type: application/json' \ + -d '{"version": "${{github.sha}}}"' diff --git a/app/discord/gateway.ts b/app/discord/gateway.ts index a5efd25..e3b8eb9 100644 --- a/app/discord/gateway.ts +++ b/app/discord/gateway.ts @@ -1,3 +1,5 @@ +import Sentry from "~/helpers/sentry.server"; + import onboardCommand, { handler as onboardHandler } from "~/commands/setup"; import reportCommand, { handler as reportHandler } from "~/commands/report"; @@ -44,6 +46,7 @@ export default function init() { }); const errorHandler = (error: unknown) => { + Sentry.captureException(error); if (error instanceof Error) { console.log("ERROR", error.message); } else if (typeof error === "string") { diff --git a/app/helpers/sentry.server.ts b/app/helpers/sentry.server.ts new file mode 100644 index 0000000..5b65200 --- /dev/null +++ b/app/helpers/sentry.server.ts @@ -0,0 +1,24 @@ +import * as Sentry from "@sentry/node"; +// import * as Tracing from "@sentry/tracing"; + +console.log(process.env.SENTRY_INGEST); + +Sentry.init({ + dsn: process.env.SENTRY_INGEST, + environment: process.env.NODE_ENV, + integrations: [ + // enable HTTP calls tracing + // new Sentry.Integrations.Http({ tracing: true }), + new Sentry.Integrations.OnUncaughtException(), + new Sentry.Integrations.OnUnhandledRejection(), + // enable Express.js middleware tracing + // new Tracing.Integrations.Express({ app }), + ], + + // Set tracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + // We recommend adjusting this value in production + tracesSampleRate: 0.2, +}); + +export default Sentry; diff --git a/app/index.ts b/app/index.ts index bda0602..d710b7a 100644 --- a/app/index.ts +++ b/app/index.ts @@ -2,11 +2,17 @@ import express from "express"; import { createRequestHandler } from "@remix-run/express"; import path from "path"; import * as build from "@remix-run/dev/server-build"; + +import Sentry from "~/helpers/sentry.server"; import discordBot from "~/discord/gateway"; const app = express(); -app.use(express.static(path.join(__dirname, "..", "public"))); +// RequestHandler creates a separate execution context using domains, so that +// every transaction/span/breadcrumb is attached to its own Hub instance +app.use(Sentry.Handlers.requestHandler()); +// TracingHandler creates a trace for every incoming request +// app.use(Sentry.Handlers.tracingHandler()); /** Route handlers and static hosting @@ -31,6 +37,12 @@ app.all( }), ); +/** ERROR TRACKING + Must go after route handlers +*/ +app.use(Sentry.Handlers.errorHandler()); + +/** Init app */ app.listen(process.env.PORT || "3000"); discordBot(); diff --git a/package-lock.json b/package-lock.json index 0b15eb0..056e2e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,8 @@ "@remix-run/react": "^1.4.1", "@remix-run/serve": "^1.4.1", "@remix-run/server-runtime": "^1.4.1", + "@sentry/node": "^7.5.1", + "@sentry/tracing": "^7.5.1", "better-sqlite3": "^7.5.1", "date-fns": "^2.27.0", "discord-api-types": "^0.33.2", @@ -3348,6 +3350,110 @@ "npm": ">=7.0.0" } }, + "node_modules/@sentry/core": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.5.1.tgz", + "integrity": "sha512-1ac5eaJi9LBIpCaert+IrttyaL8rnrK5fcdB6tyqDf8jNV5s9O32PyqjvjpWCrGOvZ4kmp+6UXB9bw/NNtvpkQ==", + "dependencies": { + "@sentry/hub": "7.5.1", + "@sentry/types": "7.5.1", + "@sentry/utils": "7.5.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/hub": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-7.5.1.tgz", + "integrity": "sha512-q14zzf5GlE4xvwFP7lZaAI4UnuqWMc3nD62Md5XBptY35bm42CGzawx9aDQ8cegZoQ5bHyX1GPzFju4lDO3O6g==", + "dependencies": { + "@sentry/types": "7.5.1", + "@sentry/utils": "7.5.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/hub/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/node": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.5.1.tgz", + "integrity": "sha512-XSpNbxBVIpcklLpk9NtQSkTZM0/mEj0TYMnzQmE2UR7UChpGhZyc19nbQWceSsaLMrrAgOX4Zzo28ENk/QY5FA==", + "dependencies": { + "@sentry/core": "7.5.1", + "@sentry/hub": "7.5.1", + "@sentry/types": "7.5.1", + "@sentry/utils": "7.5.1", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/tracing": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.5.1.tgz", + "integrity": "sha512-fOmzTk3/mTKF5d1P43yZ29lc5z/1wyL2+qX+N5rLluIeR6dEYISyjFistK8z1esMCCvJu8/x3u0imbrrFDNx0Q==", + "dependencies": { + "@sentry/hub": "7.5.1", + "@sentry/types": "7.5.1", + "@sentry/utils": "7.5.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/tracing/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/types": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.5.1.tgz", + "integrity": "sha512-+OHxQL4lXCEsUA31qlhcPABOjxtbuL+VTpgamXJjxEpQQDPUPyPK0pu7c+uTc7x4Re96Ss3pwUYE9tl3WW3xIg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.5.1.tgz", + "integrity": "sha512-5w5dEDilAkH/4x5h8VMlfFcGKdDQ8tbSEfxnMOheD3/bwk18lTVTgp6kk+VxmugGdvxsTLiPEoORsuofufWvGQ==", + "dependencies": { + "@sentry/types": "7.5.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -4818,6 +4924,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -10202,6 +10319,18 @@ "node": ">=10.19.0" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -12310,6 +12439,11 @@ "node": ">=8" } }, + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -22184,6 +22318,102 @@ "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.2.2.tgz", "integrity": "sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ==" }, + "@sentry/core": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.5.1.tgz", + "integrity": "sha512-1ac5eaJi9LBIpCaert+IrttyaL8rnrK5fcdB6tyqDf8jNV5s9O32PyqjvjpWCrGOvZ4kmp+6UXB9bw/NNtvpkQ==", + "requires": { + "@sentry/hub": "7.5.1", + "@sentry/types": "7.5.1", + "@sentry/utils": "7.5.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/hub": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-7.5.1.tgz", + "integrity": "sha512-q14zzf5GlE4xvwFP7lZaAI4UnuqWMc3nD62Md5XBptY35bm42CGzawx9aDQ8cegZoQ5bHyX1GPzFju4lDO3O6g==", + "requires": { + "@sentry/types": "7.5.1", + "@sentry/utils": "7.5.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/node": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.5.1.tgz", + "integrity": "sha512-XSpNbxBVIpcklLpk9NtQSkTZM0/mEj0TYMnzQmE2UR7UChpGhZyc19nbQWceSsaLMrrAgOX4Zzo28ENk/QY5FA==", + "requires": { + "@sentry/core": "7.5.1", + "@sentry/hub": "7.5.1", + "@sentry/types": "7.5.1", + "@sentry/utils": "7.5.1", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/tracing": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.5.1.tgz", + "integrity": "sha512-fOmzTk3/mTKF5d1P43yZ29lc5z/1wyL2+qX+N5rLluIeR6dEYISyjFistK8z1esMCCvJu8/x3u0imbrrFDNx0Q==", + "requires": { + "@sentry/hub": "7.5.1", + "@sentry/types": "7.5.1", + "@sentry/utils": "7.5.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/types": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.5.1.tgz", + "integrity": "sha512-+OHxQL4lXCEsUA31qlhcPABOjxtbuL+VTpgamXJjxEpQQDPUPyPK0pu7c+uTc7x4Re96Ss3pwUYE9tl3WW3xIg==" + }, + "@sentry/utils": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.5.1.tgz", + "integrity": "sha512-5w5dEDilAkH/4x5h8VMlfFcGKdDQ8tbSEfxnMOheD3/bwk18lTVTgp6kk+VxmugGdvxsTLiPEoORsuofufWvGQ==", + "requires": { + "@sentry/types": "7.5.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -23247,6 +23477,14 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -27024,6 +27262,15 @@ "resolve-alpn": "^1.0.0" } }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -28401,6 +28648,11 @@ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", "dev": true }, + "lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", diff --git a/package.json b/package.json index b4faa69..cfcc920 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "@remix-run/react": "^1.4.1", "@remix-run/serve": "^1.4.1", "@remix-run/server-runtime": "^1.4.1", + "@sentry/node": "^7.5.1", + "@sentry/tracing": "^7.5.1", "better-sqlite3": "^7.5.1", "date-fns": "^2.27.0", "discord-api-types": "^0.33.2", From b1d318cebbb1b6f5d8f7227a6fc02f4e7286bdbc Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Thu, 7 Jul 2022 14:05:01 -0400 Subject: [PATCH 3/4] WIP --- .github/workflows/node.js.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 8929c57..5adc16e 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -124,6 +124,13 @@ jobs: rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache + - name: Set Sentry release + run: | + curl ${{secrets.SENTRY_RELEASES}} \ + -X POST \ + -H 'Content-Type: application/json' \ + -d '{"version": "${{github.sha}}}"' + deployment: needs: build if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/feature/actions' @@ -165,10 +172,3 @@ jobs: --from-literal=DISCORD_SECRET=${{ secrets.DISCORD_SECRET }} \ --from-literal=DISCORD_HASH=${{ secrets.DISCORD_HASH }} kubectl apply -k . - - - name: Set Sentry release - run: | - curl ${{secrets.SENTRY_RELEASES}} \ - -X POST \ - -H 'Content-Type: application/json' \ - -d '{"version": "${{github.sha}}}"' From 535cc9592d2ea971f500857c0a0d4ce875e61c18 Mon Sep 17 00:00:00 2001 From: Carl Vitullo Date: Thu, 7 Jul 2022 14:13:47 -0400 Subject: [PATCH 4/4] WIP --- .github/workflows/node.js.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 5adc16e..6cf3456 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -129,7 +129,7 @@ jobs: curl ${{secrets.SENTRY_RELEASES}} \ -X POST \ -H 'Content-Type: application/json' \ - -d '{"version": "${{github.sha}}}"' + -d '{"version": "${{github.sha}}"}' deployment: needs: build