diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91e40645e0fc..4cf711bd42ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,7 +105,8 @@ jobs: - *shared - 'packages/browser/**' - 'packages/browser-utils/**' - - 'packages/replay/**' + - 'packages/replay-internal/**' + - 'packages/replay-worker/**' - 'packages/replay-canvas/**' - 'packages/feedback/**' - 'packages/wasm/**' @@ -131,6 +132,7 @@ jobs: - *node - 'packages/nextjs/**' - 'packages/react/**' + - 'packages/vercel-edge/**' remix: - *shared - *browser @@ -147,8 +149,10 @@ jobs: - 'dev-packages/e2e-tests/test-applications/node-profiling/**' deno: - *shared - - *browser - 'packages/deno/**' + bun: + - *shared + - 'packages/bun/**' any_code: - '!**/*.md' @@ -166,6 +170,7 @@ jobs: changed_profiling_node: ${{ steps.changed.outputs.profiling_node }} changed_profiling_node_bindings: ${{ steps.changed.outputs.profiling_node_bindings }} changed_deno: ${{ steps.changed.outputs.deno }} + changed_bun: ${{ steps.changed.outputs.bun }} changed_browser: ${{ steps.changed.outputs.browser }} changed_browser_integration: ${{ steps.changed.outputs.browser_integration }} changed_any_code: ${{ steps.changed.outputs.any_code }} @@ -451,6 +456,7 @@ jobs: job_bun_unit_tests: name: Bun Unit Tests needs: [job_get_metadata, job_build] + if: needs.job_get_metadata.outputs.changed_bun == 'true' || github.event_name != 'pull_request' timeout-minutes: 10 runs-on: ubuntu-20.04 strategy: @@ -1031,7 +1037,9 @@ jobs: 'generic-ts3.8', 'node-fastify', 'node-hapi', - 'nestjs', + 'nestjs-basic', + 'nestjs-distributed-tracing', + 'nestjs-with-submodules', 'node-exports-test-app', 'node-koa', 'node-connect', diff --git a/CHANGELOG.md b/CHANGELOG.md index 392564b0c24e..3f6617a2bcc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.19.0 + +- feat(core): Align Span interface with OTEL (#12898) +- fix(angular): Remove `afterSendEvent` listener once root injector is destroyed (#12786) +- fix(browser): Fix bug causing unintentional dropping of transactions (#12933) +- fix(feedback): Add a missing call of Actor.appendToDom method when DOMContentLoaded event is triggered (#12973) + +Work in this release was contributed by @jaspreet57 and @arturovt. Thank you for your contribution! + ## 8.18.0 ### Important Changes @@ -38,7 +47,7 @@ instead. If you want to disable performance monitoring, remove the `tracesSample - fix(tracing): Ensure you can pass `null` as `parentSpan` in `startSpan*` (#12928) - ref(core): Small bundle size improvement (#12830) -Work in this release was contributed by @GitSquared and @mcous. Thank you for your contributions! +Work in this release was contributed by @GitSquared, @ziyadkhalil and @mcous. Thank you for your contributions! ## 8.17.0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs/.gitignore b/dev-packages/e2e-tests/test-applications/nestjs-basic/.gitignore similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/.gitignore rename to dev-packages/e2e-tests/test-applications/nestjs-basic/.gitignore diff --git a/dev-packages/e2e-tests/test-applications/nestjs/.npmrc b/dev-packages/e2e-tests/test-applications/nestjs-basic/.npmrc similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/.npmrc rename to dev-packages/e2e-tests/test-applications/nestjs-basic/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/nestjs/nest-cli.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/nest-cli.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/nest-cli.json rename to dev-packages/e2e-tests/test-applications/nestjs-basic/nest-cli.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs/package.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json similarity index 98% rename from dev-packages/e2e-tests/test-applications/nestjs/package.json rename to dev-packages/e2e-tests/test-applications/nestjs-basic/package.json index 94c4e445bfe0..f4c44ff7cef3 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/package.json @@ -1,5 +1,5 @@ { - "name": "nestjs", + "name": "nestjs-basic", "version": "0.0.1", "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/nestjs/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nestjs-basic/playwright.config.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/playwright.config.mjs rename to dev-packages/e2e-tests/test-applications/nestjs-basic/playwright.config.mjs diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts new file mode 100644 index 000000000000..b54604d999cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts @@ -0,0 +1,37 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get('test-transaction') + testTransaction() { + return this.appService.testTransaction(); + } + + @Get('test-exception/:id') + async testException(@Param('id') id: string) { + return this.appService.testException(id); + } + + @Get('test-expected-exception/:id') + async testExpectedException(@Param('id') id: string) { + return this.appService.testExpectedException(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') + async killTestCron() { + this.appService.killTestCron(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts new file mode 100644 index 000000000000..ceb7199a99cf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [ScheduleModule.forRoot()], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts similarity index 51% rename from dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts index f5666bffeb46..3afb7b5147bd 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts @@ -3,7 +3,6 @@ import { Cron, SchedulerRegistry } from '@nestjs/schedule'; import * as Sentry from '@sentry/nestjs'; import { SentryCron, SentryTraced } from '@sentry/nestjs'; import type { MonitorConfig } from '@sentry/types'; -import { makeHttpRequest } from './utils'; const monitorConfig: MonitorConfig = { schedule: { @@ -13,53 +12,15 @@ const monitorConfig: MonitorConfig = { }; @Injectable() -export class AppService1 { +export class AppService { constructor(private schedulerRegistry: SchedulerRegistry) {} - testSuccess() { - return { version: 'v1' }; - } - - testParam(id: string) { - return { - paramWas: id, - }; - } - - testInboundHeaders(headers: Record, id: string) { - return { - headers, - id, - }; - } - - async testOutgoingHttp(id: string) { - const data = await makeHttpRequest(`http://localhost:3030/test-inbound-headers/${id}`); - - return data; - } - - async testOutgoingFetch(id: string) { - const response = await fetch(`http://localhost:3030/test-inbound-headers/${id}`); - const data = await response.json(); - - return data; - } - testTransaction() { Sentry.startSpan({ name: 'test-span' }, () => { Sentry.startSpan({ name: 'child-span' }, () => {}); }); } - async testError() { - const exceptionId = Sentry.captureException(new Error('This is an error')); - - await Sentry.flush(2000); - - return { exceptionId }; - } - testException(id: string) { throw new Error(`This is an exception with id ${id}`); } @@ -68,26 +29,6 @@ export class AppService1 { throw new HttpException(`This is an expected exception with id ${id}`, HttpStatus.FORBIDDEN); } - async testOutgoingFetchExternalAllowed() { - const fetchResponse = await fetch('http://localhost:3040/external-allowed'); - - return fetchResponse.json(); - } - - async testOutgoingFetchExternalDisallowed() { - const fetchResponse = await fetch('http://localhost:3040/external-disallowed'); - - return fetchResponse.json(); - } - - async testOutgoingHttpExternalAllowed() { - return makeHttpRequest('http://localhost:3040/external-allowed'); - } - - async testOutgoingHttpExternalDisallowed() { - return makeHttpRequest('http://localhost:3040/external-disallowed'); - } - @SentryTraced('wait and return a string') async wait() { await new Promise(resolve => setTimeout(resolve, 500)); @@ -124,20 +65,3 @@ export class AppService1 { this.schedulerRegistry.deleteCronJob('test-cron-job'); } } - -@Injectable() -export class AppService2 { - externalAllowed(headers: Record) { - return { - headers, - route: 'external-allowed', - }; - } - - externalDisallowed(headers: Record) { - return { - headers, - route: 'external-disallowed', - }; - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts new file mode 100644 index 000000000000..f1f4de865435 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts @@ -0,0 +1,8 @@ +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, +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts new file mode 100644 index 000000000000..3a7b5ded8645 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts @@ -0,0 +1,20 @@ +// Import this first +import './instrument'; + +// Import other modules +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; +import * as Sentry from '@sentry/nestjs'; +import { AppModule } from './app.module'; + +const PORT = 3030; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + const { httpAdapter } = app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); + + await app.listen(PORT); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nestjs-basic/start-event-proxy.mjs similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/start-event-proxy.mjs rename to dev-packages/e2e-tests/test-applications/nestjs-basic/start-event-proxy.mjs diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/cron-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tests/cron-decorator.test.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts similarity index 63% rename from dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts index ffb48f4e5e70..349b25b0eee9 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/errors.test.ts @@ -53,32 +53,3 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { expect(errorEventOccurred).toBe(false); }); - -test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { - const errorEventPromise = waitForError('nestjs', event => { - return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; - }); - - const response = await fetch(`${baseURL}/test-module`); - expect(response.status).toBe(500); // should be 400 - - // should never arrive, but does because the exception is not handled properly - const errorEvent = await errorEventPromise; - - expect(errorEvent.exception?.values).toHaveLength(1); - expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the test module!'); - - expect(errorEvent.request).toEqual({ - method: 'GET', - cookies: {}, - headers: expect.any(Object), - url: 'http://localhost:3030/test-module', - }); - - expect(errorEvent.transaction).toEqual('GET /test-module'); - - expect(errorEvent.contexts?.trace).toEqual({ - trace_id: expect.any(String), - span_id: expect.any(String), - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/span-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/span-decorator.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tests/span-decorator.test.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tests/span-decorator.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tests/transactions.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.build.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tsconfig.build.json rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.build.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.json similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tsconfig.json rename to dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.gitignore b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.gitignore new file mode 100644 index 000000000000..4b56acfbebf4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.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-distributed-tracing/.npmrc b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/.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-distributed-tracing/nest-cli.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/nest-cli.json new file mode 100644 index 000000000000..f9aa683b1ad5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/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-distributed-tracing/package.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json new file mode 100644 index 000000000000..b4d0ead875f9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json @@ -0,0 +1,47 @@ +{ + "name": "nestjs-distributed-tracing", + "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": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@sentry/nestjs": "latest || *", + "@sentry/types": "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": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/node": "18.15.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": "^4.9.5" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/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/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/instrument.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts rename to dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/instrument.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts new file mode 100644 index 000000000000..83d0b33d687d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts @@ -0,0 +1,25 @@ +// Import this first +import './instrument'; + +// Import other modules +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; +import * as Sentry from '@sentry/nestjs'; +import { TraceInitiatorModule } from './trace-initiator.module'; +import { TraceReceiverModule } from './trace-receiver.module'; + +const TRACE_INITIATOR_PORT = 3030; +const TRACE_RECEIVER_PORT = 3040; + +async function bootstrap() { + const trace_initiator_app = await NestFactory.create(TraceInitiatorModule); + + const { httpAdapter } = trace_initiator_app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(trace_initiator_app, new BaseExceptionFilter(httpAdapter)); + + await trace_initiator_app.listen(TRACE_INITIATOR_PORT); + + const trace_receiver_app = await NestFactory.create(TraceReceiverModule); + await trace_receiver_app.listen(TRACE_RECEIVER_PORT); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.controller.ts new file mode 100644 index 000000000000..62e0c299a239 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.controller.ts @@ -0,0 +1,42 @@ +import { Controller, Get, Headers, Param } from '@nestjs/common'; +import { TraceInitiatorService } from './trace-initiator.service'; + +@Controller() +export class TraceInitiatorController { + constructor(private readonly traceInitiatorService: TraceInitiatorService) {} + + @Get('test-inbound-headers/:id') + testInboundHeaders(@Headers() headers, @Param('id') id: string) { + return this.traceInitiatorService.testInboundHeaders(headers, id); + } + + @Get('test-outgoing-http/:id') + async testOutgoingHttp(@Param('id') id: string) { + return this.traceInitiatorService.testOutgoingHttp(id); + } + + @Get('test-outgoing-fetch/:id') + async testOutgoingFetch(@Param('id') id: string) { + return this.traceInitiatorService.testOutgoingFetch(id); + } + + @Get('test-outgoing-fetch-external-allowed') + async testOutgoingFetchExternalAllowed() { + return this.traceInitiatorService.testOutgoingFetchExternalAllowed(); + } + + @Get('test-outgoing-fetch-external-disallowed') + async testOutgoingFetchExternalDisallowed() { + return this.traceInitiatorService.testOutgoingFetchExternalDisallowed(); + } + + @Get('test-outgoing-http-external-allowed') + async testOutgoingHttpExternalAllowed() { + return this.traceInitiatorService.testOutgoingHttpExternalAllowed(); + } + + @Get('test-outgoing-http-external-disallowed') + async testOutgoingHttpExternalDisallowed() { + return this.traceInitiatorService.testOutgoingHttpExternalDisallowed(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts new file mode 100644 index 000000000000..9256f29928ab --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TraceInitiatorController } from './trace-initiator.controller'; +import { TraceInitiatorService } from './trace-initiator.service'; + +@Module({ + imports: [], + controllers: [TraceInitiatorController], + providers: [TraceInitiatorService], +}) +export class TraceInitiatorModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.service.ts new file mode 100644 index 000000000000..67c5333cedaf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { makeHttpRequest } from './utils'; + +@Injectable() +export class TraceInitiatorService { + constructor() {} + + testInboundHeaders(headers: Record, id: string) { + return { + headers, + id, + }; + } + + async testOutgoingHttp(id: string) { + const data = await makeHttpRequest(`http://localhost:3030/test-inbound-headers/${id}`); + + return data; + } + + async testOutgoingFetch(id: string) { + const response = await fetch(`http://localhost:3030/test-inbound-headers/${id}`); + const data = await response.json(); + + return data; + } + + async testOutgoingFetchExternalAllowed() { + const fetchResponse = await fetch('http://localhost:3040/external-allowed'); + + return fetchResponse.json(); + } + + async testOutgoingFetchExternalDisallowed() { + const fetchResponse = await fetch('http://localhost:3040/external-disallowed'); + + return fetchResponse.json(); + } + + async testOutgoingHttpExternalAllowed() { + return makeHttpRequest('http://localhost:3040/external-allowed'); + } + + async testOutgoingHttpExternalDisallowed() { + return makeHttpRequest('http://localhost:3040/external-disallowed'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.controller.ts new file mode 100644 index 000000000000..2a1899f1097d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, Headers } from '@nestjs/common'; +import { TraceReceiverService } from './trace-receiver.service'; + +@Controller() +export class TraceReceiverController { + constructor(private readonly traceReceiverService: TraceReceiverService) {} + + @Get('external-allowed') + externalAllowed(@Headers() headers) { + return this.traceReceiverService.externalAllowed(headers); + } + + @Get('external-disallowed') + externalDisallowed(@Headers() headers) { + return this.traceReceiverService.externalDisallowed(headers); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.module.ts new file mode 100644 index 000000000000..2680b3071fb7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TraceReceiverController } from './trace-receiver.controller'; +import { TraceReceiverService } from './trace-receiver.service'; + +@Module({ + imports: [], + controllers: [TraceReceiverController], + providers: [TraceReceiverService], +}) +export class TraceReceiverModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.service.ts new file mode 100644 index 000000000000..a40b28ad0778 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-receiver.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class TraceReceiverService { + externalAllowed(headers: Record) { + return { + headers, + route: 'external-allowed', + }; + } + + externalDisallowed(headers: Record) { + return { + headers, + route: 'external-disallowed', + }; + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/utils.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/utils.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/src/utils.ts rename to dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/utils.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/start-event-proxy.mjs new file mode 100644 index 000000000000..e9917b9273da --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nestjs', +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs/tests/propagation.test.ts rename to dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/propagation.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.build.json new file mode 100644 index 000000000000..26c30d4eddf2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/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-distributed-tracing/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.json new file mode 100644 index 000000000000..95f5641cf7f3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tsconfig.json @@ -0,0 +1,21 @@ +{ + "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 + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.gitignore b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.gitignore new file mode 100644 index 000000000000..4b56acfbebf4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.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-with-submodules/.npmrc b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/.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-with-submodules/nest-cli.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/nest-cli.json new file mode 100644 index 000000000000..f9aa683b1ad5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/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-with-submodules/package.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json new file mode 100644 index 000000000000..dfbe5e83e640 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/package.json @@ -0,0 +1,47 @@ +{ + "name": "nestjs-with-submodules", + "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": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@sentry/nestjs": "latest || *", + "@sentry/types": "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": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/node": "18.15.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": "^4.9.5" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/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-with-submodules/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts new file mode 100644 index 000000000000..71a410e8d0a8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get('test-exception/:id') + async testException(@Param('id') id: string) { + return this.appService.testException(id); + } + + @Get('test-expected-exception/:id') + async testExpectedException(@Param('id') id: string) { + return this.appService.testExpectedException(id); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts new file mode 100644 index 000000000000..944b84e66d27 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; +import { ExampleModule } from './example-module/example.module'; + +@Module({ + imports: [ExampleModule], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.service.ts new file mode 100644 index 000000000000..242408023586 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.service.ts @@ -0,0 +1,14 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + constructor() {} + + testException(id: string) { + throw new Error(`This is an exception with id ${id}`); + } + + testExpectedException(id: string) { + throw new HttpException(`This is an expected exception with id ${id}`, HttpStatus.FORBIDDEN); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts new file mode 100644 index 000000000000..b71179c195cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { ExampleException } from './example.exception'; + +@Controller('example-module') +export class ExampleController { + constructor() {} + + @Get() + getTest(): string { + throw new ExampleException(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.exception.ts new file mode 100644 index 000000000000..ac43dddfa8dc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.exception.ts @@ -0,0 +1,5 @@ +export class ExampleException extends Error { + constructor() { + super('Something went wrong in the example module!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.filter.ts similarity index 61% rename from dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.filter.ts index 87a4ca0920e5..848441caf855 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.filter.ts @@ -1,11 +1,11 @@ import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; -import { TestException } from './test.exception'; +import { ExampleException } from './example.exception'; -@Catch(TestException) -export class TestExceptionFilter extends BaseExceptionFilter { +@Catch(ExampleException) +export class ExampleExceptionFilter extends BaseExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { - if (exception instanceof TestException) { + if (exception instanceof ExampleException) { return super.catch(new BadRequestException(exception.message), host); } return super.catch(exception, host); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts new file mode 100644 index 000000000000..fabd71c4df90 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { ExampleController } from './example.controller'; +import { ExampleExceptionFilter } from './example.filter'; + +@Module({ + imports: [], + controllers: [ExampleController], + providers: [ + { + provide: APP_FILTER, + useClass: ExampleExceptionFilter, + }, + ], +}) +export class ExampleModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts new file mode 100644 index 000000000000..f1f4de865435 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/instrument.ts @@ -0,0 +1,8 @@ +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, +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts new file mode 100644 index 000000000000..3a7b5ded8645 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts @@ -0,0 +1,20 @@ +// Import this first +import './instrument'; + +// Import other modules +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; +import * as Sentry from '@sentry/nestjs'; +import { AppModule } from './app.module'; + +const PORT = 3030; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + const { httpAdapter } = app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); + + await app.listen(PORT); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/start-event-proxy.mjs new file mode 100644 index 000000000000..e9917b9273da --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nestjs', +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts new file mode 100644 index 000000000000..3711cbe8fd0f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the example module!'; + }); + + const response = await fetch(`${baseURL}/example-module`); + expect(response.status).toBe(500); // should be 400 + + // should never arrive, but does because the exception is not handled properly + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the example module!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/example-module', + }); + + expect(errorEvent.transaction).toEqual('GET /example-module'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.build.json new file mode 100644 index 000000000000..26c30d4eddf2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/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-with-submodules/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.json new file mode 100644 index 000000000000..95f5641cf7f3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tsconfig.json @@ -0,0 +1,21 @@ +{ + "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 + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.controller.ts deleted file mode 100644 index 7fda9eef768e..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.controller.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Controller, Get, Headers, Param } from '@nestjs/common'; -import { AppService1, AppService2 } from './app.service'; - -@Controller() -export class AppController1 { - constructor(private readonly appService: AppService1) {} - - @Get('test-success') - testSuccess() { - return this.appService.testSuccess(); - } - - @Get('test-param/:param') - testParam(@Param() params) { - return this.appService.testParam(params.param); - } - - @Get('test-inbound-headers/:id') - testInboundHeaders(@Headers() headers, @Param('id') id: string) { - return this.appService.testInboundHeaders(headers, id); - } - - @Get('test-outgoing-http/:id') - async testOutgoingHttp(@Param('id') id: string) { - return this.appService.testOutgoingHttp(id); - } - - @Get('test-outgoing-fetch/:id') - async testOutgoingFetch(@Param('id') id: string) { - return this.appService.testOutgoingFetch(id); - } - - @Get('test-transaction') - testTransaction() { - return this.appService.testTransaction(); - } - - @Get('test-error') - async testError() { - return this.appService.testError(); - } - - @Get('test-exception/:id') - async testException(@Param('id') id: string) { - return this.appService.testException(id); - } - - @Get('test-expected-exception/:id') - async testExpectedException(@Param('id') id: string) { - return this.appService.testExpectedException(id); - } - - @Get('test-outgoing-fetch-external-allowed') - async testOutgoingFetchExternalAllowed() { - return this.appService.testOutgoingFetchExternalAllowed(); - } - - @Get('test-outgoing-fetch-external-disallowed') - async testOutgoingFetchExternalDisallowed() { - return this.appService.testOutgoingFetchExternalDisallowed(); - } - - @Get('test-outgoing-http-external-allowed') - async testOutgoingHttpExternalAllowed() { - return this.appService.testOutgoingHttpExternalAllowed(); - } - - @Get('test-outgoing-http-external-disallowed') - async testOutgoingHttpExternalDisallowed() { - return this.appService.testOutgoingHttpExternalDisallowed(); - } - - @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') - async killTestCron() { - this.appService.killTestCron(); - } -} - -@Controller() -export class AppController2 { - constructor(private readonly appService: AppService2) {} - - @Get('external-allowed') - externalAllowed(@Headers() headers) { - return this.appService.externalAllowed(headers); - } - - @Get('external-disallowed') - externalDisallowed(@Headers() headers) { - return this.appService.externalDisallowed(headers); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts deleted file mode 100644 index 932d1af99611..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Module } from '@nestjs/common'; -import { ScheduleModule } from '@nestjs/schedule'; -import { AppController1, AppController2 } from './app.controller'; -import { AppService1, AppService2 } from './app.service'; -import { TestModule } from './test-module/test.module'; - -@Module({ - imports: [ScheduleModule.forRoot(), TestModule], - controllers: [AppController1], - providers: [AppService1], -}) -export class AppModule1 {} - -@Module({ - imports: [], - controllers: [AppController2], - providers: [AppService2], -}) -export class AppModule2 {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts deleted file mode 100644 index c2682662154d..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Import this first -import './instrument'; - -// Import other modules -import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; -import * as Sentry from '@sentry/nestjs'; -import { AppModule1, AppModule2 } from './app.module'; - -const app1Port = 3030; -const app2Port = 3040; - -async function bootstrap() { - const app1 = await NestFactory.create(AppModule1); - - const { httpAdapter } = app1.get(HttpAdapterHost); - Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); - - await app1.listen(app1Port); - - const app2 = await NestFactory.create(AppModule2); - await app2.listen(app2Port); -} - -bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts deleted file mode 100644 index 150fb0e07546..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Controller, Get } from '@nestjs/common'; -import { TestException } from './test.exception'; - -@Controller('test-module') -export class TestController { - constructor() {} - - @Get() - getTest(): string { - throw new TestException(); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts deleted file mode 100644 index b736596b6717..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class TestException extends Error { - constructor() { - super('Something went wrong in the test module!'); - } -} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts deleted file mode 100644 index 37b6dbe7e819..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; -import { TestController } from './test.controller'; -import { TestExceptionFilter } from './test.filter'; - -@Module({ - imports: [], - controllers: [TestController], - providers: [ - { - provide: APP_FILTER, - useClass: TestExceptionFilter, - }, - ], -}) -export class TestModule {} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index 4c3f56b0aa0c..265ee010b8d5 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -8,7 +8,7 @@ "test:prod": "TEST_ENV=production __NEXT_EXPERIMENTAL_INSTRUMENTATION=1 playwright test", "test:dev": "TEST_ENV=development __NEXT_EXPERIMENTAL_INSTRUMENTATION=1 playwright test", "test:build": "pnpm install && npx playwright install && pnpm build", - "test:build-canary": "pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", + "test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", "test:build-latest": "pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", "test:assert": "pnpm test:prod && pnpm test:dev" }, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts index 223da5b245e9..863e5de111a2 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts @@ -1,9 +1,20 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +const packageJson = require('../package.json'); + test('Should capture errors from nested server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ page, }) => { + const [, minor, patch, canary] = packageJson.dependencies.next.split('.'); + + test.skip( + minor === '0' && + patch.startsWith('0-') && + ((patch.includes('canary') && Number(canary) < 63) || patch.includes('rc')), + 'Next.js version does not expose these errors', + ); + const errorEventPromise = waitForError('nextjs-15', errorEvent => { return !!errorEvent?.exception?.values?.some(value => value.value === 'I am technically uncatchable'); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts index b50e9688861e..0a20e97be74a 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts @@ -1,9 +1,20 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +const packageJson = require('../package.json'); + test('Should capture errors for crashing streaming promises in server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ page, }) => { + const [, minor, patch, canary] = packageJson.dependencies.next.split('.'); + + test.skip( + minor === '0' && + patch.startsWith('0-') && + ((patch.includes('canary') && Number(canary) < 63) || patch.includes('rc')), + 'Next.js version does not expose these errors', + ); + const errorEventPromise = waitForError('nextjs-15', errorEvent => { return !!errorEvent?.exception?.values?.some(value => value.value === 'I am a data streaming error'); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreIncomingRequests.js b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreIncomingRequests.js index f1e5d9870fcf..bafd8b8f2bc4 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreIncomingRequests.js +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreIncomingRequests.js @@ -9,8 +9,14 @@ Sentry.init({ integrations: [ Sentry.httpIntegration({ - ignoreIncomingRequests: url => { - return url.includes('/liveness'); + ignoreIncomingRequests: (url, request) => { + if (url.includes('/liveness')) { + return true; + } + if (request.method === 'POST' && request.url.includes('readiness')) { + return true; + } + return false; }, }), ], @@ -33,6 +39,10 @@ app.get('/liveness', (_req, res) => { res.send({ response: 'liveness' }); }); +app.post('/readiness', (_req, res) => { + res.send({ response: 'readiness' }); +}); + Sentry.setupExpressErrorHandler(app); startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreOutgoingRequests.js b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreOutgoingRequests.js index ce520c999259..9d7d2ed069d1 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreOutgoingRequests.js +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreOutgoingRequests.js @@ -10,8 +10,14 @@ Sentry.init({ integrations: [ Sentry.httpIntegration({ - ignoreOutgoingRequests: url => { - return url.includes('example.com'); + ignoreOutgoingRequests: (url, request) => { + if (url.includes('example.com')) { + return true; + } + if (request.method === 'POST' && request.path === '/path') { + return true; + } + return false; }, }), ], @@ -37,6 +43,17 @@ app.get('/test', (_req, response) => { .end(); }); +app.post('/testPath', (_req, response) => { + http + .request('http://example.com/path', res => { + res.on('data', () => {}); + res.on('end', () => { + response.send({ response: 'done' }); + }); + }) + .end(); +}); + Sentry.setupExpressErrorHandler(app); startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts index aebe0dd676ba..972ba30eab43 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts @@ -76,58 +76,117 @@ describe('httpIntegration', () => { .makeRequest('get', '/test'); }); - test("doesn't create a root span for incoming requests ignored via `ignoreIncomingRequests`", done => { - const runner = createRunner(__dirname, 'server-ignoreIncomingRequests.js') - .expect({ - transaction: { - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - url: expect.stringMatching(/\/test$/), - 'http.response.status_code': 200, + describe("doesn't create a root span for incoming requests ignored via `ignoreIncomingRequests`", () => { + test('via the url param', done => { + const runner = createRunner(__dirname, 'server-ignoreIncomingRequests.js') + .expect({ + transaction: { + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + url: expect.stringMatching(/\/test$/), + 'http.response.status_code': 200, + }, + op: 'http.server', + status: 'ok', }, - op: 'http.server', - status: 'ok', }, + transaction: 'GET /test', }, - transaction: 'GET /test', - }, - }) - .start(done); + }) + .start(done); + + runner.makeRequest('get', '/liveness'); // should be ignored + runner.makeRequest('get', '/test'); + }); + + test('via the request param', done => { + const runner = createRunner(__dirname, 'server-ignoreIncomingRequests.js') + .expect({ + transaction: { + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + url: expect.stringMatching(/\/test$/), + 'http.response.status_code': 200, + }, + op: 'http.server', + status: 'ok', + }, + }, + transaction: 'GET /test', + }, + }) + .start(done); - runner.makeRequest('get', '/liveness'); // should be ignored - runner.makeRequest('get', '/test'); + runner.makeRequest('post', '/readiness'); // should be ignored + runner.makeRequest('get', '/test'); + }); }); - test("doesn't create child spans for outgoing requests ignored via `ignoreOutgoingRequests`", done => { - const runner = createRunner(__dirname, 'server-ignoreOutgoingRequests.js') - .expect({ - transaction: { - contexts: { - trace: { - span_id: expect.any(String), - trace_id: expect.any(String), - data: { - url: expect.stringMatching(/\/test$/), - 'http.response.status_code': 200, + describe("doesn't create child spans for outgoing requests ignored via `ignoreOutgoingRequests`", () => { + test('via the url param', done => { + const runner = createRunner(__dirname, 'server-ignoreOutgoingRequests.js') + .expect({ + transaction: { + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + url: expect.stringMatching(/\/test$/), + 'http.response.status_code': 200, + }, + op: 'http.server', + status: 'ok', }, - op: 'http.server', - status: 'ok', }, + transaction: 'GET /test', + spans: [ + expect.objectContaining({ op: 'middleware.express', description: 'query' }), + expect.objectContaining({ op: 'middleware.express', description: 'expressInit' }), + expect.objectContaining({ op: 'middleware.express', description: 'corsMiddleware' }), + expect.objectContaining({ op: 'request_handler.express', description: '/test' }), + ], }, - transaction: 'GET /test', - spans: [ - expect.objectContaining({ op: 'middleware.express', description: 'query' }), - expect.objectContaining({ op: 'middleware.express', description: 'expressInit' }), - expect.objectContaining({ op: 'middleware.express', description: 'corsMiddleware' }), - expect.objectContaining({ op: 'request_handler.express', description: '/test' }), - ], - }, - }) - .start(done); + }) + .start(done); + + runner.makeRequest('get', '/test'); + }); + + test('via the request param', done => { + const runner = createRunner(__dirname, 'server-ignoreOutgoingRequests.js') + .expect({ + transaction: { + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + url: expect.stringMatching(/\/testPath$/), + 'http.response.status_code': 200, + }, + op: 'http.server', + status: 'ok', + }, + }, + transaction: 'POST /testPath', + spans: [ + expect.objectContaining({ op: 'middleware.express', description: 'query' }), + expect.objectContaining({ op: 'middleware.express', description: 'expressInit' }), + expect.objectContaining({ op: 'middleware.express', description: 'corsMiddleware' }), + expect.objectContaining({ op: 'request_handler.express', description: '/testPath' }), + ], + }, + }) + .start(done); - runner.makeRequest('get', '/test'); + runner.makeRequest('post', '/testPath'); + }); }); }); diff --git a/docs/gitflow.md b/docs/gitflow.md index 8926c614bcfc..8bc853e8dbf0 100644 --- a/docs/gitflow.md +++ b/docs/gitflow.md @@ -18,3 +18,16 @@ We use [Gitflow](https://docs.github.com/en/get-started/quickstart/github-flow) While a release is pending, we may merge anything into develop, **except for changes to package.json files**. If we change the package.json files on develop, the gitflow PR master -> develop will have merge conflicts, because during the release the package.json files are updated on master. + +## What to do if there is a merge conflict? + +Although gitflow should help us to avoid merge conflicts, as mentioned above in "Important Caveats" it can still happen +that you get a merge conflict when trying to merge master into develop after a successful release. + +If this happen, you can resolve this as follows: + +- Close the automated PR that was created by the gitflow automation +- Create a new branch on top of `master` (e.g. `manual-develop-sync`) +- Merge `develop` into this branch, with a merge commit (and fix any merge conflicts that come up) +- Now create a PR against `develop` from your branch (e.g. `manual-develop-sync`) +- Merge this PR with a merge commit diff --git a/packages/angular/src/errorhandler.ts b/packages/angular/src/errorhandler.ts index 28c06e1e6bfd..14ca380ea3ea 100644 --- a/packages/angular/src/errorhandler.ts +++ b/packages/angular/src/errorhandler.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import type { ErrorHandler as AngularErrorHandler } from '@angular/core'; +import type { ErrorHandler as AngularErrorHandler, OnDestroy } from '@angular/core'; import { Inject, Injectable } from '@angular/core'; import * as Sentry from '@sentry/browser'; import type { ReportDialogOptions } from '@sentry/browser'; @@ -81,21 +81,28 @@ function isErrorOrErrorLikeObject(value: unknown): value is Error { * Implementation of Angular's ErrorHandler provider that can be used as a drop-in replacement for the stock one. */ @Injectable({ providedIn: 'root' }) -class SentryErrorHandler implements AngularErrorHandler { +class SentryErrorHandler implements AngularErrorHandler, OnDestroy { protected readonly _options: ErrorHandlerOptions; - /* indicates if we already registered our the afterSendEvent handler */ - private _registeredAfterSendEventHandler; + /** The cleanup function is executed when the injector is destroyed. */ + private _removeAfterSendEventListener?: () => void; public constructor(@Inject('errorHandlerOptions') options?: ErrorHandlerOptions) { - this._registeredAfterSendEventHandler = false; - this._options = { logErrors: true, ...options, }; } + /** + * Method executed when the injector is destroyed. + */ + public ngOnDestroy(): void { + if (this._removeAfterSendEventListener) { + this._removeAfterSendEventListener(); + } + } + /** * Method called for every value captured through the ErrorHandler */ @@ -119,17 +126,14 @@ class SentryErrorHandler implements AngularErrorHandler { if (this._options.showDialog) { const client = Sentry.getClient(); - if (client && !this._registeredAfterSendEventHandler) { - client.on('afterSendEvent', (event: Event) => { + if (client && !this._removeAfterSendEventListener) { + this._removeAfterSendEventListener = client.on('afterSendEvent', (event: Event) => { if (!event.type && event.event_id) { runOutsideAngular(() => { - Sentry.showReportDialog({ ...this._options.dialogOptions, eventId: event.event_id! }); + Sentry.showReportDialog({ ...this._options.dialogOptions, eventId: event.event_id }); }); } }); - - // We only want to register this hook once in the lifetime of the error handler - this._registeredAfterSendEventHandler = true; } else if (!client) { runOutsideAngular(() => { Sentry.showReportDialog({ ...this._options.dialogOptions, eventId }); diff --git a/packages/angular/test/errorhandler.test.ts b/packages/angular/test/errorhandler.test.ts index c30a7a87efc9..1ae415c5706d 100644 --- a/packages/angular/test/errorhandler.test.ts +++ b/packages/angular/test/errorhandler.test.ts @@ -546,5 +546,49 @@ describe('SentryErrorHandler', () => { expect(showReportDialogSpy).toBeCalledTimes(1); }); }); + + it('only registers the client "afterSendEvent" listener to open the dialog once', () => { + const unsubScribeSpy = vi.fn(); + const client = { + cbs: [] as ((event: Event) => void)[], + on: vi.fn((_, cb) => { + client.cbs.push(cb); + return unsubScribeSpy; + }), + }; + + vi.spyOn(SentryBrowser, 'getClient').mockImplementation(() => client as unknown as Client); + + const errorhandler = createErrorHandler({ showDialog: true }); + expect(client.cbs).toHaveLength(0); + + errorhandler.handleError(new Error('error 1')); + expect(client.cbs).toHaveLength(1); + + errorhandler.handleError(new Error('error 2')); + errorhandler.handleError(new Error('error 3')); + expect(client.cbs).toHaveLength(1); + }); + + it('cleans up the "afterSendEvent" listener once the ErrorHandler is destroyed', () => { + const unsubScribeSpy = vi.fn(); + const client = { + cbs: [] as ((event: Event) => void)[], + on: vi.fn((_, cb) => { + client.cbs.push(cb); + return unsubScribeSpy; + }), + }; + + vi.spyOn(SentryBrowser, 'getClient').mockImplementation(() => client as unknown as Client); + + const errorhandler = createErrorHandler({ showDialog: true }); + + errorhandler.handleError(new Error('error 1')); + expect(client.cbs).toHaveLength(1); + + errorhandler.ngOnDestroy(); + expect(unsubScribeSpy).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index e6b3a1aa67e3..9d89e4f2a327 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -64,8 +64,8 @@ "access": "public" }, "dependencies": { - "@opentelemetry/instrumentation-aws-lambda": "0.42.0", - "@opentelemetry/instrumentation-aws-sdk": "0.42.0", + "@opentelemetry/instrumentation-aws-lambda": "0.43.0", + "@opentelemetry/instrumentation-aws-sdk": "0.43.0", "@sentry/core": "8.18.0", "@sentry/node": "8.18.0", "@sentry/types": "8.18.0", diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index cb48c2e8b675..02c044322bd3 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -287,7 +287,13 @@ export function addPerformanceEntries(span: Span): void { // eslint-disable-next-line @typescript-eslint/no-explicit-any performanceEntries.slice(_performanceCursor).forEach((entry: Record) => { const startTime = msToSec(entry.startTime); - const duration = msToSec(entry.duration); + const duration = msToSec( + // Inexplicibly, Chrome sometimes emits a negative duration. We need to work around this. + // There is a SO post attempting to explain this, but it leaves one with open questions: https://stackoverflow.com/questions/23191918/peformance-getentries-and-negative-duration-display + // The way we clamp the value is probably not accurate, since we have observed this happen for things that may take a while to load, like for example the replay worker. + // TODO: Investigate why this happens and how to properly mitigate. For now, this is a workaround to prevent transactions being dropped due to negative duration spans. + Math.max(0, entry.duration), + ); if (op === 'navigation' && transactionStartTime && timeOrigin + startTime < transactionStartTime) { return; diff --git a/packages/core/src/tracing/sentryNonRecordingSpan.ts b/packages/core/src/tracing/sentryNonRecordingSpan.ts index 1debb45aa282..867b5684d1da 100644 --- a/packages/core/src/tracing/sentryNonRecordingSpan.ts +++ b/packages/core/src/tracing/sentryNonRecordingSpan.ts @@ -68,4 +68,37 @@ export class SentryNonRecordingSpan implements Span { ): this { return this; } + + /** + * This should generally not be used, + * but we need it for being comliant with the OTEL Span interface. + * + * @hidden + * @internal + */ + public addLink(_link: unknown): this { + return this; + } + + /** + * This should generally not be used, + * but we need it for being comliant with the OTEL Span interface. + * + * @hidden + * @internal + */ + public addLinks(_links: unknown[]): this { + return this; + } + + /** + * This should generally not be used, + * but we need it for being comliant with the OTEL Span interface. + * + * @hidden + * @internal + */ + public recordException(_exception: unknown, _time?: number | undefined): void { + // noop + } } diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index d7c800e338a7..7e1083142314 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -107,6 +107,39 @@ export class SentrySpan implements Span { } } + /** + * This should generally not be used, + * but it is needed for being compliant with the OTEL Span interface. + * + * @hidden + * @internal + */ + public addLink(_link: unknown): this { + return this; + } + + /** + * This should generally not be used, + * but it is needed for being compliant with the OTEL Span interface. + * + * @hidden + * @internal + */ + public addLinks(_links: unknown[]): this { + return this; + } + + /** + * This should generally not be used, + * but it is needed for being compliant with the OTEL Span interface. + * + * @hidden + * @internal + */ + public recordException(_exception: unknown, _time?: number | undefined): void { + // noop + } + /** @inheritdoc */ public spanContext(): SpanContextData { const { _spanId: spanId, _traceId: traceId, _sampled: sampled } = this; @@ -118,18 +151,21 @@ export class SentrySpan implements Span { } /** @inheritdoc */ - public setAttribute(key: string, value: SpanAttributeValue | undefined): void { + public setAttribute(key: string, value: SpanAttributeValue | undefined): this { if (value === undefined) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete this._attributes[key]; } else { this._attributes[key] = value; } + + return this; } /** @inheritdoc */ - public setAttributes(attributes: SpanAttributes): void { + public setAttributes(attributes: SpanAttributes): this { Object.keys(attributes).forEach(key => this.setAttribute(key, attributes[key])); + return this; } /** diff --git a/packages/feedback/src/core/integration.ts b/packages/feedback/src/core/integration.ts index 8917644cebfe..4e8caa85a135 100644 --- a/packages/feedback/src/core/integration.ts +++ b/packages/feedback/src/core/integration.ts @@ -282,7 +282,7 @@ export const buildFeedbackIntegration = ({ } if (DOCUMENT.readyState === 'loading') { - DOCUMENT.addEventListener('DOMContentLoaded', () => _createActor().appendToDom); + DOCUMENT.addEventListener('DOMContentLoaded', () => _createActor().appendToDom()); } else { _createActor().appendToDom(); } diff --git a/packages/nestjs/README.md b/packages/nestjs/README.md index b9ac0c9371c2..e336a856c03e 100644 --- a/packages/nestjs/README.md +++ b/packages/nestjs/README.md @@ -24,7 +24,7 @@ yarn add @sentry/nestjs ## Usage -```js +```typescript // CJS Syntax const Sentry = require('@sentry/nestjs'); // ESM Syntax @@ -38,12 +38,12 @@ Sentry.init({ Note that it is necessary to initialize Sentry **before you import any package that may be instrumented by us**. -## Span Decorator +## SentryTraced -Use the @SentryTraced() decorator to gain additional performance insights for any function within your NestJS +Use the `@SentryTraced()` decorator to gain additional performance insights for any function within your NestJS applications. -```js +```typescript import { Injectable } from '@nestjs/common'; import { SentryTraced } from '@sentry/nestjs'; @@ -56,6 +56,35 @@ export class ExampleService { } ``` +## SentryCron + +Use the `@SentryCron()` decorator to augment the native NestJS `@Cron` decorator to send check-ins to Sentry before and +after each cron job run. + +```typescript +import { Cron } from '@nestjs/schedule'; +import { SentryCron, MonitorConfig } from '@sentry/nestjs'; +import type { MonitorConfig } from '@sentry/types'; + +const monitorConfig: MonitorConfig = { + schedule: { + type: 'crontab', + value: '* * * * *', + }, + checkinMargin: 2, // In minutes. Optional. + maxRuntime: 10, // In minutes. Optional. + timezone: 'America/Los_Angeles', // Optional. +}; + +export class MyCronService { + @Cron('* * * * *') + @SentryCron('my-monitor-slug', monitorConfig) + handleCron() { + // Your cron job logic here + } +} +``` + ## Links - [Official SDK Docs](https://docs.sentry.io/platforms/javascript/guides/nestjs/) diff --git a/packages/node/package.json b/packages/node/package.json index 6a607c5c09fe..68dec88cf54c 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -87,7 +87,7 @@ "@opentelemetry/resources": "^1.25.1", "@opentelemetry/sdk-trace-base": "^1.25.1", "@opentelemetry/semantic-conventions": "^1.25.1", - "@prisma/instrumentation": "5.16.1", + "@prisma/instrumentation": "5.17.0", "@sentry/core": "8.18.0", "@sentry/opentelemetry": "8.18.0", "@sentry/types": "8.18.0", diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 632b6023e7a3..615506605c9b 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -1,4 +1,4 @@ -import type { ClientRequest, ServerResponse } from 'node:http'; +import type { ClientRequest, IncomingMessage, RequestOptions, ServerResponse } from 'node:http'; import type { Span } from '@opentelemetry/api'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; @@ -37,8 +37,11 @@ interface HttpOptions { * * The `url` param contains the entire URL, including query string (if any), protocol, host, etc. of the outgoing request. * For example: `'https://someService.com/users/details?id=123'` + * + * The `request` param contains the original {@type RequestOptions} object used to make the outgoing request. + * You can use it to filter on additional properties like method, headers, etc. */ - ignoreOutgoingRequests?: (url: string) => boolean; + ignoreOutgoingRequests?: (url: string, request: RequestOptions) => boolean; /** * Do not capture spans or breadcrumbs for incoming HTTP requests to URLs where the given callback returns `true`. @@ -46,8 +49,11 @@ interface HttpOptions { * * The `urlPath` param consists of the URL path and query string (if any) of the incoming request. * For example: `'/users/details?id=123'` + * + * The `request` param contains the original {@type IncomingMessage} object of the incoming request. + * You can use it to filter on additional properties like method, headers, etc. */ - ignoreIncomingRequests?: (urlPath: string) => boolean; + ignoreIncomingRequests?: (urlPath: string, request: IncomingMessage) => boolean; /** * Additional instrumentation options that are passed to the underlying HttpInstrumentation. @@ -101,7 +107,7 @@ export const instrumentHttp = Object.assign( } const _ignoreOutgoingRequests = _httpOptions.ignoreOutgoingRequests; - if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url)) { + if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url, request)) { return true; } @@ -120,7 +126,7 @@ export const instrumentHttp = Object.assign( } const _ignoreIncomingRequests = _httpOptions.ignoreIncomingRequests; - if (urlPath && _ignoreIncomingRequests && _ignoreIncomingRequests(urlPath)) { + if (urlPath && _ignoreIncomingRequests && _ignoreIncomingRequests(urlPath, request)) { return true; } diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index 6f9fe5dad6d1..6a2acd874e67 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -289,9 +289,7 @@ export function continueTrace(options: Parameters[0 function getActiveSpanWrapper(parentSpan: Span | SentrySpan | undefined | null): (callback: () => T) => T { return parentSpan !== undefined ? (callback: () => T) => { - // We cast this, because the OTEL Span has a few more methods than our Span interface - // TODO: Add these missing methods to the Span interface - return withActiveSpan(parentSpan as Span, callback); + return withActiveSpan(parentSpan, callback); } : (callback: () => T) => callback(); } diff --git a/packages/remix/package.json b/packages/remix/package.json index 92a6d04b3436..7fb23dbce78d 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -54,7 +54,7 @@ "dependencies": { "@opentelemetry/instrumentation-http": "0.52.1", "@remix-run/router": "1.x", - "@sentry/cli": "^2.32.1", + "@sentry/cli": "^2.32.2", "@sentry/core": "8.18.0", "@sentry/node": "8.18.0", "@sentry/opentelemetry": "8.18.0", diff --git a/packages/replay-canvas/.eslintrc.js b/packages/replay-canvas/.eslintrc.js index 216184b75e47..0c83f7d0ff9d 100644 --- a/packages/replay-canvas/.eslintrc.js +++ b/packages/replay-canvas/.eslintrc.js @@ -5,18 +5,4 @@ module.exports = { extends: ['../../.eslintrc.js'], - overrides: [ - { - files: ['src/**/*.ts'], - }, - { - files: ['jest.setup.ts', 'jest.config.ts'], - parserOptions: { - project: ['tsconfig.test.json'], - }, - rules: { - 'no-console': 'off', - }, - }, - ], }; diff --git a/packages/replay-canvas/jest.config.js b/packages/replay-canvas/jest.config.js deleted file mode 100644 index cd02790794a7..000000000000 --- a/packages/replay-canvas/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -const baseConfig = require('../../jest/jest.config.js'); - -module.exports = { - ...baseConfig, - testEnvironment: 'jsdom', -}; diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index cc0c3d589847..014342ac2482 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -47,8 +47,8 @@ "clean": "rimraf build sentry-replay-*.tgz", "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", - "test": "jest", - "test:watch": "jest --watch", + "test": "vitest run", + "test:watch": "vitest --watch", "yalc:publish": "yalc publish --push --sig" }, "publishConfig": { @@ -65,7 +65,6 @@ }, "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { - "@babel/core": "^7.17.5", "@sentry-internal/rrweb": "2.25.0" }, "dependencies": { diff --git a/packages/replay-canvas/test/canvas.test.ts b/packages/replay-canvas/test/canvas.test.ts index a7de3b6a28a3..6f75321a5ab5 100644 --- a/packages/replay-canvas/test/canvas.test.ts +++ b/packages/replay-canvas/test/canvas.test.ts @@ -1,10 +1,16 @@ +/** + * @vitest-environment jsdom + */ + +import { beforeEach, expect, it, vi } from 'vitest'; + import { CanvasManager } from '@sentry-internal/rrweb'; import { _replayCanvasIntegration, replayCanvasIntegration } from '../src/canvas'; -jest.mock('@sentry-internal/rrweb'); +vi.mock('@sentry-internal/rrweb'); beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('initializes with default options', () => { diff --git a/packages/replay-canvas/tsconfig.test.json b/packages/replay-canvas/tsconfig.test.json index 3995d3e18e59..f4e8a1624f08 100644 --- a/packages/replay-canvas/tsconfig.test.json +++ b/packages/replay-canvas/tsconfig.test.json @@ -1,11 +1,11 @@ { "extends": "./tsconfig.json", - "include": ["test/**/*.ts", "jest.config.ts", "jest.setup.ts"], + "include": ["test/**/*.ts", "vite.config.ts"], "compilerOptions": { "lib": ["DOM", "ES2018"], - "types": ["node", "jest"], + "types": ["node"], "esModuleInterop": true, "allowJs": true, "noImplicitAny": true, diff --git a/packages/replay-canvas/vite.config.ts b/packages/replay-canvas/vite.config.ts new file mode 100644 index 000000000000..0582a58f479a --- /dev/null +++ b/packages/replay-canvas/vite.config.ts @@ -0,0 +1,5 @@ +import baseConfig from '../../vite/vite.config'; + +export default { + ...baseConfig, +}; diff --git a/packages/replay-worker/jest.config.js b/packages/replay-worker/jest.config.js deleted file mode 100644 index 24f49ab59a4c..000000000000 --- a/packages/replay-worker/jest.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../../jest/jest.config.js'); diff --git a/packages/replay-worker/package.json b/packages/replay-worker/package.json index 07e967a3bc33..597e44329f33 100644 --- a/packages/replay-worker/package.json +++ b/packages/replay-worker/package.json @@ -32,8 +32,8 @@ "clean": "rimraf build", "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", - "test": "jest", - "test:watch": "jest --watch" + "test": "vitest run", + "test:watch": "vitest --watch" }, "repository": { "type": "git", diff --git a/packages/replay-worker/test/unit/Compressor.test.ts b/packages/replay-worker/test/unit/Compressor.test.ts index 9ac4a5bb5a6c..dcce14edce70 100644 --- a/packages/replay-worker/test/unit/Compressor.test.ts +++ b/packages/replay-worker/test/unit/Compressor.test.ts @@ -1,3 +1,5 @@ +import { describe, expect, it } from 'vitest'; + import { decompressSync, strFromU8 } from 'fflate'; import { Compressor } from '../../src/Compressor'; diff --git a/packages/replay-worker/tsconfig.test.json b/packages/replay-worker/tsconfig.test.json index 6a49bbfc2a41..2910a21e50fc 100644 --- a/packages/replay-worker/tsconfig.test.json +++ b/packages/replay-worker/tsconfig.test.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", - "include": ["test/**/*.ts"], + "include": ["test/**/*.ts", "vite.config.ts"], "compilerOptions": { - "types": ["node", "jest"] + "types": ["node"] } } diff --git a/packages/replay-worker/vite.config.ts b/packages/replay-worker/vite.config.ts new file mode 100644 index 000000000000..0582a58f479a --- /dev/null +++ b/packages/replay-worker/vite.config.ts @@ -0,0 +1,5 @@ +import baseConfig from '../../vite/vite.config'; + +export default { + ...baseConfig, +}; diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index cc5cb45213b9..a2ee74fd7cfa 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -219,13 +219,13 @@ export interface Span { * Set a single attribute on the span. * Set it to `undefined` to remove the attribute. */ - setAttribute(key: string, value: SpanAttributeValue | undefined): void; + setAttribute(key: string, value: SpanAttributeValue | undefined): this; /** * Set multiple attributes on the span. * Any attribute set to `undefined` will be removed. */ - setAttributes(attributes: SpanAttributes): void; + setAttributes(attributes: SpanAttributes): this; /** * Sets the status attribute on the current span. @@ -247,4 +247,19 @@ export interface Span { * Adds an event to the Span. */ addEvent(name: string, attributesOrStartTime?: SpanAttributes | SpanTimeInput, startTime?: SpanTimeInput): this; + + /** + * NOT USED IN SENTRY, only added for compliance with OTEL Span interface + */ + addLink(link: unknown): this; + + /** + * NOT USED IN SENTRY, only added for compliance with OTEL Span interface + */ + addLinks(links: unknown): this; + + /** + * NOT USED IN SENTRY, only added for compliance with OTEL Span interface + */ + recordException(exception: unknown, time?: number): void; } diff --git a/packages/vercel-edge/jest.config.js b/packages/vercel-edge/jest.config.js deleted file mode 100644 index dfc8f746b929..000000000000 --- a/packages/vercel-edge/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -const baseConfig = require('../../jest/jest.config.js'); - -module.exports = { - ...baseConfig, - // TODO: Fix tests to work with the Edge environment - // testEnvironment: '@edge-runtime/jest-environment', -}; diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index 34c4a5a0e828..f9dbdc7789e9 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -44,8 +44,7 @@ "@sentry/utils": "8.18.0" }, "devDependencies": { - "@edge-runtime/jest-environment": "2.2.3", - "@edge-runtime/types": "2.2.3" + "@edge-runtime/types": "3.0.1" }, "scripts": { "build": "run-p build:transpile build:types", @@ -63,8 +62,8 @@ "clean": "rimraf build coverage sentry-vercel-edge-*.tgz", "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", - "test": "jest", - "test:watch": "jest --watch", + "test": "vitest run", + "test:watch": "vitest --watch", "yalc:publish": "yalc publish --push --sig" }, "volta": { diff --git a/packages/vercel-edge/test/async.test.ts b/packages/vercel-edge/test/async.test.ts index 37b357ab4910..a4423e0ca434 100644 --- a/packages/vercel-edge/test/async.test.ts +++ b/packages/vercel-edge/test/async.test.ts @@ -1,6 +1,7 @@ import { Scope, getCurrentScope, getGlobalScope, getIsolationScope, withIsolationScope, withScope } from '@sentry/core'; import { GLOBAL_OBJ } from '@sentry/utils'; import { AsyncLocalStorage } from 'async_hooks'; +import { beforeEach, describe, expect, it } from 'vitest'; import { setAsyncLocalStorageAsyncContextStrategy } from '../src/async'; describe('withScope()', () => { @@ -13,67 +14,73 @@ describe('withScope()', () => { setAsyncLocalStorageAsyncContextStrategy(); }); - it('will make the passed scope the active scope within the callback', done => { - withScope(scope => { - expect(getCurrentScope()).toBe(scope); - done(); - }); - }); - - it('will pass a scope that is different from the current active isolation scope', done => { - withScope(scope => { - expect(getIsolationScope()).not.toBe(scope); - done(); - }); - }); - - it('will always make the inner most passed scope the current scope when nesting calls', done => { - withIsolationScope(_scope1 => { - withIsolationScope(scope2 => { - expect(getIsolationScope()).toBe(scope2); + it('will make the passed scope the active scope within the callback', () => + new Promise(done => { + withScope(scope => { + expect(getCurrentScope()).toBe(scope); done(); }); - }); - }); + })); - it('forks the scope when not passing any scope', done => { - const initialScope = getCurrentScope(); - initialScope.setTag('aa', 'aa'); - - withScope(scope => { - expect(getCurrentScope()).toBe(scope); - scope.setTag('bb', 'bb'); - expect(scope).not.toBe(initialScope); - expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); - done(); - }); - }); - - it('forks the scope when passing undefined', done => { - const initialScope = getCurrentScope(); - initialScope.setTag('aa', 'aa'); - - withScope(undefined, scope => { - expect(getCurrentScope()).toBe(scope); - scope.setTag('bb', 'bb'); - expect(scope).not.toBe(initialScope); - expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); - done(); - }); - }); + it('will pass a scope that is different from the current active isolation scope', () => + new Promise(done => { + withScope(scope => { + expect(getIsolationScope()).not.toBe(scope); + done(); + }); + })); + + it('will always make the inner most passed scope the current scope when nesting calls', () => + new Promise(done => { + withIsolationScope(_scope1 => { + withIsolationScope(scope2 => { + expect(getIsolationScope()).toBe(scope2); + done(); + }); + }); + })); + + it('forks the scope when not passing any scope', () => + new Promise(done => { + const initialScope = getCurrentScope(); + initialScope.setTag('aa', 'aa'); + + withScope(scope => { + expect(getCurrentScope()).toBe(scope); + scope.setTag('bb', 'bb'); + expect(scope).not.toBe(initialScope); + expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); + done(); + }); + })); + + it('forks the scope when passing undefined', () => + new Promise(done => { + const initialScope = getCurrentScope(); + initialScope.setTag('aa', 'aa'); + + withScope(undefined, scope => { + expect(getCurrentScope()).toBe(scope); + scope.setTag('bb', 'bb'); + expect(scope).not.toBe(initialScope); + expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); + done(); + }); + })); - it('sets the passed in scope as active scope', done => { - const initialScope = getCurrentScope(); - initialScope.setTag('aa', 'aa'); + it('sets the passed in scope as active scope', () => + new Promise(done => { + const initialScope = getCurrentScope(); + initialScope.setTag('aa', 'aa'); - const customScope = new Scope(); + const customScope = new Scope(); - withScope(customScope, scope => { - expect(getCurrentScope()).toBe(customScope); - expect(scope).toBe(customScope); - done(); - }); - }); + withScope(customScope, scope => { + expect(getCurrentScope()).toBe(customScope); + expect(scope).toBe(customScope); + done(); + }); + })); }); describe('withIsolationScope()', () => { @@ -86,65 +93,71 @@ describe('withIsolationScope()', () => { setAsyncLocalStorageAsyncContextStrategy(); }); - it('will make the passed isolation scope the active isolation scope within the callback', done => { - withIsolationScope(scope => { - expect(getIsolationScope()).toBe(scope); - done(); - }); - }); - - it('will pass an isolation scope that is different from the current active scope', done => { - withIsolationScope(scope => { - expect(getCurrentScope()).not.toBe(scope); - done(); - }); - }); - - it('will always make the inner most passed scope the current scope when nesting calls', done => { - withIsolationScope(_scope1 => { - withIsolationScope(scope2 => { - expect(getIsolationScope()).toBe(scope2); + it('will make the passed isolation scope the active isolation scope within the callback', () => + new Promise(done => { + withIsolationScope(scope => { + expect(getIsolationScope()).toBe(scope); done(); }); - }); - }); + })); - it('forks the isolation scope when not passing any isolation scope', done => { - const initialScope = getIsolationScope(); - initialScope.setTag('aa', 'aa'); - - withIsolationScope(scope => { - expect(getIsolationScope()).toBe(scope); - scope.setTag('bb', 'bb'); - expect(scope).not.toBe(initialScope); - expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); - done(); - }); - }); - - it('forks the isolation scope when passing undefined', done => { - const initialScope = getIsolationScope(); - initialScope.setTag('aa', 'aa'); - - withIsolationScope(undefined, scope => { - expect(getIsolationScope()).toBe(scope); - scope.setTag('bb', 'bb'); - expect(scope).not.toBe(initialScope); - expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); - done(); - }); - }); + it('will pass an isolation scope that is different from the current active scope', () => + new Promise(done => { + withIsolationScope(scope => { + expect(getCurrentScope()).not.toBe(scope); + done(); + }); + })); + + it('will always make the inner most passed scope the current scope when nesting calls', () => + new Promise(done => { + withIsolationScope(_scope1 => { + withIsolationScope(scope2 => { + expect(getIsolationScope()).toBe(scope2); + done(); + }); + }); + })); + + it('forks the isolation scope when not passing any isolation scope', () => + new Promise(done => { + const initialScope = getIsolationScope(); + initialScope.setTag('aa', 'aa'); + + withIsolationScope(scope => { + expect(getIsolationScope()).toBe(scope); + scope.setTag('bb', 'bb'); + expect(scope).not.toBe(initialScope); + expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); + done(); + }); + })); + + it('forks the isolation scope when passing undefined', () => + new Promise(done => { + const initialScope = getIsolationScope(); + initialScope.setTag('aa', 'aa'); + + withIsolationScope(undefined, scope => { + expect(getIsolationScope()).toBe(scope); + scope.setTag('bb', 'bb'); + expect(scope).not.toBe(initialScope); + expect(scope.getScopeData().tags).toEqual({ aa: 'aa', bb: 'bb' }); + done(); + }); + })); - it('sets the passed in isolation scope as active isolation scope', done => { - const initialScope = getIsolationScope(); - initialScope.setTag('aa', 'aa'); + it('sets the passed in isolation scope as active isolation scope', () => + new Promise(done => { + const initialScope = getIsolationScope(); + initialScope.setTag('aa', 'aa'); - const customScope = new Scope(); + const customScope = new Scope(); - withIsolationScope(customScope, scope => { - expect(getIsolationScope()).toBe(customScope); - expect(scope).toBe(customScope); - done(); - }); - }); + withIsolationScope(customScope, scope => { + expect(getIsolationScope()).toBe(customScope); + expect(scope).toBe(customScope); + done(); + }); + })); }); diff --git a/packages/vercel-edge/test/sdk.test.ts b/packages/vercel-edge/test/sdk.test.ts index f693e2777d99..b1367716c73a 100644 --- a/packages/vercel-edge/test/sdk.test.ts +++ b/packages/vercel-edge/test/sdk.test.ts @@ -1,9 +1,11 @@ +import { describe, expect, it, vi } from 'vitest'; + import * as SentryCore from '@sentry/core'; import { init } from '../src/sdk'; describe('init', () => { it('initializes and returns client', () => { - const initSpy = jest.spyOn(SentryCore, 'initAndBind'); + const initSpy = vi.spyOn(SentryCore, 'initAndBind'); expect(init({})).not.toBeUndefined(); expect(initSpy).toHaveBeenCalledTimes(1); diff --git a/packages/vercel-edge/test/transports/index.test.ts b/packages/vercel-edge/test/transports/index.test.ts index 252522171780..c2ead21998be 100644 --- a/packages/vercel-edge/test/transports/index.test.ts +++ b/packages/vercel-edge/test/transports/index.test.ts @@ -1,3 +1,5 @@ +import { afterAll, describe, expect, it, vi } from 'vitest'; + import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; @@ -23,7 +25,7 @@ class Headers { } } -const mockFetch = jest.fn(); +const mockFetch = vi.fn(); const oldFetch = global.fetch; global.fetch = mockFetch; @@ -57,7 +59,7 @@ describe('Edge Transport', () => { it('sets rate limit headers', async () => { const headers = { - get: jest.fn(), + get: vi.fn(), }; mockFetch.mockImplementationOnce(() => @@ -110,8 +112,8 @@ describe('IsolatedPromiseBuffer', () => { it('should not call tasks until drained', async () => { const ipb = new IsolatedPromiseBuffer(); - const task1 = jest.fn(() => Promise.resolve({})); - const task2 = jest.fn(() => Promise.resolve({})); + const task1 = vi.fn(() => Promise.resolve({})); + const task2 = vi.fn(() => Promise.resolve({})); await ipb.add(task1); await ipb.add(task2); @@ -128,10 +130,10 @@ describe('IsolatedPromiseBuffer', () => { it('should not allow adding more items than the specified limit', async () => { const ipb = new IsolatedPromiseBuffer(3); - const task1 = jest.fn(() => Promise.resolve({})); - const task2 = jest.fn(() => Promise.resolve({})); - const task3 = jest.fn(() => Promise.resolve({})); - const task4 = jest.fn(() => Promise.resolve({})); + const task1 = vi.fn(() => Promise.resolve({})); + const task2 = vi.fn(() => Promise.resolve({})); + const task3 = vi.fn(() => Promise.resolve({})); + const task4 = vi.fn(() => Promise.resolve({})); await ipb.add(task1); await ipb.add(task2); @@ -143,8 +145,8 @@ describe('IsolatedPromiseBuffer', () => { it('should not throw when one of the tasks throws when drained', async () => { const ipb = new IsolatedPromiseBuffer(); - const task1 = jest.fn(() => Promise.resolve({})); - const task2 = jest.fn(() => Promise.reject(new Error())); + const task1 = vi.fn(() => Promise.resolve({})); + const task2 = vi.fn(() => Promise.reject(new Error())); await ipb.add(task1); await ipb.add(task2); diff --git a/packages/vercel-edge/test/wintercg-fetch.test.ts b/packages/vercel-edge/test/wintercg-fetch.test.ts index ae830ed9c1e8..34328734cd83 100644 --- a/packages/vercel-edge/test/wintercg-fetch.test.ts +++ b/packages/vercel-edge/test/wintercg-fetch.test.ts @@ -1,3 +1,5 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + import * as sentryCore from '@sentry/core'; import type { HandlerDataFetch, Integration } from '@sentry/types'; import * as sentryUtils from '@sentry/utils'; @@ -12,15 +14,15 @@ class FakeClient extends VercelEdgeClient { } } -const addFetchInstrumentationHandlerSpy = jest.spyOn(sentryUtils, 'addFetchInstrumentationHandler'); -const instrumentFetchRequestSpy = jest.spyOn(sentryCore, 'instrumentFetchRequest'); -const addBreadcrumbSpy = jest.spyOn(sentryCore, 'addBreadcrumb'); +const addFetchInstrumentationHandlerSpy = vi.spyOn(sentryUtils, 'addFetchInstrumentationHandler'); +const instrumentFetchRequestSpy = vi.spyOn(sentryCore, 'instrumentFetchRequest'); +const addBreadcrumbSpy = vi.spyOn(sentryCore, 'addBreadcrumb'); describe('WinterCGFetch instrumentation', () => { let client: FakeClient; beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); client = new FakeClient({ dsn: 'https://public@dsn.ingest.sentry.io/1337', @@ -35,7 +37,7 @@ describe('WinterCGFetch instrumentation', () => { stackParser: createStackParser(), }); - jest.spyOn(sentryCore, 'getClient').mockImplementation(() => client); + vi.spyOn(sentryCore, 'getClient').mockImplementation(() => client); }); it('should call `instrumentFetchRequest` for outgoing fetch requests', () => { diff --git a/packages/vercel-edge/tsconfig.test.json b/packages/vercel-edge/tsconfig.test.json index 87f6afa06b86..ca7dbeb3be94 100644 --- a/packages/vercel-edge/tsconfig.test.json +++ b/packages/vercel-edge/tsconfig.test.json @@ -1,11 +1,11 @@ { "extends": "./tsconfig.json", - "include": ["test/**/*"], + "include": ["test/**/*", "vite.config.ts"], "compilerOptions": { // should include all types from `./tsconfig.json` plus types for all test frameworks used - "types": ["node", "jest"] + "types": ["node"] // other package-specific, test-specific options } diff --git a/packages/vercel-edge/vite.config.ts b/packages/vercel-edge/vite.config.ts new file mode 100644 index 000000000000..0582a58f479a --- /dev/null +++ b/packages/vercel-edge/vite.config.ts @@ -0,0 +1,5 @@ +import baseConfig from '../../vite/vite.config'; + +export default { + ...baseConfig, +}; diff --git a/yarn.lock b/yarn.lock index 7cb6273253bc..e1afaa9a9f1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4008,40 +4008,17 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@edge-runtime/jest-environment@2.2.3": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@edge-runtime/jest-environment/-/jest-environment-2.2.3.tgz#2fef094d769f45b5018b33bdc58e664b35bbc312" - integrity sha512-5DEv8nzuMFGoVbNYbOz7/mileYSbq1/oIvisyTeSfyjId7Pc5Qh2t3BH7ixLa62aVz7oCQlALM4cYGbZQZw1YQ== - dependencies: - "@edge-runtime/vm" "3.0.3" - "@jest/environment" "29.5.0" - "@jest/fake-timers" "29.5.0" - jest-mock "29.5.0" - jest-util "29.5.0" - -"@edge-runtime/primitives@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@edge-runtime/primitives/-/primitives-3.0.3.tgz#adc6a4bd34c44faf81c954cf8e5816c7d408a1ea" - integrity sha512-YnfMWMRQABAH8IsnFMJWMW+SyB4ZeYBPnR7V0aqdnew7Pq60cbH5DyFjS/FhiLwvHQk9wBREmXD7PP0HooEQ1A== - -"@edge-runtime/primitives@4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@edge-runtime/primitives/-/primitives-4.0.1.tgz#12efffac1caa8a29ae8f86a3f87f20cc0ae07131" - integrity sha512-hxWUzx1SeyOed/Ea9Z6y6tyFKSj8gQWIdLilybTR2ene1IthLZE01A1SLGoch1szUdhFlUwpWDaYBYQw00lj2g== - -"@edge-runtime/types@2.2.3": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@edge-runtime/types/-/types-2.2.3.tgz#cb57b7215bcf406324ec591346b7b51c75a54bdf" - integrity sha512-zL0ENQWwdocECEQXVopGTfnqI0tJ8wzDOCoQymoc8MLRz+Zw2V1W0ex9vczniTUzB+H/P7ubMgx3GFzLp3NPBg== - dependencies: - "@edge-runtime/primitives" "4.0.1" +"@edge-runtime/primitives@5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@edge-runtime/primitives/-/primitives-5.0.1.tgz#29e2de862d8e02d1e85fd8aa1c73afe0317e6740" + integrity sha512-qjqqCa9v3IE7Fo9OnmkIWg9l0WUu3uOfUYomuOVxaaHqlIvNI75E5IB0XNNDypz249ObRSmjRj8jLjkBUmFYYw== -"@edge-runtime/vm@3.0.3": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@edge-runtime/vm/-/vm-3.0.3.tgz#92f1930d1eb8d0ccf6a3c165561cc22b2d9ddff8" - integrity sha512-SPfI1JeIRNs/4EEE2Oc0X6gG3RqjD1TnKu2lwmwFXq0435xgZGKhc3UiKkYAdoMn2dNFD73nlabMKHBRoMRpxg== +"@edge-runtime/types@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@edge-runtime/types/-/types-3.0.1.tgz#907bd98ec551deba6fd26b72189fde651779191b" + integrity sha512-yZSWlGMQXXtpE4m1WYRpjA8V9+uU3uKHJzx9lngSYDZYYABuYxb2bICBwE1NQD0WS/u/PreP/83GcndezMFmnQ== dependencies: - "@edge-runtime/primitives" "3.0.3" + "@edge-runtime/primitives" "5.0.1" "@ember-data/rfc395-data@^0.0.4": version "0.0.4" @@ -5664,16 +5641,6 @@ slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" - integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== - dependencies: - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/node" "*" - jest-mock "^29.5.0" - "@jest/environment@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" @@ -5684,18 +5651,6 @@ "@types/node" "*" jest-mock "^27.5.1" -"@jest/fake-timers@29.5.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" - integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== - dependencies: - "@jest/types" "^29.5.0" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-util "^29.5.0" - "@jest/fake-timers@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" @@ -5708,18 +5663,6 @@ jest-mock "^27.5.1" jest-util "^27.5.1" -"@jest/fake-timers@^29.5.0": - version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" - integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== - dependencies: - "@jest/types" "^29.6.3" - "@sinonjs/fake-timers" "^10.0.2" - "@types/node" "*" - jest-message-util "^29.7.0" - jest-mock "^29.7.0" - jest-util "^29.7.0" - "@jest/globals@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" @@ -5835,18 +5778,6 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" -"@jest/types@^29.5.0", "@jest/types@^29.6.3": - version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" - integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== - dependencies: - "@jest/schemas" "^29.6.3" - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^17.0.8" - chalk "^4.0.0" - "@josephg/resolvable@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb" @@ -6961,21 +6892,14 @@ dependencies: "@opentelemetry/semantic-conventions" "1.23.0" -"@opentelemetry/core@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.24.1.tgz#35ab9d2ac9ca938e0ffbdfa40c49c169ac8ba80d" - integrity sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg== - dependencies: - "@opentelemetry/semantic-conventions" "1.24.1" - -"@opentelemetry/core@1.25.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.8.0": +"@opentelemetry/core@1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.25.0.tgz#ad034f5c2669f589bd703bfbbaa38b51f8504053" integrity sha512-n0B3s8rrqGrasTgNkXLKXzN0fXo+6IYP7M5b7AMsrZM33f/y6DS6kJ0Btd7SespASWq8bgL3taLo0oe0vB52IQ== dependencies: "@opentelemetry/semantic-conventions" "1.25.0" -"@opentelemetry/core@1.25.1", "@opentelemetry/core@^1.23.0", "@opentelemetry/core@^1.25.1": +"@opentelemetry/core@1.25.1", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.23.0", "@opentelemetry/core@^1.25.1", "@opentelemetry/core@^1.8.0": version "1.25.1" resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.25.1.tgz#ff667d939d128adfc7c793edae2f6bca177f829d" integrity sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ== @@ -6991,10 +6915,10 @@ "@opentelemetry/context-base" "^0.12.0" semver "^7.1.3" -"@opentelemetry/instrumentation-aws-lambda@0.42.0": - version "0.42.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.42.0.tgz#b832660078cbcd65293405c9bcbf9d335f021f4d" - integrity sha512-GhV3s62W8gWXDuCdPkWj60W3giHGadHoGBPGW5Wud2fUK9lY6FiYxv6AmCokzugTaiRfB2RjsaJWd9xTtYttVA== +"@opentelemetry/instrumentation-aws-lambda@0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.43.0.tgz#6a96d582627873bb34d197655b7b69792f0f8da3" + integrity sha512-pSxcWlsE/pCWQRIw92sV2C+LmKXelYkjkA7C5s39iPUi4pZ2lA1nIiw+1R/y2pDEhUHcaKkNyljQr3cx9ZpVlQ== dependencies: "@opentelemetry/instrumentation" "^0.52.0" "@opentelemetry/propagator-aws-xray" "^1.3.1" @@ -7002,10 +6926,10 @@ "@opentelemetry/semantic-conventions" "^1.22.0" "@types/aws-lambda" "8.10.122" -"@opentelemetry/instrumentation-aws-sdk@0.42.0": - version "0.42.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.42.0.tgz#5e0884282814163c328c1b3ec68fe257108a2a04" - integrity sha512-6b4LQAeBSKU5RhKEP9rH+wMcKswlllIT9J65uREmnWQQJo5zogD6cWa2sJ814o9K25/aDi+zheVHDFDuA7iVCQ== +"@opentelemetry/instrumentation-aws-sdk@0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.43.0.tgz#b579d66e624cc1545f29d2858c180204e515e110" + integrity sha512-klfA48MT0uZY/mGw3cYdQeCXTyMhtY4FzHcZy9R7DdTcuCExgbxWrUlOSiqIJ5kBgsCZfBMEeA6UQKDBwa6X7Q== dependencies: "@opentelemetry/core" "^1.8.0" "@opentelemetry/instrumentation" "^0.52.0" @@ -7227,7 +7151,7 @@ "@opentelemetry/core" "1.25.0" "@opentelemetry/semantic-conventions" "1.25.0" -"@opentelemetry/resources@1.25.1", "@opentelemetry/resources@^1.23.0", "@opentelemetry/resources@^1.25.1": +"@opentelemetry/resources@1.25.1", "@opentelemetry/resources@^1.23.0", "@opentelemetry/resources@^1.25.1", "@opentelemetry/resources@^1.8.0": version "1.25.1" resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.25.1.tgz#bb9a674af25a1a6c30840b755bc69da2796fefbb" integrity sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ== @@ -7243,14 +7167,6 @@ "@opentelemetry/api" "^0.12.0" "@opentelemetry/core" "^0.12.0" -"@opentelemetry/resources@^1.8.0": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.24.1.tgz#5e2cb84814824f3b1e1017e6caeeee8402e0ad6e" - integrity sha512-cyv0MwAaPF7O86x5hk3NNgenMObeejZFLJJDVuSeSMIsknlsj3oOZzRv3qSzlwYomXsICfBeFFlxwHQte5mGXQ== - dependencies: - "@opentelemetry/core" "1.24.1" - "@opentelemetry/semantic-conventions" "1.24.1" - "@opentelemetry/sdk-logs@0.50.0": version "0.50.0" resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.50.0.tgz#6636492cf626a9666f61d91025e25243d1a43bfc" @@ -7300,17 +7216,12 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.23.0.tgz#627f2721b960fe586b7f72a07912cb7699f06eef" integrity sha512-MiqFvfOzfR31t8cc74CTP1OZfz7MbqpAnLCra8NqQoaHJX6ncIRTdYOQYBDQ2uFISDq0WY8Y9dDTWvsgzzBYRg== -"@opentelemetry/semantic-conventions@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz#d4bcebda1cb5146d47a2a53daaa7922f8e084dfb" - integrity sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw== - -"@opentelemetry/semantic-conventions@1.25.0", "@opentelemetry/semantic-conventions@^1.17.0", "@opentelemetry/semantic-conventions@^1.22.0", "@opentelemetry/semantic-conventions@^1.23.0": +"@opentelemetry/semantic-conventions@1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.0.tgz#390eb4d42a29c66bdc30066af9035645e9bb7270" integrity sha512-M+kkXKRAIAiAP6qYyesfrC5TOmDpDVtsxuGfPcqd9B/iBrac+E14jYwrgm0yZBUIbIP2OnqC3j+UgkXLm1vxUQ== -"@opentelemetry/semantic-conventions@1.25.1", "@opentelemetry/semantic-conventions@^1.25.1": +"@opentelemetry/semantic-conventions@1.25.1", "@opentelemetry/semantic-conventions@^1.17.0", "@opentelemetry/semantic-conventions@^1.22.0", "@opentelemetry/semantic-conventions@^1.23.0", "@opentelemetry/semantic-conventions@^1.25.1": version "1.25.1" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz#0deecb386197c5e9c2c28f2f89f51fb8ae9f145e" integrity sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ== @@ -7465,10 +7376,10 @@ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.9.1.tgz#d92bd2f7f006e0316cb4fda9d73f235965cf2c64" integrity sha512-caSOnG4kxcSkhqC/2ShV7rEoWwd3XrftokxJqOCMVvia4NYV/TPtJlS9C2os3Igxw/Qyxumj9GBQzcStzECvtQ== -"@prisma/instrumentation@5.16.1": - version "5.16.1" - resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-5.16.1.tgz#93f996f9c95874156badbb5edbb97994667f7c3f" - integrity sha512-4m5gRFWnQb8s/yTyGbMZkL7A5uJgqOWcWJxapwcAD0T0kh5sGPEVSQl/zTQvE9aduXhFAxOtC3gO+R8Hb5xO1Q== +"@prisma/instrumentation@5.17.0": + version "5.17.0" + resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-5.17.0.tgz#f741ff517f54b1a896fb8605e0d702f29855c6cb" + integrity sha512-c1Sle4ji8aasMcYfBBHFM56We4ljfenVtRmS8aY06BllS7SoU6SmJBwG7vil+GHiR0Yrh+t9iBwt4AY0Jr4KNQ== dependencies: "@opentelemetry/api" "^1.8" "@opentelemetry/instrumentation" "^0.49 || ^0.50 || ^0.51 || ^0.52.0" @@ -8172,45 +8083,45 @@ magic-string "0.30.8" unplugin "1.0.1" -"@sentry/cli-darwin@2.32.1": - version "2.32.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.32.1.tgz#9cb3b8cfb7068d40979514dee72e2bb3ad2c6d0a" - integrity sha512-z/lEwANTYPCzbWTZ2+eeeNYxRLllC8knd0h+vtAKlhmGw/fyc/N39cznIFyFu+dLJ6tTdjOWOeikHtKuS/7onw== - -"@sentry/cli-linux-arm64@2.32.1": - version "2.32.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.32.1.tgz#785a5d5d3d2919c581bf5b4efc638c3695d8c3bf" - integrity sha512-hsGqHYuecUl1Yhq4MhiRejfh1gNlmhyNPcQEoO/DDRBnGnJyEAdiDpKXJcc2e/lT9k40B55Ob2CP1SeY040T2w== - -"@sentry/cli-linux-arm@2.32.1": - version "2.32.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.32.1.tgz#7f9e8292850311bab263e7b84800eb407ff37998" - integrity sha512-m0lHkn+o4YKBq8KptGZvpT64FAwSl9mYvHZO9/ChnEGIJ/WyJwiN1X1r9JHVaW4iT5lD0Y5FAyq3JLkk0m0XHg== - -"@sentry/cli-linux-i686@2.32.1": - version "2.32.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.32.1.tgz#8e85fa58dee042e6a4642e960d226788f8e7288b" - integrity sha512-SuMLN1/ceFd3Q/B0DVyh5igjetTAF423txiABAHASenEev0lG0vZkRDXFclfgDtDUKRPmOXW7VDMirM3yZWQHQ== - -"@sentry/cli-linux-x64@2.32.1": - version "2.32.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.32.1.tgz#b68ed9c4ba163b6730d386dbeca828114f1c979b" - integrity sha512-x4FGd6xgvFddz8V/dh6jii4wy9qjWyvYLBTz8Fhi9rIP+b8wQ3oxwHIdzntareetZP7C1ggx+hZheiYocNYVwA== - -"@sentry/cli-win32-i686@2.32.1": - version "2.32.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.32.1.tgz#e2532893f87f5d180f6e56f49904d4ac141c8788" - integrity sha512-i6aZma9mFzR+hqMY5VliQZEX6ypP/zUjPK0VtIMYWs5cC6PsQLRmuoeJmy3Z7d4nlh0CdK5NPC813Ej6RY6/vg== - -"@sentry/cli-win32-x64@2.32.1": - version "2.32.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.32.1.tgz#6b60607cbba243f3708779443cd3f16e09d4289c" - integrity sha512-B58w/lRHLb4MUSjJNfMMw2cQykfimDCMLMmeK+1EiT2RmSeNQliwhhBxYcKk82a8kszH6zg3wT2vCea7LyPUyA== - -"@sentry/cli@^2.22.3", "@sentry/cli@^2.32.1": - version "2.32.1" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.32.1.tgz#80932d3d58e6d3b52e2bd705673e08deeb9cb5b0" - integrity sha512-MWkbkzZfnlE7s2pPbg4VozRSAeMlIObfZlTIou9ye6XnPt6ZmmxCLOuOgSKMv4sXg6aeqKNzMNiadThxCWyvPg== +"@sentry/cli-darwin@2.32.2": + version "2.32.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.32.2.tgz#ae0147b73c2d98f8a64d1e9a1431c1afc0d6ec33" + integrity sha512-GDtePIavx3FKSRowdPdtIssahn46MfFFYNN+s7a9MjlhFwJtvC9A1bSDw7ksEtDaQolepUwmLPHaVe19y0T/zw== + +"@sentry/cli-linux-arm64@2.32.2": + version "2.32.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.32.2.tgz#a2f47447e1ad4053a7cff4f176e3b7c66b8ad24e" + integrity sha512-VECLVC1rLyvXk6rTVUfmfs4vhANjMgm4BVKGlA3rydmf2PJw2/NfipH3KeyijdE2vEoyLri+/6HH883pP0iniQ== + +"@sentry/cli-linux-arm@2.32.2": + version "2.32.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.32.2.tgz#59fa70957d4fd8e06fdb2f2a59cfaf7165431859" + integrity sha512-u9s08wr8bDDqsAl6pk9iGGlOHtU+T8btU6voNKy71QzeIBpV9c8VVk/OnmP9aswp/ea4NY416yjnzcTvCrFKAw== + +"@sentry/cli-linux-i686@2.32.2": + version "2.32.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.32.2.tgz#479c972781ffce2ca448f084a4d7787106dfaef1" + integrity sha512-XhofQz32OqLrQK1DEOsryhT7d29Df6VkccvxueGoIt2gpXEXtgRczsUwZjZqquDdkNCt+HPj9eUGcj8pY8JkmQ== + +"@sentry/cli-linux-x64@2.32.2": + version "2.32.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.32.2.tgz#fa4ede3ac428a92e305f15f23515a9f977f1d1e9" + integrity sha512-anyng4Qqt7zX4ZY4IzDH1RJWAVZNBe6sUHcuciNy7giCU3B4/XnxAHlwYmBSN5txpaumsWdstPgRKEUJG6AOSA== + +"@sentry/cli-win32-i686@2.32.2": + version "2.32.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.32.2.tgz#393176a54fecd5226571d980b84425ef73cf0b1a" + integrity sha512-/auqx7QXG7F556fNK7vaB26pX7Far1CQMfI65iV4u/VWg6gV2WfvJWXB4iowhjqkYv56sZ+zOymLkEVF0R8wtg== + +"@sentry/cli-win32-x64@2.32.2": + version "2.32.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.32.2.tgz#145b56132c04c62bb09bac802602295cd60e43e1" + integrity sha512-w7hW2sEWVYQquqdILBSFhcVW+HdoyLqVPPkLPAXRSLTwBnuni9nQEIdXr0h/7db+K3cm7PvWndp5ixVyswLHZA== + +"@sentry/cli@^2.22.3", "@sentry/cli@^2.32.2": + version "2.32.2" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.32.2.tgz#ed1ee74a498a3cf1e3344309f600184ab270b52e" + integrity sha512-m/6Z3FWu+rTd8jepVlJPKQhvbT8vCjt0N7BSWZiEUVW/8mhwAYJiwO0b+Ch/u4IqbBg1dp3805q5TFPl4AdrNw== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -8218,13 +8129,13 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.32.1" - "@sentry/cli-linux-arm" "2.32.1" - "@sentry/cli-linux-arm64" "2.32.1" - "@sentry/cli-linux-i686" "2.32.1" - "@sentry/cli-linux-x64" "2.32.1" - "@sentry/cli-win32-i686" "2.32.1" - "@sentry/cli-win32-x64" "2.32.1" + "@sentry/cli-darwin" "2.32.2" + "@sentry/cli-linux-arm" "2.32.2" + "@sentry/cli-linux-arm64" "2.32.2" + "@sentry/cli-linux-i686" "2.32.2" + "@sentry/cli-linux-x64" "2.32.2" + "@sentry/cli-win32-i686" "2.32.2" + "@sentry/cli-win32-x64" "2.32.2" "@sentry/vite-plugin@2.19.0": version "2.19.0" @@ -10143,13 +10054,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yargs@^17.0.8": - version "17.0.24" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" - integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== - dependencies: - "@types/yargs-parser" "*" - "@types/yauzl@^2.9.1": version "2.10.0" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" @@ -22472,30 +22376,6 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-message-util@^29.5.0, jest-message-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" - integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== - dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.6.3" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^29.7.0" - slash "^3.0.0" - stack-utils "^2.0.3" - -jest-mock@29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" - integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== - dependencies: - "@jest/types" "^29.5.0" - "@types/node" "*" - jest-util "^29.5.0" - jest-mock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" @@ -22504,15 +22384,6 @@ jest-mock@^27.5.1: "@jest/types" "^27.5.1" "@types/node" "*" -jest-mock@^29.5.0, jest-mock@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" - integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - jest-util "^29.7.0" - jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" @@ -22639,18 +22510,6 @@ jest-snapshot@^27.5.1: pretty-format "^27.5.1" semver "^7.3.2" -jest-util@29.5.0: - version "29.5.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" - integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== - dependencies: - "@jest/types" "^29.5.0" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - jest-util@^27.0.0, jest-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" @@ -22663,18 +22522,6 @@ jest-util@^27.0.0, jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.5.0, jest-util@^29.7.0: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" - integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== - dependencies: - "@jest/types" "^29.6.3" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - jest-validate@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067"