From d8369f759ba930e5a6d1b240bbd08a47d1f0f64b Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 10 Feb 2025 11:31:13 -0500 Subject: [PATCH 1/7] feat(ci): Add junit reporters to jest/vitest + codecov test results (#15341) I noticed that not all of our tests were being tracked in codecov. This adds `jest-junit` reporter for our jest tests and configures vitest to use junit reporter (when `CI` env var is set). This brings [the number of tests tracked](https://app.codecov.io/gh/getsentry/sentry-javascript/tests/feat-ci-jest-junit) from ~700 to 4.5k --------- Co-authored-by: joseph-sentry <136376984+joseph-sentry@users.noreply.github.com> --- .github/workflows/build.yml | 32 +++++++++++++++++++++++ jest/jest.config.js | 10 +++++++ package.json | 1 + packages/angular/vitest.config.ts | 1 - packages/replay-internal/vitest.config.ts | 1 - vite/vite.config.ts | 4 +++ yarn.lock | 15 +++++++++++ 7 files changed, 62 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5972af74bb89..7733c451171f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -381,6 +381,14 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload test results to Codecov + if: cancelled() == false + continue-on-error: true + uses: codecov/test-results-action@v1 + with: + files: packages/**/*.junit.xml + token: ${{ secrets.CODECOV_TOKEN }} + job_bun_unit_tests: name: Bun Unit Tests needs: [job_get_metadata, job_build] @@ -484,6 +492,14 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload test results to Codecov + if: cancelled() == false + continue-on-error: true + uses: codecov/test-results-action@v1 + with: + files: packages/**/*.junit.xml + token: ${{ secrets.CODECOV_TOKEN }} + job_browser_playwright_tests: name: Playwright ${{ matrix.bundle }}${{ matrix.project && matrix.project != 'chromium' && format(' {0}', matrix.project) || ''}}${{ matrix.shard && format(' ({0}/{1})', matrix.shard, matrix.shards) || ''}} Tests needs: [job_get_metadata, job_build] @@ -701,6 +717,14 @@ jobs: working-directory: dev-packages/node-integration-tests run: yarn test + - name: Upload test results to Codecov + if: cancelled() == false + continue-on-error: true + uses: codecov/test-results-action@v1 + with: + directory: dev-packages/node-integration-tests + token: ${{ secrets.CODECOV_TOKEN }} + job_remix_integration_tests: name: Remix (Node ${{ matrix.node }}) Tests needs: [job_get_metadata, job_build] @@ -737,6 +761,14 @@ jobs: cd packages/remix yarn test:integration:ci + - name: Upload test results to Codecov + if: cancelled() == false + continue-on-error: true + uses: codecov/test-results-action@v1 + with: + directory: packages/remix + token: ${{ secrets.CODECOV_TOKEN }} + job_e2e_prepare: name: Prepare E2E tests # We want to run this if: diff --git a/jest/jest.config.js b/jest/jest.config.js index 495035994b37..804519918182 100644 --- a/jest/jest.config.js +++ b/jest/jest.config.js @@ -28,6 +28,16 @@ module.exports = { ...(process.env.CI ? { coverageReporters: ['json', 'lcov', 'clover'], + reporters: [ + 'default', + [ + 'jest-junit', + { + outputName: 'jest.junit.xml', + classNameTemplate: '{filepath}', + }, + ], + ], } : {}), }; diff --git a/package.json b/package.json index a57893dde86c..1f63d4c57dda 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "eslint": "7.32.0", "jest": "^27.5.1", "jest-environment-node": "^27.5.1", + "jest-junit": "^16.0.0", "jsdom": "^21.1.2", "lerna": "7.1.1", "madge": "7.0.0", diff --git a/packages/angular/vitest.config.ts b/packages/angular/vitest.config.ts index 82015893133b..4b3cb2ffacfa 100644 --- a/packages/angular/vitest.config.ts +++ b/packages/angular/vitest.config.ts @@ -7,7 +7,6 @@ export default defineConfig({ coverage: {}, globals: true, setupFiles: ['./setup-test.ts'], - reporters: ['default'], environment: 'jsdom', }, }); diff --git a/packages/replay-internal/vitest.config.ts b/packages/replay-internal/vitest.config.ts index 976d9c37074d..3aa37d27166c 100644 --- a/packages/replay-internal/vitest.config.ts +++ b/packages/replay-internal/vitest.config.ts @@ -7,6 +7,5 @@ export default defineConfig({ test: { ...baseConfig.test, setupFiles: ['./test.setup.ts'], - reporters: ['default'], }, }); diff --git a/vite/vite.config.ts b/vite/vite.config.ts index a5be84382388..2717ad778e1d 100644 --- a/vite/vite.config.ts +++ b/vite/vite.config.ts @@ -10,6 +10,10 @@ export default defineConfig({ enabled: true, reportsDirectory: './coverage', }, + reporters: ['default', ...(process.env.CI ? [['junit', { classnameTemplate: '{filepath}' }]] : [])], + outputFile: { + junit: 'vitest.junit.xml', + }, typecheck: { tsconfig: './tsconfig.test.json', }, diff --git a/yarn.lock b/yarn.lock index f1e8c2068fdb..7ea515fcca18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19114,6 +19114,16 @@ jest-jasmine2@^27.5.1: pretty-format "^27.5.1" throat "^6.0.1" +jest-junit@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-16.0.0.tgz#d838e8c561cf9fdd7eb54f63020777eee4136785" + integrity sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ== + dependencies: + mkdirp "^1.0.4" + strip-ansi "^6.0.1" + uuid "^8.3.2" + xml "^1.0.1" + jest-leak-detector@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" @@ -30554,6 +30564,11 @@ xml-name-validator@^4.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== +xml@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" From 2afe7322a402144a1eccc7c0d01f4839482e7de2 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Mon, 10 Feb 2025 10:19:20 -0800 Subject: [PATCH 2/7] ref(flags): rename unleash integration param (#15343) --- .../featureFlags/unleash/badSignature/init.js | 2 +- .../suites/integrations/featureFlags/unleash/init.js | 2 +- .../src/integrations/featureFlags/unleash/integration.ts | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/init.js index dc92fbc296a4..1e0303b9c356 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/badSignature/init.js @@ -7,7 +7,7 @@ window.UnleashClient = class { }; window.Sentry = Sentry; -window.sentryUnleashIntegration = Sentry.unleashIntegration({ unleashClientClass: window.UnleashClient }); +window.sentryUnleashIntegration = Sentry.unleashIntegration({ featureFlagClientClass: window.UnleashClient }); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/init.js b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/init.js index 9f1f28730cf7..ddc74b6427b4 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/featureFlags/unleash/init.js @@ -41,7 +41,7 @@ window.UnleashClient = class { }; window.Sentry = Sentry; -window.sentryUnleashIntegration = Sentry.unleashIntegration({ unleashClientClass: window.UnleashClient }); +window.sentryUnleashIntegration = Sentry.unleashIntegration({ featureFlagClientClass: window.UnleashClient }); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', diff --git a/packages/browser/src/integrations/featureFlags/unleash/integration.ts b/packages/browser/src/integrations/featureFlags/unleash/integration.ts index c451afb831ba..e7f4602d17d7 100644 --- a/packages/browser/src/integrations/featureFlags/unleash/integration.ts +++ b/packages/browser/src/integrations/featureFlags/unleash/integration.ts @@ -5,6 +5,10 @@ import { DEBUG_BUILD } from '../../../debug-build'; import { copyFlagsFromScopeToEvent, insertFlagToScope } from '../../../utils/featureFlags'; import type { UnleashClient, UnleashClientClass } from './types'; +type UnleashIntegrationOptions = { + featureFlagClientClass: UnleashClientClass; +}; + /** * Sentry integration for capturing feature flag evaluations from the Unleash SDK. * @@ -17,19 +21,18 @@ import type { UnleashClient, UnleashClientClass } from './types'; * * Sentry.init({ * dsn: '___PUBLIC_DSN___', - * integrations: [Sentry.unleashIntegration({unleashClientClass: UnleashClient})], + * integrations: [Sentry.unleashIntegration({featureFlagClientClass: UnleashClient})], * }); * * const unleash = new UnleashClient(...); * unleash.start(); * * unleash.isEnabled('my-feature'); - * unleash.getVariant('other-feature'); * Sentry.captureException(new Error('something went wrong')); * ``` */ export const unleashIntegration = defineIntegration( - ({ unleashClientClass }: { unleashClientClass: UnleashClientClass }) => { + ({ featureFlagClientClass: unleashClientClass }: UnleashIntegrationOptions) => { return { name: 'Unleash', From 2f17ef13dcbe612203feac2e3b6a75c4e3c890b1 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 11 Feb 2025 10:26:59 +0100 Subject: [PATCH 3/7] feat(replay/feedback): Add experimental autoFlushOnFeedback option (#15356) This PR adds an experimental flag `autoFlushOnFeedback` to the replay options. It aims to reduce replays that only contain the user interaction within the feedback modal, whereas the we rather want to inspect what happened prior to this. Once enabled, replays are automatically flushed when: - the feedback modal is opened - feedback is captured manually through an api call partly related to https://github.com/getsentry/sentry-javascript/issues/6908 --- .../suites/replay/autoFlushOnFeedback/init.js | 20 ++++++++ .../replay/autoFlushOnFeedback/subject.js | 9 ++++ .../replay/autoFlushOnFeedback/template.html | 11 +++++ .../suites/replay/autoFlushOnFeedback/test.ts | 46 +++++++++++++++++++ packages/core/src/client.ts | 10 ++++ .../src/types-hoist/feedback/sendFeedback.ts | 1 + packages/feedback/src/modal/integration.tsx | 3 +- packages/replay-internal/src/replay.ts | 2 +- packages/replay-internal/src/types/replay.ts | 1 + .../src/util/addGlobalListeners.ts | 24 +++++++--- 10 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/init.js create mode 100644 dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/template.html create mode 100644 dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/test.ts diff --git a/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/init.js b/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/init.js new file mode 100644 index 000000000000..ecbfac30016e --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/init.js @@ -0,0 +1,20 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = Sentry.replayIntegration({ + flushMinDelay: 200, + flushMaxDelay: 200, + useCompression: false, + _experiments: { + autoFlushOnFeedback: true, + }, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + + integrations: [window.Replay], +}); diff --git a/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/subject.js b/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/subject.js new file mode 100644 index 000000000000..06fee7313b72 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/subject.js @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/browser'; + +document.getElementById('open').addEventListener('click', () => { + Sentry.getClient().emit('openFeedbackWidget'); +}); + +document.getElementById('send').addEventListener('click', () => { + Sentry.getClient().emit('beforeSendFeedback'); +}); diff --git a/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/template.html b/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/template.html new file mode 100644 index 000000000000..2218082097dc --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/template.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/test.ts b/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/test.ts new file mode 100644 index 000000000000..41e94eef690b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/autoFlushOnFeedback/test.ts @@ -0,0 +1,46 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates'; +import { getReplayEvent, shouldSkipReplayTest, waitForReplayRequest } from '../../../utils/replayHelpers'; + +/* + * In this test we want to verify that replay events are automatically flushed when user feedback is submitted via API / opening the widget. + * We emulate this by firing the feedback events directly, which should trigger an immediate flush of any + * buffered replay events, rather than waiting for the normal flush delay. + */ +sentryTest('replay events are flushed automatically on feedback events', async ({ getLocalTestUrl, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + const reqPromise2 = waitForReplayRequest(page, 2); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + const replayEvent0 = getReplayEvent(await reqPromise0); + expect(replayEvent0).toEqual(getExpectedReplayEvent()); + + // Trigger one mouse click + void page.locator('#something').click(); + + // Open the feedback widget which should trigger an immediate flush + await page.locator('#open').click(); + + // This should be flushed immediately due to feedback widget being opened + const replayEvent1 = getReplayEvent(await reqPromise1); + expect(replayEvent1).toEqual(getExpectedReplayEvent({ segment_id: 1, urls: [] })); + + // trigger another click + void page.locator('#something').click(); + + // Send feedback via API which should trigger another immediate flush + await page.locator('#send').click(); + + // This should be flushed immediately due to feedback being sent + const replayEvent2 = getReplayEvent(await reqPromise2); + expect(replayEvent2).toEqual(getExpectedReplayEvent({ segment_id: 2, urls: [] })); +}); diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 805596a8a855..0c74625e31a4 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -561,6 +561,11 @@ export abstract class Client { callback: (feedback: FeedbackEvent, options?: { includeReplay?: boolean }) => void, ): () => void; + /** + * Register a callback when the feedback widget is opened in a user's browser + */ + public on(hook: 'openFeedbackWidget', callback: () => void): () => void; + /** * A hook for the browser tracing integrations to trigger a span start for a page load. * @returns {() => void} A function that, when executed, removes the registered callback. @@ -695,6 +700,11 @@ export abstract class Client { */ public emit(hook: 'beforeSendFeedback', feedback: FeedbackEvent, options?: { includeReplay?: boolean }): void; + /** + * Fire a hook event for when the feedback widget is opened in a user's browser + */ + public emit(hook: 'openFeedbackWidget'): void; + /** * Emit a hook event for browser tracing integrations to trigger a span start for a page load. */ diff --git a/packages/core/src/types-hoist/feedback/sendFeedback.ts b/packages/core/src/types-hoist/feedback/sendFeedback.ts index 8f865b57038d..63d63b402b50 100644 --- a/packages/core/src/types-hoist/feedback/sendFeedback.ts +++ b/packages/core/src/types-hoist/feedback/sendFeedback.ts @@ -19,6 +19,7 @@ interface FeedbackContext extends Record { replay_id?: string; url?: string; associated_event_id?: string; + source?: string; } /** diff --git a/packages/feedback/src/modal/integration.tsx b/packages/feedback/src/modal/integration.tsx index bd8b9b84f148..f4b228d814b6 100644 --- a/packages/feedback/src/modal/integration.tsx +++ b/packages/feedback/src/modal/integration.tsx @@ -1,4 +1,4 @@ -import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; +import { getClient, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import type { FeedbackFormData, FeedbackModalIntegration, IntegrationFn, User } from '@sentry/core'; import { h, render } from 'preact'; import * as hooks from 'preact/hooks'; @@ -51,6 +51,7 @@ export const feedbackModalIntegration = ((): FeedbackModalIntegration => { open() { renderContent(true); options.onFormOpen?.(); + getClient()?.emit('openFeedbackWidget'); originalOverflow = DOCUMENT.body.style.overflow; DOCUMENT.body.style.overflow = 'hidden'; }, diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index 7a19f1ea7070..89df655050e5 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -933,7 +933,7 @@ export class ReplayContainer implements ReplayContainerInterface { // There is no way to remove these listeners, so ensure they are only added once if (!this._hasInitializedCoreListeners) { - addGlobalListeners(this); + addGlobalListeners(this, { autoFlushOnFeedback: this._options._experiments.autoFlushOnFeedback }); this._hasInitializedCoreListeners = true; } diff --git a/packages/replay-internal/src/types/replay.ts b/packages/replay-internal/src/types/replay.ts index 7cd4c78a21c5..6ac77d7b672f 100644 --- a/packages/replay-internal/src/types/replay.ts +++ b/packages/replay-internal/src/types/replay.ts @@ -239,6 +239,7 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions { captureExceptions: boolean; traceInternals: boolean; continuousCheckout: number; + autoFlushOnFeedback: boolean; }>; } diff --git a/packages/replay-internal/src/util/addGlobalListeners.ts b/packages/replay-internal/src/util/addGlobalListeners.ts index f29ed4087d95..afa005669a46 100644 --- a/packages/replay-internal/src/util/addGlobalListeners.ts +++ b/packages/replay-internal/src/util/addGlobalListeners.ts @@ -17,7 +17,10 @@ import type { ReplayContainer } from '../types'; /** * Add global listeners that cannot be removed. */ -export function addGlobalListeners(replay: ReplayContainer): void { +export function addGlobalListeners( + replay: ReplayContainer, + { autoFlushOnFeedback }: { autoFlushOnFeedback?: boolean }, +): void { // Listeners from core SDK // const client = getClient(); @@ -57,15 +60,22 @@ export function addGlobalListeners(replay: ReplayContainer): void { replay.lastActiveSpan = span; }); - // We want to flush replay - client.on('beforeSendFeedback', (feedbackEvent, options) => { + // We want to attach the replay id to the feedback event + client.on('beforeSendFeedback', async (feedbackEvent, options) => { const replayId = replay.getSessionId(); - if (options?.includeReplay && replay.isEnabled() && replayId) { - // This should never reject - if (feedbackEvent.contexts?.feedback) { - feedbackEvent.contexts.feedback.replay_id = replayId; + if (options?.includeReplay && replay.isEnabled() && replayId && feedbackEvent.contexts?.feedback) { + // In case the feedback is sent via API and not through our widget, we want to flush replay + if (feedbackEvent.contexts.feedback.source === 'api' && autoFlushOnFeedback) { + await replay.flush(); } + feedbackEvent.contexts.feedback.replay_id = replayId; } }); + + if (autoFlushOnFeedback) { + client.on('openFeedbackWidget', async () => { + await replay.flush(); + }); + } } } From 55cccfd25516d0a2edd1a46d5ea7aae53fe4fd5f Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 11 Feb 2025 10:28:05 +0100 Subject: [PATCH 4/7] feat(nestjs): Support v11 (#15114) Co-authored-by: s1gr1d Co-authored-by: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> --- .../test-applications/nestjs-11/.gitignore | 56 ++ .../test-applications/nestjs-11/.npmrc | 2 + .../test-applications/nestjs-11/nest-cli.json | 8 + .../test-applications/nestjs-11/package.json | 48 ++ .../nestjs-11/playwright.config.mjs | 7 + .../nestjs-11/src/app.controller.ts | 124 +++ .../nestjs-11/src/app.module.ts | 29 + .../nestjs-11/src/app.service.ts | 113 +++ .../src/async-example.interceptor.ts | 17 + .../nestjs-11/src/example-1.interceptor.ts | 15 + .../nestjs-11/src/example-2.interceptor.ts | 10 + .../src/example-global-filter.exception.ts | 5 + .../nestjs-11/src/example-global.filter.ts | 19 + .../src/example-local-filter.exception.ts | 5 + .../nestjs-11/src/example-local.filter.ts | 19 + .../nestjs-11/src/example.guard.ts | 10 + .../nestjs-11/src/example.middleware.ts | 12 + .../nestjs-11/src/instrument.ts | 12 + .../test-applications/nestjs-11/src/main.ts | 15 + .../nestjs-11/start-event-proxy.mjs | 6 + .../nestjs-11/tests/cron-decorator.test.ts | 81 ++ .../nestjs-11/tests/errors.test.ts | 171 ++++ .../nestjs-11/tests/span-decorator.test.ts | 79 ++ .../nestjs-11/tests/transactions.test.ts | 732 ++++++++++++++++++ .../nestjs-11/tsconfig.build.json | 4 + .../test-applications/nestjs-11/tsconfig.json | 22 + .../nestjs-fastify/tests/transactions.test.ts | 2 +- packages/nestjs/package.json | 10 +- packages/nestjs/src/integrations/nest.ts | 2 +- .../sentry-nest-core-instrumentation.ts | 308 ++++++++ .../sentry-nest-instrumentation.ts | 2 +- yarn.lock | 25 +- 32 files changed, 1953 insertions(+), 17 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/nest-cli.json create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/package.json create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/app.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/app.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/app.service.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/async-example.interceptor.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/example-1.interceptor.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/example-2.interceptor.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/example-global-filter.exception.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/example-global.filter.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/example-local-filter.exception.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/example-local.filter.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/example.guard.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/example.middleware.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/instrument.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/src/main.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/tests/cron-decorator.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/tests/span-decorator.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/tsconfig.build.json create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/tsconfig.json create mode 100644 packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/.gitignore b/dev-packages/e2e-tests/test-applications/nestjs-11/.gitignore new file mode 100644 index 000000000000..4b56acfbebf4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/.npmrc b/dev-packages/e2e-tests/test-applications/nestjs-11/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/nest-cli.json b/dev-packages/e2e-tests/test-applications/nestjs-11/nest-cli.json new file mode 100644 index 000000000000..f9aa683b1ad5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/package.json b/dev-packages/e2e-tests/test-applications/nestjs-11/package.json new file mode 100644 index 000000000000..9ba374954190 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/package.json @@ -0,0 +1,48 @@ +{ + "name": "nestjs-11", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test": "playwright test", + "test:build": "pnpm install", + "test:assert": "pnpm test" + }, + "dependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/microservices": "^11.0.0", + "@nestjs/schedule": "^5.0.0", + "@nestjs/platform-express": "^11.0.0", + "@sentry/nestjs": "latest || *", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.0", + "@types/express": "^4.17.17", + "@types/node": "^18.19.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^6.3.3", + "ts-loader": "^9.4.3", + "tsconfig-paths": "^4.2.0", + "typescript": "~5.0.0" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nestjs-11/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/playwright.config.mjs @@ -0,0 +1,7 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/app.controller.ts new file mode 100644 index 000000000000..33a6b1957d99 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/app.controller.ts @@ -0,0 +1,124 @@ +import { Controller, Get, Param, ParseIntPipe, UseFilters, UseGuards, UseInterceptors } from '@nestjs/common'; +import { flush } from '@sentry/nestjs'; +import { AppService } from './app.service'; +import { AsyncInterceptor } from './async-example.interceptor'; +import { ExampleInterceptor1 } from './example-1.interceptor'; +import { ExampleInterceptor2 } from './example-2.interceptor'; +import { ExampleExceptionGlobalFilter } from './example-global-filter.exception'; +import { ExampleExceptionLocalFilter } from './example-local-filter.exception'; +import { ExampleLocalFilter } from './example-local.filter'; +import { ExampleGuard } from './example.guard'; + +@Controller() +@UseFilters(ExampleLocalFilter) +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get('test-transaction') + testTransaction() { + return this.appService.testTransaction(); + } + + @Get('test-middleware-instrumentation') + testMiddlewareInstrumentation() { + return this.appService.testSpan(); + } + + @Get('test-guard-instrumentation') + @UseGuards(ExampleGuard) + testGuardInstrumentation() { + return {}; + } + + @Get('test-interceptor-instrumentation') + @UseInterceptors(ExampleInterceptor1, ExampleInterceptor2) + testInterceptorInstrumentation() { + return this.appService.testSpan(); + } + + @Get('test-async-interceptor-instrumentation') + @UseInterceptors(AsyncInterceptor) + testAsyncInterceptorInstrumentation() { + return this.appService.testSpan(); + } + + @Get('test-pipe-instrumentation/:id') + testPipeInstrumentation(@Param('id', ParseIntPipe) id: number) { + return { value: id }; + } + + @Get('test-exception/:id') + async testException(@Param('id') id: string) { + return this.appService.testException(id); + } + + @Get('test-expected-400-exception/:id') + async testExpected400Exception(@Param('id') id: string) { + return this.appService.testExpected400Exception(id); + } + + @Get('test-expected-500-exception/:id') + async testExpected500Exception(@Param('id') id: string) { + return this.appService.testExpected500Exception(id); + } + + @Get('test-expected-rpc-exception/:id') + async testExpectedRpcException(@Param('id') id: string) { + return this.appService.testExpectedRpcException(id); + } + + @Get('test-span-decorator-async') + async testSpanDecoratorAsync() { + return { result: await this.appService.testSpanDecoratorAsync() }; + } + + @Get('test-span-decorator-sync') + async testSpanDecoratorSync() { + return { result: await this.appService.testSpanDecoratorSync() }; + } + + @Get('kill-test-cron/:job') + async killTestCron(@Param('job') job: string) { + this.appService.killTestCron(job); + } + + @Get('flush') + async flush() { + await flush(); + } + + @Get('example-exception-global-filter') + async exampleExceptionGlobalFilter() { + throw new ExampleExceptionGlobalFilter(); + } + + @Get('example-exception-local-filter') + async exampleExceptionLocalFilter() { + throw new ExampleExceptionLocalFilter(); + } + + @Get('test-service-use') + testServiceWithUseMethod() { + return this.appService.use(); + } + + @Get('test-service-transform') + testServiceWithTransform() { + return this.appService.transform(); + } + + @Get('test-service-intercept') + testServiceWithIntercept() { + return this.appService.intercept(); + } + + @Get('test-service-canActivate') + testServiceWithCanActivate() { + return this.appService.canActivate(); + } + + @Get('test-function-name') + testFunctionName() { + return this.appService.getFunctionName(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/app.module.ts new file mode 100644 index 000000000000..3de3c82dc925 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/app.module.ts @@ -0,0 +1,29 @@ +import { MiddlewareConsumer, Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { ScheduleModule } from '@nestjs/schedule'; +import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { ExampleGlobalFilter } from './example-global.filter'; +import { ExampleMiddleware } from './example.middleware'; + +@Module({ + imports: [SentryModule.forRoot(), ScheduleModule.forRoot()], + controllers: [AppController], + providers: [ + AppService, + { + provide: APP_FILTER, + useClass: SentryGlobalFilter, + }, + { + provide: APP_FILTER, + useClass: ExampleGlobalFilter, + }, + ], +}) +export class AppModule { + configure(consumer: MiddlewareConsumer): void { + consumer.apply(ExampleMiddleware).forRoutes('test-middleware-instrumentation'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/app.service.ts new file mode 100644 index 000000000000..242b4c778a0e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/app.service.ts @@ -0,0 +1,113 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { RpcException } from '@nestjs/microservices'; +import { Cron, SchedulerRegistry } from '@nestjs/schedule'; +import type { MonitorConfig } from '@sentry/core'; +import * as Sentry from '@sentry/nestjs'; +import { SentryCron, SentryTraced } from '@sentry/nestjs'; + +const monitorConfig: MonitorConfig = { + schedule: { + type: 'crontab', + value: '* * * * *', + }, +}; + +@Injectable() +export class AppService { + constructor(private schedulerRegistry: SchedulerRegistry) {} + + testTransaction() { + Sentry.startSpan({ name: 'test-span' }, () => { + Sentry.startSpan({ name: 'child-span' }, () => {}); + }); + } + + testSpan() { + // span that should not be a child span of the middleware span + Sentry.startSpan({ name: 'test-controller-span' }, () => {}); + } + + testException(id: string) { + throw new Error(`This is an exception with id ${id}`); + } + + testExpected400Exception(id: string) { + throw new HttpException(`This is an expected 400 exception with id ${id}`, HttpStatus.BAD_REQUEST); + } + + testExpected500Exception(id: string) { + throw new HttpException(`This is an expected 500 exception with id ${id}`, HttpStatus.INTERNAL_SERVER_ERROR); + } + + testExpectedRpcException(id: string) { + throw new RpcException(`This is an expected RPC exception with id ${id}`); + } + + @SentryTraced('wait and return a string') + async wait() { + await new Promise(resolve => setTimeout(resolve, 500)); + return 'test'; + } + + async testSpanDecoratorAsync() { + return await this.wait(); + } + + @SentryTraced('return a string') + getString(): { result: string } { + return { result: 'test' }; + } + + @SentryTraced('return the function name') + getFunctionName(): { result: string } { + return { result: this.getFunctionName.name }; + } + + async testSpanDecoratorSync() { + const returned = this.getString(); + // Will fail if getString() is async, because returned will be a Promise<> + return returned.result; + } + + /* + Actual cron schedule differs from schedule defined in config because Sentry + only supports minute granularity, but we don't want to wait (worst case) a + full minute for the tests to finish. + */ + @Cron('*/5 * * * * *', { name: 'test-cron-job' }) + @SentryCron('test-cron-slug', monitorConfig) + async testCron() { + console.log('Test cron!'); + } + + /* + Actual cron schedule differs from schedule defined in config because Sentry + only supports minute granularity, but we don't want to wait (worst case) a + full minute for the tests to finish. + */ + @Cron('*/5 * * * * *', { name: 'test-cron-error' }) + @SentryCron('test-cron-error-slug', monitorConfig) + async testCronError() { + throw new Error('Test error from cron job'); + } + + async killTestCron(job: string) { + this.schedulerRegistry.deleteCronJob(job); + } + + use() { + console.log('Test use!'); + } + + transform() { + console.log('Test transform!'); + } + + intercept() { + console.log('Test intercept!'); + } + + canActivate() { + console.log('Test canActivate!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/async-example.interceptor.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/async-example.interceptor.ts new file mode 100644 index 000000000000..ac0ee60acc51 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/async-example.interceptor.ts @@ -0,0 +1,17 @@ +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; +import { tap } from 'rxjs'; + +@Injectable() +export class AsyncInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler) { + Sentry.startSpan({ name: 'test-async-interceptor-span' }, () => {}); + return Promise.resolve( + next.handle().pipe( + tap(() => { + Sentry.startSpan({ name: 'test-async-interceptor-span-after-route' }, () => {}); + }), + ), + ); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-1.interceptor.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-1.interceptor.ts new file mode 100644 index 000000000000..81c9f70d30e2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-1.interceptor.ts @@ -0,0 +1,15 @@ +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; +import { tap } from 'rxjs'; + +@Injectable() +export class ExampleInterceptor1 implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler) { + Sentry.startSpan({ name: 'test-interceptor-span-1' }, () => {}); + return next.handle().pipe( + tap(() => { + Sentry.startSpan({ name: 'test-interceptor-span-after-route' }, () => {}); + }), + ); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-2.interceptor.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-2.interceptor.ts new file mode 100644 index 000000000000..2cf9dfb9e043 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-2.interceptor.ts @@ -0,0 +1,10 @@ +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; + +@Injectable() +export class ExampleInterceptor2 implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler) { + Sentry.startSpan({ name: 'test-interceptor-span-2' }, () => {}); + return next.handle().pipe(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-global-filter.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-global-filter.exception.ts new file mode 100644 index 000000000000..41981ba748fe --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-global-filter.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionGlobalFilter extends Error { + constructor() { + super('Original global example exception!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-global.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-global.filter.ts new file mode 100644 index 000000000000..988696d0e13d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-global.filter.ts @@ -0,0 +1,19 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common'; +import { Request, Response } from 'express'; +import { ExampleExceptionGlobalFilter } from './example-global-filter.exception'; + +@Catch(ExampleExceptionGlobalFilter) +export class ExampleGlobalFilter implements ExceptionFilter { + catch(exception: BadRequestException, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + response.status(400).json({ + statusCode: 400, + timestamp: new Date().toISOString(), + path: request.url, + message: 'Example exception was handled by global filter!', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-local-filter.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-local-filter.exception.ts new file mode 100644 index 000000000000..8f76520a3b94 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-local-filter.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionLocalFilter extends Error { + constructor() { + super('Original local example exception!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-local.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-local.filter.ts new file mode 100644 index 000000000000..505217f5dcbd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example-local.filter.ts @@ -0,0 +1,19 @@ +import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common'; +import { Request, Response } from 'express'; +import { ExampleExceptionLocalFilter } from './example-local-filter.exception'; + +@Catch(ExampleExceptionLocalFilter) +export class ExampleLocalFilter implements ExceptionFilter { + catch(exception: BadRequestException, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + response.status(400).json({ + statusCode: 400, + timestamp: new Date().toISOString(), + path: request.url, + message: 'Example exception was handled by local filter!', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/example.guard.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example.guard.ts new file mode 100644 index 000000000000..e12bbdc4e994 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example.guard.ts @@ -0,0 +1,10 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; + +@Injectable() +export class ExampleGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean { + Sentry.startSpan({ name: 'test-guard-span' }, () => {}); + return true; + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/example.middleware.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example.middleware.ts new file mode 100644 index 000000000000..31d15c9372ea --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/example.middleware.ts @@ -0,0 +1,12 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; +import { NextFunction, Request, Response } from 'express'; + +@Injectable() +export class ExampleMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction) { + // span that should be a child span of the middleware span + Sentry.startSpan({ name: 'test-middleware-span' }, () => {}); + next(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/instrument.ts new file mode 100644 index 000000000000..4f16ebb36d11 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/instrument.ts @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/nestjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1, + transportOptions: { + // We expect the app to send a lot of events in a short time + bufferSize: 1000, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/src/main.ts new file mode 100644 index 000000000000..71ce685f4d61 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/src/main.ts @@ -0,0 +1,15 @@ +// Import this first +import './instrument'; + +// Import other modules +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +const PORT = 3030; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(PORT); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nestjs-11/start-event-proxy.mjs new file mode 100644 index 000000000000..d61ed3b5d609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nestjs-11', +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/cron-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/cron-decorator.test.ts new file mode 100644 index 000000000000..e6ac7ae855ae --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/cron-decorator.test.ts @@ -0,0 +1,81 @@ +import { expect, test } from '@playwright/test'; +import { waitForEnvelopeItem, waitForError } from '@sentry-internal/test-utils'; + +test('Cron job triggers send of in_progress envelope', async ({ baseURL }) => { + const inProgressEnvelopePromise = waitForEnvelopeItem('nestjs-11', envelope => { + return ( + envelope[0].type === 'check_in' && + envelope[1]['monitor_slug'] === 'test-cron-slug' && + envelope[1]['status'] === 'in_progress' + ); + }); + + const okEnvelopePromise = waitForEnvelopeItem('nestjs-11', envelope => { + return ( + envelope[0].type === 'check_in' && + envelope[1]['monitor_slug'] === 'test-cron-slug' && + envelope[1]['status'] === 'ok' + ); + }); + + const inProgressEnvelope = await inProgressEnvelopePromise; + const okEnvelope = await okEnvelopePromise; + + expect(inProgressEnvelope[1]).toEqual( + expect.objectContaining({ + check_in_id: expect.any(String), + monitor_slug: 'test-cron-slug', + status: 'in_progress', + environment: 'qa', + monitor_config: { + schedule: { + type: 'crontab', + value: '* * * * *', + }, + }, + contexts: { + trace: { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + }), + ); + + expect(okEnvelope[1]).toEqual( + expect.objectContaining({ + check_in_id: expect.any(String), + monitor_slug: 'test-cron-slug', + status: 'ok', + environment: 'qa', + duration: expect.any(Number), + contexts: { + trace: { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + }), + ); + + // kill cron so tests don't get stuck + await fetch(`${baseURL}/kill-test-cron/test-cron-job`); +}); + +test('Sends exceptions to Sentry on error in cron job', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs-11', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Test error from cron job'; + }); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('Test error from cron job'); + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + }); + + // kill cron so tests don't get stuck + await fetch(`${baseURL}/kill-test-cron/test-cron-error`); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/errors.test.ts new file mode 100644 index 000000000000..0fa13fea32aa --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/errors.test.ts @@ -0,0 +1,171 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends exception to Sentry', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs-11', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; + }); + + const response = await fetch(`${baseURL}/test-exception/123`); + expect(response.status).toBe(500); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/test-exception/123', + }); + + expect(errorEvent.transaction).toEqual('GET /test-exception/:id'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + }); +}); + +test('Does not send HttpExceptions to Sentry', async ({ baseURL }) => { + let errorEventOccurred = false; + + waitForError('nestjs-11', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 400 exception with id 123') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /test-expected-400-exception/:id'; + }); + + waitForError('nestjs-11', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected 500 exception with id 123') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /test-expected-500-exception/:id'; + }); + + const transactionEventPromise400 = waitForTransaction('nestjs-11', transactionEvent => { + // todo(express-5): parametrize /test-expected-400-exception/:id + return transactionEvent?.transaction === 'GET /test-expected-400-exception/123'; + }); + + const transactionEventPromise500 = waitForTransaction('nestjs-11', transactionEvent => { + // todo(express-5): parametrize /test-expected-500-exception/:id + return transactionEvent?.transaction === 'GET /test-expected-500-exception/123'; + }); + + const response400 = await fetch(`${baseURL}/test-expected-400-exception/123`); + expect(response400.status).toBe(400); + + const response500 = await fetch(`${baseURL}/test-expected-500-exception/123`); + expect(response500.status).toBe(500); + + await transactionEventPromise400; + await transactionEventPromise500; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Does not send RpcExceptions to Sentry', async ({ baseURL }) => { + let errorEventOccurred = false; + + waitForError('nestjs-11', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected RPC exception with id 123') { + errorEventOccurred = true; + } + + // todo(express-5): parametrize /test-expected-rpc-exception/:id + return event?.transaction === 'GET /test-expected-rpc-exception/123'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + // todo(express-5): parametrize /test-expected-rpc-exception/:id + return transactionEvent?.transaction === 'GET /test-expected-rpc-exception/123'; + }); + + const response = await fetch(`${baseURL}/test-expected-rpc-exception/123`); + expect(response.status).toBe(500); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Global exception filter registered in main module is applied and exception is not sent to Sentry', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-11', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by global filter!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-exception-global-filter'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-exception-global-filter'; + }); + + const response = await fetch(`${baseURL}/example-exception-global-filter`); + const responseBody = await response.json(); + + expect(response.status).toBe(400); + expect(responseBody).toEqual({ + statusCode: 400, + timestamp: expect.any(String), + path: '/example-exception-global-filter', + message: 'Example exception was handled by global filter!', + }); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); + +test('Local exception filter registered in main module is applied and exception is not sent to Sentry', async ({ + baseURL, +}) => { + let errorEventOccurred = false; + + waitForError('nestjs-11', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Example exception was handled by local filter!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-exception-local-filter'; + }); + + const transactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-exception-local-filter'; + }); + + const response = await fetch(`${baseURL}/example-exception-local-filter`); + const responseBody = await response.json(); + + expect(response.status).toBe(400); + expect(responseBody).toEqual({ + statusCode: 400, + timestamp: expect.any(String), + path: '/example-exception-local-filter', + message: 'Example exception was handled by local filter!', + }); + + await transactionEventPromise; + + (await fetch(`${baseURL}/flush`)).text(); + + expect(errorEventOccurred).toBe(false); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/span-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/span-decorator.test.ts new file mode 100644 index 000000000000..3d63c1d17953 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/span-decorator.test.ts @@ -0,0 +1,79 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Transaction includes span and correct value for decorated async function', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-span-decorator-async' + ); + }); + + const response = await fetch(`${baseURL}/test-span-decorator-async`); + const body = await response.json(); + + expect(body.result).toEqual('test'); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.origin': 'manual', + 'sentry.op': 'wait and return a string', + }, + description: 'wait', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + status: 'ok', + op: 'wait and return a string', + origin: 'manual', + }), + ]), + ); +}); + +test('Transaction includes span and correct value for decorated sync function', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-span-decorator-sync' + ); + }); + + const response = await fetch(`${baseURL}/test-span-decorator-sync`); + const body = await response.json(); + + expect(body.result).toEqual('test'); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.origin': 'manual', + 'sentry.op': 'return a string', + }, + description: 'getString', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + status: 'ok', + op: 'return a string', + origin: 'manual', + }), + ]), + ); +}); + +test('preserves original function name on decorated functions', async ({ baseURL }) => { + const response = await fetch(`${baseURL}/test-function-name`); + const body = await response.json(); + + expect(body.result).toEqual('getFunctionName'); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts new file mode 100644 index 000000000000..7e0947d53ec1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tests/transactions.test.ts @@ -0,0 +1,732 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends an API route transaction', async ({ baseURL }) => { + const pageloadTransactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-transaction' + ); + }); + + await fetch(`${baseURL}/test-transaction`); + + const transactionEvent = await pageloadTransactionEventPromise; + + expect(transactionEvent.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'url', // todo(express-5): 'route' + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: 'http://localhost:3030/test-transaction', + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': 'http://localhost:3030/test-transaction', + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': '/test-transaction', + 'http.user_agent': 'node', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + // 'http.route': '/test-transaction', // todo(express-5): add this line again + }, + op: 'http.server', + span_id: expect.stringMatching(/[a-f0-9]{16}/), + status: 'ok', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.http.otel.http', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + /* todo(express-5): add this part again + { + data: { + 'express.name': '/test-transaction', + 'express.type': 'request_handler', + 'http.route': '/test-transaction', + 'sentry.origin': 'auto.http.otel.express', + 'sentry.op': 'request_handler.express', + }, + op: 'request_handler.express', + description: '/test-transaction', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'auto.http.otel.express', + }, */ + { + data: { + 'sentry.origin': 'manual', + }, + description: 'test-span', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'manual', + }, + { + data: { + 'sentry.origin': 'manual', + }, + description: 'child-span', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + origin: 'manual', + }, + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.origin': 'auto.http.otel.nestjs', + 'sentry.op': 'handler.nestjs', + component: '@nestjs/core', + 'nestjs.version': expect.any(String), + 'nestjs.type': 'handler', + 'nestjs.callback': 'testTransaction', + }, + description: 'testTransaction', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'auto.http.otel.nestjs', + op: 'handler.nestjs', + }, + ]), + transaction: 'GET /test-transaction', + type: 'transaction', + transaction_info: { + source: 'url', // todo(express-5): 'route' + }, + }), + ); +}); + +test('API route transaction includes nest middleware span. Spans created in and after middleware are nested correctly', async ({ + baseURL, +}) => { + const pageloadTransactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-middleware-instrumentation' + ); + }); + + const response = await fetch(`${baseURL}/test-middleware-instrumentation`); + expect(response.status).toBe(200); + + const transactionEvent = await pageloadTransactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'ExampleMiddleware', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); + + const exampleMiddlewareSpan = transactionEvent.spans.find(span => span.description === 'ExampleMiddleware'); + const exampleMiddlewareSpanId = exampleMiddlewareSpan?.span_id; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-controller-span', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-middleware-span', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + ]), + }), + ); + + // verify correct span parent-child relationships + const testMiddlewareSpan = transactionEvent.spans.find(span => span.description === 'test-middleware-span'); + const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span'); + + // 'ExampleMiddleware' is the parent of 'test-middleware-span' + expect(testMiddlewareSpan.parent_span_id).toBe(exampleMiddlewareSpanId); + + // 'ExampleMiddleware' is NOT the parent of 'test-controller-span' + expect(testControllerSpan.parent_span_id).not.toBe(exampleMiddlewareSpanId); +}); + +test('API route transaction includes nest guard span and span started in guard is nested correctly', async ({ + baseURL, +}) => { + const transactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-guard-instrumentation' + ); + }); + + const response = await fetch(`${baseURL}/test-guard-instrumentation`); + expect(response.status).toBe(200); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'ExampleGuard', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); + + const exampleGuardSpan = transactionEvent.spans.find(span => span.description === 'ExampleGuard'); + const exampleGuardSpanId = exampleGuardSpan?.span_id; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-guard-span', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + ]), + }), + ); + + // verify correct span parent-child relationships + const testGuardSpan = transactionEvent.spans.find(span => span.description === 'test-guard-span'); + + // 'ExampleGuard' is the parent of 'test-guard-span' + expect(testGuardSpan.parent_span_id).toBe(exampleGuardSpanId); +}); + +test('API route transaction includes nest pipe span for valid request', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + // todo(express-5): parametrize test-pipe-instrumentation/:id + transactionEvent?.transaction === 'GET /test-pipe-instrumentation/123' && + transactionEvent?.request?.url?.includes('/test-pipe-instrumentation/123') + ); + }); + + const response = await fetch(`${baseURL}/test-pipe-instrumentation/123`); + expect(response.status).toBe(200); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'ParseIntPipe', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); +}); + +test('API route transaction includes nest pipe span for invalid request', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + // todo(express-5): parametrize test-pipe-instrumentation/:id + transactionEvent?.transaction === 'GET /test-pipe-instrumentation/abc' && + transactionEvent?.request?.url?.includes('/test-pipe-instrumentation/abc') + ); + }); + + const response = await fetch(`${baseURL}/test-pipe-instrumentation/abc`); + expect(response.status).toBe(400); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'ParseIntPipe', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'unknown_error', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); +}); + +test('API route transaction includes nest interceptor spans before route execution. Spans created in and after interceptor are nested correctly', async ({ + baseURL, +}) => { + const pageloadTransactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-interceptor-instrumentation' + ); + }); + + const response = await fetch(`${baseURL}/test-interceptor-instrumentation`); + expect(response.status).toBe(200); + + const transactionEvent = await pageloadTransactionEventPromise; + + // check if interceptor spans before route execution exist + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'ExampleInterceptor1', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'ExampleInterceptor2', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); + + // get interceptor spans + const exampleInterceptor1Span = transactionEvent.spans.find(span => span.description === 'ExampleInterceptor1'); + const exampleInterceptor1SpanId = exampleInterceptor1Span?.span_id; + const exampleInterceptor2Span = transactionEvent.spans.find(span => span.description === 'ExampleInterceptor2'); + const exampleInterceptor2SpanId = exampleInterceptor2Span?.span_id; + + // check if manually started spans exist + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-controller-span', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-interceptor-span-1', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-interceptor-span-2', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + ]), + }), + ); + + // verify correct span parent-child relationships + const testInterceptor1Span = transactionEvent.spans.find(span => span.description === 'test-interceptor-span-1'); + const testInterceptor2Span = transactionEvent.spans.find(span => span.description === 'test-interceptor-span-2'); + const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span'); + + // 'ExampleInterceptor1' is the parent of 'test-interceptor-span-1' + expect(testInterceptor1Span.parent_span_id).toBe(exampleInterceptor1SpanId); + + // 'ExampleInterceptor1' is NOT the parent of 'test-controller-span' + expect(testControllerSpan.parent_span_id).not.toBe(exampleInterceptor1SpanId); + + // 'ExampleInterceptor2' is the parent of 'test-interceptor-span-2' + expect(testInterceptor2Span.parent_span_id).toBe(exampleInterceptor2SpanId); + + // 'ExampleInterceptor2' is NOT the parent of 'test-controller-span' + expect(testControllerSpan.parent_span_id).not.toBe(exampleInterceptor2SpanId); +}); + +test('API route transaction includes exactly one nest interceptor span after route execution. Spans created in controller and in interceptor are nested correctly', async ({ + baseURL, +}) => { + const pageloadTransactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-interceptor-instrumentation' + ); + }); + + const response = await fetch(`${baseURL}/test-interceptor-instrumentation`); + expect(response.status).toBe(200); + + const transactionEvent = await pageloadTransactionEventPromise; + + // check if interceptor spans after route execution exist + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'Interceptors - After Route', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); + + // check that exactly one after route span is sent + const allInterceptorSpansAfterRoute = transactionEvent.spans.filter( + span => span.description === 'Interceptors - After Route', + ); + expect(allInterceptorSpansAfterRoute.length).toBe(1); + + // get interceptor span + const exampleInterceptorSpanAfterRoute = transactionEvent.spans.find( + span => span.description === 'Interceptors - After Route', + ); + const exampleInterceptorSpanAfterRouteId = exampleInterceptorSpanAfterRoute?.span_id; + + // check if manually started span in interceptor after route exists + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-interceptor-span-after-route', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + ]), + }), + ); + + // verify correct span parent-child relationships + const testInterceptorSpanAfterRoute = transactionEvent.spans.find( + span => span.description === 'test-interceptor-span-after-route', + ); + const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span'); + + // 'Interceptor - After Route' is the parent of 'test-interceptor-span-after-route' + expect(testInterceptorSpanAfterRoute.parent_span_id).toBe(exampleInterceptorSpanAfterRouteId); + + // 'Interceptor - After Route' is NOT the parent of 'test-controller-span' + expect(testControllerSpan.parent_span_id).not.toBe(exampleInterceptorSpanAfterRouteId); +}); + +test('API route transaction includes nest async interceptor spans before route execution. Spans created in and after async interceptor are nested correctly', async ({ + baseURL, +}) => { + const pageloadTransactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-async-interceptor-instrumentation' + ); + }); + + const response = await fetch(`${baseURL}/test-async-interceptor-instrumentation`); + expect(response.status).toBe(200); + + const transactionEvent = await pageloadTransactionEventPromise; + + // check if interceptor spans before route execution exist + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'AsyncInterceptor', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); + + // get interceptor spans + const exampleAsyncInterceptor = transactionEvent.spans.find(span => span.description === 'AsyncInterceptor'); + const exampleAsyncInterceptorSpanId = exampleAsyncInterceptor?.span_id; + + // check if manually started spans exist + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-controller-span', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-async-interceptor-span', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + ]), + }), + ); + + // verify correct span parent-child relationships + const testAsyncInterceptorSpan = transactionEvent.spans.find( + span => span.description === 'test-async-interceptor-span', + ); + const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span'); + + // 'AsyncInterceptor' is the parent of 'test-async-interceptor-span' + expect(testAsyncInterceptorSpan.parent_span_id).toBe(exampleAsyncInterceptorSpanId); + + // 'AsyncInterceptor' is NOT the parent of 'test-controller-span' + expect(testControllerSpan.parent_span_id).not.toBe(exampleAsyncInterceptorSpanId); +}); + +test('API route transaction includes exactly one nest async interceptor span after route execution. Spans created in controller and in async interceptor are nested correctly', async ({ + baseURL, +}) => { + const pageloadTransactionEventPromise = waitForTransaction('nestjs-11', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-async-interceptor-instrumentation' + ); + }); + + const response = await fetch(`${baseURL}/test-async-interceptor-instrumentation`); + expect(response.status).toBe(200); + + const transactionEvent = await pageloadTransactionEventPromise; + + // check if interceptor spans after route execution exist + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: { + 'sentry.op': 'middleware.nestjs', + 'sentry.origin': 'auto.middleware.nestjs', + }, + description: 'Interceptors - After Route', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + op: 'middleware.nestjs', + origin: 'auto.middleware.nestjs', + }, + ]), + }), + ); + + // check that exactly one after route span is sent + const allInterceptorSpansAfterRoute = transactionEvent.spans.filter( + span => span.description === 'Interceptors - After Route', + ); + expect(allInterceptorSpansAfterRoute.length).toBe(1); + + // get interceptor span + const exampleInterceptorSpanAfterRoute = transactionEvent.spans.find( + span => span.description === 'Interceptors - After Route', + ); + const exampleInterceptorSpanAfterRouteId = exampleInterceptorSpanAfterRoute?.span_id; + + // check if manually started span in interceptor after route exists + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + span_id: expect.stringMatching(/[a-f0-9]{16}/), + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + data: expect.any(Object), + description: 'test-async-interceptor-span-after-route', + parent_span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'manual', + }, + ]), + }), + ); + + // verify correct span parent-child relationships + const testInterceptorSpanAfterRoute = transactionEvent.spans.find( + span => span.description === 'test-async-interceptor-span-after-route', + ); + const testControllerSpan = transactionEvent.spans.find(span => span.description === 'test-controller-span'); + + // 'Interceptor - After Route' is the parent of 'test-interceptor-span-after-route' + expect(testInterceptorSpanAfterRoute.parent_span_id).toBe(exampleInterceptorSpanAfterRouteId); + + // 'Interceptor - After Route' is NOT the parent of 'test-controller-span' + expect(testControllerSpan.parent_span_id).not.toBe(exampleInterceptorSpanAfterRouteId); +}); + +test('Calling use method on service with Injectable decorator returns 200', async ({ baseURL }) => { + const response = await fetch(`${baseURL}/test-service-use`); + expect(response.status).toBe(200); +}); + +test('Calling transform method on service with Injectable decorator returns 200', async ({ baseURL }) => { + const response = await fetch(`${baseURL}/test-service-transform`); + expect(response.status).toBe(200); +}); + +test('Calling intercept method on service with Injectable decorator returns 200', async ({ baseURL }) => { + const response = await fetch(`${baseURL}/test-service-intercept`); + expect(response.status).toBe(200); +}); + +test('Calling canActivate method on service with Injectable decorator returns 200', async ({ baseURL }) => { + const response = await fetch(`${baseURL}/test-service-canActivate`); + expect(response.status).toBe(200); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/nestjs-11/tsconfig.build.json new file mode 100644 index 000000000000..26c30d4eddf2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist"] +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-11/tsconfig.json new file mode 100644 index 000000000000..cf79f029c781 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "moduleResolution": "Node16" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts index 96b60e5d976f..337e98decc31 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/tests/transactions.test.ts @@ -92,7 +92,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { component: '@nestjs/core', 'nestjs.version': expect.any(String), 'nestjs.type': 'request_context', - 'http.method': 'GET', + 'http.request.method': 'GET', 'http.url': '/test-transaction', 'http.route': '/test-transaction', 'nestjs.controller': 'AppController', diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index ec5cbdf4668a..1ac2f8af6549 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -45,18 +45,20 @@ }, "dependencies": { "@opentelemetry/core": "^1.30.1", + "@opentelemetry/api": "^1.9.0", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/instrumentation-nestjs-core": "0.44.0", + "@opentelemetry/semantic-conventions": "^1.27.0", "@sentry/core": "9.0.0-alpha.2", "@sentry/node": "9.0.0-alpha.2" }, "devDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0" }, "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/nestjs/src/integrations/nest.ts b/packages/nestjs/src/integrations/nest.ts index b7f1a8ef1485..4cc68c720541 100644 --- a/packages/nestjs/src/integrations/nest.ts +++ b/packages/nestjs/src/integrations/nest.ts @@ -1,6 +1,6 @@ -import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { defineIntegration } from '@sentry/core'; import { generateInstrumentOnce } from '@sentry/node'; +import { NestInstrumentation } from './sentry-nest-core-instrumentation'; import { SentryNestEventInstrumentation } from './sentry-nest-event-instrumentation'; import { SentryNestInstrumentation } from './sentry-nest-instrumentation'; diff --git a/packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts new file mode 100644 index 000000000000..a7fdb66558ba --- /dev/null +++ b/packages/nestjs/src/integrations/sentry-nest-core-instrumentation.ts @@ -0,0 +1,308 @@ +/* + * This file is based on code from the OpenTelemetry Authors + * Source: https://github.com/open-telemetry/opentelemetry-js-contrib + * + * Modified for immediate requirements while maintaining compliance + * with the original Apache 2.0 license terms. + * + * Original License: + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Controller } from '@nestjs/common/interfaces'; +import type { NestFactory } from '@nestjs/core/nest-factory.js'; +import type { RouterExecutionContext } from '@nestjs/core/router/router-execution-context.js'; +import * as api from '@opentelemetry/api'; +import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; +import { + InstrumentationBase, + InstrumentationNodeModuleDefinition, + InstrumentationNodeModuleFile, + isWrapped, +} from '@opentelemetry/instrumentation'; +import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE, SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; + +import { SDK_VERSION } from '@sentry/core'; + +const supportedVersions = ['>=4.0.0 <12']; +const COMPONENT = '@nestjs/core'; + +enum AttributeNames { + VERSION = 'nestjs.version', + TYPE = 'nestjs.type', + MODULE = 'nestjs.module', + CONTROLLER = 'nestjs.controller', + CALLBACK = 'nestjs.callback', + PIPES = 'nestjs.pipes', + INTERCEPTORS = 'nestjs.interceptors', + GUARDS = 'nestjs.guards', +} + +export enum NestType { + APP_CREATION = 'app_creation', + REQUEST_CONTEXT = 'request_context', + REQUEST_HANDLER = 'handler', +} + +/** + * + */ +export class NestInstrumentation extends InstrumentationBase { + public constructor(config: InstrumentationConfig = {}) { + super('sentry-nestjs', SDK_VERSION, config); + } + + /** + * + */ + public init(): InstrumentationNodeModuleDefinition { + const module = new InstrumentationNodeModuleDefinition(COMPONENT, supportedVersions); + + module.files.push( + this._getNestFactoryFileInstrumentation(supportedVersions), + this._getRouterExecutionContextFileInstrumentation(supportedVersions), + ); + + return module; + } + + /** + * + */ + private _getNestFactoryFileInstrumentation(versions: string[]): InstrumentationNodeModuleFile { + return new InstrumentationNodeModuleFile( + '@nestjs/core/nest-factory.js', + versions, + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (NestFactoryStatic: any, moduleVersion?: string) => { + this._ensureWrapped( + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + NestFactoryStatic.NestFactoryStatic.prototype, + 'create', + createWrapNestFactoryCreate(this.tracer, moduleVersion), + ); + return NestFactoryStatic; + }, + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (NestFactoryStatic: any) => { + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + this._unwrap(NestFactoryStatic.NestFactoryStatic.prototype, 'create'); + }, + ); + } + + /** + * + */ + private _getRouterExecutionContextFileInstrumentation(versions: string[]): InstrumentationNodeModuleFile { + return new InstrumentationNodeModuleFile( + '@nestjs/core/router/router-execution-context.js', + versions, + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (RouterExecutionContext: any, moduleVersion?: string) => { + this._ensureWrapped( + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + RouterExecutionContext.RouterExecutionContext.prototype, + 'create', + createWrapCreateHandler(this.tracer, moduleVersion), + ); + return RouterExecutionContext; + }, + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (RouterExecutionContext: any) => { + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + this._unwrap(RouterExecutionContext.RouterExecutionContext.prototype, 'create'); + }, + ); + } + + /** + * + */ + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private _ensureWrapped(obj: any, methodName: string, wrapper: (original: any) => any): void { + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (isWrapped(obj[methodName])) { + this._unwrap(obj, methodName); + } + this._wrap(obj, methodName, wrapper); + } +} + +function createWrapNestFactoryCreate(tracer: api.Tracer, moduleVersion?: string) { + return function wrapCreate(original: typeof NestFactory.create) { + return function createWithTrace( + this: typeof NestFactory, + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + nestModule: any, + /* serverOrOptions */ + ) { + const span = tracer.startSpan('Create Nest App', { + attributes: { + component: COMPONENT, + [AttributeNames.TYPE]: NestType.APP_CREATION, + [AttributeNames.VERSION]: moduleVersion, + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + [AttributeNames.MODULE]: nestModule.name, + }, + }); + const spanContext = api.trace.setSpan(api.context.active(), span); + + return api.context.with(spanContext, async () => { + try { + // todo + // eslint-disable-next-line prefer-rest-params, @typescript-eslint/no-explicit-any + return await original.apply(this, arguments as any); + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + throw addError(span, e); + } finally { + span.end(); + } + }); + }; + }; +} + +function createWrapCreateHandler(tracer: api.Tracer, moduleVersion?: string) { + return function wrapCreateHandler(original: RouterExecutionContext['create']) { + return function createHandlerWithTrace( + this: RouterExecutionContext, + instance: Controller, + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback: (...args: any[]) => unknown, + ) { + // todo + // eslint-disable-next-line prefer-rest-params + arguments[1] = createWrapHandler(tracer, moduleVersion, callback); + // todo + // eslint-disable-next-line prefer-rest-params, @typescript-eslint/no-explicit-any + const handler = original.apply(this, arguments as any); + const callbackName = callback.name; + const instanceName = + // todo + // eslint-disable-next-line @typescript-eslint/prefer-optional-chain + instance.constructor && instance.constructor.name ? instance.constructor.name : 'UnnamedInstance'; + const spanName = callbackName ? `${instanceName}.${callbackName}` : instanceName; + + // todo + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + return function (this: any, req: any, res: any, next: (...args: any[]) => unknown) { + const span = tracer.startSpan(spanName, { + attributes: { + component: COMPONENT, + [AttributeNames.VERSION]: moduleVersion, + [AttributeNames.TYPE]: NestType.REQUEST_CONTEXT, + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + [ATTR_HTTP_REQUEST_METHOD]: req.method, + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, deprecation/deprecation + [SEMATTRS_HTTP_URL]: req.originalUrl || req.url, + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + [ATTR_HTTP_ROUTE]: req.route?.path || req.routeOptions?.url || req.routerPath, + [AttributeNames.CONTROLLER]: instanceName, + [AttributeNames.CALLBACK]: callbackName, + }, + }); + const spanContext = api.trace.setSpan(api.context.active(), span); + + return api.context.with(spanContext, async () => { + try { + // todo + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, prefer-rest-params + return await handler.apply(this, arguments as unknown); + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + throw addError(span, e); + } finally { + span.end(); + } + }); + }; + }; + }; +} + +function createWrapHandler( + tracer: api.Tracer, + moduleVersion: string | undefined, + // todo + // eslint-disable-next-line @typescript-eslint/ban-types + handler: Function, + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): (this: RouterExecutionContext) => Promise { + const spanName = handler.name || 'anonymous nest handler'; + const options = { + attributes: { + component: COMPONENT, + [AttributeNames.VERSION]: moduleVersion, + [AttributeNames.TYPE]: NestType.REQUEST_HANDLER, + [AttributeNames.CALLBACK]: handler.name, + }, + }; + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const wrappedHandler = function (this: RouterExecutionContext): Promise { + const span = tracer.startSpan(spanName, options); + const spanContext = api.trace.setSpan(api.context.active(), span); + + return api.context.with(spanContext, async () => { + try { + // todo + // eslint-disable-next-line prefer-rest-params + return await handler.apply(this, arguments); + // todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + throw addError(span, e); + } finally { + span.end(); + } + }); + }; + + if (handler.name) { + Object.defineProperty(wrappedHandler, 'name', { value: handler.name }); + } + + // Get the current metadata and set onto the wrapper to ensure other decorators ( ie: NestJS EventPattern / RolesGuard ) + // won't be affected by the use of this instrumentation + Reflect.getMetadataKeys(handler).forEach(metadataKey => { + Reflect.defineMetadata(metadataKey, Reflect.getMetadata(metadataKey, handler), wrappedHandler); + }); + return wrappedHandler; +} + +const addError = (span: api.Span, error: Error): Error => { + span.recordException(error); + span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message }); + return error; +}; diff --git a/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts b/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts index ea7d65176aed..c4f36728c906 100644 --- a/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts +++ b/packages/nestjs/src/integrations/sentry-nest-instrumentation.ts @@ -19,7 +19,7 @@ import { import { getMiddlewareSpanOptions, getNextProxy, instrumentObservable, isPatched } from './helpers'; import type { CallHandler, CatchTarget, InjectableTarget, MinimalNestJsExecutionContext, Observable } from './types'; -const supportedVersions = ['>=8.0.0 <11']; +const supportedVersions = ['>=8.0.0 <12']; const COMPONENT = '@nestjs/common'; /** diff --git a/yarn.lock b/yarn.lock index 7ea515fcca18..794c60e3975a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4773,14 +4773,14 @@ iterare "1.2.1" tslib "2.7.0" -"@nestjs/common@^8.0.0 || ^9.0.0 || ^10.0.0": - version "10.4.7" - resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.7.tgz#076cb77c06149805cb1e193d8cdc69bbe8446c75" - integrity sha512-gIOpjD3Mx8gfYGxYm/RHPcJzqdknNNFCyY+AxzBT3gc5Xvvik1Dn5OxaMGw5EbVfhZgJKVP0n83giUOAlZQe7w== +"@nestjs/common@^10.0.0": + version "10.4.15" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.15.tgz#27c291466d9100eb86fdbe6f7bbb4d1a6ad55f70" + integrity sha512-vaLg1ZgwhG29BuLDxPA9OAcIlgqzp9/N8iG0wGapyUNTf4IY4O6zAHgN6QalwLhFxq7nOI021vdRojR1oF3bqg== dependencies: uid "2.0.2" iterare "1.2.1" - tslib "2.7.0" + tslib "2.8.1" "@nestjs/core@10.4.6": version "10.4.6" @@ -4794,17 +4794,17 @@ path-to-regexp "3.3.0" tslib "2.7.0" -"@nestjs/core@^8.0.0 || ^9.0.0 || ^10.0.0": - version "10.4.7" - resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.4.7.tgz#adb27067a8c40b79f0713b417457fdfc6cf3406a" - integrity sha512-AIpQzW/vGGqSLkKvll1R7uaSNv99AxZI2EFyVJPNGDgFsfXaohfV1Ukl6f+s75Km+6Fj/7aNl80EqzNWQCS8Ig== +"@nestjs/core@^10.0.0": + version "10.4.15" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.4.15.tgz#1343a3395d5c54e9b792608cb75eef39053806d5" + integrity sha512-UBejmdiYwaH6fTsz2QFBlC1cJHM+3UDeLZN+CiP9I1fRv2KlBZsmozGLbV5eS1JAVWJB4T5N5yQ0gjN8ZvcS2w== dependencies: uid "2.0.2" "@nuxtjs/opencollective" "0.3.2" fast-safe-stringify "2.1.1" iterare "1.2.1" path-to-regexp "3.3.0" - tslib "2.7.0" + tslib "2.8.1" "@nestjs/platform-express@10.4.6": version "10.4.6" @@ -28623,6 +28623,11 @@ tslib@2.7.0, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2. resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== +tslib@2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" From 1c7edabf0c1492329cef335130f0847fac3b43ee Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 11 Feb 2025 11:29:13 +0100 Subject: [PATCH 5/7] ci: Downgrade runners to `ubuntu-20.04` (#15367) --- .github/workflows/auto-release.yml | 2 +- .github/workflows/build.yml | 38 +++++++++---------- .github/workflows/canary.yml | 4 +- .github/workflows/clear-cache.yml | 2 +- .../workflows/enforce-license-compliance.yml | 2 +- .github/workflows/external-contributors.yml | 2 +- .github/workflows/flaky-test-detector.yml | 2 +- .github/workflows/gitflow-sync-develop.yml | 2 +- .github/workflows/release-comment-issues.yml | 2 +- .github/workflows/release-size-info.yml | 2 +- .github/workflows/release.yml | 2 +- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 39e73722dfa5..dd41d7d50e5e 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -9,7 +9,7 @@ on: # This workflow tirggers a release when merging a branch with the pattern `prepare-release/VERSION` into master. jobs: release: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 name: 'Prepare a new version' steps: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7733c451171f..77c94ab78a11 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ env: jobs: job_get_metadata: name: Get Metadata - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 permissions: pull-requests: read steps: @@ -118,7 +118,7 @@ jobs: job_build: name: Build needs: job_get_metadata - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 15 if: | needs.job_get_metadata.outputs.changed_any_code == 'true' || @@ -196,7 +196,7 @@ jobs: job_check_branches: name: Check PR branches needs: job_get_metadata - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 if: github.event_name == 'pull_request' permissions: pull-requests: write @@ -212,7 +212,7 @@ jobs: name: Size Check needs: [job_get_metadata, job_build] timeout-minutes: 15 - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 if: github.event_name == 'pull_request' || needs.job_get_metadata.outputs.is_base_branch == 'true' || needs.job_get_metadata.outputs.is_release == 'true' @@ -242,7 +242,7 @@ jobs: # inter-package dependencies resolve cleanly. needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -267,7 +267,7 @@ jobs: name: Check file formatting needs: [job_get_metadata] timeout-minutes: 10 - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -290,7 +290,7 @@ jobs: name: Circular Dependency Check needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -310,7 +310,7 @@ jobs: job_artifacts: name: Upload Artifacts needs: [job_get_metadata, job_build] - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 # Build artifacts are only needed for releasing workflow. if: needs.job_get_metadata.outputs.is_release == 'true' steps: @@ -347,7 +347,7 @@ jobs: name: Browser Unit Tests needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - name: Check out base commit (${{ github.event.pull_request.base.sha }}) uses: actions/checkout@v4 @@ -394,7 +394,7 @@ jobs: needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_bun == 'true' || github.event_name != 'pull_request' timeout-minutes: 10 - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false steps: @@ -421,7 +421,7 @@ jobs: needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_deno == 'true' || github.event_name != 'pull_request' timeout-minutes: 10 - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false steps: @@ -451,7 +451,7 @@ jobs: name: Node (${{ matrix.node }}) Unit Tests needs: [job_get_metadata, job_build] timeout-minutes: 10 - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: @@ -594,7 +594,7 @@ jobs: name: PW ${{ matrix.bundle }} Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 15 strategy: fail-fast: false @@ -654,7 +654,7 @@ jobs: job_check_for_faulty_dts: name: Check for faulty .d.ts files needs: [job_get_metadata, job_build] - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 5 steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) @@ -682,7 +682,7 @@ jobs: Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_node_integration == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 15 strategy: fail-fast: false @@ -729,7 +729,7 @@ jobs: name: Remix (Node ${{ matrix.node }}) Tests needs: [job_get_metadata, job_build] if: needs.job_build.outputs.changed_remix == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 10 strategy: fail-fast: false @@ -833,7 +833,7 @@ jobs: # See: https://github.com/actions/runner/issues/2205 if: always() && needs.job_e2e_prepare.result == 'success' && needs.job_e2e_prepare.outputs.matrix != '{"include":[]}' needs: [job_get_metadata, job_build, job_e2e_prepare] - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 15 env: # We just use a dummy DSN here, only send to the tunnel anyhow @@ -955,7 +955,7 @@ jobs: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' needs: [job_get_metadata, job_build, job_e2e_prepare] - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 15 env: E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} @@ -1075,7 +1075,7 @@ jobs: ] # Always run this, even if a dependent job failed if: always() - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - name: Check for failures if: contains(needs.*.result, 'failure') diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 614a971623b3..24f25fd1ea9a 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -27,7 +27,7 @@ permissions: jobs: job_e2e_prepare: name: Prepare E2E Canary tests - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 30 steps: - name: Check out current commit @@ -54,7 +54,7 @@ jobs: job_e2e_tests: name: E2E ${{ matrix.label }} Test needs: [job_e2e_prepare] - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 20 env: # We just use a dummy DSN here, only send to the tunnel anyhow diff --git a/.github/workflows/clear-cache.yml b/.github/workflows/clear-cache.yml index 78f1e3f66586..5c327553e3b8 100644 --- a/.github/workflows/clear-cache.yml +++ b/.github/workflows/clear-cache.yml @@ -21,7 +21,7 @@ on: jobs: clear-caches: name: Delete all caches - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml index f83a03a51b42..776f8135178d 100644 --- a/.github/workflows/enforce-license-compliance.yml +++ b/.github/workflows/enforce-license-compliance.yml @@ -17,7 +17,7 @@ on: jobs: enforce-license-compliance: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 steps: - name: 'Enforce License Compliance' uses: getsentry/action-enforce-license-compliance@main diff --git a/.github/workflows/external-contributors.yml b/.github/workflows/external-contributors.yml index cc2cbdb72774..e9b1e05a2c92 100644 --- a/.github/workflows/external-contributors.yml +++ b/.github/workflows/external-contributors.yml @@ -12,7 +12,7 @@ jobs: permissions: pull-requests: write contents: write - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 if: | github.event.pull_request.merged == true && github.event.pull_request.author_association != 'COLLABORATOR' diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml index c24f306c98ea..190b95e6df18 100644 --- a/.github/workflows/flaky-test-detector.yml +++ b/.github/workflows/flaky-test-detector.yml @@ -23,7 +23,7 @@ concurrency: jobs: flaky-detector: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 timeout-minutes: 60 name: 'Check tests for flakiness' # Also skip if PR is from master -> develop diff --git a/.github/workflows/gitflow-sync-develop.yml b/.github/workflows/gitflow-sync-develop.yml index 9bf8b6a556d6..893dbbbf56fb 100644 --- a/.github/workflows/gitflow-sync-develop.yml +++ b/.github/workflows/gitflow-sync-develop.yml @@ -17,7 +17,7 @@ env: jobs: main: name: Create PR master->develop - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 permissions: pull-requests: write contents: write diff --git a/.github/workflows/release-comment-issues.yml b/.github/workflows/release-comment-issues.yml index ab4c45fc8f17..4bbcb29aba21 100644 --- a/.github/workflows/release-comment-issues.yml +++ b/.github/workflows/release-comment-issues.yml @@ -12,7 +12,7 @@ on: # This workflow is triggered when a release is published jobs: release-comment-issues: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 name: 'Notify issues' steps: - name: Get version diff --git a/.github/workflows/release-size-info.yml b/.github/workflows/release-size-info.yml index ea0cef636b8e..04e51e5ae14e 100644 --- a/.github/workflows/release-size-info.yml +++ b/.github/workflows/release-size-info.yml @@ -13,7 +13,7 @@ on: # It fetches the size-limit info from the release branch and adds it to the release jobs: release-size-info: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 name: 'Add size-limit info to release' steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99569fd7f1aa..2768f18a5bc8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ on: default: master jobs: release: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 name: 'Release a new version' steps: - name: Get auth token From 1774a9ea9d711fb6eec693c2039c8a678297aa9c Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 11 Feb 2025 07:53:14 +0000 Subject: [PATCH 6/7] meta(changelog): Update changelog for 9.0.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0116544b2de..5ddde6885dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 9.0.1 + +- ref(flags): rename unleash integration param ([#15343](https://github.com/getsentry/sentry-javascript/pull/15343)) + ## 9.0.0 Version `9.0.0` marks a release of the Sentry JavaScript SDKs that contains breaking changes. From 7fc8083335ae63b471794f6bbcb9da9c779652a5 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 11 Feb 2025 11:07:29 +0000 Subject: [PATCH 7/7] release: 9.0.1 --- .../browser-integration-tests/package.json | 4 ++-- .../bundle-analyzer-scenarios/package.json | 2 +- dev-packages/clear-cache-gh-action/package.json | 2 +- dev-packages/e2e-tests/package.json | 2 +- .../external-contributor-gh-action/package.json | 2 +- dev-packages/node-integration-tests/package.json | 8 ++++---- dev-packages/rollup-utils/package.json | 2 +- dev-packages/size-limit-gh-action/package.json | 2 +- dev-packages/test-utils/package.json | 4 ++-- lerna.json | 2 +- packages/angular/package.json | 6 +++--- packages/astro/package.json | 8 ++++---- packages/aws-serverless/package.json | 6 +++--- packages/browser-utils/package.json | 4 ++-- packages/browser/package.json | 14 +++++++------- packages/bun/package.json | 8 ++++---- packages/cloudflare/package.json | 4 ++-- packages/core/package.json | 2 +- packages/deno/package.json | 4 ++-- packages/ember/package.json | 6 +++--- packages/eslint-config-sdk/package.json | 6 +++--- packages/eslint-plugin-sdk/package.json | 2 +- packages/feedback/package.json | 4 ++-- packages/gatsby/package.json | 6 +++--- packages/google-cloud-serverless/package.json | 6 +++--- packages/integration-shims/package.json | 4 ++-- packages/nestjs/package.json | 8 ++++---- packages/nextjs/package.json | 14 +++++++------- packages/node/package.json | 6 +++--- packages/nuxt/package.json | 12 ++++++------ packages/opentelemetry/package.json | 4 ++-- packages/profiling-node/package.json | 6 +++--- packages/react-router/package.json | 12 ++++++------ packages/react/package.json | 6 +++--- packages/remix/package.json | 10 +++++----- packages/replay-canvas/package.json | 6 +++--- packages/replay-internal/package.json | 8 ++++---- packages/replay-worker/package.json | 2 +- packages/solid/package.json | 6 +++--- packages/solidstart/package.json | 10 +++++----- packages/svelte/package.json | 6 +++--- packages/sveltekit/package.json | 10 +++++----- packages/types/package.json | 4 ++-- packages/typescript/package.json | 2 +- packages/vercel-edge/package.json | 6 +++--- packages/vue/package.json | 6 +++--- packages/wasm/package.json | 6 +++--- 47 files changed, 135 insertions(+), 135 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index fd96ad273775..520591ada229 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/browser-integration-tests", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "main": "index.js", "license": "MIT", "engines": { @@ -42,7 +42,7 @@ "@babel/preset-typescript": "^7.16.7", "@playwright/test": "~1.50.0", "@sentry-internal/rrweb": "2.31.0", - "@sentry/browser": "9.0.0-alpha.2", + "@sentry/browser": "9.0.1", "axios": "1.7.7", "babel-loader": "^8.2.2", "fflate": "0.8.2", diff --git a/dev-packages/bundle-analyzer-scenarios/package.json b/dev-packages/bundle-analyzer-scenarios/package.json index 0c78cc152329..57f902448677 100644 --- a/dev-packages/bundle-analyzer-scenarios/package.json +++ b/dev-packages/bundle-analyzer-scenarios/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/bundle-analyzer-scenarios", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Scenarios to test bundle analysis with", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/dev-packages/bundle-analyzer-scenarios", diff --git a/dev-packages/clear-cache-gh-action/package.json b/dev-packages/clear-cache-gh-action/package.json index 3a12bab582aa..6b5cd3066d4e 100644 --- a/dev-packages/clear-cache-gh-action/package.json +++ b/dev-packages/clear-cache-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/clear-cache-gh-action", "description": "An internal Github Action to clear GitHub caches.", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index 78c71ba9e1b0..8a0eb8010128 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/e2e-tests", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "license": "MIT", "private": true, "scripts": { diff --git a/dev-packages/external-contributor-gh-action/package.json b/dev-packages/external-contributor-gh-action/package.json index 635ffc844f28..f67215caeaf3 100644 --- a/dev-packages/external-contributor-gh-action/package.json +++ b/dev-packages/external-contributor-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/external-contributor-gh-action", "description": "An internal Github Action to add external contributors to the CHANGELOG.md file.", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index d38a0cc3577b..c98722e16a0b 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/node-integration-tests", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "license": "MIT", "engines": { "node": ">=18" @@ -31,9 +31,9 @@ "@nestjs/common": "10.4.6", "@nestjs/core": "10.4.6", "@nestjs/platform-express": "10.4.6", - "@sentry/aws-serverless": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", + "@sentry/aws-serverless": "9.0.1", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", "@types/mongodb": "^3.6.20", "@types/mysql": "^2.15.21", "@types/pg": "^8.6.5", diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json index 0bce04fb3a08..b20d9847c5a1 100644 --- a/dev-packages/rollup-utils/package.json +++ b/dev-packages/rollup-utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/rollup-utils", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Rollup utilities used at Sentry for the Sentry JavaScript SDK", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/rollup-utils", diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json index c28fb78fa5a9..98e861d0959f 100644 --- a/dev-packages/size-limit-gh-action/package.json +++ b/dev-packages/size-limit-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/size-limit-gh-action", "description": "An internal Github Action to compare the current size of a PR against the one on develop.", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json index d3cee302a1c6..2872d83f326d 100644 --- a/dev-packages/test-utils/package.json +++ b/dev-packages/test-utils/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "9.0.0-alpha.2", + "version": "9.0.1", "name": "@sentry-internal/test-utils", "author": "Sentry", "license": "MIT", @@ -45,7 +45,7 @@ }, "devDependencies": { "@playwright/test": "~1.50.0", - "@sentry/core": "9.0.0-alpha.2" + "@sentry/core": "9.0.1" }, "volta": { "extends": "../../package.json" diff --git a/lerna.json b/lerna.json index 841f01197729..e493eac1e20e 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "npmClient": "yarn" } diff --git a/packages/angular/package.json b/packages/angular/package.json index 55e4a44bdaeb..882456f706a9 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/angular", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Angular", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular", @@ -21,8 +21,8 @@ "rxjs": "^6.5.5 || ^7.x" }, "dependencies": { - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2", + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1", "tslib": "^2.4.1" }, "devDependencies": { diff --git a/packages/astro/package.json b/packages/astro/package.json index 90bc722758f4..0247a3c3a750 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/astro", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Astro", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/astro", @@ -56,9 +56,9 @@ "astro": ">=3.x || >=4.0.0-beta || >=5.x" }, "dependencies": { - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", "@sentry/vite-plugin": "^2.22.6" }, "devDependencies": { diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index e61772fdcbda..3834d0775408 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/aws-serverless", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for AWS Lambda and AWS Serverless Environments", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/serverless", @@ -68,8 +68,8 @@ "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/instrumentation-aws-lambda": "0.50.2", "@opentelemetry/instrumentation-aws-sdk": "0.49.0", - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", "@types/aws-lambda": "^8.10.62" }, "devDependencies": { diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json index 00849ce6b6eb..5f293e390441 100644 --- a/packages/browser-utils/package.json +++ b/packages/browser-utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/browser-utils", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Browser Utilities for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser-utils", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2" + "@sentry/core": "9.0.1" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/browser/package.json b/packages/browser/package.json index 768fcd49d472..aaf6a5a824e8 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/browser", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for browsers", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser", @@ -39,14 +39,14 @@ "access": "public" }, "dependencies": { - "@sentry-internal/browser-utils": "9.0.0-alpha.2", - "@sentry-internal/feedback": "9.0.0-alpha.2", - "@sentry-internal/replay": "9.0.0-alpha.2", - "@sentry-internal/replay-canvas": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2" + "@sentry-internal/browser-utils": "9.0.1", + "@sentry-internal/feedback": "9.0.1", + "@sentry-internal/replay": "9.0.1", + "@sentry-internal/replay-canvas": "9.0.1", + "@sentry/core": "9.0.1" }, "devDependencies": { - "@sentry-internal/integration-shims": "9.0.0-alpha.2", + "@sentry-internal/integration-shims": "9.0.1", "fake-indexeddb": "^4.0.1" }, "scripts": { diff --git a/packages/bun/package.json b/packages/bun/package.json index 2bb889417f8f..b71bdb6cdf92 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/bun", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for bun", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bun", @@ -39,9 +39,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", - "@sentry/opentelemetry": "9.0.0-alpha.2" + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", + "@sentry/opentelemetry": "9.0.1" }, "devDependencies": { "bun-types": "latest" diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index daf28ba1976e..9305f8c31148 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cloudflare", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Cloudflare Workers and Pages", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/cloudflare", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2" + "@sentry/core": "9.0.1" }, "optionalDependencies": { "@cloudflare/workers-types": "^4.x" diff --git a/packages/core/package.json b/packages/core/package.json index 3eee72167327..40f04589dd57 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/core", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Base implementation for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/core", diff --git a/packages/deno/package.json b/packages/deno/package.json index 6a6c6d97d259..5fcba02fe4c5 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/deno", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Deno", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/deno", @@ -24,7 +24,7 @@ "/build" ], "dependencies": { - "@sentry/core": "9.0.0-alpha.2" + "@sentry/core": "9.0.1" }, "scripts": { "deno-types": "node ./scripts/download-deno-types.mjs", diff --git a/packages/ember/package.json b/packages/ember/package.json index 5dc7a17ad352..58810c7e3eb9 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/ember", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Ember.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember", @@ -32,8 +32,8 @@ "dependencies": { "@babel/core": "^7.24.4", "@embroider/macros": "^1.16.0", - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2", + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1", "ember-auto-import": "^2.7.2", "ember-cli-babel": "^8.2.0", "ember-cli-htmlbars": "^6.1.1", diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json index 5083c70ad3ff..592b049aebc9 100644 --- a/packages/eslint-config-sdk/package.json +++ b/packages/eslint-config-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-config-sdk", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK eslint config", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-config-sdk", @@ -22,8 +22,8 @@ "access": "public" }, "dependencies": { - "@sentry-internal/eslint-plugin-sdk": "9.0.0-alpha.2", - "@sentry-internal/typescript": "9.0.0-alpha.2", + "@sentry-internal/eslint-plugin-sdk": "9.0.1", + "@sentry-internal/typescript": "9.0.1", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", "eslint-config-prettier": "^6.11.0", diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json index 046d06c8ccda..dccedfc50ef8 100644 --- a/packages/eslint-plugin-sdk/package.json +++ b/packages/eslint-plugin-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-plugin-sdk", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK eslint plugin", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-plugin-sdk", diff --git a/packages/feedback/package.json b/packages/feedback/package.json index aef8b093f938..d8dcaadebf9a 100644 --- a/packages/feedback/package.json +++ b/packages/feedback/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/feedback", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Sentry SDK integration for user feedback", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2" + "@sentry/core": "9.0.1" }, "devDependencies": { "preact": "^10.19.4" diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 9cbd383364dd..c66ab2e4969a 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/gatsby", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Gatsby.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby", @@ -45,8 +45,8 @@ "access": "public" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2", - "@sentry/react": "9.0.0-alpha.2", + "@sentry/core": "9.0.1", + "@sentry/react": "9.0.1", "@sentry/webpack-plugin": "2.22.7" }, "peerDependencies": { diff --git a/packages/google-cloud-serverless/package.json b/packages/google-cloud-serverless/package.json index 2e2879517773..0df798373e3c 100644 --- a/packages/google-cloud-serverless/package.json +++ b/packages/google-cloud-serverless/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/google-cloud-serverless", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Google Cloud Functions", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/google-cloud-serverless", @@ -48,8 +48,8 @@ "access": "public" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", "@types/express": "^4.17.14" }, "devDependencies": { diff --git a/packages/integration-shims/package.json b/packages/integration-shims/package.json index c8e59956c937..2815556b5b98 100644 --- a/packages/integration-shims/package.json +++ b/packages/integration-shims/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/integration-shims", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Shims for integrations in Sentry SDK.", "main": "build/cjs/index.js", "module": "build/esm/index.js", @@ -55,7 +55,7 @@ "url": "https://github.com/getsentry/sentry-javascript/issues" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2" + "@sentry/core": "9.0.1" }, "engines": { "node": ">=18" diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 1ac2f8af6549..5821254b81af 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nestjs", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for NestJS", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nestjs", @@ -44,13 +44,13 @@ "access": "public" }, "dependencies": { - "@opentelemetry/core": "^1.30.1", "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/instrumentation-nestjs-core": "0.44.0", "@opentelemetry/semantic-conventions": "^1.27.0", - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2" + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1" }, "devDependencies": { "@nestjs/common": "^10.0.0", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 89bafe860cd6..3260776bb5b3 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nextjs", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Next.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs", @@ -79,12 +79,12 @@ "@opentelemetry/api": "^1.9.0", "@opentelemetry/semantic-conventions": "^1.28.0", "@rollup/plugin-commonjs": "28.0.1", - "@sentry-internal/browser-utils": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", - "@sentry/opentelemetry": "9.0.0-alpha.2", - "@sentry/react": "9.0.0-alpha.2", - "@sentry/vercel-edge": "9.0.0-alpha.2", + "@sentry-internal/browser-utils": "9.0.1", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", + "@sentry/opentelemetry": "9.0.1", + "@sentry/react": "9.0.1", + "@sentry/vercel-edge": "9.0.1", "@sentry/webpack-plugin": "3.1.2", "chalk": "3.0.0", "resolve": "1.22.8", diff --git a/packages/node/package.json b/packages/node/package.json index 0ea222b6d606..0c0dce9a910a 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/node", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Sentry Node SDK using OpenTelemetry for performance instrumentation", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node", @@ -96,8 +96,8 @@ "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.28.0", "@prisma/instrumentation": "6.2.1", - "@sentry/core": "9.0.0-alpha.2", - "@sentry/opentelemetry": "9.0.0-alpha.2", + "@sentry/core": "9.0.1", + "@sentry/opentelemetry": "9.0.1", "import-in-the-middle": "^1.12.0" }, "devDependencies": { diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index dbb92a4eac9f..33bcb751d0f4 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nuxt", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Nuxt (EXPERIMENTAL)", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nuxt", @@ -43,13 +43,13 @@ }, "dependencies": { "@nuxt/kit": "^3.13.2", - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", - "@sentry/opentelemetry": "9.0.0-alpha.2", + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", + "@sentry/opentelemetry": "9.0.1", "@sentry/rollup-plugin": "3.1.2", "@sentry/vite-plugin": "2.22.6", - "@sentry/vue": "9.0.0-alpha.2" + "@sentry/vue": "9.0.1" }, "devDependencies": { "@nuxt/module-builder": "^0.8.4", diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index c2931adbd059..9cd1f1e10dfc 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/opentelemetry", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry utilities for OpenTelemetry", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/opentelemetry", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2" + "@sentry/core": "9.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index c28d5b77ff97..7d80a9a166b9 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/profiling-node", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Node.js Profiling", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/profiling-node", @@ -63,8 +63,8 @@ }, "dependencies": { "@sentry-internal/node-cpu-profiler": "^2.0.0", - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2" + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1" }, "devDependencies": { "@types/node": "^18.19.1" diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 8db3107ecffb..0d3e5c03309b 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/react-router", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for React Router (Framework)", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react-router", @@ -34,9 +34,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2", - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2" + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1" }, "devDependencies": { "@react-router/node": "^7.1.5", @@ -44,8 +44,8 @@ }, "peerDependencies": { "@react-router/node": "7.x", - "react-router": "7.x", - "react": ">=18" + "react": ">=18", + "react-router": "7.x" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/react/package.json b/packages/react/package.json index b50743546cd4..6abc1387f3e3 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/react", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for React.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2", + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { diff --git a/packages/remix/package.json b/packages/remix/package.json index e6c3bc862ce8..8396e5f6c8e5 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/remix", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Remix", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/remix", @@ -55,10 +55,10 @@ "@opentelemetry/api": "^1.9.0", "@remix-run/router": "1.x", "@sentry/cli": "^2.41.1", - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", - "@sentry/opentelemetry": "9.0.0-alpha.2", - "@sentry/react": "9.0.0-alpha.2", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", + "@sentry/opentelemetry": "9.0.1", + "@sentry/react": "9.0.1", "glob": "^10.3.4", "opentelemetry-instrumentation-remix": "0.8.0", "yargs": "^17.6.0" diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index a9189d591538..59de2b7f5766 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay-canvas", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Replay canvas integration", "main": "build/npm/cjs/index.js", "module": "build/npm/esm/index.js", @@ -68,8 +68,8 @@ "@sentry-internal/rrweb": "2.31.0" }, "dependencies": { - "@sentry-internal/replay": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2" + "@sentry-internal/replay": "9.0.1", + "@sentry/core": "9.0.1" }, "engines": { "node": ">=18" diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index ad72bd424b0e..044517367cc1 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "User replays for Sentry", "main": "build/npm/cjs/index.js", "module": "build/npm/esm/index.js", @@ -68,7 +68,7 @@ "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { "@babel/core": "^7.17.5", - "@sentry-internal/replay-worker": "9.0.0-alpha.2", + "@sentry-internal/replay-worker": "9.0.1", "@sentry-internal/rrweb": "2.31.0", "@sentry-internal/rrweb-snapshot": "2.31.0", "fflate": "0.8.2", @@ -76,8 +76,8 @@ "jsdom-worker": "^0.2.1" }, "dependencies": { - "@sentry-internal/browser-utils": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2" + "@sentry-internal/browser-utils": "9.0.1", + "@sentry/core": "9.0.1" }, "engines": { "node": ">=18" diff --git a/packages/replay-worker/package.json b/packages/replay-worker/package.json index 841d6c4793c6..b48c02790248 100644 --- a/packages/replay-worker/package.json +++ b/packages/replay-worker/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay-worker", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Worker for @sentry-internal/replay", "main": "build/esm/index.js", "module": "build/esm/index.js", diff --git a/packages/solid/package.json b/packages/solid/package.json index e50365ded5a7..c0ead0cbcead 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/solid", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Solid", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solid", @@ -44,8 +44,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2" + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1" }, "peerDependencies": { "@solidjs/router": "^0.13.4", diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index c5008c775e11..8a4d978ee617 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/solidstart", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Solid Start", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solidstart", @@ -66,10 +66,10 @@ } }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", - "@sentry/opentelemetry": "9.0.0-alpha.2", - "@sentry/solid": "9.0.0-alpha.2", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", + "@sentry/opentelemetry": "9.0.1", + "@sentry/solid": "9.0.1", "@sentry/vite-plugin": "2.22.6" }, "devDependencies": { diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 89ca2156bbb6..7ceb59ce15c4 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/svelte", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Svelte", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/svelte", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2", + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1", "magic-string": "^0.30.0" }, "peerDependencies": { diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 002c60fa0312..862c62fcd1a6 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/sveltekit", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for SvelteKit", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/sveltekit", @@ -40,10 +40,10 @@ } }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2", - "@sentry/node": "9.0.0-alpha.2", - "@sentry/opentelemetry": "9.0.0-alpha.2", - "@sentry/svelte": "9.0.0-alpha.2", + "@sentry/core": "9.0.1", + "@sentry/node": "9.0.1", + "@sentry/opentelemetry": "9.0.1", + "@sentry/svelte": "9.0.1", "@sentry/vite-plugin": "2.22.6", "magic-string": "0.30.7", "magicast": "0.2.8", diff --git a/packages/types/package.json b/packages/types/package.json index 9e0b73d4e1a7..fe3b253c31f2 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/types", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Types for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/types", @@ -56,7 +56,7 @@ "yalc:publish": "yalc publish --push --sig" }, "dependencies": { - "@sentry/core": "9.0.0-alpha.2" + "@sentry/core": "9.0.1" }, "volta": { "extends": "../../package.json" diff --git a/packages/typescript/package.json b/packages/typescript/package.json index cfc8e4569cc0..92a171db6ace 100644 --- a/packages/typescript/package.json +++ b/packages/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/typescript", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Typescript configuration used at Sentry", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/typescript", diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index d6152b1e0efa..c93059aa5663 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/vercel-edge", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for the Vercel Edge Runtime", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vercel-edge", @@ -40,7 +40,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@sentry/core": "9.0.0-alpha.2" + "@sentry/core": "9.0.1" }, "devDependencies": { "@edge-runtime/types": "3.0.1", @@ -48,7 +48,7 @@ "@opentelemetry/resources": "^1.30.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.28.0", - "@sentry/opentelemetry": "9.0.0-alpha.2" + "@sentry/opentelemetry": "9.0.1" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/vue/package.json b/packages/vue/package.json index e7f90bef2048..56c84c4176ea 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/vue", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Official Sentry SDK for Vue.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vue", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2" + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1" }, "peerDependencies": { "pinia": "2.x", diff --git a/packages/wasm/package.json b/packages/wasm/package.json index b3c4fe913bbe..b76553159273 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/wasm", - "version": "9.0.0-alpha.2", + "version": "9.0.1", "description": "Support for WASM.", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/wasm", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "9.0.0-alpha.2", - "@sentry/core": "9.0.0-alpha.2" + "@sentry/browser": "9.0.1", + "@sentry/core": "9.0.1" }, "scripts": { "build": "run-p build:transpile build:bundle build:types",