diff --git a/.size-limit.js b/.size-limit.js index 207fd6a2b85b..e8124a622962 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -38,7 +38,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration'), gzip: true, - limit: '42 KB', + limit: '43 KB', }, { name: '@sentry/browser (incl. Tracing, Profiling)', @@ -287,7 +287,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '163 KB', + limit: '166 KB', }, { name: '@sentry/node - without tracing', diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d67c0238bb2..e32b01c44a72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 10.36.0 + +- feat(node): Add Prisma v7 support ([#18908](https://github.com/getsentry/sentry-javascript/pull/18908)) +- feat(opentelemetry): Support `db.system.name` attribute for database spans ([#18902](https://github.com/getsentry/sentry-javascript/pull/18902)) +- feat(deps): Bump OpenTelemetry dependencies ([#18878](https://github.com/getsentry/sentry-javascript/pull/18878)) +- fix(core): Sanitize data URLs in `http.client` spans ([#18896](https://github.com/getsentry/sentry-javascript/pull/18896)) +- fix(nextjs): Add ALS runner fallbacks for serverless environments ([#18889](https://github.com/getsentry/sentry-javascript/pull/18889)) +- fix(node): Profiling debug ID matching + +
+ Internal Changes + +- chore(deps-dev): bump @remix-run/server-runtime from 2.15.2 to 2.17.3 in /packages/remix ([#18750](https://github.com/getsentry/sentry-javascript/pull/18750)) + +
+ ## 10.35.0 ### Important Changes diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-data-url/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-data-url/init.js new file mode 100644 index 000000000000..5ab240338c8c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-data-url/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, + autoSessionTracking: false, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-data-url/subject.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-data-url/subject.js new file mode 100644 index 000000000000..63d2d14fbd43 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-data-url/subject.js @@ -0,0 +1,6 @@ +// Fetch a data URL to verify that the span name and attributes are sanitized +// Data URLs are used for inline resources, e.g., Web Workers with inline scripts +const dataUrl = 'data:text/plain;base64,SGVsbG8gV29ybGQh'; +fetch(dataUrl).catch(() => { + // Data URL fetch might fail in some browsers, but the span should still be created +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-data-url/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-data-url/test.ts new file mode 100644 index 000000000000..46995dd6c152 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-data-url/test.ts @@ -0,0 +1,35 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + envelopeRequestParser, + shouldSkipTracingTest, + waitForTransactionRequestOnUrl, +} from '../../../../utils/helpers'; + +sentryTest('sanitizes data URLs in fetch span name and attributes', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const req = await waitForTransactionRequestOnUrl(page, url); + const transactionEvent = envelopeRequestParser(req); + + const requestSpans = transactionEvent.spans?.filter(({ op }) => op === 'http.client'); + + expect(requestSpans).toHaveLength(1); + + const span = requestSpans?.[0]; + + const sanitizedUrl = 'data:text/plain,base64,SGVsbG8gV2... [truncated]'; + expect(span?.description).toBe(`GET ${sanitizedUrl}`); + + expect(span?.data).toMatchObject({ + 'http.method': 'GET', + url: sanitizedUrl, + type: 'fetch', + }); + + expect(span?.data?.['http.url']).toBe(sanitizedUrl); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-data-url/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-data-url/init.js new file mode 100644 index 000000000000..5ab240338c8c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-data-url/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1, + autoSessionTracking: false, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-data-url/subject.js b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-data-url/subject.js new file mode 100644 index 000000000000..76656f862519 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-data-url/subject.js @@ -0,0 +1,5 @@ +// XHR request to a data URL to verify that the span name and attributes are sanitized +const dataUrl = 'data:text/plain;base64,SGVsbG8gV29ybGQh'; +const xhr = new XMLHttpRequest(); +xhr.open('GET', dataUrl); +xhr.send(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-data-url/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-data-url/test.ts new file mode 100644 index 000000000000..88bce31e1753 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-data-url/test.ts @@ -0,0 +1,30 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest('sanitizes data URLs in XHR span name and attributes', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const requestSpans = eventData.spans?.filter(({ op }) => op === 'http.client'); + + expect(requestSpans).toHaveLength(1); + + const span = requestSpans?.[0]; + + const sanitizedUrl = 'data:text/plain,base64,SGVsbG8gV2... [truncated]'; + expect(span?.description).toBe(`GET ${sanitizedUrl}`); + + expect(span?.data).toMatchObject({ + 'http.method': 'GET', + url: sanitizedUrl, + type: 'xhr', + }); + + expect(span?.data?.['http.url']).toBe(sanitizedUrl); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json index d7c83c773514..62d5bc10cd1a 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-custom-sampler/package.json @@ -12,12 +12,12 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.2.0", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/instrumentation": "^0.208.0", - "@opentelemetry/instrumentation-http": "^0.208.0", - "@opentelemetry/resources": "^2.2.0", - "@opentelemetry/sdk-trace-node": "^2.2.0", + "@opentelemetry/context-async-hooks": "^2.4.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/instrumentation-http": "^0.210.0", + "@opentelemetry/resources": "^2.4.0", + "@opentelemetry/sdk-trace-node": "^2.4.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@sentry/node-core": "latest || *", "@sentry/opentelemetry": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json index c8987cd84f39..49e9bfe01dc8 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2-sdk-node/package.json @@ -12,15 +12,15 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.2.0", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/instrumentation": "^0.208.0", - "@opentelemetry/instrumentation-http": "^0.208.0", - "@opentelemetry/resources": "^2.2.0", - "@opentelemetry/sdk-trace-node": "^2.2.0", + "@opentelemetry/context-async-hooks": "^2.4.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/instrumentation-http": "^0.210.0", + "@opentelemetry/resources": "^2.4.0", + "@opentelemetry/sdk-trace-node": "^2.4.0", "@opentelemetry/semantic-conventions": "^1.37.0", - "@opentelemetry/sdk-node": "^0.208.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.208.0", + "@opentelemetry/sdk-node": "^0.210.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.210.0", "@sentry/node-core": "latest || *", "@sentry/opentelemetry": "latest || *", "@types/express": "4.17.17", diff --git a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json index c532f50e5ef9..bda2295cc692 100644 --- a/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json +++ b/dev-packages/e2e-tests/test-applications/node-core-express-otel-v2/package.json @@ -14,12 +14,12 @@ "@sentry/node-core": "latest || *", "@sentry/opentelemetry": "latest || *", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.2.0", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/instrumentation": "^0.208.0", - "@opentelemetry/instrumentation-http": "^0.208.0", - "@opentelemetry/resources": "^2.2.0", - "@opentelemetry/sdk-trace-node": "^2.2.0", + "@opentelemetry/context-async-hooks": "^2.4.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/instrumentation-http": "^0.210.0", + "@opentelemetry/resources": "^2.4.0", + "@opentelemetry/sdk-trace-node": "^2.4.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@types/express": "^4.17.21", "@types/node": "^18.19.1", diff --git a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json index 85fc9b85de85..681a10ccc39d 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-custom-sampler/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/sdk-trace-node": "^2.1.0", + "@opentelemetry/sdk-trace-node": "^2.4.0", "@sentry/node": "latest || *", "@sentry/opentelemetry": "latest || *", "@types/express": "4.17.17", diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json index 709d597b19af..cadd3c29470c 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json @@ -11,8 +11,8 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/sdk-node": "0.208.0", - "@opentelemetry/exporter-trace-otlp-http": "0.208.0", + "@opentelemetry/sdk-node": "0.210.0", + "@opentelemetry/exporter-trace-otlp-http": "0.210.0", "@sentry/node": "latest || *", "@types/express": "4.17.17", "@types/node": "^18.19.1", diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json index a66c60146c65..a445a9ef4aac 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json @@ -12,11 +12,11 @@ }, "dependencies": { "@opentelemetry/api": "1.9.0", - "@opentelemetry/sdk-trace-node": "2.2.0", - "@opentelemetry/exporter-trace-otlp-http": "0.208.0", - "@opentelemetry/instrumentation-undici": "0.13.2", - "@opentelemetry/instrumentation-http": "0.208.0", - "@opentelemetry/instrumentation": "0.208.0", + "@opentelemetry/sdk-trace-node": "2.4.0", + "@opentelemetry/exporter-trace-otlp-http": "0.210.0", + "@opentelemetry/instrumentation-undici": "0.20.0", + "@opentelemetry/instrumentation-http": "0.210.0", + "@opentelemetry/instrumentation": "0.210.0", "@sentry/node": "latest || *", "@types/express": "4.17.17", "@types/node": "^18.19.1", diff --git a/dev-packages/e2e-tests/test-applications/node-otel/package.json b/dev-packages/e2e-tests/test-applications/node-otel/package.json index 0b202bd80556..f13c0cdb896e 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel/package.json @@ -11,8 +11,8 @@ "test:assert": "pnpm test" }, "dependencies": { - "@opentelemetry/sdk-node": "0.208.0", - "@opentelemetry/exporter-trace-otlp-http": "0.208.0", + "@opentelemetry/sdk-node": "0.210.0", + "@opentelemetry/exporter-trace-otlp-http": "0.210.0", "@sentry/node": "latest || *", "@types/express": "4.17.17", "@types/node": "^18.19.1", diff --git a/dev-packages/node-core-integration-tests/package.json b/dev-packages/node-core-integration-tests/package.json index 6157ec378d4f..a6448dd06030 100644 --- a/dev-packages/node-core-integration-tests/package.json +++ b/dev-packages/node-core-integration-tests/package.json @@ -27,12 +27,12 @@ "@nestjs/core": "^11", "@nestjs/platform-express": "^11", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.2.0", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/instrumentation": "^0.208.0", - "@opentelemetry/instrumentation-http": "0.208.0", - "@opentelemetry/resources": "^2.2.0", - "@opentelemetry/sdk-trace-base": "^2.2.0", + "@opentelemetry/context-async-hooks": "^2.4.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/instrumentation-http": "0.210.0", + "@opentelemetry/resources": "^2.4.0", + "@opentelemetry/sdk-trace-base": "^2.4.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@sentry/core": "10.35.0", "@sentry/node-core": "10.35.0", diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 0305a70e0029..b503e11b5c51 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -35,6 +35,7 @@ "@nestjs/common": "^11", "@nestjs/core": "^11", "@nestjs/platform-express": "^11", + "@prisma/adapter-pg": "7.2.0", "@prisma/client": "6.15.0", "@sentry/aws-serverless": "10.35.0", "@sentry/core": "10.35.0", diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/docker-compose.yml new file mode 100644 index 000000000000..a56fd51cf7d9 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.9' + +services: + db: + image: postgres:13 + restart: always + container_name: integration-tests-prisma-v7 + ports: + - '5435:5432' + environment: + POSTGRES_USER: prisma + POSTGRES_PASSWORD: prisma + POSTGRES_DB: tests diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/instrument.mjs new file mode 100644 index 000000000000..46a27dd03b74 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/instrument.mjs @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma.config.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma.config.ts new file mode 100644 index 000000000000..cb67c05a6f37 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'prisma/config'; + +export default defineConfig({ + schema: './prisma/schema.prisma', + migrations: './prisma/migrations', + datasource: { + url: 'postgresql://prisma:prisma@localhost:5435/tests', + }, +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/migrations/migration_lock.toml b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/migrations/migration_lock.toml new file mode 100644 index 000000000000..99e4f2009079 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/migrations/sentry_test/migration.sql b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/migrations/sentry_test/migration.sql new file mode 100644 index 000000000000..8619aaceb2b0 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/migrations/sentry_test/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "email" TEXT NOT NULL, + "name" TEXT, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/schema.prisma b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/schema.prisma new file mode 100644 index 000000000000..6aa93e968540 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/schema.prisma @@ -0,0 +1,15 @@ +datasource db { + provider = "postgresql" +} + +generator client { + provider = "prisma-client" + output = "./generated/prisma" +} + +model User { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + email String @unique + name String? +} diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/tsconfig.json b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/tsconfig.json new file mode 100644 index 000000000000..f95f8e88df1b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/prisma/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2023", + "declaration": false, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true + }, + "include": ["./generated/prisma/**/*.ts"] +} diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/scenario.mjs new file mode 100644 index 000000000000..61a7d3c43984 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/scenario.mjs @@ -0,0 +1,46 @@ +import { PrismaPg } from '@prisma/adapter-pg'; +import * as Sentry from '@sentry/node'; +import { randomBytes } from 'crypto'; +import { PrismaClient } from './prisma/generated/prisma/client.js'; + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const connectionString = 'postgresql://prisma:prisma@localhost:5435/tests'; + +async function run() { + await Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + async span => { + const adapter = new PrismaPg({ connectionString }); + const client = new PrismaClient({ adapter }); + + await client.user.create({ + data: { + name: 'Tilda', + email: `tilda_${randomBytes(4).toString('hex')}@sentry.io`, + }, + }); + + await client.user.findMany(); + + await client.user.deleteMany({ + where: { + email: { + contains: 'sentry.io', + }, + }, + }); + + setTimeout(async () => { + span.end(); + await client.$disconnect(); + }, 500); + }, + ); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/test.ts new file mode 100644 index 000000000000..9ae4efd136e7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v7/test.ts @@ -0,0 +1,74 @@ +import { afterAll, expect } from 'vitest'; +import { conditionalTest } from '../../../utils'; +import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; + +afterAll(() => { + cleanupChildProcesses(); +}); + +// Prisma 7 requires Node.js 20.19+ +conditionalTest({ min: 20 })('Prisma ORM v7 Tests', () => { + createEsmAndCjsTests( + __dirname, + 'scenario.mjs', + 'instrument.mjs', + (createRunner, test, _mode, cwd) => { + test('should instrument PostgreSQL queries from Prisma ORM', { timeout: 75_000 }, async () => { + await createRunner() + .withDockerCompose({ + workingDirectory: [cwd], + readyMatches: ['port 5432'], + setupCommand: `prisma generate --schema ${cwd}/prisma/schema.prisma && tsc -p ${cwd}/prisma/tsconfig.json && prisma migrate dev -n sentry-test --schema ${cwd}/prisma/schema.prisma`, + }) + .expect({ + transaction: transaction => { + expect(transaction.transaction).toBe('Test Transaction'); + + const spans = transaction.spans || []; + expect(spans.length).toBeGreaterThanOrEqual(5); + + // Verify Prisma spans have the correct origin + const prismaSpans = spans.filter( + span => span.data && span.data['sentry.origin'] === 'auto.db.otel.prisma', + ); + expect(prismaSpans.length).toBeGreaterThanOrEqual(5); + + // Check for key Prisma span descriptions + const spanDescriptions = prismaSpans.map(span => span.description); + expect(spanDescriptions).toContain('prisma:client:operation'); + expect(spanDescriptions).toContain('prisma:client:serialize'); + expect(spanDescriptions).toContain('prisma:client:connect'); + expect(spanDescriptions).toContain('prisma:client:db_query'); + + // Verify the create operation has correct metadata + const createSpan = prismaSpans.find( + span => + span.description === 'prisma:client:operation' && + span.data?.['method'] === 'create' && + span.data?.['model'] === 'User', + ); + expect(createSpan).toBeDefined(); + + // Verify db_query span has system info and correct op (v7 uses db.system.name) + const dbQuerySpan = prismaSpans.find(span => span.description === 'prisma:client:db_query'); + expect(dbQuerySpan?.data?.['db.system.name']).toBe('postgresql'); + expect(dbQuerySpan?.data?.['sentry.op']).toBe('db'); + expect(dbQuerySpan?.op).toBe('db'); + }, + }) + .start() + .completed(); + }); + }, + { + additionalDependencies: { + '@prisma/adapter-pg': '7.2.0', + '@prisma/client': '7.2.0', + pg: '^8.11.0', + prisma: '7.2.0', + typescript: '^5.9.0', + }, + copyPaths: ['prisma', 'prisma.config.ts', 'docker-compose.yml'], + }, + ); +}); diff --git a/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts index 985db0a80e6c..ee2fae0bc06b 100644 --- a/dev-packages/node-integration-tests/utils/runner.ts +++ b/dev-packages/node-integration-tests/utils/runner.ts @@ -110,7 +110,9 @@ async function runDockerCompose(options: DockerOptions): Promise { clearTimeout(timeout); if (options.setupCommand) { try { - execSync(options.setupCommand, { cwd, stdio: 'inherit' }); + // Prepend local node_modules/.bin to PATH so additionalDependencies binaries take precedence + const env = { ...process.env, PATH: `${cwd}/node_modules/.bin:${process.env.PATH}` }; + execSync(options.setupCommand, { cwd, stdio: 'inherit', env }); } catch (e) { log('Error running docker setup command', e); } diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index aa93a915f338..56174588929c 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -66,8 +66,8 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/instrumentation": "^0.208.0", - "@opentelemetry/instrumentation-aws-sdk": "0.64.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/instrumentation-aws-sdk": "0.65.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@sentry/core": "10.35.0", "@sentry/node": "10.35.0", diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index 6bada802b98e..c8cd806d0062 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -9,6 +9,7 @@ import { getLocationHref, isPrimitive, isString, + stripDataUrlContent, UNKNOWN_FUNCTION, } from '@sentry/core'; import type { BrowserClient } from '../client'; @@ -208,14 +209,13 @@ function getFilenameFromUrl(url: string | undefined): string | undefined { return undefined; } - // stack frame urls can be data urls, for example when initializing a Worker with a base64 encoded script - // in this case we just show the data prefix and mime type to avoid too long raw data urls + // Strip data URL content to avoid long base64 strings in stack frames + // (e.g. when initializing a Worker with a base64 encoded script) + // Don't include data prefix for filenames as it's not useful for stack traces + // Wrap with < > to indicate it's a placeholder if (url.startsWith('data:')) { - const match = url.match(/^data:([^;]+)/); - const mimeType = match ? match[1] : 'text/javascript'; - const isBase64 = url.includes('base64,'); - return ``; + return `<${stripDataUrlContent(url, false)}>`; } - return url; // it's fine to not truncate it as it's not put in a regex (https://codeql.github.com/codeql-query-help/javascript/js-polynomial-redos) + return url; } diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index 025b08b12168..0c0e30629436 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -23,6 +23,7 @@ import { spanToJSON, startInactiveSpan, stringMatchesSomePattern, + stripDataUrlContent, stripUrlQueryAndFragment, } from '@sentry/core'; import type { XhrHint } from '@sentry-internal/browser-utils'; @@ -199,7 +200,7 @@ export function instrumentOutgoingRequests(client: Client, _options?: Partial @@ -317,9 +322,22 @@ function getSpanStartOptions( method: string, spanOrigin: SpanOrigin, ): Parameters[0] { + // Data URLs need special handling because parseStringToURLObject treats them as "relative" + // (no "://"), causing getSanitizedUrlStringFromUrlObject to return just the pathname + // without the "data:" prefix, making later stripDataUrlContent calls ineffective. + // So for data URLs, we strip the content first and use that directly. + if (url.startsWith('data:')) { + const sanitizedUrl = stripDataUrlContent(url); + return { + name: `${method} ${sanitizedUrl}`, + attributes: getFetchSpanAttributes(url, undefined, method, spanOrigin), + }; + } + const parsedUrl = parseStringToURLObject(url); + const sanitizedUrl = parsedUrl ? getSanitizedUrlStringFromUrlObject(parsedUrl) : url; return { - name: parsedUrl ? `${method} ${getSanitizedUrlStringFromUrlObject(parsedUrl)}` : method, + name: `${method} ${sanitizedUrl}`, attributes: getFetchSpanAttributes(url, parsedUrl, method, spanOrigin), }; } @@ -331,7 +349,7 @@ function getFetchSpanAttributes( spanOrigin: SpanOrigin, ): SpanAttributes { const attributes: SpanAttributes = { - url, + url: stripDataUrlContent(url), type: 'fetch', 'http.method': method, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanOrigin, @@ -339,7 +357,7 @@ function getFetchSpanAttributes( }; if (parsedUrl) { if (!isURLObjectRelative(parsedUrl)) { - attributes['http.url'] = parsedUrl.href; + attributes['http.url'] = stripDataUrlContent(parsedUrl.href); attributes['server.address'] = parsedUrl.host; } if (parsedUrl.search) { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0fdd328a42d2..19a83d230155 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -313,6 +313,7 @@ export { getHttpSpanDetailsFromUrlObject, isURLObjectRelative, getSanitizedUrlStringFromUrlObject, + stripDataUrlContent, } from './utils/url'; export { eventFromMessage, diff --git a/packages/core/src/utils/debug-ids.ts b/packages/core/src/utils/debug-ids.ts index fd31009ae32d..ea45a8fb2e6c 100644 --- a/packages/core/src/utils/debug-ids.ts +++ b/packages/core/src/utils/debug-ids.ts @@ -1,5 +1,6 @@ import type { DebugImage } from '../types-hoist/debugMeta'; import type { StackParser } from '../types-hoist/stacktrace'; +import { normalizeStackTracePath } from './stacktrace'; import { GLOBAL_OBJ } from './worldwide'; type StackString = string; @@ -10,6 +11,17 @@ let lastSentryKeysCount: number | undefined; let lastNativeKeysCount: number | undefined; let cachedFilenameDebugIds: Record | undefined; +/** + * Clears the cached debug ID mappings. + * Useful for testing or when the global debug ID state changes. + */ +export function clearDebugIdCache(): void { + parsedStackResults = undefined; + lastSentryKeysCount = undefined; + lastNativeKeysCount = undefined; + cachedFilenameDebugIds = undefined; +} + /** * Returns a map of filenames to debug identifiers. * Supports both proprietary _sentryDebugIds and native _debugIds (e.g., from Vercel) formats. @@ -101,11 +113,12 @@ export function getDebugImagesForResources( const images: DebugImage[] = []; for (const path of resource_paths) { - if (path && filenameDebugIdMap[path]) { + const normalizedPath = normalizeStackTracePath(path); + if (normalizedPath && filenameDebugIdMap[normalizedPath]) { images.push({ type: 'sourcemap', code_file: path, - debug_id: filenameDebugIdMap[path], + debug_id: filenameDebugIdMap[normalizedPath], }); } } diff --git a/packages/core/src/utils/node-stack-trace.ts b/packages/core/src/utils/node-stack-trace.ts index 0cecd3dbf1e9..1132471b0e8f 100644 --- a/packages/core/src/utils/node-stack-trace.ts +++ b/packages/core/src/utils/node-stack-trace.ts @@ -22,7 +22,7 @@ // THE SOFTWARE. import type { StackLineParser, StackLineParserFn } from '../types-hoist/stacktrace'; -import { UNKNOWN_FUNCTION } from './stacktrace'; +import { normalizeStackTracePath, UNKNOWN_FUNCTION } from './stacktrace'; export type GetModuleFn = (filename: string | undefined) => string | undefined; @@ -55,7 +55,6 @@ export function node(getModule?: GetModuleFn): StackLineParserFn { const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+):(\d+):(\d+)?|([^)]+))\)?/; const DATA_URI_MATCH = /at (?:async )?(.+?) \(data:(.*?),/; - // eslint-disable-next-line complexity return (line: string) => { const dataUriMatch = line.match(DATA_URI_MATCH); if (dataUriMatch) { @@ -109,14 +108,9 @@ export function node(getModule?: GetModuleFn): StackLineParserFn { functionName = typeName ? `${typeName}.${methodName}` : methodName; } - let filename = lineMatch[2]?.startsWith('file://') ? lineMatch[2].slice(7) : lineMatch[2]; + let filename = normalizeStackTracePath(lineMatch[2]); const isNative = lineMatch[5] === 'native'; - // If it's a Windows path, trim the leading slash so that `/C:/foo` becomes `C:/foo` - if (filename?.match(/\/[A-Z]:/)) { - filename = filename.slice(1); - } - if (!filename && lineMatch[5] && !isNative) { filename = lineMatch[5]; } diff --git a/packages/core/src/utils/stacktrace.ts b/packages/core/src/utils/stacktrace.ts index 6b50caf48b30..16a32ede4e58 100644 --- a/packages/core/src/utils/stacktrace.ts +++ b/packages/core/src/utils/stacktrace.ts @@ -177,3 +177,15 @@ export function getVueInternalName(value: VueViewModel | VNode): string { return isVNode ? '[VueVNode]' : '[VueViewModel]'; } + +/** + * Normalizes stack line paths by removing file:// prefix and leading slashes for Windows paths + */ +export function normalizeStackTracePath(path: string | undefined): string | undefined { + let filename = path?.startsWith('file://') ? path.slice(7) : path; + // If it's a Windows path, trim the leading slash so that `/C:/foo` becomes `C:/foo` + if (filename?.match(/\/[A-Z]:/)) { + filename = filename.slice(1); + } + return filename; +} diff --git a/packages/core/src/utils/url.ts b/packages/core/src/utils/url.ts index ca09e6e8b5e7..bf0c17dbc278 100644 --- a/packages/core/src/utils/url.ts +++ b/packages/core/src/utils/url.ts @@ -263,3 +263,39 @@ export function getSanitizedUrlString(url: PartialURL): string { return `${protocol ? `${protocol}://` : ''}${filteredHost}${path}`; } + +/** + * Strips the content from a data URL, returning a placeholder with the MIME type. + * + * Data URLs can be very long (e.g. base64 encoded scripts for Web Workers), + * with little valuable information, often leading to envelopes getting dropped due + * to size limit violations. Therefore, we strip data URLs and replace them with a + * placeholder. + * + * @param url - The URL to process + * @param includeDataPrefix - If true, includes the first 10 characters of the data stream + * for debugging (e.g., to identify magic bytes like WASM's AGFzbQ). + * Defaults to true. + * @returns For data URLs, returns a short format like `data:text/javascript;base64,SGVsbG8gV2... [truncated]`. + * For non-data URLs, returns the original URL unchanged. + */ +export function stripDataUrlContent(url: string, includeDataPrefix: boolean = true): string { + if (url.startsWith('data:')) { + // Match the MIME type (everything after 'data:' until the first ';' or ',') + const match = url.match(/^data:([^;,]+)/); + const mimeType = match ? match[1] : 'text/plain'; + const isBase64 = url.includes(';base64,'); + + // Find where the actual data starts (after the comma) + const dataStart = url.indexOf(','); + let dataPrefix = ''; + if (includeDataPrefix && dataStart !== -1) { + const data = url.slice(dataStart + 1); + // Include first 10 chars of data to help identify content (e.g., magic bytes) + dataPrefix = data.length > 10 ? `${data.slice(0, 10)}... [truncated]` : data; + } + + return `data:${mimeType}${isBase64 ? ',base64' : ''}${dataPrefix ? `,${dataPrefix}` : ''}`; + } + return url; +} diff --git a/packages/core/test/lib/utils/debug-ids.test.ts b/packages/core/test/lib/utils/debug-ids.test.ts new file mode 100644 index 000000000000..45ff58c82565 --- /dev/null +++ b/packages/core/test/lib/utils/debug-ids.test.ts @@ -0,0 +1,187 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { nodeStackLineParser } from '../../../src'; +import { clearDebugIdCache, getDebugImagesForResources, getFilenameToDebugIdMap } from '../../../src/utils/debug-ids'; +import { createStackParser } from '../../../src/utils/stacktrace'; + +const nodeStackParser = createStackParser(nodeStackLineParser()); + +describe('getDebugImagesForResources', () => { + beforeEach(() => { + // Clear any existing debug ID maps + delete (globalThis as any)._sentryDebugIds; + delete (globalThis as any)._debugIds; + clearDebugIdCache(); + }); + + it('should return debug images for resources without file:// prefix', () => { + // Setup debug IDs + (globalThis as any)._sentryDebugIds = { + 'at mockFunction (/var/task/index.js:1:1)': 'debug-id-123', + }; + + const resources = ['/var/task/index.js']; + const images = getDebugImagesForResources(nodeStackParser, resources); + + expect(images).toHaveLength(1); + expect(images[0]).toEqual({ + type: 'sourcemap', + code_file: '/var/task/index.js', + debug_id: 'debug-id-123', + }); + }); + + it('should return debug images for resources with file:// prefix', () => { + // Setup debug IDs - the stack parser strips file:// when parsing + (globalThis as any)._sentryDebugIds = { + 'at mockFunction (/var/task/index.js:1:1)': 'debug-id-123', + }; + + // V8 profiler returns resources WITH file:// prefix + const resources = ['file:///var/task/index.js']; + const images = getDebugImagesForResources(nodeStackParser, resources); + + expect(images).toHaveLength(1); + expect(images[0]).toEqual({ + type: 'sourcemap', + code_file: 'file:///var/task/index.js', + debug_id: 'debug-id-123', + }); + }); + + it('should handle mixed resources with and without file:// prefix', () => { + (globalThis as any)._sentryDebugIds = { + 'at mockFunction (/var/task/index.js:1:1)': 'debug-id-123', + 'at anotherFunction (/var/task/utils.js:10:5)': 'debug-id-456', + }; + + const resources = ['file:///var/task/index.js', '/var/task/utils.js']; + const images = getDebugImagesForResources(nodeStackParser, resources); + + expect(images).toHaveLength(2); + expect(images[0]).toEqual({ + type: 'sourcemap', + code_file: 'file:///var/task/index.js', + debug_id: 'debug-id-123', + }); + expect(images[1]).toEqual({ + type: 'sourcemap', + code_file: '/var/task/utils.js', + debug_id: 'debug-id-456', + }); + }); + + it('should return empty array when no debug IDs match', () => { + (globalThis as any)._sentryDebugIds = { + 'at mockFunction (/var/task/index.js:1:1)': 'debug-id-123', + }; + + const resources = ['file:///var/task/other.js']; + const images = getDebugImagesForResources(nodeStackParser, resources); + + expect(images).toHaveLength(0); + }); + + it('should return empty array when no debug IDs are registered', () => { + const resources = ['file:///var/task/index.js']; + const images = getDebugImagesForResources(nodeStackParser, resources); + + expect(images).toHaveLength(0); + }); + + it('should handle empty resource paths array', () => { + (globalThis as any)._sentryDebugIds = { + 'at mockFunction (/var/task/index.js:1:1)': 'debug-id-123', + }; + + const resources: string[] = []; + const images = getDebugImagesForResources(nodeStackParser, resources); + + expect(images).toHaveLength(0); + }); + + it('should handle Windows paths with file:// prefix', () => { + // Stack parser normalizes Windows paths: file:///C:/foo.js -> C:/foo.js + (globalThis as any)._sentryDebugIds = { + 'at mockFunction (C:/Users/dev/project/index.js:1:1)': 'debug-id-win-123', + }; + + // V8 profiler returns Windows paths with file:// prefix + const resources = ['file:///C:/Users/dev/project/index.js']; + const images = getDebugImagesForResources(nodeStackParser, resources); + + expect(images).toHaveLength(1); + expect(images[0]).toEqual({ + type: 'sourcemap', + code_file: 'file:///C:/Users/dev/project/index.js', + debug_id: 'debug-id-win-123', + }); + }); + + it('should handle Windows paths without file:// prefix', () => { + (globalThis as any)._sentryDebugIds = { + 'at mockFunction (C:/Users/dev/project/index.js:1:1)': 'debug-id-win-123', + }; + + const resources = ['C:/Users/dev/project/index.js']; + const images = getDebugImagesForResources(nodeStackParser, resources); + + expect(images).toHaveLength(1); + expect(images[0]).toEqual({ + type: 'sourcemap', + code_file: 'C:/Users/dev/project/index.js', + debug_id: 'debug-id-win-123', + }); + }); +}); + +describe('getFilenameToDebugIdMap', () => { + beforeEach(() => { + delete (globalThis as any)._sentryDebugIds; + delete (globalThis as any)._debugIds; + clearDebugIdCache(); + }); + + it('should return empty object when no debug IDs are registered', () => { + const map = getFilenameToDebugIdMap(nodeStackParser); + expect(map).toEqual({}); + }); + + it('should build map from _sentryDebugIds', () => { + (globalThis as any)._sentryDebugIds = { + 'at mockFunction (/var/task/index.js:1:1)': 'debug-id-123', + 'at anotherFunction (/var/task/utils.js:10:5)': 'debug-id-456', + }; + + const map = getFilenameToDebugIdMap(nodeStackParser); + + expect(map).toEqual({ + '/var/task/index.js': 'debug-id-123', + '/var/task/utils.js': 'debug-id-456', + }); + }); + + it('should build map from native _debugIds', () => { + (globalThis as any)._debugIds = { + 'at mockFunction (/var/task/index.js:1:1)': 'native-debug-id-123', + }; + + const map = getFilenameToDebugIdMap(nodeStackParser); + + expect(map).toEqual({ + '/var/task/index.js': 'native-debug-id-123', + }); + }); + + it('should prioritize native _debugIds over _sentryDebugIds', () => { + (globalThis as any)._sentryDebugIds = { + 'at mockFunction (/var/task/index.js:1:1)': 'sentry-debug-id', + }; + (globalThis as any)._debugIds = { + 'at mockFunction (/var/task/index.js:1:1)': 'native-debug-id', + }; + + const map = getFilenameToDebugIdMap(nodeStackParser); + + expect(map['/var/task/index.js']).toBe('native-debug-id'); + }); +}); diff --git a/packages/core/test/lib/utils/url.test.ts b/packages/core/test/lib/utils/url.test.ts index 33364d66daa5..7bdfcfd63804 100644 --- a/packages/core/test/lib/utils/url.test.ts +++ b/packages/core/test/lib/utils/url.test.ts @@ -6,6 +6,7 @@ import { isURLObjectRelative, parseStringToURLObject, parseUrl, + stripDataUrlContent, stripUrlQueryAndFragment, } from '../../../src/utils/url'; @@ -638,3 +639,119 @@ describe('getHttpSpanDetailsFromUrlObject', () => { }); }); }); + +describe('stripDataUrlContent', () => { + it('returns regular URLs unchanged', () => { + expect(stripDataUrlContent('https://example.com/api')).toBe('https://example.com/api'); + expect(stripDataUrlContent('http://localhost:3000/test')).toBe('http://localhost:3000/test'); + expect(stripDataUrlContent('/relative/path')).toBe('/relative/path'); + }); + + it('should be applied BEFORE parseStringToURLObject for data URLs', () => { + // This test documents an important behavior: + // Data URLs are treated as "relative" by parseStringToURLObject because they don't contain "://". + // This means getSanitizedUrlStringFromUrlObject returns just the pathname (without "data:" prefix), + // and stripDataUrlContent won't match since it checks url.startsWith('data:'). + // Therefore, stripDataUrlContent MUST be applied to the original URL before parsing. + const dataUrl = 'data:text/javascript;base64,SGVsbG8gV29ybGQ='; + + // Verify data URLs are treated as relative + const parsedUrl = parseStringToURLObject(dataUrl); + expect(parsedUrl).toBeDefined(); + expect(isURLObjectRelative(parsedUrl!)).toBe(true); + + // getSanitizedUrlStringFromUrlObject returns just the pathname for relative URLs + const sanitizedWithoutStripping = getSanitizedUrlStringFromUrlObject(parsedUrl!); + // The pathname doesn't start with 'data:', so stripDataUrlContent wouldn't work on it + expect(sanitizedWithoutStripping.startsWith('data:')).toBe(false); + // Applying stripDataUrlContent AFTER parsing is ineffective + expect(stripDataUrlContent(sanitizedWithoutStripping)).toBe(sanitizedWithoutStripping); + + // CORRECT approach: strip data URL content FIRST, before any URL parsing + const strippedUrl = stripDataUrlContent(dataUrl); + // Default behavior includes first 10 chars of data for debugging (e.g., magic bytes) + expect(strippedUrl).toBe('data:text/javascript,base64,SGVsbG8gV2... [truncated]'); + // The stripped URL is already sanitized and can be used directly as the span name + }); + + describe('with includeDataPrefix=true (default)', () => { + it('includes first 10 chars of data for base64 data URLs', () => { + // SGVsbG8gV29ybGQ= is "Hello World" in base64 + expect(stripDataUrlContent('data:text/javascript;base64,SGVsbG8gV29ybGQ=')).toBe( + 'data:text/javascript,base64,SGVsbG8gV2... [truncated]', + ); + expect(stripDataUrlContent('data:application/json;base64,eyJrZXkiOiJ2YWx1ZSJ9')).toBe( + 'data:application/json,base64,eyJrZXkiOi... [truncated]', + ); + }); + + it('includes first 10 chars of data for non-base64 data URLs', () => { + expect(stripDataUrlContent('data:text/plain,Hello%20World')).toBe('data:text/plain,Hello%20Wo... [truncated]'); + expect(stripDataUrlContent('data:text/html,

Hello

')).toBe('data:text/html,

Hello<... [truncated]'); + }); + + it('includes all data if less than 10 chars', () => { + expect(stripDataUrlContent('data:text/plain,Hi')).toBe('data:text/plain,Hi'); + expect(stripDataUrlContent('data:text/plain;base64,SGk=')).toBe('data:text/plain,base64,SGk='); + }); + + it('helps identify WASM by magic bytes (AGFzbQ)', () => { + // WASM magic bytes: \0asm -> base64: AGFzbQ + const wasmDataUrl = 'data:application/wasm;base64,AGFzbQEAAAA='; + expect(stripDataUrlContent(wasmDataUrl)).toBe('data:application/wasm,base64,AGFzbQEAAA... [truncated]'); + }); + + it('handles various MIME types', () => { + expect(stripDataUrlContent('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA')).toBe( + 'data:image/png,base64,iVBORw0KGg... [truncated]', + ); + expect(stripDataUrlContent('data:image/svg+xml;base64,PHN2Zz4=')).toBe('data:image/svg+xml,base64,PHN2Zz4='); + }); + + it('defaults to text/plain for data URLs without MIME type', () => { + expect(stripDataUrlContent('data:,Hello')).toBe('data:text/plain,Hello'); + expect(stripDataUrlContent('data:;base64,SGVsbG8=')).toBe('data:text/plain,base64,SGVsbG8='); + }); + + it('handles empty data URLs', () => { + expect(stripDataUrlContent('data:')).toBe('data:text/plain'); + }); + + it('handles very long base64 encoded data URLs', () => { + const longBase64 = 'A'.repeat(10000); + expect(stripDataUrlContent(`data:text/javascript;base64,${longBase64}`)).toBe( + 'data:text/javascript,base64,AAAAAAAAAA... [truncated]', + ); + }); + }); + + describe('with includeDataPrefix=false', () => { + it('strips all content from base64 data URLs', () => { + expect(stripDataUrlContent('data:text/javascript;base64,SGVsbG8gV29ybGQ=', false)).toBe( + 'data:text/javascript,base64', + ); + expect(stripDataUrlContent('data:application/json;base64,eyJrZXkiOiJ2YWx1ZSJ9', false)).toBe( + 'data:application/json,base64', + ); + }); + + it('strips all content from non-base64 data URLs', () => { + expect(stripDataUrlContent('data:text/plain,Hello%20World', false)).toBe('data:text/plain'); + expect(stripDataUrlContent('data:text/html,

Hello

', false)).toBe('data:text/html'); + }); + + it('handles various MIME types', () => { + expect(stripDataUrlContent('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA', false)).toBe( + 'data:image/png,base64', + ); + expect(stripDataUrlContent('data:application/wasm;base64,AGFzbQEAAAA=', false)).toBe( + 'data:application/wasm,base64', + ); + }); + + it('defaults to text/plain for data URLs without MIME type', () => { + expect(stripDataUrlContent('data:,Hello', false)).toBe('data:text/plain'); + expect(stripDataUrlContent('data:;base64,SGVsbG8=', false)).toBe('data:text/plain,base64'); + }); + }); +}); diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 38838303b35c..67a0afa84c2e 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -45,9 +45,9 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/instrumentation": "^0.208.0", - "@opentelemetry/instrumentation-nestjs-core": "0.55.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/instrumentation-nestjs-core": "0.56.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@sentry/core": "10.35.0", "@sentry/node": "10.35.0" diff --git a/packages/nextjs/src/server/prepareSafeIdGeneratorContext.ts b/packages/nextjs/src/server/prepareSafeIdGeneratorContext.ts index bd262eb736e1..b060ae040cfe 100644 --- a/packages/nextjs/src/server/prepareSafeIdGeneratorContext.ts +++ b/packages/nextjs/src/server/prepareSafeIdGeneratorContext.ts @@ -1,8 +1,4 @@ -import { - type _INTERNAL_RandomSafeContextRunner as _INTERNAL_RandomSafeContextRunner, - debug, - GLOBAL_OBJ, -} from '@sentry/core'; +import { type _INTERNAL_RandomSafeContextRunner as RandomSafeContextRunner, debug, GLOBAL_OBJ } from '@sentry/core'; import { DEBUG_BUILD } from '../common/debug-build'; // Inline AsyncLocalStorage interface from current types @@ -15,17 +11,53 @@ type OriginalAsyncLocalStorage = typeof AsyncLocalStorage; */ export function prepareSafeIdGeneratorContext(): void { const sym = Symbol.for('__SENTRY_SAFE_RANDOM_ID_WRAPPER__'); - const globalWithSymbol: typeof GLOBAL_OBJ & { [sym]?: _INTERNAL_RandomSafeContextRunner } = GLOBAL_OBJ; - const als = getAsyncLocalStorage(); - if (!als || typeof als.snapshot !== 'function') { - DEBUG_BUILD && - debug.warn( - '[@sentry/nextjs] No AsyncLocalStorage found in the runtime or AsyncLocalStorage.snapshot() is not available, skipping safe random ID generator context preparation, you may see some errors with cache components.', - ); + const globalWithSymbol: typeof GLOBAL_OBJ & { [sym]?: RandomSafeContextRunner } = GLOBAL_OBJ; + + // Get initial snapshot - if unavailable, don't set up the wrapper at all + const initialSnapshot = getAsyncLocalStorageSnapshot(); + if (!initialSnapshot) { return; } - globalWithSymbol[sym] = als.snapshot(); + // We store a wrapper function instead of the raw snapshot because in serverless + // environments (e.g., Cloudflare Workers), the snapshot is bound to the request + // context it was created in. Once that request ends, the snapshot becomes invalid. + // The wrapper catches this and creates a fresh snapshot for the current request context. + let cachedSnapshot: RandomSafeContextRunner = initialSnapshot; + + globalWithSymbol[sym] = (callback: () => T): T => { + try { + return cachedSnapshot(callback); + } catch (error) { + // Only handle AsyncLocalStorage-related errors, rethrow others + if (!isAsyncLocalStorageError(error)) { + throw error; + } + + // Snapshot likely stale, try to get a fresh one and retry + const freshSnapshot = getAsyncLocalStorageSnapshot(); + // No snapshot available, fall back to direct execution + if (!freshSnapshot) { + return callback(); + } + + // Update the cached snapshot + cachedSnapshot = freshSnapshot; + + // Retry the callback with the fresh snapshot + try { + return cachedSnapshot(callback); + } catch (retryError) { + // Only fall back for AsyncLocalStorage errors, rethrow others + if (!isAsyncLocalStorageError(retryError)) { + throw retryError; + } + // If fresh snapshot also fails with ALS error, fall back to direct execution + return callback(); + } + } + }; + DEBUG_BUILD && debug.log('[@sentry/nextjs] Prepared safe random ID generator context'); } @@ -47,3 +79,21 @@ function getAsyncLocalStorage(): OriginalAsyncLocalStorage | undefined { return undefined; } + +function getAsyncLocalStorageSnapshot(): RandomSafeContextRunner | undefined { + const als = getAsyncLocalStorage(); + + if (!als || typeof als.snapshot !== 'function') { + DEBUG_BUILD && + debug.warn( + '[@sentry/nextjs] No AsyncLocalStorage found in the runtime or AsyncLocalStorage.snapshot() is not available, skipping safe random ID generator context preparation, you may see some errors with cache components.', + ); + return undefined; + } + + return als.snapshot(); +} + +function isAsyncLocalStorageError(error: unknown): boolean { + return error instanceof Error && error.message.includes('AsyncLocalStorage'); +} diff --git a/packages/node-core/package.json b/packages/node-core/package.json index de16a80e79d7..bc6b4eed4b5e 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -58,11 +58,11 @@ }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0 || ^2.2.0", - "@opentelemetry/core": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/instrumentation": ">=0.57.1 <1", - "@opentelemetry/resources": "^1.30.1 || ^2.1.0 || ^2.2.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/resources": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.37.0" }, "dependencies": { @@ -74,11 +74,11 @@ "devDependencies": { "@apm-js-collab/code-transformer": "^0.8.2", "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.2.0", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/instrumentation": "^0.208.0", - "@opentelemetry/resources": "^2.2.0", - "@opentelemetry/sdk-trace-base": "^2.2.0", + "@opentelemetry/context-async-hooks": "^2.4.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/resources": "^2.4.0", + "@opentelemetry/sdk-trace-base": "^2.4.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@types/node": "^18.19.1" }, diff --git a/packages/node/package.json b/packages/node/package.json index c54ee3edc9f9..a17ab657b6e8 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -66,35 +66,35 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.2.0", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/instrumentation": "^0.208.0", - "@opentelemetry/instrumentation-amqplib": "0.55.0", - "@opentelemetry/instrumentation-connect": "0.52.0", - "@opentelemetry/instrumentation-dataloader": "0.26.0", - "@opentelemetry/instrumentation-express": "0.57.0", - "@opentelemetry/instrumentation-fs": "0.28.0", - "@opentelemetry/instrumentation-generic-pool": "0.52.0", - "@opentelemetry/instrumentation-graphql": "0.56.0", - "@opentelemetry/instrumentation-hapi": "0.55.0", - "@opentelemetry/instrumentation-http": "0.208.0", - "@opentelemetry/instrumentation-ioredis": "0.56.0", - "@opentelemetry/instrumentation-kafkajs": "0.18.0", - "@opentelemetry/instrumentation-knex": "0.53.0", - "@opentelemetry/instrumentation-koa": "0.57.0", - "@opentelemetry/instrumentation-lru-memoizer": "0.53.0", - "@opentelemetry/instrumentation-mongodb": "0.61.0", - "@opentelemetry/instrumentation-mongoose": "0.55.0", - "@opentelemetry/instrumentation-mysql": "0.54.0", - "@opentelemetry/instrumentation-mysql2": "0.55.0", - "@opentelemetry/instrumentation-pg": "0.61.0", - "@opentelemetry/instrumentation-redis": "0.57.0", - "@opentelemetry/instrumentation-tedious": "0.27.0", - "@opentelemetry/instrumentation-undici": "0.19.0", - "@opentelemetry/resources": "^2.2.0", - "@opentelemetry/sdk-trace-base": "^2.2.0", + "@opentelemetry/context-async-hooks": "^2.4.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/instrumentation": "^0.210.0", + "@opentelemetry/instrumentation-amqplib": "0.57.0", + "@opentelemetry/instrumentation-connect": "0.53.0", + "@opentelemetry/instrumentation-dataloader": "0.27.0", + "@opentelemetry/instrumentation-express": "0.58.0", + "@opentelemetry/instrumentation-fs": "0.29.0", + "@opentelemetry/instrumentation-generic-pool": "0.53.0", + "@opentelemetry/instrumentation-graphql": "0.57.0", + "@opentelemetry/instrumentation-hapi": "0.56.0", + "@opentelemetry/instrumentation-http": "0.210.0", + "@opentelemetry/instrumentation-ioredis": "0.58.0", + "@opentelemetry/instrumentation-kafkajs": "0.19.0", + "@opentelemetry/instrumentation-knex": "0.54.0", + "@opentelemetry/instrumentation-koa": "0.58.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.54.0", + "@opentelemetry/instrumentation-mongodb": "0.63.0", + "@opentelemetry/instrumentation-mongoose": "0.56.0", + "@opentelemetry/instrumentation-mysql": "0.56.0", + "@opentelemetry/instrumentation-mysql2": "0.56.0", + "@opentelemetry/instrumentation-pg": "0.62.0", + "@opentelemetry/instrumentation-redis": "0.58.0", + "@opentelemetry/instrumentation-tedious": "0.29.0", + "@opentelemetry/instrumentation-undici": "0.20.0", + "@opentelemetry/resources": "^2.4.0", + "@opentelemetry/sdk-trace-base": "^2.4.0", "@opentelemetry/semantic-conventions": "^1.37.0", - "@prisma/instrumentation": "6.19.0", + "@prisma/instrumentation": "7.2.0", "@sentry/core": "10.35.0", "@sentry/node-core": "10.35.0", "@sentry/opentelemetry": "10.35.0", diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index e6c48a6bd550..7c2cadf9eb43 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -3,7 +3,13 @@ import { diag } from '@opentelemetry/api'; import type { HttpInstrumentationConfig } from '@opentelemetry/instrumentation-http'; import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; import type { Span } from '@sentry/core'; -import { defineIntegration, getClient, hasSpansEnabled } from '@sentry/core'; +import { + defineIntegration, + getClient, + hasSpansEnabled, + SEMANTIC_ATTRIBUTE_URL_FULL, + stripDataUrlContent, +} from '@sentry/core'; import type { HTTPModuleRequestIncomingMessage, NodeClient, SentryHttpInstrumentationOptions } from '@sentry/node-core'; import { addOriginToSpan, @@ -282,6 +288,15 @@ function getConfigWithDefaults(options: Partial = {}): HttpInstrume requestHook: (span, req) => { addOriginToSpan(span, 'auto.http.otel.http'); + // Sanitize data URLs to prevent long base64 strings in span attributes + const url = getRequestUrl(req as ClientRequest); + if (url.startsWith('data:')) { + const sanitizedUrl = stripDataUrlContent(url); + span.setAttribute('http.url', sanitizedUrl); + span.setAttribute(SEMANTIC_ATTRIBUTE_URL_FULL, sanitizedUrl); + span.updateName(`${(req as ClientRequest).method || 'GET'} ${sanitizedUrl}`); + } + options.instrumentation?.requestHook?.(span, req); }, responseHook: (span, res) => { diff --git a/packages/node/src/integrations/node-fetch.ts b/packages/node/src/integrations/node-fetch.ts index 6da9fd628bac..b07c1cce8628 100644 --- a/packages/node/src/integrations/node-fetch.ts +++ b/packages/node/src/integrations/node-fetch.ts @@ -1,7 +1,15 @@ import type { UndiciInstrumentationConfig } from '@opentelemetry/instrumentation-undici'; import { UndiciInstrumentation } from '@opentelemetry/instrumentation-undici'; import type { IntegrationFn } from '@sentry/core'; -import { defineIntegration, getClient, hasSpansEnabled, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; +import { + defineIntegration, + getClient, + hasSpansEnabled, + SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_URL_FULL, + stripDataUrlContent, +} from '@sentry/core'; import type { NodeClient } from '@sentry/node-core'; import { generateInstrumentOnce, SentryNodeFetchInstrumentation } from '@sentry/node-core'; import type { NodeClientOptions } from '../types'; @@ -101,7 +109,20 @@ function getConfigWithDefaults(options: Partial = {}): UndiciI return !!shouldIgnore; }, - startSpanHook: () => { + startSpanHook: request => { + const url = getAbsoluteUrl(request.origin, request.path); + + // Sanitize data URLs to prevent long base64 strings in span attributes + if (url.startsWith('data:')) { + const sanitizedUrl = stripDataUrlContent(url); + return { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.node_fetch', + 'http.url': sanitizedUrl, + [SEMANTIC_ATTRIBUTE_URL_FULL]: sanitizedUrl, + [SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]: `${request.method || 'GET'} ${sanitizedUrl}`, + }; + } + return { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.node_fetch', }; diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 28a12de398c6..f8a5e3a391f4 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -43,16 +43,16 @@ }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0 || ^2.2.0", - "@opentelemetry/core": "^1.30.1 || ^2.1.0 || ^2.2.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0 || ^2.2.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/semantic-conventions": "^1.37.0" }, "devDependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.2.0", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/sdk-trace-base": "^2.2.0", + "@opentelemetry/context-async-hooks": "^2.4.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/sdk-trace-base": "^2.4.0", "@opentelemetry/semantic-conventions": "^1.37.0" }, "scripts": { diff --git a/packages/opentelemetry/src/utils/parseSpanDescription.ts b/packages/opentelemetry/src/utils/parseSpanDescription.ts index 9328320e404f..bb9c5f59acf7 100644 --- a/packages/opentelemetry/src/utils/parseSpanDescription.ts +++ b/packages/opentelemetry/src/utils/parseSpanDescription.ts @@ -1,6 +1,7 @@ import type { Attributes, AttributeValue } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { + ATTR_DB_SYSTEM_NAME, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE, ATTR_URL_FULL, @@ -47,7 +48,7 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind } // eslint-disable-next-line deprecation/deprecation - const dbSystem = attributes[SEMATTRS_DB_SYSTEM]; + const dbSystem = attributes[ATTR_DB_SYSTEM_NAME] || attributes[SEMATTRS_DB_SYSTEM]; const opIsCache = typeof attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'string' && attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP].startsWith('cache.'); diff --git a/packages/opentelemetry/test/utils/parseSpanDescription.test.ts b/packages/opentelemetry/test/utils/parseSpanDescription.test.ts index 1d717b1d7488..529866b8a2ac 100644 --- a/packages/opentelemetry/test/utils/parseSpanDescription.test.ts +++ b/packages/opentelemetry/test/utils/parseSpanDescription.test.ts @@ -2,6 +2,7 @@ import type { Span } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { + ATTR_DB_SYSTEM_NAME, ATTR_HTTP_ROUTE, SEMATTRS_DB_STATEMENT, SEMATTRS_DB_SYSTEM, @@ -147,6 +148,48 @@ describe('parseSpanDescription', () => { source: 'task', }, ], + [ + 'works with db.system.name (stable attribute)', + { + [ATTR_DB_SYSTEM_NAME]: 'postgresql', + [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', + }, + 'test name', + SpanKind.CLIENT, + { + description: 'SELECT * from users', + op: 'db', + source: 'task', + }, + ], + [ + 'works with db.system.name without statement', + { + [ATTR_DB_SYSTEM_NAME]: 'postgresql', + }, + 'test name', + SpanKind.CLIENT, + { + description: 'test name', + op: 'db', + source: 'task', + }, + ], + [ + 'prefers db.system.name over deprecated db.system', + { + [ATTR_DB_SYSTEM_NAME]: 'postgresql', + [SEMATTRS_DB_SYSTEM]: 'mysql', + [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', + }, + 'test name', + SpanKind.CLIENT, + { + description: 'SELECT * from users', + op: 'db', + source: 'task', + }, + ], [ 'works with rpc service', { diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 307ea8dd48f6..9f8ec57975ea 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -46,8 +46,8 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/instrumentation": "^0.210.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@sentry/browser": "10.35.0", "@sentry/cli": "^2.58.4", diff --git a/packages/remix/package.json b/packages/remix/package.json index 923ef935e18c..4cf7f1e83e1f 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -65,7 +65,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/instrumentation": "^0.208.0", + "@opentelemetry/instrumentation": "^0.210.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@remix-run/router": "1.x", "@sentry/cli": "^2.58.2", @@ -76,9 +76,9 @@ "yargs": "^17.6.0" }, "devDependencies": { - "@remix-run/node": "^2.15.2", - "@remix-run/react": "^2.15.2", - "@remix-run/server-runtime": "2.15.2", + "@remix-run/node": "^2.17.4", + "@remix-run/react": "^2.17.4", + "@remix-run/server-runtime": "^2.17.4", "@types/express": "^4.17.14", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index 22d49718de4d..af20da5dec82 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -40,13 +40,13 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/resources": "^2.2.0", + "@opentelemetry/resources": "^2.4.0", "@sentry/core": "10.35.0" }, "devDependencies": { "@edge-runtime/types": "3.0.1", - "@opentelemetry/core": "^2.2.0", - "@opentelemetry/sdk-trace-base": "^2.2.0", + "@opentelemetry/core": "^2.4.0", + "@opentelemetry/sdk-trace-base": "^2.4.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@sentry/opentelemetry": "10.35.0" }, diff --git a/packages/vercel-edge/rollup.npm.config.mjs b/packages/vercel-edge/rollup.npm.config.mjs index d8f1704e2f8a..d19ef3a09e2f 100644 --- a/packages/vercel-edge/rollup.npm.config.mjs +++ b/packages/vercel-edge/rollup.npm.config.mjs @@ -43,6 +43,9 @@ const baseConfig = makeBaseNPMConfig({ plugins.makeJsonPlugin(), // Needed because `require-in-the-middle` imports json via require replace({ preventAssignment: true, + // Use negative lookahead/lookbehind instead of word boundaries so `process.argv0` is also replaced in + // `process.argv0.length` (where `.` follows). Default `\b` delimiters don't match before `.`. + delimiters: ['(?=0.52.0 <1", "@opentelemetry/instrumentation@^0.208.0": - version "0.208.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.208.0.tgz#d764f8e4329dad50804e2e98f010170c14c4ce8f" - integrity sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA== +"@opentelemetry/instrumentation@0.210.0", "@opentelemetry/instrumentation@^0.210.0": + version "0.210.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.210.0.tgz#3c9cf77072b7c7796fffcb04e19cad2976a4afbf" + integrity sha512-sLMhyHmW9katVaLUOKpfCnxSGhZq2t1ReWgwsu2cSgxmDVMB690H9TanuexanpFI94PJaokrqbp8u9KYZDUT5g== + dependencies: + "@opentelemetry/api-logs" "0.210.0" + import-in-the-middle "^2.0.0" + require-in-the-middle "^8.0.0" + +"@opentelemetry/instrumentation@^0.207.0": + version "0.207.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.207.0.tgz#1a5a921c04f171ff28096fa320af713f3c87ec14" + integrity sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA== dependencies: - "@opentelemetry/api-logs" "0.208.0" + "@opentelemetry/api-logs" "0.207.0" import-in-the-middle "^2.0.0" require-in-the-middle "^8.0.0" @@ -6203,21 +6225,21 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz#cefa4f3e79db1cd54f19e233b7dfb56621143955" integrity sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA== -"@opentelemetry/resources@2.2.0", "@opentelemetry/resources@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.2.0.tgz#b90a950ad98551295b76ea8a0e7efe45a179badf" - integrity sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A== +"@opentelemetry/resources@2.4.0", "@opentelemetry/resources@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.4.0.tgz#51188708204ba888685de019286a3969508c444d" + integrity sha512-RWvGLj2lMDZd7M/5tjkI/2VHMpXebLgPKvBUd9LRasEWR2xAynDwEYZuLvY9P2NGG73HF07jbbgWX2C9oavcQg== dependencies: - "@opentelemetry/core" "2.2.0" + "@opentelemetry/core" "2.4.0" "@opentelemetry/semantic-conventions" "^1.29.0" -"@opentelemetry/sdk-trace-base@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz#ddef9a0afd01a623d8625a3529f2137b05e67d0b" - integrity sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw== +"@opentelemetry/sdk-trace-base@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.4.0.tgz#0ab37a996cb574e7efc94e58fc759cb4a8df8401" + integrity sha512-WH0xXkz/OHORDLKqaxcUZS0X+t1s7gGlumr2ebiEgNZQl2b0upK2cdoD0tatf7l8iP74woGJ/Kmxe82jdvcWRw== dependencies: - "@opentelemetry/core" "2.2.0" - "@opentelemetry/resources" "2.2.0" + "@opentelemetry/core" "2.4.0" + "@opentelemetry/resources" "2.4.0" "@opentelemetry/semantic-conventions" "^1.29.0" "@opentelemetry/semantic-conventions@^1.24.0", "@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.30.0", "@opentelemetry/semantic-conventions@^1.33.0", "@opentelemetry/semantic-conventions@^1.33.1", "@opentelemetry/semantic-conventions@^1.34.0", "@opentelemetry/semantic-conventions@^1.36.0", "@opentelemetry/semantic-conventions@^1.37.0": @@ -6385,6 +6407,15 @@ resolved "https://registry.yarnpkg.com/@poppinss/exception/-/exception-1.2.2.tgz#8d30d42e126c54fe84e997433e4dcac610090743" integrity sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg== +"@prisma/adapter-pg@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/adapter-pg/-/adapter-pg-7.2.0.tgz#a956ae4b6b168ed2e1c687a0b45e0d1633959bf0" + integrity sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w== + dependencies: + "@prisma/driver-adapter-utils" "7.2.0" + pg "^8.16.3" + postgres-array "3.0.4" + "@prisma/client@6.15.0": version "6.15.0" resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.15.0.tgz#c4166aa8492878c842e63c515853d7a4125e6168" @@ -6405,6 +6436,18 @@ resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-6.15.0.tgz#8595f8ae91eadd20d926398449b8602c74e36bbf" integrity sha512-y7cSeLuQmyt+A3hstAs6tsuAiVXSnw9T55ra77z0nbNkA8Lcq9rNcQg6PI00by/+WnE/aMRJ/W7sZWn2cgIy1g== +"@prisma/debug@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-7.2.0.tgz#569b1cbc10eb3e8cae798b40075fd11d21f6b533" + integrity sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw== + +"@prisma/driver-adapter-utils@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.2.0.tgz#53d6686cdbb695338826e1c8ab5ac97c92efb676" + integrity sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ== + dependencies: + "@prisma/debug" "7.2.0" + "@prisma/engines-version@6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb": version "6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb" resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb.tgz#718b17890287447360107bef3675487d8cb0ae15" @@ -6436,12 +6479,12 @@ dependencies: "@prisma/debug" "6.15.0" -"@prisma/instrumentation@6.19.0": - version "6.19.0" - resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.19.0.tgz#46d15adc8bc4a5a3167032eea6d0a7aa64fb7d93" - integrity sha512-QcuYy25pkXM8BJ37wVFBO7Zh34nyRV1GOb2n3lPkkbRYfl4hWl3PTcImP41P0KrzVXfa/45p6eVCos27x3exIg== +"@prisma/instrumentation@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-7.2.0.tgz#9409a436d8f98e8950c8659aeeba045c4a07e891" + integrity sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g== dependencies: - "@opentelemetry/instrumentation" ">=0.52.0 <1" + "@opentelemetry/instrumentation" "^0.207.0" "@protobuf-ts/plugin-framework@^2.0.7", "@protobuf-ts/plugin-framework@^2.9.4": version "2.9.4" @@ -6609,47 +6652,52 @@ resolved "https://registry.yarnpkg.com/@redis/time-series/-/time-series-1.0.5.tgz#a6d70ef7a0e71e083ea09b967df0a0ed742bc6ad" integrity sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg== -"@remix-run/node@^2.15.2": - version "2.15.2" - resolved "https://registry.yarnpkg.com/@remix-run/node/-/node-2.15.2.tgz#7ea460335b66a06d22e554a84edf09e2b8879a8a" - integrity sha512-NS/h5uxje7DYCNgcKqKAiUhf0r2HVnoYUBWLyIIMmCUP1ddWurBP6xTPcWzGhEvV/EvguniYi1wJZ5+X8sonWw== +"@remix-run/node@^2.17.4": + version "2.17.4" + resolved "https://registry.yarnpkg.com/@remix-run/node/-/node-2.17.4.tgz#dfcc43106fc631c1bd6644d215d5698eb4958962" + integrity sha512-9A29JaYiGHDEmaiQuD1IlO/TrQxnnkj98GpytihU+Nz6yTt6RwzzyMMqTAoasRd1dPD4OeSaSqbwkcim/eE76Q== dependencies: - "@remix-run/server-runtime" "2.15.2" + "@remix-run/server-runtime" "2.17.4" "@remix-run/web-fetch" "^4.4.2" "@web3-storage/multipart-parser" "^1.0.0" cookie-signature "^1.1.0" source-map-support "^0.5.21" stream-slice "^0.1.2" - undici "^6.11.1" + undici "^6.21.2" -"@remix-run/react@^2.15.2": - version "2.15.2" - resolved "https://registry.yarnpkg.com/@remix-run/react/-/react-2.15.2.tgz#4f57434c120e0b7885d8b737c417b67f72a3a042" - integrity sha512-NAAMsSgoC/sdOgovUewwRCE/RUm3F+MBxxZKfwu3POCNeHaplY5qGkH/y8PUXvdN1EBG7Z0Ko43dyzCfcEy5PA== +"@remix-run/react@^2.17.4": + version "2.17.4" + resolved "https://registry.yarnpkg.com/@remix-run/react/-/react-2.17.4.tgz#128b6505c118e4bf67d919ee7d3991c0f6c65028" + integrity sha512-MeXHacIBoohr9jzec5j/Rmk57xk34korkPDDb0OPHgkdvh20lO5fJoSAcnZfjTIOH+Vsq1ZRQlmvG5PRQ/64Sw== dependencies: - "@remix-run/router" "1.21.0" - "@remix-run/server-runtime" "2.15.2" - react-router "6.28.1" - react-router-dom "6.28.1" - turbo-stream "2.4.0" + "@remix-run/router" "1.23.2" + "@remix-run/server-runtime" "2.17.4" + react-router "6.30.3" + react-router-dom "6.30.3" + turbo-stream "2.4.1" -"@remix-run/router@1.21.0", "@remix-run/router@1.x": +"@remix-run/router@1.21.0": version "1.21.0" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.21.0.tgz#c65ae4262bdcfe415dbd4f64ec87676e4a56e2b5" integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA== -"@remix-run/server-runtime@2.15.2": - version "2.15.2" - resolved "https://registry.yarnpkg.com/@remix-run/server-runtime/-/server-runtime-2.15.2.tgz#5be945027612c0891748d1788d39fea1ef0ba33c" - integrity sha512-OqiPcvEnnU88B8b1LIWHHkQ3Tz2GDAmQ1RihFNQsbrFKpDsQLkw0lJlnfgKA/uHd0CEEacpfV7C9qqJT3V6Z2g== +"@remix-run/router@1.23.2", "@remix-run/router@1.x": + version "1.23.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.2.tgz#156c4b481c0bee22a19f7924728a67120de06971" + integrity sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w== + +"@remix-run/server-runtime@2.17.4", "@remix-run/server-runtime@^2.17.4": + version "2.17.4" + resolved "https://registry.yarnpkg.com/@remix-run/server-runtime/-/server-runtime-2.17.4.tgz#00d52eb69e684b13c152ab9ace70b6c00aaeee5f" + integrity sha512-oCsFbPuISgh8KpPKsfBChzjcntvTz5L+ggq9VNYWX8RX3yA7OgQpKspRHOSxb05bw7m0Hx+L1KRHXjf3juKX8w== dependencies: - "@remix-run/router" "1.21.0" + "@remix-run/router" "1.23.2" "@types/cookie" "^0.6.0" "@web3-storage/multipart-parser" "^1.0.0" - cookie "^0.6.0" + cookie "^0.7.2" set-cookie-parser "^2.4.8" source-map "^0.7.3" - turbo-stream "2.4.0" + turbo-stream "2.4.1" "@remix-run/web-blob@^3.1.0": version "3.1.0" @@ -8982,10 +9030,10 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== -"@types/pg-pool@2.0.6": - version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.6.tgz#1376d9dc5aec4bb2ec67ce28d7e9858227403c77" - integrity sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ== +"@types/pg-pool@2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.7.tgz#c17945a74472d9a3beaf8e66d5aa6fc938328734" + integrity sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng== dependencies: "@types/pg" "*" @@ -13582,7 +13630,7 @@ cookie@^0.6.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== -cookie@^0.7.1, cookie@~0.7.2: +cookie@^0.7.1, cookie@^0.7.2, cookie@~0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== @@ -24887,35 +24935,35 @@ periscopic@^3.1.0: estree-walker "^3.0.0" is-reference "^3.0.0" -pg-cloudflare@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz#2e3649c38a7a9c74a7e5327c8098a2fd9af595bd" - integrity sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg== +pg-cloudflare@^1.2.5, pg-cloudflare@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz#386035d4bfcf1a7045b026f8b21acf5353f14d65" + integrity sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ== 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.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.9.0.tgz#f75e06591fdd42ec7636fe2c6a03febeedbec9bf" - integrity sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ== +pg-connection-string@^2.10.0, pg-connection-string@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.10.0.tgz#5570fd4837bd813a2b3938cd1744586c7df4a5f1" + integrity sha512-ur/eoPKzDx2IjPaYyXS6Y8NSblxM7X64deV2ObV57vhjsWiwLvUD6meukAzogiOsu60GO8m/3Cb6FdJsWNjwXg== pg-int8@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-pool@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.10.0.tgz#134b0213755c5e7135152976488aa7cd7ee1268d" - integrity sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA== +pg-pool@^3.10.0, pg-pool@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.11.0.tgz#adf9a6651a30c839f565a3cc400110949c473d69" + integrity sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w== -pg-protocol@*, pg-protocol@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.10.0.tgz#a473afcbb1c6e5dc3ac24869ba3dd563f8a1ae1b" - integrity sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q== +pg-protocol@*, pg-protocol@^1.10.0, pg-protocol@^1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.11.0.tgz#2502908893edaa1e8c0feeba262dd7b40b317b53" + integrity sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g== pg-types@2.2.0, pg-types@^2.2.0: version "2.2.0" @@ -24941,6 +24989,19 @@ pg@8.16.0: optionalDependencies: pg-cloudflare "^1.2.5" +pg@^8.16.3: + version "8.17.1" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.17.1.tgz#cecf0c96d3f5004951ccbafaaa230779ebc89d35" + integrity sha512-EIR+jXdYNSMOrpRp7g6WgQr7SaZNZfS7IzZIO0oTNEeibq956JxeD15t3Jk3zZH0KH8DmOIx38qJfQenoE8bXQ== + dependencies: + pg-connection-string "^2.10.0" + pg-pool "^3.11.0" + pg-protocol "^1.11.0" + pg-types "2.2.0" + pgpass "1.0.5" + optionalDependencies: + pg-cloudflare "^1.3.0" + pgpass@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" @@ -25739,6 +25800,11 @@ postcss@^8.1.10, postcss@^8.2.14, postcss@^8.2.15, postcss@^8.3.7, postcss@^8.4. picocolors "^1.1.1" source-map-js "^1.2.1" +postgres-array@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.4.tgz#4efcaf4d2c688d8bcaa8620ed13f35f299f7528c" + integrity sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ== + postgres-array@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" @@ -26340,20 +26406,20 @@ react-refresh@^0.14.0: dependencies: "@remix-run/router" "1.21.0" -react-router-dom@6.28.1: - version "6.28.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.28.1.tgz#b78fe452d2cd31919b80e57047a896bfa1509f8c" - integrity sha512-YraE27C/RdjcZwl5UCqF/ffXnZDxpJdk9Q6jw38SZHjXs7NNdpViq2l2c7fO7+4uWaEfcwfGCv3RSg4e1By/fQ== +react-router-dom@6.30.3: + version "6.30.3" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.30.3.tgz#42ae6dc4c7158bfb0b935f162b9621b29dddf740" + integrity sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag== dependencies: - "@remix-run/router" "1.21.0" - react-router "6.28.1" + "@remix-run/router" "1.23.2" + react-router "6.30.3" -react-router@6.28.1: - version "6.28.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.28.1.tgz#f82317ab24eee67d7beb7b304c0378b2b48fa178" - integrity sha512-2omQTA3rkMljmrvvo6WtewGdVh45SpL9hGiCI9uUrwGGfNFDIvGK4gYJsKlJoNVi6AQZcopSCballL+QGOm7fA== +react-router@6.30.3: + version "6.30.3" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.30.3.tgz#994b3ccdbe0e81fe84d4f998100f62584dfbf1cf" + integrity sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw== dependencies: - "@remix-run/router" "1.21.0" + "@remix-run/router" "1.23.2" react-router@^7.9.2: version "7.9.2" @@ -29958,10 +30024,10 @@ tunnel@^0.0.6: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== -turbo-stream@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/turbo-stream/-/turbo-stream-2.4.0.tgz#1e4fca6725e90fa14ac4adb782f2d3759a5695f0" - integrity sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g== +turbo-stream@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/turbo-stream/-/turbo-stream-2.4.1.tgz#c1a64397724084c09b0f6ea4a2c528d3f67931f9" + integrity sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw== twirp-ts@^2.5.0: version "2.5.0" @@ -30288,10 +30354,10 @@ undici@^5.25.4, undici@^5.28.5: dependencies: "@fastify/busboy" "^2.0.0" -undici@^6.11.1, undici@^6.19.2: - version "6.21.3" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.3.tgz#185752ad92c3d0efe7a7d1f6854a50f83b552d7a" - integrity sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw== +undici@^6.19.2, undici@^6.21.2: + version "6.23.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.23.0.tgz#7953087744d9095a96f115de3140ca3828aff3a4" + integrity sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g== unenv@2.0.0-rc.17: version "2.0.0-rc.17"