diff --git a/.github/workflows/external-contributors.yml b/.github/workflows/external-contributors.yml index aac1805c1bb5..e9b1e05a2c92 100644 --- a/.github/workflows/external-contributors.yml +++ b/.github/workflows/external-contributors.yml @@ -41,8 +41,8 @@ jobs: # This token is scoped to Daniel Griesser # If we used the default GITHUB_TOKEN, the resulting PR would not trigger CI :( token: ${{ secrets.REPO_SCOPED_TOKEN }} - commit-message: "ref: Add external contributor to CHANGELOG.md" - title: "ref: Add external contributor to CHANGELOG.md" + commit-message: "chore: Add external contributor to CHANGELOG.md" + title: "chore: Add external contributor to CHANGELOG.md" branch: 'external-contributor/patch-${{ github.event.pull_request.user.login }}' base: 'develop' delete-branch: true diff --git a/.size-limit.js b/.size-limit.js index 8b506b8f683b..4903d38fef62 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -79,7 +79,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'), gzip: true, - limit: '78.1 KB', + limit: '78.2 KB', }, { name: '@sentry/browser (incl. Tracing, Replay, Feedback)', diff --git a/CHANGELOG.md b/CHANGELOG.md index 58035fb653c2..23375eb92a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,26 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.38.0 + +- docs: Improve docstrings for node otel integrations ([#14217](https://github.com/getsentry/sentry-javascript/pull/14217)) +- feat(browser): Add moduleMetadataIntegration lazy loading support ([#13817](https://github.com/getsentry/sentry-javascript/pull/13817)) +- feat(core): Add trpc path to context in trpcMiddleware ([#14218](https://github.com/getsentry/sentry-javascript/pull/14218)) +- feat(deps): Bump @opentelemetry/instrumentation-amqplib from 0.42.0 to 0.43.0 ([#14230](https://github.com/getsentry/sentry-javascript/pull/14230)) +- feat(deps): Bump @sentry/cli from 2.37.0 to 2.38.2 ([#14232](https://github.com/getsentry/sentry-javascript/pull/14232)) +- feat(node): Add `knex` integration ([#13526](https://github.com/getsentry/sentry-javascript/pull/13526)) +- feat(node): Add `tedious` integration ([#13486](https://github.com/getsentry/sentry-javascript/pull/13486)) +- feat(utils): Single implementation to fetch debug ids ([#14199](https://github.com/getsentry/sentry-javascript/pull/14199)) +- fix(browser): Avoid recording long animation frame spans starting before their parent span ([#14186](https://github.com/getsentry/sentry-javascript/pull/14186)) +- fix(node): Include `debug_meta` with ANR events ([#14203](https://github.com/getsentry/sentry-javascript/pull/14203)) +- fix(nuxt): Fix dynamic import rollup plugin to work with latest nitro ([#14243](https://github.com/getsentry/sentry-javascript/pull/14243)) +- fix(react): Support wildcard routes on React Router 6 ([#14205](https://github.com/getsentry/sentry-javascript/pull/14205)) +- fix(spotlight): Export spotlightBrowserIntegration from the main browser package ([#14208](https://github.com/getsentry/sentry-javascript/pull/14208)) +- ref(browser): Ensure start time of interaction root and child span is aligned ([#14188](https://github.com/getsentry/sentry-javascript/pull/14188)) +- ref(nextjs): Make build-time value injection turbopack compatible ([#14081](https://github.com/getsentry/sentry-javascript/pull/14081)) + +Work in this release was contributed by @grahamhency, @Zen-cronic, @gilisho and @phuctm97. Thank you for your contributions! + ## 8.37.1 - feat(deps): Bump @opentelemetry/instrumentation from 0.53.0 to 0.54.0 for @sentry/opentelemetry ([#14187](https://github.com/getsentry/sentry-javascript/pull/14187)) diff --git a/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/init.js b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/init.js new file mode 100644 index 000000000000..5991109fce66 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/init.js @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/browser'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [], +}); + +window.Sentry = { + ...Sentry, + // Ensure this is _not_ set + moduleMetadataIntegration: undefined, +}; diff --git a/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/subject.js b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/subject.js new file mode 100644 index 000000000000..312b4cf91f10 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/subject.js @@ -0,0 +1,7 @@ +window._testLazyLoadIntegration = async function run() { + const integration = await window.Sentry.lazyLoadIntegration('moduleMetadataIntegration'); + + window.Sentry.getClient()?.addIntegration(integration()); + + window._integrationLoaded = true; +}; diff --git a/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/test.ts b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/test.ts new file mode 100644 index 000000000000..ac8465736743 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/test.ts @@ -0,0 +1,33 @@ +import { expect } from '@playwright/test'; +import { SDK_VERSION } from '@sentry/browser'; + +import { sentryTest } from '../../../../utils/fixtures'; + +sentryTest('it allows to lazy load the moduleMetadata integration', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route(`https://browser.sentry-cdn.com/${SDK_VERSION}/modulemetadata.min.js`, route => { + return route.fulfill({ + status: 200, + contentType: 'application/javascript;', + body: "window.Sentry.moduleMetadataIntegration = () => ({ name: 'ModuleMetadata' })", + }); + }); + + await page.goto(url); + + const hasIntegration = await page.evaluate('!!window.Sentry.getClient()?.getIntegrationByName("ModuleMetadata")'); + expect(hasIntegration).toBe(false); + + const scriptTagsBefore = await page.evaluate('document.querySelectorAll("script").length'); + + await page.evaluate('window._testLazyLoadIntegration()'); + await page.waitForFunction('window._integrationLoaded'); + + const scriptTagsAfter = await page.evaluate('document.querySelectorAll("script").length'); + + const hasIntegration2 = await page.evaluate('!!window.Sentry.getClient()?.getIntegrationByName("ModuleMetadata")'); + expect(hasIntegration2).toBe(true); + + expect(scriptTagsAfter).toBe(scriptTagsBefore + 1); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js new file mode 100644 index 000000000000..f00d680435bb --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js @@ -0,0 +1,17 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 9000, + enableLongTask: false, + enableLongAnimationFrame: true, + instrumentPageLoad: false, + enableInp: false, + }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/subject.js new file mode 100644 index 000000000000..b02ed6efa33b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/subject.js @@ -0,0 +1,18 @@ +function getElapsed(startTime) { + const time = Date.now(); + return time - startTime; +} + +function handleClick() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } + window.history.pushState({}, '', `#myHeading`); +} + +const button = document.getElementById('clickme'); + +console.log('button', button); + +button.addEventListener('click', handleClick); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/template.html new file mode 100644 index 000000000000..1d883292beb0 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/template.html @@ -0,0 +1,13 @@ + + + + + + + + +

My Heading

+ + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts new file mode 100644 index 000000000000..e6fb88232d63 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts @@ -0,0 +1,31 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + "doesn't capture long animation frame that starts before a navigation.", + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + + const navigationTransactionEventPromise = getFirstSentryEnvelopeRequest(page); + + await page.locator('#clickme').click(); + + const navigationTransactionEvent = await navigationTransactionEventPromise; + + expect(navigationTransactionEvent.contexts?.trace?.op).toBe('navigation'); + + const loafSpans = navigationTransactionEvent.spans?.filter(s => s.op?.startsWith('ui.long-animation-frame')); + + expect(loafSpans?.length).toEqual(0); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js index 1f396416d855..5986089e5aa4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js @@ -15,5 +15,4 @@ Sentry.init({ }), ], tracesSampleRate: 1, - debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js index 2c477161b9f4..d814f8875715 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js @@ -13,5 +13,5 @@ longTaskButton?.addEventListener('click', () => { } // trigger a navigation in the same event loop tick - window.history.pushState({}, '', '/#myHeading'); + window.history.pushState({}, '', '#myHeading'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts index fe2efb6b3565..d7504eba840c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts @@ -15,9 +15,11 @@ sentryTest( await page.goto(url); + const navigationTransactionEventPromise = getFirstSentryEnvelopeRequest(page); + await page.locator('#myButton').click(); - const navigationTransactionEvent = await getFirstSentryEnvelopeRequest(page, url); + const navigationTransactionEvent = await navigationTransactionEventPromise; expect(navigationTransactionEvent.contexts?.trace?.op).toBe('navigation'); diff --git a/dev-packages/browser-integration-tests/utils/generatePlugin.ts b/dev-packages/browser-integration-tests/utils/generatePlugin.ts index acc583506df4..26a086bf2a77 100644 --- a/dev-packages/browser-integration-tests/utils/generatePlugin.ts +++ b/dev-packages/browser-integration-tests/utils/generatePlugin.ts @@ -37,6 +37,7 @@ const IMPORTED_INTEGRATION_CDN_BUNDLE_PATHS: Record = { reportingObserverIntegration: 'reportingobserver', sessionTimingIntegration: 'sessiontiming', feedbackIntegration: 'feedback', + moduleMetadataIntegration: 'modulemetadata', }; const BUNDLE_PATHS: Record> = { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts index 0245b641db5c..d3e175e0558b 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts @@ -12,9 +12,10 @@ test('should capture error with trpc context', async ({ page }) => { const trpcError = await errorEventPromise; expect(trpcError).toBeDefined(); - expect(trpcError.contexts.trpc).toBeDefined(); - expect(trpcError.contexts.trpc.procedure_type).toEqual('mutation'); - expect(trpcError.contexts.trpc.input).toEqual({ name: 'I love dogs' }); + expect(trpcError.contexts?.trpc).toBeDefined(); + expect(trpcError.contexts?.trpc?.procedure_type).toEqual('mutation'); + expect(trpcError.contexts?.trpc?.procedure_path).toBe('post.throwError'); + expect(trpcError.contexts?.trpc?.input).toEqual({ name: 'I love dogs' }); }); test('should create transaction with trpc input for error', async ({ page }) => { @@ -26,9 +27,5 @@ test('should create transaction with trpc input for error', async ({ page }) => await page.click('#error-button'); const trpcTransaction = await trpcTransactionPromise; - expect(trpcTransaction).toBeDefined(); - expect(trpcTransaction.contexts.trpc).toBeDefined(); - expect(trpcTransaction.contexts.trpc.procedure_type).toEqual('mutation'); - expect(trpcTransaction.contexts.trpc.input).toEqual({ name: 'I love dogs' }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts index 47d6a52f8a19..ee3ebfd099ff 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts @@ -13,7 +13,4 @@ test('should create transaction with trpc input for mutation', async ({ page }) const trpcTransaction = await trpcTransactionPromise; expect(trpcTransaction).toBeDefined(); - expect(trpcTransaction.contexts.trpc).toBeDefined(); - expect(trpcTransaction.contexts.trpc.procedure_type).toEqual('mutation'); - expect(trpcTransaction.contexts.trpc.input).toEqual({ name: 'I love dogs' }); }); diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts index 4f274fdc16ae..fcdd9b39a103 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts @@ -33,11 +33,6 @@ test('Should record span for trpc query', async ({ baseURL }) => { description: `trpc/getSomething`, }), ); - - expect(transaction.contexts?.trpc).toMatchObject({ - procedure_type: 'query', - input: 'foobar', - }); }); test('Should record transaction for trpc mutation', async ({ baseURL }) => { @@ -70,10 +65,6 @@ test('Should record transaction for trpc mutation', async ({ baseURL }) => { description: `trpc/createSomething`, }), ); - - expect(transaction.contexts?.trpc).toMatchObject({ - procedure_type: 'mutation', - }); }); test('Should record transaction and error for a crashing trpc handler', async ({ baseURL }) => { @@ -100,6 +91,9 @@ test('Should record transaction and error for a crashing trpc handler', async ({ await expect(transactionEventPromise).resolves.toBeDefined(); await expect(errorEventPromise).resolves.toBeDefined(); + + expect((await errorEventPromise).contexts?.trpc?.['procedure_type']).toBe('mutation'); + expect((await errorEventPromise).contexts?.trpc?.['procedure_path']).toBe('crashSomething'); }); test('Should record transaction and error for a trpc handler that returns a status code', async ({ baseURL }) => { diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts index 24ae70449ced..b34fc8123899 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts @@ -66,8 +66,12 @@ test('Waits for sse streaming when sse has been explicitly aborted', async ({ pa expect(resolveBodyDuration).toBe(0); // validate abort error was thrown by inspecting console - const consoleBreadcrumb = rootSpan.breadcrumbs?.find(breadcrumb => breadcrumb.category === 'console'); - expect(consoleBreadcrumb?.message).toBe('Could not fetch sse AbortError: BodyStreamBuffer was aborted'); + expect(rootSpan.breadcrumbs).toContainEqual( + expect.objectContaining({ + category: 'console', + message: 'Could not fetch sse AbortError: BodyStreamBuffer was aborted', + }), + ); }); test('Aborts when stream takes longer than 5s, by not updating the span duration', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json index 4684e3401e63..95b9c3bd78b4 100644 --- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json @@ -9,7 +9,7 @@ "@types/react-dom": "18.0.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "^6.4.1", + "react-router-dom": "6.4.1", "react-scripts": "5.0.1", "typescript": "4.9.5" }, diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 84d38e7aaf93..e37952cf2381 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -50,6 +50,7 @@ "graphql": "^16.3.0", "http-terminator": "^3.2.0", "ioredis": "^5.4.1", + "knex": "^2.5.1", "kafkajs": "2.2.4", "lru-memoizer": "2.3.0", "mongodb": "^3.7.3", @@ -65,6 +66,7 @@ "redis-4": "npm:redis@^4.6.14", "reflect-metadata": "0.2.1", "rxjs": "^7.8.1", + "tedious": "^18.6.1", "yargs": "^16.2.0" }, "devDependencies": { diff --git a/dev-packages/node-integration-tests/suites/anr/basic.js b/dev-packages/node-integration-tests/suites/anr/basic.js index b1dddf958d46..e2adf0e8c60f 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.js +++ b/dev-packages/node-integration-tests/suites/anr/basic.js @@ -3,6 +3,8 @@ const assert = require('assert'); const Sentry = require('@sentry/node'); +global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; + setTimeout(() => { process.exit(); }, 10000); diff --git a/dev-packages/node-integration-tests/suites/anr/basic.mjs b/dev-packages/node-integration-tests/suites/anr/basic.mjs index c3e74222f587..18777e5ecdbd 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.mjs +++ b/dev-packages/node-integration-tests/suites/anr/basic.mjs @@ -3,6 +3,8 @@ import * as crypto from 'crypto'; import * as Sentry from '@sentry/node'; +global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; + setTimeout(() => { process.exit(); }, 10000); diff --git a/dev-packages/node-integration-tests/suites/anr/test.ts b/dev-packages/node-integration-tests/suites/anr/test.ts index 78f89d7451c0..0352212a8293 100644 --- a/dev-packages/node-integration-tests/suites/anr/test.ts +++ b/dev-packages/node-integration-tests/suites/anr/test.ts @@ -1,3 +1,4 @@ +import type { Event } from '@sentry/types'; import { conditionalTest } from '../../utils'; import { cleanupChildProcesses, createRunner } from '../../utils/runner'; @@ -64,17 +65,33 @@ const ANR_EVENT_WITH_SCOPE = { ]), }; +const ANR_EVENT_WITH_DEBUG_META: Event = { + ...ANR_EVENT_WITH_SCOPE, + debug_meta: { + images: [ + { + type: 'sourcemap', + debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa', + code_file: expect.stringContaining('basic.'), + }, + ], + }, +}; + conditionalTest({ min: 16 })('should report ANR when event loop blocked', () => { afterAll(() => { cleanupChildProcesses(); }); test('CJS', done => { - createRunner(__dirname, 'basic.js').withMockSentryServer().expect({ event: ANR_EVENT_WITH_SCOPE }).start(done); + createRunner(__dirname, 'basic.js').withMockSentryServer().expect({ event: ANR_EVENT_WITH_DEBUG_META }).start(done); }); test('ESM', done => { - createRunner(__dirname, 'basic.mjs').withMockSentryServer().expect({ event: ANR_EVENT_WITH_SCOPE }).start(done); + createRunner(__dirname, 'basic.mjs') + .withMockSentryServer() + .expect({ event: ANR_EVENT_WITH_DEBUG_META }) + .start(done); }); test('blocked indefinitely', done => { diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/knex/docker-compose.yml new file mode 100644 index 000000000000..50eb7bc94237 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/knex/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.9' + +services: + db_postgres: + image: postgres:13 + restart: always + container_name: integration-tests-knex-postgres + ports: + - '5445:5432' + environment: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: tests + + db_mysql2: + image: mysql:8 + restart: always + container_name: integration-tests-knex-mysql2 + ports: + - '3307:3306' + environment: + MYSQL_ROOT_PASSWORD: docker + MYSQL_DATABASE: tests diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withMysql2.js b/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withMysql2.js new file mode 100644 index 000000000000..5d57e38d9318 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withMysql2.js @@ -0,0 +1,53 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + integrations: [Sentry.knexIntegration()], +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const knex = require('knex').default; + +const mysql2Client = knex({ + client: 'mysql2', + connection: { + host: 'localhost', + port: 3307, + user: 'root', + password: 'docker', + database: 'tests', + }, +}); + +async function run() { + await Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + async () => { + try { + await mysql2Client.schema.createTable('User', table => { + table.increments('id').notNullable().primary({ constraintName: 'User_pkey' }); + table.timestamp('createdAt', { precision: 3 }).notNullable().defaultTo(mysql2Client.fn.now(3)); + table.text('email').notNullable(); + table.text('name').notNullable(); + }); + + await mysql2Client('User').insert({ name: 'jane', email: 'jane@domain.com' }); + await mysql2Client('User').select('*'); + } finally { + await mysql2Client.destroy(); + } + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withPostgres.js b/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withPostgres.js new file mode 100644 index 000000000000..a9f2d558a618 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withPostgres.js @@ -0,0 +1,53 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + integrations: [Sentry.knexIntegration()], +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const knex = require('knex').default; + +const pgClient = knex({ + client: 'pg', + connection: { + host: 'localhost', + port: 5445, + user: 'test', + password: 'test', + database: 'tests', + }, +}); + +async function run() { + await Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + async () => { + try { + await pgClient.schema.createTable('User', table => { + table.increments('id').notNullable().primary({ constraintName: 'User_pkey' }); + table.timestamp('createdAt', { precision: 3 }).notNullable().defaultTo(pgClient.fn.now(3)); + table.text('email').notNullable(); + table.text('name').notNullable(); + }); + + await pgClient('User').insert({ name: 'bob', email: 'bob@domain.com' }); + await pgClient('User').select('*'); + } finally { + await pgClient.destroy(); + } + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/test.ts b/dev-packages/node-integration-tests/suites/tracing/knex/test.ts new file mode 100644 index 000000000000..3ededda4f162 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/knex/test.ts @@ -0,0 +1,129 @@ +import { createRunner } from '../../../utils/runner'; + +// When running docker compose, we need a larger timeout, as this takes some time... +jest.setTimeout(90000); + +describe('knex auto instrumentation', () => { + // Update this if another knex version is installed + const KNEX_VERSION = '2.5.1'; + + test('should auto-instrument `knex` package when using `pg` client', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.system': 'postgresql', + 'db.name': 'tests', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + 'net.peer.name': 'localhost', + 'net.peer.port': 5445, + }), + status: 'ok', + description: + 'create table "User" ("id" serial primary key, "createdAt" timestamptz(3) not null default CURRENT_TIMESTAMP(3), "email" text not null, "name" text not null)', + origin: 'auto.db.otel.knex', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.system': 'postgresql', + 'db.name': 'tests', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + 'net.peer.name': 'localhost', + 'net.peer.port': 5445, + }), + status: 'ok', + // In the knex-otel spans, the placeholders (e.g., `$1`) are replaced by a `?`. + description: 'insert into "User" ("email", "name") values (?, ?)', + origin: 'auto.db.otel.knex', + }), + + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.operation': 'select', + 'db.sql.table': 'User', + 'db.system': 'postgresql', + 'db.name': 'tests', + 'db.statement': 'select * from "User"', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + }), + status: 'ok', + description: 'select * from "User"', + origin: 'auto.db.otel.knex', + }), + ]), + }; + + createRunner(__dirname, 'scenario-withPostgres.js') + .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port 5432'] }) + .expect({ transaction: EXPECTED_TRANSACTION }) + .start(done); + }); + + test('should auto-instrument `knex` package when using `mysql2` client', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.system': 'mysql2', + 'db.name': 'tests', + 'db.user': 'root', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + 'net.peer.name': 'localhost', + 'net.peer.port': 3307, + }), + status: 'ok', + description: + 'create table `User` (`id` int unsigned not null auto_increment primary key, `createdAt` timestamp(3) not null default CURRENT_TIMESTAMP(3), `email` text not null, `name` text not null)', + origin: 'auto.db.otel.knex', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.system': 'mysql2', + 'db.name': 'tests', + 'db.user': 'root', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + 'net.peer.name': 'localhost', + 'net.peer.port': 3307, + }), + status: 'ok', + description: 'insert into `User` (`email`, `name`) values (?, ?)', + origin: 'auto.db.otel.knex', + }), + + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.operation': 'select', + 'db.sql.table': 'User', + 'db.system': 'mysql2', + 'db.name': 'tests', + 'db.statement': 'select * from `User`', + 'db.user': 'root', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + }), + status: 'ok', + description: 'select * from `User`', + origin: 'auto.db.otel.knex', + }), + ]), + }; + + createRunner(__dirname, 'scenario-withMysql2.js') + .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port: 3306'] }) + .expect({ transaction: EXPECTED_TRANSACTION }) + .start(done); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/tedious/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/tedious/docker-compose.yml new file mode 100644 index 000000000000..8e3604dca209 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/tedious/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.9' + +services: + db: + image: mcr.microsoft.com/mssql/server:2022-latest + restart: always + container_name: integration-tests-tedious + ports: + - '1433:1433' + environment: + ACCEPT_EULA: 'Y' + MSSQL_SA_PASSWORD: 'TESTing123' diff --git a/dev-packages/node-integration-tests/suites/tracing/tedious/scenario.js b/dev-packages/node-integration-tests/suites/tracing/tedious/scenario.js new file mode 100644 index 000000000000..1a375cfb78e9 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/tedious/scenario.js @@ -0,0 +1,65 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +const { Connection, Request } = require('tedious'); + +const config = { + server: '127.0.0.1', + authentication: { + type: 'default', + options: { + userName: 'sa', + password: 'TESTing123', + }, + }, + options: { + port: 1433, + encrypt: false, + }, +}; + +const connection = new Connection(config); + +function executeAllStatements(span) { + executeStatement('SELECT 1 + 1 AS solution', () => { + executeStatement('SELECT GETDATE()', () => { + span.end(); + connection.close(); + }); + }); +} + +function executeStatement(query, callback) { + const request = new Request(query, err => { + if (err) { + throw err; + } + callback(); + }); + + connection.execSql(request); +} + +connection.connect(err => { + if (err) { + throw err; + } + + Sentry.startSpanManual( + { + op: 'transaction', + name: 'Test Transaction', + }, + span => { + // span must be ended manually after all queries + executeAllStatements(span); + }, + ); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts b/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts new file mode 100644 index 000000000000..c4a0ae29fe38 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts @@ -0,0 +1,53 @@ +import { conditionalTest } from '../../../utils'; +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +jest.setTimeout(75000); + +// Tedious version we are testing against only supports Node 18+ +// https://github.com/tediousjs/tedious/blob/8310c455a2cc1cba83c1ca3c16677da4f83e12a9/package.json#L38 +conditionalTest({ min: 18 })('tedious auto instrumentation', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('should auto-instrument `tedious` package', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'SELECT GETDATE()', + data: expect.objectContaining({ + 'sentry.origin': 'auto.db.otel.tedious', + 'sentry.op': 'db', + 'db.name': 'master', + 'db.statement': 'SELECT GETDATE()', + 'db.system': 'mssql', + 'db.user': 'sa', + 'net.peer.name': '127.0.0.1', + 'net.peer.port': 1433, + }), + status: 'ok', + }), + expect.objectContaining({ + description: 'SELECT 1 + 1 AS solution', + data: expect.objectContaining({ + 'sentry.origin': 'auto.db.otel.tedious', + 'sentry.op': 'db', + 'db.name': 'master', + 'db.statement': 'SELECT 1 + 1 AS solution', + 'db.system': 'mssql', + 'db.user': 'sa', + 'net.peer.name': '127.0.0.1', + 'net.peer.port': 1433, + }), + status: 'ok', + }), + ]), + }; + + createRunner(__dirname, 'scenario.js') + .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['1433'] }) + .expect({ transaction: EXPECTED_TRANSACTION }) + .start(done); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 27d83ce7980b..e4c871ec74ea 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -69,6 +69,7 @@ export { isInitialized, kafkaIntegration, koaIntegration, + knexIntegration, lastEventId, linkedErrorsIntegration, localVariablesIntegration, @@ -125,6 +126,7 @@ export { startSession, startSpan, startSpanManual, + tediousIntegration, trpcMiddleware, withActiveSpan, withIsolationScope, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index faa43522f2fc..060dddd51787 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -92,6 +92,7 @@ export { fsIntegration, genericPoolIntegration, graphqlIntegration, + knexIntegration, kafkaIntegration, lruMemoizerIntegration, mongoIntegration, @@ -99,6 +100,7 @@ export { mysqlIntegration, mysql2Integration, redisIntegration, + tediousIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 670b1ce4ae25..09714e90c11f 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, startInactiveSpan } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan } from '@sentry/core'; import { setMeasurement } from '@sentry/core'; import type { Measurements, Span, SpanAttributes, StartSpanOptions } from '@sentry/types'; import { browserPerformanceTimeOrigin, getComponentName, htmlTreeAsString, logger, parseUrl } from '@sentry/utils'; @@ -143,7 +143,8 @@ export function startTrackingLongAnimationFrames(): void { // we directly observe `long-animation-frame` events instead of through the web-vitals // `observe` helper function. const observer = new PerformanceObserver(list => { - if (!getActiveSpan()) { + const parent = getActiveSpan(); + if (!parent) { return; } for (const entry of list.getEntries() as PerformanceLongAnimationFrameTiming[]) { @@ -152,6 +153,17 @@ export function startTrackingLongAnimationFrames(): void { } const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + + const { start_timestamp: parentStartTimestamp, op: parentOp } = spanToJSON(parent); + + if (parentOp === 'navigation' && parentStartTimestamp && startTime < parentStartTimestamp) { + // Skip adding the span if the long animation frame started before the navigation started. + // `startAndEndSpan` will otherwise adjust the parent's start time to the span's start + // time, potentially skewing the duration of the actual navigation as reported via our + // routing instrumentations + continue; + } + const duration = msToSec(entry.duration); const attributes: SpanAttributes = { @@ -172,15 +184,11 @@ export function startTrackingLongAnimationFrames(): void { attributes['browser.script.source_char_position'] = sourceCharPosition; } - const span = startInactiveSpan({ + startAndEndSpan(parent, startTime, startTime + duration, { name: 'Main UI thread blocked', op: 'ui.long-animation-frame', - startTime, attributes, }); - if (span) { - span.end(startTime + duration); - } } }); @@ -192,7 +200,8 @@ export function startTrackingLongAnimationFrames(): void { */ export function startTrackingInteractions(): void { addPerformanceInstrumentationHandler('event', ({ entries }) => { - if (!getActiveSpan()) { + const parent = getActiveSpan(); + if (!parent) { return; } for (const entry of entries) { @@ -214,10 +223,7 @@ export function startTrackingInteractions(): void { spanOptions.attributes['ui.component_name'] = componentName; } - const span = startInactiveSpan(spanOptions); - if (span) { - span.end(startTime + duration); - } + startAndEndSpan(parent, startTime, startTime + duration, spanOptions); } } }); diff --git a/packages/browser/rollup.bundle.config.mjs b/packages/browser/rollup.bundle.config.mjs index 6a3c6342842b..f65c27aad6e9 100644 --- a/packages/browser/rollup.bundle.config.mjs +++ b/packages/browser/rollup.bundle.config.mjs @@ -12,6 +12,7 @@ const reexportedPluggableIntegrationFiles = [ 'rewriteframes', 'sessiontiming', 'feedback', + 'modulemetadata', ]; browserPluggableIntegrationFiles.forEach(integrationName => { diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 10b974dc35e8..56724b926c5b 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -74,3 +74,4 @@ export { export type { Span } from '@sentry/types'; export { makeBrowserOfflineTransport } from './transports/offline'; export { browserProfilingIntegration } from './profiling/integration'; +export { spotlightBrowserIntegration } from './integrations/spotlight'; diff --git a/packages/browser/src/integrations-bundle/index.modulemetadata.ts b/packages/browser/src/integrations-bundle/index.modulemetadata.ts new file mode 100644 index 000000000000..c4f4a2b9cf75 --- /dev/null +++ b/packages/browser/src/integrations-bundle/index.modulemetadata.ts @@ -0,0 +1 @@ +export { moduleMetadataIntegration } from '@sentry/core'; diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 50272d9b5554..926ea6bbb9f5 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -1,21 +1,11 @@ /* eslint-disable max-lines */ import { DEFAULT_ENVIRONMENT, getClient, spanToJSON } from '@sentry/core'; -import type { - DebugImage, - Envelope, - Event, - EventEnvelope, - Profile, - Span, - StackFrame, - StackParser, - ThreadCpuProfile, -} from '@sentry/types'; +import type { DebugImage, Envelope, Event, EventEnvelope, Profile, Span, ThreadCpuProfile } from '@sentry/types'; import { - GLOBAL_OBJ, browserPerformanceTimeOrigin, forEachEnvelopeItem, + getDebugImagesForResources, logger, timestampInSeconds, uuid4, @@ -352,17 +342,10 @@ export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[ return events; } -const debugIdStackParserCache = new WeakMap>(); /** * Applies debug meta data to an event from a list of paths to resources (sourcemaps) */ export function applyDebugMetadata(resource_paths: ReadonlyArray): DebugImage[] { - const debugIdMap = GLOBAL_OBJ._sentryDebugIds; - - if (!debugIdMap) { - return []; - } - const client = getClient(); const options = client && client.getOptions(); const stackParser = options && options.stackParser; @@ -371,51 +354,7 @@ export function applyDebugMetadata(resource_paths: ReadonlyArray): Debug return []; } - let debugIdStackFramesCache: Map; - const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser); - if (cachedDebugIdStackFrameCache) { - debugIdStackFramesCache = cachedDebugIdStackFrameCache; - } else { - debugIdStackFramesCache = new Map(); - debugIdStackParserCache.set(stackParser, debugIdStackFramesCache); - } - - // Build a map of filename -> debug_id - const filenameDebugIdMap = Object.keys(debugIdMap).reduce>((acc, debugIdStackTrace) => { - let parsedStack: StackFrame[]; - - const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); - if (cachedParsedStack) { - parsedStack = cachedParsedStack; - } else { - parsedStack = stackParser(debugIdStackTrace); - debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); - } - - for (let i = parsedStack.length - 1; i >= 0; i--) { - const stackFrame = parsedStack[i]; - const file = stackFrame && stackFrame.filename; - - if (stackFrame && file) { - acc[file] = debugIdMap[debugIdStackTrace] as string; - break; - } - } - return acc; - }, {}); - - const images: DebugImage[] = []; - for (const path of resource_paths) { - if (path && filenameDebugIdMap[path]) { - images.push({ - type: 'sourcemap', - code_file: path, - debug_id: filenameDebugIdMap[path] as string, - }); - } - } - - return images; + return getDebugImagesForResources(stackParser, resource_paths); } /** diff --git a/packages/browser/src/utils/lazyLoadIntegration.ts b/packages/browser/src/utils/lazyLoadIntegration.ts index 82260ae9724f..5b78f646c577 100644 --- a/packages/browser/src/utils/lazyLoadIntegration.ts +++ b/packages/browser/src/utils/lazyLoadIntegration.ts @@ -21,6 +21,7 @@ const LazyLoadableIntegrations = { rewriteFramesIntegration: 'rewriteframes', sessionTimingIntegration: 'sessiontiming', browserProfilingIntegration: 'browserprofiling', + moduleMetadataIntegration: 'modulemetadata', } as const; const WindowWithMaybeIntegration = WINDOW as { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 4d8e9fbb1310..5688d1007769 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -113,6 +113,7 @@ export { setupConnectErrorHandler, genericPoolIntegration, graphqlIntegration, + knexIntegration, kafkaIntegration, lruMemoizerIntegration, mongoIntegration, @@ -120,6 +121,7 @@ export { mysqlIntegration, mysql2Integration, redisIntegration, + tediousIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/core/src/trpc.ts b/packages/core/src/trpc.ts index 366a0ba9aa62..fbcdf5832b46 100644 --- a/packages/core/src/trpc.ts +++ b/packages/core/src/trpc.ts @@ -1,7 +1,7 @@ import { normalize } from '@sentry/utils'; -import { getClient } from './currentScopes'; -import { captureException, setContext } from './exports'; +import { getClient, withScope } from './currentScopes'; +import { captureException } from './exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from './semanticAttributes'; import { startSpanManual } from './tracing'; @@ -48,6 +48,7 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { const clientOptions = client && client.getOptions(); const trpcContext: Record = { + procedure_path: path, procedure_type: type, }; @@ -66,29 +67,31 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { } } } - setContext('trpc', trpcContext); - return startSpanManual( - { - name: `trpc/${path}`, - op: 'rpc.server', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.rpc.trpc', + return withScope(scope => { + scope.setContext('trpc', trpcContext); + return startSpanManual( + { + name: `trpc/${path}`, + op: 'rpc.server', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.rpc.trpc', + }, }, - }, - async span => { - try { - const nextResult = await next(); - captureIfError(nextResult); - span.end(); - return nextResult; - } catch (e) { - captureException(e, trpcCaptureContext); - span.end(); - throw e; - } - }, - ) as SentryTrpcMiddleware; + async span => { + try { + const nextResult = await next(); + captureIfError(nextResult); + span.end(); + return nextResult; + } catch (e) { + captureException(e, trpcCaptureContext); + span.end(); + throw e; + } + }, + ) as SentryTrpcMiddleware; + }); }; } diff --git a/packages/core/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index f7f209a49089..9ee0a0f1b1b6 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -6,10 +6,16 @@ import type { EventHint, Scope as ScopeInterface, ScopeContext, - StackFrame, StackParser, } from '@sentry/types'; -import { GLOBAL_OBJ, addExceptionMechanism, dateTimestampInSeconds, normalize, truncate, uuid4 } from '@sentry/utils'; +import { + addExceptionMechanism, + dateTimestampInSeconds, + getFilenameToDebugIdMap, + normalize, + truncate, + uuid4, +} from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from '../constants'; import { getGlobalScope } from '../currentScopes'; @@ -161,51 +167,12 @@ function applyClientOptions(event: Event, options: ClientOptions): void { } } -const debugIdStackParserCache = new WeakMap>(); - /** * Puts debug IDs into the stack frames of an error event. */ export function applyDebugIds(event: Event, stackParser: StackParser): void { - const debugIdMap = GLOBAL_OBJ._sentryDebugIds; - - if (!debugIdMap) { - return; - } - - let debugIdStackFramesCache: Map; - const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser); - if (cachedDebugIdStackFrameCache) { - debugIdStackFramesCache = cachedDebugIdStackFrameCache; - } else { - debugIdStackFramesCache = new Map(); - debugIdStackParserCache.set(stackParser, debugIdStackFramesCache); - } - // Build a map of filename -> debug_id - const filenameDebugIdMap = Object.entries(debugIdMap).reduce>( - (acc, [debugIdStackTrace, debugIdValue]) => { - let parsedStack: StackFrame[]; - const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); - if (cachedParsedStack) { - parsedStack = cachedParsedStack; - } else { - parsedStack = stackParser(debugIdStackTrace); - debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); - } - - for (let i = parsedStack.length - 1; i >= 0; i--) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const stackFrame = parsedStack[i]!; - if (stackFrame.filename) { - acc[stackFrame.filename] = debugIdValue; - break; - } - } - return acc; - }, - {}, - ); + const filenameDebugIdMap = getFilenameToDebugIdMap(stackParser); try { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 6bb4bbcd30a7..5e1c2bba5bc1 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -92,6 +92,7 @@ export { fastifyIntegration, genericPoolIntegration, graphqlIntegration, + knexIntegration, kafkaIntegration, lruMemoizerIntegration, mongoIntegration, @@ -99,6 +100,7 @@ export { mysqlIntegration, mysql2Integration, redisIntegration, + tediousIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index c50bbce37305..a1e6aba11308 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -16,7 +16,7 @@ export * from '@sentry/react'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesAssetPrefixPath__: string; + _sentryRewriteFramesAssetPrefixPath: string; }; // Treeshakable guard to remove all code related to tracing @@ -64,7 +64,10 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const assetPrefixPath = globalWithInjectedValues.__rewriteFramesAssetPrefixPath__ || ''; + const assetPrefixPath = + process.env._sentryRewriteFramesAssetPrefixPath || + globalWithInjectedValues._sentryRewriteFramesAssetPrefixPath || + ''; customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefixPath })); return customDefaultIntegrations; diff --git a/packages/nextjs/src/client/tunnelRoute.ts b/packages/nextjs/src/client/tunnelRoute.ts index 3c93b93e41f2..59ce4cfe82b7 100644 --- a/packages/nextjs/src/client/tunnelRoute.ts +++ b/packages/nextjs/src/client/tunnelRoute.ts @@ -4,14 +4,14 @@ import { GLOBAL_OBJ, dsnFromString, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../common/debug-build'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryRewritesTunnelPath__?: string; + _sentryRewritesTunnelPath?: string; }; /** * Applies the `tunnel` option to the Next.js SDK options based on `withSentryConfig`'s `tunnelRoute` option. */ export function applyTunnelRouteOption(options: BrowserOptions): void { - const tunnelRouteOption = globalWithInjectedValues.__sentryRewritesTunnelPath__; + const tunnelRouteOption = process.env._sentryRewritesTunnelPath || globalWithInjectedValues._sentryRewritesTunnelPath; if (tunnelRouteOption && options.dsn) { const dsnComponents = dsnFromString(options.dsn); if (!dsnComponents) { diff --git a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts index 6c37859a851d..143bf6fef6ef 100644 --- a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts +++ b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts @@ -11,7 +11,7 @@ type OriginalStackFrameResponse = { }; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryBasePath?: string; + _sentryBasePath?: string; }; async function resolveStackFrame( @@ -32,7 +32,7 @@ async function resolveStackFrame( params.append(key, (frame[key as keyof typeof frame] ?? '').toString()); }); - let basePath = globalWithInjectedValues.__sentryBasePath ?? ''; + let basePath = process.env._sentryBasePath ?? globalWithInjectedValues._sentryBasePath ?? ''; // Prefix the basepath with a slash if it doesn't have one if (basePath !== '' && !basePath.match(/^\//)) { diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index d887369606b6..14be35215cf2 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -47,6 +47,8 @@ export type NextConfigObject = { clientTraceMetadata?: string[]; }; productionBrowserSourceMaps?: boolean; + // https://nextjs.org/docs/pages/api-reference/next-config-js/env + env?: Record; }; export type SentryBuildOptions = { @@ -548,7 +550,7 @@ export type ModuleRuleUseProperty = { * Global with values we add when we inject code into people's pages, for use at runtime. */ export type EnhancedGlobal = typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; + _sentryRewriteFramesDistDir?: string; SENTRY_RELEASE?: { id: string }; SENTRY_RELEASES?: { [key: string]: { id: string } }; }; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 6b96b96ecec1..b83008a4817e 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -562,6 +562,8 @@ function setUpModuleRules(newConfig: WebpackConfigObject): WebpackConfigObjectWi /** * Adds loaders to inject values on the global object based on user configuration. */ +// TODO(v9): Remove this loader and replace it with a nextConfig.env (https://web.archive.org/web/20240917153554/https://nextjs.org/docs/app/api-reference/next-config-js/env) or define based (https://github.com/vercel/next.js/discussions/71476) approach. +// In order to remove this loader though we need to make sure the minimum supported Next.js version includes this PR (https://github.com/vercel/next.js/pull/61194), otherwise the nextConfig.env based approach will not work, as our SDK code is not processed by Next.js. function addValueInjectionLoader( newConfig: WebpackConfigObjectWithModuleRules, userNextConfig: NextConfigObject, @@ -572,7 +574,7 @@ function addValueInjectionLoader( const isomorphicValues = { // `rewritesTunnel` set by the user in Next.js config - __sentryRewritesTunnelPath__: + _sentryRewritesTunnelPath: userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' ? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}` : undefined, @@ -582,21 +584,21 @@ function addValueInjectionLoader( SENTRY_RELEASE: buildContext.dev ? undefined : { id: userSentryOptions.release?.name ?? getSentryRelease(buildContext.buildId) }, - __sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined, + _sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined, }; const serverValues = { ...isomorphicValues, // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape // characters) - __rewriteFramesDistDir__: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', + _sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', }; const clientValues = { ...isomorphicValues, // Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if // `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.) - __rewriteFramesAssetPrefixPath__: assetPrefix + _sentryRewriteFramesAssetPrefixPath: assetPrefix ? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '') : '', }; diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 4f5205fecfcb..539e75c20596 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -20,6 +20,7 @@ let showedExportModeTunnelWarning = false; * @param sentryBuildOptions Additional options to configure instrumentation and * @returns The modified config to be exported */ +// TODO(v9): Always return an async function here to allow us to do async things like grabbing a deterministic build ID. export function withSentryConfig(nextConfig?: C, sentryBuildOptions: SentryBuildOptions = {}): C { const castNextConfig = (nextConfig as NextConfig) || {}; if (typeof castNextConfig === 'function') { @@ -73,6 +74,8 @@ function getFinalConfigObject( } } + setUpBuildTimeVariables(incomingUserNextConfigObject, userSentryOptions); + const nextJsVersion = getNextjsVersion(); // Add the `clientTraceMetadata` experimental option based on Next.js version. The option got introduced in Next.js version 15.0.0 (actually 14.3.0-canary.64). @@ -253,6 +256,43 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s }; } +// TODO(v9): Inject the release into all the bundles. This is breaking because grabbing the build ID if the user provides +// it in `generateBuildId` (https://nextjs.org/docs/app/api-reference/next-config-js/generateBuildId) is async but we do +// not turn the next config function in the type it was passed. +function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void { + const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; + const basePath = userNextConfig.basePath ?? ''; + const rewritesTunnelPath = + userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' + ? `${basePath}${userSentryOptions.tunnelRoute}` + : undefined; + + const buildTimeVariables: Record = { + // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape + // characters) + _sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', + // Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if + // `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.) + _sentryRewriteFramesAssetPrefixPath: assetPrefix + ? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '') + : '', + }; + + if (rewritesTunnelPath) { + buildTimeVariables._sentryRewritesTunnelPath = rewritesTunnelPath; + } + + if (basePath) { + buildTimeVariables._sentryBasePath = basePath; + } + + if (typeof userNextConfig.env === 'object') { + userNextConfig.env = { ...buildTimeVariables, ...userNextConfig.env }; + } else if (userNextConfig.env === undefined) { + userNextConfig.env = buildTimeVariables; + } +} + function getNextjsVersion(): string | undefined { const nextjsPackageJsonPath = resolveNextjsPackageJson(); if (nextjsPackageJsonPath) { diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index fff4236bf3be..5bfc8cca054b 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -21,7 +21,7 @@ export { captureUnderscoreErrorException } from '../common/pages-router-instrume export type EdgeOptions = VercelEdgeOptions; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; + _sentryRewriteFramesDistDir?: string; }; /** Inits the Sentry NextJS SDK on the Edge Runtime. */ @@ -36,7 +36,7 @@ export function init(options: VercelEdgeOptions = {}): void { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); diff --git a/packages/nextjs/src/edge/rewriteFramesIntegration.ts b/packages/nextjs/src/edge/rewriteFramesIntegration.ts index 84d5a41b0922..15a541311ed1 100644 --- a/packages/nextjs/src/edge/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/edge/rewriteFramesIntegration.ts @@ -3,7 +3,7 @@ import type { IntegrationFn, StackFrame } from '@sentry/types'; import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; + _sentryRewriteFramesDistDir?: string; }; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -14,9 +14,8 @@ interface RewriteFramesOptions { } export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { - // This value is injected at build time, based on the output directory specified in the build config. Though a default - // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + // This value is injected at build time, based on the output directory specified in the build config. + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 7aade8cbd5c3..bdff0de922b2 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -42,8 +42,8 @@ export * from '@sentry/node'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; - __sentryRewritesTunnelPath__?: string; + _sentryRewriteFramesDistDir?: string; + _sentryRewritesTunnelPath?: string; }; /** @@ -109,7 +109,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); } @@ -212,8 +212,10 @@ export function init(options: NodeOptions): NodeClient | undefined { // Filter out transactions for requests to the tunnel route if ( - globalWithInjectedValues.__sentryRewritesTunnelPath__ && - event.transaction === `POST ${globalWithInjectedValues.__sentryRewritesTunnelPath__}` + (globalWithInjectedValues._sentryRewritesTunnelPath && + event.transaction === `POST ${globalWithInjectedValues._sentryRewritesTunnelPath}`) || + (process.env._sentryRewritesTunnelPath && + event.transaction === `POST ${process.env._sentryRewritesTunnelPath}`) ) { return null; } diff --git a/packages/nextjs/src/server/rewriteFramesIntegration.ts b/packages/nextjs/src/server/rewriteFramesIntegration.ts index 6438ccb0d922..33bc7d90cb99 100644 --- a/packages/nextjs/src/server/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/server/rewriteFramesIntegration.ts @@ -4,7 +4,7 @@ import type { IntegrationFn, StackFrame } from '@sentry/types'; import { escapeStringForRegex } from '@sentry/utils'; const globalWithInjectedValues = global as typeof global & { - __rewriteFramesDistDir__?: string; + _sentryRewriteFramesDistDir?: string; }; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -17,7 +17,7 @@ interface RewriteFramesOptions { export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { // nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index 1129bcdbbf2b..fed88fe25d33 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -6,7 +6,7 @@ import { GLOBAL_OBJ } from '@sentry/utils'; import { init } from '../src/server'; // normally this is set as part of the build process, so mock it here -(GLOBAL_OBJ as typeof GLOBAL_OBJ & { __rewriteFramesDistDir__: string }).__rewriteFramesDistDir__ = '.next'; +(GLOBAL_OBJ as typeof GLOBAL_OBJ & { _sentryRewriteFramesDistDir: string })._sentryRewriteFramesDistDir = '.next'; const nodeInit = jest.spyOn(SentryNode, 'init'); diff --git a/packages/nextjs/test/utils/tunnelRoute.test.ts b/packages/nextjs/test/utils/tunnelRoute.test.ts index 576898c061b2..05aa992f39e6 100644 --- a/packages/nextjs/test/utils/tunnelRoute.test.ts +++ b/packages/nextjs/test/utils/tunnelRoute.test.ts @@ -3,16 +3,16 @@ import type { BrowserOptions } from '@sentry/react'; import { applyTunnelRouteOption } from '../../src/client/tunnelRoute'; const globalWithInjectedValues = global as typeof global & { - __sentryRewritesTunnelPath__?: string; + _sentryRewritesTunnelPath?: string; }; beforeEach(() => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = undefined; + globalWithInjectedValues._sentryRewritesTunnelPath = undefined; }); describe('applyTunnelRouteOption()', () => { it('Correctly applies `tunnelRoute` option when conditions are met', () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.sentry.io/3333333', } as BrowserOptions; @@ -23,7 +23,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't apply `tunnelRoute` when DSN is missing", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { // no dsn } as BrowserOptions; @@ -34,7 +34,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't apply `tunnelRoute` when DSN is invalid", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'invalidDsn', } as BrowserOptions; @@ -55,7 +55,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't `tunnelRoute` option when DSN is not a SaaS DSN", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@example.com/3333333', } as BrowserOptions; @@ -66,7 +66,7 @@ describe('applyTunnelRouteOption()', () => { }); it('Correctly applies `tunnelRoute` option to region DSNs', () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.us.sentry.io/3333333', } as BrowserOptions; diff --git a/packages/node/package.json b/packages/node/package.json index 456f0c210929..6e7a2a1660e1 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -9,9 +9,7 @@ "engines": { "node": ">=14.18" }, - "files": [ - "/build" - ], + "files": ["/build"], "main": "build/cjs/index.js", "module": "build/esm/index.js", "types": "build/types/index.d.ts", @@ -56,9 +54,7 @@ }, "typesVersions": { "<4.9": { - "build/types/index.d.ts": [ - "build/types-ts3.8/index.d.ts" - ] + "build/types/index.d.ts": ["build/types-ts3.8/index.d.ts"] } }, "publishConfig": { @@ -69,7 +65,7 @@ "@opentelemetry/context-async-hooks": "^1.25.1", "@opentelemetry/core": "^1.25.1", "@opentelemetry/instrumentation": "^0.54.0", - "@opentelemetry/instrumentation-amqplib": "^0.42.0", + "@opentelemetry/instrumentation-amqplib": "^0.43.0", "@opentelemetry/instrumentation-connect": "0.40.0", "@opentelemetry/instrumentation-dataloader": "0.12.0", "@opentelemetry/instrumentation-express": "0.44.0", @@ -81,6 +77,7 @@ "@opentelemetry/instrumentation-http": "0.53.0", "@opentelemetry/instrumentation-ioredis": "0.43.0", "@opentelemetry/instrumentation-kafkajs": "0.4.0", + "@opentelemetry/instrumentation-knex": "0.41.0", "@opentelemetry/instrumentation-koa": "0.43.0", "@opentelemetry/instrumentation-lru-memoizer": "0.40.0", "@opentelemetry/instrumentation-mongodb": "0.48.0", @@ -90,6 +87,7 @@ "@opentelemetry/instrumentation-nestjs-core": "0.40.0", "@opentelemetry/instrumentation-pg": "0.44.0", "@opentelemetry/instrumentation-redis-4": "0.42.0", + "@opentelemetry/instrumentation-tedious": "0.15.0", "@opentelemetry/instrumentation-undici": "0.6.0", "@opentelemetry/resources": "^1.26.0", "@opentelemetry/sdk-trace-base": "^1.26.0", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 4e1d1be22a8f..6ab536034894 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -28,6 +28,8 @@ export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/h export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa'; export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect'; export { spotlightIntegration } from './integrations/spotlight'; +export { knexIntegration } from './integrations/tracing/knex'; +export { tediousIntegration } from './integrations/tracing/tedious'; export { genericPoolIntegration } from './integrations/tracing/genericPool'; export { dataloaderIntegration } from './integrations/tracing/dataloader'; export { amqplibIntegration } from './integrations/tracing/amqplib'; diff --git a/packages/node/src/integrations/anr/index.ts b/packages/node/src/integrations/anr/index.ts index 92a4078aa766..c5f5b28e0888 100644 --- a/packages/node/src/integrations/anr/index.ts +++ b/packages/node/src/integrations/anr/index.ts @@ -1,7 +1,8 @@ +import * as diagnosticsChannel from 'node:diagnostics_channel'; import { Worker } from 'node:worker_threads'; import { defineIntegration, getCurrentScope, getGlobalScope, getIsolationScope, mergeScopeData } from '@sentry/core'; import type { Contexts, Event, EventHint, Integration, IntegrationFn, ScopeData } from '@sentry/types'; -import { GLOBAL_OBJ, logger } from '@sentry/utils'; +import { GLOBAL_OBJ, getFilenameToDebugIdMap, logger } from '@sentry/utils'; import { NODE_VERSION } from '../../nodeVersion'; import type { NodeClient } from '../../sdk/client'; import type { AnrIntegrationOptions, WorkerStartData } from './common'; @@ -100,6 +101,13 @@ type AnrReturn = (options?: Partial) => Integration & Anr export const anrIntegration = defineIntegration(_anrIntegration) as AnrReturn; +function onModuleLoad(callback: () => void): void { + // eslint-disable-next-line deprecation/deprecation + diagnosticsChannel.channel('module.require.end').subscribe(() => callback()); + // eslint-disable-next-line deprecation/deprecation + diagnosticsChannel.channel('module.import.asyncEnd').subscribe(() => callback()); +} + /** * Starts the ANR worker thread * @@ -153,6 +161,12 @@ async function _startWorker( } } + let debugImages: Record = getFilenameToDebugIdMap(initOptions.stackParser); + + onModuleLoad(() => { + debugImages = getFilenameToDebugIdMap(initOptions.stackParser); + }); + const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), { workerData: options, // We don't want any Node args to be passed to the worker @@ -171,7 +185,7 @@ async function _startWorker( // serialized without making it a SerializedSession const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined; // message the worker to tell it the main event loop is still running - worker.postMessage({ session }); + worker.postMessage({ session, debugImages }); } catch (_) { // } diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts index 67532435a39e..8e20fbeeb39a 100644 --- a/packages/node/src/integrations/anr/worker.ts +++ b/packages/node/src/integrations/anr/worker.ts @@ -8,7 +8,7 @@ import { makeSession, updateSession, } from '@sentry/core'; -import type { Event, ScopeData, Session, StackFrame } from '@sentry/types'; +import type { DebugImage, Event, ScopeData, Session, StackFrame } from '@sentry/types'; import { callFrameToStackFrame, normalizeUrlToBase, @@ -26,6 +26,7 @@ type VoidFunction = () => void; const options: WorkerStartData = workerData; let session: Session | undefined; let hasSentAnrEvent = false; +let mainDebugImages: Record = {}; function log(msg: string): void { if (options.debug) { @@ -87,6 +88,35 @@ function prepareStackFrames(stackFrames: StackFrame[] | undefined): StackFrame[] return strippedFrames; } +function applyDebugMeta(event: Event): void { + if (Object.keys(mainDebugImages).length === 0) { + return; + } + + const filenameToDebugId = new Map(); + + for (const exception of event.exception?.values || []) { + for (const frame of exception.stacktrace?.frames || []) { + const filename = frame.abs_path || frame.filename; + if (filename && mainDebugImages[filename]) { + filenameToDebugId.set(filename, mainDebugImages[filename] as string); + } + } + } + + if (filenameToDebugId.size > 0) { + const images: DebugImage[] = []; + for (const [filename, debugId] of filenameToDebugId.entries()) { + images.push({ + type: 'sourcemap', + code_file: filename, + debug_id: debugId, + }); + } + event.debug_meta = { images }; + } +} + function applyScopeToEvent(event: Event, scope: ScopeData): void { applyScopeDataToEvent(event, scope); @@ -140,6 +170,8 @@ async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise { +parentPort?.on('message', (msg: { session: Session | undefined; debugImages?: Record }) => { if (msg.session) { session = makeSession(msg.session); } + if (msg.debugImages) { + mainDebugImages = msg.debugImages; + } + poll(); }); diff --git a/packages/node/src/integrations/tracing/amqplib.ts b/packages/node/src/integrations/tracing/amqplib.ts index 4b44a145ae1f..d56dd365924c 100644 --- a/packages/node/src/integrations/tracing/amqplib.ts +++ b/packages/node/src/integrations/tracing/amqplib.ts @@ -27,4 +27,18 @@ const _amqplibIntegration = (() => { }; }) satisfies IntegrationFn; +/** + * Adds Sentry tracing instrumentation for the [amqplib](https://www.npmjs.com/package/amqplib) library. + * + * For more information, see the [`amqplibIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/amqplib/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.amqplibIntegration()], + * }); + * ``` + */ export const amqplibIntegration = defineIntegration(_amqplibIntegration); diff --git a/packages/node/src/integrations/tracing/connect.ts b/packages/node/src/integrations/tracing/connect.ts index 5ea6011c5257..b2938d5ee6f2 100644 --- a/packages/node/src/integrations/tracing/connect.ts +++ b/packages/node/src/integrations/tracing/connect.ts @@ -29,6 +29,22 @@ const _connectIntegration = (() => { }; }) satisfies IntegrationFn; +/** + * Adds Sentry tracing instrumentation for [Connect](https://github.com/senchalabs/connect/). + * + * If you also want to capture errors, you need to call `setupConnectErrorHandler(app)` after you initialize your connect app. + * + * For more information, see the [connect documentation](https://docs.sentry.io/platforms/javascript/guides/connect/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.connectIntegration()], + * }) + * ``` + */ export const connectIntegration = defineIntegration(_connectIntegration); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -37,6 +53,25 @@ function connectErrorMiddleware(err: any, req: any, res: any, next: any): void { next(err); } +/** + * Add a Connect middleware to capture errors to Sentry. + * + * @param app The Connect app to attach the error handler to + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const connect = require("connect"); + * + * const app = connect(); + * + * Sentry.setupConnectErrorHandler(app); + * + * // Add you connect routes here + * + * app.listen(3000); + * ``` + */ export const setupConnectErrorHandler = (app: ConnectApp): void => { app.use(connectErrorMiddleware); diff --git a/packages/node/src/integrations/tracing/dataloader.ts b/packages/node/src/integrations/tracing/dataloader.ts index d4567ea0dfbe..87a479cf0589 100644 --- a/packages/node/src/integrations/tracing/dataloader.ts +++ b/packages/node/src/integrations/tracing/dataloader.ts @@ -50,8 +50,17 @@ const _dataloaderIntegration = (() => { }) satisfies IntegrationFn; /** - * Dataloader integration + * Adds Sentry tracing instrumentation for the [dataloader](https://www.npmjs.com/package/dataloader) library. * - * Capture tracing data for Dataloader. + * For more information, see the [`dataloaderIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/dataloader/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.dataloaderIntegration()], + * }); + * ``` */ export const dataloaderIntegration = defineIntegration(_dataloaderIntegration); diff --git a/packages/node/src/integrations/tracing/express.ts b/packages/node/src/integrations/tracing/express.ts index b8c50e0eb621..0b4b6aa35c3e 100644 --- a/packages/node/src/integrations/tracing/express.ts +++ b/packages/node/src/integrations/tracing/express.ts @@ -60,10 +60,20 @@ const _expressIntegration = (() => { }) satisfies IntegrationFn; /** - * Express integration + * Adds Sentry tracing instrumentation for [Express](https://expressjs.com/). * - * Capture tracing data for express. - * In order to capture exceptions, you have to call `setupExpressErrorHandler(app)` before any other middleware and after all controllers. + * If you also want to capture errors, you need to call `setupExpressErrorHandler(app)` after you set up your Express server. + * + * For more information, see the [express documentation](https://docs.sentry.io/platforms/javascript/guides/express/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.expressIntegration()], + * }) + * ``` */ export const expressIntegration = defineIntegration(_expressIntegration); @@ -134,8 +144,28 @@ export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressMid } /** - * Setup an error handler for Express. + * Add an Express error handler to capture errors to Sentry. + * * The error handler must be before any other middleware and after all controllers. + * + * @param app The Express instances + * @param options {ExpressHandlerOptions} Configuration options for the handler + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const express = require("express"); + * + * const app = express(); + * + * // Add your routes, etc. + * + * // Add this after all routes, + * // but before any and other error-handling middlewares are defined + * Sentry.setupExpressErrorHandler(app); + * + * app.listen(3000); + * ``` */ export function setupExpressErrorHandler( app: { use: (middleware: ExpressMiddleware) => unknown }, diff --git a/packages/node/src/integrations/tracing/fastify.ts b/packages/node/src/integrations/tracing/fastify.ts index 27657d94d3d3..5a75e204b5f5 100644 --- a/packages/node/src/integrations/tracing/fastify.ts +++ b/packages/node/src/integrations/tracing/fastify.ts @@ -55,14 +55,41 @@ const _fastifyIntegration = (() => { }) satisfies IntegrationFn; /** - * Express integration + * Adds Sentry tracing instrumentation for [Fastify](https://fastify.dev/). * - * Capture tracing data for fastify. + * If you also want to capture errors, you need to call `setupFastifyErrorHandler(app)` after you set up your Fastify server. + * + * For more information, see the [fastify documentation](https://docs.sentry.io/platforms/javascript/guides/fastify/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.fastifyIntegration()], + * }) + * ``` */ export const fastifyIntegration = defineIntegration(_fastifyIntegration); /** - * Setup an error handler for Fastify. + * Add an Fastify error handler to capture errors to Sentry. + * + * @param fastify The Fastify instance to which to add the error handler + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const Fastify = require("fastify"); + * + * const app = Fastify(); + * + * Sentry.setupFastifyErrorHandler(app); + * + * // Add your routes, etc. + * + * app.listen({ port: 3000 }); + * ``` */ export function setupFastifyErrorHandler(fastify: Fastify): void { const plugin = Object.assign( diff --git a/packages/node/src/integrations/tracing/genericPool.ts b/packages/node/src/integrations/tracing/genericPool.ts index 8bc84554071c..ab3e5c25a207 100644 --- a/packages/node/src/integrations/tracing/genericPool.ts +++ b/packages/node/src/integrations/tracing/genericPool.ts @@ -33,8 +33,17 @@ const _genericPoolIntegration = (() => { }) satisfies IntegrationFn; /** - * GenericPool integration + * Adds Sentry tracing instrumentation for the [generic-pool](https://www.npmjs.com/package/generic-pool) library. * - * Capture tracing data for GenericPool. + * For more information, see the [`genericPoolIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/genericpool/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.genericPoolIntegration()], + * }); + * ``` */ export const genericPoolIntegration = defineIntegration(_genericPoolIntegration); diff --git a/packages/node/src/integrations/tracing/graphql.ts b/packages/node/src/integrations/tracing/graphql.ts index f4d817145cf2..c2f1402a0229 100644 --- a/packages/node/src/integrations/tracing/graphql.ts +++ b/packages/node/src/integrations/tracing/graphql.ts @@ -93,9 +93,19 @@ const _graphqlIntegration = ((options: GraphqlOptions = {}) => { }) satisfies IntegrationFn; /** - * GraphQL integration + * Adds Sentry tracing instrumentation for the [graphql](https://www.npmjs.com/package/graphql) library. * - * Capture tracing data for GraphQL. + * For more information, see the [`graphqlIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/graphql/). + * + * @param {GraphqlOptions} options Configuration options for the GraphQL integration. + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.graphqlIntegration()], + * }); */ export const graphqlIntegration = defineIntegration(_graphqlIntegration); diff --git a/packages/node/src/integrations/tracing/hapi/index.ts b/packages/node/src/integrations/tracing/hapi/index.ts index be452636bef8..9d1015704d60 100644 --- a/packages/node/src/integrations/tracing/hapi/index.ts +++ b/packages/node/src/integrations/tracing/hapi/index.ts @@ -31,10 +31,20 @@ const _hapiIntegration = (() => { }) satisfies IntegrationFn; /** - * Hapi integration + * Adds Sentry tracing instrumentation for [Hapi](https://hapi.dev/). * - * Capture tracing data for Hapi. * If you also want to capture errors, you need to call `setupHapiErrorHandler(server)` after you set up your server. + * + * For more information, see the [hapi documentation](https://docs.sentry.io/platforms/javascript/guides/hapi/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.hapiIntegration()], + * }) + * ``` */ export const hapiIntegration = defineIntegration(_hapiIntegration); @@ -81,6 +91,24 @@ export const hapiErrorPlugin = { /** * Add a Hapi plugin to capture errors to Sentry. + * + * @param server The Hapi server to attach the error handler to + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const Hapi = require('@hapi/hapi'); + * + * const init = async () => { + * const server = Hapi.server(); + * + * // all your routes here + * + * await Sentry.setupHapiErrorHandler(server); + * + * await server.start(); + * }; + * ``` */ export async function setupHapiErrorHandler(server: Server): Promise { await server.register(hapiErrorPlugin); diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 328767c403be..1a1b8835011d 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -18,6 +18,7 @@ import { instrumentMysql2, mysql2Integration } from './mysql2'; import { instrumentNest, nestIntegration } from './nest/nest'; import { instrumentPostgres, postgresIntegration } from './postgres'; import { instrumentRedis, redisIntegration } from './redis'; +import { instrumentTedious, tediousIntegration } from './tedious'; /** * With OTEL, all performance integrations will be added, as OTEL only initializes them when the patched package is actually required. @@ -41,6 +42,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { hapiIntegration(), koaIntegration(), connectIntegration(), + tediousIntegration(), genericPoolIntegration(), kafkaIntegration(), amqplibIntegration(), @@ -71,6 +73,7 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => instrumentHapi, instrumentGraphql, instrumentRedis, + instrumentTedious, instrumentGenericPool, instrumentAmqplib, ]; diff --git a/packages/node/src/integrations/tracing/kafka.ts b/packages/node/src/integrations/tracing/kafka.ts index 7bdab00459e1..68a9ccf8bab4 100644 --- a/packages/node/src/integrations/tracing/kafka.ts +++ b/packages/node/src/integrations/tracing/kafka.ts @@ -30,8 +30,16 @@ const _kafkaIntegration = (() => { }) satisfies IntegrationFn; /** - * KafkaJs integration + * Adds Sentry tracing instrumentation for the [kafkajs](https://www.npmjs.com/package/kafkajs) library. * - * Capture tracing data for KafkaJs. + * For more information, see the [`kafkaIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/kafka/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.kafkaIntegration()], + * }); */ export const kafkaIntegration = defineIntegration(_kafkaIntegration); diff --git a/packages/node/src/integrations/tracing/knex.ts b/packages/node/src/integrations/tracing/knex.ts new file mode 100644 index 000000000000..55457680e101 --- /dev/null +++ b/packages/node/src/integrations/tracing/knex.ts @@ -0,0 +1,47 @@ +import { KnexInstrumentation } from '@opentelemetry/instrumentation-knex'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import { generateInstrumentOnce } from '../../otel/instrument'; + +const INTEGRATION_NAME = 'Knex'; + +export const instrumentKnex = generateInstrumentOnce( + INTEGRATION_NAME, + () => new KnexInstrumentation({ requireParentSpan: true }), +); + +const _knexIntegration = (() => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentKnex(); + }, + + setup(client) { + client.on('spanStart', span => { + const { data } = spanToJSON(span); + // knex.version is always set in the span data + // https://github.com/open-telemetry/opentelemetry-js-contrib/blob/0309caeafc44ac9cb13a3345b790b01b76d0497d/plugins/node/opentelemetry-instrumentation-knex/src/instrumentation.ts#L138 + if (data && 'knex.version' in data) { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.knex'); + } + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Knex integration + * + * Capture tracing data for [Knex](https://knexjs.org/). + * + * @example + * ```javascript + * import * as Sentry from '@sentry/node'; + * + * Sentry.init({ + * integrations: [Sentry.knexIntegration()], + * }); + * ``` + */ +export const knexIntegration = defineIntegration(_knexIntegration); diff --git a/packages/node/src/integrations/tracing/koa.ts b/packages/node/src/integrations/tracing/koa.ts index 15ddc4658fa9..a244c6cb56d7 100644 --- a/packages/node/src/integrations/tracing/koa.ts +++ b/packages/node/src/integrations/tracing/koa.ts @@ -48,8 +48,46 @@ const _koaIntegration = (() => { }; }) satisfies IntegrationFn; +/** + * Adds Sentry tracing instrumentation for [Koa](https://koajs.com/). + * + * If you also want to capture errors, you need to call `setupKoaErrorHandler(app)` after you set up your Koa server. + * + * For more information, see the [koa documentation](https://docs.sentry.io/platforms/javascript/guides/koa/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.koaIntegration()], + * }) + * ``` + */ export const koaIntegration = defineIntegration(_koaIntegration); +/** + * Add an Koa error handler to capture errors to Sentry. + * + * The error handler must be before any other middleware and after all controllers. + * + * @param app The Express instances + * @param options {ExpressHandlerOptions} Configuration options for the handler + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const Koa = require("koa"); + * + * const app = new Koa(); + * + * Sentry.setupKoaErrorHandler(app); + * + * // Add your routes, etc. + * + * app.listen(3000); + * ``` + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export const setupKoaErrorHandler = (app: { use: (arg0: (ctx: any, next: any) => Promise) => void }): void => { app.use(async (ctx, next) => { diff --git a/packages/node/src/integrations/tracing/lrumemoizer.ts b/packages/node/src/integrations/tracing/lrumemoizer.ts index d94234c3e57d..6c8a1962338c 100644 --- a/packages/node/src/integrations/tracing/lrumemoizer.ts +++ b/packages/node/src/integrations/tracing/lrumemoizer.ts @@ -18,8 +18,16 @@ const _lruMemoizerIntegration = (() => { }) satisfies IntegrationFn; /** - * LruMemoizer integration + * Adds Sentry tracing instrumentation for the [lru-memoizer](https://www.npmjs.com/package/lru-memoizer) library. * - * Propagate traces through LruMemoizer. + * For more information, see the [`lruMemoizerIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/lrumemoizer/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.lruMemoizerIntegration()], + * }); */ export const lruMemoizerIntegration = defineIntegration(_lruMemoizerIntegration); diff --git a/packages/node/src/integrations/tracing/mongo.ts b/packages/node/src/integrations/tracing/mongo.ts index 143c7bf99a6d..5e42f5611db8 100644 --- a/packages/node/src/integrations/tracing/mongo.ts +++ b/packages/node/src/integrations/tracing/mongo.ts @@ -27,8 +27,17 @@ const _mongoIntegration = (() => { }) satisfies IntegrationFn; /** - * MongoDB integration + * Adds Sentry tracing instrumentation for the [mongodb](https://www.npmjs.com/package/mongodb) library. * - * Capture tracing data for MongoDB. + * For more information, see the [`mongoIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/mongo/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.mongoIntegration()], + * }); + * ``` */ export const mongoIntegration = defineIntegration(_mongoIntegration); diff --git a/packages/node/src/integrations/tracing/mongoose.ts b/packages/node/src/integrations/tracing/mongoose.ts index 4a4566fa98da..58234c1ec87f 100644 --- a/packages/node/src/integrations/tracing/mongoose.ts +++ b/packages/node/src/integrations/tracing/mongoose.ts @@ -27,8 +27,17 @@ const _mongooseIntegration = (() => { }) satisfies IntegrationFn; /** - * Mongoose integration + * Adds Sentry tracing instrumentation for the [mongoose](https://www.npmjs.com/package/mongoose) library. * - * Capture tracing data for Mongoose. + * For more information, see the [`mongooseIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/mongoose/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.mongooseIntegration()], + * }); + * ``` */ export const mongooseIntegration = defineIntegration(_mongooseIntegration); diff --git a/packages/node/src/integrations/tracing/mysql.ts b/packages/node/src/integrations/tracing/mysql.ts index 67b46ae9bdcf..d6509de0f765 100644 --- a/packages/node/src/integrations/tracing/mysql.ts +++ b/packages/node/src/integrations/tracing/mysql.ts @@ -17,8 +17,17 @@ const _mysqlIntegration = (() => { }) satisfies IntegrationFn; /** - * MySQL integration + * Adds Sentry tracing instrumentation for the [mysql](https://www.npmjs.com/package/mysql) library. * - * Capture tracing data for mysql. + * For more information, see the [`mysqlIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/mysql/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.mysqlIntegration()], + * }); + * ``` */ export const mysqlIntegration = defineIntegration(_mysqlIntegration); diff --git a/packages/node/src/integrations/tracing/mysql2.ts b/packages/node/src/integrations/tracing/mysql2.ts index b3c36435979c..c44fa2433528 100644 --- a/packages/node/src/integrations/tracing/mysql2.ts +++ b/packages/node/src/integrations/tracing/mysql2.ts @@ -27,8 +27,17 @@ const _mysql2Integration = (() => { }) satisfies IntegrationFn; /** - * MySQL2 integration + * Adds Sentry tracing instrumentation for the [mysql2](https://www.npmjs.com/package/mysql2) library. * - * Capture tracing data for mysql2 + * For more information, see the [`mysql2Integration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/mysql2/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.mysqlIntegration()], + * }); + * ``` */ export const mysql2Integration = defineIntegration(_mysql2Integration); diff --git a/packages/node/src/integrations/tracing/postgres.ts b/packages/node/src/integrations/tracing/postgres.ts index 05b56d9152ff..6df045156d41 100644 --- a/packages/node/src/integrations/tracing/postgres.ts +++ b/packages/node/src/integrations/tracing/postgres.ts @@ -28,8 +28,17 @@ const _postgresIntegration = (() => { }) satisfies IntegrationFn; /** - * Postgres integration + * Adds Sentry tracing instrumentation for the [pg](https://www.npmjs.com/package/pg) library. * - * Capture tracing data for pg. + * For more information, see the [`postgresIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/postgres/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.postgresIntegration()], + * }); + * ``` */ export const postgresIntegration = defineIntegration(_postgresIntegration); diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts index c9e7acafb09d..56768bf1ba71 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -38,12 +38,30 @@ const _prismaIntegration = (() => { }) satisfies IntegrationFn; /** - * Prisma integration + * Adds Sentry tracing instrumentation for the [prisma](https://www.npmjs.com/package/prisma) library. * - * Capture tracing data for prisma. - * Note: This requires to set: - * previewFeatures = ["tracing"] - * For the prisma client. - * See https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing for more details. + * For more information, see the [`prismaIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/). + * + * @example + * + * Make sure `previewFeatures = ["tracing"]` is set in the prisma client generator block. See the + * [prisma docs](https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing) for more details. + * + * ```prisma + * generator client { + * provider = "prisma-client-js" + * previewFeatures = ["tracing"] + * } + * ``` + * + * Then you can use the integration like this: + * + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.prismaIntegration()], + * }); + * ``` */ export const prismaIntegration = defineIntegration(_prismaIntegration); diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts index 4204e4a2abe5..cf30d8f953f3 100644 --- a/packages/node/src/integrations/tracing/redis.ts +++ b/packages/node/src/integrations/tracing/redis.ts @@ -110,8 +110,18 @@ const _redisIntegration = ((options: RedisOptions = {}) => { }) satisfies IntegrationFn; /** - * Redis integration for "ioredis" + * Adds Sentry tracing instrumentation for the [redis](https://www.npmjs.com/package/redis) and + * [ioredis](https://www.npmjs.com/package/ioredis) libraries. * - * Capture tracing data for redis and ioredis. + * For more information, see the [`redisIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/redis/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.redisIntegration()], + * }); + * ``` */ export const redisIntegration = defineIntegration(_redisIntegration); diff --git a/packages/node/src/integrations/tracing/tedious.ts b/packages/node/src/integrations/tracing/tedious.ts new file mode 100644 index 000000000000..6ad1cf56f28e --- /dev/null +++ b/packages/node/src/integrations/tracing/tedious.ts @@ -0,0 +1,57 @@ +import { TediousInstrumentation } from '@opentelemetry/instrumentation-tedious'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import { generateInstrumentOnce } from '../../otel/instrument'; + +const TEDIUS_INSTRUMENTED_METHODS = new Set([ + 'callProcedure', + 'execSql', + 'execSqlBatch', + 'execBulkLoad', + 'prepare', + 'execute', +]); + +const INTEGRATION_NAME = 'Tedious'; + +export const instrumentTedious = generateInstrumentOnce(INTEGRATION_NAME, () => new TediousInstrumentation({})); + +const _tediousIntegration = (() => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentTedious(); + }, + + setup(client) { + client.on('spanStart', span => { + const { description, data } = spanToJSON(span); + // Tedius integration always set a span name and `db.system` attribute to `mssql`. + if (!description || data?.['db.system'] !== 'mssql') { + return; + } + + const operation = description?.split(' ')[0] || ''; + if (TEDIUS_INSTRUMENTED_METHODS.has(operation)) { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.tedious'); + } + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Adds Sentry tracing instrumentation for the [tedious](https://www.npmjs.com/package/tedious) library. + * + * For more information, see the [`tediousIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/tedious/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.tediousIntegration()], + * }); + * ``` + */ +export const tediousIntegration = defineIntegration(_tediousIntegration); diff --git a/packages/nuxt/src/vite/addServerConfig.ts b/packages/nuxt/src/vite/addServerConfig.ts index cf4b2a95473e..824b46781e0d 100644 --- a/packages/nuxt/src/vite/addServerConfig.ts +++ b/packages/nuxt/src/vite/addServerConfig.ts @@ -118,6 +118,12 @@ function wrapEntryWithDynamicImport({ entrypointWrappedFunctions, debug, }: { resolvedSentryConfigPath: string; entrypointWrappedFunctions: string[]; debug?: boolean }): InputPluginOption { + // In order to correctly import the server config file + // and dynamically import the nitro runtime, we need to + // mark the resolutionId with '\0raw' to fall into the + // raw chunk group, c.f. https://github.com/nitrojs/nitro/commit/8b4a408231bdc222569a32ce109796a41eac4aa6#diff-e58102d2230f95ddeef2662957b48d847a6e891e354cfd0ae6e2e03ce848d1a2R142 + const resolutionIdPrefix = '\0raw'; + return { name: 'sentry-wrap-entry-with-dynamic-import', async resolveId(source, importer, options) { @@ -146,19 +152,19 @@ function wrapEntryWithDynamicImport({ // The enclosing `if` already checks for the suffix in `source`, but a check in `resolution.id` is needed as well to prevent multiple attachment of the suffix return resolution.id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`) ? resolution.id - : resolution.id + : `${resolutionIdPrefix}${resolution.id // Concatenates the query params to mark the file (also attaches names of re-exports - this is needed for serverless functions to re-export the handler) .concat(SENTRY_WRAPPED_ENTRY) .concat( constructWrappedFunctionExportQuery(moduleInfo.exportedBindings, entrypointWrappedFunctions, debug), ) - .concat(QUERY_END_INDICATOR); + .concat(QUERY_END_INDICATOR)}`; } return null; }, load(id: string) { if (id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { - const entryId = removeSentryQueryFromPath(id); + const entryId = removeSentryQueryFromPath(id).slice(resolutionIdPrefix.length); // Mostly useful for serverless `handler` functions const reExportedFunctions = diff --git a/packages/profiling-node/src/utils.ts b/packages/profiling-node/src/utils.ts index 1ec575aaaf3f..2693467d1f47 100644 --- a/packages/profiling-node/src/utils.ts +++ b/packages/profiling-node/src/utils.ts @@ -14,11 +14,16 @@ import type { ProfileChunkEnvelope, ProfileChunkItem, SdkInfo, - StackFrame, - StackParser, ThreadCpuProfile, } from '@sentry/types'; -import { GLOBAL_OBJ, createEnvelope, dsnToString, forEachEnvelopeItem, logger, uuid4 } from '@sentry/utils'; +import { + createEnvelope, + dsnToString, + forEachEnvelopeItem, + getDebugImagesForResources, + logger, + uuid4, +} from '@sentry/utils'; import { env, versions } from 'process'; import { isMainThread, threadId } from 'worker_threads'; @@ -415,69 +420,17 @@ export function makeProfileChunkEnvelope( ]); } -const debugIdStackParserCache = new WeakMap>(); - /** * Cross reference profile collected resources with debug_ids and return a list of debug images. * @param {string[]} resource_paths * @returns {DebugImage[]} */ export function applyDebugMetadata(client: Client, resource_paths: ReadonlyArray): DebugImage[] { - const debugIdMap = GLOBAL_OBJ._sentryDebugIds; - if (!debugIdMap) { - return []; - } - const options = client.getOptions(); if (!options || !options.stackParser) { return []; } - let debugIdStackFramesCache: Map; - const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(options.stackParser); - if (cachedDebugIdStackFrameCache) { - debugIdStackFramesCache = cachedDebugIdStackFrameCache; - } else { - debugIdStackFramesCache = new Map(); - debugIdStackParserCache.set(options.stackParser, debugIdStackFramesCache); - } - - // Build a map of filename -> debug_id. - const filenameDebugIdMap = Object.keys(debugIdMap).reduce>((acc, debugIdStackTrace) => { - let parsedStack: StackFrame[]; - - const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); - if (cachedParsedStack) { - parsedStack = cachedParsedStack; - } else { - parsedStack = options.stackParser(debugIdStackTrace); - debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); - } - - for (let i = parsedStack.length - 1; i >= 0; i--) { - const stackFrame = parsedStack[i]; - const file = stackFrame && stackFrame.filename; - - if (stackFrame && file) { - acc[file] = debugIdMap[debugIdStackTrace] as string; - break; - } - } - return acc; - }, {}); - - const images: DebugImage[] = []; - - for (const resource of resource_paths) { - if (resource && filenameDebugIdMap[resource]) { - images.push({ - type: 'sourcemap', - code_file: resource, - debug_id: filenameDebugIdMap[resource] as string, - }); - } - } - - return images; + return getDebugImagesForResources(options.stackParser, resource_paths); } diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index 1ec1ae4b7d35..2576ba664f40 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ // Inspired from Donnie McNeal's solution: // https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536 @@ -141,6 +142,29 @@ function stripBasenameFromPathname(pathname: string, basename: string): string { return pathname.slice(startIndex) || '/'; } +function sendIndexPath(pathBuilder: string, pathname: string, basename: string): [string, TransactionSource] { + const reconstructedPath = pathBuilder || _stripBasename ? stripBasenameFromPathname(pathname, basename) : pathname; + + const formattedPath = + // If the path ends with a slash, remove it + reconstructedPath[reconstructedPath.length - 1] === '/' + ? reconstructedPath.slice(0, -1) + : // If the path ends with a wildcard, remove it + reconstructedPath.slice(-2) === '/*' + ? reconstructedPath.slice(0, -1) + : reconstructedPath; + + return [formattedPath, 'route']; +} + +function pathEndsWithWildcard(path: string, branch: RouteMatch): boolean { + return (path.slice(-2) === '/*' && branch.route.children && branch.route.children.length > 0) || false; +} + +function pathIsWildcardAndHasChildren(path: string, branch: RouteMatch): boolean { + return (path === '*' && branch.route.children && branch.route.children.length > 0) || false; +} + function getNormalizedName( routes: RouteObject[], location: Location, @@ -158,14 +182,16 @@ function getNormalizedName( if (route) { // Early return if index route if (route.index) { - return [_stripBasename ? stripBasenameFromPathname(branch.pathname, basename) : branch.pathname, 'route']; + return sendIndexPath(pathBuilder, branch.pathname, basename); } - const path = route.path; - if (path) { + + // If path is not a wildcard and has no child routes, append the path + if (path && !pathIsWildcardAndHasChildren(path, branch)) { const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`; pathBuilder += newPath; + // If the path matches the current location, return the path if (basename + branch.pathname === location.pathname) { if ( // If the route defined on the element is something like @@ -177,6 +203,12 @@ function getNormalizedName( ) { return [(_stripBasename ? '' : basename) + newPath, 'route']; } + + // if the last character of the pathbuilder is a wildcard and there are children, remove the wildcard + if (pathEndsWithWildcard(pathBuilder, branch)) { + pathBuilder = pathBuilder.slice(0, -1); + } + return [(_stripBasename ? '' : basename) + pathBuilder, 'route']; } } diff --git a/packages/react/test/reactrouterv6.test.tsx b/packages/react/test/reactrouterv6.test.tsx index fbb135a5449c..de65159c56e4 100644 --- a/packages/react/test/reactrouterv6.test.tsx +++ b/packages/react/test/reactrouterv6.test.tsx @@ -391,6 +391,106 @@ describe('reactRouterV6BrowserTracingIntegration', () => { }); }); + it('works with wildcard routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + }> + } /> + Account Page} /> + + }> + Project Page}> + Project Page Root} /> + Editor}> + View Canvas} /> + Space Canvas} /> + + + + + No Match Page} /> + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/projects/:projectId/views/:viewId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with nested wildcard routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + }> + } /> + Account Page} /> + + }> + Project Page}> + Project Page Root} /> + Editor}> + }> + View Canvas} /> + Space Canvas} /> + + + + + + No Match Page} /> + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/projects/:projectId/views/:viewId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + it("updates the scope's `transactionName` on a navigation", () => { const client = createMockBrowserClient(); setCurrentClient(client); @@ -849,6 +949,84 @@ describe('reactRouterV6BrowserTracingIntegration', () => { }); }); + it('works with wildcard routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + index: true, + element: , + }, + { + path: '*', + element: <>, + children: [ + { + path: 'profile', + element: <>, + }, + { + path: 'param-page', + element: , + children: [ + { + path: '*', + element: , + children: [ + { + path: ':id', + element: <>, + children: [ + { + element: <>, + path: 'details', + children: [ + { + element: <>, + path: ':superId', + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/param-page/:id/details/:superId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + it('does not add double slashes to URLS', () => { const client = createMockBrowserClient(); setCurrentClient(client); diff --git a/packages/remix/package.json b/packages/remix/package.json index 61cb4be25825..66ffa6bb9507 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -53,7 +53,7 @@ }, "dependencies": { "@remix-run/router": "1.x", - "@sentry/cli": "^2.37.0", + "@sentry/cli": "^2.38.2", "@sentry/core": "8.37.1", "@sentry/node": "8.37.1", "@sentry/opentelemetry": "8.37.1", diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index ffb5cf74983f..1291a3fb8767 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -68,6 +68,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + knexIntegration, kafkaIntegration, koaIntegration, lastEventId, @@ -124,6 +125,7 @@ export { startSession, startSpan, startSpanManual, + tediousIntegration, trpcMiddleware, withActiveSpan, withIsolationScope, diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index ccec95b916d3..54e0e8cf68ab 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -59,6 +59,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + knexIntegration, kafkaIntegration, koaIntegration, lastEventId, @@ -115,6 +116,7 @@ export { startSession, startSpan, startSpanManual, + tediousIntegration, trpcMiddleware, withActiveSpan, withIsolationScope, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 728616945286..05f105c252a8 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -61,6 +61,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + knexIntegration, kafkaIntegration, koaIntegration, lastEventId, @@ -117,6 +118,7 @@ export { startSession, startSpan, startSpanManual, + tediousIntegration, trpcMiddleware, withActiveSpan, withIsolationScope, diff --git a/packages/utils/src/debug-ids.ts b/packages/utils/src/debug-ids.ts new file mode 100644 index 000000000000..4802b9356965 --- /dev/null +++ b/packages/utils/src/debug-ids.ts @@ -0,0 +1,70 @@ +import type { DebugImage, StackFrame, StackParser } from '@sentry/types'; +import { GLOBAL_OBJ } from './worldwide'; + +const debugIdStackParserCache = new WeakMap>(); + +/** + * Returns a map of filenames to debug identifiers. + */ +export function getFilenameToDebugIdMap(stackParser: StackParser): Record { + const debugIdMap = GLOBAL_OBJ._sentryDebugIds; + if (!debugIdMap) { + return {}; + } + + let debugIdStackFramesCache: Map; + const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser); + if (cachedDebugIdStackFrameCache) { + debugIdStackFramesCache = cachedDebugIdStackFrameCache; + } else { + debugIdStackFramesCache = new Map(); + debugIdStackParserCache.set(stackParser, debugIdStackFramesCache); + } + + // Build a map of filename -> debug_id. + return Object.keys(debugIdMap).reduce>((acc, debugIdStackTrace) => { + let parsedStack: StackFrame[]; + + const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); + if (cachedParsedStack) { + parsedStack = cachedParsedStack; + } else { + parsedStack = stackParser(debugIdStackTrace); + debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); + } + + for (let i = parsedStack.length - 1; i >= 0; i--) { + const stackFrame = parsedStack[i]; + const file = stackFrame && stackFrame.filename; + + if (stackFrame && file) { + acc[file] = debugIdMap[debugIdStackTrace] as string; + break; + } + } + return acc; + }, {}); +} + +/** + * Returns a list of debug images for the given resources. + */ +export function getDebugImagesForResources( + stackParser: StackParser, + resource_paths: ReadonlyArray, +): DebugImage[] { + const filenameDebugIdMap = getFilenameToDebugIdMap(stackParser); + + const images: DebugImage[] = []; + for (const path of resource_paths) { + if (path && filenameDebugIdMap[path]) { + images.push({ + type: 'sourcemap', + code_file: path, + debug_id: filenameDebugIdMap[path] as string, + }); + } + } + + return images; +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 4a2d68ca0d8b..2a89826313e8 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -40,3 +40,4 @@ export * from './buildPolyfills'; export * from './propagationContext'; export * from './vercelWaitUntil'; export * from './version'; +export * from './debug-ids'; diff --git a/yarn.lock b/yarn.lock index 652c5721f5b6..da1c9aa37efc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1059,6 +1059,179 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== + dependencies: + tslib "^2.2.0" + +"@azure/abort-controller@^2.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-2.1.2.tgz#42fe0ccab23841d9905812c58f1082d27784566d" + integrity sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA== + dependencies: + tslib "^2.6.2" + +"@azure/core-auth@^1.3.0", "@azure/core-auth@^1.4.0": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.7.2.tgz#558b7cb7dd12b00beec07ae5df5907d74df1ebd9" + integrity sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-util" "^1.1.0" + tslib "^2.6.2" + +"@azure/core-auth@^1.5.0", "@azure/core-auth@^1.7.2": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.8.0.tgz#281b4a6d3309c3e7b15bcd967f01d4c79ae4a1d6" + integrity sha512-YvFMowkXzLbXNM11yZtVLhUCmuG0ex7JKOH366ipjmHBhL3vpDcPAeWF+jf0X+jVXwFqo3UhsWUq4kH0ZPdu/g== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-util" "^1.1.0" + tslib "^2.6.2" + +"@azure/core-client@^1.3.0", "@azure/core-client@^1.5.0", "@azure/core-client@^1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.9.2.tgz#6fc69cee2816883ab6c5cdd653ee4f2ff9774f74" + integrity sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.9.1" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.6.1" + "@azure/logger" "^1.0.0" + tslib "^2.6.2" + +"@azure/core-http-compat@^2.0.1": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@azure/core-http-compat/-/core-http-compat-2.1.2.tgz#d1585ada24ba750dc161d816169b33b35f762f0d" + integrity sha512-5MnV1yqzZwgNLLjlizsU3QqOeQChkIXw781Fwh1xdAqJR5AA32IUaq6xv1BICJvfbHoa+JYcaij2HFkhLbNTJQ== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-client" "^1.3.0" + "@azure/core-rest-pipeline" "^1.3.0" + +"@azure/core-lro@^2.2.0": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.7.2.tgz#787105027a20e45c77651a98b01a4d3b01b75a08" + integrity sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-util" "^1.2.0" + "@azure/logger" "^1.0.0" + tslib "^2.6.2" + +"@azure/core-paging@^1.1.1": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.6.2.tgz#40d3860dc2df7f291d66350b2cfd9171526433e7" + integrity sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA== + dependencies: + tslib "^2.6.2" + +"@azure/core-rest-pipeline@^1.1.0", "@azure/core-rest-pipeline@^1.3.0", "@azure/core-rest-pipeline@^1.8.1", "@azure/core-rest-pipeline@^1.9.1": + version "1.16.3" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.16.3.tgz#bde3bc3ebad7f885ddd9de6af5e5a8fc254b287e" + integrity sha512-VxLk4AHLyqcHsfKe4MZ6IQ+D+ShuByy+RfStKfSjxJoL3WBWq17VNmrz8aT8etKzqc2nAeIyLxScjpzsS4fz8w== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.9.0" + "@azure/logger" "^1.0.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.0" + tslib "^2.6.2" + +"@azure/core-tracing@^1.0.0", "@azure/core-tracing@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.1.2.tgz#065dab4e093fb61899988a1cdbc827d9ad90b4ee" + integrity sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA== + dependencies: + tslib "^2.6.2" + +"@azure/core-util@^1.0.0", "@azure/core-util@^1.1.0", "@azure/core-util@^1.2.0", "@azure/core-util@^1.6.1", "@azure/core-util@^1.9.0": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.9.2.tgz#1dc37dc5b0dae34c578be62cf98905ba7c0cafe7" + integrity sha512-l1Qrqhi4x1aekkV+OlcqsJa4AnAkj5p0JV8omgwjaV9OAbP41lvrMvs+CptfetKkeEaGRGSzby7sjPZEX7+kkQ== + dependencies: + "@azure/abort-controller" "^2.0.0" + tslib "^2.6.2" + +"@azure/core-util@^1.3.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.10.0.tgz#cf3163382d40343972848c914869864df5d44bdb" + integrity sha512-dqLWQsh9Nro1YQU+405POVtXnwrIVqPyfUzc4zXCbThTg7+vNNaiMkwbX9AMXKyoFYFClxmB3s25ZFr3+jZkww== + dependencies: + "@azure/abort-controller" "^2.0.0" + tslib "^2.6.2" + +"@azure/identity@^4.2.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.4.1.tgz#490fa2ad26786229afa36411892bb53dfa3478d3" + integrity sha512-DwnG4cKFEM7S3T+9u05NstXU/HN0dk45kPOinUyNKsn5VWwpXd9sbPKEg6kgJzGbm1lMuhx9o31PVbCtM5sfBA== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.5.0" + "@azure/core-client" "^1.9.2" + "@azure/core-rest-pipeline" "^1.1.0" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.3.0" + "@azure/logger" "^1.0.0" + "@azure/msal-browser" "^3.14.0" + "@azure/msal-node" "^2.9.2" + events "^3.0.0" + jws "^4.0.0" + open "^8.0.0" + stoppable "^1.1.0" + tslib "^2.2.0" + +"@azure/keyvault-keys@^4.4.0": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@azure/keyvault-keys/-/keyvault-keys-4.8.0.tgz#1513b3a187bb3a9a372b5980c593962fb793b2ad" + integrity sha512-jkuYxgkw0aaRfk40OQhFqDIupqblIOIlYESWB6DKCVDxQet1pyv86Tfk9M+5uFM0+mCs6+MUHU+Hxh3joiUn4Q== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-client" "^1.5.0" + "@azure/core-http-compat" "^2.0.1" + "@azure/core-lro" "^2.2.0" + "@azure/core-paging" "^1.1.1" + "@azure/core-rest-pipeline" "^1.8.1" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.0.0" + "@azure/logger" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.1.4.tgz#223cbf2b424dfa66478ce9a4f575f59c6f379768" + integrity sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ== + dependencies: + tslib "^2.6.2" + +"@azure/msal-browser@^3.14.0": + version "3.25.0" + resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.25.0.tgz#7ce0949977bc9e0c58319f7090c44fe5537104d4" + integrity sha512-a0Y7pmSy8SC1s9bvwr+REvyAA1nQcITlZvkElM2gNUPYFTTNUTEdcpg73TmawNucyMdZ9xb/GFcuhrLOqYAzwg== + dependencies: + "@azure/msal-common" "14.15.0" + +"@azure/msal-common@14.15.0": + version "14.15.0" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.15.0.tgz#0e27ac0bb88fe100f4f8d1605b64d5c268636a55" + integrity sha512-ImAQHxmpMneJ/4S8BRFhjt1MZ3bppmpRPYYNyzeQPeFN288YKbb8TmmISQEbtfkQ1BPASvYZU5doIZOPBAqENQ== + +"@azure/msal-node@^2.9.2": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.15.0.tgz#50bf8e692a6656027c073a75d877a8a478aafdfd" + integrity sha512-gVPW8YLz92ZeCibQH2QUw96odJoiM3k/ZPH3f2HxptozmH6+OnyyvKXo/Egg39HAM230akarQKHf0W74UHlh0Q== + dependencies: + "@azure/msal-common" "14.15.0" + jsonwebtoken "^9.0.0" + uuid "^8.3.0" + "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -6367,6 +6540,11 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@js-joda/core@^5.6.1": + version "5.6.3" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-5.6.3.tgz#41ae1c07de1ebe0f6dde1abcbc9700a09b9c6056" + integrity sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA== + "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -7302,13 +7480,13 @@ "@opentelemetry/context-base" "^0.12.0" semver "^7.1.3" -"@opentelemetry/instrumentation-amqplib@^0.42.0": - version "0.42.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.42.0.tgz#b3cab5a7207736a30d769962eed3af3838f986c4" - integrity sha512-fiuU6OKsqHJiydHWgTRQ7MnIrJ2lEqsdgFtNIH4LbAUJl/5XmrIeoDzDnox+hfkgWK65jsleFuQDtYb5hW1koQ== +"@opentelemetry/instrumentation-amqplib@^0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.43.0.tgz#e18b7d763b69c605a7abf9869e1c278f9bfdc1eb" + integrity sha512-ALjfQC+0dnIEcvNYsbZl/VLh7D2P1HhFF4vicRKHhHFIUV3Shpg4kXgiek5PLhmeKSIPiUB25IYH5RIneclL4A== dependencies: "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/instrumentation" "^0.54.0" "@opentelemetry/semantic-conventions" "^1.27.0" "@opentelemetry/instrumentation-aws-lambda@0.44.0": @@ -7425,6 +7603,14 @@ "@opentelemetry/instrumentation" "^0.54.0" "@opentelemetry/semantic-conventions" "^1.27.0" +"@opentelemetry/instrumentation-knex@0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.41.0.tgz#74d611489e823003a825097bac019c6c2ad061a5" + integrity sha512-OhI1SlLv5qnsnm2dOVrian/x3431P75GngSpnR7c4fcVFv7prXGYu29Z6ILRWJf/NJt6fkbySmwdfUUnFnHCTg== + dependencies: + "@opentelemetry/instrumentation" "^0.54.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/instrumentation-koa@0.43.0": version "0.43.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz#963fd192a1b5f6cbae5dabf4ec82e3105cbb23b1" @@ -7504,6 +7690,15 @@ "@opentelemetry/redis-common" "^0.36.2" "@opentelemetry/semantic-conventions" "^1.27.0" +"@opentelemetry/instrumentation-tedious@0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.15.0.tgz#da82f4d153fb6ff7d1f85d39872ac40bf9db12ea" + integrity sha512-Kb7yo8Zsq2TUwBbmwYgTAMPK0VbhoS8ikJ6Bup9KrDtCx2JC01nCb+M0VJWXt7tl0+5jARUbKWh5jRSoImxdCw== + dependencies: + "@opentelemetry/instrumentation" "^0.54.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/tedious" "^4.0.14" + "@opentelemetry/instrumentation-undici@0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz#9436ee155c8dcb0b760b66947c0e0f347688a5ef" @@ -8336,45 +8531,45 @@ magic-string "0.30.8" unplugin "1.0.1" -"@sentry/cli-darwin@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.37.0.tgz#9c890c68abf30ceaad27826212a0963b125b8bbf" - integrity sha512-CsusyMvO0eCPSN7H+sKHXS1pf637PWbS4rZak/7giz/z31/6qiXmeMlcL3f9lLZKtFPJmXVFO9uprn1wbBVF8A== - -"@sentry/cli-linux-arm64@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.37.0.tgz#2070155bade6d72d6b706807c6f365c65f9b82ea" - integrity sha512-2vzUWHLZ3Ct5gpcIlfd/2Qsha+y9M8LXvbZE26VxzYrIkRoLAWcnClBv8m4XsHLMURYvz3J9QSZHMZHSO7kAzw== - -"@sentry/cli-linux-arm@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.37.0.tgz#a08c2133e8e2566074fd6fe4f68e9ffd0c85664a" - integrity sha512-Dz0qH4Yt+gGUgoVsqVt72oDj4VQynRF1QB1/Sr8g76Vbi+WxWZmUh0iFwivYVwWxdQGu/OQrE0tx946HToCRyA== - -"@sentry/cli-linux-i686@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.37.0.tgz#53fff0e7f232b656b0ee3413b66006ee724a4abf" - integrity sha512-MHRLGs4t/CQE1pG+mZBQixyWL6xDZfNalCjO8GMcTTbZFm44S3XRHfYJZNVCgdtnUP7b6OHGcu1v3SWE10LcwQ== - -"@sentry/cli-linux-x64@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.37.0.tgz#2fbaf51ef3884bd6561c987f01ac98f544457150" - integrity sha512-k76ClefKZaDNJZU/H3mGeR8uAzAGPzDRG/A7grzKfBeyhP3JW09L7Nz9IQcSjCK+xr399qLhM2HFCaPWQ6dlMw== - -"@sentry/cli-win32-i686@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.37.0.tgz#fa195664da27ce8c40fdb6db1bf1d125cdf587d9" - integrity sha512-FFyi5RNYQQkEg4GkP2f3BJcgQn0F4fjFDMiWkjCkftNPXQG+HFUEtrGsWr6mnHPdFouwbYg3tEPUWNxAoypvTw== - -"@sentry/cli-win32-x64@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.37.0.tgz#84fa4d070b8a4a115c46ab38f42d29580143fd26" - integrity sha512-nSMj4OcfQmyL+Tu/jWCJwhKCXFsCZW1MUk6wjjQlRt9SDLfgeapaMlK1ZvT1eZv5ZH6bj3qJfefwj4U8160uOA== - -"@sentry/cli@^2.36.1", "@sentry/cli@^2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.37.0.tgz#dd01e933cf1caed7d7b6abab5a96044fe1c9c7a1" - integrity sha512-fM3V4gZRJR/s8lafc3O07hhOYRnvkySdPkvL/0e0XW0r+xRwqIAgQ5ECbsZO16A5weUiXVSf03ztDL1FcmbJCQ== +"@sentry/cli-darwin@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.38.2.tgz#2a53028e143d0cfed607588b87e04906ef5317e7" + integrity sha512-21ywIcJCCFrCTyiF1o1PaT7rbelFC2fWmayKYgFElnQ55IzNYkcn8BYhbh/QknE0l1NBRaeWMXwTTdeoqETCCg== + +"@sentry/cli-linux-arm64@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.38.2.tgz#1b45de7e4f5e1a953b88b0b811d789de1fc708aa" + integrity sha512-4Fp/jjQpNZj4Th+ZckMQvldAuuP0ZcyJ9tJCP1CCOn5poIKPYtY6zcbTP036R7Te14PS4ALOcDNX3VNKfpsifA== + +"@sentry/cli-linux-arm@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.38.2.tgz#91f73c251f1d4b591fa98af10ee3889c9b93d208" + integrity sha512-+AiKDBQKIdQe4NhBiHSHGl0KR+b//HHTrnfK1SaTrOm9HtM4ELXAkjkRF3bmbpSzSQCS5WzcbIxxCJOeaUaO0A== + +"@sentry/cli-linux-i686@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.38.2.tgz#26e30a9bc358f910e21d812359294dd4c6103fda" + integrity sha512-6zVJN10dHIn4R1v+fxuzlblzVBhIVwsaN/S7aBED6Vn1HhAyAcNG2tIzeCLGeDfieYjXlE2sCI82sZkQBCbAGw== + +"@sentry/cli-linux-x64@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.38.2.tgz#18728bbb20e28315c4368baded677786f2dba70a" + integrity sha512-4UiLu9zdVtqPeltELR5MDGKcuqAdQY9xz3emISuA6bm+MXGbt2W1WgX+XY3GElwjZbmH8qpyLUEd34sw6sdcbQ== + +"@sentry/cli-win32-i686@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.38.2.tgz#dfe268b041c3e3db556290dba745455d0b2c0d72" + integrity sha512-DYfSvd5qLPerLpIxj3Xu2rRe3CIlpGOOfGSNI6xvJ5D8j6hqbOHlCzvfC4oBWYVYGtxnwQLMeDGJ7o7RMYulig== + +"@sentry/cli-win32-x64@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.38.2.tgz#e7b5744026ff5f7e84971512bee228620ba5857d" + integrity sha512-W5UX58PKY1hNUHo9YJxWNhGvgvv2uOYHI27KchRiUvFYBIqlUUcIdPZDfyzetDfd8qBCxlAsFnkL2VJSNdpA9A== + +"@sentry/cli@^2.36.1", "@sentry/cli@^2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.38.2.tgz#e9a7a9bbeaaade4557de91704d50d131760345d3" + integrity sha512-CR0oujpAnhegK2pBAv6ZReMqbFTuNJLDZLvoD1B+syrKZX+R+oxkgT2e1htsBbht+wGxAsluVWsIAydSws1GAA== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -8382,13 +8577,13 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.37.0" - "@sentry/cli-linux-arm" "2.37.0" - "@sentry/cli-linux-arm64" "2.37.0" - "@sentry/cli-linux-i686" "2.37.0" - "@sentry/cli-linux-x64" "2.37.0" - "@sentry/cli-win32-i686" "2.37.0" - "@sentry/cli-win32-x64" "2.37.0" + "@sentry/cli-darwin" "2.38.2" + "@sentry/cli-linux-arm" "2.38.2" + "@sentry/cli-linux-arm64" "2.38.2" + "@sentry/cli-linux-i686" "2.38.2" + "@sentry/cli-linux-x64" "2.38.2" + "@sentry/cli-win32-i686" "2.38.2" + "@sentry/cli-win32-x64" "2.38.2" "@sentry/rollup-plugin@2.22.6": version "2.22.6" @@ -9913,6 +10108,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.2.tgz#d76fb80d87d0d8abfe334fc6d292e83e5524efc4" integrity sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w== +"@types/node@>=18": + version "22.7.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.4.tgz#e35d6f48dca3255ce44256ddc05dee1c23353fcc" + integrity sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg== + dependencies: + undici-types "~6.19.2" + "@types/node@^10.1.0": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" @@ -10040,6 +10242,14 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/readable-stream@^4.0.0": + version "4.0.15" + resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-4.0.15.tgz#e6ec26fe5b02f578c60baf1fa9452e90957d2bfb" + integrity sha512-oAZ3kw+kJFkEqyh7xORZOku1YAKvsFTogRY8kVl4vHpEKiDkfnSA/My8haRE7fvmix5Zyy+1pwzOi7yycGLBJw== + dependencies: + "@types/node" "*" + safe-buffer "~5.1.1" + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -10161,6 +10371,13 @@ resolved "https://registry.yarnpkg.com/@types/symlink-or-copy/-/symlink-or-copy-1.2.0.tgz#4151a81b4052c80bc2becbae09f3a9ec010a9c7a" integrity sha512-Lja2xYuuf2B3knEsga8ShbOdsfNOtzT73GyJmZyY7eGl2+ajOqrs8yM5ze0fsSoYwvA6bw7/Qr7OZ7PEEmYwWg== +"@types/tedious@^4.0.14": + version "4.0.14" + resolved "https://registry.yarnpkg.com/@types/tedious/-/tedious-4.0.14.tgz#868118e7a67808258c05158e9cad89ca58a2aec1" + integrity sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw== + dependencies: + "@types/node" "*" + "@types/tmp@^0.2.2": version "0.2.3" resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165" @@ -11338,6 +11555,13 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" +agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + agentkeepalive@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" @@ -12700,6 +12924,16 @@ bl@^5.0.0: inherits "^2.0.4" readable-stream "^3.4.0" +bl@^6.0.11: + version "6.0.16" + resolved "https://registry.yarnpkg.com/bl/-/bl-6.0.16.tgz#29b190f1a754e2d168de3dc8c74ed8d12bf78e6e" + integrity sha512-V/kz+z2Mx5/6qDfRCilmrukUXcXuCoXKg3/3hDvzKKoSUx8CJKudfIoT29XZc3UE9xBvxs5qictiHdprwtteEg== + dependencies: + "@types/readable-stream" "^4.0.0" + buffer "^6.0.3" + inherits "^2.0.4" + readable-stream "^4.2.0" + blake3-wasm@^2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz#b22dbb84bc9419ed0159caa76af4b1b132e6ba52" @@ -14238,6 +14472,11 @@ colord@^2.9.3: resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== +colorette@2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + colorette@^2.0.10: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" @@ -14285,7 +14524,7 @@ commander@7.2.0, commander@^7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^10.0.1: +commander@^10.0.0, commander@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== @@ -17659,7 +17898,7 @@ esm-env@^1.0.0: resolved "https://registry.yarnpkg.com/esm-env/-/esm-env-1.0.0.tgz#b124b40b180711690a4cb9b00d16573391950413" integrity sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA== -esm@^3.2.4: +esm@^3.2.25, esm@^3.2.4: version "3.2.25" resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== @@ -17764,7 +18003,7 @@ events-to-array@^1.0.1: resolved "https://registry.yarnpkg.com/events-to-array/-/events-to-array-1.1.2.tgz#2d41f563e1fe400ed4962fe1a4d5c6a7539df7f6" integrity sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y= -events@^3.2.0, events@^3.3.0: +events@^3.0.0, events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -18970,6 +19209,11 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +getopts@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" + integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== + giget@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/giget/-/giget-1.2.3.tgz#ef6845d1140e89adad595f7f3bb60aa31c672cb6" @@ -20166,6 +20410,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + http-proxy-middleware@^2.0.3: version "2.0.7" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" @@ -20209,6 +20461,14 @@ https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: agent-base "6" debug "4" +https-proxy-agent@^7.0.0: + version "7.0.5" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== + dependencies: + agent-base "^7.0.2" + debug "4" + https@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4" @@ -20606,6 +20866,11 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + invariant@^2.2.1, invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -21885,6 +22150,11 @@ js-cleanup@^1.2.0: perf-regexes "^1.0.1" skip-regex "^1.0.2" +js-md4@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/js-md4/-/js-md4-0.3.2.tgz#cd3b3dc045b0c404556c81ddb5756c23e59d7cf5" + integrity sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA== + js-string-escape@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" @@ -22142,6 +22412,22 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.2.0" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" @@ -22155,6 +22441,15 @@ just-extend@^6.2.0: resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + jwa@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" @@ -22164,6 +22459,14 @@ jwa@^2.0.0: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + jws@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" @@ -22235,6 +22538,26 @@ klona@^2.0.4, klona@^2.0.5, klona@^2.0.6: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== +knex@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/knex/-/knex-2.5.1.tgz#a6c6b449866cf4229f070c17411f23871ba52ef9" + integrity sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA== + dependencies: + colorette "2.0.19" + commander "^10.0.0" + debug "4.3.4" + escalade "^3.1.1" + esm "^3.2.25" + get-package-type "^0.1.0" + getopts "2.3.0" + interpret "^2.2.0" + lodash "^4.17.21" + pg-connection-string "2.6.1" + rechoir "^0.8.0" + resolve-from "^5.0.0" + tarn "^3.0.2" + tildify "2.0.0" + knitwork@^1.0.0, knitwork@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/knitwork/-/knitwork-1.1.0.tgz#d8c9feafadd7ee744ff64340b216a52c7199c417" @@ -22762,6 +23085,11 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + lodash.isarguments@^3.0.0, lodash.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -22772,11 +23100,36 @@ lodash.isarray@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -22806,6 +23159,11 @@ lodash.omit@^4.1.0: resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" @@ -24601,6 +24959,11 @@ napi-wasm@^1.1.0: resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.0.tgz#bbe617823765ae9c1bc12ff5942370eae7b2ba4e" integrity sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg== +native-duplexpair@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/native-duplexpair/-/native-duplexpair-1.0.0.tgz#7899078e64bf3c8a3d732601b3d40ff05db58fa0" + integrity sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -25750,7 +26113,7 @@ open@^10.1.0: is-inside-container "^1.0.0" is-wsl "^3.1.0" -open@^8.0.9: +open@^8.0.0, open@^8.0.9: version "8.4.2" resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== @@ -26501,6 +26864,11 @@ periscopic@^3.1.0: estree-walker "^3.0.0" is-reference "^3.0.0" +pg-connection-string@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb" + integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg== + pg-connection-string@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" @@ -28344,7 +28712,7 @@ readable-stream@^2.0.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^4.0.0: +readable-stream@^4.0.0, readable-stream@^4.2.0: version "4.5.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== @@ -28442,6 +28810,13 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -31156,6 +31531,27 @@ tar@^6.2.0: mkdirp "^1.0.3" yallist "^4.0.0" +tarn@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" + integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ== + +tedious@^18.6.1: + version "18.6.1" + resolved "https://registry.yarnpkg.com/tedious/-/tedious-18.6.1.tgz#1c4a3f06c891be67a032117e2e25193286d44496" + integrity sha512-9AvErXXQTd6l7TDd5EmM+nxbOGyhnmdbp/8c3pw+tjaiSXW9usME90ET/CRG1LN1Y9tPMtz/p83z4Q97B4DDpw== + dependencies: + "@azure/core-auth" "^1.7.2" + "@azure/identity" "^4.2.1" + "@azure/keyvault-keys" "^4.4.0" + "@js-joda/core" "^5.6.1" + "@types/node" ">=18" + bl "^6.0.11" + iconv-lite "^0.6.3" + js-md4 "^0.3.2" + native-duplexpair "^1.0.0" + sprintf-js "^1.1.3" + teeny-request@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.0.1.tgz#bdd41fdffea5f8fbc0d29392cb47bec4f66b2b4c" @@ -31356,6 +31752,11 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +tildify@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" + integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== + tiny-glob@0.2.9, tiny-glob@^0.2.9: version "0.2.9" resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" @@ -32025,6 +32426,11 @@ underscore@>=1.8.3: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + undici@^5.25.4: version "5.28.3" resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.3.tgz#a731e0eff2c3fcfd41c1169a869062be222d1e5b" @@ -32651,7 +33057,7 @@ uuid-v4@^0.1.0: resolved "https://registry.yarnpkg.com/uuid-v4/-/uuid-v4-0.1.0.tgz#62d7b310406f6cecfea1528c69f1e8e0bcec5a3a" integrity sha512-m11RYDtowtAIihBXMoGajOEKpAXrKbpKlpmxqyztMYQNGSY5nZAZ/oYch/w2HNS1RMA4WLGcZvuD8/wFMuCEzA== -uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.1, uuid@^8.3.2: +uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.0, uuid@^8.3.1, uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==