From 3aaac6493e2a1e2d7ecf99d6db6613e13fc8f396 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Wed, 28 Oct 2026 17:01:44 +0100 Subject: [PATCH 001/169] chore(deps): Bump @sentry/conventions to 0.30.0 (#25837) Bump `@sentry/conventions` to `0.30.0` across the SDK packages and test applications, preserving existing exact and caret pins. The release adds attributes and operations without removing exports or changing existing constant values, so no SDK code changes are required. Co-authored-by: GPT-6 --- .../browser-integration-tests/package.json | 2 +- .../cloudflare-integration-tests/package.json | 2 +- .../deno-integration-tests/package.json | 2 +- .../test-applications/angular-19/package.json | 2 +- .../cloudflare-pi-durable/package.json | 2 +- .../package.json | 2 +- .../nextjs-16-tunnel/package.json | 2 +- .../test-applications/nitro-3/package.json | 2 +- .../test-applications/node-eve/package.json | 2 +- .../test-applications/node-hapi/package.json | 2 +- .../node-mastra/package.json | 2 +- .../package.json | 2 +- .../node-pi-durable/package.json | 2 +- .../nuxt-4-static/package.json | 2 +- .../solidstart-static/package.json | 2 +- .../node-integration-tests/package.json | 2 +- packages/angular/package.json | 2 +- packages/astro/package.json | 2 +- packages/aws-serverless/package.json | 2 +- packages/browser-utils/package.json | 2 +- packages/browser/package.json | 2 +- packages/bun/package.json | 2 +- packages/cloudflare/package.json | 2 +- packages/core/package.json | 2 +- packages/deno/package.json | 2 +- packages/effect/package.json | 2 +- packages/elysia/package.json | 2 +- packages/ember/package.json | 2 +- packages/google-cloud-serverless/package.json | 2 +- packages/hono/package.json | 2 +- packages/nestjs/package.json | 2 +- packages/nextjs/package.json | 2 +- packages/nitro/package.json | 2 +- packages/node/package.json | 2 +- packages/nuxt/package.json | 2 +- packages/opentelemetry/package.json | 2 +- packages/react-router/package.json | 2 +- packages/react/package.json | 2 +- packages/remix/package.json | 2 +- packages/replay-internal/package.json | 2 +- packages/server-utils/package.json | 2 +- packages/solid/package.json | 2 +- packages/solidstart/package.json | 2 +- packages/svelte/package.json | 2 +- packages/sveltekit/package.json | 2 +- packages/tanstackstart-react/package.json | 2 +- packages/vue/package.json | 2 +- pnpm-lock.yaml | 146 +++++++++--------- pnpm-workspace.yaml | 2 +- 49 files changed, 121 insertions(+), 121 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index c42609533af1..f2c5860f3141 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -63,7 +63,7 @@ "@sentry/browser": "11.9.0", "@sentry/replay": "11.9.0", "@sentry/opentelemetry": "11.9.0", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@supabase/supabase-js": "2.109.0", "axios": "1.20.0", "babel-loader": "^10.1.1", diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json index a81c60d026ff..298581f561fd 100644 --- a/dev-packages/cloudflare-integration-tests/package.json +++ b/dev-packages/cloudflare-integration-tests/package.json @@ -36,7 +36,7 @@ "@cloudflare/workers-types-v5": "npm:@cloudflare/workers-types@5.20260710.1", "@opentelemetry/api": "^1.9.1", "@sentry-internal/test-utils": "11.9.0", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "eslint-plugin-regexp": "^3.1.0", "prisma": "6.15.0", "vite": "^8.3.1", diff --git a/dev-packages/deno-integration-tests/package.json b/dev-packages/deno-integration-tests/package.json index 58809ae6896c..646eb680721e 100644 --- a/dev-packages/deno-integration-tests/package.json +++ b/dev-packages/deno-integration-tests/package.json @@ -23,7 +23,7 @@ }, "devDependencies": { "@sentry-internal/node-integration-tests": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "vitest": "^4.1.11" } } diff --git a/dev-packages/e2e-tests/test-applications/angular-19/package.json b/dev-packages/e2e-tests/test-applications/angular-19/package.json index c4d95a31e2cf..1c0a004af2fe 100644 --- a/dev-packages/e2e-tests/test-applications/angular-19/package.json +++ b/dev-packages/e2e-tests/test-applications/angular-19/package.json @@ -36,7 +36,7 @@ "@angular/cli": "^19.0.0", "@angular/compiler-cli": "^19.0.0", "@playwright/test": "1.64.1", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", "@sentry/core": "file:../../packed/sentry-core-packed.tgz", "@types/jasmine": "~5.1.0", diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-pi-durable/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-pi-durable/package.json index 4d537a600017..5989cce49a5f 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-pi-durable/package.json +++ b/dev-packages/e2e-tests/test-applications/cloudflare-pi-durable/package.json @@ -22,7 +22,7 @@ "@cloudflare/workers-types": "^4.20260426.0", "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "typescript": "^5.5.2", "vite": "7.3.5", "wrangler": "^4.136.2" diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/package.json index f32066af21d8..78012b914707 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/package.json +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/package.json @@ -19,7 +19,7 @@ "@cloudflare/workers-types": "^5.20260727.1", "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@types/node": "^26.1.2", "sentry": "~0.44.1", "typescript": "~6.0.3", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/package.json index 49fab7eb7fe7..4e887a8425ec 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/dev-packages/e2e-tests/test-applications/nitro-3/package.json b/dev-packages/e2e-tests/test-applications/nitro-3/package.json index 05907e35b114..3bfee836718a 100644 --- a/dev-packages/e2e-tests/test-applications/nitro-3/package.json +++ b/dev-packages/e2e-tests/test-applications/nitro-3/package.json @@ -21,7 +21,7 @@ "devDependencies": { "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@sentry/core": "latest || *", "nitro": "^3.0.260522-beta", "rolldown": "latest", diff --git a/dev-packages/e2e-tests/test-applications/node-eve/package.json b/dev-packages/e2e-tests/test-applications/node-eve/package.json index bf4389573a07..3b45ee182d4e 100644 --- a/dev-packages/e2e-tests/test-applications/node-eve/package.json +++ b/dev-packages/e2e-tests/test-applications/node-eve/package.json @@ -32,7 +32,7 @@ "devDependencies": { "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@sentry/core": "file:../../packed/sentry-core-packed.tgz", "@types/node": "24.x", "typescript": "~5.9.0" diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/package.json b/dev-packages/e2e-tests/test-applications/node-hapi/package.json index 2b0a3b370e22..8a7725dad0b5 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/package.json +++ b/dev-packages/e2e-tests/test-applications/node-hapi/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@playwright/test": "1.64.1", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz" } } diff --git a/dev-packages/e2e-tests/test-applications/node-mastra/package.json b/dev-packages/e2e-tests/test-applications/node-mastra/package.json index e17539bcbe70..b24a01edc9be 100644 --- a/dev-packages/e2e-tests/test-applications/node-mastra/package.json +++ b/dev-packages/e2e-tests/test-applications/node-mastra/package.json @@ -29,7 +29,7 @@ "@mastra/deployer": "~1.74.0", "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@types/node": "24.x", "mastra": "~1.28.0", "typescript": "~5.9.0" diff --git a/dev-packages/e2e-tests/test-applications/node-openai-decisions-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/node-openai-decisions-send-to-sentry/package.json index 5f2855dd6dc1..7785dcfc5b72 100644 --- a/dev-packages/e2e-tests/test-applications/node-openai-decisions-send-to-sentry/package.json +++ b/dev-packages/e2e-tests/test-applications/node-openai-decisions-send-to-sentry/package.json @@ -24,7 +24,7 @@ "devDependencies": { "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@types/express": "^4.17.21", "@types/node": "^24.0.0", "sentry": "~0.44.1", diff --git a/dev-packages/e2e-tests/test-applications/node-pi-durable/package.json b/dev-packages/e2e-tests/test-applications/node-pi-durable/package.json index 423983c0ca7b..12bd7ae0b2cd 100644 --- a/dev-packages/e2e-tests/test-applications/node-pi-durable/package.json +++ b/dev-packages/e2e-tests/test-applications/node-pi-durable/package.json @@ -19,7 +19,7 @@ "devDependencies": { "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@sentry/core": "file:../../packed/sentry-core-packed.tgz", "@types/node": "24.x" }, diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4-static/package.json b/dev-packages/e2e-tests/test-applications/nuxt-4-static/package.json index e21627e76f08..63fd1d8c01e9 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4-static/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-4-static/package.json @@ -26,7 +26,7 @@ }, "devDependencies": { "@playwright/test": "1.64.1", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz" }, "sentryTest": { diff --git a/dev-packages/e2e-tests/test-applications/solidstart-static/package.json b/dev-packages/e2e-tests/test-applications/solidstart-static/package.json index 38ddeac40f07..1952ea19d33a 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-static/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-static/package.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@playwright/test": "1.64.1", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^1.0.0", diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 33358dd59fc2..fb4e2a5c2075 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -118,7 +118,7 @@ "@neondatabase/serverless": "1.2.0", "@opentelemetry/instrumentation-http": "0.220.0", "@sentry-internal/rollup-utils": "11.9.0", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "@sentry-internal/test-utils": "11.9.0", "@sentry/node-native": "11.9.0", "@types/amqplib": "^0.10.5", diff --git a/packages/angular/package.json b/packages/angular/package.json index eeb70cb12cf1..dd9cedba286d 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -23,7 +23,7 @@ "dependencies": { "@sentry/browser": "11.9.0", "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "tslib": "^2.4.1" }, "devDependencies": { diff --git a/packages/astro/package.json b/packages/astro/package.json index bf1ef9a81cad..f6111dc2aa5e 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -53,7 +53,7 @@ "dependencies": { "@sentry/browser": "11.9.0", "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/node": "11.9.0", "@sentry/server-runtime-injection": "11.9.0", "@sentry/server-utils": "11.9.0", diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index ee96999c0236..35fd2cc288ad 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -66,7 +66,7 @@ "access": "public" }, "dependencies": { - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0", "@sentry/server-runtime-injection": "11.9.0", diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json index bad56e53adf6..eee3838458f2 100644 --- a/packages/browser-utils/package.json +++ b/packages/browser-utils/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "web-vitals": "^6.2.3" }, "devDependencies": { diff --git a/packages/browser/package.json b/packages/browser/package.json index 786778dc9ae0..050cb70e27ed 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -39,7 +39,7 @@ }, "dependencies": { "@sentry/browser-utils": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/feedback": "11.9.0", "@sentry/replay": "11.9.0", "@sentry/replay-canvas": "11.9.0", diff --git a/packages/bun/package.json b/packages/bun/package.json index 11d630c9eb77..8e6ae1f0d2b2 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -45,7 +45,7 @@ "dependencies": { "@sentry/bundler-plugins": "11.9.0", "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/node": "11.9.0", "@sentry/server-utils": "11.9.0" }, diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 579acff8f018..bc725a0198df 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -53,7 +53,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.1", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/opentelemetry": "11.9.0", "@sentry/server-utils": "11.9.0", diff --git a/packages/core/package.json b/packages/core/package.json index cefeae6d9a5f..610a479a9e0e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -92,6 +92,6 @@ "zod": "^4.6.7" }, "dependencies": { - "@sentry/conventions": "^0.29.0" + "@sentry/conventions": "^0.30.0" } } diff --git a/packages/deno/package.json b/packages/deno/package.json index 80192978281f..07f1c597210a 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -28,7 +28,7 @@ ], "dependencies": { "@opentelemetry/api": "^1.9.1", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/server-runtime-injection": "11.9.0", "@sentry/server-utils": "11.9.0" diff --git a/packages/effect/package.json b/packages/effect/package.json index 18c630ac3927..3263afd87819 100644 --- a/packages/effect/package.json +++ b/packages/effect/package.json @@ -45,7 +45,7 @@ }, "dependencies": { "@sentry/browser": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0" }, diff --git a/packages/elysia/package.json b/packages/elysia/package.json index 4a8d96ed5226..60aeba82d343 100644 --- a/packages/elysia/package.json +++ b/packages/elysia/package.json @@ -34,7 +34,7 @@ "dependencies": { "@sentry/bun": "11.9.0", "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0" + "@sentry/conventions": "^0.30.0" }, "peerDependencies": { "elysia": "^1.4.0" diff --git a/packages/ember/package.json b/packages/ember/package.json index a7be16636789..456d590218ce 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -45,7 +45,7 @@ "@embroider/addon-shim": "^1.10.2", "@embroider/macros": "^1.20.2", "@sentry/browser": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "decorator-transforms": "^2.3.2" }, diff --git a/packages/google-cloud-serverless/package.json b/packages/google-cloud-serverless/package.json index 48fc86974e89..3c993a16495c 100644 --- a/packages/google-cloud-serverless/package.json +++ b/packages/google-cloud-serverless/package.json @@ -36,7 +36,7 @@ "access": "public" }, "dependencies": { - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0", "@sentry/server-runtime-injection": "11.9.0", diff --git a/packages/hono/package.json b/packages/hono/package.json index 13efe1db2f62..0bc0e11d7cfd 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -74,7 +74,7 @@ "dependencies": { "@opentelemetry/api": "^1.9.1", "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/server-utils": "11.9.0" }, "peerDependencies": { diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 7ed973221f41..37d41ed61f44 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -54,7 +54,7 @@ "access": "public" }, "dependencies": { - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0", "@sentry/server-utils": "11.9.0" diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index df4689b0b218..3ffcde165c1f 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -87,7 +87,7 @@ "@sentry/browser-utils": "11.9.0", "@sentry/bundler-plugins": "11.9.0", "@sentry/cloudflare": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0", "@sentry/opentelemetry": "11.9.0", diff --git a/packages/nitro/package.json b/packages/nitro/package.json index 7870033a75cb..debf94113cde 100644 --- a/packages/nitro/package.json +++ b/packages/nitro/package.json @@ -36,7 +36,7 @@ }, "dependencies": { "@sentry/bundler-plugins": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0", "@sentry/server-runtime-injection": "11.9.0", diff --git a/packages/node/package.json b/packages/node/package.json index 35ad65e7cb29..53e031a730b6 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -78,7 +78,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.1", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/opentelemetry": "11.9.0", "@sentry/server-runtime-injection": "11.9.0", diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index a270d2044bc3..c7b12da1ad78 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -56,7 +56,7 @@ "@nuxt/kit": "^4.6.0", "@sentry/browser": "11.9.0", "@sentry/cloudflare": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0", "@sentry/bundler-plugins": "11.9.0", diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index e9fb730e6f1e..bd0c6cbc5d04 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -27,7 +27,7 @@ "access": "public" }, "dependencies": { - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0" }, "peerDependencies": { diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 097e965abe48..657fe794c837 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -57,7 +57,7 @@ "dependencies": { "@opentelemetry/api": "^1.9.1", "@sentry/browser": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0", "@sentry/react": "11.9.0", diff --git a/packages/react/package.json b/packages/react/package.json index 9ce7f6289594..197f37323307 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -59,7 +59,7 @@ "dependencies": { "@sentry/browser": "11.9.0", "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0" + "@sentry/conventions": "^0.30.0" }, "peerDependencies": { "react": "17.x || 18.x || 19.x", diff --git a/packages/remix/package.json b/packages/remix/package.json index 9ba20c92c624..0bfc05081f82 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -78,7 +78,7 @@ "@remix-run/router": "^1.23.4", "@sentry/browser": "11.9.0", "@sentry/bundler-plugins": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0", "@sentry/react": "11.9.0", diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index b5f133420845..a07da40c610d 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -74,7 +74,7 @@ "@babel/core": "^7.29.6", "@sentry-internal/replay-worker": "11.9.0", "@sentry-internal/rollup-utils": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/rrweb": "2.45.0", "@sentry/rrweb-snapshot": "2.45.0", "fflate": "0.8.3", diff --git a/packages/server-utils/package.json b/packages/server-utils/package.json index 5ac7402840b8..d12ca1b31756 100644 --- a/packages/server-utils/package.json +++ b/packages/server-utils/package.json @@ -97,7 +97,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.1", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0" }, "devDependencies": { diff --git a/packages/solid/package.json b/packages/solid/package.json index 65642c9199bf..c563e7c1e1c5 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -56,7 +56,7 @@ "dependencies": { "@sentry/browser": "11.9.0", "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0" + "@sentry/conventions": "^0.30.0" }, "peerDependencies": { "@solidjs/router": ">=0.13.4 <2.0.0-0", diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index bd3bf459cb15..65e8bdca018d 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -87,7 +87,7 @@ "@sentry/server-utils": "11.9.0", "@sentry/solid": "11.9.0", "@sentry/bundler-plugins": "11.9.0", - "@sentry/conventions": "0.29.0", + "@sentry/conventions": "0.30.0", "h3": "^1.15.10" }, "devDependencies": { diff --git a/packages/svelte/package.json b/packages/svelte/package.json index f9f59c288044..b09e17909c9f 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@sentry/browser": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "magic-string": "~0.30.0" }, diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index f5bf6beae848..d54565d6293b 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -73,7 +73,7 @@ "@opentelemetry/api": "^1.9.1", "@sentry/cloudflare": "11.9.0", "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/node": "11.9.0", "@sentry/server-runtime-injection": "11.9.0", "@sentry/server-utils": "11.9.0", diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 7f8e27ab2ebe..32416e62acdb 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -53,7 +53,7 @@ "dependencies": { "@opentelemetry/api": "^1.9.1", "@sentry/browser-utils": "11.9.0", - "@sentry/conventions": "^0.29.0", + "@sentry/conventions": "^0.30.0", "@sentry/core": "11.9.0", "@sentry/node": "11.9.0", "@sentry/react": "11.9.0", diff --git a/packages/vue/package.json b/packages/vue/package.json index 260253be913d..44d591596bcc 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -46,7 +46,7 @@ "dependencies": { "@sentry/browser": "11.9.0", "@sentry/core": "11.9.0", - "@sentry/conventions": "^0.29.0" + "@sentry/conventions": "^0.30.0" }, "peerDependencies": { "@tanstack/vue-router": "^1.64.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1fb1417361a2..ada7519afbd4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -298,8 +298,8 @@ importers: specifier: 11.9.0 version: link:../../packages/browser '@sentry/conventions': - specifier: 0.29.0 - version: 0.29.0 + specifier: 0.30.0 + version: 0.30.0 '@sentry/opentelemetry': specifier: 11.9.0 version: link:../../packages/opentelemetry @@ -510,8 +510,8 @@ importers: specifier: 11.9.0 version: link:../test-utils '@sentry/conventions': - specifier: 0.29.0 - version: 0.29.0 + specifier: 0.30.0 + version: 0.30.0 eslint-plugin-regexp: specifier: ^3.1.0 version: 3.1.0 @@ -547,8 +547,8 @@ importers: specifier: 11.9.0 version: link:../node-integration-tests '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 vitest: specifier: ^4.1.11 version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) @@ -852,8 +852,8 @@ importers: specifier: 11.9.0 version: link:../test-utils '@sentry/conventions': - specifier: 0.29.0 - version: 0.29.0 + specifier: 0.30.0 + version: 0.30.0 '@sentry/node-native': specifier: 11.9.0 version: link:../../packages/node-native @@ -974,8 +974,8 @@ importers: specifier: 11.9.0 version: link:../browser '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1035,8 +1035,8 @@ importers: specifier: 11.9.0 version: link:../bundler-plugins '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1063,8 +1063,8 @@ importers: packages/aws-serverless: dependencies: '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1097,8 +1097,8 @@ importers: specifier: 11.9.0 version: link:../browser-utils '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1125,8 +1125,8 @@ importers: packages/browser-utils: dependencies: '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1144,8 +1144,8 @@ importers: specifier: 11.9.0 version: link:../bundler-plugins '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1227,8 +1227,8 @@ importers: specifier: ^1.9.1 version: 1.10.0 '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1264,8 +1264,8 @@ importers: packages/core: dependencies: '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 devDependencies: '@sentry-internal/rollup-utils': specifier: 11.9.0 @@ -1280,8 +1280,8 @@ importers: specifier: ^1.9.1 version: 1.10.0 '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1302,8 +1302,8 @@ importers: specifier: 11.9.0 version: link:../browser '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1327,8 +1327,8 @@ importers: specifier: 11.9.0 version: link:../bun '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1355,8 +1355,8 @@ importers: specifier: 11.9.0 version: link:../browser '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1504,8 +1504,8 @@ importers: packages/google-cloud-serverless: dependencies: '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1544,8 +1544,8 @@ importers: specifier: ^1.9.1 version: 1.10.0 '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1597,8 +1597,8 @@ importers: packages/nestjs: dependencies: '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1643,8 +1643,8 @@ importers: specifier: 11.9.0 version: link:../cloudflare '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1695,8 +1695,8 @@ importers: specifier: 11.9.0 version: link:../bundler-plugins '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1735,8 +1735,8 @@ importers: specifier: 11.9.0 version: link:../bundler-plugins '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1791,8 +1791,8 @@ importers: specifier: 11.9.0 version: link:../cloudflare '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1852,8 +1852,8 @@ importers: packages/opentelemetry: dependencies: '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1890,8 +1890,8 @@ importers: specifier: 11.9.0 version: link:../browser '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -1966,8 +1966,8 @@ importers: specifier: 11.9.0 version: link:../bundler-plugins '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -2030,8 +2030,8 @@ importers: specifier: 11.9.0 version: link:../bundler-plugins '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -2126,8 +2126,8 @@ importers: specifier: 11.9.0 version: link:../../dev-packages/rollup-utils '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/rrweb': specifier: 2.45.0 version: 2.45.0 @@ -2197,8 +2197,8 @@ importers: specifier: ^1.9.1 version: 1.10.0 '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -2231,8 +2231,8 @@ importers: specifier: 11.9.0 version: link:../browser '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -2274,8 +2274,8 @@ importers: specifier: 11.9.0 version: link:../bundler-plugins '@sentry/conventions': - specifier: 0.29.0 - version: 0.29.0 + specifier: 0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -2347,8 +2347,8 @@ importers: specifier: 11.9.0 version: link:../browser '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -2384,8 +2384,8 @@ importers: specifier: 11.9.0 version: link:../cloudflare '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -2448,8 +2448,8 @@ importers: specifier: 11.9.0 version: link:../bundler-plugins '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -2516,8 +2516,8 @@ importers: specifier: 11.9.0 version: link:../browser '@sentry/conventions': - specifier: ^0.29.0 - version: 0.29.0 + specifier: ^0.30.0 + version: 0.30.0 '@sentry/core': specifier: 11.9.0 version: link:../core @@ -8178,8 +8178,8 @@ packages: resolution: {integrity: sha512-MLxTpTU3E8QACQ/5c0sENMR2gRiMXpGaKeD5IHY+3wyU2fUSJVB0QPU/l1WhoyZbX8N9ospBgf5UEG7taVF9rg==} engines: {node: ^14.15.0 || >=16.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@sentry/conventions@0.29.0': - resolution: {integrity: sha512-kYq9/HJ/l6MYP/srIc20dy1W4mWyJfCF0OWFF+RhBGZwyr/wCmtabRFKyw7kqM+DTwZXQOjSTSZUcJhDowcInQ==} + '@sentry/conventions@0.30.0': + resolution: {integrity: sha512-WTumd5A2iFmHTRubi6mc6gLHU8LswPQqoNaZu6T1cxiTUmy3WdEw0i8FAnVbcgxTlsumX5oh63/b4B9lY5wAPQ==} engines: {node: '>=14'} '@sentry/node-cpu-profiler@2.4.4': @@ -26135,7 +26135,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@sentry/conventions@0.29.0': {} + '@sentry/conventions@0.30.0': {} '@sentry/node-cpu-profiler@2.4.4': dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6a1a161a3dd0..4a08da54755f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -116,7 +116,7 @@ allowBuilds: workerd: true minimumReleaseAgeExclude: - '@opentelemetry/api@1.10.0' - - '@sentry/conventions@0.29.0' + - '@sentry/conventions@0.30.0' - '@sentry/rrdom@2.45.0' - '@sentry/rrweb-snapshot@2.45.0' - '@sentry/rrweb-types@2.45.0' From b382e301e24b3d905ecb208dc96bd233bce2a3e9 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 28 Oct 2026 17:03:22 +0100 Subject: [PATCH 002/169] fix(server-utils): Require a parent span for Tedious queries (#25822) **Why:** Skip automatic Tedious DB spans outside an instrumented operation because they lack the application context needed to identify their origin, aligning with other DB integrations. **How to review:** Fix is to early return in case there is no active span. The rest of the PR is just adding regression coverage. Closes #25797 --------- Co-authored-by: GPT-6 --- .../tracing/tedious/scenario-no-parent.mjs | 51 +++++++++++++++++++ .../suites/tracing/tedious/test.ts | 39 ++++++++++++++ .../server-utils/src/integrations/tedious.ts | 5 ++ 3 files changed, 95 insertions(+) create mode 100644 dev-packages/node-integration-tests/suites/tracing/tedious/scenario-no-parent.mjs diff --git a/dev-packages/node-integration-tests/suites/tracing/tedious/scenario-no-parent.mjs b/dev-packages/node-integration-tests/suites/tracing/tedious/scenario-no-parent.mjs new file mode 100644 index 000000000000..a9ec1c51be16 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/tedious/scenario-no-parent.mjs @@ -0,0 +1,51 @@ +import * as Sentry from '@sentry/node'; +import { waitForConnection } from '@sentry-internal/node-integration-tests'; +import { Connection, Request } from 'tedious'; + +function connect() { + return new Promise((resolve, reject) => { + const connection = new Connection({ + server: '127.0.0.1', + authentication: { + type: 'default', + options: { userName: 'sa', password: 'TESTing123' }, + }, + options: { port: 1433, encrypt: false }, + }); + + connection.on('connect', err => { + if (err) { + connection.close(); + reject(err); + } else { + resolve(connection); + } + }); + connection.connect(); + }); +} + +function query(connection, sql) { + return new Promise((resolve, reject) => { + connection.execSql(new Request(sql, err => (err ? reject(err) : resolve()))); + }); +} + +async function run() { + const connection = await waitForConnection(connect); + + await query(connection, 'SELECT 1 AS before_parent'); + + const parent = Sentry.startInactiveSpan({ name: 'parent operation' }); + await Sentry.withActiveSpan(parent, () => query(connection, 'SELECT 2 AS inside_parent')); + + await query(connection, 'SELECT 3 AS after_parent'); + connection.close(); + + // Flush every query before the parent signals that span collection is complete. + await Sentry.flush(); + parent.end(); + await Sentry.flush(); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts b/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts index 31dd20a0c712..e6cd9de30da0 100644 --- a/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts @@ -1,3 +1,4 @@ +import type { SerializedStreamedSpan } from '@sentry/core'; import { DB_NAMESPACE, DB_QUERY_SUMMARY, @@ -122,6 +123,44 @@ describeWithDockerCompose('tedious auto instrumentation', { workingDirectory: [_ }); }); + createEsmAndCjsTests(__dirname, 'scenario-no-parent.mjs', 'instrument.mjs', (createTestRunner, test) => { + test('only instruments queries with an active parent span', async () => { + const runner = createTestRunner(); + const dbSpans = new Map(); + const spansPromise = runner.collectStreamedSpans(spans => { + for (const span of spans) { + if (span.attributes[SENTRY_ORIGIN]?.value === ORIGIN) { + dbSpans.set(span.span_id, span); + } + } + return spans.some(span => span.is_segment && span.name === 'parent operation'); + }); + + await runner.unordered().start().completed(); + + const spans = await spansPromise; + const parent = spans.find(span => span.is_segment && span.name === 'parent operation'); + expect(parent).toBeDefined(); + expect( + Array.from(dbSpans.values(), span => ({ + query: span.attributes[DB_QUERY_TEXT]?.value, + database: span.attributes[DB_NAMESPACE]?.value, + parentSpanId: span.parent_span_id, + traceId: span.trace_id, + isSegment: span.is_segment, + })), + ).toEqual([ + { + query: 'SELECT ? AS inside_parent', + database: 'master', + parentSpanId: parent?.span_id, + traceId: parent?.trace_id, + isSegment: false, + }, + ]); + }); + }); + // tedious 20 requires Node >= 22. describe.runIf(supports({ min: 22 }))('tedious v20', () => { createEsmAndCjsTests( diff --git a/packages/server-utils/src/integrations/tedious.ts b/packages/server-utils/src/integrations/tedious.ts index 0878dc5e3118..42c3f1654c06 100644 --- a/packages/server-utils/src/integrations/tedious.ts +++ b/packages/server-utils/src/integrations/tedious.ts @@ -7,6 +7,7 @@ import * as diagnosticsChannel from '../utils/diagnosticsChannel'; import type { IntegrationFn, SpanAttributes } from '@sentry/core'; import { defineIntegration, + getActiveSpan, getClient, hasSpanStreamingEnabled, SPAN_STATUS_ERROR, @@ -110,6 +111,10 @@ function subscribeConnect(): void { function subscribeQuery(channelName: string, operation: string): void { diagnosticsChannel.tracingChannel(channelName).start.subscribe(message => { + if (!getActiveSpan()) { + return; + } + const data = message as TediousQueryChannelContext; const connection = data.self; const request = data.arguments[0]; From a33cba8fb05ef0ad2cc0a403aaeb1752005a5ece Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 12:47:48 -0400 Subject: [PATCH 003/169] ref(core): Deprecate the released `handleTunnelRequest` form and reword the `serveTunnelIntegration` docs (#25844) Lukas pointed out after #25783 merged that the `serveTunnelIntegration` docs did not say when to add it and that the released `handleTunnelRequest({ request })` form was not marked deprecated, fixed by rewording the JSDoc and tagging that overload `@deprecated`, plus an oxlint allow entry since `no-deprecated` otherwise flags every SDK re-export of it. --- .oxlintrc.base.json | 6 +++++- packages/core/src/integrations/serveTunnel.ts | 20 ++++++++++++++----- packages/core/src/utils/tunnelForward.ts | 4 +++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.oxlintrc.base.json b/.oxlintrc.base.json index 41be2844716d..3c42d69b4bb4 100644 --- a/.oxlintrc.base.json +++ b/.oxlintrc.base.json @@ -61,10 +61,14 @@ "typescript/no-duplicate-type-constituents": "off", // @types/node 20 still marks `channel.subscribe()` and `channel.unsubscribe()` as deprecated, // but Node revoked this deprecation (DEP0163) in v22.20.0 and v24.8.0. + // `handleTunnelRequest` has only one deprecated overload, but the rule flags every re-export of it. "typescript/no-deprecated": [ "error", { - "allow": [{ "from": "package", "package": "diagnostics_channel", "name": ["subscribe", "unsubscribe"] }] + "allow": [ + { "from": "package", "package": "diagnostics_channel", "name": ["subscribe", "unsubscribe"] }, + "handleTunnelRequest" + ] } ] }, diff --git a/packages/core/src/integrations/serveTunnel.ts b/packages/core/src/integrations/serveTunnel.ts index 9ee2d81334c1..3994b35a252d 100644 --- a/packages/core/src/integrations/serveTunnel.ts +++ b/packages/core/src/integrations/serveTunnel.ts @@ -16,19 +16,29 @@ const _serveTunnelIntegration = ((options: ServerTunnelOptions = {}) => { }) satisfies IntegrationFn; /** - * Serves the managed tunnel, so a browser `init` with `tunnel: true` sends its envelopes through the app's own origin - * instead of the Sentry ingest URL, where ad-blockers can't drop them. The meta-framework SDKs answer the tunnel - * requests from their request hooks. + * Lets your server forward the events of a browser SDK set up with `tunnel: true` to Sentry. * - * By default the tunnel path is derived from the DSN, under the app's base path, and only envelopes for the server's - * own DSN are forwarded. A `path` is served as written. + * With this integration on the server and `tunnel: true` in the browser `init`, the browser sends its events to your + * own server instead of Sentry, where ad-blockers can't drop them, and your server forwards them to Sentry. + * + * Meta-framework SDKs (e.g. Next.js, Nuxt or SvelteKit) answer the tunnel requests for you. On a standalone server + * (e.g. Express or Fastify), also call `handleTunnelRequestIfMatched` in your request handling. + * + * By default only envelopes for the server's own DSN are forwarded. * * @example * ```js + * // Server * Sentry.init({ * dsn: '__DSN__', * integrations: [Sentry.serveTunnelIntegration()], * }); + * + * // Browser + * Sentry.init({ + * dsn: '__DSN__', + * tunnel: true, + * }); * ``` */ export const serveTunnelIntegration = defineIntegration(_serveTunnelIntegration); diff --git a/packages/core/src/utils/tunnelForward.ts b/packages/core/src/utils/tunnelForward.ts index e30d7e21bfe4..01f2af0947bd 100644 --- a/packages/core/src/utils/tunnelForward.ts +++ b/packages/core/src/utils/tunnelForward.ts @@ -62,7 +62,9 @@ export function handleTunnelRequest( options?: TunnelForwardOptions, ): Promise; /** - * The released form, kept until v12. Its `request` option is deprecated: pass the request as the first argument. + * The released form, kept until v12. + * + * @deprecated Pass the request as the first argument instead: `handleTunnelRequest(request, options)`. */ export function handleTunnelRequest(options: HandleTunnelRequestOptions): Promise; export function handleTunnelRequest( From de10e521332b93b27669164e75a756726b5ab807 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 28 Oct 2026 13:45:31 -0400 Subject: [PATCH 004/169] fix(replay): Skip buffered replays for captureMessage stacks from the attachStacktrace default (#25846) Defaulting `attachStacktrace` to true gave `captureMessage` events a synthetic exception so they started flushing buffered replays, fixed by flagging those events in core and skipping them in Replay's buffer sampling. I think a decision to be made later to get rid of this later for when both APIs merge is: **should message level captures trigger a replay or not.** --- .../replay/errors/captureMessage/test.ts | 33 ++++++++++++++++ .../captureMessageAttachStacktrace/init.js | 18 +++++++++ .../captureMessageAttachStacktrace/test.ts | 36 +++++++++++++++++ packages/core/src/client.ts | 13 ++++++- packages/core/src/scope.ts | 5 +++ packages/core/test/lib/client.test.ts | 39 +++++++++++++++++++ .../util/shouldSampleForBufferEvent.ts | 4 ++ .../coreHandlers/handleGlobalEvent.test.ts | 12 ++++++ 8 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 dev-packages/browser-integration-tests/suites/replay/errors/captureMessage/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/replay/errors/captureMessageAttachStacktrace/init.js create mode 100644 dev-packages/browser-integration-tests/suites/replay/errors/captureMessageAttachStacktrace/test.ts diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/captureMessage/test.ts b/dev-packages/browser-integration-tests/suites/replay/errors/captureMessage/test.ts new file mode 100644 index 000000000000..90b867c117e3 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/errors/captureMessage/test.ts @@ -0,0 +1,33 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, waitForErrorRequest } from '../../../../utils/helpers'; +import { getReplaySnapshot, shouldSkipReplayTest, waitForReplayRunning } from '../../../../utils/replayHelpers'; + +sentryTest( + '[error-mode] should not flush for captureMessage when attachStacktrace is left at its default', + async ({ getLocalTestUrl, page, forceFlushReplay }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + await waitForReplayRunning(page); + + const errorRequestPromise = waitForErrorRequest(page); + await page.evaluate(() => (window as any).Sentry.captureMessage('Some message')); + const event = envelopeRequestParser(await errorRequestPromise); + await forceFlushReplay(); + + expect(event.exception?.values?.[0]?.stacktrace?.frames?.length).toBeGreaterThan(0); + expect(event.tags?.replayId).toBeUndefined(); + expect(await getReplaySnapshot(page)).toEqual( + expect.objectContaining({ + _isEnabled: true, + _isPaused: false, + recordingMode: 'buffer', + }), + ); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/captureMessageAttachStacktrace/init.js b/dev-packages/browser-integration-tests/suites/replay/errors/captureMessageAttachStacktrace/init.js new file mode 100644 index 000000000000..76d3c2afd889 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/errors/captureMessageAttachStacktrace/init.js @@ -0,0 +1,18 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = Sentry.replayIntegration({ + flushMinDelay: 200, + flushMaxDelay: 200, + minReplayDuration: 0, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 1, + replaysSessionSampleRate: 0.0, + replaysOnErrorSampleRate: 1.0, + attachStacktrace: true, + + integrations: [window.Replay], +}); diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/captureMessageAttachStacktrace/test.ts b/dev-packages/browser-integration-tests/suites/replay/errors/captureMessageAttachStacktrace/test.ts new file mode 100644 index 000000000000..d89371695436 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/errors/captureMessageAttachStacktrace/test.ts @@ -0,0 +1,36 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, waitForErrorRequest } from '../../../../utils/helpers'; +import { + getReplaySnapshot, + shouldSkipReplayTest, + waitForReplayRequest, + waitForReplayRunning, +} from '../../../../utils/replayHelpers'; + +sentryTest( + '[error-mode] should flush for captureMessage when attachStacktrace is explicitly enabled', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + await waitForReplayRunning(page); + + const errorRequestPromise = waitForErrorRequest(page); + const replayRequestPromise = waitForReplayRequest(page, 0); + await page.evaluate(() => (window as any).Sentry.captureMessage('Some message')); + const event = envelopeRequestParser(await errorRequestPromise); + await replayRequestPromise; + + expect(event.tags?.replayId).toEqual(expect.any(String)); + expect(await getReplaySnapshot(page)).toEqual( + expect.objectContaining({ + recordingMode: 'session', + }), + ); + }, +); diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 761a2519d6d3..54f0c2dae0d2 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -250,6 +250,9 @@ export abstract class Client { */ protected _unhandledSessionStatus: SessionStatus; + /** Whether `attachStacktrace` came from the SDK default rather than the user's options. */ + protected readonly _attachStacktraceIsDefault: boolean; + /** * Initializes this client instance. * @@ -271,6 +274,7 @@ export abstract class Client { this._promiseBuffer = makePromiseBuffer(options.transportOptions?.bufferSize ?? DEFAULT_TRANSPORT_BUFFER_SIZE); this._dataCollection = resolveDataCollectionOptions(options); this._unhandledSessionStatus = 'crashed'; + this._attachStacktraceIsDefault = options.attachStacktrace === undefined; if (options.dsn) { this._dsn = makeDsn(options.dsn); @@ -372,7 +376,14 @@ export abstract class Client { const eventMessage = isParameterizedString(message) ? message : String(message); const isMessage = isPrimitive(message); const promisedEvent = isMessage - ? this.eventFromMessage(eventMessage, level, hintWithEventId) + ? this.eventFromMessage(eventMessage, level, hintWithEventId).then(event => { + // Lets Replay keep treating messages as non-errors unless the user opted into stack traces + if (event.exception && this._attachStacktraceIsDefault) { + event.sdkProcessingMetadata = { ...event.sdkProcessingMetadata, hasDefaultSyntheticStacktrace: true }; + } + + return event; + }) : this.eventFromException(message, hintWithEventId); this._process( diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 1a2a1761854a..7130bc5eb0d2 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -67,6 +67,11 @@ export interface SdkProcessingMetadata { */ spanCountBeforeProcessing?: number; ipAddress?: string; + /** + * Set on `captureMessage` events whose stack trace exists only because `attachStacktrace` + * defaults to `true`, so Replay does not treat them as errors in buffer mode. + */ + hasDefaultSyntheticStacktrace?: boolean; } /** diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index fce65f946b89..0d3117f7ac4a 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -9,6 +9,7 @@ import { lastEventId, linkedErrorsIntegration, makeSession, + resolvedSyncPromise, Scope, setCurrentClient, SyncPromise, @@ -664,6 +665,44 @@ describe('Client', () => { ); }); + test.each([ + ['left at its default', {}, expect.objectContaining({ hasDefaultSyntheticStacktrace: true })], + [ + 'explicitly enabled', + { attachStacktrace: true }, + expect.not.objectContaining({ hasDefaultSyntheticStacktrace: true }), + ], + ])('marks message stack traces only when attachStacktrace is %s', (_, attachStacktraceOptions, metadataMatcher) => { + const client = new TestClient(getDefaultTestClientOptions({ dsn: PUBLIC_DSN, ...attachStacktraceOptions })); + vi.spyOn(client, 'eventFromMessage').mockReturnValue( + resolvedSyncPromise({ message: 'test message', exception: { values: [{ value: 'test message' }] } }), + ); + const processor = vi.fn((event: Event) => event); + client.addEventProcessor(processor); + + client.captureMessage('test message'); + + expect(processor).toHaveBeenCalledWith( + expect.objectContaining({ sdkProcessingMetadata: metadataMatcher }), + expect.anything(), + ); + }); + + test('does not mark message events without a stack trace', () => { + const client = new TestClient(getDefaultTestClientOptions({ dsn: PUBLIC_DSN })); + const processor = vi.fn((event: Event) => event); + client.addEventProcessor(processor); + + client.captureMessage('test message'); + + expect(processor).toHaveBeenCalledWith( + expect.objectContaining({ + sdkProcessingMetadata: expect.not.objectContaining({ hasDefaultSyntheticStacktrace: true }), + }), + expect.anything(), + ); + }); + test('sets the correct lastEventId', () => { const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN }); const client = new TestClient(options); diff --git a/packages/replay-internal/src/coreHandlers/util/shouldSampleForBufferEvent.ts b/packages/replay-internal/src/coreHandlers/util/shouldSampleForBufferEvent.ts index 3a7a8f423dba..c73303f794c7 100644 --- a/packages/replay-internal/src/coreHandlers/util/shouldSampleForBufferEvent.ts +++ b/packages/replay-internal/src/coreHandlers/util/shouldSampleForBufferEvent.ts @@ -24,5 +24,9 @@ export function shouldSampleForBufferEvent(replay: ReplayContainer, event: Event return false; } + if (event.sdkProcessingMetadata?.hasDefaultSyntheticStacktrace) { + return false; + } + return isSampled(replay.getOptions().errorSampleRate); } diff --git a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts index 6c7230e2c680..95df766bb69f 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts @@ -209,6 +209,18 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(replay.recordingMode).toBe('buffer'); }); + it('does not sample events whose stack trace only comes from the attachStacktrace default in buffer mode', () => { + const messageEvent: Event = { + message: 'Some message', + exception: { values: [{ value: 'Some message', stacktrace: { frames: [{ filename: 'app.js' }] } }] }, + sdkProcessingMetadata: { hasDefaultSyntheticStacktrace: true }, + }; + + expect(replay.recordingMode).toBe('buffer'); + expect(handleGlobalEventListener(replay)(messageEvent, {})?.tags?.replayId).toBeUndefined(); + expect(replay.session?.dirty).toBe(false); + }); + it('does not skip non-rrweb errors', () => { const errorEvent: Event = { exception: { From 2b1607d59fd2478ff0d5ee6bdb1f98ce04155fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Wed, 28 Oct 2026 15:40:59 -0600 Subject: [PATCH 005/169] fix(server-utils): Read the pi-durable session id in every phase outside a run (#25838) follow up to #25792 This makes every pi-durable phase outside a run read the stored session id, not only an idle compaction. ## Why An extension task that calls the model while its conversation has no run in progress kept the `:` id. The runs of the same conversation report the session id, so Sentry showed the task as a separate conversation. Co-authored-by: Claude Opus 5.5 --- .../server-utils/src/ai/pi-durable/index.ts | 4 +-- .../test/ai/lib/tracing/pi-durable.test.ts | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/server-utils/src/ai/pi-durable/index.ts b/packages/server-utils/src/ai/pi-durable/index.ts index b7ee007e6a02..d2187aae394e 100644 --- a/packages/server-utils/src/ai/pi-durable/index.ts +++ b/packages/server-utils/src/ai/pi-durable/index.ts @@ -220,9 +220,9 @@ function runPhase( const runInScope = async (scope: Scope): Promise => { // Read at the first phase of a run, which is a `pi.tool` phase when a restarted Harness reruns a - // tool call. + // tool call, and at each phase outside a run, such as an idle compaction or a task of an extension. let sessionId = run?.sessionId; - if (!sessionId && (run || kind === PI_TASK.COMPACTION)) { + if (!sessionId) { sessionId = await readProviderSessionId(runtime, context); if (run && sessionId) { run.sessionId = sessionId; diff --git a/packages/server-utils/test/ai/lib/tracing/pi-durable.test.ts b/packages/server-utils/test/ai/lib/tracing/pi-durable.test.ts index 5c35581138cf..e7bd701485cf 100644 --- a/packages/server-utils/test/ai/lib/tracing/pi-durable.test.ts +++ b/packages/server-utils/test/ai/lib/tracing/pi-durable.test.ts @@ -1049,6 +1049,35 @@ describe('instrumentPiDurableHarnessOptions', () => { ); }); + it('uses the provider session id for a task of an extension on an idle conversation', async () => { + const sessionId = '019a2b3c-4d5e-7f60-8172-93a4b5c6d7e8'; + const task: PiTask = { + definition: { + name: 'app.title', + phases: { + request: async () => { + await harness.models!.completeSimple!(MODEL, { messages: [] }); + }, + }, + }, + }; + const harness: PiHarnessOptions = instrumentPiDurableHarnessOptions({ + models: { completeSimple: async () => ANSWER }, + registry: { snapshot: () => ({ task: () => task, tasks: () => [task] }), subscribe: () => () => undefined }, + }); + const runtime: PiTaskRuntime = { + taskId: 7, + conversationId: 1, + agent: async () => ({}), + commit: async () => undefined, + snapshot: async () => ({ sessionId }), + }; + + await harness.registry!.snapshot().task('app.title')!.definition.phases.request!(undefined, runtime, undefined); + + expect(spanToJSON(endedSpans[0]!).attributes[GEN_AI_CONVERSATION_ID]).toBe(sessionId); + }); + it('ends the open runs when the Harness closes', async () => { const harness = harnessFor(generationPhase([{ run: { inputs: [5] } }])); let onClose: (() => void) | undefined; From b722d3cfde2f4484ff8928bdd166f7afc3cadb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Wed, 28 Oct 2026 15:46:23 -0600 Subject: [PATCH 006/169] feat(node): Add BullMQ integration (#21777) closes #12956 closes [JS-89](https://linear.app/getsentry/issue/JS-89/insights-auto-instrument-bullmq-for-queue-insights) ## Description This adds telemetry for [BullMQ](https://bullmq.io/). It only piggybacks on the `telemetry` option. It has metrics, tracing with span links between producer and consumer It can be used as following: ```js import * as Sentry from '@sentry/node'; import { Queue, Worker } from 'bullmq'; const telemetry = new Sentry.BullMQTelemetry(); const queue = new Queue('myQueue', { connection: { host: '127.0.0.1', port: 6379 }, telemetry, }); ``` It is the same usage as the BullMQ OTel package: https://docs.bullmq.io/guide/telemetry/getting-started To make this an integration the types were vendored in and based on that the integration was done. ## OP decisions The consumer spans generate `queue.task` where the producer emits `queue.submit`, this should be reflecting our [span operations](https://develop.sentry.dev/sdk/telemetry/traces/span-operations/#messagesqueues). I didn't use `queue.task.bullmq`, like it is for Celery - but this can be changed if wanted. ## Sentry traces / metrics Example metrics: https://sentry-sdks.sentry.io/explore/metrics/?end=2026-10-05T07%3A25%3A00&metric=%7B%22metric%22%3A%7B%22name%22%3A%22bullmq.jobs.completed%22%2C%22type%22%3A%22counter%22%2C%22unit%22%3A%22none%22%7D%2C%22query%22%3A%22%22%2C%22aggregateFields%22%3A%5B%7B%22yAxes%22%3A%5B%22sum%28value%2Cbullmq.jobs.completed%2Ccounter%2Cnone%29%22%5D%7D%5D%2C%22aggregateSortBys%22%3A%5B%7B%22field%22%3A%22sum%28value%2Cbullmq.jobs.completed%2Ccounter%2Cnone%29%22%2C%22kind%22%3A%22desc%22%7D%5D%2C%22sortBys%22%3A%5B%7B%22field%22%3A%22timestamp%22%2C%22kind%22%3A%22desc%22%7D%5D%2C%22mode%22%3A%22samples%22%7D&project=4510555608449024&start=2026-10-05T07%3A15%3A00 Consumer trace (queue.task span with a link to the producer): https://sentry-sdks.sentry.io/explore/traces/trace/1a21906f0d8d4db7bbf80bf9cf7d32f3/?node=span-9b305b00fd106329&project=4510555608449024&source=traces&targetId=9b305b00fd106329×tamp=1791184722 Producer trace (GET /enqueue with a queue.submit span): https://sentry-sdks.sentry.io/explore/traces/trace/f135329fd15a440091a2ee445a8df4fc/?node=span-9db4d2c0668abe46&project=4510555608449024&source=traces&targetId=9db4d2c0668abe46×tamp=1791184722 --- .../node-bullmq/docker-compose.yml | 7 + .../node-bullmq/global-setup.mjs | 13 + .../node-bullmq/global-teardown.mjs | 13 + .../node-bullmq/package.json | 25 + .../node-bullmq/playwright.config.mjs | 11 + .../node-bullmq/pnpm-workspace.yaml | 3 + .../test-applications/node-bullmq/src/app.mjs | 49 ++ .../node-bullmq/src/instrument.mjs | 12 + .../node-bullmq/start-event-proxy.mjs | 6 + .../node-bullmq/tests/bullmq.test.ts | 229 +++++ .../node-integration-tests/package.json | 1 + .../suites/tracing/bullmq/docker-compose.yml | 12 + .../suites/tracing/bullmq/instrument.mjs | 12 + .../tracing/bullmq/scenario-attributes.mjs | 68 ++ .../tracing/bullmq/scenario-failed-job.mjs | 30 + .../suites/tracing/bullmq/scenario.mjs | 39 + .../suites/tracing/bullmq/test.ts | 781 ++++++++++++++++++ packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/index.ts | 1 + .../tracing/bullmq/contextManager.ts | 62 ++ .../src/integrations/tracing/bullmq/index.ts | 56 ++ .../src/integrations/tracing/bullmq/meter.ts | 121 +++ .../src/integrations/tracing/bullmq/span.ts | 205 +++++ .../src/integrations/tracing/bullmq/tracer.ts | 80 ++ .../src/integrations/tracing/bullmq/types.ts | 81 ++ .../test/integrations/tracing/bullmq.test.ts | 688 +++++++++++++++ pnpm-lock.yaml | 204 +++-- pnpm-workspace.yaml | 1 + 31 files changed, 2767 insertions(+), 47 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/docker-compose.yml create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/global-setup.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/global-teardown.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/package.json create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/src/app.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/src/instrument.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-bullmq/tests/bullmq.test.ts create mode 100644 dev-packages/node-integration-tests/suites/tracing/bullmq/docker-compose.yml create mode 100644 dev-packages/node-integration-tests/suites/tracing/bullmq/instrument.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/bullmq/scenario-attributes.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/bullmq/scenario-failed-job.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/bullmq/scenario.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/bullmq/test.ts create mode 100644 packages/node/src/integrations/tracing/bullmq/contextManager.ts create mode 100644 packages/node/src/integrations/tracing/bullmq/index.ts create mode 100644 packages/node/src/integrations/tracing/bullmq/meter.ts create mode 100644 packages/node/src/integrations/tracing/bullmq/span.ts create mode 100644 packages/node/src/integrations/tracing/bullmq/tracer.ts create mode 100644 packages/node/src/integrations/tracing/bullmq/types.ts create mode 100644 packages/node/test/integrations/tracing/bullmq.test.ts diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/docker-compose.yml b/dev-packages/e2e-tests/test-applications/node-bullmq/docker-compose.yml new file mode 100644 index 000000000000..15a423ffbc07 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/docker-compose.yml @@ -0,0 +1,7 @@ +services: + redis: + image: redis:8 + restart: always + container_name: e2e-tests-node-bullmq-redis + ports: + - '6379:6379' diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/global-setup.mjs b/dev-packages/e2e-tests/test-applications/node-bullmq/global-setup.mjs new file mode 100644 index 000000000000..438b88b61794 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/global-setup.mjs @@ -0,0 +1,13 @@ +import { execSync } from 'child_process'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default async function globalSetup() { + // Start Redis via Docker Compose + execSync('docker compose up -d --wait', { + cwd: __dirname, + stdio: 'inherit', + }); +} diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/global-teardown.mjs b/dev-packages/e2e-tests/test-applications/node-bullmq/global-teardown.mjs new file mode 100644 index 000000000000..35ce41179193 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/global-teardown.mjs @@ -0,0 +1,13 @@ +import { execSync } from 'child_process'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default async function globalTeardown() { + // Stop Redis and remove containers + execSync('docker compose down --volumes', { + cwd: __dirname, + stdio: 'inherit', + }); +} diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/package.json b/dev-packages/e2e-tests/test-applications/node-bullmq/package.json new file mode 100644 index 000000000000..b86c4b846925 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/package.json @@ -0,0 +1,25 @@ +{ + "name": "node-bullmq", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "start": "node src/app.mjs", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test": "playwright test", + "test:build": "pnpm install", + "test:assert": "pnpm test" + }, + "dependencies": { + "@sentry/node": "file:../../packed/sentry-node-packed.tgz", + "bullmq": "^5.0.0", + "express": "^4.21.0" + }, + "devDependencies": { + "@playwright/test": "1.64.1", + "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-bullmq/playwright.config.mjs new file mode 100644 index 000000000000..d5fd0b394f15 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/playwright.config.mjs @@ -0,0 +1,11 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +export default { + ...config, + globalSetup: './global-setup.mjs', + globalTeardown: './global-teardown.mjs', +}; diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-bullmq/pnpm-workspace.yaml new file mode 100644 index 000000000000..0f973fea7a88 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + msgpackr-extract: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/src/app.mjs b/dev-packages/e2e-tests/test-applications/node-bullmq/src/app.mjs new file mode 100644 index 000000000000..4b51a6f775f5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/src/app.mjs @@ -0,0 +1,49 @@ +import './instrument.mjs'; + +import * as Sentry from '@sentry/node'; +import { Queue, Worker } from 'bullmq'; +import express from 'express'; + +const app = express(); +const port = 3030; + +const connection = { host: '127.0.0.1', port: 6379 }; +const telemetry = new Sentry.BullMQTelemetry({ enableMetrics: true }); + +const testQueue = new Queue('test-queue', { connection, telemetry }); + +const worker = new Worker( + 'test-queue', + async job => { + if (job.name === 'fail-job') { + throw new Error('Test error from BullMQ processor'); + } + + if (job.name === 'breadcrumb-job') { + Sentry.addBreadcrumb({ message: 'breadcrumb-from-bullmq-processor' }); + } + + return { success: true }; + }, + { connection, telemetry }, +); + +worker.on('error', err => { + console.error('Worker error:', err); +}); + +app.get('/enqueue/:jobName', async (req, res) => { + await testQueue.add(req.params.jobName, { data: 'test' }); + res.send('Job enqueued'); +}); + +app.get('/check-isolation', async (req, res) => { + Sentry.captureException(new Error('Isolation check')); + res.send('Isolation check'); +}); + +Sentry.setupExpressErrorHandler(app); + +app.listen(port, () => { + console.log(`App listening on port ${port}`); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/src/instrument.mjs b/dev-packages/e2e-tests/test-applications/node-bullmq/src/instrument.mjs new file mode 100644 index 000000000000..5644b907f3f1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/src/instrument.mjs @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: process.env.E2E_TEST_DSN, + tunnel: 'http://localhost:3031/', + tracesSampleRate: 1.0, + // A fixed release, so that a release from the CI environment does not change the span attributes. + release: '1.0', + // The Context integration adds machine-specific attributes to segment spans, and adds the `os.*` ones only after an + // async lookup. Without it, the tests can compare all span attributes. + integrations: integrations => integrations.filter(integration => integration.name !== 'Context'), +}); diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-bullmq/start-event-proxy.mjs new file mode 100644 index 000000000000..2426471fe487 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'node-bullmq', +}); diff --git a/dev-packages/e2e-tests/test-applications/node-bullmq/tests/bullmq.test.ts b/dev-packages/e2e-tests/test-applications/node-bullmq/tests/bullmq.test.ts new file mode 100644 index 000000000000..2508bdb5b2b9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-bullmq/tests/bullmq.test.ts @@ -0,0 +1,229 @@ +import { expect, test } from '@playwright/test'; +import { getSpanOp, waitForError, waitForMetric, waitForStreamedSpan } from '@sentry-internal/test-utils'; + +test('Creates a queue.publish span when adding a job', async ({ baseURL }) => { + const publishSpanPromise = waitForStreamedSpan('node-bullmq', span => { + return getSpanOp(span) === 'queue.publish' && span.attributes['messaging.bullmq.job.name']?.value === 'publish-job'; + }); + + await fetch(`${baseURL}/enqueue/publish-job`); + + const publishSpan = await publishSpanPromise; + + expect(publishSpan.name).toBe('add test-queue.publish-job'); + expect(publishSpan.is_segment).toBe(false); + expect(publishSpan.status).toBe('ok'); + expect(publishSpan.attributes).toEqual({ + 'sentry.op': { type: 'string', value: 'queue.publish' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.producer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'test-queue' }, + 'messaging.operation.name': { type: 'string', value: 'add' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: 'publish-job' }, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: true }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node' }, + 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, + 'sentry.segment.id': { type: 'string', value: expect.stringMatching(/^[\da-f]{16}$/) }, + 'sentry.segment.name': { type: 'string', value: 'GET /enqueue/:jobName' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + }); +}); + +test('Creates a segment span for queue.process when processing a job', async ({ baseURL }) => { + const processSpanPromise = waitForStreamedSpan('node-bullmq', span => { + return ( + span.is_segment && + getSpanOp(span) === 'queue.process' && + span.attributes['messaging.bullmq.job.name']?.value === 'process-job' + ); + }); + + await fetch(`${baseURL}/enqueue/process-job`); + + const processSpan = await processSpanPromise; + + expect(processSpan.name).toBe('process test-queue'); + expect(processSpan.status).toBe('ok'); + expect(processSpan.attributes).toEqual({ + 'sentry.op': { type: 'string', value: 'queue.process' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.consumer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'test-queue' }, + 'messaging.operation.name': { type: 'string', value: 'process' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.message.retry.count': { type: 'integer', value: 0 }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: 'process-job' }, + 'messaging.bullmq.job.processed_timestamp': { type: 'integer', value: expect.any(Number) }, + 'messaging.bullmq.job.attempt_finished_timestamp': { type: 'integer', value: expect.any(Number) }, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: false }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sample_rate': { type: 'integer', value: 1 }, + 'sentry.sdk.integrations': { type: 'array', value: expect.any(Array) }, + 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node' }, + 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, + 'sentry.segment.id': { type: 'string', value: processSpan.span_id }, + 'sentry.segment.name': { type: 'string', value: 'process test-queue' }, + 'sentry.segment.name.source': { type: 'string', value: 'custom' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + }); +}); + +test('Sends exception to Sentry on error in job processor', async ({ baseURL }) => { + const errorEventPromise = waitForError('node-bullmq', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Test error from BullMQ processor'; + }); + const processSpanPromise = waitForStreamedSpan('node-bullmq', span => { + return ( + span.is_segment && + getSpanOp(span) === 'queue.process' && + span.attributes['messaging.bullmq.job.name']?.value === 'fail-job' + ); + }); + + await fetch(`${baseURL}/enqueue/fail-job`); + + const errorEvent = await errorEventPromise; + const processSpan = await processSpanPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({ + handled: false, + type: 'auto.queue.bullmq', + }); + expect(processSpan.status).toBe('error'); + expect(errorEvent.contexts?.trace?.trace_id).toBe(processSpan.trace_id); + expect(errorEvent.contexts?.trace?.span_id).toBe(processSpan.span_id); +}); + +test('BullMQ processor breadcrumbs do not leak into subsequent HTTP requests', async ({ baseURL }) => { + const processSpanPromise = waitForStreamedSpan('node-bullmq', span => { + return ( + span.is_segment && + getSpanOp(span) === 'queue.process' && + span.attributes['messaging.bullmq.job.name']?.value === 'breadcrumb-job' + ); + }); + + await fetch(`${baseURL}/enqueue/breadcrumb-job`); + + await processSpanPromise; + + const errorEventPromise = waitForError('node-bullmq', event => { + return event.exception?.values?.[0]?.value === 'Isolation check'; + }); + + await fetch(`${baseURL}/check-isolation`); + + const errorEvent = await errorEventPromise; + + const leakedBreadcrumb = (errorEvent.breadcrumbs || []).find( + (b: { message?: string }) => b.message === 'breadcrumb-from-bullmq-processor', + ); + expect(leakedBreadcrumb).toBeUndefined(); +}); + +test('Links the queue.process segment span to its producer span', async ({ baseURL }) => { + const producerSpanPromise = waitForStreamedSpan('node-bullmq', span => { + return getSpanOp(span) === 'queue.publish' && span.attributes['messaging.bullmq.job.name']?.value === 'link-job'; + }); + + const consumerSpanPromise = waitForStreamedSpan('node-bullmq', span => { + return ( + span.is_segment && + getSpanOp(span) === 'queue.process' && + span.attributes['messaging.bullmq.job.name']?.value === 'link-job' + ); + }); + + await fetch(`${baseURL}/enqueue/link-job`); + + const producerSpan = await producerSpanPromise; + const consumerSpan = await consumerSpanPromise; + + expect(producerSpan.name).toBe('add test-queue.link-job'); + expect(consumerSpan.name).toBe('process test-queue'); + expect(consumerSpan.trace_id).not.toBe(producerSpan.trace_id); + expect(consumerSpan.links).toEqual([ + { + trace_id: producerSpan.trace_id, + span_id: producerSpan.span_id, + sampled: true, + attributes: { 'sentry.link.type': { type: 'string', value: 'message_origin' } }, + }, + ]); +}); + +test('Emits messaging.bullmq.jobs.completed counter metric on successful job', async ({ baseURL }) => { + const metricPromise = waitForMetric('node-bullmq', metric => { + return ( + metric.name === 'messaging.bullmq.jobs.completed' && + metric.attributes?.['messaging.bullmq.job.name']?.value === 'counter-job' + ); + }); + + await fetch(`${baseURL}/enqueue/counter-job`); + + const metric = await metricPromise; + + expect({ + type: metric.type, + unit: metric.unit, + value: metric.value, + attributes: metric.attributes, + }).toEqual({ + type: 'counter', + unit: 'none', + value: 1, + attributes: { + 'messaging.destination.name': { type: 'string', value: 'test-queue' }, + 'messaging.bullmq.job.name': { type: 'string', value: 'counter-job' }, + 'messaging.bullmq.job.state': { type: 'string', value: 'completed' }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node' }, + 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, + 'server.address': { type: 'string', value: expect.any(String) }, + }, + }); +}); + +test('Emits messaging.process.duration histogram metric on job completion', async ({ baseURL }) => { + const metricPromise = waitForMetric('node-bullmq', metric => { + return ( + metric.name === 'messaging.process.duration' && + metric.attributes?.['messaging.bullmq.job.name']?.value === 'duration-job' + ); + }); + + await fetch(`${baseURL}/enqueue/duration-job`); + + const metric = await metricPromise; + + expect({ + type: metric.type, + unit: metric.unit, + value: metric.value, + attributes: metric.attributes, + }).toEqual({ + type: 'distribution', + unit: 'second', + value: expect.any(Number), + attributes: { + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.operation.name': { type: 'string', value: 'process' }, + 'messaging.destination.name': { type: 'string', value: 'test-queue' }, + 'messaging.bullmq.job.name': { type: 'string', value: 'duration-job' }, + 'messaging.bullmq.job.state': { type: 'string', value: 'completed' }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: 'sentry.javascript.node' }, + 'sentry.sdk.version': { type: 'string', value: expect.any(String) }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, + 'server.address': { type: 'string', value: expect.any(String) }, + }, + }); +}); diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 61f0e050b408..27187e843dfb 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -65,6 +65,7 @@ "ai": "^4.3.16", "amqplib": "^0.10.9", "body-parser": "^2.3.0", + "bullmq": "^5.79.1", "consola": "^3.2.3", "cors": "^2.8.5", "cron": "^3.1.6", diff --git a/dev-packages/node-integration-tests/suites/tracing/bullmq/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/bullmq/docker-compose.yml new file mode 100644 index 000000000000..deac6f3496a4 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/bullmq/docker-compose.yml @@ -0,0 +1,12 @@ +services: + db: + image: redis:latest + restart: always + ports: + - '6384:6379' + healthcheck: + test: ['CMD-SHELL', 'redis-cli ping | grep -q PONG'] + interval: 2s + timeout: 3s + retries: 30 + start_period: 5s diff --git a/dev-packages/node-integration-tests/suites/tracing/bullmq/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/bullmq/instrument.mjs new file mode 100644 index 000000000000..039b3bdb5104 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/bullmq/instrument.mjs @@ -0,0 +1,12 @@ +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, + // The Context integration adds machine-specific attributes to segment spans, and adds the `os.*` ones only after an + // async lookup. Without it, the tests can compare all span attributes. + integrations: integrations => integrations.filter(integration => integration.name !== 'Context'), +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/bullmq/scenario-attributes.mjs b/dev-packages/node-integration-tests/suites/tracing/bullmq/scenario-attributes.mjs new file mode 100644 index 000000000000..1d3b1e445320 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/bullmq/scenario-attributes.mjs @@ -0,0 +1,68 @@ +import * as Sentry from '@sentry/node'; +import { FlowProducer, Queue, Worker } from 'bullmq'; + +const telemetry = new Sentry.BullMQTelemetry({ enableMetrics: true }); +const connection = { host: '127.0.0.1', port: 6384 }; + +async function run() { + const opsQueue = new Queue('attributes-ops-queue', { connection, telemetry }); + const opsWorker = new Worker('attributes-ops-queue', null, { connection, telemetry, name: 'attributes-ops-worker' }); + const flowProducer = new FlowProducer({ connection, telemetry }); + const processQueue = new Queue('attributes-process-queue', { connection, telemetry }); + const processWorker = new Worker('attributes-process-queue', async () => {}, { + connection, + telemetry, + name: 'attributes-worker', + }); + + const jobProcessed = new Promise(resolve => { + processWorker.once('completed', () => resolve()); + }); + + await Sentry.startSpan({ name: 'bullmq attributes' }, async () => { + await processQueue.add('process-job', { data: 'test-data' }); + + await opsQueue.addBulk([ + { name: 'bulk-job-1', data: {} }, + { name: 'bulk-job-2', data: {} }, + ]); + await flowProducer.add({ + name: 'flow-root', + queueName: 'attributes-ops-queue', + children: [{ name: 'flow-child', queueName: 'attributes-ops-queue' }], + }); + await opsQueue.upsertJobScheduler('attributes-scheduler', { every: 60_000 }, { name: 'scheduled-job' }); + + const progressJob = await opsQueue.add('progress-job', {}); + await opsQueue.updateJobProgress(progressJob.id, { percentage: 50 }); + await opsQueue.remove(progressJob.id, { removeChildren: true }); + await opsQueue.removeDeduplicationKey('attributes-deduplication-id'); + await opsQueue.removeDebounceKey('attributes-debounce-id'); + await opsQueue.retryJobs({ count: 10 }); + await opsQueue.promoteJobs({ count: 10 }); + await opsQueue.trimEvents(100); + await opsQueue.clean(0, 100, 'completed'); + + await opsWorker.getNextJob('attributes-token', { block: false }); + // BullMQ runs these two on timers. Calling them directly runs them once, inside this span. + await opsWorker['moveStalledJobsToWait'](); + await opsWorker['lockManager'].extendLocks([]); + await opsWorker.rateLimit(1); + await opsWorker.pause(true); + await opsWorker.close(true); + + await opsQueue.rateLimit(1); + await opsQueue.drain(true); + }); + + await jobProcessed; + await processQueue.recordJobCountsMetric('waiting', 'completed'); + await processWorker.close(); + + await opsQueue.obliterate({ force: true }); + await processQueue.obliterate({ force: true }); + await Promise.all([flowProducer.close(), opsQueue.close(), processQueue.close()]); + await Sentry.flush(); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/bullmq/scenario-failed-job.mjs b/dev-packages/node-integration-tests/suites/tracing/bullmq/scenario-failed-job.mjs new file mode 100644 index 000000000000..90eea3bb176e --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/bullmq/scenario-failed-job.mjs @@ -0,0 +1,30 @@ +import * as Sentry from '@sentry/node'; +import { Queue, Worker } from 'bullmq'; + +const telemetry = new Sentry.BullMQTelemetry(); +const connection = { host: '127.0.0.1', port: 6384 }; + +async function run() { + const queue = new Queue('test-failed-queue', { connection, telemetry }); + + const worker = new Worker( + 'test-failed-queue', + async () => { + throw new Error('Job failed on purpose'); + }, + { connection, telemetry }, + ); + + const jobFailed = new Promise(resolve => { + worker.on('failed', () => resolve()); + }); + + await queue.add('failing-job', { data: 'test-data' }); + + await jobFailed; + await worker.close(); + await queue.close(); + await Sentry.flush(); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/bullmq/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/bullmq/scenario.mjs new file mode 100644 index 000000000000..ffdcead133e6 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/bullmq/scenario.mjs @@ -0,0 +1,39 @@ +import * as Sentry from '@sentry/node'; +import { Queue, Worker } from 'bullmq'; + +const telemetry = new Sentry.BullMQTelemetry({ enableMetrics: true }); +const connection = { host: '127.0.0.1', port: 6384 }; + +async function run() { + const queue = new Queue('test-queue', { connection, telemetry }); + + const worker = new Worker( + 'test-queue', + async () => { + // job processed + }, + { connection, telemetry }, + ); + + const jobsProcessed = new Promise(resolve => { + let completed = 0; + worker.on('completed', () => { + completed++; + if (completed === 2) { + resolve(); + } + }); + }); + + await Sentry.startSpan({ name: 'enqueue test-jobs' }, async () => { + await queue.add('test-job-1', { data: 'test-data' }); + await queue.add('test-job-2', { data: 'test-data' }); + }); + + await jobsProcessed; + await worker.close(); + await queue.close(); + await Sentry.flush(); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/bullmq/test.ts b/dev-packages/node-integration-tests/suites/tracing/bullmq/test.ts new file mode 100644 index 000000000000..49871dcc9786 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/bullmq/test.ts @@ -0,0 +1,781 @@ +import type { Event, SerializedMetric } from '@sentry/core'; +import { SDK_VERSION } from '@sentry/core'; +import { afterAll, expect } from 'vitest'; +import { EXPECTED_SDK_NAME } from '../../../utils'; +import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner'; + +describeWithDockerCompose('bullmq', { workingDirectory: [__dirname] }, () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('traces producer and consumer operations with queue attributes', { timeout: 90_000 }, async () => { + const runner = createRunner().ignore('trace_metric'); + const producerSpansPromise = runner.collectStreamedSpansUntilSegment('enqueue test-jobs'); + const consumer1SpansPromise = runner.collectStreamedSpansUntilSegment( + span => span.attributes['messaging.bullmq.job.name']?.value === 'test-job-1', + ); + const consumer2SpansPromise = runner.collectStreamedSpansUntilSegment( + span => span.attributes['messaging.bullmq.job.name']?.value === 'test-job-2', + ); + + await runner.start().completed(); + + const producerSpans = await producerSpansPromise; + const consumerSpans = [await consumer1SpansPromise, await consumer2SpansPromise]; + const producerSegment = producerSpans.find(span => span.is_segment && span.name === 'enqueue test-jobs')!; + const publishSpans = producerSpans + .filter(span => span.attributes['messaging.system']?.value === 'bullmq') + .sort((a, b) => a.start_timestamp - b.start_timestamp); + const consumerSegments = consumerSpans.map(spans => + spans.find(span => span.is_segment && span.name === 'process test-queue')!, + ); + + expect( + publishSpans.map(span => ({ + name: span.name, + parent_span_id: span.parent_span_id, + status: span.status, + attributes: span.attributes, + })), + ).toEqual( + ['test-job-1', 'test-job-2'].map(jobName => ({ + name: `add test-queue.${jobName}`, + parent_span_id: producerSegment.span_id, + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue.publish' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.producer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'test-queue' }, + 'messaging.operation.name': { type: 'string', value: 'add' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: jobName }, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: false }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.segment.id': { type: 'string', value: producerSegment.span_id }, + 'sentry.segment.name': { type: 'string', value: 'enqueue test-jobs' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + }, + })), + ); + + expect( + consumerSpans.map(spans => + spans + .filter(span => span.attributes['messaging.system']?.value === 'bullmq') + .sort((a, b) => a.start_timestamp - b.start_timestamp) + .map(span => ({ + name: span.name, + is_segment: span.is_segment, + status: span.status, + attributes: span.attributes, + })), + ), + ).toEqual( + ['test-job-1', 'test-job-2'].map((jobName, index) => [ + { + name: 'process test-queue', + is_segment: true, + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue.process' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.consumer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'test-queue' }, + 'messaging.operation.name': { type: 'string', value: 'process' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.message.retry.count': { type: 'integer', value: 0 }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: jobName }, + 'messaging.bullmq.job.processed_timestamp': { type: 'integer', value: expect.any(Number) }, + 'messaging.bullmq.job.attempt_finished_timestamp': { type: 'integer', value: expect.any(Number) }, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: false }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.segment.id': { type: 'string', value: consumerSegments[index]!.span_id }, + 'sentry.segment.name': { type: 'string', value: 'process test-queue' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + 'sentry.sample_rate': { type: 'integer', value: 1 }, + 'sentry.sdk.integrations': { type: 'array', value: expect.any(Array) }, + 'sentry.segment.name.source': { type: 'string', value: 'custom' }, + }, + }, + { + name: 'complete test-queue', + is_segment: false, + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'test-queue' }, + 'messaging.operation.name': { type: 'string', value: 'complete' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: jobName }, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: false }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.segment.id': { type: 'string', value: consumerSegments[index]!.span_id }, + 'sentry.segment.name': { type: 'string', value: 'process test-queue' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + }, + }, + ]), + ); + + expect(consumerSegments.map(segment => segment.attributes['messaging.message.id'])).toEqual( + publishSpans.map(span => span.attributes['messaging.message.id']), + ); + expect(consumerSegments.map(segment => segment.links)).toEqual( + publishSpans.map(span => [ + { + trace_id: span.trace_id, + span_id: span.span_id, + sampled: true, + attributes: { 'sentry.link.type': { type: 'string', value: 'message_origin' } }, + }, + ]), + ); + expect(new Set([producerSegment.trace_id, ...consumerSegments.map(segment => segment.trace_id)]).size).toBe(3); + }); + + test('emits completion counter and duration histogram for processed jobs', { timeout: 90_000 }, async () => { + let metrics: SerializedMetric[] = []; + + await createRunner() + .ignore('span') + .expect({ + trace_metric: container => { + metrics = container.items; + }, + }) + .start() + .completed(); + + expect( + metrics.map(metric => ({ + name: metric.name, + type: metric.type, + unit: metric.unit, + value: metric.value, + attributes: metric.attributes, + })), + ).toEqual( + ['test-job-1', 'test-job-2'].flatMap(jobName => [ + { + name: 'messaging.bullmq.jobs.completed', + type: 'counter', + unit: 'none', + value: 1, + attributes: { + 'messaging.destination.name': { type: 'string', value: 'test-queue' }, + 'messaging.bullmq.job.name': { type: 'string', value: jobName }, + 'messaging.bullmq.job.state': { type: 'string', value: 'completed' }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, + 'server.address': { type: 'string', value: expect.any(String) }, + }, + }, + { + name: 'messaging.process.duration', + type: 'distribution', + unit: 'second', + value: expect.any(Number), + attributes: { + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.operation.name': { type: 'string', value: 'process' }, + 'messaging.destination.name': { type: 'string', value: 'test-queue' }, + 'messaging.bullmq.job.name': { type: 'string', value: jobName }, + 'messaging.bullmq.job.state': { type: 'string', value: 'completed' }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, + 'server.address': { type: 'string', value: expect.any(String) }, + }, + }, + ]), + ); + }); + }); + + createEsmAndCjsTests(__dirname, 'scenario-failed-job.mjs', 'instrument.mjs', (createRunner, test) => { + test( + 'captures an error for a failed job and sets the error status on its process span', + { timeout: 90_000 }, + async () => { + let errorEvent: Event | undefined; + const runner = createRunner().ignore('trace_metric'); + const consumerSpansPromise = runner.collectStreamedSpansUntilSegment('process test-failed-queue'); + + await runner + .expect({ + event: event => { + errorEvent = event; + }, + }) + .start() + .completed(); + + const consumerSpans = await consumerSpansPromise; + const processSpan = consumerSpans.find(span => span.is_segment && span.name === 'process test-failed-queue')!; + + expect( + consumerSpans + .filter(span => span.attributes['messaging.system']?.value === 'bullmq') + .sort((a, b) => a.start_timestamp - b.start_timestamp) + .map(span => ({ + name: span.name, + status: span.status, + attributes: span.attributes, + })), + ).toEqual([ + { + name: 'process test-failed-queue', + status: 'error', + attributes: { + 'sentry.op': { type: 'string', value: 'queue.process' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.consumer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'test-failed-queue' }, + 'messaging.operation.name': { type: 'string', value: 'process' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.message.retry.count': { type: 'integer', value: 0 }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: 'failing-job' }, + 'messaging.bullmq.job.processed_timestamp': { type: 'integer', value: expect.any(Number) }, + 'messaging.bullmq.job.attempt_finished_timestamp': { type: 'integer', value: expect.any(Number) }, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: false }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.segment.id': { type: 'string', value: processSpan.span_id }, + 'sentry.segment.name': { type: 'string', value: 'process test-failed-queue' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + 'sentry.sample_rate': { type: 'integer', value: 1 }, + 'sentry.sdk.integrations': { type: 'array', value: expect.any(Array) }, + 'sentry.segment.name.source': { type: 'string', value: 'custom' }, + 'sentry.status.message': { type: 'string', value: 'internal_error' }, + }, + }, + { + name: 'fail test-failed-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'test-failed-queue' }, + 'messaging.operation.name': { type: 'string', value: 'fail' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: 'failing-job' }, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: false }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.segment.id': { type: 'string', value: processSpan.span_id }, + 'sentry.segment.name': { type: 'string', value: 'process test-failed-queue' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + }, + }, + ]); + expect(errorEvent?.exception?.values).toHaveLength(1); + expect(errorEvent?.exception?.values?.[0]?.type).toBe('Error'); + expect(errorEvent?.exception?.values?.[0]?.value).toBe('Job failed on purpose'); + expect(errorEvent?.exception?.values?.[0]?.mechanism).toEqual({ type: 'auto.queue.bullmq', handled: false }); + expect(errorEvent?.exception?.values?.[0]?.stacktrace).toBeUndefined(); + expect(errorEvent?.contexts?.trace?.trace_id).toBe(processSpan.trace_id); + expect(errorEvent?.contexts?.trace?.span_id).toBe(processSpan.span_id); + }, + ); + }); + + createEsmAndCjsTests(__dirname, 'scenario-attributes.mjs', 'instrument.mjs', (createRunner, test) => { + test('sends the attributes and metrics of all BullMQ operations', { timeout: 90_000 }, async () => { + let metrics: SerializedMetric[] = []; + const runner = createRunner(); + const opsSpansPromise = runner.collectStreamedSpansUntilSegment('bullmq attributes'); + const processSpansPromise = runner.collectStreamedSpansUntilSegment('process attributes-process-queue'); + + await runner + .expect({ + trace_metric: container => { + metrics = container.items; + }, + }) + .start() + .completed(); + + const opsSpans = await opsSpansPromise; + const processSpans = await processSpansPromise; + const opsSegment = opsSpans.find(span => span.is_segment && span.name === 'bullmq attributes')!; + const processSegment = processSpans.find( + span => span.is_segment && span.name === 'process attributes-process-queue', + )!; + + expect( + opsSpans + .filter(span => span.attributes['messaging.system']?.value === 'bullmq') + .sort((a, b) => a.start_timestamp - b.start_timestamp) + .map(span => ({ + name: span.name, + status: span.status, + attributes: span.attributes, + })), + ).toEqual( + [ + { + name: 'add attributes-process-queue.process-job', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue.publish' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.producer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-process-queue' }, + 'messaging.operation.name': { type: 'string', value: 'add' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: 'process-job' }, + }, + }, + { + name: 'addBulk attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue.publish' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.producer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'addBulk' }, + 'messaging.batch.message_count': { type: 'integer', value: 2 }, + 'messaging.bullmq.job.bulk.names': { type: 'array', value: ['bulk-job-1', 'bulk-job-2'] }, + }, + }, + { + name: 'addFlow attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue.publish' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.producer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'addFlow' }, + 'messaging.bullmq.flow.name': { type: 'string', value: 'flow-root' }, + }, + }, + { + name: 'addNode attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'addNode' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: 'flow-root' }, + }, + }, + { + name: 'addNode attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'addNode' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: 'flow-child' }, + }, + }, + { + name: 'add attributes-ops-queue.scheduled-job', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue.publish' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.producer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'add' }, + 'messaging.message.id': { + type: 'string', + value: expect.stringMatching(/^repeat:attributes-scheduler:\d+$/), + }, + 'messaging.bullmq.job.scheduler.id': { type: 'string', value: 'attributes-scheduler' }, + }, + }, + { + name: 'add attributes-ops-queue.progress-job', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue.publish' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.producer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'add' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: 'progress-job' }, + }, + }, + { + name: 'updateJobProgress attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'updateJobProgress' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + }, + }, + { + name: 'remove attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'remove' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + }, + }, + { + name: 'removeDeduplicationKey attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'removeDeduplicationKey' }, + 'messaging.bullmq.job.deduplication.key': { type: 'string', value: 'attributes-deduplication-id' }, + }, + }, + { + name: 'removeDebounceKey attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'removeDebounceKey' }, + 'messaging.bullmq.job.key': { type: 'string', value: 'attributes-debounce-id' }, + }, + }, + { + name: 'retryJobs attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'retryJobs' }, + }, + }, + { + name: 'promoteJobs attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'promoteJobs' }, + }, + }, + { + name: 'trimEvents attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'trimEvents' }, + 'messaging.bullmq.queue.event.max_length': { type: 'integer', value: 100 }, + }, + }, + { + // `messaging.bullmq.queue.clean.count` replaces `messaging.bullmq.job.ids` in BullMQ 6. + name: 'clean attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'clean' }, + 'messaging.bullmq.job.type': { type: 'string', value: 'completed' }, + 'messaging.bullmq.job.ids': { type: 'array', value: [] }, + 'messaging.bullmq.queue.clean.limit': { type: 'integer', value: 100 }, + 'messaging.bullmq.queue.grace': { type: 'integer', value: 0 }, + }, + }, + { + name: 'getNextJob attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'getNextJob' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.worker.name': { type: 'string', value: 'attributes-ops-worker' }, + }, + }, + { + name: 'moveStalledJobsToWait attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'moveStalledJobsToWait' }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.worker.name': { type: 'string', value: 'attributes-ops-worker' }, + 'messaging.bullmq.worker.stalled_jobs': { type: 'array', value: [] }, + }, + }, + { + name: 'extendLocks attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'extendLocks' }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.worker.name': { type: 'string', value: 'attributes-ops-worker' }, + 'messaging.bullmq.worker.jobs_to_extend_locks': { type: 'array', value: [] }, + }, + }, + { + name: 'rateLimit attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'rateLimit' }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.worker.rate_limit': { type: 'integer', value: 1 }, + }, + }, + { + name: 'pause attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'pause' }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.worker.name': { type: 'string', value: 'attributes-ops-worker' }, + 'messaging.bullmq.worker.do_not_wait_active': { type: 'boolean', value: true }, + }, + }, + { + name: 'close attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'close' }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.worker.name': { type: 'string', value: 'attributes-ops-worker' }, + 'messaging.bullmq.worker.force_close': { type: 'boolean', value: true }, + }, + }, + { + name: 'rateLimit attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'rateLimit' }, + 'messaging.bullmq.queue.rate_limit': { type: 'integer', value: 1 }, + }, + }, + { + name: 'drain attributes-ops-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-ops-queue' }, + 'messaging.operation.name': { type: 'string', value: 'drain' }, + 'messaging.bullmq.queue.drain.delay': { type: 'boolean', value: true }, + }, + }, + ].map(span => ({ + ...span, + attributes: { + ...span.attributes, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: false }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.segment.id': { type: 'string', value: opsSegment.span_id }, + 'sentry.segment.name': { type: 'string', value: 'bullmq attributes' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + }, + })), + ); + + expect( + processSpans + .filter(span => span.attributes['messaging.system']?.value === 'bullmq') + .sort((a, b) => a.start_timestamp - b.start_timestamp) + .map(span => ({ + name: span.name, + status: span.status, + attributes: span.attributes, + })), + ).toEqual([ + { + name: 'process attributes-process-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue.process' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq.consumer' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-process-queue' }, + 'messaging.operation.name': { type: 'string', value: 'process' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.message.retry.count': { type: 'integer', value: 0 }, + 'messaging.client.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.worker.name': { type: 'string', value: 'attributes-worker' }, + 'messaging.bullmq.job.name': { type: 'string', value: 'process-job' }, + 'messaging.bullmq.job.processed_timestamp': { type: 'integer', value: expect.any(Number) }, + 'messaging.bullmq.job.attempt_finished_timestamp': { type: 'integer', value: expect.any(Number) }, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: false }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.segment.id': { type: 'string', value: processSegment.span_id }, + 'sentry.segment.name': { type: 'string', value: 'process attributes-process-queue' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + 'sentry.sample_rate': { type: 'integer', value: 1 }, + 'sentry.sdk.integrations': { type: 'array', value: expect.any(Array) }, + 'sentry.segment.name.source': { type: 'string', value: 'custom' }, + }, + }, + { + name: 'complete attributes-process-queue', + status: 'ok', + attributes: { + 'sentry.op': { type: 'string', value: 'queue' }, + 'sentry.origin': { type: 'string', value: 'auto.queue.bullmq' }, + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-process-queue' }, + 'messaging.operation.name': { type: 'string', value: 'complete' }, + 'messaging.message.id': { type: 'string', value: expect.any(String) }, + 'messaging.bullmq.job.name': { type: 'string', value: 'process-job' }, + 'sentry.environment': { type: 'string', value: 'production' }, + 'sentry.is_localhost': { type: 'boolean', value: false }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.segment.id': { type: 'string', value: processSegment.span_id }, + 'sentry.segment.name': { type: 'string', value: 'process attributes-process-queue' }, + 'sentry.trace_lifecycle': { type: 'string', value: 'stream' }, + }, + }, + ]); + + expect( + metrics.map(metric => ({ + name: metric.name, + type: metric.type, + unit: metric.unit, + value: metric.value, + attributes: metric.attributes, + })), + ).toEqual([ + { + name: 'messaging.bullmq.jobs.completed', + type: 'counter', + unit: 'none', + value: 1, + attributes: { + 'messaging.destination.name': { type: 'string', value: 'attributes-process-queue' }, + 'messaging.bullmq.job.name': { type: 'string', value: 'process-job' }, + 'messaging.bullmq.job.state': { type: 'string', value: 'completed' }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, + 'server.address': { type: 'string', value: expect.any(String) }, + }, + }, + { + name: 'messaging.process.duration', + type: 'distribution', + unit: 'second', + value: expect.any(Number), + attributes: { + 'messaging.system': { type: 'string', value: 'bullmq' }, + 'messaging.operation.name': { type: 'string', value: 'process' }, + 'messaging.destination.name': { type: 'string', value: 'attributes-process-queue' }, + 'messaging.bullmq.job.name': { type: 'string', value: 'process-job' }, + 'messaging.bullmq.job.state': { type: 'string', value: 'completed' }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, + 'server.address': { type: 'string', value: expect.any(String) }, + }, + }, + ...[ + { state: 'waiting', value: 0 }, + { state: 'completed', value: 1 }, + { state: 'paused', value: 0 }, + ].map(({ state, value }) => ({ + name: 'messaging.bullmq.queue.jobs', + type: 'gauge', + unit: 'none', + value, + attributes: { + 'messaging.destination.name': { type: 'string', value: 'attributes-process-queue' }, + 'messaging.bullmq.queue.jobs.state': { type: 'string', value: state }, + 'sentry.release': { type: 'string', value: '1.0' }, + 'sentry.sdk.name': { type: 'string', value: EXPECTED_SDK_NAME }, + 'sentry.sdk.version': { type: 'string', value: SDK_VERSION }, + 'sentry.timestamp.sequence': { type: 'integer', value: expect.any(Number) }, + 'server.address': { type: 'string', value: expect.any(String) }, + }, + })), + ]); + }); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 69a5899ae619..58bcd049c076 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -19,6 +19,7 @@ export { googleGenAIIntegration, // eslint-disable-next-line typescript/no-deprecated disableAnrDetectionForCallback, + BullMQTelemetry, captureCheckIn, captureConsoleIntegration, captureEvent, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 095858178815..41c0d0131184 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -170,6 +170,7 @@ export { zodErrorsIntegration, profiler, amqplibIntegration, + BullMQTelemetry, anthropicAIIntegration, googleGenAIIntegration, vercelAIIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 763082aba7cd..5ff613985c91 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -191,6 +191,7 @@ export { zodErrorsIntegration, profiler, amqplibIntegration, + BullMQTelemetry, anthropicAIIntegration, googleGenAIIntegration, vercelAIIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index cd588df14aae..f6abbb875256 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -168,6 +168,7 @@ export { zodErrorsIntegration, profiler, amqplibIntegration, + BullMQTelemetry, anthropicAIIntegration, googleGenAIIntegration, childProcessIntegration, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 53f1b781d45c..183069ccbadf 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -68,6 +68,7 @@ export { export { setupHapiErrorHandler } from './integrations/tracing/hapi'; // oxlint-disable-next-line typescript/no-deprecated -- deprecated but still re-exported for backwards compatibility export { setupKoaErrorHandler } from './integrations/tracing/koa'; +export { BullMQTelemetry } from './integrations/tracing/bullmq'; export { launchDarklyIntegration, buildLaunchDarklyFlagUsedHandler, diff --git a/packages/node/src/integrations/tracing/bullmq/contextManager.ts b/packages/node/src/integrations/tracing/bullmq/contextManager.ts new file mode 100644 index 000000000000..ad32e5689cdb --- /dev/null +++ b/packages/node/src/integrations/tracing/bullmq/contextManager.ts @@ -0,0 +1,62 @@ +import type { Span } from '@opentelemetry/api'; +import { + extractTraceparentData, + getActiveSpan, + getCurrentScope, + spanToTraceHeader, + withActiveSpan, + withIsolationScope, +} from '@sentry/core'; +import type { ContextManager, SentryContext } from './types'; + +export class SentryBullMQContextManager implements ContextManager { + public active(): SentryContext { + return { + span: getActiveSpan(), + scope: getCurrentScope(), + }; + } + + public with unknown>(context: SentryContext, fn: A): ReturnType { + if (context.span) { + return withIsolationScope(() => { + return withActiveSpan(context.span as Span, fn) as ReturnType; + }); + } + return withIsolationScope(() => withActiveSpan(null, fn)) as ReturnType; + } + + /** + * Returns a context without a span. BullMQ 6.3.11+ runs the stalled jobs checker in it, so the + * checker does not stay attached to the span that was active when the worker started. + */ + public root(): SentryContext { + return { + span: undefined, + scope: getCurrentScope(), + }; + } + + public getMetadata(context: SentryContext): string { + if (context.span) { + return spanToTraceHeader(context.span); + } + return ''; + } + + public fromMetadata(activeContext: SentryContext, metadata: string): SentryContext { + const traceparent = extractTraceparentData(metadata); + if (!traceparent?.traceId || !traceparent?.parentSpanId) { + return activeContext; + } + + return { + ...activeContext, + producerSpanContext: { + traceId: traceparent.traceId, + spanId: traceparent.parentSpanId, + sampled: traceparent.parentSampled === true, + }, + }; + } +} diff --git a/packages/node/src/integrations/tracing/bullmq/index.ts b/packages/node/src/integrations/tracing/bullmq/index.ts new file mode 100644 index 000000000000..984b1af13591 --- /dev/null +++ b/packages/node/src/integrations/tracing/bullmq/index.ts @@ -0,0 +1,56 @@ +import { SentryBullMQContextManager } from './contextManager'; +import { SentryBullMQMeter } from './meter'; +import { SentryBullMQTracer } from './tracer'; +import type { ContextManager, Meter, Telemetry, Tracer, SentryContext } from './types'; + +/** + * A Sentry-specific telemetry implementation for BullMQ. + * + * This implements BullMQ's Telemetry interface to provide automatic tracing + * for queue operations with proper Queue Insights attributes. + * + * @example + * ```javascript + * import * as Sentry from '@sentry/node'; + * import { Queue, Worker } from 'bullmq'; + * + * const telemetry = new Sentry.BullMQTelemetry(); + * + * const queue = new Queue('myQueue', { + * connection: { host: '127.0.0.1', port: 6379 }, + * telemetry, + * }); + * + * const worker = new Worker('myQueue', async (job) => { + * // Process job + * }, { + * connection: { host: '127.0.0.1', port: 6379 }, + * telemetry, + * }); + * ``` + * + * To also send BullMQ's job metrics to Sentry, use `new Sentry.BullMQTelemetry({ enableMetrics: true })`. + * + * @see https://docs.bullmq.io/guide/telemetry + */ +export class BullMQTelemetry implements Telemetry { + public tracer: Tracer; + public contextManager: ContextManager; + public meter?: Meter; + + public constructor( + options: { + /** + * Sends BullMQ's job metrics, such as `messaging.bullmq.jobs.completed` and `messaging.process.duration`, to Sentry. Defaults to `false`. + */ + enableMetrics?: boolean; + } = {}, + ) { + this.tracer = new SentryBullMQTracer(); + this.contextManager = new SentryBullMQContextManager(); + + if (options.enableMetrics) { + this.meter = new SentryBullMQMeter(); + } + } +} diff --git a/packages/node/src/integrations/tracing/bullmq/meter.ts b/packages/node/src/integrations/tracing/bullmq/meter.ts new file mode 100644 index 000000000000..bfae7aa342f8 --- /dev/null +++ b/packages/node/src/integrations/tracing/bullmq/meter.ts @@ -0,0 +1,121 @@ +import { MESSAGING_OPERATION_NAME, MESSAGING_SYSTEM } from '@sentry/conventions/attributes'; +import { metrics } from '@sentry/core'; +import { toMessagingKey, toSentryAttributes } from './span'; +import type { AttributeValue, Counter, Gauge, Histogram, Meter, MetricOptions } from './types'; + +function toMetricAttributes( + attributes?: Record, +): Record | undefined { + if (!attributes) { + return undefined; + } + const result: Record = {}; + for (const [key, value] of Object.entries(toSentryAttributes(attributes))) { + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + result[key] = value; + } + } + return result; +} + +/** + * Converts the OpenTelemetry (UCUM) unit that BullMQ passes to the matching Sentry unit. + */ +function toSentryUnit(unit?: string): string | undefined { + if (unit === 'ms') { + return 'millisecond'; + } + + // In UCUM, `1` and annotations such as `{jobs}` mark a value without a unit. + if (unit === '1' || unit?.startsWith('{')) { + return 'none'; + } + + return unit; +} + +class SentryBullMQCounter implements Counter { + private _name: string; + private _unit?: string; + + public constructor(name: string, unit?: string) { + this._name = name; + this._unit = unit; + } + + public add(value: number, attributes?: Record): void { + metrics.count(this._name, value, { + unit: this._unit, + attributes: toMetricAttributes(attributes), + }); + } +} + +class SentryBullMQHistogram implements Histogram { + private _name: string; + private _unit?: string; + + public constructor(name: string, unit?: string) { + this._name = name; + this._unit = unit; + } + + public record(value: number, attributes?: Record): void { + metrics.distribution(this._name, value, { + unit: this._unit, + attributes: toMetricAttributes(attributes), + }); + } +} + +class SentryBullMQGauge implements Gauge { + private _name: string; + private _unit?: string; + + public constructor(name: string, unit?: string) { + this._name = name; + this._unit = unit; + } + + public record(value: number, attributes?: Record): void { + metrics.gauge(this._name, value, { + unit: this._unit, + attributes: toMetricAttributes(attributes), + }); + } +} + +/** + * Records BullMQ's `bullmq.job.duration` histogram, which is in milliseconds, as the OTel + * `messaging.process.duration` metric in seconds. + */ +class SentryBullMQProcessDuration implements Histogram { + public record(value: number, attributes?: Record): void { + metrics.distribution('messaging.process.duration', value / 1000, { + unit: 'second', + attributes: { + ...toMetricAttributes(attributes), + [MESSAGING_SYSTEM]: 'bullmq', + [MESSAGING_OPERATION_NAME]: 'process', + }, + }); + } +} + +export class SentryBullMQMeter implements Meter { + public createCounter(name: string, options?: MetricOptions): Counter { + return new SentryBullMQCounter(toMessagingKey(name), toSentryUnit(options?.unit)); + } + + public createHistogram(name: string, options?: MetricOptions): Histogram { + if (name === 'bullmq.job.duration') { + return new SentryBullMQProcessDuration(); + } + + return new SentryBullMQHistogram(toMessagingKey(name), toSentryUnit(options?.unit)); + } + + public createGauge(name: string, options?: MetricOptions): Gauge { + return new SentryBullMQGauge(toMessagingKey(name), toSentryUnit(options?.unit)); + } +} diff --git a/packages/node/src/integrations/tracing/bullmq/span.ts b/packages/node/src/integrations/tracing/bullmq/span.ts new file mode 100644 index 000000000000..10ec13bee4c2 --- /dev/null +++ b/packages/node/src/integrations/tracing/bullmq/span.ts @@ -0,0 +1,205 @@ +import type { Attributes, Span } from '@opentelemetry/api'; +import { + MESSAGING_BATCH_MESSAGE_COUNT, + MESSAGING_BULLMQ_FLOW_NAME, + MESSAGING_BULLMQ_JOB_ATTEMPT_FINISHED_TIMESTAMP, + MESSAGING_BULLMQ_JOB_BULK_NAMES, + MESSAGING_BULLMQ_JOB_DEDUPLICATION_KEY, + MESSAGING_BULLMQ_JOB_IDS, + MESSAGING_BULLMQ_JOB_KEY, + MESSAGING_BULLMQ_JOB_NAME, + MESSAGING_BULLMQ_JOB_PROCESSED_TIMESTAMP, + MESSAGING_BULLMQ_JOB_SCHEDULER_ID, + MESSAGING_BULLMQ_JOB_STATE, + MESSAGING_BULLMQ_JOB_TYPE, + MESSAGING_BULLMQ_QUEUE_CLEAN_COUNT, + MESSAGING_BULLMQ_QUEUE_CLEAN_LIMIT, + MESSAGING_BULLMQ_QUEUE_DRAIN_DELAY, + MESSAGING_BULLMQ_QUEUE_EVENT_MAX_LENGTH, + MESSAGING_BULLMQ_QUEUE_GRACE, + MESSAGING_BULLMQ_QUEUE_JOBS_STATE, + MESSAGING_BULLMQ_QUEUE_RATE_LIMIT, + MESSAGING_BULLMQ_WORKER_DO_NOT_WAIT_ACTIVE, + MESSAGING_BULLMQ_WORKER_FORCE_CLOSE, + MESSAGING_BULLMQ_WORKER_JOBS_TO_EXTEND_LOCKS, + MESSAGING_BULLMQ_WORKER_NAME, + MESSAGING_BULLMQ_WORKER_RATE_LIMIT, + MESSAGING_BULLMQ_WORKER_STALLED_JOBS, + MESSAGING_CLIENT_ID, + MESSAGING_DESTINATION_NAME, + MESSAGING_MESSAGE_ID, + MESSAGING_MESSAGE_RETRY_COUNT, + MESSAGING_OPERATION_NAME, +} from '@sentry/conventions/attributes'; +import type { Scope } from '@sentry/core'; +import { captureException, SPAN_STATUS_ERROR } from '@sentry/core'; +import type { AttributeValue, TelemetrySpan } from './types'; + +const DROPPED_ATTRIBUTES = new Set([ + // BullMQ sets these to `JSON.stringify()` output. They are left out until Sentry attributes support objects. + 'bullmq.job.options', + 'bullmq.job.progress', + 'bullmq.queue.options', + 'bullmq.worker.options', + // BullMQ deprecated this for `bullmq.job.attempt_finished_timestamp`, which it sets on the same spans. + 'bullmq.job.finished.timestamp', +]); + +// The BullMQ attribute keys and their `messaging.bullmq.*` keys in the Sentry conventions. Where BullMQ separates +// the words of a key with dots, the Sentry key uses snake_case. +const MESSAGING_BULLMQ_KEYS = new Map([ + ['bullmq.flow.name', MESSAGING_BULLMQ_FLOW_NAME], + ['bullmq.job.attempt_finished_timestamp', MESSAGING_BULLMQ_JOB_ATTEMPT_FINISHED_TIMESTAMP], + ['bullmq.job.bulk.names', MESSAGING_BULLMQ_JOB_BULK_NAMES], + ['bullmq.job.deduplication.key', MESSAGING_BULLMQ_JOB_DEDUPLICATION_KEY], + ['bullmq.job.ids', MESSAGING_BULLMQ_JOB_IDS], + ['bullmq.job.key', MESSAGING_BULLMQ_JOB_KEY], + ['bullmq.job.name', MESSAGING_BULLMQ_JOB_NAME], + ['bullmq.job.processed.timestamp', MESSAGING_BULLMQ_JOB_PROCESSED_TIMESTAMP], + ['bullmq.job.scheduler.id', MESSAGING_BULLMQ_JOB_SCHEDULER_ID], + ['bullmq.job.type', MESSAGING_BULLMQ_JOB_TYPE], + ['bullmq.queue.clean.count', MESSAGING_BULLMQ_QUEUE_CLEAN_COUNT], + ['bullmq.queue.clean.limit', MESSAGING_BULLMQ_QUEUE_CLEAN_LIMIT], + ['bullmq.queue.drain.delay', MESSAGING_BULLMQ_QUEUE_DRAIN_DELAY], + ['bullmq.queue.event.max.length', MESSAGING_BULLMQ_QUEUE_EVENT_MAX_LENGTH], + ['bullmq.queue.grace', MESSAGING_BULLMQ_QUEUE_GRACE], + ['bullmq.queue.jobs.state', MESSAGING_BULLMQ_QUEUE_JOBS_STATE], + ['bullmq.queue.rate.limit', MESSAGING_BULLMQ_QUEUE_RATE_LIMIT], + ['bullmq.worker.do.not.wait.active', MESSAGING_BULLMQ_WORKER_DO_NOT_WAIT_ACTIVE], + ['bullmq.worker.force.close', MESSAGING_BULLMQ_WORKER_FORCE_CLOSE], + ['bullmq.worker.jobs.to.extend.locks', MESSAGING_BULLMQ_WORKER_JOBS_TO_EXTEND_LOCKS], + ['bullmq.worker.name', MESSAGING_BULLMQ_WORKER_NAME], + ['bullmq.worker.rate.limit', MESSAGING_BULLMQ_WORKER_RATE_LIMIT], + ['bullmq.worker.stalled.jobs', MESSAGING_BULLMQ_WORKER_STALLED_JOBS], +]); + +/** + * Returns the Sentry key for a `bullmq.*` attribute key or metric name: the key from `MESSAGING_BULLMQ_KEYS`, or + * `messaging.` for the keys that are not in it, such as the metric names. Returns all other keys unchanged. + */ +export function toMessagingKey(key: string): string { + return MESSAGING_BULLMQ_KEYS.get(key) ?? (key.startsWith('bullmq.') ? `messaging.${key}` : key); +} + +/** + * Returns the BullMQ attributes with Sentry keys: the `messaging.*` convention where one exists, and the + * `messaging.bullmq.*` key for all others. The attributes that hold JSON strings and the deprecated ones are left out. + */ +export function toSentryAttributes(attributes: Record): Attributes { + const { + 'bullmq.queue.name': queueName, + 'bullmq.queue.operation': operation, + 'bullmq.job.id': jobId, + 'bullmq.job.attempts.made': attemptsMade, + 'bullmq.job.bulk.count': bulkCount, + 'bullmq.worker.id': workerId, + 'bullmq.job.state': jobState, + 'bullmq.job.status': jobStatus, + ...rest + } = attributes; + const result: Attributes = {}; + + for (const [key, value] of Object.entries(rest)) { + if (!DROPPED_ATTRIBUTES.has(key)) { + result[toMessagingKey(key)] = value; + } + } + + if (typeof queueName === 'string') { + result[MESSAGING_DESTINATION_NAME] = queueName; + } + + if (typeof operation === 'string') { + result[MESSAGING_OPERATION_NAME] = operation; + } + + if (jobId !== undefined) { + result[MESSAGING_MESSAGE_ID] = String(jobId); + } + + // BullMQ counts the attempt that just finished in `attemptsMade`. + if (typeof attemptsMade === 'number') { + result[MESSAGING_MESSAGE_RETRY_COUNT] = Math.max(attemptsMade - 1, 0); + } + + if (typeof bulkCount === 'number') { + result[MESSAGING_BATCH_MESSAGE_COUNT] = bulkCount; + } + + if (typeof workerId === 'string') { + result[MESSAGING_CLIENT_ID] = workerId; + } + + // BullMQ before 5.81.2 sets `bullmq.job.status` instead of `bullmq.job.state`. + const state = jobState ?? jobStatus; + if (typeof state === 'string') { + result[MESSAGING_BULLMQ_JOB_STATE] = state; + } + + return result; +} + +export class SentryBullMQSpan implements TelemetrySpan { + private _span: Span; + private _scope: Scope; + + public constructor(span: Span, scope: Scope) { + this._span = span; + this._scope = scope; + } + + public setAttribute(key: string, value: AttributeValue): void { + this.setAttributes({ [key]: value }); + } + + public setAttributes(attributes: Record): void { + this._span.setAttributes(toSentryAttributes(attributes)); + } + + public addEvent(name: string, attributes?: Record): void { + this._span.addEvent(name, attributes); + + // BullMQ sends this event from `Worker.handleFailed`, and the event name is not an exported constant: + // https://github.com/taskforcesh/bullmq/blob/01b8b14a973984845dc0bbcefccf35b3ed30782d/src/classes/worker.ts#L1223-L1225 + if (name === 'job failed') { + this._span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + + const reason = attributes?.['bullmq.job.failed.reason']; + const error = new Error(String(reason || 'Unknown error')); + // BullMQ passes only the error message (https://github.com/taskforcesh/bullmq/issues/4902), so this stack has only + // BullMQ and SDK frames, the same for every job. Without it, Sentry groups the errors by their message. + error.stack = undefined; + captureException(error, { + mechanism: { + handled: false, + type: 'auto.queue.bullmq', + }, + }); + } + } + + /** + * Does not capture to Sentry. BullMQ calls this for Redis, lock and serialization errors that it + * retries itself, often several times for one fault. Job failures are captured in `addEvent`. + */ + public recordException(exception: Error | string | { code?: number; message?: string; name?: string }): void { + const error = + exception instanceof Error + ? exception + : new Error(typeof exception === 'string' ? exception : exception.message || 'Unknown error'); + + this._span.recordException(error); + } + + public setSpanOnContext(context: unknown): unknown { + return { + ...(context as object), + span: this._span, + scope: this._scope, + }; + } + + public end(): void { + this._span.end(); + } +} diff --git a/packages/node/src/integrations/tracing/bullmq/tracer.ts b/packages/node/src/integrations/tracing/bullmq/tracer.ts new file mode 100644 index 000000000000..5722b013e451 --- /dev/null +++ b/packages/node/src/integrations/tracing/bullmq/tracer.ts @@ -0,0 +1,80 @@ +import { MESSAGING_SYSTEM, SENTRY_LINK_TYPE, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { QUEUE, QUEUE_PROCESS, QUEUE_PUBLISH } from '@sentry/conventions/op'; +import type { SpanAttributes } from '@sentry/core'; +import { getCurrentScope, startInactiveSpan, startNewTrace } from '@sentry/core'; +import { SentryBullMQSpan, toSentryAttributes } from './span'; +import type { SpanOptions, TelemetrySpan, Tracer, SentryContext } from './types'; + +// BullMQ span names follow OTel messaging semconv: "{operation} {destination}" +// e.g. "add myQueue", "addBulk myQueue", "process myQueue", "addFlow myQueue" +const PRODUCER_OPERATIONS = new Set(['add', 'addbulk', 'addflow', 'addbulkflows']); +const CONSUMER_OPERATIONS = new Set(['process']); + +function getOperation(name: string): string { + return name.split(' ')[0]!.toLowerCase(); +} + +function getOpFromSpanName(name: string): string { + const operation = getOperation(name); + + if (CONSUMER_OPERATIONS.has(operation)) { + return QUEUE_PROCESS; + } + + if (PRODUCER_OPERATIONS.has(operation)) { + return QUEUE_PUBLISH; + } + + return QUEUE; +} + +function getOriginFromSpanName(name: string): string { + const operation = getOperation(name); + + if (CONSUMER_OPERATIONS.has(operation)) { + return 'auto.queue.bullmq.consumer'; + } + + if (PRODUCER_OPERATIONS.has(operation)) { + return 'auto.queue.bullmq.producer'; + } + + return 'auto.queue.bullmq'; +} + +export class SentryBullMQTracer implements Tracer { + public startSpan(name: string, options?: SpanOptions, context?: SentryContext): TelemetrySpan { + const op = getOpFromSpanName(name); + const origin = getOriginFromSpanName(name); + + const attributes: SpanAttributes = { + [SENTRY_OP]: op, + [SENTRY_ORIGIN]: origin, + [MESSAGING_SYSTEM]: 'bullmq', + ...(options?.attributes && toSentryAttributes(options.attributes)), + }; + + // Internal operations such as `moveStalledJobsToWait` run on timers. Without a parent, each tick would start a new root span. + const span = + op === QUEUE_PROCESS + ? startNewTrace(() => startInactiveSpan({ name, attributes })) + : startInactiveSpan({ name, attributes, onlyIfParent: op === QUEUE }); + + if (context?.producerSpanContext) { + const producerSpanCtx = { + traceId: context.producerSpanContext.traceId, + spanId: context.producerSpanContext.spanId, + traceFlags: context.producerSpanContext.sampled ? 1 : 0, + }; + + span.addLink({ + context: producerSpanCtx, + attributes: { + [SENTRY_LINK_TYPE]: 'message_origin', + }, + }); + } + + return new SentryBullMQSpan(span, getCurrentScope()); + } +} diff --git a/packages/node/src/integrations/tracing/bullmq/types.ts b/packages/node/src/integrations/tracing/bullmq/types.ts new file mode 100644 index 000000000000..81d2e96696ce --- /dev/null +++ b/packages/node/src/integrations/tracing/bullmq/types.ts @@ -0,0 +1,81 @@ +import type { Span } from '@opentelemetry/api'; +import type { Scope } from '@sentry/core'; + +/** + * Vendored from bullmq@5.79.1 — src/interfaces/telemetry.ts + * https://github.com/taskforcesh/bullmq/blob/master/src/interfaces/telemetry.ts + * + * Minimal subset of BullMQ's Telemetry interface so we don't depend on bullmq at runtime. + * + * SPDX-License-Identifier: MIT + * Copyright (c) 2018 BullForce Labs AB and contributors + */ + +export type AttributeValue = string | number | boolean | Array; + +export interface SpanOptions { + kind?: number; + attributes?: Record; + parent?: unknown; +} + +export interface TelemetrySpan { + setAttribute(key: string, value: AttributeValue): void; + setAttributes(attributes: Record): void; + addEvent(name: string, attributes?: Record): void; + recordException(exception: Error | string | { code?: number; message?: string; name?: string }): void; + setSpanOnContext(context: unknown): unknown; + end(): void; +} + +export interface Tracer { + startSpan(name: string, options?: SpanOptions, context?: Context): TelemetrySpan; +} + +export interface ContextManager { + active(): Context; + with unknown>(context: Context, fn: A): ReturnType; + // Added in bullmq@6.3.11 + root?(): Context; + getMetadata(context: Context): string; + fromMetadata(activeContext: Context, metadata: string): Context; +} + +export interface MetricOptions { + description?: string; + unit?: string; +} + +export interface Counter { + add(value: number, attributes?: Record): void; +} + +export interface Histogram { + record(value: number, attributes?: Record): void; +} + +export interface Gauge { + record(value: number, attributes?: Record): void; +} + +export interface Meter { + createCounter(name: string, options?: MetricOptions): Counter; + createHistogram(name: string, options?: MetricOptions): Histogram; + createGauge?(name: string, options?: MetricOptions): Gauge; +} + +export interface Telemetry { + tracer: Tracer; + contextManager: ContextManager; + meter?: Meter; +} + +export interface SentryContext { + span: Span | undefined; + scope: Scope; + producerSpanContext?: { + traceId: string; + spanId: string; + sampled: boolean; + }; +} diff --git a/packages/node/test/integrations/tracing/bullmq.test.ts b/packages/node/test/integrations/tracing/bullmq.test.ts new file mode 100644 index 000000000000..a4fec420645f --- /dev/null +++ b/packages/node/test/integrations/tracing/bullmq.test.ts @@ -0,0 +1,688 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import * as SentryCore from '@sentry/core'; +import { BullMQTelemetry } from '../../../src/integrations/tracing/bullmq'; + +vi.mock('@sentry/core', async importOriginal => { + const actual = await importOriginal(); + return { + ...actual, + startInactiveSpan: vi.fn(() => mockOtelSpan()), + getActiveSpan: vi.fn(() => undefined), + getCurrentScope: vi.fn(() => mockScope()), + withActiveSpan: vi.fn((_span, fn) => fn()), + startNewTrace: vi.fn(fn => fn()), + withIsolationScope: vi.fn(fn => fn()), + spanToTraceHeader: vi.fn(() => '00-traceid-spanid-01'), + captureException: vi.fn(), + metrics: { + count: vi.fn(), + distribution: vi.fn(), + gauge: vi.fn(), + }, + }; +}); + +function mockOtelSpan() { + return { + setAttribute: vi.fn(), + setAttributes: vi.fn(), + addEvent: vi.fn(), + addLink: vi.fn(), + recordException: vi.fn(), + setStatus: vi.fn(), + end: vi.fn(), + spanContext: () => ({ spanId: 'abc123', traceId: 'def456', traceFlags: 1 }), + }; +} + +function mockScope() { + return { + setTag: vi.fn(), + setContext: vi.fn(), + }; +} + +beforeEach(() => { + vi.clearAllMocks(); +}); + +describe('BullMQTelemetry', () => { + it('does not create a meter by default', () => { + const telemetry = new BullMQTelemetry(); + + expect(telemetry.meter).toBeUndefined(); + }); + + it('creates a meter when enableMetrics is true', () => { + const telemetry = new BullMQTelemetry({ enableMetrics: true }); + + expect(telemetry.meter).toBeDefined(); + }); +}); + +describe('SentryBullMQTracer', () => { + describe('startSpan', () => { + it.each([ + { name: 'add myQueue', expectedOp: 'queue.publish', expectedOrigin: 'auto.queue.bullmq.producer' }, + { name: 'addBulk myQueue', expectedOp: 'queue.publish', expectedOrigin: 'auto.queue.bullmq.producer' }, + { name: 'addFlow myQueue', expectedOp: 'queue.publish', expectedOrigin: 'auto.queue.bullmq.producer' }, + { name: 'addBulkFlows myQueue', expectedOp: 'queue.publish', expectedOrigin: 'auto.queue.bullmq.producer' }, + { name: 'pause myQueue', expectedOp: 'queue', expectedOrigin: 'auto.queue.bullmq' }, + { name: 'close myQueue', expectedOp: 'queue', expectedOrigin: 'auto.queue.bullmq' }, + { name: 'drain myQueue', expectedOp: 'queue', expectedOrigin: 'auto.queue.bullmq' }, + { name: 'getNextJob myQueue', expectedOp: 'queue', expectedOrigin: 'auto.queue.bullmq' }, + { name: 'moveStalledJobsToWait myQueue', expectedOp: 'queue', expectedOrigin: 'auto.queue.bullmq' }, + ])('maps "$name" to op=$expectedOp and origin=$expectedOrigin', ({ name, expectedOp, expectedOrigin }) => { + const telemetry = new BullMQTelemetry(); + + telemetry.tracer.startSpan(name); + + expect(SentryCore.startInactiveSpan).toHaveBeenCalledWith({ + name, + attributes: { + 'sentry.op': expectedOp, + 'sentry.origin': expectedOrigin, + 'messaging.system': 'bullmq', + }, + onlyIfParent: expectedOp === 'queue', + }); + expect(SentryCore.startNewTrace).not.toHaveBeenCalled(); + }); + + it('maps "process myQueue" to op=queue.process and origin=auto.queue.bullmq.consumer', () => { + const telemetry = new BullMQTelemetry(); + + telemetry.tracer.startSpan('process myQueue'); + + expect(SentryCore.startInactiveSpan).toHaveBeenCalledWith({ + name: 'process myQueue', + attributes: { + 'sentry.op': 'queue.process', + 'sentry.origin': 'auto.queue.bullmq.consumer', + 'messaging.system': 'bullmq', + }, + }); + }); + + it('merges attributes from SpanOptions and gives them messaging keys', () => { + const telemetry = new BullMQTelemetry(); + + telemetry.tracer.startSpan('add emails', { + attributes: { 'bullmq.queue.name': 'emails', 'bullmq.job.name': 'welcome-email' }, + }); + + expect(SentryCore.startInactiveSpan).toHaveBeenCalledWith({ + name: 'add emails', + attributes: { + 'sentry.op': 'queue.publish', + 'sentry.origin': 'auto.queue.bullmq.producer', + 'messaging.system': 'bullmq', + 'messaging.destination.name': 'emails', + 'messaging.bullmq.job.name': 'welcome-email', + }, + onlyIfParent: false, + }); + }); + + it('starts each queue.process span in a new trace', () => { + const telemetry = new BullMQTelemetry(); + + telemetry.tracer.startSpan('process notifications'); + + expect(SentryCore.startNewTrace).toHaveBeenCalledOnce(); + }); + + it('adds span link to producer when context has producerSpanContext', () => { + const telemetry = new BullMQTelemetry(); + const otelSpan = mockOtelSpan(); + vi.mocked(SentryCore.startInactiveSpan).mockReturnValueOnce(otelSpan as any); + + telemetry.tracer.startSpan('process myQueue', undefined, { + span: undefined, + scope: mockScope() as any, + producerSpanContext: { + traceId: 'aabbccddaabbccddaabbccddaabbccdd', + spanId: '1122334455667788', + sampled: true, + }, + }); + + expect(otelSpan.addLink).toHaveBeenCalledWith({ + context: { + traceId: 'aabbccddaabbccddaabbccddaabbccdd', + spanId: '1122334455667788', + traceFlags: 1, + }, + attributes: { + 'sentry.link.type': 'message_origin', + }, + }); + }); + + it('adds the span link with traceFlags 0 when the producer span was not sampled', () => { + const telemetry = new BullMQTelemetry(); + const otelSpan = mockOtelSpan(); + vi.mocked(SentryCore.startInactiveSpan).mockReturnValueOnce(otelSpan as any); + + telemetry.tracer.startSpan('process myQueue', undefined, { + span: undefined, + scope: mockScope() as any, + producerSpanContext: { + traceId: 'aabbccddaabbccddaabbccddaabbccdd', + spanId: '1122334455667788', + sampled: false, + }, + }); + + expect(otelSpan.addLink).toHaveBeenCalledWith({ + context: { + traceId: 'aabbccddaabbccddaabbccddaabbccdd', + spanId: '1122334455667788', + traceFlags: 0, + }, + attributes: { + 'sentry.link.type': 'message_origin', + }, + }); + }); + + it('does not add span link when context has no producerSpanContext', () => { + const telemetry = new BullMQTelemetry(); + const otelSpan = mockOtelSpan(); + vi.mocked(SentryCore.startInactiveSpan).mockReturnValueOnce(otelSpan as any); + + telemetry.tracer.startSpan('process myQueue', undefined, { + span: undefined, + scope: mockScope() as any, + }); + + expect(otelSpan.addLink).not.toHaveBeenCalled(); + }); + }); +}); + +describe('SentryBullMQSpan', () => { + function createSpan() { + const telemetry = new BullMQTelemetry(); + const otelSpan = mockOtelSpan(); + const scope = mockScope(); + vi.mocked(SentryCore.startInactiveSpan).mockReturnValueOnce(otelSpan as any); + vi.mocked(SentryCore.getCurrentScope).mockReturnValueOnce(scope as any); + const span = telemetry.tracer.startSpan('process emails'); + return { span, otelSpan, scope }; + } + + it('replaces the BullMQ attributes that have a messaging convention and prefixes the others', () => { + const { span, otelSpan } = createSpan(); + + span.setAttributes({ + 'bullmq.queue.name': 'emails', + 'bullmq.queue.operation': 'process', + 'bullmq.job.id': '42', + 'bullmq.job.attempts.made': 3, + 'bullmq.job.bulk.count': 5, + 'bullmq.worker.id': '03327d57-05cc-4528-9d24-c59bf6520039', + 'bullmq.job.name': 'welcome-email', + 'bullmq.worker.name': 'mailer', + }); + + expect(otelSpan.setAttributes).toHaveBeenCalledWith({ + 'messaging.bullmq.job.name': 'welcome-email', + 'messaging.bullmq.worker.name': 'mailer', + 'messaging.destination.name': 'emails', + 'messaging.operation.name': 'process', + 'messaging.message.id': '42', + 'messaging.message.retry.count': 2, + 'messaging.batch.message_count': 5, + 'messaging.client.id': '03327d57-05cc-4528-9d24-c59bf6520039', + }); + }); + + it('replaces the queue name when BullMQ sets it with setAttribute', () => { + const { span, otelSpan } = createSpan(); + + span.setAttribute('bullmq.queue.name', 'emails'); + + expect(otelSpan.setAttributes).toHaveBeenCalledWith({ + 'messaging.destination.name': 'emails', + }); + }); + + it('sets a retry count of 0 for the first attempt', () => { + const { span, otelSpan } = createSpan(); + + span.setAttributes({ 'bullmq.job.attempts.made': 1 }); + + expect(otelSpan.setAttributes).toHaveBeenCalledWith({ + 'messaging.message.retry.count': 0, + }); + }); + + it('leaves out the BullMQ attributes that hold JSON strings', () => { + const { span, otelSpan } = createSpan(); + + span.setAttributes({ + 'bullmq.job.id': '42', + 'bullmq.job.options': '{"removeChildren":true}', + 'bullmq.job.progress': '{"percentage":50}', + 'bullmq.queue.options': '{"count":10}', + 'bullmq.worker.options': '{"block":false}', + }); + + expect(otelSpan.setAttributes).toHaveBeenCalledWith({ + 'messaging.message.id': '42', + }); + }); + + it('uses snake_case for the words of multi-word BullMQ keys', () => { + const { span, otelSpan } = createSpan(); + + span.setAttributes({ + 'bullmq.job.processed.timestamp': 1, + 'bullmq.queue.event.max.length': 2, + 'bullmq.queue.rate.limit': 3, + 'bullmq.worker.do.not.wait.active': 4, + 'bullmq.worker.force.close': 5, + 'bullmq.worker.jobs.to.extend.locks': 6, + 'bullmq.worker.rate.limit': 7, + 'bullmq.worker.stalled.jobs': 8, + }); + + expect(otelSpan.setAttributes).toHaveBeenCalledWith({ + 'messaging.bullmq.job.processed_timestamp': 1, + 'messaging.bullmq.queue.event.max_length': 2, + 'messaging.bullmq.queue.rate_limit': 3, + 'messaging.bullmq.worker.do_not_wait_active': 4, + 'messaging.bullmq.worker.force_close': 5, + 'messaging.bullmq.worker.jobs_to_extend_locks': 6, + 'messaging.bullmq.worker.rate_limit': 7, + 'messaging.bullmq.worker.stalled_jobs': 8, + }); + }); + + it('leaves out the deprecated bullmq.job.finished.timestamp', () => { + const { span, otelSpan } = createSpan(); + + span.setAttributes({ + 'bullmq.job.finished.timestamp': 1791184722000, + 'bullmq.job.attempt_finished_timestamp': 1791184722000, + }); + + expect(otelSpan.setAttributes).toHaveBeenCalledWith({ + 'messaging.bullmq.job.attempt_finished_timestamp': 1791184722000, + }); + }); + + it('sends bullmq.job.status as messaging.bullmq.job.state', () => { + const { span, otelSpan } = createSpan(); + + span.setAttributes({ 'bullmq.job.status': 'completed' }); + + expect(otelSpan.setAttributes).toHaveBeenCalledWith({ + 'messaging.bullmq.job.state': 'completed', + }); + }); + + it('prefers bullmq.job.state when BullMQ sets it together with bullmq.job.status', () => { + const { span, otelSpan } = createSpan(); + + span.setAttributes({ 'bullmq.job.state': 'failed', 'bullmq.job.status': 'completed' }); + + expect(otelSpan.setAttributes).toHaveBeenCalledWith({ + 'messaging.bullmq.job.state': 'failed', + }); + }); + + it('passes events to the OTel span without renaming their attributes', () => { + const { span, otelSpan } = createSpan(); + + span.addEvent('job completed', { 'bullmq.job.result': '{"sent":true}' }); + + expect(otelSpan.addEvent).toHaveBeenCalledWith('job completed', { 'bullmq.job.result': '{"sent":true}' }); + }); + + describe('recordException', () => { + it('records Error instances on the OTel span without capturing them', () => { + const { span, otelSpan } = createSpan(); + const error = new Error('Redis connection refused'); + + span.recordException(error); + + expect(otelSpan.recordException).toHaveBeenCalledWith(error); + expect(SentryCore.captureException).not.toHaveBeenCalled(); + }); + + it('wraps non-Error exception objects in an Error', () => { + const { span, otelSpan } = createSpan(); + + span.recordException({ code: 503, message: 'Service unavailable' }); + + expect(otelSpan.recordException).toHaveBeenCalledWith(new Error('Service unavailable')); + expect(SentryCore.captureException).not.toHaveBeenCalled(); + }); + + it('wraps string exceptions in an Error', () => { + const { span, otelSpan } = createSpan(); + + (span as any).recordException('Connection timed out'); + + expect(otelSpan.recordException).toHaveBeenCalledWith(new Error('Connection timed out')); + expect(SentryCore.captureException).not.toHaveBeenCalled(); + }); + + it('uses fallback message when non-Error exception has no message', () => { + const { span, otelSpan } = createSpan(); + + span.recordException({ code: 500 }); + + expect(otelSpan.recordException).toHaveBeenCalledWith(new Error('Unknown error')); + expect(SentryCore.captureException).not.toHaveBeenCalled(); + }); + }); + + describe('addEvent', () => { + it('captures exception when event name is "job failed"', () => { + const { span } = createSpan(); + + span.addEvent('job failed', { 'bullmq.job.failed.reason': 'Connection refused' }); + + expect(SentryCore.captureException).toHaveBeenCalledWith(new Error('Connection refused'), { + mechanism: { handled: false, type: 'auto.queue.bullmq' }, + }); + }); + + it('sets the error status on the span when the event name is "job failed"', () => { + const { span, otelSpan } = createSpan(); + + span.addEvent('job failed', { 'bullmq.job.failed.reason': 'Connection refused' }); + + expect(otelSpan.setStatus).toHaveBeenCalledWith({ + code: SentryCore.SPAN_STATUS_ERROR, + message: 'internal_error', + }); + }); + + it('does not capture an exception or set an error status for other event names', () => { + const { span, otelSpan } = createSpan(); + + span.addEvent('job completed', { 'bullmq.job.result': '{"ok":true}' }); + + expect(SentryCore.captureException).not.toHaveBeenCalled(); + expect(otelSpan.setStatus).not.toHaveBeenCalled(); + }); + }); + + it('returns context with span and scope from setSpanOnContext', () => { + const { span, otelSpan, scope } = createSpan(); + const inputContext = { existingKey: 'value' }; + + const result = span.setSpanOnContext(inputContext); + + expect(result).toEqual({ existingKey: 'value', span: otelSpan, scope }); + }); + + it('delegates end to the underlying OTel span', () => { + const { span, otelSpan } = createSpan(); + + span.end(); + + expect(otelSpan.end).toHaveBeenCalledOnce(); + }); +}); + +describe('SentryBullMQContextManager', () => { + it('returns current active span and scope from active()', () => { + const telemetry = new BullMQTelemetry(); + const activeSpan = mockOtelSpan(); + const scope = mockScope(); + vi.mocked(SentryCore.getActiveSpan).mockReturnValueOnce(activeSpan as any); + vi.mocked(SentryCore.getCurrentScope).mockReturnValueOnce(scope as any); + + const context = telemetry.contextManager.active(); + + expect(context).toEqual({ span: activeSpan, scope }); + }); + + describe('with', () => { + it('calls withActiveSpan inside withIsolationScope when context has a span', () => { + const telemetry = new BullMQTelemetry(); + const fakeSpan = mockOtelSpan(); + const context = { span: fakeSpan, scope: mockScope() }; + const fn = vi.fn(() => 'result'); + + const result = telemetry.contextManager.with(context as any, fn); + + expect(result).toBe('result'); + expect(SentryCore.withIsolationScope).toHaveBeenCalledOnce(); + expect(SentryCore.withActiveSpan).toHaveBeenCalledWith(fakeSpan, fn); + }); + + it('clears the active span inside withIsolationScope when context has no span', () => { + const telemetry = new BullMQTelemetry(); + const context = { span: undefined, scope: mockScope() }; + const fn = vi.fn(() => 'result'); + + const result = telemetry.contextManager.with(context as any, fn); + + expect(result).toBe('result'); + expect(SentryCore.withIsolationScope).toHaveBeenCalledOnce(); + expect(SentryCore.withActiveSpan).toHaveBeenCalledWith(null, fn); + }); + }); + + describe('root', () => { + it('returns a context without a span that runs callbacks without an active span', () => { + const telemetry = new BullMQTelemetry(); + const fn = vi.fn(() => 'result'); + + const context = telemetry.contextManager.root!(); + const result = telemetry.contextManager.with(context, fn); + + expect(context.span).toBeUndefined(); + expect(result).toBe('result'); + expect(SentryCore.withActiveSpan).toHaveBeenCalledWith(null, fn); + }); + }); + + describe('getMetadata', () => { + it('returns sentry-trace header when context has a span', () => { + const telemetry = new BullMQTelemetry(); + const fakeSpan = mockOtelSpan(); + const context = { span: fakeSpan, scope: mockScope() }; + + const metadata = telemetry.contextManager.getMetadata(context as any); + + expect(SentryCore.spanToTraceHeader).toHaveBeenCalledWith(fakeSpan); + expect(metadata).toBe('00-traceid-spanid-01'); + }); + + it('returns empty string when context has no span', () => { + const telemetry = new BullMQTelemetry(); + const context = { span: undefined, scope: mockScope() }; + + const metadata = telemetry.contextManager.getMetadata(context as any); + + expect(metadata).toBe(''); + expect(SentryCore.spanToTraceHeader).not.toHaveBeenCalled(); + }); + }); + + describe('fromMetadata', () => { + it('parses sentry-trace header and attaches producerSpanContext', () => { + const telemetry = new BullMQTelemetry(); + const fakeSpan = mockOtelSpan(); + const context = { span: fakeSpan, scope: mockScope() }; + + const result = telemetry.contextManager.fromMetadata( + context as any, + 'aabbccddaabbccddaabbccddaabbccdd-1122334455667788-1', + ); + + expect(result).toEqual({ + span: fakeSpan, + scope: context.scope, + producerSpanContext: { + traceId: 'aabbccddaabbccddaabbccddaabbccdd', + spanId: '1122334455667788', + sampled: true, + }, + }); + }); + + it('marks the producer span context as not sampled when the sentry-trace header has no sampled flag', () => { + const telemetry = new BullMQTelemetry(); + const context = { span: undefined, scope: mockScope() }; + + const result = telemetry.contextManager.fromMetadata( + context as any, + 'aabbccddaabbccddaabbccddaabbccdd-1122334455667788', + ); + + expect(result.producerSpanContext).toEqual({ + traceId: 'aabbccddaabbccddaabbccddaabbccdd', + spanId: '1122334455667788', + sampled: false, + }); + }); + + it('returns the active context unchanged when metadata is empty', () => { + const telemetry = new BullMQTelemetry(); + const context = { span: undefined, scope: mockScope() }; + + const result = telemetry.contextManager.fromMetadata(context as any, ''); + + expect(result).toBe(context); + }); + + it('returns the active context unchanged when metadata is invalid', () => { + const telemetry = new BullMQTelemetry(); + const context = { span: undefined, scope: mockScope() }; + + const result = telemetry.contextManager.fromMetadata(context as any, 'not-a-valid-header'); + + expect(result).toBe(context); + }); + }); +}); + +describe('SentryBullMQMeter', () => { + describe('counter', () => { + it('delegates add to metrics.count with name and unit', () => { + const telemetry = new BullMQTelemetry({ enableMetrics: true }); + const counter = telemetry.meter!.createCounter('bullmq.jobs.completed', { unit: '1' }); + + counter.add(5, { 'queue.name': 'emails' }); + + expect(SentryCore.metrics.count).toHaveBeenCalledWith('messaging.bullmq.jobs.completed', 5, { + unit: 'none', + attributes: { 'queue.name': 'emails' }, + }); + }); + + it('passes undefined attributes when none provided', () => { + const telemetry = new BullMQTelemetry({ enableMetrics: true }); + const counter = telemetry.meter!.createCounter('bullmq.jobs.failed'); + + counter.add(1); + + expect(SentryCore.metrics.count).toHaveBeenCalledWith('messaging.bullmq.jobs.failed', 1, { + unit: undefined, + attributes: undefined, + }); + }); + }); + + describe('histogram', () => { + it('records bullmq.job.duration as messaging.process.duration in seconds', () => { + const telemetry = new BullMQTelemetry({ enableMetrics: true }); + const histogram = telemetry.meter!.createHistogram('bullmq.job.duration', { unit: 'ms' }); + + histogram.record(1500, { 'bullmq.queue.name': 'notifications' }); + + expect(SentryCore.metrics.distribution).toHaveBeenCalledWith('messaging.process.duration', 1.5, { + unit: 'second', + attributes: { + 'messaging.destination.name': 'notifications', + 'messaging.system': 'bullmq', + 'messaging.operation.name': 'process', + }, + }); + }); + + it('keeps a unit that is already a Sentry unit', () => { + const telemetry = new BullMQTelemetry({ enableMetrics: true }); + const histogram = telemetry.meter!.createHistogram('bullmq.job.payload', { unit: 'byte' }); + + histogram.record(512); + + expect(SentryCore.metrics.distribution).toHaveBeenCalledWith('messaging.bullmq.job.payload', 512, { + unit: 'byte', + attributes: undefined, + }); + }); + }); + + describe('gauge', () => { + it('delegates record to metrics.gauge', () => { + const telemetry = new BullMQTelemetry({ enableMetrics: true }); + const gauge = telemetry.meter!.createGauge!('bullmq.queue.jobs', { unit: '{jobs}' }); + + gauge.record(37, { 'queue.name': 'reports' }); + + expect(SentryCore.metrics.gauge).toHaveBeenCalledWith('messaging.bullmq.queue.jobs', 37, { + unit: 'none', + attributes: { 'queue.name': 'reports' }, + }); + }); + }); + + describe('attribute filtering', () => { + it('replaces the BullMQ queue name with the messaging destination', () => { + const telemetry = new BullMQTelemetry({ enableMetrics: true }); + const counter = telemetry.meter!.createCounter('bullmq.jobs.completed'); + + counter.add(1, { 'bullmq.queue.name': 'emails', 'bullmq.job.name': 'welcome-email' }); + + expect(SentryCore.metrics.count).toHaveBeenCalledWith('messaging.bullmq.jobs.completed', 1, { + unit: undefined, + attributes: { + 'messaging.bullmq.job.name': 'welcome-email', + 'messaging.destination.name': 'emails', + }, + }); + }); + + it('filters out array attribute values', () => { + const telemetry = new BullMQTelemetry({ enableMetrics: true }); + const counter = telemetry.meter!.createCounter('bullmq.jobs.completed'); + + counter.add(1, { + 'queue.name': 'emails', + tags: ['urgent', 'retry'], + priority: 5, + enabled: true, + }); + + expect(SentryCore.metrics.count).toHaveBeenCalledWith('messaging.bullmq.jobs.completed', 1, { + unit: undefined, + attributes: { + 'queue.name': 'emails', + priority: 5, + enabled: true, + }, + }); + }); + + it('returns empty object when all attributes are arrays', () => { + const telemetry = new BullMQTelemetry({ enableMetrics: true }); + const counter = telemetry.meter!.createCounter('bullmq.jobs.completed'); + + counter.add(1, { tags: ['a', 'b'] }); + + expect(SentryCore.metrics.count).toHaveBeenCalledWith('messaging.bullmq.jobs.completed', 1, { + unit: undefined, + attributes: {}, + }); + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0bb67d140bf1..7d319b2ae320 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -694,6 +694,9 @@ importers: body-parser: specifier: ^2.3.0 version: 2.3.0(supports-color@10.0.0) + bullmq: + specifier: ^5.79.1 + version: 5.81.5(redis@5.12.1(@opentelemetry/api@1.9.0))(supports-color@10.0.0) consola: specifier: ^3.2.3 version: 3.4.2 @@ -738,7 +741,7 @@ importers: version: 5.10.1(supports-color@10.0.0) ioredis-5: specifier: npm:ioredis@^5.11.0 - version: ioredis@5.11.0(supports-color@10.0.0) + version: ioredis@5.11.1(supports-color@10.0.0) ioredis-6: specifier: npm:ioredis@^6.0.0 version: ioredis@6.0.0(supports-color@10.0.0) @@ -1718,13 +1721,13 @@ importers: version: 2.0.1-rc.22(crossws@0.4.10(srvx@0.11.22)) nitro: specifier: ^3.0.260415-beta - version: 3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) srvx: specifier: ^0.11.22 version: 0.11.22 unstorage: specifier: ^2.0.0-alpha.7 - version: 2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@1.5.1) + version: 2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@1.5.1) packages/node: dependencies: @@ -1814,7 +1817,7 @@ importers: version: 4.20260519.1 '@nuxt/nitro-server': specifier: ^3.21.11 - version: 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.0(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + version: 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) '@nuxt/schema': specifier: ^3.21.11 version: 3.21.11 @@ -1829,7 +1832,7 @@ importers: version: 1.15.11 nitro: specifier: ^3.0.260311-beta - version: 3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) nitropack: specifier: ^2.13.4 version: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) @@ -1838,10 +1841,10 @@ importers: version: 3.25.1 nuxt: specifier: 3.21.11 - version: 3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c) + version: 3.21.11(955174819940f662d7743f134f0d89c6) unstorage: specifier: ^1.17.5 - version: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0)) + version: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) vite: specifier: ^8.3.1 version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) @@ -2309,7 +2312,7 @@ importers: version: 1.0.0(solid-js@1.9.11) '@solidjs/start': specifier: ^1.3.2 - version: 1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) '@solidjs/testing-library': specifier: 0.8.5 version: 0.8.5(@solidjs/router@1.0.0(solid-js@1.9.11))(solid-js@1.9.11) @@ -2333,7 +2336,7 @@ importers: version: 1.9.11 vinxi: specifier: ^0.5.11 - version: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) + version: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) vite: specifier: ^8.3.1 version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) @@ -6118,6 +6121,36 @@ packages: '@mongodb-js/saslprep@1.4.6': resolution: {integrity: sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': + resolution: {integrity: sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': + resolution: {integrity: sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': + resolution: {integrity: sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': + resolution: {integrity: sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': + resolution: {integrity: sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': + resolution: {integrity: sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==} + cpu: [x64] + os: [win32] + '@mswjs/interceptors@0.41.9': resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} engines: {node: '>=18'} @@ -10355,6 +10388,15 @@ packages: builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} + bullmq@5.81.5: + resolution: {integrity: sha512-pW7pFc/X+9AEIx91TWTdIpZ1icfbJnA0unsyzD7VICjCUw8a3qedSjej5dxxn6LjfM1kr2jvalOCGbCmoA5Kxg==} + engines: {node: '>=12.22.0'} + peerDependencies: + redis: '>=5.0.0' + peerDependenciesMeta: + redis: + optional: true + bun-types@1.2.9: resolution: {integrity: sha512-dk/kOEfQbajENN/D6FyiSgOKEuUi9PWfqKQJEgwKrCMWbjS/S6tEXp178mWvWAcUSYm9ArDlWHZKO3T/4cLXiw==} @@ -13172,8 +13214,8 @@ packages: resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} engines: {node: '>=12.22.0'} - ioredis@5.11.0: - resolution: {integrity: sha512-EZBErytyVovD8f6pDfG3Kb37N6Y3lmDA9NNj+4+IP13CzzHGeX+OyeRM2Um13khRzoBSzzL+5lVnCX8V2RLeMg==} + ioredis@5.11.1: + resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} engines: {node: '>=12.22.0'} ioredis@6.0.0: @@ -14716,6 +14758,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msgpackr-extract@3.0.4: + resolution: {integrity: sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==} + hasBin: true + + msgpackr@2.0.5: + resolution: {integrity: sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA==} + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -14925,6 +14974,9 @@ packages: resolution: {integrity: sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==} engines: {node: '>=10'} + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-addon-api@3.2.1: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} @@ -14952,6 +15004,10 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true @@ -24216,6 +24272,24 @@ snapshots: dependencies: sparse-bitfield: 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4': + optional: true + '@mswjs/interceptors@0.41.9': dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -24460,7 +24534,7 @@ snapshots: pkg-types: 2.3.1 semver: 7.8.5 - '@nuxt/devtools@3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': + '@nuxt/devtools@3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': dependencies: '@nuxt/devtools-kit': 3.4.1(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) '@nuxt/devtools-wizard': 3.4.1 @@ -24490,7 +24564,7 @@ snapshots: sirv: 3.0.2 structured-clone-es: 2.0.1 tinyglobby: 0.2.17 - unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0)) + unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) vite-plugin-inspect: 11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) vite-plugin-vue-tracer: 1.5.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) @@ -24656,7 +24730,7 @@ snapshots: - magic-string - unplugin - '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.0(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))': + '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.11(magicast@0.5.4) @@ -24674,7 +24748,7 @@ snapshots: klona: 2.0.6 mocked-exports: 0.1.1 nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.143.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) - nuxt: 3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c) + nuxt: 3.21.11(955174819940f662d7743f134f0d89c6) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.1 @@ -24682,7 +24756,7 @@ snapshots: std-env: 4.3.0 ufo: 1.6.4 unctx: 2.5.0 - unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0)) + unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) vue: 3.5.44(typescript@7.0.2) vue-bundle-renderer: 2.3.2 vue-devtools-stub: 0.1.0 @@ -24730,7 +24804,7 @@ snapshots: - webpack - xml2js - '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.0(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))': + '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.11(magicast@0.5.4) @@ -24748,7 +24822,7 @@ snapshots: klona: 2.0.6 mocked-exports: 0.1.1 nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) - nuxt: 3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c) + nuxt: 3.21.11(955174819940f662d7743f134f0d89c6) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.1 @@ -24756,7 +24830,7 @@ snapshots: std-env: 4.3.0 ufo: 1.6.4 unctx: 2.5.0 - unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0)) + unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) vue: 3.5.44(typescript@7.0.2) vue-bundle-renderer: 2.3.2 vue-devtools-stub: 0.1.0 @@ -24825,7 +24899,7 @@ snapshots: rc9: 3.1.0 std-env: 4.3.0 - '@nuxt/vite-builder@3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0)': + '@nuxt/vite-builder@3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0)': dependencies: '@nuxt/kit': 3.21.11(magicast@0.5.4) '@rollup/plugin-replace': 6.0.3(rollup@4.62.3) @@ -24845,7 +24919,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c) + nuxt: 3.21.11(955174819940f662d7743f134f0d89c6) nypm: 0.6.9 ohash: 2.0.11 pathe: 2.0.3 @@ -26633,11 +26707,11 @@ snapshots: dependencies: solid-js: 1.9.11 - '@solidjs/start@1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@solidjs/start@1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': dependencies: '@tanstack/server-functions-plugin': 1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0)) - '@vinxi/server-components': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0)) + '@vinxi/server-components': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0)) cookie-es: 2.0.1 defu: 6.1.7 error-stack-parser: 2.1.4 @@ -26649,7 +26723,7 @@ snapshots: source-map-js: 1.2.2 terracotta: 1.0.5(solid-js@1.9.11) tinyglobby: 0.2.17 - vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) + vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) vite-plugin-solid: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) transitivePeerDependencies: - '@testing-library/jest-dom' @@ -27590,7 +27664,7 @@ snapshots: untun: 0.1.3 uqr: 0.1.3 - '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))': + '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))': dependencies: '@babel/parser': 7.29.9 acorn: 8.18.0 @@ -27601,18 +27675,18 @@ snapshots: magicast: 0.2.11 recast: 0.23.11 tslib: 2.8.1 - vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) + vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) - '@vinxi/server-components@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))': + '@vinxi/server-components@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))': dependencies: - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0)) acorn: 8.18.0 acorn-loose: 8.4.0 acorn-typescript: 1.4.13(acorn@8.18.0) astring: 1.9.0 magicast: 0.2.11 recast: 0.23.11 - vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) + vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) '@vitejs/plugin-vue-jsx@5.1.6(supports-color@10.0.0)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': dependencies: @@ -29082,6 +29156,19 @@ snapshots: dependencies: semver: 7.8.5 + bullmq@5.81.5(redis@5.12.1(@opentelemetry/api@1.9.0))(supports-color@10.0.0): + dependencies: + cron-parser: 4.9.0 + ioredis: 5.11.1(supports-color@10.0.0) + msgpackr: 2.0.5 + node-abort-controller: 3.1.1 + semver: 7.8.5 + tslib: 2.8.1 + optionalDependencies: + redis: 5.12.1(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + bun-types@1.2.9: dependencies: '@types/node': 20.19.43 @@ -32557,7 +32644,7 @@ snapshots: transitivePeerDependencies: - supports-color - ioredis@5.11.0(supports-color@10.0.0): + ioredis@5.11.1(supports-color@10.0.0): dependencies: '@ioredis/commands': 1.10.0 cluster-key-slot: 1.1.1 @@ -34350,6 +34437,22 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.4: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.4 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.4 + optional: true + + msgpackr@2.0.5: + optionalDependencies: + msgpackr-extract: 3.0.4 + muggle-string@0.4.1: {} multer@2.1.1: @@ -34513,7 +34616,7 @@ snapshots: node-gyp-build: 4.6.0 optional: true - nitro@3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + nitro@3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.10(srvx@0.11.22) @@ -34528,7 +34631,7 @@ snapshots: rolldown: 1.2.11 srvx: 0.11.22 unenv: 2.0.0-rc.24 - unstorage: 2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@2.0.0-alpha.3) + unstorage: 2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@2.0.0-alpha.3) optionalDependencies: dotenv: 17.4.2 giget: 3.3.1 @@ -34566,7 +34669,7 @@ snapshots: - sqlite3 - uploadthing - nitro@3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + nitro@3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.10(srvx@0.11.22) @@ -34581,7 +34684,7 @@ snapshots: rolldown: 1.2.11 srvx: 0.11.22 unenv: 2.0.0-rc.24 - unstorage: 2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@2.0.0-alpha.3) + unstorage: 2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@2.0.0-alpha.3) optionalDependencies: dotenv: 17.4.2 giget: 3.3.1 @@ -34965,6 +35068,8 @@ snapshots: dependencies: semver: 7.8.5 + node-abort-controller@3.1.1: {} + node-addon-api@3.2.1: optional: true @@ -34982,6 +35087,11 @@ snapshots: node-forge@1.4.0: {} + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.1.2 + optional: true + node-gyp-build@4.6.0: {} node-gyp@9.3.0(supports-color@10.0.0): @@ -35147,16 +35257,16 @@ snapshots: nuxi@3.25.1: {} - nuxt@3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c): + nuxt@3.21.11(955174819940f662d7743f134f0d89c6): dependencies: '@dxup/nuxt': 0.5.6(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(esbuild@0.28.1)(giget@3.3.1)(jiti@2.7.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) '@nuxt/cli': 3.37.0(@nuxt/schema@3.21.11)(@parcel/watcher@2.5.6)(cac@6.7.14)(commander@14.0.3)(magicast@0.5.4)(supports-color@10.0.0) - '@nuxt/devtools': 3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) + '@nuxt/devtools': 3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) '@nuxt/kit': 3.21.11(magicast@0.5.4) - '@nuxt/nitro-server': 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.0(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + '@nuxt/nitro-server': 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) '@nuxt/schema': 3.21.11 '@nuxt/telemetry': 2.8.0(@nuxt/kit@3.21.11(magicast@0.5.4)) - '@nuxt/vite-builder': 3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(0dc0d2df1a20b551c0e4dd687b74c07c))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0) + '@nuxt/vite-builder': 3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0) '@unhead/vue': 2.1.17(vue@3.5.44(typescript@7.0.2)) '@vue/shared': 3.5.44 c12: 3.3.4(magicast@0.5.4) @@ -39486,7 +39596,7 @@ snapshots: db0: 0.3.4(mysql2@3.23.3(@types/node@20.19.43)) ioredis: 5.10.1(supports-color@10.0.0) - unstorage@1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0)): + unstorage@1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)): dependencies: anymatch: 3.1.3 chokidar: 5.0.0 @@ -39499,7 +39609,7 @@ snapshots: optionalDependencies: '@azure/storage-blob': 12.31.0(supports-color@10.0.0) db0: 0.3.4(mysql2@3.23.3(@types/node@20.19.43)) - ioredis: 5.11.0(supports-color@10.0.0) + ioredis: 5.11.1(supports-color@10.0.0) unstorage@2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@3.7.4)(ofetch@1.5.1): optionalDependencies: @@ -39511,22 +39621,22 @@ snapshots: mongodb: 3.7.4 ofetch: 1.5.1 - unstorage@2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@1.5.1): + unstorage@2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@1.5.1): optionalDependencies: '@azure/storage-blob': 12.31.0(supports-color@10.0.0) chokidar: 5.0.0 db0: 0.3.4(mysql2@3.23.3(@types/node@20.19.43)) - ioredis: 5.11.0(supports-color@10.0.0) + ioredis: 5.11.1(supports-color@10.0.0) lru-cache: 11.4.0 mongodb: 7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7) ofetch: 1.5.1 - unstorage@2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@2.0.0-alpha.3): + unstorage@2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(ofetch@2.0.0-alpha.3): optionalDependencies: '@azure/storage-blob': 12.31.0(supports-color@10.0.0) chokidar: 5.0.0 db0: 0.3.4(mysql2@3.23.3(@types/node@20.19.43)) - ioredis: 5.11.0(supports-color@10.0.0) + ioredis: 5.11.1(supports-color@10.0.0) lru-cache: 11.4.0 mongodb: 7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7) ofetch: 2.0.0-alpha.3 @@ -39647,7 +39757,7 @@ snapshots: '@types/unist': 3.0.0 vfile-message: 4.0.3 - vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.0(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0): + vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0): dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.0.0)) @@ -39680,7 +39790,7 @@ snapshots: ufo: 1.6.4 unctx: 2.5.0 unenv: 1.10.0 - unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.0(supports-color@10.0.0)) + unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) vite: 6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) zod: 4.6.7 transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4a08da54755f..f0da0985135e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -108,6 +108,7 @@ allowBuilds: core-js-pure: false esbuild: true mongodb-memory-server-global: true + msgpackr-extract: true nice-napi: true nx: true prisma: true From fa13bed62c7f40913f0cdb412a1d36d6bd0c79af Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Thu, 29 Oct 2026 12:47:30 +0500 Subject: [PATCH 007/169] ci: Drop dead matrix reference in Deno unit tests job (#25852) **Why:** The Deno unit tests job has no `strategy.matrix`, so `matrix.deno-version` is always empty and the step always falls back to `v2.8.3`. This pins the version directly, the same way the Deno integration tests job already does. The orchestrion-mysql TODO from the issue is already gone on `develop`. Closes #23898 Co-authored-by: Claude Opus 5.5 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f6d386c40b43..8766de594d0c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -553,7 +553,7 @@ jobs: - name: Set up Deno uses: denoland/setup-deno@v2.0.5 with: - deno-version: ${{ matrix.deno-version || 'v2.8.3' }} + deno-version: 'v2.8.3' - name: Restore caches uses: ./.github/actions/restore-cache - name: Run tests From eaa99976c5262da2f243162dc68c703e16daa0d8 Mon Sep 17 00:00:00 2001 From: "javascript-sdk-gitflow[bot]" <255134079+javascript-sdk-gitflow[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 08:51:57 +0100 Subject: [PATCH 008/169] chore: Add external contributor to CHANGELOG.md (#25862) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #25852 Co-authored-by: nicohrubec <29484629+nicohrubec@users.noreply.github.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0d0150cd82..964bb5cd0f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +Work in this release was contributed by @lazerg. Thank you for your contribution! + ## 11.10.0 ### Important Changes From fddeafcbe234c79d2bc53c4a416f4c66a55aba0b Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 29 Oct 2026 09:13:04 +0100 Subject: [PATCH 009/169] test(e2e): Remove node-flue-send-to-sentry (#25839) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** `node-flue` already runs the same Flue turns against real OpenRouter and checks a superset of what this app checks (6 tests vs 4, plus dev mode and the same `(latest)` variant), just through the event proxy. Whether the spans land in Sentry isn't something we need to test for each AI integration, so this app only adds CI time and a slow, flaky poll against the Sentry API. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- .../node-flue-send-to-sentry/.gitignore | 7 -- .../node-flue-send-to-sentry/flue.config.ts | 5 - .../node-flue-send-to-sentry/package.json | 49 -------- .../playwright.config.ts | 24 ---- .../pnpm-workspace.yaml | 6 - .../node-flue-send-to-sentry/sentry-init.ts | 11 -- .../src/agents/hello.ts | 50 --------- .../node-flue-send-to-sentry/src/app.ts | 10 -- .../tests/dataloader.test.ts | 25 ----- .../tests/errors.test.ts | 36 ------ .../tests/flue.test.ts | 105 ------------------ .../node-flue-send-to-sentry/tests/utils.ts | 60 ---------- .../node-flue-send-to-sentry/tsconfig.json | 14 --- .../node-flue-send-to-sentry/vite.config.ts | 7 -- 14 files changed, 409 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/.gitignore delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/flue.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/playwright.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/pnpm-workspace.yaml delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/sentry-init.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/src/agents/hello.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/src/app.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/dataloader.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/errors.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/flue.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/utils.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/vite.config.ts diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/.gitignore b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/.gitignore deleted file mode 100644 index 2685f6ec088a..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -dist -.flue -*.tsbuildinfo -results.junit.xml -test-results -playwright-report diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/flue.config.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/flue.config.ts deleted file mode 100644 index a31c8523a9a7..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/flue.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { defineConfig } from '@flue/runtime/config'; - -export default defineConfig({ - target: 'node', -}); diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/package.json deleted file mode 100644 index 409652d5c19e..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "node-flue-send-to-sentry", - "description": "The Flue integration as a user runs it: a built Flue agent server, real tool-calling turns through OpenRouter, and the gen_ai spans read back from a real Sentry project", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "vite build", - "start": "NODE_OPTIONS='--import=@sentry/node/import' PORT=3030 node dist/server.mjs", - "test": "OPENROUTER_API_KEY=$E2E_OPENROUTER_API_KEY playwright test", - "clean": "npx rimraf node_modules dist pnpm-lock.yaml", - "test:build": "pnpm install && pnpm build", - "test:build-latest": "pnpm install && pnpm add @flue/runtime@latest @flue/vite@latest @flue/cli@latest && pnpm build", - "test:assert": "pnpm test" - }, - "dependencies": { - "@flue/runtime": "2.0.5", - "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "dataloader": "^2.2.3", - "hono": "^4.7.0", - "valibot": "^1.5.0" - }, - "devDependencies": { - "@flue/cli": "2.0.5", - "@flue/vite": "2.0.5", - "@playwright/test": "1.64.1", - "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@sentry/conventions": "0.27.0", - "@types/node": "24.x", - "sentry": "~0.44.1", - "typescript": "~5.9.0", - "vite": "^8.0.14" - }, - "engines": { - "node": "24.x" - }, - "volta": { - "extends": "../../package.json" - }, - "sentryTest": { - "optional": true, - "optionalVariants": [ - { - "build-command": "pnpm test:build-latest", - "label": "node-flue-send-to-sentry (latest)" - } - ] - } -} diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/playwright.config.ts deleted file mode 100644 index 161e4b1c5d72..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/playwright.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineConfig } from '@playwright/test'; - -const port = 3030; - -export default defineConfig({ - testDir: './tests', - // A model turn, then Sentry polls of up to 180s each. Spans become queryable within the first one. - timeout: 400_000, - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: 0, - // Every test spends most of its time polling Sentry, so run them all at once. - workers: '100%', - reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', - use: { - baseURL: `http://localhost:${port}`, - }, - webServer: { - command: 'pnpm start', - port, - stdout: 'pipe', - stderr: 'pipe', - }, -}); diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/pnpm-workspace.yaml deleted file mode 100644 index 0e523a16e6f0..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/pnpm-workspace.yaml +++ /dev/null @@ -1,6 +0,0 @@ -allowBuilds: - '@google/genai': false - core-js-pure: false - esbuild: true - protobufjs: false -minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/sentry-init.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/sentry-init.ts deleted file mode 100644 index 30f0b4c8cd49..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/sentry-init.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { instrument } from '@flue/runtime'; -import * as Sentry from '@sentry/node'; - -// No tunnel: the spans go to the Sentry project behind `E2E_TEST_DSN`, where the tests read them back. -Sentry.init({ - environment: 'qa', // dynamic sampling bias to keep transactions - dsn: process.env.E2E_TEST_DSN, - tracesSampleRate: 1.0, -}); - -instrument(Sentry.createFlueInstrumentation()); diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/src/agents/hello.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/src/agents/hello.ts deleted file mode 100644 index 9d218454a86e..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/src/agents/hello.ts +++ /dev/null @@ -1,50 +0,0 @@ -'use agent'; -import { useModel, useTool } from '@flue/runtime'; -import * as Sentry from '@sentry/node'; -import * as v from 'valibot'; -import DataLoader from 'dataloader'; - -export function Hello() { - useModel('openrouter/anthropic/claude-haiku-4.5'); - - useTool({ - name: 'get_weather', - description: 'Get the current weather for a city.', - input: v.object({ city: v.string() }), - // Wrapped in a manual span: Flue runs the tool while the SDK's `execute_tool` span is active, - // so this should nest directly under it rather than landing beside it. - run: ({ data }) => - Sentry.startSpan( - { name: 'resolve-weather', attributes: { 'weather.source': 'static-table', 'weather.city': data.city } }, - () => { - return `It is 21 degrees and sunny in ${data.city}.`; - }, - ), - }); - - // Called from inside a tool on purpose: the dataloader span then lands under `execute_tool` in - // the agent's trace, which is what "captured alongside the AI spans" has to mean. - useTool({ - name: 'count_items', - description: 'Count items by loading them. Call this when the user asks to count items.', - input: v.object({}), - // Constructed per execution, like node-eve does: a module-level loader caches its keys, so a - // second call would skip the batch function and emit no span. - run: async () => { - const loader = new DataLoader(async keys => keys.map(key => key * 2)); - const doubled = await Promise.all([loader.load(1), loader.load(2), loader.load(3)]); - return `Loaded ${doubled.length} items: ${doubled.join(', ')}.`; - }, - }); - - useTool({ - name: 'fail_now', - description: 'Always throws an error. Call this when the user asks to trigger a failure.', - input: v.object({}), - run: () => { - throw new Error('Intentional flue tool failure'); - }, - }); - - return 'You are a helpful assistant. Use get_weather when asked about weather, count_items when asked to count items, and fail_now when asked to fail.'; -} diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/src/app.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/src/app.ts deleted file mode 100644 index b54a3357e4ca..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/src/app.ts +++ /dev/null @@ -1,10 +0,0 @@ -import '../sentry-init.ts'; -import { createAgentRouter } from '@flue/runtime/routing'; -import { Hono } from 'hono'; -import { Hello } from './agents/hello.ts'; - -const app = new Hono(); - -app.route('/agents/hello', createAgentRouter(Hello)); - -export default app; diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/dataloader.test.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/dataloader.test.ts deleted file mode 100644 index ba6a62fc8f7d..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/dataloader.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { SEARCH_ORIGIN } from '@sentry/conventions/attributes/search'; -import { CACHE_GET } from '@sentry/conventions/op'; -import { EVENT_POLLING_OPTIONS, fetchSpanAttributes } from '@sentry-internal/test-utils/cli'; -import { newInstanceId, runAgentTurn, waitForConversationTrace } from './utils'; - -// Orchestrion only hooks `dataloader` with the loader preloaded (`--import` in the start script). -test('sends orchestrion-instrumented dataloader spans in the same trace as the AI spans', async ({ baseURL }) => { - const conversationId = await runAgentTurn( - baseURL!, - newInstanceId('dataloader'), - 'Please call count_items to count the items.', - ); - - const { traceId, spans } = await waitForConversationTrace( - conversationId, - spans => - spans.some(span => span.description === 'execute_tool count_items') && spans.some(span => span.op === CACHE_GET), - ); - - const dataloaderSpan = spans.find(span => span.op === CACHE_GET); - await expect - .poll(() => fetchSpanAttributes(traceId, dataloaderSpan!.event_id!), EVENT_POLLING_OPTIONS) - .toMatchObject({ [SEARCH_ORIGIN]: 'auto.db.dataloader' }); -}); diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/errors.test.ts deleted file mode 100644 index aa4666c1a3bf..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/errors.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { SEARCH_GEN_AI__TOOL__NAME, SEARCH_SPAN__STATUS } from '@sentry/conventions/attributes/search'; -import { - EVENT_POLLING_OPTIONS, - fetchEvent, - fetchSpanAttributes, - findErrorInTrace, -} from '@sentry-internal/test-utils/cli'; -import type { TraceItem } from '@sentry-internal/test-utils/cli'; -import { newInstanceId, runAgentTurn, waitForConversationTrace } from './utils'; - -test('sends an error thrown inside a Flue tool and marks its span errored', async ({ baseURL }) => { - const conversationId = await runAgentTurn( - baseURL!, - newInstanceId('failure'), - 'Please call fail_now to trigger a failure.', - ); - - const { traceId, spans } = await waitForConversationTrace(conversationId, spans => - spans.some(span => span.description === 'execute_tool fail_now'), - ); - - let error: TraceItem | undefined; - await expect.poll(() => (error = findErrorInTrace(traceId)), EVENT_POLLING_OPTIONS).toBeDefined(); - await expect - .poll( - () => fetchEvent(error!.event_id!)?.entries.find(entry => entry.type === 'exception')?.data.values?.[0], - EVENT_POLLING_OPTIONS, - ) - .toMatchObject({ value: 'Intentional flue tool failure', mechanism: { type: 'auto.ai.flue' } }); - - const executeTool = spans.find(span => span.description === 'execute_tool fail_now'); - await expect - .poll(() => fetchSpanAttributes(traceId, executeTool!.event_id!), EVENT_POLLING_OPTIONS) - .toMatchObject({ [SEARCH_GEN_AI__TOOL__NAME]: 'fail_now', [SEARCH_SPAN__STATUS]: 'error' }); -}); diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/flue.test.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/flue.test.ts deleted file mode 100644 index 6610ad009546..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/flue.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { - SEARCH_GEN_AI__AGENT__NAME, - SEARCH_GEN_AI__CONVERSATION__ID, - SEARCH_GEN_AI__COST__TOTAL_TOKENS, - SEARCH_GEN_AI__INPUT__MESSAGES, - SEARCH_GEN_AI__OPERATION__NAME, - SEARCH_GEN_AI__PROVIDER__NAME, - SEARCH_GEN_AI__TOOL__CALL__ARGUMENTS, - SEARCH_GEN_AI__TOOL__CALL__RESULT, - SEARCH_GEN_AI__TOOL__NAME, - SEARCH_GEN_AI__USAGE__INPUT_TOKENS, - SEARCH_GEN_AI__USAGE__OUTPUT_TOKENS, - SEARCH_ORIGIN, - SEARCH_SPAN__STATUS, -} from '@sentry/conventions/attributes/search'; -import { - GEN_AI_CHAT, - GEN_AI_EXECUTE_TOOL, - GEN_AI_INVOKE_AGENT, - HTTP_CLIENT, - HTTP_SERVER, -} from '@sentry/conventions/op'; -import type { TraceItem } from '@sentry-internal/test-utils/cli'; -import { EVENT_POLLING_OPTIONS, fetchSpanAttributes } from '@sentry-internal/test-utils/cli'; -import { newInstanceId, runAgentTurn, waitForConversationTrace } from './utils'; - -test('sends the request / invoke_agent / chat / execute_tool hierarchy of a Flue turn', async ({ baseURL }) => { - const conversationId = await runAgentTurn(baseURL!, newInstanceId('weather'), 'What is the weather in Paris?'); - - // Spans land at different times, so wait for the relationships checked below, not just the ops. - const isProviderCall = (span: TraceItem, spans: TraceItem[]) => - span.op === HTTP_CLIENT && spans.some(chat => chat.op === GEN_AI_CHAT && chat.event_id === span.parent_span_id); - const { traceId, spans } = await waitForConversationTrace( - conversationId, - spans => - [HTTP_SERVER, GEN_AI_INVOKE_AGENT, GEN_AI_EXECUTE_TOOL].every(op => spans.some(span => span.op === op)) && - spans.some(span => isProviderCall(span, spans)), - ); - - const request = spans.find(span => span.op === HTTP_SERVER); - const agents = spans.filter(span => span.op === GEN_AI_INVOKE_AGENT); - const invokeAgent = agents[0]; - const chats = spans.filter(span => span.op === GEN_AI_CHAT); - const executeTool = spans.find(span => span.description === 'execute_tool get_weather'); - - // More than one means other conversations leaked into this trace (withastro/flue#813). - expect(agents).toHaveLength(1); - expect(invokeAgent?.parent_span_id).toBe(request?.event_id); - // A chat span anywhere else would be a provider SDK reporting the same call a second time. - expect(chats.map(chat => chat.parent_span_id)).toEqual(chats.map(() => invokeAgent?.event_id)); - expect(executeTool?.parent_span_id).toBe(invokeAgent?.event_id); - - await expect - .poll(() => fetchSpanAttributes(traceId, invokeAgent!.event_id!), EVENT_POLLING_OPTIONS) - .toMatchObject({ - [SEARCH_ORIGIN]: 'auto.ai.flue', - [SEARCH_GEN_AI__OPERATION__NAME]: 'invoke_agent', - [SEARCH_GEN_AI__AGENT__NAME]: 'Hello', - [SEARCH_GEN_AI__CONVERSATION__ID]: conversationId, - }); - - let chatAttributes: Record | undefined; - await expect - .poll(() => (chatAttributes = fetchSpanAttributes(traceId, chats[0]!.event_id!)), EVENT_POLLING_OPTIONS) - .toMatchObject({ - [SEARCH_ORIGIN]: 'auto.ai.flue', - [SEARCH_SPAN__STATUS]: 'ok', - [SEARCH_GEN_AI__PROVIDER__NAME]: 'openrouter', - [SEARCH_GEN_AI__INPUT__MESSAGES]: expect.stringContaining('What is the weather in Paris?'), - }); - // The trace-items API returns integer attributes as strings. - expect(Number(chatAttributes![SEARCH_GEN_AI__USAGE__INPUT_TOKENS])).toBeGreaterThan(0); - expect(Number(chatAttributes![SEARCH_GEN_AI__USAGE__OUTPUT_TOKENS])).toBeGreaterThan(0); - // Flue computes cost itself; no provider SDK reports it. - expect(chatAttributes![SEARCH_GEN_AI__COST__TOTAL_TOKENS]).toBeGreaterThan(0); - - await expect - .poll(() => fetchSpanAttributes(traceId, executeTool!.event_id!), EVENT_POLLING_OPTIONS) - .toMatchObject({ - [SEARCH_ORIGIN]: 'auto.ai.flue', - [SEARCH_GEN_AI__TOOL__NAME]: 'get_weather', - // The model picks the argument (`Paris`, `Paris, France`, ...). - [SEARCH_GEN_AI__TOOL__CALL__ARGUMENTS]: expect.stringContaining('Paris'), - [SEARCH_GEN_AI__TOOL__CALL__RESULT]: expect.stringMatching(/^It is 21 degrees and sunny in Paris/), - }); -}); - -test('nests a manual span raised inside a tool under that tool span', async ({ baseURL }) => { - const conversationId = await runAgentTurn(baseURL!, newInstanceId('manual-span'), 'What is the weather in Berlin?'); - - const { traceId, spans } = await waitForConversationTrace(conversationId, spans => - spans.some( - span => span.description === 'resolve-weather' && spans.some(parent => parent.event_id === span.parent_span_id), - ), - ); - - const manualSpan = spans.find(span => span.description === 'resolve-weather'); - const parent = spans.find(span => span.event_id === manualSpan?.parent_span_id); - expect(parent?.description).toBe('execute_tool get_weather'); - - await expect - .poll(() => fetchSpanAttributes(traceId, manualSpan!.event_id!), EVENT_POLLING_OPTIONS) - .toMatchObject({ 'weather.source': 'static-table', 'weather.city': expect.stringContaining('Berlin') }); -}); diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/utils.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/utils.ts deleted file mode 100644 index 7b3f2814266f..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tests/utils.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { expect } from '@playwright/test'; -import { SEARCH_GEN_AI__CONVERSATION__ID } from '@sentry/conventions/attributes/search'; -import type { TraceItem } from '@sentry-internal/test-utils/cli'; -import { - EVENT_POLLING_OPTIONS, - fetchTrace, - findTraceIdOfSpan, - flattenTrace, - traceTarget, -} from '@sentry-internal/test-utils/cli'; - -/** A fresh instance id, so an earlier settlement cannot end the wait early. */ -export function newInstanceId(prefix: string): string { - return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; -} - -/** Send one message and wait for the turn to settle. Returns the conversation id. */ -export async function runAgentTurn(baseURL: string, instanceId: string, message: string): Promise { - const url = `${baseURL}/agents/hello/${instanceId}`; - - const res = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ kind: 'user', body: message }), - }); - const body = await res.text(); - expect(res.status, body).toBe(202); - - const deadline = Date.now() + 90_000; - while (Date.now() < deadline) { - const conversation = (await (await fetch(url)).json()) as { conversationId: string; settlements?: unknown[] }; - if (conversation.settlements?.length) { - return conversation.conversationId; - } - await new Promise(resolve => setTimeout(resolve, 1_000)); - } - - throw new Error(`Flue turn for "${instanceId}" did not settle within 90s`); -} - -/** The router answers `202` before the turn runs, so the trace is found by conversation id. */ -export async function waitForConversationTrace( - conversationId: string, - isComplete: (spans: TraceItem[]) => boolean, -): Promise<{ traceId: string; spans: TraceItem[] }> { - let traceId: string | undefined; - await expect - .poll( - () => (traceId = findTraceIdOfSpan(`${SEARCH_GEN_AI__CONVERSATION__ID}:${conversationId}`)), - EVENT_POLLING_OPTIONS, - ) - .toBeDefined(); - - console.log(`Polling for the agent spans: sentry trace view ${traceTarget(traceId!)}`); - - let spans: TraceItem[] = []; - await expect.poll(() => isComplete((spans = flattenTrace(fetchTrace(traceId!)))), EVENT_POLLING_OPTIONS).toBe(true); - - return { traceId: traceId!, spans }; -} diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tsconfig.json deleted file mode 100644 index 2cb98b13c9f8..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "esnext", - "moduleResolution": "bundler", - "types": ["node"], - "strict": true, - "allowImportingTsExtensions": true, - "esModuleInterop": true, - "skipLibCheck": true, - "noEmit": true - }, - "include": ["src/**/*.ts", "sentry-init.ts", "flue.config.ts", "vite.config.ts"] -} diff --git a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/vite.config.ts b/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/vite.config.ts deleted file mode 100644 index a77574b64290..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-flue-send-to-sentry/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { flue } from '@flue/vite'; -import { defineConfig } from 'vite'; - -// As `flue init` scaffolds it. Flue keeps dependencies as bare imports, so orchestrion can still hook them. -export default defineConfig({ - plugins: [flue()], -}); From 61d2b2688f09e23470f5f696008bfaff7dbb3c6b Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 29 Oct 2026 09:55:18 +0100 Subject: [PATCH 010/169] ref(cloudflare): Simplify shared SDK initialization (#25864) **Why:** The main SDK and `/request` already use the same default integrations, so passing a callback to select the default integrations is no longer necessary. This PR simplifies the setup. **How to review:** Both entry points now share `initBaseSdk()` and the same default integrations. Check that `sdk.ts` still handles optional OpenTelemetry setup, while `/request` imports only the base initializer and never reaches `sdk.ts`. Existing module-graph and OpenTelemetry tests cover this boundary. supersedes #25853 closes https://github.com/getsentry/sentry-javascript/issues/24039 Co-authored-by: Codex --- packages/cloudflare/src/baseSdk.ts | 34 +++++-------------- packages/cloudflare/src/request.ts | 7 ++-- packages/cloudflare/src/sdk.ts | 17 +++------- .../src/wrapRequestHandlerWithInit.ts | 2 +- .../test/requestModuleGraph.test.ts | 2 +- 5 files changed, 18 insertions(+), 44 deletions(-) diff --git a/packages/cloudflare/src/baseSdk.ts b/packages/cloudflare/src/baseSdk.ts index af1d2d681f66..43b5b9604fcd 100644 --- a/packages/cloudflare/src/baseSdk.ts +++ b/packages/cloudflare/src/baseSdk.ts @@ -41,14 +41,10 @@ function getRegisteredChannelIntegrations(): Integration[] { } /** - * Get the default integrations that run on any Workers-compatible runtime, i.e. without the - * `nodejs_compat` compatibility flag. - * - * `getDefaultIntegrations` in `sdk.ts` extends this set with the integrations that do depend on - * Node.js APIs. Keeping the two apart is what allows `wrapRequestHandler` to stay usable on runtimes - * that cannot enable `nodejs_compat`, such as Shopify Oxygen. + * Get the default integrations for the Cloudflare SDK. + * These also work on Workers-compatible runtimes without the `nodejs_compat` compatibility flag. */ -export function getBaseDefaultIntegrations(options: CloudflareOptions): Integration[] { +export function getDefaultIntegrations(options: CloudflareOptions): Integration[] { return [ // The Dedupe integration should not be used in workflows because we want to // capture all step failures, even if they are the same error. @@ -72,12 +68,10 @@ export function getBaseDefaultIntegrations(options: CloudflareOptions): Integrat } /** - * Initializes the Cloudflare SDK with the passed default integrations. + * Initializes the Cloudflare SDK without setting up an OpenTelemetry tracer provider. * - * The default integrations are injected rather than imported so that this module stays free of - * Node.js-only code. `request.ts` — which backs both `wrapRequestHandler` and the - * `@sentry/cloudflare/request` entry point, and therefore has to work on runtimes without the - * `nodejs_compat` compatibility flag — creates its client from here instead of from `sdk.ts`. + * Kept separate from `sdk.ts` so `@sentry/cloudflare/request` can load without Node.js-only + * dependencies on runtimes such as Shopify Oxygen. * * The client is cached and reused across invocations within the same isolate, * unless `cacheClient: false` is passed. This avoids the @@ -87,10 +81,7 @@ export function getBaseDefaultIntegrations(options: CloudflareOptions): Integrat * boundary that reliably extends execution, so spans/events that end after * disposal would otherwise be lost. */ -export function initWithDefaultIntegrations( - options: CloudflareOptions, - getDefaultIntegrationsImpl: (options: CloudflareOptions) => Integration[], -): CloudflareClient | undefined { +export function initBaseSdk(options: CloudflareOptions): CloudflareClient | undefined { const cached = getCachedClient(); const cacheEnabled = options.cacheClient !== false; @@ -111,7 +102,7 @@ export function initWithDefaultIntegrations( // included, unless an SDK built the list from the defaults. const usesSdkDefaults = options.defaultIntegrations === undefined || !!options._extendsDefaultIntegrations; if (options.defaultIntegrations === undefined) { - options.defaultIntegrations = getDefaultIntegrationsImpl(options); + options.defaultIntegrations = getDefaultIntegrations(options); } // A cached client outlives any single invocation, so binding it to one @@ -191,12 +182,3 @@ export function initWithDefaultIntegrations( return client; } - -/** - * Initializes the Cloudflare SDK with only the default integrations from - * {@link getBaseDefaultIntegrations}, i.e. those that work without the `nodejs_compat` - * compatibility flag. - */ -export function initBaseSdk(options: CloudflareOptions): CloudflareClient | undefined { - return initWithDefaultIntegrations(options, getBaseDefaultIntegrations); -} diff --git a/packages/cloudflare/src/request.ts b/packages/cloudflare/src/request.ts index 760398c38b73..7f504dedcdef 100644 --- a/packages/cloudflare/src/request.ts +++ b/packages/cloudflare/src/request.ts @@ -5,10 +5,9 @@ import { type RequestHandlerWrapperOptions, wrapRequestHandlerWithInit } from '. /** * Wraps a cloudflare request handler in Sentry instrumentation. * - * The client is set up with the default integrations that work without the `nodejs_compat` - * compatibility flag, so that this also works on runtimes that cannot enable it (e.g. Shopify - * Oxygen). On a runtime that has `nodejs_compat`, pass `defaultIntegrations: - * getDefaultIntegrations(options)` in `options` to get the full set instead. + * The client uses the same default integrations as `init`, without setting up an OpenTelemetry + * tracer provider. This entry point also works on runtimes that cannot enable the `nodejs_compat` + * compatibility flag, such as Shopify Oxygen. */ export function wrapRequestHandler( wrapperOptions: Omit & { diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index 4f4148396040..649bd589794e 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -1,5 +1,4 @@ -import type { Integration } from '@sentry/core'; -import { getBaseDefaultIntegrations, initWithDefaultIntegrations } from './baseSdk'; +import { initBaseSdk } from './baseSdk'; import type { CloudflareClient, CloudflareOptions } from './client'; import { setupOpenTelemetryTracer } from './opentelemetry/tracer'; import { type RequestHandlerWrapperOptions, wrapRequestHandlerWithInit } from './wrapRequestHandlerWithInit'; @@ -7,12 +6,7 @@ import { type RequestHandlerWrapperOptions, wrapRequestHandlerWithInit } from '. // Test-only helper, re-exported here so tests can reset the global client cache. export { _clearGlobalClientCache } from './clientCache'; -/** - * Get the default integrations for the Cloudflare SDK. - */ -export function getDefaultIntegrations(options: CloudflareOptions): Integration[] { - return getBaseDefaultIntegrations(options); -} +export { getDefaultIntegrations } from './baseSdk'; /** * Initializes the cloudflare SDK. @@ -28,13 +22,12 @@ export function init(options: CloudflareOptions): CloudflareClient | undefined { setupOpenTelemetryTracer(); } - return initWithDefaultIntegrations(options, getDefaultIntegrations); + return initBaseSdk(options); } /** - * `wrapRequestHandler` backed by `init`, so the request gets the full default integrations and - * honors `enableOpenTelemetrySetup`. The `@sentry/cloudflare/request` variant deliberately skips - * both to stay usable without `nodejs_compat`. + * `wrapRequestHandler` backed by `init`, so the request honors `enableOpenTelemetrySetup`. The + * `@sentry/cloudflare/request` variant deliberately skips it to stay usable without `nodejs_compat`. * * For framework SDKs building on the main entry point, e.g. SvelteKit, whose OpenTelemetry spans * need the tracer provider. diff --git a/packages/cloudflare/src/wrapRequestHandlerWithInit.ts b/packages/cloudflare/src/wrapRequestHandlerWithInit.ts index e852ad1cf661..fbc5262c3e55 100644 --- a/packages/cloudflare/src/wrapRequestHandlerWithInit.ts +++ b/packages/cloudflare/src/wrapRequestHandlerWithInit.ts @@ -63,7 +63,7 @@ type InitSdk = (options: CloudflareOptions) => CloudflareClient | undefined; * Same as {@link wrapRequestHandler}, but with the SDK initialization injected. * * Wrappers that are only reachable from the main entry point — where `nodejs_compat` is a - * requirement anyway — pass `init` from `sdk.ts` to get the full default integrations. + * requirement anyway — pass `init` from `sdk.ts` so `enableOpenTelemetrySetup` is honored. * * @internal */ diff --git a/packages/cloudflare/test/requestModuleGraph.test.ts b/packages/cloudflare/test/requestModuleGraph.test.ts index 2100f4a47311..64827ebc8b5c 100644 --- a/packages/cloudflare/test/requestModuleGraph.test.ts +++ b/packages/cloudflare/test/requestModuleGraph.test.ts @@ -76,7 +76,7 @@ describe('module graph of `wrapRequestHandler`', () => { expect(modules).not.toContain('async.ts'); }); - it('does not reach `sdk.ts`, which holds the default integrations that need `nodejs_compat`', () => { + it('does not reach `sdk.ts`, which imports the OpenTelemetry tracer setup', () => { expect(modules).not.toContain('sdk.ts'); }); }); From 379346e3498337e86007ee67a5a5c83255a104e0 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:34:18 +0100 Subject: [PATCH 011/169] test(e2e): Move canary variants into `canaryVariants` (#25865) **Why:** Canary variants ran in two places: as `optionalVariants` on every PR and in the nightly `canary.yml`. This adds a `sentryTest.canaryVariants` field for variants that build against upstream prereleases (`canary`, `next`, nightly) and moves them there, so they stop running on PRs. `create-react-app`, `react-create-hash-router` and `react-router-6` only had canary builds in `canary.yml`, so they get `canaryVariants` entries. The `latest` builds that only ran in `canary.yml` (`tanstackstart-react (latest)`, `nestjs-12 (latest)`) are not canary builds, so they become `optionalVariants`, and `nextjs-14 (latest)` / `nextjs-15 (latest)` stay optional. `getTestMatrix.mjs` now takes `--variant=optional|canary` instead of the boolean `--optional=true`, because the matrices are mutually exclusive. `ci:build-matrix-canary` outputs the canary variants, and `run.mjs --variant` finds them for local runs. First of 2 stacked PRs (#25866). `canary.yml` keeps its hard-coded list until #25866, so the canary variants that were only optional before (`angular-21`, `nextjs-16-standalone`, `nuxt-4-static`, `vue-3`) run nowhere until that one lands. Merge them together. **How to review:** Start with `getTestMatrix.mjs`. The `package.json` changes only move entries. The angular canary labels are renamed to `angular-21 (canary)` / `angular-22 (canary)` so the two are not both called `angular (canary)`. **Unsure about:** `nextjs-app-dir (next@15)` was in `canary.yml`, but it is already a required variant on PRs, so I left it in `variants`. After #25866 it no longer also runs nightly. via **Francesco Gringl-Novy**. -- [View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3AD0CA8FXFXAS%3A1793261840.120889) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AD0CA8FXFXAS%3A1793261840.120889/?project=4510944073809921) --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Francesco Gringl-Novy --- dev-packages/e2e-tests/README.md | 12 +++++++ dev-packages/e2e-tests/lib/getTestMatrix.mjs | 34 +++++++++++++------ dev-packages/e2e-tests/package.json | 3 +- dev-packages/e2e-tests/run.mjs | 3 +- .../test-applications/angular-21/package.json | 4 +-- .../test-applications/angular-22/package.json | 4 +-- .../create-react-app/package.json | 8 +++++ .../test-applications/nestjs-12/package.json | 8 +++++ .../test-applications/nextjs-14/package.json | 10 +++--- .../nextjs-16-standalone/package.json | 2 +- .../test-applications/nextjs-16/package.json | 22 ++++++------ .../test-applications/nuxt-3/package.json | 2 +- .../nuxt-4-static/package.json | 2 +- .../test-applications/nuxt-4/package.json | 2 +- .../react-create-hash-router/package.json | 8 +++++ .../react-router-6/package.json | 8 ++++- .../tanstackstart-react/package.json | 6 ++++ .../test-applications/vue-3/package.json | 10 +++--- 18 files changed, 108 insertions(+), 40 deletions(-) diff --git a/dev-packages/e2e-tests/README.md b/dev-packages/e2e-tests/README.md index ce769d3c87f8..400d227bc864 100644 --- a/dev-packages/e2e-tests/README.md +++ b/dev-packages/e2e-tests/README.md @@ -290,6 +290,18 @@ The required E2E jobs run on 2 vCPUs (`namespace-profile-sentry-javascript-small about 90 seconds, set `"sentryTest": { "runner": "namespace-profile-sentry-javascript" }` to run it and its variants on 4 vCPUs. +Variants that build against upstream prereleases (for example `canary`, `next` or nightly builds of a framework) go into +`canaryVariants`. They do not run on PRs, only in the nightly `CI: Canary Tests` workflow (`.github/workflows/canary.yml`), +so failures caused by an upstream prerelease do not show up on unrelated PRs. Run one locally with +`pnpm run test:run --variant
Release notes

Sourced from webpack's releases.

v5.112.2

Patch Changes

v5.112.1

Patch Changes

... (truncated)

Changelog

Sourced from webpack's changelog.

5.112.2

Patch Changes

5.112.1

Patch Changes

... (truncated)

Commits
  • e95a7f2 chore(release): new release (#22867)
  • 6e43fc2 fix: return the value of every module type from importModule() (#22869)
  • 4ffac8d ci: rename the parser job to spec and run CSS, HTML and XML in one leg (#22870)
  • 0c02242 test: split the syntax unit tests into tokenizer, parser and printer (#22825)
  • 660608c perf(css): cut CPU and allocation in the CSS minifier (#22868)
  • 8c3cbd3 perf: avoid allocating unused CSS module sets (#22866)
  • a1e5f6a perf: avoid duplicate scans in module concatenation (#22865)
  • 759b539 chore(release): new release (#22720)
  • 539f8de fix(minify): keep globals a globalThis write replaces, and slice on a pat...
  • 9668155 ci: run the deep-imports check in the lint job (#22859)
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for webpack since your current version.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=webpack&package-manager=npm_and_yarn&previous-version=5.104.1&new-version=5.112.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Charly Gomez Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> --- .../browser-integration-tests/package.json | 2 +- dev-packages/bundler-tests/package.json | 2 +- package.json | 2 +- packages/bundler-plugins/package.json | 2 +- packages/gatsby/package.json | 2 +- packages/server-utils/package.json | 2 +- pnpm-lock.yaml | 821 ++++++++++-------- 7 files changed, 466 insertions(+), 367 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index d26f4c7e9a63..714fe69a262f 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -69,7 +69,7 @@ "babel-loader": "^10.1.1", "fflate": "0.8.3", "html-webpack-plugin": "^5.5.0", - "webpack": "^5.95.0" + "webpack": "^5.112.2" }, "devDependencies": { "@sentry/browser-utils": "11.10.0", diff --git a/dev-packages/bundler-tests/package.json b/dev-packages/bundler-tests/package.json index 9f119ab94f65..35d63991cbc8 100644 --- a/dev-packages/bundler-tests/package.json +++ b/dev-packages/bundler-tests/package.json @@ -17,7 +17,7 @@ "rollup": "^4.60.3", "vite": "^8.3.1", "vitest": "^4.1.11", - "webpack": "^5.0.0" + "webpack": "^5.112.2" }, "type": "module" } diff --git a/package.json b/package.json index 954928582f62..087310513a66 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,7 @@ "tsx": "^4.23.0", "typescript": "~7.0.2", "vitest": "^4.1.11", - "webpack": "^5.104.1", + "webpack": "^5.112.2", "yalc": "^1.0.0-pre.53" }, "version": "0.0.0", diff --git a/packages/bundler-plugins/package.json b/packages/bundler-plugins/package.json index 5a856948c8f9..3bb53c945278 100644 --- a/packages/bundler-plugins/package.json +++ b/packages/bundler-plugins/package.json @@ -146,7 +146,7 @@ "premove": "^4.0.0", "rolldown": "^1.0.0", "vitest": "^4.1.11", - "webpack": "5.104.1" + "webpack": "5.112.2" }, "engines": { "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 53322962f1ae..4f0e474fe884 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -51,7 +51,7 @@ "@testing-library/react": "^15.0.5", "react": "^18.3.1", "react-dom": "^18.3.1", - "webpack": "^5.0.0" + "webpack": "^5.112.2" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/server-utils/package.json b/packages/server-utils/package.json index 2e270b6e9338..64bca0f41e4d 100644 --- a/packages/server-utils/package.json +++ b/packages/server-utils/package.json @@ -107,7 +107,7 @@ "@types/node": "^20.19.0", "meriyah": "^6.1.4", "vite": "^8.3.1", - "webpack": "^5.104.1" + "webpack": "^5.112.2" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0998abef0ad2..d8ed6712cf3a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -206,7 +206,7 @@ importers: version: 12.1.0(size-limit@12.1.0(jiti@2.7.0)) '@size-limit/webpack': specifier: ~12.1.0 - version: 12.1.0(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(size-limit@12.1.0(jiti@2.7.0))(uglify-js@3.13.3) + version: 12.1.0(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(size-limit@12.1.0(jiti@2.7.0))(svgo@4.1.0)(uglify-js@3.13.3) '@types/jsdom': specifier: ^21.1.6 version: 21.1.6 @@ -275,10 +275,10 @@ importers: version: 7.0.2 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) webpack: - specifier: ^5.104.1 - version: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + specifier: ^5.112.2 + version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) yalc: specifier: ^1.0.0-pre.53 version: 1.0.0-pre.53 @@ -317,16 +317,16 @@ importers: version: 1.20.0(debug@4.4.3(supports-color@10.0.0))(supports-color@10.0.0) babel-loader: specifier: ^10.1.1 - version: 10.1.1(@babel/core@7.29.7(supports-color@10.0.0))(webpack@5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + version: 10.1.1(@babel/core@7.29.7(supports-color@10.0.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) fflate: specifier: 0.8.3 version: 0.8.3 html-webpack-plugin: specifier: ^5.5.0 - version: 5.6.0(webpack@5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + version: 5.6.0(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) webpack: - specifier: ^5.95.0 - version: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + specifier: ^5.112.2 + version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) devDependencies: '@sentry/browser-utils': specifier: 11.10.0 @@ -391,7 +391,7 @@ importers: version: 1.2.9 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) dev-packages/bundler-plugin-integration-tests: dependencies: @@ -410,7 +410,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) dev-packages/bundler-tests: dependencies: @@ -425,13 +425,13 @@ importers: version: 4.62.3 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) webpack: - specifier: ^5.0.0 - version: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + specifier: ^5.112.2 + version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) dev-packages/clear-cache-gh-action: dependencies: @@ -496,7 +496,7 @@ importers: devDependencies: '@cloudflare/vite-plugin': specifier: 1.34.0 - version: 1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1)) + version: 1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1)) '@cloudflare/workers-types': specifier: ^4.20260426.0 version: 4.20260519.1 @@ -520,10 +520,10 @@ importers: version: 6.15.0(magicast@0.3.5)(typescript@7.0.2) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) wrangler: specifier: 4.86.0 version: 4.86.0(@cloudflare/workers-types@4.20260519.1) @@ -551,7 +551,7 @@ importers: version: 0.30.0 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) dev-packages/e2e-tests: devDependencies: @@ -988,7 +988,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^14.2.13 - version: 14.2.13(@angular/compiler-cli@14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4))(chokidar@3.6.0)(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.0(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(lightningcss@1.33.0)(ng-packagr@14.3.0(@angular/compiler-cli@14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4))(@types/node@14.18.63)(supports-color@10.0.0)(tslib@2.8.1)(typescript@4.6.4))(supports-color@10.0.0)(typescript@4.6.4)(uglify-js@3.13.3) + version: 14.2.13(@angular/compiler-cli@14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4))(chokidar@3.6.0)(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(debug@4.4.3(supports-color@10.0.0))(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.0(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)))(lightningcss@1.33.0)(ng-packagr@14.3.0(@angular/compiler-cli@14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4))(@types/node@14.18.63)(supports-color@10.0.0)(tslib@2.8.1)(typescript@4.6.4))(supports-color@10.0.0)(typescript@4.6.4)(uglify-js@3.13.3) '@angular/cli': specifier: ^14.2.13 version: 14.2.13(chokidar@3.6.0)(supports-color@10.0.0) @@ -1058,10 +1058,10 @@ importers: version: link:../../dev-packages/rollup-utils astro: specifier: ^4.16.19 - version: 4.16.19(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(typescript@7.0.2) + version: 4.16.19(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(typescript@7.0.2) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) packages/aws-serverless: dependencies: @@ -1219,10 +1219,10 @@ importers: version: 1.2.11 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@8.1.1))(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@8.1.1))(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) webpack: - specifier: 5.104.1 - version: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + specifier: 5.112.2 + version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) packages/cloudflare: dependencies: @@ -1259,7 +1259,7 @@ importers: version: 8.18.0 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) wrangler: specifier: 4.61.0 version: 4.61.0(@cloudflare/workers-types@5.20260710.1) @@ -1393,7 +1393,7 @@ importers: version: 4.6.3(@glint/template@1.8.0)(supports-color@10.0.0) '@embroider/vite': specifier: ^1.7.2 - version: 1.7.9(@embroider/core@4.6.3(@glint/template@1.8.0)(supports-color@10.0.0))(@glint/template@1.8.0)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 1.7.9(@embroider/core@4.6.3(@glint/template@1.8.0)(supports-color@10.0.0))(@glint/template@1.8.0)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) '@glint/ember-tsc': specifier: ^1.5.0 version: 1.10.0(ember-source@6.12.0(rsvp@4.8.5)(supports-color@10.0.0))(supports-color@10.0.0)(typescript@5.8.3) @@ -1408,7 +1408,7 @@ importers: version: 5.8.3 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) packages/esbuild-plugin: dependencies: @@ -1501,8 +1501,8 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) webpack: - specifier: ^5.0.0 - version: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + specifier: ^5.112.2 + version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) packages/google-cloud-serverless: dependencies: @@ -1721,7 +1721,7 @@ importers: version: 2.0.1-rc.22(crossws@0.4.10(srvx@0.11.22)) nitro: specifier: ^3.0.260415-beta - version: 3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) srvx: specifier: ^0.11.22 version: 0.11.22 @@ -1783,7 +1783,7 @@ importers: dependencies: '@nuxt/kit': specifier: ^4.6.0 - version: 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))) + version: 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) '@sentry/browser': specifier: 11.10.0 version: link:../browser @@ -1817,7 +1817,7 @@ importers: version: 4.20260519.1 '@nuxt/nitro-server': specifier: ^3.21.11 - version: 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + version: 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) '@nuxt/schema': specifier: ^3.21.11 version: 3.21.11 @@ -1832,22 +1832,22 @@ importers: version: 1.15.11 nitro: specifier: ^3.0.260311-beta - version: 3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) nitropack: specifier: ^2.13.4 - version: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + version: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) nuxi: specifier: ^3.25.1 version: 3.25.1 nuxt: specifier: 3.21.11 - version: 3.21.11(955174819940f662d7743f134f0d89c6) + version: 3.21.11(51972c16778dadf3035cea341382f655) unstorage: specifier: ^1.17.5 version: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) vue: specifier: ^3.5.41 version: 3.5.44(typescript@7.0.2) @@ -1998,7 +1998,7 @@ importers: devDependencies: '@react-router/dev': specifier: ^7.17.0 - version: 7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0) + version: 7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0) '@react-router/node': specifier: ^7.15.0 version: 7.18.1(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3) @@ -2022,7 +2022,7 @@ importers: version: 7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) packages/remix: dependencies: @@ -2095,7 +2095,7 @@ importers: version: 7.8.5 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) packages/replay-canvas: dependencies: @@ -2211,7 +2211,7 @@ importers: devDependencies: '@apm-js-collab/code-transformer-bundler-plugins': specifier: ^0.7.4 - version: 0.7.4(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + version: 0.7.4(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) '@neondatabase/serverless': specifier: 1.2.0 version: 1.2.0 @@ -2226,10 +2226,10 @@ importers: version: 6.1.4 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) webpack: - specifier: ^5.104.1 - version: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + specifier: ^5.112.2 + version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) packages/solid: dependencies: @@ -2269,10 +2269,10 @@ importers: version: 1.9.11 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) packages/solidstart: dependencies: @@ -2315,7 +2315,7 @@ importers: version: 1.0.0(solid-js@1.9.11) '@solidjs/start': specifier: ^1.3.2 - version: 1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 1.3.2(a3d6e0cbce53af562c34fef3544b2a36) '@solidjs/testing-library': specifier: 0.8.5 version: 0.8.5(@solidjs/router@1.0.0(solid-js@1.9.11))(solid-js@1.9.11) @@ -2330,7 +2330,7 @@ importers: version: 20.19.43 nitropack: specifier: ^2.13.4 - version: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + version: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) node-mock-http: specifier: ^1.0.4 version: 1.0.4 @@ -2339,13 +2339,13 @@ importers: version: 1.9.11 vinxi: specifier: ^0.5.11 - version: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) + version: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) packages/svelte: dependencies: @@ -2367,7 +2367,7 @@ importers: version: link:../../dev-packages/rollup-utils '@sveltejs/vite-plugin-svelte': specifier: 1.4.0 - version: 1.4.0(supports-color@10.0.0)(svelte@3.59.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 1.4.0(supports-color@10.0.0)(svelte@3.59.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) '@testing-library/svelte': specifier: ^3.2.1 version: 3.2.1(svelte@3.59.2) @@ -2376,7 +2376,7 @@ importers: version: 3.59.2 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) packages/sveltekit: dependencies: @@ -2428,10 +2428,10 @@ importers: version: link:../browser '@sveltejs/kit': specifier: ^2.70.2 - version: 2.70.2(@opentelemetry/api@1.10.0)(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)))(svelte@4.2.8)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 2.70.2(@opentelemetry/api@1.10.0)(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)))(svelte@4.2.8)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) '@sveltejs/vite-plugin-svelte': specifier: ^3.0.0 - version: 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + version: 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) '@types/node': specifier: ^20.19.0 version: 20.19.43 @@ -2440,7 +2440,7 @@ importers: version: 4.2.8 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) packages/tanstackstart-react: dependencies: @@ -2480,7 +2480,7 @@ importers: version: 20.19.43 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) packages/typescript: {} @@ -9673,12 +9673,6 @@ packages: peerDependencies: acorn: ^8 - acorn-import-phases@1.0.4: - resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} - engines: {node: '>=10.13.0'} - peerDependencies: - acorn: ^8.14.0 - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -11613,8 +11607,8 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - enhanced-resolve@5.24.0: - resolution: {integrity: sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==} + enhanced-resolve@5.26.0: + resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} engines: {node: '>=10.13.0'} enquirer@2.3.6: @@ -14613,6 +14607,61 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minimizer-webpack-plugin@5.13.1: + resolution: {integrity: sha512-1mhAfvachWYYOm8tpmany5NLuvP+F2wxZWBSX8DC0uFS2wJpM9q3rWgN9wBooAH9d5V12wX0BjyJe4fMjHMarQ==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@minify-html/node': '*' + '@napi-rs/image': '*' + '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' + esbuild: '*' + html-minifier-terser: '*' + imagemin: '*' + lightningcss: '*' + postcss: '*' + sharp: '*' + svgo: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@minify-html/node': + optional: true + '@napi-rs/image': + optional: true + '@swc/core': + optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true + esbuild: + optional: true + html-minifier-terser: + optional: true + imagemin: + optional: true + lightningcss: + optional: true + postcss: + optional: true + sharp: + optional: true + svgo: + optional: true + uglify-js: + optional: true + minipass-collect@1.0.2: resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} engines: {node: '>= 8'} @@ -17133,9 +17182,15 @@ packages: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} - schema-utils@4.3.3: - resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + schema-utils@4.8.1: + resolution: {integrity: sha512-x39pYlJPyH7Uge5QO64B9XGP9zLDc9WNmiQZc+CE3IzZPvMu3lHOuAMGpYNoPixEL7MSWlGhYTg5UlLSL8G6iw==} engines: {node: '>= 10.13.0'} + hasBin: true + peerDependencies: + webpack: ^5.112.0 + peerDependenciesMeta: + webpack: + optional: true scslre@0.3.0: resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} @@ -17935,8 +17990,8 @@ packages: engines: {node: '>=10'} hasBin: true - terser@5.43.1: - resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} + terser@5.51.2: + resolution: {integrity: sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==} engines: {node: '>=10'} hasBin: true @@ -19183,8 +19238,8 @@ packages: resolution: {integrity: sha512-MrJK9z7kD5Gl3jHBnnBVHvr1saVGAfmkyyrvuNzV/oe0Gr1nwZTy5VSA0Gw2j2Or0Mu8HcjUa44qlBvC2Ofnpg==} engines: {node: '>= 8'} - watchpack@2.5.1: - resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + watchpack@2.6.3: + resolution: {integrity: sha512-s3NZy0QpTY9qGqzc0wtz4qI28z3+5qKbbAec4MV4G9N3CfgZ9wgvQRr0cl2jh/tqkWG1NWHkmVMHZpCamev5Wg==} engines: {node: '>=10.13.0'} wbuf@1.7.3: @@ -19234,8 +19289,8 @@ packages: resolution: {integrity: sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==} engines: {node: '>=10.0.0'} - webpack-sources@3.5.0: - resolution: {integrity: sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==} + webpack-sources@3.6.0: + resolution: {integrity: sha512-EIMmPVvNI0CYXyRPp34F2Qk7W37/BZVZIofS6LAUnrNjCVahS+hO4mxwFJDjQjODTGZDtVzLYoN2+1dYkhk9qA==} engines: {node: '>=10.13.0'} webpack-subresource-integrity@5.1.0: @@ -19251,18 +19306,8 @@ packages: webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - webpack@5.104.1: - resolution: {integrity: sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - - webpack@5.107.2: - resolution: {integrity: sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==} + webpack@5.112.2: + resolution: {integrity: sha512-B2tp1LI9D8GVpPDqprdt12ukTkXXahj9MzqSWJq4faSPgOUM7CdZaGAB6HrL/8E6UwbBpdj4vzWDbKoFnVoU6g==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -19764,11 +19809,11 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@14.2.13(@angular/compiler-cli@14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4))(chokidar@3.6.0)(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.0(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(lightningcss@1.33.0)(ng-packagr@14.3.0(@angular/compiler-cli@14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4))(@types/node@14.18.63)(supports-color@10.0.0)(tslib@2.8.1)(typescript@4.6.4))(supports-color@10.0.0)(typescript@4.6.4)(uglify-js@3.13.3)': + '@angular-devkit/build-angular@14.2.13(@angular/compiler-cli@14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4))(chokidar@3.6.0)(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(debug@4.4.3(supports-color@10.0.0))(html-minifier-terser@6.1.0)(html-webpack-plugin@5.6.0(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)))(lightningcss@1.33.0)(ng-packagr@14.3.0(@angular/compiler-cli@14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4))(@types/node@14.18.63)(supports-color@10.0.0)(tslib@2.8.1)(typescript@4.6.4))(supports-color@10.0.0)(typescript@4.6.4)(uglify-js@3.13.3)': dependencies: '@ampproject/remapping': 2.2.0 '@angular-devkit/architect': 0.1402.13(chokidar@3.6.0) - '@angular-devkit/build-webpack': 0.1402.13(chokidar@3.6.0)(webpack-dev-server@4.11.0(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)))(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) + '@angular-devkit/build-webpack': 0.1402.13(chokidar@3.6.0)(webpack-dev-server@4.11.0(debug@4.4.3(supports-color@10.0.0))(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)))(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) '@angular-devkit/core': 14.2.13(chokidar@3.6.0) '@angular/compiler-cli': 14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4) '@babel/core': 7.18.10(supports-color@10.0.0) @@ -19827,9 +19872,9 @@ snapshots: typescript: 4.6.4 webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) webpack-dev-middleware: 5.3.3(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) - webpack-dev-server: 4.11.0(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) + webpack-dev-server: 4.11.0(debug@4.4.3(supports-color@10.0.0))(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) webpack-merge: 5.8.0 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.0(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.0(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)))(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) optionalDependencies: esbuild: 0.15.5 ng-packagr: 14.3.0(@angular/compiler-cli@14.3.0(@angular/compiler@14.3.0(@angular/core@14.3.0(rxjs@7.8.2)(zone.js@0.12.0)))(supports-color@10.0.0)(typescript@4.6.4))(@types/node@14.18.63)(supports-color@10.0.0)(tslib@2.8.1)(typescript@4.6.4) @@ -19856,12 +19901,12 @@ snapshots: - utf-8-validate - webpack-cli - '@angular-devkit/build-webpack@0.1402.13(chokidar@3.6.0)(webpack-dev-server@4.11.0(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)))(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3))': + '@angular-devkit/build-webpack@0.1402.13(chokidar@3.6.0)(webpack-dev-server@4.11.0(debug@4.4.3(supports-color@10.0.0))(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)))(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3))': dependencies: '@angular-devkit/architect': 0.1402.13(chokidar@3.6.0) rxjs: 6.6.7 webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) - webpack-dev-server: 4.11.0(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) + webpack-dev-server: 4.11.0(debug@4.4.3(supports-color@10.0.0))(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) transitivePeerDependencies: - chokidar @@ -19982,7 +20027,7 @@ snapshots: optionalDependencies: zod: 4.6.7 - '@apm-js-collab/code-transformer-bundler-plugins@0.7.4(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))': + '@apm-js-collab/code-transformer-bundler-plugins@0.7.4(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: '@apm-js-collab/code-transformer': 0.18.1 es-module-lexer: 2.1.0 @@ -19991,8 +20036,8 @@ snapshots: optionalDependencies: esbuild: 0.28.1 rollup: 4.62.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - webpack: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) '@apm-js-collab/code-transformer@0.18.1': dependencies: @@ -22465,12 +22510,12 @@ snapshots: optionalDependencies: workerd: 1.20260426.1 - '@cloudflare/vite-plugin@1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))': + '@cloudflare/vite-plugin@1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))': dependencies: '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260426.1) miniflare: 4.20260426.0 unenv: 2.0.0-rc.24 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) wrangler: 4.86.0(@cloudflare/workers-types@4.20260519.1) ws: 8.18.0 transitivePeerDependencies: @@ -22736,17 +22781,17 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@dxup/nuxt@0.5.6(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(esbuild@0.28.1)(giget@3.3.1)(jiti@2.7.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))': + '@dxup/nuxt@0.5.6(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(esbuild@0.28.1)(giget@3.3.1)(jiti@2.7.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: '@dxup/unimport': 0.1.2 - '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))) + '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) '@vue/compiler-dom': 3.5.44 chokidar: 5.0.0 knitwork: 1.3.0 magic-string: 1.1.0 pathe: 2.0.3 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) transitivePeerDependencies: - '@farmfe/core' - '@nuxt/schema' @@ -22776,7 +22821,7 @@ snapshots: '@effect/vitest@4.0.0(effect@4.0.0)(vitest@4.1.11)': dependencies: effect: 4.0.0 - vitest: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vitest: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) '@ember-data/rfc395-data@0.0.4': {} @@ -22944,7 +22989,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@embroider/vite@1.7.9(@embroider/core@4.6.3(@glint/template@1.8.0)(supports-color@10.0.0))(@glint/template@1.8.0)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@embroider/vite@1.7.9(@embroider/core@4.6.3(@glint/template@1.8.0)(supports-color@10.0.0))(@glint/template@1.8.0)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@embroider/core': 4.6.3(@glint/template@1.8.0)(supports-color@10.0.0) @@ -22961,8 +23006,8 @@ snapshots: jsdom: 25.0.1(supports-color@10.0.0) send: 1.2.1(supports-color@10.0.0) source-map-url: 0.4.1 - terser: 5.43.1 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + terser: 5.51.2 + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@glint/template' - bufferutil @@ -24583,11 +24628,11 @@ snapshots: '@nuxt/devalue@2.0.2': {} - '@nuxt/devtools-kit@3.4.1(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@nuxt/devtools-kit@3.4.1(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: - '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))) + '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) execa: 8.0.1 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@nuxt/schema' - confbox @@ -24611,11 +24656,11 @@ snapshots: pkg-types: 2.3.1 semver: 7.8.5 - '@nuxt/devtools@3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': + '@nuxt/devtools@3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': dependencies: - '@nuxt/devtools-kit': 3.4.1(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + '@nuxt/devtools-kit': 3.4.1(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) '@nuxt/devtools-wizard': 3.4.1 - '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))) + '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) '@vue/devtools-core': 8.2.1(vue@3.5.44(typescript@7.0.2)) '@vue/devtools-kit': 8.2.1 birpc: 4.1.0 @@ -24642,9 +24687,9 @@ snapshots: structured-clone-es: 2.0.1 tinyglobby: 0.2.17 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - vite-plugin-inspect: 11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) - vite-plugin-vue-tracer: 1.5.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite-plugin-inspect: 11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + vite-plugin-vue-tracer: 1.5.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) which: 6.0.1 ws: 8.21.3 transitivePeerDependencies: @@ -24708,7 +24753,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))': + '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))': dependencies: consola: 3.4.2 defu: 6.1.7 @@ -24726,7 +24771,7 @@ snapshots: std-env: 4.3.0 tinyexec: 1.3.1 tinyglobby: 0.2.17 - unctx: 3.0.1(magic-string@0.30.21)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))) + unctx: 3.0.1(magic-string@0.30.21)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) verkit: 0.5.0 optionalDependencies: '@nuxt/schema': 3.21.11 @@ -24741,7 +24786,7 @@ snapshots: - magic-string - unplugin - '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))': + '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))': dependencies: consola: 3.4.2 defu: 6.1.7 @@ -24759,7 +24804,7 @@ snapshots: std-env: 4.3.0 tinyexec: 1.3.1 tinyglobby: 0.2.17 - unctx: 3.0.1(magic-string@1.1.0)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))) + unctx: 3.0.1(magic-string@1.1.0)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) verkit: 0.5.0 optionalDependencies: '@nuxt/schema': 3.21.11 @@ -24774,7 +24819,7 @@ snapshots: - magic-string - unplugin - '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))': + '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))': dependencies: consola: 3.4.2 defu: 6.1.7 @@ -24792,7 +24837,7 @@ snapshots: std-env: 4.3.0 tinyexec: 1.3.1 tinyglobby: 0.2.17 - unctx: 3.0.1(magic-string@1.1.0)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))) + unctx: 3.0.1(magic-string@1.1.0)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) verkit: 0.5.0 optionalDependencies: '@nuxt/schema': 3.21.11 @@ -24807,7 +24852,7 @@ snapshots: - magic-string - unplugin - '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))': + '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.11(magicast@0.5.4) @@ -24821,11 +24866,11 @@ snapshots: escape-string-regexp: 5.0.0 exsolve: 1.1.3 h3: 1.15.11 - impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.143.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) - nuxt: 3.21.11(955174819940f662d7743f134f0d89c6) + nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.143.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + nuxt: 3.21.11(51972c16778dadf3035cea341382f655) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.1 @@ -24881,7 +24926,7 @@ snapshots: - webpack - xml2js - '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))': + '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.11(magicast@0.5.4) @@ -24895,11 +24940,11 @@ snapshots: escape-string-regexp: 5.0.0 exsolve: 1.1.3 h3: 1.15.11 - impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) - nuxt: 3.21.11(955174819940f662d7743f134f0d89c6) + nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + nuxt: 3.21.11(51972c16778dadf3035cea341382f655) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.1 @@ -24976,12 +25021,12 @@ snapshots: rc9: 3.1.0 std-env: 4.3.0 - '@nuxt/vite-builder@3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0)': + '@nuxt/vite-builder@3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0)': dependencies: '@nuxt/kit': 3.21.11(magicast@0.5.4) '@rollup/plugin-replace': 6.0.3(rollup@4.62.3) - '@vitejs/plugin-vue': 6.0.8(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) - '@vitejs/plugin-vue-jsx': 5.1.6(supports-color@10.0.0)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) + '@vitejs/plugin-vue': 6.0.8(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) + '@vitejs/plugin-vue-jsx': 5.1.6(supports-color@10.0.0)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) autoprefixer: 10.5.4(postcss@8.5.31) consola: 3.4.2 cssnano: 7.1.9(postcss@8.5.31) @@ -24996,7 +25041,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 3.21.11(955174819940f662d7743f134f0d89c6) + nuxt: 3.21.11(51972c16778dadf3035cea341382f655) nypm: 0.6.9 ohash: 2.0.11 pathe: 2.0.3 @@ -25007,9 +25052,9 @@ snapshots: std-env: 4.3.0 ufo: 1.6.4 unenv: 2.0.0-rc.24 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - vite-node: 5.3.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - vite-plugin-checker: 0.14.5(meow@13.2.0)(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(typescript@7.0.2)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite-node: 5.3.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite-plugin-checker: 0.14.5(meow@13.2.0)(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(typescript@7.0.2)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) vue: 3.5.44(typescript@7.0.2) vue-bundle-renderer: 2.3.2 optionalDependencies: @@ -25815,7 +25860,7 @@ snapshots: '@protobufjs/utf8@1.1.1': {} - '@react-router/dev@7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0)': + '@react-router/dev@7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0)': dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@babel/generator': 7.29.8 @@ -25845,8 +25890,8 @@ snapshots: semver: 7.8.5 tinyglobby: 0.2.17 valibot: 1.4.2(typescript@6.0.3) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - vite-node: 3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) optionalDependencies: typescript: 6.0.3 wrangler: 4.86.0(@cloudflare/workers-types@4.20260519.1) @@ -26174,7 +26219,7 @@ snapshots: dependencies: serialize-javascript: 6.0.2 smob: 1.4.1 - terser: 5.43.1 + terser: 5.51.2 optionalDependencies: rollup: 4.62.3 @@ -26182,7 +26227,7 @@ snapshots: dependencies: serialize-javascript: 7.0.5 smob: 1.4.1 - terser: 5.43.1 + terser: 5.51.2 optionalDependencies: rollup: 4.62.3 @@ -26379,13 +26424,14 @@ snapshots: dependencies: size-limit: 12.1.0(jiti@2.7.0) - '@size-limit/webpack@12.1.0(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(size-limit@12.1.0(jiti@2.7.0))(uglify-js@3.13.3)': + '@size-limit/webpack@12.1.0(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(size-limit@12.1.0(jiti@2.7.0))(svgo@4.1.0)(uglify-js@3.13.3)': dependencies: nanoid: 5.1.9 size-limit: 12.1.0(jiti@2.7.0) - webpack: 5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) transitivePeerDependencies: - '@minify-html/node' + - '@napi-rs/image' - '@swc/core' - '@swc/css' - '@swc/html' @@ -26394,8 +26440,11 @@ snapshots: - csso - esbuild - html-minifier-terser + - imagemin - lightningcss - postcss + - sharp + - svgo - uglify-js - webpack-cli @@ -26784,11 +26833,11 @@ snapshots: dependencies: solid-js: 1.9.11 - '@solidjs/start@1.3.2(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@solidjs/start@1.3.2(a3d6e0cbce53af562c34fef3544b2a36)': dependencies: - '@tanstack/server-functions-plugin': 1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0)) - '@vinxi/server-components': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0)) + '@tanstack/server-functions-plugin': 1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0)) + '@vinxi/server-components': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0)) cookie-es: 2.0.1 defu: 6.1.7 error-stack-parser: 2.1.4 @@ -26800,8 +26849,8 @@ snapshots: source-map-js: 1.2.2 terracotta: 1.0.5(solid-js@1.9.11) tinyglobby: 0.2.17 - vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) - vite-plugin-solid: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) + vite-plugin-solid: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) transitivePeerDependencies: - '@testing-library/jest-dom' - solid-js @@ -26856,11 +26905,11 @@ snapshots: dependencies: acorn: 8.18.0 - '@sveltejs/kit@2.70.2(@opentelemetry/api@1.10.0)(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)))(svelte@4.2.8)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/kit@2.70.2(@opentelemetry/api@1.10.0)(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)))(svelte@4.2.8)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.18.0) - '@sveltejs/vite-plugin-svelte': 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.18.0 cookie: 0.6.0 @@ -26872,21 +26921,21 @@ snapshots: set-cookie-parser: 3.0.1 sirv: 3.0.2 svelte: 4.2.8 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) optionalDependencies: '@opentelemetry/api': 1.10.0 typescript: 6.0.3 - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)))(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)))(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) debug: 4.4.3(supports-color@10.0.0) svelte: 4.2.8 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@1.4.0(supports-color@10.0.0)(svelte@3.59.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@1.4.0(supports-color@10.0.0)(svelte@3.59.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: debug: 4.4.3(supports-color@10.0.0) deepmerge: 4.3.1 @@ -26894,22 +26943,22 @@ snapshots: magic-string: 0.26.7 svelte: 3.59.2 svelte-hmr: 0.15.3(svelte@3.59.2) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - vitefu: 0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vitefu: 0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)))(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)))(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) debug: 4.4.3(supports-color@10.0.0) deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.21 svelte: 4.2.8 svelte-hmr: 0.16.0(svelte@4.2.8) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - vitefu: 0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vitefu: 0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) transitivePeerDependencies: - supports-color @@ -26917,7 +26966,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@tanstack/directive-functions-plugin@1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@tanstack/directive-functions-plugin@1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.29.7(supports-color@10.0.0) @@ -26926,7 +26975,7 @@ snapshots: '@tanstack/router-utils': 1.154.7(supports-color@10.0.0) babel-dead-code-elimination: 1.0.12(supports-color@10.0.0) tiny-invariant: 1.3.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -26963,7 +27012,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/server-functions-plugin@1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@tanstack/server-functions-plugin@1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.29.7(supports-color@10.0.0) @@ -26972,7 +27021,7 @@ snapshots: '@babel/template': 7.29.7 '@babel/traverse': 7.29.8(supports-color@10.0.0) '@babel/types': 7.29.8 - '@tanstack/directive-functions-plugin': 1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + '@tanstack/directive-functions-plugin': 1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) babel-dead-code-elimination: 1.0.12(supports-color@10.0.0) tiny-invariant: 1.3.3 transitivePeerDependencies: @@ -27205,11 +27254,11 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 8.2.1 - '@types/estree': 1.0.9 + '@types/estree': 0.0.51 '@types/eslint@8.2.1': dependencies: - '@types/estree': 1.0.9 + '@types/estree': 0.0.51 '@types/json-schema': 7.0.15 '@types/estree@0.0.39': {} @@ -27741,7 +27790,7 @@ snapshots: untun: 0.1.3 uqr: 0.1.3 - '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))': + '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0))': dependencies: '@babel/parser': 7.29.9 acorn: 8.18.0 @@ -27752,35 +27801,35 @@ snapshots: magicast: 0.2.11 recast: 0.23.11 tslib: 2.8.1 - vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) + vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) - '@vinxi/server-components@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0))': + '@vinxi/server-components@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0))': dependencies: - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0)) acorn: 8.18.0 acorn-loose: 8.4.0 acorn-typescript: 1.4.13(acorn@8.18.0) astring: 1.9.0 magicast: 0.2.11 recast: 0.23.11 - vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0) + vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) - '@vitejs/plugin-vue-jsx@5.1.6(supports-color@10.0.0)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': + '@vitejs/plugin-vue-jsx@5.1.6(supports-color@10.0.0)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.0.0)) '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.0.0))(supports-color@10.0.0) '@rolldown/pluginutils': 1.0.1 '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.7(supports-color@10.0.0))(supports-color@10.0.0) - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) vue: 3.5.44(typescript@7.0.2) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@6.0.8(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': + '@vitejs/plugin-vue@6.0.8(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) vue: 3.5.44(typescript@7.0.2) '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': @@ -27795,7 +27844,7 @@ snapshots: obug: 2.1.4 std-env: 4.3.0 tinyrainbow: 3.1.1 - vitest: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vitest: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) '@vitest/expect@4.1.11': dependencies: @@ -27806,13 +27855,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.11': dependencies: @@ -28223,10 +28272,6 @@ snapshots: dependencies: acorn: 8.18.0 - acorn-import-phases@1.0.4(acorn@8.18.0): - dependencies: - acorn: 8.18.0 - acorn-jsx@5.3.2(acorn@8.18.0): dependencies: acorn: 8.18.0 @@ -28306,7 +28351,7 @@ snapshots: ajv-formats@2.1.1: dependencies: - ajv: 8.20.0 + ajv: 8.11.0 ajv-formats@3.0.1: dependencies: @@ -28554,7 +28599,7 @@ snapshots: astring@1.9.0: {} - astro@4.16.19(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(typescript@7.0.2): + astro@4.16.19(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(typescript@7.0.2): dependencies: '@astrojs/compiler': 2.13.1 '@astrojs/internal-helpers': 0.4.1 @@ -28610,8 +28655,8 @@ snapshots: tsconfck: 3.1.6(typescript@7.0.2) unist-util-visit: 5.0.0 vfile: 6.0.3 - vite: 5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1) - vitefu: 1.0.7(vite@5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)) + vite: 5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2) + vitefu: 1.0.7(vite@5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)) which-pm: 3.0.1 xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 @@ -28748,12 +28793,12 @@ snapshots: babel-import-util@3.0.1: {} - babel-loader@10.1.1(@babel/core@7.29.7(supports-color@10.0.0))(webpack@5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + babel-loader@10.1.1(@babel/core@7.29.7(supports-color@10.0.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) find-up: 5.0.0 optionalDependencies: - webpack: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) babel-loader@8.2.5(@babel/core@7.18.10(supports-color@10.0.0))(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)): dependencies: @@ -29743,7 +29788,7 @@ snapshots: glob-parent: 6.0.2 globby: 13.2.2 normalize-path: 3.0.0 - schema-utils: 4.3.3 + schema-utils: 4.8.1(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) serialize-javascript: 6.0.2 webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) @@ -30518,7 +30563,7 @@ snapshots: dependencies: once: 1.4.0 - enhanced-resolve@5.24.0: + enhanced-resolve@5.26.0: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -31365,7 +31410,7 @@ snapshots: externality@1.0.2: dependencies: - enhanced-resolve: 5.24.0 + enhanced-resolve: 5.26.0 mlly: 1.8.2 pathe: 1.1.2 ufo: 1.6.4 @@ -31548,7 +31593,7 @@ snapshots: dependencies: app-module-path: 2.2.0 commander: 12.1.0 - enhanced-resolve: 5.24.0 + enhanced-resolve: 5.26.0 module-definition: 6.0.1 module-lookup-amd: 9.0.5 resolve: 1.22.12 @@ -32388,13 +32433,13 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.43.1 + terser: 5.51.2 html-to-image@1.11.11: {} html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.0(webpack@5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + html-webpack-plugin@5.6.0(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -32402,9 +32447,10 @@ snapshots: pretty-error: 4.0.0 tapable: 2.3.3 optionalDependencies: - webpack: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3) + optional: true - html-webpack-plugin@5.6.0(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + html-webpack-plugin@5.6.0(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -32412,8 +32458,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.3.3 optionalDependencies: - webpack: 5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) - optional: true + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) htmlparser2@6.1.0: dependencies: @@ -32488,7 +32533,7 @@ snapshots: transitivePeerDependencies: - supports-color - http-proxy-middleware@2.0.10(@types/express@4.17.21): + http-proxy-middleware@2.0.10(@types/express@4.17.21)(debug@4.4.3(supports-color@10.0.0)): dependencies: '@types/http-proxy': 1.17.14 http-proxy: 1.18.1(debug@4.4.3(supports-color@10.0.0)) @@ -32622,12 +32667,12 @@ snapshots: import-meta-resolve@4.2.0: {} - impound@1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + impound@1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@jridgewell/trace-mapping': 0.3.31 es-module-lexer: 2.1.0 pathe: 2.0.3 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 transitivePeerDependencies: - '@farmfe/core' @@ -33513,7 +33558,7 @@ snapshots: license-webpack-plugin@4.0.2(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)): dependencies: - webpack-sources: 3.5.0 + webpack-sources: 3.6.0 optionalDependencies: webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) @@ -34263,7 +34308,7 @@ snapshots: mini-css-extract-plugin@2.6.1(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)): dependencies: - schema-utils: 4.3.3 + schema-utils: 4.8.1(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) miniflare@4.20260124.0: @@ -34326,6 +34371,45 @@ snapshots: minimist@1.2.8: {} + minimizer-webpack-plugin@5.13.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.8.1(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)) + terser: 5.51.2 + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3) + optionalDependencies: + clean-css: 5.2.2 + cssnano: 7.1.9(postcss@8.5.31) + csso: 5.0.5 + esbuild: 0.28.1 + html-minifier-terser: 6.1.0 + lightningcss: 1.33.0 + postcss: 8.5.31 + sharp: 0.35.5(@types/node@14.18.63) + svgo: 4.1.0 + uglify-js: 3.13.3 + optional: true + + minimizer-webpack-plugin@5.13.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.8.1(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + terser: 5.51.2 + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) + optionalDependencies: + clean-css: 5.2.2 + cssnano: 7.1.9(postcss@8.5.31) + csso: 5.0.5 + esbuild: 0.28.1 + html-minifier-terser: 6.1.0 + lightningcss: 1.33.0 + postcss: 8.5.31 + sharp: 0.35.5(@types/node@20.19.43) + svgo: 4.1.0 + uglify-js: 3.13.3 + minipass-collect@1.0.2: dependencies: minipass: 3.3.6 @@ -34703,7 +34787,7 @@ snapshots: node-gyp-build: 4.6.0 optional: true - nitro@3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + nitro@3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.10(srvx@0.11.22) @@ -34724,7 +34808,7 @@ snapshots: giget: 3.3.1 jiti: 2.7.0 rollup: 4.62.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -34756,7 +34840,7 @@ snapshots: - sqlite3 - uploadthing - nitro@3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + nitro@3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.10(srvx@0.11.22) @@ -34777,7 +34861,7 @@ snapshots: giget: 3.3.1 jiti: 2.7.0 rollup: 4.62.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -34809,7 +34893,7 @@ snapshots: - sqlite3 - uploadthing - nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.143.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.143.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.3) @@ -34874,7 +34958,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0)) untyped: 2.0.0 @@ -34918,7 +35002,7 @@ snapshots: - vite - webpack - nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.3) @@ -34983,7 +35067,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0)) untyped: 2.0.0 @@ -35027,7 +35111,7 @@ snapshots: - vite - webpack - nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.3) @@ -35092,7 +35176,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0)) untyped: 2.0.0 @@ -35344,16 +35428,16 @@ snapshots: nuxi@3.25.1: {} - nuxt@3.21.11(955174819940f662d7743f134f0d89c6): + nuxt@3.21.11(51972c16778dadf3035cea341382f655): dependencies: - '@dxup/nuxt': 0.5.6(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(esbuild@0.28.1)(giget@3.3.1)(jiti@2.7.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + '@dxup/nuxt': 0.5.6(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(esbuild@0.28.1)(giget@3.3.1)(jiti@2.7.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) '@nuxt/cli': 3.37.0(@nuxt/schema@3.21.11)(@parcel/watcher@2.5.6)(cac@6.7.14)(commander@14.0.3)(magicast@0.5.4)(supports-color@10.0.0) - '@nuxt/devtools': 3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) + '@nuxt/devtools': 3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) '@nuxt/kit': 3.21.11(magicast@0.5.4) - '@nuxt/nitro-server': 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + '@nuxt/nitro-server': 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) '@nuxt/schema': 3.21.11 '@nuxt/telemetry': 2.8.0(@nuxt/kit@3.21.11(magicast@0.5.4)) - '@nuxt/vite-builder': 3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(955174819940f662d7743f134f0d89c6))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0) + '@nuxt/vite-builder': 3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0) '@unhead/vue': 2.1.17(vue@3.5.44(typescript@7.0.2)) '@vue/shared': 3.5.44 c12: 3.3.4(magicast@0.5.4) @@ -35370,7 +35454,7 @@ snapshots: h3: 1.15.11 hookable: 5.5.3 ignore: 7.0.12 - impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) jiti: 2.7.0 klona: 2.0.6 knitwork: 1.3.0 @@ -35397,8 +35481,8 @@ snapshots: ultrahtml: 1.7.0 uncrypto: 0.1.3 unctx: 2.5.0 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.44)(vue-router@4.6.4(vue@3.5.44(typescript@7.0.2)))(vue@3.5.44(typescript@7.0.2)) untyped: 2.0.0 vue: 3.5.44(typescript@7.0.2) @@ -37980,7 +38064,7 @@ snapshots: sass-lookup@6.1.0: dependencies: commander: 12.1.0 - enhanced-resolve: 5.24.0 + enhanced-resolve: 5.26.0 sass@1.54.4: dependencies: @@ -38018,12 +38102,33 @@ snapshots: ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) - schema-utils@4.3.3: + schema-utils@4.8.1(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@types/json-schema': 7.0.15 ajv: 8.20.0 - ajv-formats: 2.1.1 + ajv-formats: 3.0.1 ajv-keywords: 5.1.0(ajv@8.20.0) + optionalDependencies: + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3) + optional: true + + schema-utils@4.8.1(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-formats: 3.0.1 + ajv-keywords: 5.1.0(ajv@8.20.0) + optionalDependencies: + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) + + schema-utils@4.8.1(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)): + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-formats: 3.0.1 + ajv-keywords: 5.1.0(ajv@8.20.0) + optionalDependencies: + webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) scslre@0.3.0: dependencies: @@ -38255,6 +38360,40 @@ snapshots: '@img/sharp-win32-ia32': 0.34.5 '@img/sharp-win32-x64': 0.34.5 + sharp@0.35.5(@types/node@14.18.63): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.5 + '@img/sharp-darwin-x64': 0.35.5 + '@img/sharp-freebsd-wasm32': 0.35.5 + '@img/sharp-libvips-darwin-arm64': 1.3.4 + '@img/sharp-libvips-darwin-x64': 1.3.4 + '@img/sharp-libvips-linux-arm': 1.3.4 + '@img/sharp-libvips-linux-arm64': 1.3.4 + '@img/sharp-libvips-linux-ppc64': 1.3.4 + '@img/sharp-libvips-linux-riscv64': 1.3.4 + '@img/sharp-libvips-linux-s390x': 1.3.4 + '@img/sharp-libvips-linux-x64': 1.3.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + '@img/sharp-linux-arm': 0.35.5 + '@img/sharp-linux-arm64': 0.35.5 + '@img/sharp-linux-ppc64': 0.35.5 + '@img/sharp-linux-riscv64': 0.35.5 + '@img/sharp-linux-s390x': 0.35.5 + '@img/sharp-linux-x64': 0.35.5 + '@img/sharp-linuxmusl-arm64': 0.35.5 + '@img/sharp-linuxmusl-x64': 0.35.5 + '@img/sharp-webcontainers-wasm32': 0.35.5 + '@img/sharp-win32-arm64': 0.35.5 + '@img/sharp-win32-ia32': 0.35.5 + '@img/sharp-win32-x64': 0.35.5 + '@types/node': 14.18.63 + optional: true + sharp@0.35.5(@types/node@20.19.43): dependencies: '@img/colour': 1.1.0 @@ -38942,8 +39081,8 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.43.1 + schema-utils: 4.8.1(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) + terser: 5.51.2 webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) optionalDependencies: clean-css: 5.2.2 @@ -38955,40 +39094,6 @@ snapshots: postcss: 8.4.31 uglify-js: 3.13.3 - terser-webpack-plugin@5.6.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)(webpack@5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.43.1 - webpack: 5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) - optionalDependencies: - clean-css: 5.2.2 - cssnano: 7.1.9(postcss@8.5.31) - csso: 5.0.5 - esbuild: 0.28.1 - html-minifier-terser: 6.1.0 - lightningcss: 1.33.0 - postcss: 8.5.31 - uglify-js: 3.13.3 - - terser-webpack-plugin@5.6.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.43.1 - webpack: 5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) - optionalDependencies: - clean-css: 5.2.2 - cssnano: 7.1.9(postcss@8.5.31) - csso: 5.0.5 - esbuild: 0.28.1 - html-minifier-terser: 6.1.0 - lightningcss: 1.33.0 - postcss: 8.5.31 - uglify-js: 3.13.3 - terser@5.14.2: dependencies: '@jridgewell/source-map': 0.3.5 @@ -38996,7 +39101,7 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - terser@5.43.1: + terser@5.51.2: dependencies: '@jridgewell/source-map': 0.3.5 acorn: 8.18.0 @@ -39362,26 +39467,26 @@ snapshots: magic-string: 0.30.21 unplugin: 2.3.11 - unctx@3.0.1(magic-string@0.30.21)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))): + unctx@3.0.1(magic-string@0.30.21)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))): optionalDependencies: magic-string: 0.30.21 oxc-parser: 0.143.0 rolldown: 1.2.11 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) - unctx@3.0.1(magic-string@1.1.0)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))): + unctx@3.0.1(magic-string@1.1.0)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))): optionalDependencies: magic-string: 1.1.0 oxc-parser: 0.143.0 rolldown: 1.2.11 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) - unctx@3.0.1(magic-string@1.1.0)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))): + unctx@3.0.1(magic-string@1.1.0)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))): optionalDependencies: magic-string: 1.1.0 oxc-parser: 0.152.0 rolldown: 1.2.11 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) undefsafe@2.0.5: {} @@ -39445,7 +39550,7 @@ snapshots: trough: 2.1.0 vfile: 6.0.3 - unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: acorn: 8.18.0 escape-string-regexp: 5.0.0 @@ -39459,7 +39564,7 @@ snapshots: scule: 1.3.0 strip-literal: 4.0.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 optionalDependencies: oxc-parser: 0.143.0 @@ -39474,7 +39579,7 @@ snapshots: - vite - webpack - unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: acorn: 8.18.0 escape-string-regexp: 5.0.0 @@ -39488,7 +39593,7 @@ snapshots: scule: 1.3.0 strip-literal: 4.0.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 optionalDependencies: oxc-parser: 0.152.0 @@ -39503,7 +39608,7 @@ snapshots: - vite - webpack - unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: acorn: 8.18.0 escape-string-regexp: 5.0.0 @@ -39517,7 +39622,7 @@ snapshots: scule: 1.3.0 strip-literal: 4.0.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 optionalDependencies: oxc-parser: 0.152.0 @@ -39644,7 +39749,7 @@ snapshots: picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 - unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 @@ -39653,10 +39758,10 @@ snapshots: esbuild: 0.28.1 rolldown: 1.2.11 rollup: 4.62.3 - vite: 6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - webpack: 5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + vite: 6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) - unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)): + unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 @@ -39665,8 +39770,8 @@ snapshots: esbuild: 0.28.1 rolldown: 1.2.11 rollup: 4.62.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - webpack: 5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) unstorage@1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0)): dependencies: @@ -39844,7 +39949,7 @@ snapshots: '@types/unist': 3.0.0 vfile-message: 4.0.3 - vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))(yaml@2.9.0): + vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0): dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.0.0)) @@ -39865,7 +39970,7 @@ snapshots: hookable: 5.5.3 http-proxy: 1.18.1(debug@4.4.3(supports-color@10.0.0)) micromatch: 4.0.8 - nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) node-fetch-native: 1.6.7 path-to-regexp: 6.3.0 pathe: 1.1.2 @@ -39878,7 +39983,7 @@ snapshots: unctx: 2.5.0 unenv: 1.10.0 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) - vite: 6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) zod: 4.6.7 transitivePeerDependencies: - '@azure/app-configuration' @@ -39931,23 +40036,23 @@ snapshots: - xml2js - yaml - vite-dev-rpc@2.0.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + vite-dev-rpc@2.0.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): dependencies: birpc: 4.1.0 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - vite-hot-client: 2.2.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite-hot-client: 2.2.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) - vite-hot-client@2.2.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + vite-hot-client@2.2.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): dependencies: - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vite-node@3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0): + vite-node@3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@10.0.0) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -39962,13 +40067,13 @@ snapshots: - tsx - yaml - vite-node@5.3.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0): + vite-node@5.3.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): dependencies: cac: 6.7.14 es-module-lexer: 2.1.0 obug: 2.1.4 pathe: 2.0.3 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -39982,7 +40087,7 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.14.5(meow@13.2.0)(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(typescript@7.0.2)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + vite-plugin-checker@0.14.5(meow@13.2.0)(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(typescript@7.0.2)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): dependencies: '@babel/code-frame': 7.29.7 chokidar: 5.0.0 @@ -39991,14 +40096,14 @@ snapshots: picomatch: 4.0.7 proper-lockfile: 4.1.2 tiny-invariant: 1.3.3 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) optionalDependencies: meow: 13.2.0 optionator: 0.9.4 oxlint: 1.89.0(oxlint-tsgolint@7.0.2004) typescript: 7.0.2 - vite-plugin-inspect@11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + vite-plugin-inspect@11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): dependencies: ansis: 4.3.1 error-stack-parser-es: 1.0.5 @@ -40008,12 +40113,12 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.2 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - vite-dev-rpc: 2.0.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite-dev-rpc: 2.0.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) optionalDependencies: - '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3))) + '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) - vite-plugin-solid@2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + vite-plugin-solid@2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@types/babel__core': 7.20.5 @@ -40021,24 +40126,24 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.11 solid-refresh: 0.6.3(solid-js@1.9.11)(supports-color@10.0.0) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) - vitefu: 1.0.7(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vitefu: 1.0.7(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) optionalDependencies: '@testing-library/jest-dom': 6.9.1 transitivePeerDependencies: - supports-color - vite-plugin-vue-tracer@1.5.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)): + vite-plugin-vue-tracer@1.5.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)): dependencies: estree-walker: 3.0.3 exsolve: 1.1.3 magic-string: 1.1.0 pathe: 2.0.3 source-map-js: 1.2.2 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) vue: 3.5.44(typescript@7.0.2) - vite@5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1): + vite@5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2): dependencies: esbuild: 0.21.5 postcss: 8.5.31 @@ -40050,9 +40155,9 @@ snapshots: lightningcss: 1.33.0 sass: 1.72.0 stylus: 0.59.0(supports-color@10.0.0) - terser: 5.43.1 + terser: 5.51.2 - vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0): + vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.7) @@ -40068,11 +40173,11 @@ snapshots: lightningcss: 1.33.0 sass: 1.72.0 stylus: 0.59.0(supports-color@10.0.0) - terser: 5.43.1 + terser: 5.51.2 tsx: 4.23.0 yaml: 2.9.0 - vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0): + vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): dependencies: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.7) @@ -40088,11 +40193,11 @@ snapshots: lightningcss: 1.33.0 sass: 1.72.0 stylus: 0.59.0(supports-color@10.0.0) - terser: 5.43.1 + terser: 5.51.2 tsx: 4.23.0 yaml: 2.9.0 - vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0): + vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -40107,11 +40212,11 @@ snapshots: less: 4.2.0(supports-color@10.0.0) sass: 1.72.0 stylus: 0.59.0(supports-color@10.0.0) - terser: 5.43.1 + terser: 5.51.2 tsx: 4.23.0 yaml: 2.9.0 - vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0): + vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -40126,26 +40231,26 @@ snapshots: less: 4.2.0(supports-color@8.1.1) sass: 1.72.0 stylus: 0.59.0(supports-color@8.1.1) - terser: 5.43.1 + terser: 5.51.2 tsx: 4.23.0 yaml: 2.9.0 - vitefu@0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + vitefu@0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): optionalDependencies: - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vitefu@1.0.7(vite@5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)): + vitefu@1.0.7(vite@5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)): optionalDependencies: - vite: 5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1) + vite: 5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2) - vitefu@1.0.7(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)): + vitefu@1.0.7(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): optionalDependencies: - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vitest@4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0): + vitest@4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): dependencies: '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 @@ -40162,7 +40267,7 @@ snapshots: tinyexec: 1.3.1 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.10.0 @@ -40183,10 +40288,10 @@ snapshots: - tsx - yaml - vitest@4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@8.1.1))(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0): + vitest@4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@8.1.1))(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): dependencies: '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 @@ -40203,7 +40308,7 @@ snapshots: tinyexec: 1.3.1 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.43.1)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.10.0 @@ -40349,9 +40454,8 @@ snapshots: transitivePeerDependencies: - supports-color - watchpack@2.5.1: + watchpack@2.6.3: dependencies: - glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 wbuf@1.7.3: @@ -40384,10 +40488,10 @@ snapshots: memfs: 3.6.0 mime-types: 2.1.35 range-parser: 1.2.1 - schema-utils: 4.3.3 + schema-utils: 4.8.1(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) - webpack-dev-server@4.11.0(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)): + webpack-dev-server@4.11.0(debug@4.4.3(supports-color@10.0.0))(supports-color@10.0.0)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -40406,12 +40510,12 @@ snapshots: express: 4.22.1(supports-color@10.0.0) graceful-fs: 4.2.11 html-entities: 2.5.2 - http-proxy-middleware: 2.0.10(@types/express@4.17.21) + http-proxy-middleware: 2.0.10(@types/express@4.17.21)(debug@4.4.3(supports-color@10.0.0)) ipaddr.js: 2.4.0 open: 8.4.0 p-retry: 4.6.2 rimraf: 3.0.2 - schema-utils: 4.3.3 + schema-utils: 4.8.1(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) selfsigned: 2.4.1 serve-index: 1.9.1(supports-color@10.0.0) sockjs: 0.3.24 @@ -40430,46 +40534,39 @@ snapshots: clone-deep: 4.0.1 wildcard: 2.0.0 - webpack-sources@3.5.0: {} + webpack-sources@3.6.0: {} - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.0(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)))(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.0(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)))(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)): dependencies: typed-assert: 1.0.9 webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) optionalDependencies: - html-webpack-plugin: 5.6.0(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) + html-webpack-plugin: 5.6.0(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)) webpack-virtual-modules@0.6.2: {} - webpack@5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3): + webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3): dependencies: - '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.18.0 - acorn-import-phases: 1.0.4(acorn@8.18.0) browserslist: 4.29.0 chrome-trace-event: 1.0.2 - enhanced-resolve: 5.24.0 + enhanced-resolve: 5.26.0 es-module-lexer: 2.1.0 - eslint-scope: 5.1.1 events: 3.3.0 - glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.2 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 4.3.3 + mime-db: 1.54.0 + minimizer-webpack-plugin: 5.13.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)) + schema-utils: 4.8.1(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@14.18.63))(svgo@4.1.0)(uglify-js@3.13.3)) tapable: 2.3.3 - terser-webpack-plugin: 5.6.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)(webpack@5.104.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) - watchpack: 2.5.1 - webpack-sources: 3.5.0 + watchpack: 2.6.3 + webpack-sources: 3.6.0 transitivePeerDependencies: - '@minify-html/node' + - '@napi-rs/image' - '@swc/core' - '@swc/css' - '@swc/html' @@ -40478,37 +40575,36 @@ snapshots: - csso - esbuild - html-minifier-terser + - imagemin - lightningcss - postcss + - sharp + - svgo - uglify-js + optional: true - webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3): + webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.18.0 - acorn-import-phases: 1.0.4(acorn@8.18.0) browserslist: 4.29.0 chrome-trace-event: 1.0.2 - enhanced-resolve: 5.24.0 + enhanced-resolve: 5.26.0 es-module-lexer: 2.1.0 - eslint-scope: 5.1.1 events: 3.3.0 - glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - loader-runner: 4.3.2 mime-db: 1.54.0 - neo-async: 2.6.2 - schema-utils: 4.3.3 + minimizer-webpack-plugin: 5.13.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + schema-utils: 4.8.1(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) tapable: 2.3.3 - terser-webpack-plugin: 5.6.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)(webpack@5.107.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(uglify-js@3.13.3)) - watchpack: 2.5.1 - webpack-sources: 3.5.0 + watchpack: 2.6.3 + webpack-sources: 3.6.0 transitivePeerDependencies: - '@minify-html/node' + - '@napi-rs/image' - '@swc/core' - '@swc/css' - '@swc/html' @@ -40517,8 +40613,11 @@ snapshots: - csso - esbuild - html-minifier-terser + - imagemin - lightningcss - postcss + - sharp + - svgo - uglify-js webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3): @@ -40532,7 +40631,7 @@ snapshots: acorn-import-assertions: 1.8.0(acorn@8.18.0) browserslist: 4.29.0 chrome-trace-event: 1.0.2 - enhanced-resolve: 5.24.0 + enhanced-resolve: 5.26.0 es-module-lexer: 0.9.3 eslint-scope: 5.1.1 events: 3.3.0 @@ -40545,8 +40644,8 @@ snapshots: schema-utils: 3.3.0 tapable: 2.3.3 terser-webpack-plugin: 5.6.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) - watchpack: 2.5.1 - webpack-sources: 3.5.0 + watchpack: 2.6.3 + webpack-sources: 3.6.0 transitivePeerDependencies: - '@minify-html/node' - '@swc/core' From a36d4b78edc46f9910e8f7e501787370ffd9f6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Thu, 29 Oct 2026 11:37:58 -0600 Subject: [PATCH 044/169] fix(server-utils): Fix orchestrion source maps of instrumented modules (#25772) closes #25329 closes [JS-3928](https://linear.app/getsentry/issue/JS-3928) ## Description This bumps `@apm-js-collab/code-transformer-bundler-plugins` to 0.7.5, which has the upstream fix (apm-js-collab/code-transformer-bundler-plugins#50, nodejs/orchestrion-js#104). The source map of an instrumented module now names its real file in `node_modules`, with `sourcesContent`. Before, Vite 8 and webpack builds had a `/` source that does not exist (for example `ioredis/built/Redis.js`), and source map flattening in `@sentry/sveltekit` failed for that chunk. ## Type decisions code-transformer 0.19.0 also allows a function in the `transform` field of an instrumentation. I cast `serializeInstrumentations` to our vendored types, so the public `transform` type stays `string`. The alternative is to also allow a function in the vendored type. --------- Co-authored-by: Claude Opus 5.5 Co-authored-by: Lukas Stracke --- .../server-runtime-injection/package.json | 4 +- packages/server-utils/package.json | 2 +- .../src/orchestrion/bundler/webpack.ts | 7 ++- .../test/orchestrion/config.test.ts | 2 +- .../test/orchestrion/sourcemap.test.ts | 59 +++++++++++++++++++ pnpm-lock.yaml | 34 +++++------ 6 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 packages/server-utils/test/orchestrion/sourcemap.test.ts diff --git a/packages/server-runtime-injection/package.json b/packages/server-runtime-injection/package.json index b6285ac2060a..7fbbce3d0db6 100644 --- a/packages/server-runtime-injection/package.json +++ b/packages/server-runtime-injection/package.json @@ -49,8 +49,8 @@ "@sentry/server-utils": "11.10.0" }, "devDependencies": { - "@apm-js-collab/code-transformer": "^0.18.1", - "@apm-js-collab/tracing-hooks": "^0.13.3", + "@apm-js-collab/code-transformer": "^0.19.0", + "@apm-js-collab/tracing-hooks": "^0.14.0", "@sentry-internal/rollup-utils": "11.10.0", "@types/node": "^20.19.0", "@vercel/nft": "^1.11.0", diff --git a/packages/server-utils/package.json b/packages/server-utils/package.json index 64bca0f41e4d..0b0307509a36 100644 --- a/packages/server-utils/package.json +++ b/packages/server-utils/package.json @@ -101,7 +101,7 @@ "@sentry/core": "11.10.0" }, "devDependencies": { - "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.4", + "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.5", "@neondatabase/serverless": "1.2.0", "@sentry-internal/rollup-utils": "11.10.0", "@types/node": "^20.19.0", diff --git a/packages/server-utils/src/orchestrion/bundler/webpack.ts b/packages/server-utils/src/orchestrion/bundler/webpack.ts index 4aed6df60460..dec13a661026 100644 --- a/packages/server-utils/src/orchestrion/bundler/webpack.ts +++ b/packages/server-utils/src/orchestrion/bundler/webpack.ts @@ -16,8 +16,11 @@ import { externalEntryMatchesModule, externalizedModulesWarning, orchestrionTran // Explicitly annotated with the vendored types so the emitted declaration doesn't reference // `@apm-js-collab/code-transformer-bundler-plugins` — a bundled devDependency consumers don't have. -export const serializeInstrumentations: (configs: AnyInstrumentationConfig[]) => SerializableInstrumentationConfig[] = - serializeInstrumentationsImpl; +// Upstream also allows a function in `transform`. The vendored configs only allow a string there, +// and the serializer copies `transform` unchanged, so its output only contains strings too. +export const serializeInstrumentations = serializeInstrumentationsImpl as ( + configs: AnyInstrumentationConfig[], +) => SerializableInstrumentationConfig[]; export type { SerializableInstrumentationConfig } from '../apmTypes'; export { getOrchestrionLoaderPath, resolveOrchestrionRuntimeRequest }; diff --git a/packages/server-utils/test/orchestrion/config.test.ts b/packages/server-utils/test/orchestrion/config.test.ts index 3d9a76415b9c..0385405c1573 100644 --- a/packages/server-utils/test/orchestrion/config.test.ts +++ b/packages/server-utils/test/orchestrion/config.test.ts @@ -1,4 +1,4 @@ -import type { InstrumentationConfig } from '@apm-js-collab/code-transformer-bundler-plugins/core'; +import type { InstrumentationConfig } from '../../src/orchestrion/apmTypes'; import { describe, expect, it } from 'vitest'; import { getInstrumentedModuleNames, diff --git a/packages/server-utils/test/orchestrion/sourcemap.test.ts b/packages/server-utils/test/orchestrion/sourcemap.test.ts new file mode 100644 index 000000000000..6b9a2d738994 --- /dev/null +++ b/packages/server-utils/test/orchestrion/sourcemap.test.ts @@ -0,0 +1,59 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { build, type Plugin, type Rollup } from 'vite'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import type { InstrumentationConfig } from '../../src/orchestrion/apmTypes'; +import { sentryOrchestrionPlugin as sentryOrchestrionRollupPlugin } from '../../src/orchestrion/bundler/rollup'; +import { sentryOrchestrionPlugin as sentryOrchestrionVitePlugin } from '../../src/orchestrion/bundler/vite'; + +const instrumentations: InstrumentationConfig[] = [ + { + channelName: 'query', + module: { name: 'fake-db', versionRange: '>=1.0.0', filePath: 'index.js' }, + functionQuery: { expressionName: 'query', kind: 'Sync' }, + }, +]; + +describe('orchestrion bundler plugins source maps', () => { + let root: string; + + beforeAll(() => { + root = mkdtempSync(join(tmpdir(), 'orchestrion-sourcemap-')); + const packageDir = join(root, 'node_modules', 'fake-db'); + mkdirSync(packageDir, { recursive: true }); + writeFileSync(join(packageDir, 'package.json'), JSON.stringify({ name: 'fake-db', version: '1.0.0' })); + writeFileSync(join(packageDir, 'index.js'), 'exports.query = function query(sql) {\n return sql;\n};\n'); + writeFileSync( + join(root, 'server.js'), + "import { query } from 'fake-db';\nexport const run = () => query('SELECT 1');\n", + ); + }); + + afterAll(() => rmSync(root, { recursive: true, force: true })); + + // https://github.com/getsentry/sentry-javascript/issues/25329 + it.each([ + ['vite', () => sentryOrchestrionVitePlugin({ instrumentations })], + ['rollup', () => sentryOrchestrionRollupPlugin({ instrumentations }) as Plugin], + ])('maps the transformed module only to its file in node_modules (%s plugin)', async (_name, createPlugin) => { + const output = (await build({ + root, + configFile: false, + logLevel: 'silent', + plugins: [createPlugin()], + ssr: { noExternal: ['fake-db'] }, + build: { + ssr: 'server.js', + sourcemap: true, + write: false, + rollupOptions: { external: ['@sentry/server-utils'] }, + }, + })) as Rollup.RollupOutput; + + const chunk = output.output[0]; + + expect(chunk.code).toContain('orchestrion:fake-db:query'); + expect(chunk.map?.sources).toEqual(['../node_modules/fake-db/index.js', '../server.js']); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8ed6712cf3a..e197899faf36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2176,11 +2176,11 @@ importers: version: link:../server-utils devDependencies: '@apm-js-collab/code-transformer': - specifier: ^0.18.1 - version: 0.18.1 + specifier: ^0.19.0 + version: 0.19.0 '@apm-js-collab/tracing-hooks': - specifier: ^0.13.3 - version: 0.13.3(supports-color@7.2.0) + specifier: ^0.14.0 + version: 0.14.0(supports-color@7.2.0) '@sentry-internal/rollup-utils': specifier: 11.10.0 version: link:../../dev-packages/rollup-utils @@ -2210,8 +2210,8 @@ importers: version: link:../core devDependencies: '@apm-js-collab/code-transformer-bundler-plugins': - specifier: ^0.7.4 - version: 0.7.4(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + specifier: ^0.7.5 + version: 0.7.5(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) '@neondatabase/serverless': specifier: 1.2.0 version: 1.2.0 @@ -2788,8 +2788,8 @@ packages: zod: optional: true - '@apm-js-collab/code-transformer-bundler-plugins@0.7.4': - resolution: {integrity: sha512-nAfOeZPSUAQvJa1iFT/5oCrTm5YQhMMrfCNthNnaXHZiOQhu1KGuLoIx7HtbAi3wfwaBYLaICPIeenIaEwcXIg==} + '@apm-js-collab/code-transformer-bundler-plugins@0.7.5': + resolution: {integrity: sha512-/nmTAee5mBly9QiIQSGIKITv9TcEyLTQJFbYjfyWCEfYofxQgurMrT5fLDIRneSPqICucRTxgPMHmc6/q7cgfw==} engines: {node: '>=18.0.0'} peerDependencies: esbuild: '*' @@ -2806,12 +2806,12 @@ packages: webpack: optional: true - '@apm-js-collab/code-transformer@0.18.1': - resolution: {integrity: sha512-u1Hb6bHjWtkSpiprwVP6YaHC1DTN4RAU3zYkUDUe7WMnJwdyU1pwTL9dFKiSJB9IiLue/EQovmyx6xhU7FFtAQ==} + '@apm-js-collab/code-transformer@0.19.0': + resolution: {integrity: sha512-KGR8dyF4L5dIbnfW2a7n3WQiUxIvR+Zs5IPVXJKNL6XVR9oWR9lm6N/AQXtnimiUDiURlwrZOIlpbGgQ3hU6yQ==} hasBin: true - '@apm-js-collab/tracing-hooks@0.13.3': - resolution: {integrity: sha512-l/ScKzA7sg1hZKc3QMtbnA3blVkTteN3Yq97AejfFrEiR0S0lay3KmyJJ44p45EEVIGP85fLfki4a6OD/7Jb8w==} + '@apm-js-collab/tracing-hooks@0.14.0': + resolution: {integrity: sha512-6QM7NohS/sfIsPDjK/0BlJ00I90VZrGUEA+bhEL3Ul/BoUWCBLfr9oCEcQYPhVZBDPfno5sSjMK8pFpno5gddQ==} '@apollo/cache-control-types@1.0.3': resolution: {integrity: sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==} @@ -20027,9 +20027,9 @@ snapshots: optionalDependencies: zod: 4.6.7 - '@apm-js-collab/code-transformer-bundler-plugins@0.7.4(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': + '@apm-js-collab/code-transformer-bundler-plugins@0.7.5(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: - '@apm-js-collab/code-transformer': 0.18.1 + '@apm-js-collab/code-transformer': 0.19.0 es-module-lexer: 2.1.0 magic-string: 0.30.21 module-details-from-path: 1.0.4 @@ -20039,7 +20039,7 @@ snapshots: vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) - '@apm-js-collab/code-transformer@0.18.1': + '@apm-js-collab/code-transformer@0.19.0': dependencies: '@types/estree': 1.0.9 astring: 1.9.0 @@ -20048,9 +20048,9 @@ snapshots: semifies: 1.0.0 source-map: 0.6.1 - '@apm-js-collab/tracing-hooks@0.13.3(supports-color@7.2.0)': + '@apm-js-collab/tracing-hooks@0.14.0(supports-color@7.2.0)': dependencies: - '@apm-js-collab/code-transformer': 0.18.1 + '@apm-js-collab/code-transformer': 0.19.0 debug: 4.4.3(supports-color@7.2.0) module-details-from-path: 1.0.4 transitivePeerDependencies: From 30cfb4cbafd193973d818adb1905d67f132a4a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Thu, 29 Oct 2026 11:37:59 -0600 Subject: [PATCH 045/169] fix(cloudflare): Run build-time instrumentation in vite dev (#25777) part of #25765 part of [JS-4010](https://linear.app/getsentry/issue/JS-4010) ## Description This runs the orchestrion transform in the Vite dependency optimizer, so integrations that are added at build time (for example `hono` and `graphql`) also send spans in `vite dev` on Cloudflare. `@cloudflare/vite-plugin` pre-bundles the Worker dependencies into `node_modules/.vite/deps_*`, and before, the transform did not run on these files. The plugin adds a Rolldown plugin for Vite 8 and an esbuild plugin for Vite 6 and 7. The new `transformOptimizedDeps` plugin option turns this on, and `sentryCloudflareVitePlugin` sets it. Node servers do not need it, because the runtime hook instruments the dependencies that Node loads. The Cloudflare variant of the `hono-4` e2e app now also runs its tests in `vite dev`. ## Known issues - A package that is excluded from `optimizeDeps` is still not instrumented in `vite dev`: its module id ends with `?v=`, and the file matcher of `@apm-js-collab/code-transformer-bundler-plugins` does not remove the query (apm-js-collab/code-transformer-bundler-plugins#52). ## Sentry traces Before (11.9.0), `/graphql` in `vite dev`, no `graphql` spans: https://sentry-sdks.sentry.io/explore/traces/trace/9221590d886f4648b401eded8a996046/ After, `/graphql` in `vite dev`, with `graphql` spans: https://sentry-sdks.sentry.io/explore/traces/trace/2aea20834d3d4fa38e39d37166273e14/ Before (11.9.0), `/hono/users/1` in `vite dev`, span name `GET /hono/users/1`: https://sentry-sdks.sentry.io/explore/traces/trace/b8e5b8dba83b4b5aba45bdfdf40b307c/ After, `/hono/users/1` in `vite dev`, span name `GET /hono/users/:id` from the Hono route: https://sentry-sdks.sentry.io/explore/traces/trace/20b2a5dcfe6a4dbfb7e68eb3037bf3b2/ --------- Co-authored-by: Claude Opus 5.5 --- dev-packages/e2e-tests/README.md | 6 + .../test-applications/hono-4/package.json | 4 +- .../hono-4/playwright.config.ts | 6 +- packages/cloudflare/src/vite/index.ts | 1 + .../src/orchestrion/bundler/options.ts | 7 + .../src/orchestrion/bundler/vite.ts | 166 +++++++++++++---- .../test/orchestrion/bundler.test.ts | 174 +++++++++++++++++- .../test/orchestrion/viteDepOptimizer.test.ts | 60 ++++++ 8 files changed, 374 insertions(+), 50 deletions(-) create mode 100644 packages/server-utils/test/orchestrion/viteDepOptimizer.test.ts diff --git a/dev-packages/e2e-tests/README.md b/dev-packages/e2e-tests/README.md index 400d227bc864..426b274da2d0 100644 --- a/dev-packages/e2e-tests/README.md +++ b/dev-packages/e2e-tests/README.md @@ -193,6 +193,12 @@ is the reference setup. because workerd does not allow it. `nextjs-16` has no Vite build: its Cloudflare variant builds with `opennextjs-cloudflare build --config wrangler.opennext.jsonc` and wraps the generated Worker in `worker.ts` with `withSentry` from `@sentry/nextjs/cloudflare`. +- **Cloudflare in `vite dev`**: to run the Worker in `vite dev` (for example the dev tests of the `hono-4` Cloudflare + variant), start it with `CLOUDFLARE_INCLUDE_PROCESS_ENV=true vite dev`. The runner gives `E2E_TEST_DSN` to the app + only as a process env var. `wrangler dev` adds it to the Worker `env` with `--var "E2E_TEST_DSN:$E2E_TEST_DSN"`, but + `@cloudflare/vite-plugin` has no such option. It fills the Worker `env` from the `vars` of the wrangler config and + from `.dev.vars` or `.env`, and adds process env vars only when `CLOUDFLARE_INCLUDE_PROCESS_ENV` is `true`. Without + it, `env.E2E_TEST_DSN` is `undefined`, the SDK sends no events, and the tests fail. - **Build flavors**: a variant can also build the same app with another tool, for example `nextjs-16 (vinext)` builds `nextjs-16` with vinext. A build flavor is not a runtime: its assert script sets its own env var (`VINEXT=1`, read by `tests/isVinext.ts`). It sets `runtime` only to run on another runtime, for example `nextjs-16 (vinext, bun)`. diff --git a/dev-packages/e2e-tests/test-applications/hono-4/package.json b/dev-packages/e2e-tests/test-applications/hono-4/package.json index 008be0636b19..baf473e3471e 100644 --- a/dev-packages/e2e-tests/test-applications/hono-4/package.json +++ b/dev-packages/e2e-tests/test-applications/hono-4/package.json @@ -8,14 +8,16 @@ "dev:bun": "bun run dist/entry.bun.js", "dev:deno": "deno run --allow-net --allow-env --allow-read --preload=npm:@sentry/deno/import src/entry.deno.ts", "dev:cloudflare": "wrangler dev --config ./dist/hono_4/wrangler.json --var \"E2E_TEST_DSN:$E2E_TEST_DSN\" --log-level=$(test $CI && echo 'none' || echo 'log')", + "dev:cloudflare:vite": "CLOUDFLARE_INCLUDE_PROCESS_ENV=true vite dev", "test:build": "pnpm install && pnpm typecheck", "typecheck": "tsc --noEmit -p tsconfig.typecheck.json", "test": "TEST_ENV=production playwright test", + "test:dev": "TEST_ENV=development playwright test", "test:assert": "pnpm test:assert:node", "test:assert:node": "RUNTIME=node pnpm test", "test:assert:bun": "bun run build-bun.ts && RUNTIME=bun pnpm test", "test:assert:deno": "RUNTIME=deno pnpm test", - "test:assert:cloudflare": "vite build && RUNTIME=cloudflare pnpm test" + "test:assert:cloudflare": "vite build && RUNTIME=cloudflare pnpm test && RUNTIME=cloudflare pnpm test:dev" }, "dependencies": { "@sentry/bun": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/hono-4/playwright.config.ts b/dev-packages/e2e-tests/test-applications/hono-4/playwright.config.ts index 475e082970af..5b40a1bd48a0 100644 --- a/dev-packages/e2e-tests/test-applications/hono-4/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/hono-4/playwright.config.ts @@ -10,7 +10,11 @@ if (!testEnv) { const APP_PORT = 8787; const startCommands: Record = { - cloudflare: `pnpm dev:cloudflare --port ${APP_PORT}`, + // `vite dev` pre-bundles `hono` with its Rolldown dependency optimizer, so this also tests the transform there. + cloudflare: + testEnv === 'development' + ? `pnpm dev:cloudflare:vite --port ${APP_PORT} --strictPort` + : `pnpm dev:cloudflare --port ${APP_PORT}`, node: `pnpm dev:node`, bun: `pnpm dev:bun`, deno: `pnpm dev:deno`, diff --git a/packages/cloudflare/src/vite/index.ts b/packages/cloudflare/src/vite/index.ts index d6005250d9ee..a0a4b91fecdc 100644 --- a/packages/cloudflare/src/vite/index.ts +++ b/packages/cloudflare/src/vite/index.ts @@ -90,6 +90,7 @@ export function sentryCloudflareVitePlugin(options: SentryCloudflareVitePluginOp // Route the injected `tracingChannel` imports through the workerd-safe façade so a // dependency wrapped at module scope (`const app = new Hono()`) doesn't throw on workerd. dcModule: '@sentry/cloudflare/orchestrion-diagnostics-channel', + transformOptimizedDeps: true, }), sentryMastraObservabilityProviderPlugin(), sentryFlueRuntimeProviderPlugin(), diff --git a/packages/server-utils/src/orchestrion/bundler/options.ts b/packages/server-utils/src/orchestrion/bundler/options.ts index ad6439cd5962..0d9aec844d59 100644 --- a/packages/server-utils/src/orchestrion/bundler/options.ts +++ b/packages/server-utils/src/orchestrion/bundler/options.ts @@ -36,6 +36,13 @@ export type PluginOptions = { * routing every channel through it is harmless. */ dcModule?: string; + /** + * When `true`, the Vite plugin also runs the transform in the dependency optimizer of server environments in + * `vite dev`. Needed for `@cloudflare/vite-plugin`, which pre-bundles the dependencies of a Worker there. + * + * @default false + */ + transformOptimizedDeps?: boolean; }; /** diff --git a/packages/server-utils/src/orchestrion/bundler/vite.ts b/packages/server-utils/src/orchestrion/bundler/vite.ts index 16e642dc76fd..d1a40295c9ee 100644 --- a/packages/server-utils/src/orchestrion/bundler/vite.ts +++ b/packages/server-utils/src/orchestrion/bundler/vite.ts @@ -1,33 +1,34 @@ +import esbuildCodeTransformer from '@apm-js-collab/code-transformer-bundler-plugins/esbuild'; import codeTransformer from '@apm-js-collab/code-transformer-bundler-plugins/vite'; -import type { ConfigEnv, Plugin, ResolvedConfig, UserConfig } from 'vite'; +import type { Plugin as EsbuildPlugin } from 'esbuild'; +import type { ConfigEnv, ConfigPluginContext, EnvironmentOptions, Plugin, ResolvedConfig, UserConfig } from 'vite'; export type { Plugin as VitePlugin } from 'vite'; import { instrumentedModuleNames } from '../config'; import type { PluginOptions } from './options'; import { externalEntryMatchesModule, externalizedModulesWarning, orchestrionTransformOptions } from './options'; import { createExternalRequireResolver } from './externalRequire'; -import { resolveOrchestrionRuntimeRequest, SNIPPET_IMPORT_SPECIFIER } from './resolve'; +import { resolveOrchestrionRuntimeRequest, SNIPPET_IMPORT_SPECIFIER, SNIPPET_IMPORT_SPECIFIER_FILTER } from './resolve'; type TransformHandler = (this: unknown, code: string, id: string, opts?: { ssr?: boolean }) => unknown; +type ResolveIdHandler = Extract, (...args: never[]) => unknown>; // Key of a property on the plugin object. Its value is an object that is unique to each `sentryOrchestrionPlugin()` // call. An SDK that wraps the plugin with an object spread (`@sentry/remix`) copies the property, so `configResolved` // can find the first instance in a build also through a wrapper. const INSTANCE_KEY = '__sentryOrchestrionInstance'; -type OrchestrionVitePlugin = Plugin & { [INSTANCE_KEY]?: object }; +type OrchestrionInstance = { transformOptimizedDeps: boolean }; -// On Vite >= 6 `applyToEnvironment` (below) keeps the whole plugin out of -// client environments. Vite 5 (e.g. Remix v2) ignores that hook, so without -// this gate the transform would also run in the CLIENT build — where modules -// like `@remix-run/server-runtime` sit in the client graph, and the injected -// snippet's import of the subscriber factories (which import -// `node:diagnostics_channel`) breaks against Vite's browser builtin shim. Gate -// on the `ssr` flag, which Vite passes on both major versions. -function ssrOnlyTransform(transform: Plugin['transform'], isDuplicate: () => boolean): Plugin['transform'] { +type OrchestrionVitePlugin = Plugin & { [INSTANCE_KEY]?: OrchestrionInstance }; + +function gateTransform( + transform: Plugin['transform'], + skip: (opts?: { ssr?: boolean }) => boolean, +): Plugin['transform'] { const gate = (handler: TransformHandler): TransformHandler => function (code, id, opts) { - if (!opts?.ssr || isDuplicate()) { + if (skip(opts)) { return null; } return handler.call(this, code, id, opts); @@ -47,6 +48,74 @@ function toEsmEntry(id: string): string { return id.replace(/([/\\])build[/\\]cjs[/\\]index\.js$/, '$1build$1esm$1index.js'); } +// The module-injected snippet imports `@sentry/server-utils` from INSIDE +// transformed `node_modules` files. Under isolated installs (pnpm) that bare +// specifier doesn't resolve from an instrumented package's location, so when +// normal resolution fails, fall back to this package's own resolution so it +// gets bundled from its real on-disk path. +// +// Always bundle the ESM entry. In a CommonJS module the snippet `require()`s the specifier, +// which resolves to the CJS build. Vite 5-7 turn that build's `require()`s of external +// packages into default imports, and `@sentry/core` has no default export, so the server +// crashes at startup. Vite 8 keeps the `require()`s, which loads a second copy of `@sentry/core`. +async function resolveSnippetImport( + this: ThisParameterType, + ...[source, importer, resolveOptions]: Parameters +): Promise>> { + const resolved = await this.resolve(source, importer, { ...resolveOptions, skipSelf: true }); + if (resolved) { + return resolved.external ? resolved : { ...resolved, id: toEsmEntry(resolved.id) }; + } + const fallback = resolveOrchestrionRuntimeRequest(source); + return fallback ? toEsmEntry(fallback) : null; +} + +// Rolldown plugin for the dependency optimizer of Vite 8. +function depOptimizerRolldownPlugin(options: PluginOptions, isDuplicate: () => boolean): Plugin { + const upstream = codeTransformer(orchestrionTransformOptions(options)); + return { + ...upstream, + name: 'sentry-orchestrion-dep-optimizer', + transform: gateTransform(upstream.transform, isDuplicate), + resolveId(source, importer, resolveOptions) { + return source === SNIPPET_IMPORT_SPECIFIER + ? resolveSnippetImport.call(this, source, importer, resolveOptions) + : null; + }, + }; +} + +// esbuild plugin for the dependency optimizer of Vite 6 and 7. +function depOptimizerEsbuildPlugin(options: PluginOptions, isDuplicate: () => boolean): EsbuildPlugin { + const upstream = esbuildCodeTransformer(orchestrionTransformOptions(options)); + return { + name: 'sentry-orchestrion-dep-optimizer', + setup(build) { + if (isDuplicate()) { + return; + } + // Same resolution as `resolveSnippetImport`. The `pluginData` marker stops the recursion back into this callback. + build.onResolve({ filter: SNIPPET_IMPORT_SPECIFIER_FILTER }, async args => { + if (args.pluginData === 'sentry-orchestrion-resolving') { + return null; + } + const resolved = await build.resolve(args.path, { + resolveDir: args.resolveDir, + importer: args.importer, + kind: args.kind, + pluginData: 'sentry-orchestrion-resolving', + }); + if (resolved.errors.length === 0) { + return resolved.external ? resolved : { ...resolved, path: toEsmEntry(resolved.path) }; + } + const fallback = resolveOrchestrionRuntimeRequest(args.path); + return fallback ? { path: toEsmEntry(fallback) } : null; + }); + return upstream.setup(build); + }, + }; +} + /** * Vite plugin that runs the orchestrion code transform on the bundled output. * @@ -76,38 +145,30 @@ export function sentryOrchestrionPlugin(options: PluginOptions = {}): Plugin { ]; const resolveExternalRequire = createExternalRequireResolver(noExternalModules()); - const instance = {}; + const instance: OrchestrionInstance = { transformOptimizedDeps: options.transformOptimizedDeps === true }; // `true` when an earlier instance of this plugin is in the same build, which then transforms the modules alone. let isDuplicate = false; + // `true` when an earlier instance with `transformOptimizedDeps` is in the same build, which then transforms the + // pre-bundled dependencies alone. + let isDuplicateOptimizer = false; const plugin: OrchestrionVitePlugin = { ...upstream, [INSTANCE_KEY]: instance, - transform: ssrOnlyTransform(upstream.transform, () => isDuplicate), - // The module-injected snippet imports `@sentry/server-utils` from INSIDE - // transformed `node_modules` files. Under isolated installs (pnpm) that bare - // specifier doesn't resolve from an instrumented package's location, so when - // normal resolution fails, fall back to this package's own resolution so it - // gets bundled from its real on-disk path. SSR-gated like the transform: the - // specifier only appears in SSR modules. - // - // Always bundle the ESM entry. In a CommonJS module the snippet `require()`s the specifier, - // which resolves to the CJS build. Vite 5-7 turn that build's `require()`s of external - // packages into default imports, and `@sentry/core` has no default export, so the server - // crashes at startup. Vite 8 keeps the `require()`s, which loads a second copy of `@sentry/core`. + // On Vite >= 6 `applyToEnvironment` (below) keeps the whole plugin out of + // client environments. Vite 5 (e.g. Remix v2) ignores that hook, so without + // this gate the transform would also run in the CLIENT build — where modules + // like `@remix-run/server-runtime` sit in the client graph, and the injected + // snippet's import of the subscriber factories (which import + // `node:diagnostics_channel`) breaks against Vite's browser builtin shim. Gate + // on the `ssr` flag, which Vite passes on both major versions. + transform: gateTransform(upstream.transform, opts => !opts?.ssr || isDuplicate), + // SSR-gated like the transform: the specifier only appears in SSR modules. async resolveId(source, importer, resolveOptions) { if (source !== SNIPPET_IMPORT_SPECIFIER) { return resolveExternalRequire.call(this, source, importer, resolveOptions); } - if (!resolveOptions?.ssr) { - return null; - } - const resolved = await this.resolve(source, importer, { ...resolveOptions, skipSelf: true }); - if (resolved) { - return resolved.external ? resolved : { ...resolved, id: toEsmEntry(resolved.id) }; - } - const fallback = resolveOrchestrionRuntimeRequest(source); - return fallback ? toEsmEntry(fallback) : null; + return resolveOptions?.ssr ? resolveSnippetImport.call(this, source, importer, resolveOptions) : null; }, applyToEnvironment(environment) { // Orchestrion splices `node:diagnostics_channel` calls into instrumented modules, which only @@ -136,21 +197,50 @@ export function sentryOrchestrionPlugin(options: PluginOptions = {}): Plugin { }, }, configEnvironment( + // Vite 6 calls this hook without a context. + this: ConfigPluginContext | undefined, name: string, config: { consumer?: 'client' | 'server' }, env?: ConfigEnv, - ): { resolve: { noExternal: string[] } } | null { - if (env?.command === 'serve' || (config.consumer ?? (name === 'client' ? 'client' : 'server')) !== 'server') { + ): EnvironmentOptions | null { + if ((config.consumer ?? (name === 'client' ? 'client' : 'server')) !== 'server') { return null; } + // `@cloudflare/vite-plugin` pre-bundles the dependencies of a Worker in `serve`. The dependency + // optimizer runs no Vite plugins, so the transform has to run inside it. A Node server loads its + // dependencies with Node, where the runtime hook instruments them. + if (env?.command === 'serve') { + if (!options.transformOptimizedDeps) { + return null; + } + if (this?.meta?.rolldownVersion) { + return { + optimizeDeps: { + rolldownOptions: { plugins: [depOptimizerRolldownPlugin(options, () => isDuplicateOptimizer)] }, + }, + }; + } + return { + optimizeDeps: { + // Vite 6 and 7 have no `rolldownOptions`. + // oxlint-disable-next-line typescript/no-deprecated + esbuildOptions: { plugins: [depOptimizerEsbuildPlugin(options, () => isDuplicateOptimizer)] }, + }, + }; + } + return { resolve: { noExternal: noExternalModules() } }; }, configResolved(config: ResolvedConfig): void { // Two Sentry Vite plugins can each add this plugin to one build, for example `sentryCloudflareVitePlugin` and // `sentryReactRouter`. A module transformed twice declares the injected snippet twice and fails the build. - const first = (config.plugins as readonly OrchestrionVitePlugin[]).find(p => p[INSTANCE_KEY]); - isDuplicate = first !== undefined && first[INSTANCE_KEY] !== instance; + const instances = (config.plugins as readonly OrchestrionVitePlugin[]).flatMap(p => p[INSTANCE_KEY] ?? []); + isDuplicate = instances[0] !== undefined && instances[0] !== instance; + // The first instance can be one without `transformOptimizedDeps`, for example the one of `sentryReactRouter` + // before `sentryCloudflareVitePlugin`. + const firstOptimizer = instances.find(i => i.transformOptimizedDeps); + isDuplicateOptimizer = firstOptimizer !== undefined && firstOptimizer !== instance; // Nothing is force-bundled in `serve`, so an externalized module is expected there. if (isDuplicate || config.command === 'serve') { diff --git a/packages/server-utils/test/orchestrion/bundler.test.ts b/packages/server-utils/test/orchestrion/bundler.test.ts index b0690ab74903..33abf914aa74 100644 --- a/packages/server-utils/test/orchestrion/bundler.test.ts +++ b/packages/server-utils/test/orchestrion/bundler.test.ts @@ -1,8 +1,8 @@ import { existsSync } from 'node:fs'; import { isAbsolute } from 'node:path'; -import type { OnStartResult, PluginBuild } from 'esbuild'; +import type { OnResolveArgs, OnResolveResult, OnStartResult, Plugin as EsbuildPlugin, PluginBuild } from 'esbuild'; import type { NormalizedInputOptions, PluginContext } from 'rollup'; -import type { ResolvedConfig } from 'vite'; +import type { ResolvedConfig, Plugin as VitePlugin } from 'vite'; import type { Compiler } from 'webpack'; import { describe, expect, it, vi } from 'vitest'; import { sentryOrchestrionPlugin as bunPlugin } from '../../src/orchestrion/bundler/bun'; @@ -245,18 +245,34 @@ describe('sentryOrchestrionPlugin (vite)', () => { return config.handler.call(plugin, userConfig, { command, mode: 'production' }); } + type EnvironmentConfigResult = { + resolve?: { noExternal: string[] }; + optimizeDeps?: { + esbuildOptions?: { plugins: EsbuildPlugin[] }; + rolldownOptions?: { plugins: VitePlugin[] }; + }; + } | null; + function runConfigEnvironment( name: string, environmentConfig: Record, - command: 'build' | 'serve' = 'build', - ): { resolve: { noExternal: string[] } } | null { - const plugin = vitePlugin(); + { + command = 'build', + context, + plugin = vitePlugin(), + }: { + command?: 'build' | 'serve'; + // Vite 6 passes no context, Vite 8 passes `rolldownVersion`. + context?: { meta: { rolldownVersion?: string } }; + plugin?: VitePlugin; + } = {}, + ): EnvironmentConfigResult { const configEnvironment = plugin.configEnvironment as ( name: string, config: unknown, env: unknown, - ) => { resolve: { noExternal: string[] } } | null; - return configEnvironment.call(plugin, name, environmentConfig, { command, mode: 'production' }); + ) => EnvironmentConfigResult; + return configEnvironment.call(context, name, environmentConfig, { command, mode: 'production' }); } it('warns when instrumented modules are listed in ssr.external', () => { @@ -286,7 +302,145 @@ describe('sentryOrchestrionPlugin (vite)', () => { it('does not force-bundle instrumented modules on the dev server', () => { // Inlined in dev, the CommonJS drivers throw `exports is not defined` on import. expect(runConfig('serve')).toBeNull(); - expect(runConfigEnvironment('ssr', {}, 'serve')).toBeNull(); + expect( + runConfigEnvironment('ssr', {}, { command: 'serve', plugin: vitePlugin({ transformOptimizedDeps: true }) }) + ?.resolve, + ).toBeUndefined(); + }); + + it('does not change the dependency optimizer on the dev server without transformOptimizedDeps', () => { + // A Node server loads its dependencies with Node, where the runtime hook instruments them. + expect(runConfigEnvironment('ssr', {}, { command: 'serve' })).toBeNull(); + expect( + runConfigEnvironment( + 'worker', + { consumer: 'server' }, + { command: 'serve', context: { meta: { rolldownVersion: '1.2.14' } } }, + ), + ).toBeNull(); + }); + + it('runs the transform in the dependency optimizer of server environments with transformOptimizedDeps', () => { + const plugin = vitePlugin({ transformOptimizedDeps: true }); + const context = { meta: { rolldownVersion: '1.2.14' } }; + + expect(runConfigEnvironment('ssr', {}, { command: 'serve', plugin })).toEqual({ + optimizeDeps: { + esbuildOptions: { plugins: [expect.objectContaining({ name: 'sentry-orchestrion-dep-optimizer' })] }, + }, + }); + expect(runConfigEnvironment('worker', { consumer: 'server' }, { command: 'serve', context, plugin })).toEqual({ + optimizeDeps: { + rolldownOptions: { plugins: [expect.objectContaining({ name: 'sentry-orchestrion-dep-optimizer' })] }, + }, + }); + expect(runConfigEnvironment('client', {}, { command: 'serve', plugin })).toBeNull(); + expect(runConfigEnvironment('browser', { consumer: 'client' }, { command: 'serve', context, plugin })).toBeNull(); + }); + + it('transforms in the Rolldown dependency optimizer without the ssr flag, only in the first instance with transformOptimizedDeps', () => { + // For example the instance of `sentryReactRouter` before the one of `sentryCloudflareVitePlugin`. + const other = vitePlugin(); + const first = vitePlugin({ transformOptimizedDeps: true }); + const second = vitePlugin({ transformOptimizedDeps: true }); + const config = { + command: 'serve', + plugins: [other, first, second], + ssr: {}, + logger: { warn: vi.fn() }, + } as unknown as ResolvedConfig; + const context = { meta: { rolldownVersion: '1.2.14' } }; + const firstOptimizerPlugin = runConfigEnvironment('worker', {}, { command: 'serve', context, plugin: first }) + ?.optimizeDeps?.rolldownOptions?.plugins[0]; + const secondOptimizerPlugin = runConfigEnvironment('worker', {}, { command: 'serve', context, plugin: second }) + ?.optimizeDeps?.rolldownOptions?.plugins[0]; + + (other.configResolved as (config: unknown) => void)(config); + (first.configResolved as (config: unknown) => void)(config); + (second.configResolved as (config: unknown) => void)(config); + + const firstTransform = firstOptimizerPlugin?.transform as (this: unknown, code: string, id: string) => unknown; + const secondTransform = secondOptimizerPlugin?.transform as (this: unknown, code: string, id: string) => unknown; + expect(firstTransform.call({}, 'code', 'id')).toBe('transformed'); + expect(secondTransform.call({}, 'code', 'id')).toBeNull(); + }); + + it('resolves the snippet import to the ESM entry in the Rolldown dependency optimizer', async () => { + const optimizerPlugin = runConfigEnvironment( + 'worker', + {}, + { + command: 'serve', + context: { meta: { rolldownVersion: '1.2.14' } }, + plugin: vitePlugin({ transformOptimizedDeps: true }), + }, + )?.optimizeDeps?.rolldownOptions?.plugins[0]; + const resolveId = optimizerPlugin?.resolveId as ( + this: unknown, + source: string, + importer: string, + opts: { kind: string }, + ) => unknown; + const resolve = vi.fn().mockResolvedValue({ id: '/app/node_modules/@sentry/server-utils/build/cjs/index.js' }); + + await expect( + resolveId.call({ resolve }, '@sentry/server-utils', '/app/node_modules/mysql/index.js', { kind: 'require-call' }), + ).resolves.toEqual({ id: '/app/node_modules/@sentry/server-utils/build/esm/index.js' }); + expect(resolveId.call({ resolve }, 'mysql', '/app/src/index.ts', { kind: 'import-statement' })).toBeNull(); + expect(resolve).toHaveBeenCalledTimes(1); + }); + + it('resolves the snippet import to the ESM entry in the esbuild dependency optimizer', async () => { + const optimizerPlugin = runConfigEnvironment( + 'ssr', + {}, + { command: 'serve', plugin: vitePlugin({ transformOptimizedDeps: true }) }, + )?.optimizeDeps?.esbuildOptions?.plugins[0]; + const onResolve = vi.fn(); + const resolve = vi.fn().mockResolvedValue({ + errors: [], + path: '/app/node_modules/@sentry/server-utils/build/cjs/index.js', + }); + await optimizerPlugin?.setup({ onResolve, resolve } as unknown as PluginBuild); + + expect(onResolve).toHaveBeenCalledWith({ filter: /^@sentry\/server-utils$/ }, expect.any(Function)); + const callback = onResolve.mock.lastCall?.[1] as (args: Partial) => Promise; + await expect( + callback({ path: '@sentry/server-utils', importer: '/app/node_modules/mysql/index.js', kind: 'require-call' }), + ).resolves.toEqual({ errors: [], path: '/app/node_modules/@sentry/server-utils/build/esm/index.js' }); + + // When it fails (pnpm isolation), fall back to this package's own resolution. + resolve.mockResolvedValueOnce({ errors: [{ text: 'Could not resolve' }] }); + const fallback = await callback({ path: '@sentry/server-utils', importer: '/x.js', kind: 'require-call' }); + expect(fallback?.path).toMatch(/[/\\]build[/\\]esm[/\\]index\.js$/); + }); + + it('sets up the esbuild dependency optimizer plugin only in the first instance with transformOptimizedDeps', async () => { + const other = vitePlugin(); + const first = vitePlugin({ transformOptimizedDeps: true }); + const second = vitePlugin({ transformOptimizedDeps: true }); + const config = { + command: 'serve', + plugins: [other, first, second], + ssr: {}, + logger: { warn: vi.fn() }, + } as unknown as ResolvedConfig; + const firstOptimizerPlugin = runConfigEnvironment('ssr', {}, { command: 'serve', plugin: first })?.optimizeDeps + ?.esbuildOptions?.plugins[0]; + const secondOptimizerPlugin = runConfigEnvironment('ssr', {}, { command: 'serve', plugin: second })?.optimizeDeps + ?.esbuildOptions?.plugins[0]; + + (other.configResolved as (config: unknown) => void)(config); + (first.configResolved as (config: unknown) => void)(config); + (second.configResolved as (config: unknown) => void)(config); + + const firstOnResolve = vi.fn(); + const secondOnResolve = vi.fn(); + await firstOptimizerPlugin?.setup({ onResolve: firstOnResolve } as unknown as PluginBuild); + await secondOptimizerPlugin?.setup({ onResolve: secondOnResolve } as unknown as PluginBuild); + + expect(firstOnResolve).toHaveBeenCalledTimes(1); + expect(secondOnResolve).not.toHaveBeenCalled(); }); it('adds the top-level ssr option only when the config already has ssr or build.ssr', () => { @@ -296,10 +450,10 @@ describe('sentryOrchestrionPlugin (vite)', () => { }); it('force-bundles instrumented modules in server environments', () => { - expect(runConfigEnvironment('ssr', {})?.resolve.noExternal).toEqual( + expect(runConfigEnvironment('ssr', {})?.resolve?.noExternal).toEqual( expect.arrayContaining(['mysql', '@sentry/server-utils']), ); - expect(runConfigEnvironment('worker', { consumer: 'server' })?.resolve.noExternal).toContain('mysql'); + expect(runConfigEnvironment('worker', { consumer: 'server' })?.resolve?.noExternal).toContain('mysql'); expect(runConfigEnvironment('client', {})).toBeNull(); expect(runConfigEnvironment('browser', { consumer: 'client' })).toBeNull(); }); diff --git a/packages/server-utils/test/orchestrion/viteDepOptimizer.test.ts b/packages/server-utils/test/orchestrion/viteDepOptimizer.test.ts new file mode 100644 index 000000000000..d8ca916ede5a --- /dev/null +++ b/packages/server-utils/test/orchestrion/viteDepOptimizer.test.ts @@ -0,0 +1,60 @@ +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { createServer, type ViteDevServer } from 'vite'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import type { InstrumentationConfig } from '../../src/orchestrion/apmTypes'; +import { sentryOrchestrionPlugin } from '../../src/orchestrion/bundler/vite'; + +const instrumentations: InstrumentationConfig[] = [ + { + channelName: 'query', + module: { name: 'fake-db', versionRange: '>=1.0.0', filePath: 'index.js' }, + functionQuery: { functionName: 'query', kind: 'Sync' }, + }, +]; + +describe('sentryOrchestrionPlugin (vite) in the Rolldown dependency optimizer', () => { + let root: string; + let server: ViteDevServer; + + beforeAll(async () => { + root = mkdtempSync(join(tmpdir(), 'orchestrion-dep-optimizer-')); + const packageDir = join(root, 'node_modules', 'fake-db'); + mkdirSync(packageDir, { recursive: true }); + writeFileSync( + join(packageDir, 'package.json'), + JSON.stringify({ name: 'fake-db', version: '1.0.0', type: 'module' }), + ); + writeFileSync(join(packageDir, 'index.js'), 'export function query(sql) {\n return sql;\n}\n'); + + server = await createServer({ + root, + configFile: false, + logLevel: 'silent', + plugins: [sentryOrchestrionPlugin({ instrumentations, transformOptimizedDeps: true })], + server: { middlewareMode: true, ws: false, watch: null }, + environments: { + ssr: { + optimizeDeps: { include: ['fake-db'], rolldownOptions: { external: ['@sentry/server-utils'] } }, + }, + }, + }); + await server.environments.ssr.depsOptimizer?.init(); + }); + + afterAll(async () => { + await server.close(); + rmSync(root, { recursive: true, force: true }); + }); + + it('transforms a pre-bundled dependency and maps it only to its file in node_modules', () => { + const file = server.environments.ssr.depsOptimizer?.metadata.optimized['fake-db']?.file; + expect(file).toBeDefined(); + + expect(readFileSync(file!, 'utf8')).toContain('orchestrion:fake-db:query'); + const map = JSON.parse(readFileSync(`${file}.map`, 'utf8')) as { sources: string[] }; + // `node_modules/.vite/deps_ssr/fake-db.js` -> `node_modules/fake-db/index.js` + expect(map.sources).toEqual(['../../fake-db/index.js']); + }); +}); From cbab405d51b17e34c332cce0bb8852342a321767 Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Thu, 29 Oct 2026 12:48:32 -0700 Subject: [PATCH 046/169] feat(nestjs): Derive SentryCron monitor config from @Cron (#25013) Part of a series adding automatic Crons monitoring for the most-used job schedulers in each SDK. Check-ins for a monitor that doesn't exist yet are dropped, so today each monitor has to be created by hand with its schedule copied from the code. Sending the job's schedule as the monitor config lets Sentry create the monitor and keep it in sync. `@SentryCron` now sends the schedule and time zone of the method's `@Cron()` decorator with its check-ins, so Sentry creates the monitor on the first run. It's on by default; pass `fromCronDecorator: false` to send no config. A config with its own `schedule` is used as is. Monitors created this way are billed, and their schedule and time zone are overwritten from `@Cron()` on each check-in. ```ts @Cron('0 30 9 * * 1-5', { timeZone: 'Europe/Vienna' }) @SentryCron('daily-report', { checkinMargin: 2 }) async dailyReport() {} ``` Without a `timeZone`, the server's local time zone is sent. Schedules Sentry would read differently from `@nestjs/schedule` (sub-minute, numeric months, some day-field combinations, fixed UTC offsets) check in without a config. Docs: https://github.com/getsentry/sentry-docs/pull/19782 --------- Co-authored-by: Claude Opus 5.5 Co-authored-by: isaacs --- CHANGELOG.md | 6 + .../nestjs-basic/src/app.controller.ts | 6 + .../nestjs-basic/src/app.service.ts | 7 + .../nestjs-basic/tests/cron-decorator.test.ts | 29 ++ packages/nestjs/src/decorators.ts | 256 ++++++++++++++- packages/nestjs/src/index.ts | 1 + packages/nestjs/src/integrations/helpers.ts | 22 ++ .../integrations/orchestrion-subscriber.ts | 13 +- packages/nestjs/test/decorators.test.ts | 292 +++++++++++++++++- 9 files changed, 616 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29fecaa4a5ad..eac3bceb49b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +### Important Changes + +- **feat(nestjs): Derive SentryCron monitor config from @Cron ([#25013](https://github.com/getsentry/sentry-javascript/pull/25013))** + + `@SentryCron` now sends the schedule and time zone from the method's `@Cron()` decorator, so Sentry creates the monitor on the first run. If you use `@SentryCron` without a `schedule`, Sentry now creates missing monitors, which are billed, and overwrites schedules and time zones set by hand. Pass `fromCronDecorator: false` to turn this off. + Work in this release was contributed by @lazerg and @chiliec. Thank you for your contributions! ## 11.10.0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts index 9ffc328a2c30..33584dea5e78 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts @@ -86,6 +86,12 @@ export class AppController { return { result: await this.appService.testSpanDecoratorSync() }; } + @Get('test-derived-cron') + async testDerivedCron() { + await this.appService.testDerivedCron(); + return {}; + } + @Get('kill-test-cron/:job') async killTestCron(@Param('job') job: string) { this.appService.killTestCron(job); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts index a7b91b7b3d98..e9bb0c7f69c6 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts @@ -113,6 +113,13 @@ export class AppService { throw new Error('Test error from cron sync job'); } + // Disabled so it only runs through the `test-derived-cron` endpoint, without waiting for the schedule. + @Cron('0 30 9 * * 1-5', { name: 'test-derived-cron', timeZone: 'Europe/Vienna', disabled: true }) + @SentryCron('test-derived-cron-slug', { checkinMargin: 2 }) + async testDerivedCron() { + console.log('Test derived cron!'); + } + async killTestCron(job: string) { this.schedulerRegistry.deleteCronJob(job); } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts index 6aeeae723a64..e1d006ccdb5d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts @@ -62,6 +62,35 @@ test('Cron job triggers send of in_progress envelope', async ({ baseURL }) => { await fetch(`${baseURL}/kill-test-cron/test-cron-job`); }); +test('Sends the schedule and time zone of @Cron with the check-in', async ({ baseURL }) => { + const inProgressEnvelopePromise = waitForEnvelopeItem('nestjs-basic', envelope => { + return ( + envelope[0].type === 'check_in' && + envelope[1]['monitor_slug'] === 'test-derived-cron-slug' && + envelope[1]['status'] === 'in_progress' + ); + }); + + await fetch(`${baseURL}/test-derived-cron`); + + const inProgressEnvelope = await inProgressEnvelopePromise; + + expect(inProgressEnvelope[1]).toEqual( + expect.objectContaining({ + monitor_slug: 'test-derived-cron-slug', + status: 'in_progress', + monitor_config: { + schedule: { + type: 'crontab', + value: '30 9 * * 1-5', + }, + timezone: 'Europe/Vienna', + checkin_margin: 2, + }, + }), + ); +}); + test('Sends exceptions to Sentry on error in async cron job', async ({ baseURL }) => { const errorEventPromise = waitForError('nestjs-basic', event => { return ( diff --git a/packages/nestjs/src/decorators.ts b/packages/nestjs/src/decorators.ts index 4d5762481d2f..ead5d10338f0 100644 --- a/packages/nestjs/src/decorators.ts +++ b/packages/nestjs/src/decorators.ts @@ -1,34 +1,268 @@ import type { MonitorConfig } from '@sentry/core'; import { CODE_FUNCTION_NAME, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; -import { captureException } from '@sentry/core'; +import { captureException, debug } from '@sentry/core'; import * as Sentry from '@sentry/node'; import { startSpan } from '@sentry/node'; +import { DEBUG_BUILD } from './debug-build'; import { isExpectedError } from './helpers'; -import { copyReflectMetadata } from './integrations/helpers'; +import type { ReflectWithMetadata } from './integrations/helpers'; +import { copyReflectMetadata, getNestScheduleVersion } from './integrations/helpers'; + +/** + * Monitor settings for `@SentryCron` whose schedule and time zone come from the `@Cron()` decorator + * of `@nestjs/schedule` on the same method. Set `fromCronDecorator: false` to not send them. + */ +export type SentryCronMonitorSettings = Omit & { + schedule?: never; + timezone?: never; + fromCronDecorator?: boolean; +}; /** * A decorator wrapping the native nest Cron decorator, sending check-ins to Sentry. + * + * Unless a monitor config with a `schedule` is passed, the schedule and time zone of the method's + * `@Cron()` decorator are sent with each check-in, so Sentry can create the monitor on the first run. */ -export const SentryCron = (monitorSlug: string, monitorConfig?: MonitorConfig): MethodDecorator => { +export const SentryCron = ( + monitorSlug: string, + monitorConfig?: (MonitorConfig & { fromCronDecorator?: never }) | SentryCronMonitorSettings, +): MethodDecorator => { return (target: unknown, propertyKey, descriptor: PropertyDescriptor) => { const originalMethod = descriptor.value as (...args: unknown[]) => Promise; - descriptor.value = function (...args: unknown[]) { - return Sentry.withMonitor( - monitorSlug, - () => { - return originalMethod.apply(this, args); - }, - monitorConfig, - ); + let resolvedMonitorConfig: MonitorConfig | undefined; + let isolateTraceWithoutConfig = false; + let resolved = false; + + const wrappedMethod = function (this: unknown, ...args: unknown[]): unknown { + if (!resolved) { + resolved = true; + // A `@Cron()` applied after `@SentryCron()` puts its metadata on this wrapper, so read it at call time. + resolvedMonitorConfig = resolveMonitorConfig(monitorSlug, monitorConfig, [ + wrappedMethod, + (target as Record | undefined)?.[propertyKey], + ]); + isolateTraceWithoutConfig = !resolvedMonitorConfig && !!monitorConfig?.isolateTrace; + } + + const runWithMonitor = (): unknown => + Sentry.withMonitor( + monitorSlug, + () => { + return originalMethod.apply(this, args); + }, + resolvedMonitorConfig, + ); + + // `withMonitor` reads `isolateTrace` from the monitor config, which is not sent without a schedule. + return isolateTraceWithoutConfig ? Sentry.startNewTrace(runWithMonitor) : runWithMonitor(); }; + descriptor.value = wrappedMethod; + copyFunctionNameAndMetadata({ originalMethod, descriptor }); return descriptor; }; }; +function resolveMonitorConfig( + monitorSlug: string, + monitorConfig: MonitorConfig | SentryCronMonitorSettings | undefined, + candidates: unknown[], +): MonitorConfig | undefined { + if (monitorConfig?.schedule) { + return monitorConfig; + } + + const { fromCronDecorator = true, ...monitorSettings } = monitorConfig || {}; + const cronConfig = fromCronDecorator ? getMonitorConfigFromNestCron(candidates) : undefined; + + if (!cronConfig) { + const { isolateTrace: _isolateTrace, ...unsentSettings } = monitorSettings; + if (DEBUG_BUILD && Object.keys(unsentSettings).length) { + const reason = fromCronDecorator + ? 'no schedule could be taken from @Cron()' + : 'fromCronDecorator is false and no schedule was passed'; + debug.warn( + `[SentryCron] The monitor settings for "${monitorSlug}" are not sent, because ${reason}. A monitor config needs a schedule.`, + ); + } + return undefined; + } + + return { ...monitorSettings, ...cronConfig }; +} + +const SCHEDULE_CRON_OPTIONS = 'SCHEDULE_CRON_OPTIONS'; + +// `cron` presets (case-insensitive). Sentry accepts only the first five, so the others are sent as crontabs. +const CRON_PRESETS: Record = { + '@yearly': '@yearly', + '@monthly': '@monthly', + '@weekly': '@weekly', + '@daily': '@daily', + '@hourly': '@hourly', + '@minutely': '* * * * *', + '@weekdays': '0 0 * * 1-5', + '@weekends': '0 0 * * 0,6', +}; + +interface NestCronOptions { + cronTime?: unknown; + timeZone?: unknown; + utcOffset?: unknown; +} + +function getMonitorConfigFromNestCron(candidates: unknown[]): MonitorConfig | undefined { + const R = Reflect as ReflectWithMetadata; + if (typeof R.getMetadata !== 'function') { + return undefined; + } + + for (const candidate of candidates) { + if (typeof candidate !== 'function') { + continue; + } + const cronOptions = R.getMetadata(SCHEDULE_CRON_OPTIONS, candidate); + if (cronOptions && typeof cronOptions === 'object') { + return nestCronOptionsToMonitorConfig(cronOptions, getNestScheduleVersion(cronOptions)); + } + } + + return undefined; +} + +/** + * Converts the options `@Cron()` stores as metadata into a Sentry monitor config. + * Returns `undefined` for schedules Sentry can't represent. + */ +function nestCronOptionsToMonitorConfig( + cronOptions: NestCronOptions, + scheduleVersion: string | undefined, +): MonitorConfig | undefined { + const { cronTime, timeZone, utcOffset } = cronOptions; + + // A fixed UTC offset has no IANA time zone equivalent. + if (typeof cronTime !== 'string' || utcOffset != null) { + return undefined; + } + + const fields = cronTime.trim().split(/\s+/); + let crontab: string | undefined; + if (fields.length === 1) { + crontab = CRON_PRESETS[(fields[0] as string).toLowerCase()]; + } else if (fields.length === 5) { + crontab = isSupportedCrontab(fields, scheduleVersion) ? fields.join(' ') : undefined; + } else if (fields.length === 6 && /^\d+$/.test(fields[0] as string)) { + // Sentry schedules have minute granularity, so only a fixed second can be dropped. + crontab = isSupportedCrontab(fields.slice(1), scheduleVersion) ? fields.slice(1).join(' ') : undefined; + } + + if (!crontab) { + return undefined; + } + + // Without a `timeZone`, `cron` uses the server's time zone, but Sentry would assume UTC. + const timezone = toSentryTimeZone(typeof timeZone === 'string' && timeZone ? timeZone : getLocalTimeZone()); + if (!timezone) { + return undefined; + } + + return { + schedule: { type: 'crontab', value: crontab }, + timezone, + }; +} + +/** + * Whether Sentry reads the 5 crontab fields the same way `cron` (used by `@nestjs/schedule`) runs them. + */ +function isSupportedCrontab( + [, , dayOfMonth = '', month = '', dayOfWeek = '']: string[], + scheduleVersion: string | undefined, +): boolean { + // `cron` 2.x (`@nestjs/schedule` 2 and 3) counts months from 0. Steps don't depend on that. + const countsMonthsFromOne = parseInt(scheduleVersion ?? '', 10) >= 4; + if (!countsMonthsFromOne && /\d/.test(month.replace(/\/\d+/g, ''))) { + return false; + } + + const daysOfMonth = countCronValues(dayOfMonth, 1, 31); + const daysOfWeek = countCronValues(dayOfWeek, 0, 7); + if (daysOfMonth === undefined || daysOfWeek === undefined) { + return false; + } + + // `cron` ignores a day field that covers every day (like `1-31` or `sun-sat`) and runs on either day field + // otherwise. Sentry reads full ranges as they are and needs both day fields when one starts with `*` (like `*/2`). + const allDaysOfMonth = daysOfMonth === 31; + const allDaysOfWeek = daysOfWeek === 7; + const cronRunsOnEither = !allDaysOfMonth && !allDaysOfWeek; + const sentryRunsOnEither = !dayOfMonth.startsWith('*') && !dayOfWeek.startsWith('*'); + return (allDaysOfMonth && allDaysOfWeek) || cronRunsOnEither === sentryRunsOnEither; +} + +const DAY_NAMES: Record = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 }; + +/** + * How many days a day field matches, expanded the way `cron` does it, or `undefined` if `cron` can't parse it. + * Day of week `7` is Sunday, like `0`. + */ +function countCronValues(field: string, low: number, high: number): number | undefined { + let unknownName = false; + const numericField = field.replace(/[a-z]{1,3}/gi, name => { + const value = DAY_NAMES[name.toLowerCase()]; + unknownName ||= value === undefined; + return String(value); + }); + if (unknownName) { + return undefined; + } + + const values = new Set(); + for (const part of numericField.split(',')) { + const match = part.match(/^(?:(\*)|(\d+)(?:-(\d+))?)(?:\/(\d+))?$/); + if (!match) { + return undefined; + } + const [, wildcard, from, to, step] = match; + const lower = wildcard ? low : Number(from); + const upper = wildcard || (step && !to) ? high : Number(to ?? from); + const increment = Number(step ?? 1); + if (!increment || lower < low || upper > high || lower > upper) { + return undefined; + } + for (let value = lower; value <= upper; value += increment) { + values.add(high === 7 && value === 7 ? 0 : value); + } + } + return values.size; +} + +/** + * The canonical IANA name of the time zone, or `undefined` if Sentry won't accept it (like `UTC+3`). + */ +function toSentryTimeZone(timezone: string | undefined): string | undefined { + if (!timezone || timezone === 'Etc/Unknown' || /^(?:utc|gmt)?[+-]/i.test(timezone)) { + return undefined; + } + try { + return new Intl.DateTimeFormat('en-US', { timeZone: timezone }).resolvedOptions().timeZone || undefined; + } catch { + return undefined; + } +} + +function getLocalTimeZone(): string | undefined { + try { + return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined; + } catch { + return undefined; + } +} + /** * A decorator usable to wrap arbitrary functions with spans. */ diff --git a/packages/nestjs/src/index.ts b/packages/nestjs/src/index.ts index b96fb4c5390a..10a3400ee400 100644 --- a/packages/nestjs/src/index.ts +++ b/packages/nestjs/src/index.ts @@ -8,3 +8,4 @@ export { nestIntegration } from './integrations/nest'; export { getDefaultIntegrations, init } from './sdk'; export { SentryCron, SentryExceptionCaptured, SentryTraced } from './decorators'; +export type { SentryCronMonitorSettings } from './decorators'; diff --git a/packages/nestjs/src/integrations/helpers.ts b/packages/nestjs/src/integrations/helpers.ts index 5056413e911a..8c2b5755d910 100644 --- a/packages/nestjs/src/integrations/helpers.ts +++ b/packages/nestjs/src/integrations/helpers.ts @@ -31,6 +31,28 @@ export function markWrapped(fn: AnyFn): void { (fn as AnyFn & Record)[SENTRY_WRAPPED] = true; } +// `@nestjs/schedule`'s metadata key for the `@Cron()` options. +const SCHEDULE_CRON_OPTIONS = 'SCHEDULE_CRON_OPTIONS'; + +const NEST_SCHEDULE_VERSIONS = new WeakMap(); + +/** Remembers the `@nestjs/schedule` version whose `@Cron()` stored the options on `method`. */ +export function setNestScheduleVersion(method: unknown, version: string | undefined): void { + const R = Reflect as ReflectWithMetadata; + if (typeof method !== 'function' || !version || typeof R.getMetadata !== 'function') { + return; + } + const cronOptions = R.getMetadata(SCHEDULE_CRON_OPTIONS, method); + if (cronOptions && typeof cronOptions === 'object') { + NEST_SCHEDULE_VERSIONS.set(cronOptions, version); + } +} + +/** The `@nestjs/schedule` version that stored these `@Cron()` options, if the SDK saw the decorator run. */ +export function getNestScheduleVersion(cronOptions: object): string | undefined { + return NEST_SCHEDULE_VERSIONS.get(cronOptions); +} + /** * The subset of `reflect-metadata`'s `Reflect` augmentation that NestJS * relies on. Methods are optional because `reflect-metadata` may not be diff --git a/packages/nestjs/src/integrations/orchestrion-subscriber.ts b/packages/nestjs/src/integrations/orchestrion-subscriber.ts index 53e21dda735f..0f207438d716 100644 --- a/packages/nestjs/src/integrations/orchestrion-subscriber.ts +++ b/packages/nestjs/src/integrations/orchestrion-subscriber.ts @@ -5,7 +5,7 @@ import { bindTracingChannelToSpan } from '@sentry/server-utils'; import { nestjsChannels as CHANNELS } from '@sentry/server-utils/orchestrion/config'; import { DEBUG_BUILD } from '../debug-build'; import type { AnyFn } from './helpers'; -import { isWrapped, markWrapped } from './helpers'; +import { isWrapped, markWrapped, setNestScheduleVersion } from './helpers'; import type { CatchTarget, InjectableTarget, MinimalNestJsExecutionContext } from './types'; import { patchCatchTarget, patchInjectableTarget } from './wrap-components'; import { @@ -184,9 +184,14 @@ export function subscribeToNestChannels(): void { subscribeDecoratorChannel(CHANNELS.NESTJS_CATCH, patchCatchTarget); // @Cron/@Interval/@Timeout (schedule), @OnEvent (event), @Processor (bullmq) - subscribeFactoryDecorator(CHANNELS.NESTJS_SCHEDULE_CRON, decorator => - makeMethodDecorator(decorator, handler => wrapScheduleHandler(handler, MECHANISM_CRON)), - ); + subscribeFactoryDecorator(CHANNELS.NESTJS_SCHEDULE_CRON, (decorator, data) => { + const cronDecorator = makeMethodDecorator(decorator, handler => wrapScheduleHandler(handler, MECHANISM_CRON)); + return function (this: unknown, ...args: unknown[]): unknown { + const result = cronDecorator.apply(this, args); + setNestScheduleVersion((args[2] as PropertyDescriptor | undefined)?.value, data.moduleVersion); + return result; + }; + }); subscribeFactoryDecorator(CHANNELS.NESTJS_SCHEDULE_INTERVAL, decorator => makeMethodDecorator(decorator, handler => wrapScheduleHandler(handler, MECHANISM_INTERVAL)), ); diff --git a/packages/nestjs/test/decorators.test.ts b/packages/nestjs/test/decorators.test.ts index 661f00b89182..47bc07d14ab4 100644 --- a/packages/nestjs/test/decorators.test.ts +++ b/packages/nestjs/test/decorators.test.ts @@ -1,9 +1,14 @@ import 'reflect-metadata'; +import { tracingChannel } from 'node:diagnostics_channel'; +import { SetMetadata } from '@nestjs/common'; import { CODE_FUNCTION_NAME, SENTRY_OP, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; import * as core from '@sentry/core'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { OrchestrionChannelContext } from '@sentry/server-utils'; +import { nestjsChannels as CHANNELS } from '@sentry/server-utils/orchestrion/config'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { SentryCron, SentryExceptionCaptured, SentryTraced } from '../src/decorators'; import * as helpers from '../src/helpers'; +import { subscribeToNestChannels } from '../src/integrations/orchestrion-subscriber'; describe('SentryTraced decorator', () => { beforeEach(() => { @@ -258,6 +263,291 @@ describe('SentryCron decorator', () => { }); }); +describe('SentryCron decorator with @Cron', () => { + // Mirrors `@Cron()` of `@nestjs/schedule`, which stores its options under this metadata key. + const Cron = (cronTime: unknown, options: Record = {}): MethodDecorator => + SetMetadata('SCHEDULE_CRON_OPTIONS', { ...options, cronTime }); + + // `@Cron()` as the SDK's instrumentation sees it, with the `@nestjs/schedule` version. + const VersionedCron = (cronTime: string, moduleVersion: string): MethodDecorator => { + subscribeToNestChannels(); + const data: OrchestrionChannelContext = { arguments: [cronTime], moduleVersion }; + tracingChannel(CHANNELS.NESTJS_SCHEDULE_CRON).traceSync(() => Cron(cronTime), data); + return data.result as MethodDecorator; + }; + + // Mirrors the SDK's own `@Cron()` instrumentation, which swaps the method before nest stores the metadata. + const WrappingCron = + (cronTime: unknown): MethodDecorator => + (target, propertyKey, descriptor) => { + const original = descriptor.value as unknown as (...args: unknown[]) => unknown; + (descriptor as PropertyDescriptor).value = function (this: unknown, ...args: unknown[]) { + return original.apply(this, args); + }; + return Cron(cronTime)(target, propertyKey, descriptor); + }; + + // Applies decorators the way TypeScript does: listed top to bottom, applied bottom to top. + function decorate(...decorators: MethodDecorator[]): { job: () => Promise } { + class Service {} + let descriptor: PropertyDescriptor = { + value: async () => 'done', + writable: true, + enumerable: false, + configurable: true, + }; + for (const decorator of [...decorators].reverse()) { + descriptor = (decorator(Service.prototype, 'job', descriptor) as PropertyDescriptor | undefined) ?? descriptor; + } + Object.defineProperty(Service.prototype, 'job', descriptor); + return new Service() as { job: () => Promise }; + } + + beforeEach(() => { + // `@Cron()` without a `timeZone` runs in the server's local time zone. + const DateTimeFormat = Intl.DateTimeFormat; + vi.spyOn(Intl, 'DateTimeFormat').mockImplementation(function ( + locales?: string | string[], + options?: Intl.DateTimeFormatOptions, + ) { + return new DateTimeFormat(locales, { timeZone: 'Asia/Tokyo', ...options }); + } as typeof Intl.DateTimeFormat); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('derives the monitor config when @SentryCron is above @Cron', async () => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job'), Cron('0 * * * *')); + + expect(await service.job()).toBe('done'); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), { + schedule: { type: 'crontab', value: '0 * * * *' }, + timezone: 'Asia/Tokyo', + }); + }); + + it('derives the monitor config when @Cron is above @SentryCron', async () => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(Cron('0 * * * *'), SentryCron('my-job')); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), { + schedule: { type: 'crontab', value: '0 * * * *' }, + timezone: 'Asia/Tokyo', + }); + }); + + it('derives the monitor config when @Cron replaces the method', async () => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(WrappingCron('0 * * * *'), SentryCron('my-job')); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), { + schedule: { type: 'crontab', value: '0 * * * *' }, + timezone: 'Asia/Tokyo', + }); + }); + + it('drops a fixed seconds field and passes the time zone', async () => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job'), Cron('0 30 9 * * 1-5', { timeZone: 'Europe/Vienna' })); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), { + schedule: { type: 'crontab', value: '30 9 * * 1-5' }, + timezone: 'Europe/Vienna', + }); + }); + + it('sends the canonical time zone name', async () => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job'), Cron('0 * * * *', { timeZone: 'europe/vienna' })); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), { + schedule: { type: 'crontab', value: '0 * * * *' }, + timezone: 'Europe/Vienna', + }); + }); + + it.each([ + ['a sub-minute schedule', Cron('*/5 * * * * *')], + ['a one-off date', Cron(new Date())], + ['an unknown preset', Cron('@reboot')], + ['a utc offset', Cron('0 * * * *', { utcOffset: 120 })], + ['a numeric month', Cron('0 9 1 5 *')], + ['a numeric month range', Cron('0 9 1 1-6 *')], + ['a numeric month list', Cron('0 9 1 1,7 *')], + ['a numeric month with a step', Cron('0 9 1 2/3 *')], + // `0 0 1 0 *` in `@nestjs/schedule` 3 + ['CronExpression.EVERY_YEAR', Cron('0 0 1 1 *')], + ['a day-of-month step with a day of week', Cron('0 9 */2 * MON')], + ['a full day-of-month range with a day of week', Cron('0 9 1-31 * MON')], + ['a full day-of-week range with a day of month', Cron('0 9 1 * 0-6')], + ['a full 1-7 day-of-week range with a day of month', Cron('0 9 15 * 1-7')], + ['a full day-of-week name range with a day of month', Cron('0 9 1 * sun-sat')], + ['a full upper-case day-of-week name range with a day of month', Cron('0 9 1 * SUN-SAT')], + ['a full mon-sun day-of-week range with a day of month', Cron('0 9 1 * mon-sun')], + ['a full day-of-week list with a day of month', Cron('0 9 1 * 0,1,2,3,4,5,6')], + ['a full day-of-month range with a step of 1 and a day of week', Cron('0 9 1-31/1 * MON')], + ['an unknown day name', Cron('0 9 1 * sunday')], + ['an out-of-range day of month', Cron('0 9 32 * *')], + ['a numeric month from @nestjs/schedule 3', VersionedCron('0 9 1 5 *', '3.0.4')], + ['a fixed-offset time zone', Cron('0 * * * *', { timeZone: 'UTC+3' })], + ['an unknown time zone', Cron('0 * * * *', { timeZone: 'Mars/Olympus' })], + ])('sends no monitor config for %s', async (_, cronDecorator) => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job'), cronDecorator); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), undefined); + }); + + it.each([ + ['a month name', '0 9 1 MAY *'], + ['both day fields', '0 9 1-7 * MON'], + ['a day-of-week name list with a day of month', '0 9 1 * mon,fri'], + ['a full day-of-month range with a day-of-week step', '0 9 1-31 * */2'], + ['a day-of-month step of 1 with a day of week', '0 9 */1 * MON'], + ['both day fields covering every day', '0 9 1-31 * sun-sat'], + ['CronExpression.EVERY_QUARTER', '0 0 1 */3 *'], + ['CronExpression.EVERY_6_MONTHS', '0 0 1 */6 *'], + ])('sends a crontab with %s', async (_, crontab) => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job'), Cron(crontab)); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), { + schedule: { type: 'crontab', value: crontab }, + timezone: 'Asia/Tokyo', + }); + }); + + it.each([ + ['a numeric month', '0 9 1 5 *'], + ['a numeric month range', '0 9 1 1-6 *'], + ['CronExpression.EVERY_YEAR', '0 0 1 1 *'], + ])('sends a crontab with %s from @nestjs/schedule 4 or later', async (_, crontab) => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job'), VersionedCron(crontab, '4.1.2')); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), { + schedule: { type: 'crontab', value: crontab }, + timezone: 'Asia/Tokyo', + }); + }); + + it('sends no monitor config when the local time zone is unknown', async () => { + vi.mocked(Intl.DateTimeFormat).mockImplementation(function () { + return { resolvedOptions: () => ({ timeZone: 'Etc/Unknown' }) }; + } as unknown as typeof Intl.DateTimeFormat); + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job'), Cron('0 * * * *')); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), undefined); + }); + + it.each([ + ['@hourly', '@hourly'], + ['@daily', '@daily'], + ['@WEEKLY', '@weekly'], + ['@monthly', '@monthly'], + ['@yearly', '@yearly'], + ['@weekdays', '0 0 * * 1-5'], + ])('sends the preset %s as %s', async (preset, value) => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job'), Cron(preset)); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), { + schedule: { type: 'crontab', value }, + timezone: 'Asia/Tokyo', + }); + }); + + it('warns when monitor settings are passed but no schedule can be derived', async () => { + const warnSpy = vi.spyOn(core.debug, 'warn').mockImplementation(() => undefined); + const withMonitorSpy = vi.spyOn(core, 'withMonitor').mockImplementation((_, callback) => callback()); + const service = decorate(SentryCron('my-job', { checkinMargin: 2 }), Cron('*/5 * * * * *')); + + await service.job(); + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), undefined); + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('"my-job"')); + }); + + it('does not warn when no monitor settings are passed', async () => { + const warnSpy = vi.spyOn(core.debug, 'warn').mockImplementation(() => undefined); + vi.spyOn(core, 'withMonitor').mockImplementation((_, callback) => callback()); + const service = decorate(SentryCron('my-job'), Cron('*/5 * * * * *')); + + await service.job(); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('sends no monitor config with fromCronDecorator: false', async () => { + const warnSpy = vi.spyOn(core.debug, 'warn').mockImplementation(() => undefined); + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job', { fromCronDecorator: false, checkinMargin: 2 }), Cron('0 * * * *')); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), undefined); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('fromCronDecorator is false')); + }); + + it('isolates the trace without a monitor config', async () => { + const warnSpy = vi.spyOn(core.debug, 'warn').mockImplementation(() => undefined); + const traceIds: string[] = []; + const withMonitorSpy = vi.spyOn(core, 'withMonitor').mockImplementation((_, callback) => { + traceIds.push(core.getCurrentScope().getPropagationContext().traceId); + return callback(); + }); + const service = decorate(SentryCron('my-job', { isolateTrace: true }), Cron('*/5 * * * * *')); + const parentTraceId = core.getCurrentScope().getPropagationContext().traceId; + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), undefined); + expect(traceIds).toHaveLength(1); + expect(traceIds[0]).not.toBe(parentTraceId); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('keeps the other monitor settings', async () => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const service = decorate(SentryCron('my-job', { checkinMargin: 2, maxRuntime: 10 }), Cron('0 * * * *')); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), { + schedule: { type: 'crontab', value: '0 * * * *' }, + timezone: 'Asia/Tokyo', + checkinMargin: 2, + maxRuntime: 10, + }); + }); + + it('uses an explicit monitor config as is', async () => { + const withMonitorSpy = vi.spyOn(core, 'withMonitor'); + const monitorConfig: core.MonitorConfig = { schedule: { type: 'interval', value: 1, unit: 'hour' } }; + const service = decorate(SentryCron('my-job', monitorConfig), Cron('0 * * * *')); + + await service.job(); + expect(withMonitorSpy).toHaveBeenCalledWith('my-job', expect.any(Function), monitorConfig); + }); + + it('does not accept a schedule or time zone together with fromCronDecorator', () => { + // @ts-expect-error - `fromCronDecorator` only applies to settings without a schedule + SentryCron('my-job', { schedule: { type: 'crontab', value: '0 * * * *' }, fromCronDecorator: false }); + // @ts-expect-error - the time zone comes from `@Cron()` + SentryCron('my-job', { timezone: 'Europe/Vienna', checkinMargin: 2 }); + }); +}); + describe('SentryExceptionCaptured decorator', () => { beforeEach(() => { vi.clearAllMocks(); From 9310b4de0d6b134994b137cd74b2a0d736b2a54c Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Thu, 29 Oct 2026 13:45:48 -0700 Subject: [PATCH 047/169] feat(node): Add nodeCronIntegration to monitor node-cron tasks (#25473) Part of a series adding automatic Crons monitoring for the most-used job schedulers in each SDK. Check-ins for a monitor that doesn't exist yet are dropped, so today each monitor has to be created by hand with its schedule copied from the code. Sending the job's schedule as the monitor config lets Sentry create the monitor and keep it in sync. Adds `nodeCronIntegration`, which sends check-ins for every named `node-cron` task (3.x and 4.x) with its schedule and time zone as the monitor config. Today each module has to be wrapped in `Sentry.cron.instrumentNodeCron`; with this, adding the integration is enough. It is not a default integration. Tasks without a `name` aren't monitored. ```js Sentry.init({ dsn: '...', integrations: [Sentry.nodeCronIntegration({ excludeJobs: ['cleanup', /^debug-/] })], }); cron.schedule('0 9 * * 1-5', sendReport, { name: 'daily-report', timezone: 'Europe/Vienna' }); ``` Tasks with schedules Crons can't express, like every 10 seconds, aren't monitored, and neither are node-cron 3 schedules with steps it reads differently from standard cron, like `*/2` days. Tasks already wrapped by `instrumentNodeCron` are skipped, so using both doesn't send duplicate check-ins. --------- Co-authored-by: Claude --- .../node-suites/excludes.ts | 1 + .../cron/node-cron/integration/instrument.mjs | 9 + .../cron/node-cron/integration/scenario.mjs | 25 +++ .../suites/cron/node-cron/integration/test.ts | 50 +++++ packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/deno/src/index.ts | 1 + packages/elysia/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/cron/node-cron.ts | 5 +- packages/node/src/index.ts | 2 + packages/remix/src/server/index.ts | 1 + packages/server-utils/src/index.ts | 2 + .../src/integrations/node-cron.ts | 104 ++++++++++ .../server-utils/src/orchestrion/channels.ts | 2 + .../src/orchestrion/config/index.ts | 2 + .../src/orchestrion/config/node-cron.ts | 52 +++++ .../server-utils/src/utils/cronMonitoring.ts | 190 ++++++++++++++++++ .../test/integrations/node-cron.test.ts | 66 ++++++ .../moduleInjectedTransform.test.ts | 4 +- .../test/utils/cronMonitoring.test.ts | 174 ++++++++++++++++ packages/solidstart/src/server/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + 24 files changed, 695 insertions(+), 2 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/cron/node-cron/integration/instrument.mjs create mode 100644 dev-packages/node-integration-tests/suites/cron/node-cron/integration/scenario.mjs create mode 100644 dev-packages/node-integration-tests/suites/cron/node-cron/integration/test.ts create mode 100644 packages/server-utils/src/integrations/node-cron.ts create mode 100644 packages/server-utils/src/orchestrion/config/node-cron.ts create mode 100644 packages/server-utils/src/utils/cronMonitoring.ts create mode 100644 packages/server-utils/test/integrations/node-cron.test.ts create mode 100644 packages/server-utils/test/utils/cronMonitoring.test.ts diff --git a/dev-packages/bun-integration-tests/node-suites/excludes.ts b/dev-packages/bun-integration-tests/node-suites/excludes.ts index 116d86d2634c..30c08744ef1d 100644 --- a/dev-packages/bun-integration-tests/node-suites/excludes.ts +++ b/dev-packages/bun-integration-tests/node-suites/excludes.ts @@ -64,6 +64,7 @@ const NO_FETCH_INSTRUMENTATION = [ // instrumentation creates no spans. Apps must be built with `@sentry/bun/plugin`. // See https://github.com/getsentry/sentry-javascript/issues/23882 export const NO_AUTO_INSTRUMENTATION = [ + 'suites/cron/node-cron/integration/test.ts', 'suites/express/**', 'suites/fs-instrumentation/test.ts', 'suites/hono/test.ts', diff --git a/dev-packages/node-integration-tests/suites/cron/node-cron/integration/instrument.mjs b/dev-packages/node-integration-tests/suites/cron/node-cron/integration/instrument.mjs new file mode 100644 index 000000000000..521d7bbe12ff --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/node-cron/integration/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', + transport: loggingTransport, + integrations: [Sentry.nodeCronIntegration({ excludeJobs: ['excluded-job'] })], +}); diff --git a/dev-packages/node-integration-tests/suites/cron/node-cron/integration/scenario.mjs b/dev-packages/node-integration-tests/suites/cron/node-cron/integration/scenario.mjs new file mode 100644 index 000000000000..bee2fec76356 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/node-cron/integration/scenario.mjs @@ -0,0 +1,25 @@ +import cron from 'node-cron'; + +const excluded = cron.schedule('0 0 0 1 1 *', () => {}, { name: 'excluded-job' }); +const unnamed = cron.schedule('0 0 0 1 1 *', () => {}); + +let runs = 0; +const task = cron.schedule( + '0 0 0 1 1 *', + () => { + runs++; + if (runs > 1) { + throw new Error('Error in cron job'); + } + }, + { name: 'my-cron-job', timezone: 'America/New_York' }, +); + +// Run the tasks by hand. Their schedules can't fire during the test. +excluded + .execute() + .then(() => unnamed.execute()) + .then(() => task.execute()) + .then(() => task.execute()); + +setTimeout(() => process.exit(), 10_000); diff --git a/dev-packages/node-integration-tests/suites/cron/node-cron/integration/test.ts b/dev-packages/node-integration-tests/suites/cron/node-cron/integration/test.ts new file mode 100644 index 000000000000..e90549ce84c8 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/node-cron/integration/test.ts @@ -0,0 +1,50 @@ +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; + +const MONITOR_CONFIG = { + schedule: { type: 'crontab', value: '0 0 1 1 *' }, + timezone: 'America/New_York', +}; + +describe('nodeCronIntegration', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('sends check-ins for named tasks', async () => { + await createRunner() + .ignore('event') + .expect({ + check_in: { monitor_slug: 'my-cron-job', status: 'in_progress', monitor_config: MONITOR_CONFIG }, + }) + .expect({ check_in: { monitor_slug: 'my-cron-job', status: 'ok', duration: expect.any(Number) } }) + .expect({ + check_in: { monitor_slug: 'my-cron-job', status: 'in_progress', monitor_config: MONITOR_CONFIG }, + }) + .expect({ check_in: { monitor_slug: 'my-cron-job', status: 'error', duration: expect.any(Number) } }) + .start() + .completed(); + }); + + test('captures job errors', async () => { + await createRunner() + .unordered() + .expect({ + event: { + exception: { + values: [ + { + type: 'Error', + value: 'Error in cron job', + mechanism: { type: 'auto.function.node-cron', handled: false }, + }, + ], + }, + }, + }) + .start() + .completed(); + }); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 58bcd049c076..0db5cfe2ef90 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -49,6 +49,7 @@ export { fsIntegration, functionToStringIntegration, genericPoolIntegration, + nodeCronIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 41c0d0131184..9df4b06ff240 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -118,6 +118,7 @@ export { firebaseIntegration, fsIntegration, genericPoolIntegration, + nodeCronIntegration, graphqlIntegration, knexIntegration, kafkaIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 5ff613985c91..3f14e1c6da6f 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -143,6 +143,7 @@ export { // oxlint-disable-next-line typescript/no-deprecated setupKoaErrorHandler, genericPoolIntegration, + nodeCronIntegration, graphqlIntegration, knexIntegration, kafkaIntegration, diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index d26507f6988a..52f2a040cd49 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -195,6 +195,7 @@ export { postgresIntegration, postgresJsIntegration, neonIntegration, + nodeCronIntegration, tediousIntegration, eveConversationHook, eveIntegration, diff --git a/packages/elysia/src/index.ts b/packages/elysia/src/index.ts index abe6212f2728..8a3da3bf62b5 100644 --- a/packages/elysia/src/index.ts +++ b/packages/elysia/src/index.ts @@ -117,6 +117,7 @@ export { // oxlint-disable-next-line typescript/no-deprecated setupKoaErrorHandler, genericPoolIntegration, + nodeCronIntegration, graphqlIntegration, knexIntegration, kafkaIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index f6abbb875256..35eddeb055a7 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -118,6 +118,7 @@ export { fastifyIntegration, firebaseIntegration, genericPoolIntegration, + nodeCronIntegration, graphqlIntegration, knexIntegration, kafkaIntegration, diff --git a/packages/node/src/cron/node-cron.ts b/packages/node/src/cron/node-cron.ts index db3445fe8471..ab3d935c1a89 100644 --- a/packages/node/src/cron/node-cron.ts +++ b/packages/node/src/cron/node-cron.ts @@ -1,4 +1,4 @@ -import { captureException, type MonitorConfig, withMonitor } from '@sentry/core'; +import { addNonEnumerableProperty, captureException, type MonitorConfig, withMonitor } from '@sentry/core'; import { replaceCronNames } from './common'; export interface NodeCronOptions { @@ -78,6 +78,9 @@ export function instrumentNodeCron( ); }; + // Tells `nodeCronIntegration` that this task is already monitored. + addNonEnumerableProperty(monitoredCallback, Symbol.for('sentry.cron.monitored'), true); + return target.apply(thisArg, [expression, monitoredCallback, options]); }, }); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 183069ccbadf..5ddd71b4b9f4 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -12,6 +12,8 @@ export { type ExpressIntegrationOptions, firebaseIntegration, genericPoolIntegration, + nodeCronIntegration, + type NodeCronIntegrationOptions, googleGenAIIntegration, graphqlIntegration, groqIntegration, diff --git a/packages/remix/src/server/index.ts b/packages/remix/src/server/index.ts index a1aaa76b57aa..e7d3142e0be2 100644 --- a/packages/remix/src/server/index.ts +++ b/packages/remix/src/server/index.ts @@ -37,6 +37,7 @@ export { flush, functionToStringIntegration, genericPoolIntegration, + nodeCronIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/server-utils/src/index.ts b/packages/server-utils/src/index.ts index dacf7ff16b7f..acb0ffb47c75 100644 --- a/packages/server-utils/src/index.ts +++ b/packages/server-utils/src/index.ts @@ -38,6 +38,8 @@ export { anthropicAIIntegration } from './integrations/anthropic'; export { awsIntegration } from './integrations/aws-sdk'; export { dataloaderIntegration } from './integrations/dataloader'; export { genericPoolIntegration } from './integrations/generic-pool'; +export { nodeCronIntegration } from './integrations/node-cron'; +export type { NodeCronIntegrationOptions } from './integrations/node-cron'; export { googleGenAIIntegration } from './integrations/google-genai'; export { graphqlIntegration } from './integrations/graphql'; export { hapiIntegration } from './integrations/hapi'; diff --git a/packages/server-utils/src/integrations/node-cron.ts b/packages/server-utils/src/integrations/node-cron.ts new file mode 100644 index 000000000000..330870117aca --- /dev/null +++ b/packages/server-utils/src/integrations/node-cron.ts @@ -0,0 +1,104 @@ +import type { IntegrationFn, MonitorConfig } from '@sentry/core'; +import { defineIntegration } from '@sentry/core'; +import { CHANNELS } from '../orchestrion/channels'; +import { nodeCronModuleNames } from '../orchestrion/config/node-cron'; +import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation'; +import type { AnyFn } from '../utils/cronMonitoring'; +import { + cronExpressionToMonitorConfig, + shouldMonitorJob, + subscribeToJobCreation, + wrapScheduledJob, +} from '../utils/cronMonitoring'; + +const INTEGRATION_NAME = 'NodeCron' as const; + +export interface NodeCronIntegrationOptions { + /** Task names not to monitor. Strings must match the name exactly. */ + excludeJobs?: Array; +} + +const _nodeCronIntegration = ((options: NodeCronIntegrationOptions = {}) => { + return { + name: INTEGRATION_NAME, + setup(client) { + invokeOrchestrionInstrumentation(client, nodeCronModuleNames, subscribe, [options], { + // The binding only parents spans, and this integration creates none. + requiresTracingChannelBinding: false, + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Sends Crons check-ins for every named `node-cron` task, with its schedule as the monitor config. + * The monitor slug is the task's `name` option. Tasks without a name, or with a schedule Sentry can't express, are not + * monitored. + */ +export const nodeCronIntegration = defineIntegration(_nodeCronIntegration); + +function subscribe(options: NodeCronIntegrationOptions): void { + subscribeToJobCreation(CHANNELS.NODE_CRON_CREATE_TASK, INTEGRATION_NAME, args => wrapTask(args, options)); + subscribeToJobCreation(CHANNELS.NODE_CRON_V4_CREATE_TASK, INTEGRATION_NAME, args => + wrapTask(args, options, isReadAlikeByNodeCron4), + ); + subscribeToJobCreation(CHANNELS.NODE_CRON_V3_CREATE_TASK, INTEGRATION_NAME, args => + wrapTask(args, options, isReadAlikeByNodeCron3), + ); +} + +// `createTask(expression, func, options)` +function wrapTask( + args: unknown[], + options: NodeCronIntegrationOptions, + isReadAlike?: (expression: string) => boolean, +): void { + const [expression, func, taskOptions] = args; + const { name, timezone } = (taskOptions || {}) as { name?: unknown; timezone?: unknown }; + if (!shouldMonitorJob(name, func, options.excludeJobs)) { + return; + } + const monitorConfig = getMonitorConfig(String(expression), timezone, isReadAlike); + args[1] = wrapScheduledJob(func as AnyFn, name, monitorConfig, 'auto.function.node-cron'); +} + +/** The monitor config for a task, or `undefined` if `isReadAlike` says this node-cron version reads it differently. */ +export function getMonitorConfig( + expression: string, + timezone: unknown, + isReadAlike?: (expression: string) => boolean, +): MonitorConfig | undefined { + return isReadAlike && !isReadAlike(expression) + ? undefined + : cronExpressionToMonitorConfig(expression, timezone, 'and'); +} + +const NODE_CRON_3_FIELD_MINIMUMS = [0, 0, 1, 1, 0]; + +/** + * node-cron before 4.6 reads `7` as `0` and swaps reversed ranges, so it runs `sat-sun`, `6-0` and `6-7` every day. + * Returns `false` for weekday ranges that could hit that. + */ +export function isReadAlikeByNodeCron4(expression: string): boolean { + const weekday = expression.trim().split(/\s+/).pop() || ''; + return !(weekday.includes('-') && (weekday.includes('7') || /-(?:0|sun)/i.test(weekday))); +} + +/** + * node-cron 3 keeps the values of a stepped field that are multiples of the step (`*\/2` days are 2, 4, ...) + * and reads weekday ranges like node-cron 4 before 4.6. Returns `false` where that differs from standard cron. + */ +export function isReadAlikeByNodeCron3(expression: string): boolean { + const fields = expression.trim().split(/\s+/); + const minimums = fields.length === 6 ? [0, ...NODE_CRON_3_FIELD_MINIMUMS] : NODE_CRON_3_FIELD_MINIMUMS; + return ( + isReadAlikeByNodeCron4(expression) && + fields.every((field, index) => { + if (!field.includes('/')) { + return true; + } + const match = /^(?:\*|(\d+)-\d+)\/(\d+)$/.exec(field); + return !!match && Number(match[1] ?? minimums[index]) % Number(match[2]) === 0; + }) + ); +} diff --git a/packages/server-utils/src/orchestrion/channels.ts b/packages/server-utils/src/orchestrion/channels.ts index b5919b9eb071..81f9b34d2e27 100644 --- a/packages/server-utils/src/orchestrion/channels.ts +++ b/packages/server-utils/src/orchestrion/channels.ts @@ -26,6 +26,7 @@ import { mysql2Channels } from './config/mysql2'; import { mysqlChannels } from './config/mysql'; import { neonChannels } from './config/neon'; import { nestjsChannels } from './config/nestjs'; +import { nodeCronChannels } from './config/node-cron'; import { openaiChannels } from './config/openai'; import { pgChannels } from './config/pg'; import { piDurableChannels } from './config/pi-durable'; @@ -83,6 +84,7 @@ export const CHANNELS = { ...mysqlChannels, ...neonChannels, ...nestjsChannels, + ...nodeCronChannels, ...openaiChannels, ...pgChannels, ...piDurableChannels, diff --git a/packages/server-utils/src/orchestrion/config/index.ts b/packages/server-utils/src/orchestrion/config/index.ts index e60810315420..56b7bf008c56 100644 --- a/packages/server-utils/src/orchestrion/config/index.ts +++ b/packages/server-utils/src/orchestrion/config/index.ts @@ -33,6 +33,7 @@ import { mysqlConfig } from './mysql'; import { neonConfig } from './neon'; import { nestjsConfig } from './nestjs'; import { nitroConfig } from './nitro'; +import { nodeCronConfig } from './node-cron'; import { openaiConfig } from './openai'; import { pgConfig } from './pg'; import { postgresJsConfig } from './postgres'; @@ -91,6 +92,7 @@ export const SENTRY_INSTRUMENTATIONS: InstrumentationConfig[] = [ ...neonConfig, ...nestjsConfig, ...nitroConfig, + ...nodeCronConfig, ...openaiConfig, ...pgConfig, ...piDurableConfig, diff --git a/packages/server-utils/src/orchestrion/config/node-cron.ts b/packages/server-utils/src/orchestrion/config/node-cron.ts new file mode 100644 index 000000000000..f0688dcc483d --- /dev/null +++ b/packages/server-utils/src/orchestrion/config/node-cron.ts @@ -0,0 +1,52 @@ +import type { InstrumentationConfig } from '../apmTypes'; +import { getModuleNames } from './module-names'; + +const createTask = { functionName: 'createTask', kind: 'Sync' } as const; + +// `schedule()` calls `createTask(expression, func, options)`, so wrapping `createTask` covers both. +// Before 4.6, node-cron reads weekday ranges like `sat-sun` differently, so those versions get their own channel. +export const nodeCronConfig = [ + { + channelName: 'createTask', + module: { name: 'node-cron', versionRange: '>=4.6.0 <5', filePath: 'dist/node-cron.cjs' }, + functionQuery: createTask, + }, + { + channelName: 'createTask', + module: { name: 'node-cron', versionRange: '>=4.6.0 <5', filePath: 'dist/node-cron.js' }, + functionQuery: createTask, + }, + { + channelName: 'v4_createTask', + module: { name: 'node-cron', versionRange: '>=4.3.0 <4.6.0', filePath: 'dist/node-cron.cjs' }, + functionQuery: createTask, + }, + { + channelName: 'v4_createTask', + module: { name: 'node-cron', versionRange: '>=4.0.0 <4.6.0', filePath: 'dist/node-cron.js' }, + functionQuery: createTask, + }, + { + channelName: 'v4_createTask', + module: { name: 'node-cron', versionRange: '>=4.0.0 <4.3.0', filePath: 'dist/cjs/node-cron.js' }, + functionQuery: createTask, + }, + { + channelName: 'v4_createTask', + module: { name: 'node-cron', versionRange: '>=4.0.0 <4.3.0', filePath: 'dist/esm/node-cron.js' }, + functionQuery: createTask, + }, + { + channelName: 'v3_createTask', + module: { name: 'node-cron', versionRange: '>=3.0.0 <4', filePath: 'src/node-cron.js' }, + functionQuery: createTask, + }, +] satisfies InstrumentationConfig[]; + +export const nodeCronModuleNames = getModuleNames(nodeCronConfig); + +export const nodeCronChannels = { + NODE_CRON_CREATE_TASK: 'orchestrion:node-cron:createTask', + NODE_CRON_V4_CREATE_TASK: 'orchestrion:node-cron:v4_createTask', + NODE_CRON_V3_CREATE_TASK: 'orchestrion:node-cron:v3_createTask', +} as const; diff --git a/packages/server-utils/src/utils/cronMonitoring.ts b/packages/server-utils/src/utils/cronMonitoring.ts new file mode 100644 index 000000000000..975763d3c0a7 --- /dev/null +++ b/packages/server-utils/src/utils/cronMonitoring.ts @@ -0,0 +1,190 @@ +import type { MonitorConfig } from '@sentry/core'; +import { + captureException, + debug, + isThenable, + startNewTrace, + stringMatchesSomePattern, + withMonitor, +} from '@sentry/core'; +import { DEBUG_BUILD } from '../debug-build'; +import * as diagnosticsChannel from './diagnosticsChannel'; + +export type AnyFn = (this: unknown, ...args: unknown[]) => unknown; + +// Set by `Sentry.cron.instrumentNodeCron`, which already sends check-ins. +const MONITORED = Symbol.for('sentry.cron.monitored'); + +/** + * Calls `onJob` with the arguments of each call on a scheduler's job-creation channel, so it can wrap the job. + * Errors are logged and the job is created unmonitored. + */ +export function subscribeToJobCreation( + channelName: string, + integrationName: string, + onJob: (args: unknown[]) => void, +): void { + diagnosticsChannel.tracingChannel<{ arguments: unknown[] }>(channelName).start.subscribe(message => { + try { + onJob((message as { arguments: unknown[] }).arguments); + } catch (e) { + DEBUG_BUILD && debug.error(`[${integrationName}] Failed to set up the cron monitor:`, e); + } + }); +} + +/** Whether a job is named, not excluded, and not already monitored by `Sentry.cron.instrumentNodeCron`. */ +export function shouldMonitorJob( + name: unknown, + job: unknown, + excludeJobs: Array | undefined, +): name is string { + return ( + typeof name === 'string' && + !!name && + typeof job === 'function' && + !(job as { [MONITORED]?: boolean })[MONITORED] && + !stringMatchesSomePattern(name, excludeJobs, true) + ); +} + +const NICKNAMES: Record = { + '@yearly': '0 0 1 1 *', + '@annually': '0 0 1 1 *', + '@monthly': '0 0 1 * *', + '@weekly': '0 0 * * 0', + '@daily': '0 0 * * *', + '@midnight': '0 0 * * *', + '@hourly': '0 * * * *', +}; + +const MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']; +const WEEKDAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; + +/** + * How a scheduler combines a restricted day-of-month and day-of-week: + * - `and`: both must match (`node-cron`). + * - `or`: either matches, unless one is `*` or a full range (`cron-parser`, `cron`). + */ +export type DayFieldSemantics = 'and' | 'or'; + +/** + * The Sentry monitor config for a cron expression, or `undefined` if Sentry would read it differently. + * Without a `timezone`, the server's time zone is used, like the schedulers do. + */ +export function cronExpressionToMonitorConfig( + expression: string, + timezone: unknown, + daySemantics: DayFieldSemantics, +): MonitorConfig | undefined { + const trimmed = expression.trim(); + const fields = (NICKNAMES[trimmed.toLowerCase()] || trimmed).split(/\s+/); + + // Sentry schedules have minute granularity, so only a fixed second can be dropped. + if (fields.length === 6 && /^\d+$/.test(fields[0] as string)) { + fields.shift(); + } + if (fields.length !== 5 || !fields.every(field => /^[\d*,/a-z-]+$/i.test(field))) { + return undefined; + } + + const [minute, hour, dayOfMonth, month, dayOfWeek] = fields as [string, string, string, string, string]; + if (!isSameDayMatching(dayOfMonth, dayOfWeek, daySemantics)) { + return undefined; + } + + const crontab = [ + minute, + hour, + dayOfMonth, + replaceNames(month, MONTHS, 1), + endRangesOnSundayAsSeven(replaceNames(dayOfWeek, WEEKDAYS, 0)), + ]; + if (crontab.some(field => /[a-z]/i.test(field) || hasReversedRange(field))) { + return undefined; + } + + const sentryTimezone = toSentryTimezone(typeof timezone === 'string' && timezone ? timezone : undefined); + if (!sentryTimezone) { + return undefined; + } + + return { schedule: { type: 'crontab', value: crontab.join(' ') }, timezone: sentryTimezone }; +} + +// Sentry requires both day fields to match when one starts with `*`, and either otherwise. +function isSameDayMatching(dayOfMonth: string, dayOfWeek: string, daySemantics: DayFieldSemantics): boolean { + if (dayOfMonth === '*' || dayOfWeek === '*') { + return true; + } + if (daySemantics === 'and') { + return dayOfMonth.startsWith('*') || dayOfWeek.startsWith('*'); + } + return !( + dayOfMonth.startsWith('*') || + dayOfWeek.startsWith('*') || + dayOfMonth === '1-31' || + ['0-6', '0-7', '1-7'].includes(dayOfWeek) + ); +} + +function replaceNames(field: string, names: string[], offset: number): string { + return field.replace(/[a-z]+/gi, word => { + const index = names.indexOf(word.slice(0, 3).toLowerCase()); + return index === -1 ? word : String(index + offset); + }); +} + +// `sat-sun` is `6-0`, which node-cron 4.6+ runs on Saturday and Sunday. Sentry only accepts it as `6-7`. +function endRangesOnSundayAsSeven(field: string): string { + return field.replace(/(\d+)-0(?!\d)/g, (range, start: string) => (Number(start) ? `${start}-7` : range)); +} + +// node-cron 4.6+ wraps these around (`22-2` hours), earlier versions swap them, the other schedulers reject them, +// and so does Sentry. +function hasReversedRange(field: string): boolean { + return field.split(',').some(part => { + const range = /^(\d+)-(\d+)/.exec(part); + return !!range && Number(range[1]) > Number(range[2]); + }); +} + +function toSentryTimezone(timezone: string | undefined): string | undefined { + if (timezone && /^(?:utc|gmt)?[+-]/i.test(timezone)) { + return undefined; + } + try { + const resolved = new Intl.DateTimeFormat('en-US', { timeZone: timezone }).resolvedOptions().timeZone; + return resolved && resolved !== 'Etc/Unknown' ? resolved : undefined; + } catch { + return undefined; + } +} + +/** + * Wraps a scheduled job with check-ins. Errors are captured, because schedulers catch them, and rethrown. + * Jobs without a monitor config send no check-ins. + */ +export function wrapScheduledJob( + func: AnyFn, + monitorSlug: string, + monitorConfig: MonitorConfig | undefined, + mechanismType: string, +): AnyFn { + const captureAndRethrow = (error: unknown): never => { + captureException(error, { mechanism: { handled: false, type: mechanismType } }); + throw error; + }; + + return function (this: unknown, ...args: unknown[]): unknown { + const run = (): unknown => { + try { + const result: unknown = func.apply(this, args); + return isThenable(result) ? result.then(undefined, captureAndRethrow) : result; + } catch (e) { + return captureAndRethrow(e); + } + }; + return startNewTrace(() => (monitorConfig ? withMonitor(monitorSlug, run, monitorConfig) : run())); + }; +} diff --git a/packages/server-utils/test/integrations/node-cron.test.ts b/packages/server-utils/test/integrations/node-cron.test.ts new file mode 100644 index 000000000000..c3c78dc701f9 --- /dev/null +++ b/packages/server-utils/test/integrations/node-cron.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from 'vitest'; +import { getMonitorConfig, isReadAlikeByNodeCron3, isReadAlikeByNodeCron4 } from '../../src/integrations/node-cron'; + +describe('isReadAlikeByNodeCron3', () => { + it.each(['* * * * *', '*/15 * * * *', '0 */2 * * *', '0 8-18/2 * * *', '0 0 9 * * 1-5', '0 9 * * 7', '0 9 * * 0,7'])( + 'is true for %s', + expression => { + expect(isReadAlikeByNodeCron3(expression)).toBe(true); + }, + ); + + // node-cron 3 runs these on 2,4,...; 10,12,...; 3,6,...; Sunday-Monday; and every day but Saturday. + it.each([ + '0 9 */2 * *', + '0 9-17/2 * * *', + '0 9 * */3 *', + '0 9 * * 1-7', + '0 9 * * 5-7', + '0 9 * * sat-sun', + '0 9 * * 6-0', + '0 9 * * fri-Sun', + '5/15 * * * *', + '0,30/15 * * * *', + ])('is false for %s', expression => { + expect(isReadAlikeByNodeCron3(expression)).toBe(false); + }); +}); + +describe('isReadAlikeByNodeCron4', () => { + it.each(['* * * * *', '0 9 * * 1-5', '0 9 * * sun-tue', '0 9 * * 0,7', '0 9 */2 * *', '0 22-2 * * *'])( + 'is true for %s', + expression => { + expect(isReadAlikeByNodeCron4(expression)).toBe(true); + }, + ); + + it.each(['0 9 * * sat-sun', '0 9 * * 6-0', '0 9 * * 6-7', '0 9 * * 5-0/2', '0 9 * * 1-3,sat-sun'])( + 'is false for %s', + expression => { + expect(isReadAlikeByNodeCron4(expression)).toBe(false); + }, + ); +}); + +describe('getMonitorConfig', () => { + // node-cron 4.6 runs these on the weekend; earlier versions run them every day. + it.each(['0 9 * * sat-sun', '0 9 * * 6-0'])('sends %s as 6-7 for node-cron 4.6', expression => { + expect(getMonitorConfig(expression, 'UTC')?.schedule).toEqual({ type: 'crontab', value: '0 9 * * 6-7' }); + }); + + it.each(['0 9 * * sat-sun', '0 9 * * 6-0'])('sends no config for %s before node-cron 4.6', expression => { + expect(getMonitorConfig(expression, 'UTC', isReadAlikeByNodeCron4)).toBeUndefined(); + }); + + it('sends no config for a reversed hour range', () => { + expect(getMonitorConfig('0 22-2 * * *', 'UTC')).toBeUndefined(); + expect(getMonitorConfig('0 22-2 * * *', 'UTC', isReadAlikeByNodeCron4)).toBeUndefined(); + }); + + it('sends the config for other ranges before node-cron 4.6', () => { + expect(getMonitorConfig('0 9 * * mon-fri', 'UTC', isReadAlikeByNodeCron4)?.schedule).toEqual({ + type: 'crontab', + value: '0 9 * * 1-5', + }); + }); +}); diff --git a/packages/server-utils/test/orchestrion/moduleInjectedTransform.test.ts b/packages/server-utils/test/orchestrion/moduleInjectedTransform.test.ts index 750ffeaa8d19..5dac489035d1 100644 --- a/packages/server-utils/test/orchestrion/moduleInjectedTransform.test.ts +++ b/packages/server-utils/test/orchestrion/moduleInjectedTransform.test.ts @@ -43,8 +43,10 @@ describe('channel integration definitions', () => { const defined = new Set(CHANNEL_INTEGRATION_DEFINITIONS.flatMap(d => d.modules as readonly string[])); // `@nestjs/*` and `@remix-run/*` are covered by listeners in their own SDK packages. + // Each cron monitor is billed, so bundler-only SDKs must not install crons integrations for every app. + const optIn = new Set(['node-cron']); const uncovered = [...configured].filter( - name => !defined.has(name) && !name.startsWith('@nestjs/') && !name.startsWith('@remix-run/'), + name => !defined.has(name) && !optIn.has(name) && !name.startsWith('@nestjs/') && !name.startsWith('@remix-run/'), ); expect(uncovered).toEqual([]); }); diff --git a/packages/server-utils/test/utils/cronMonitoring.test.ts b/packages/server-utils/test/utils/cronMonitoring.test.ts new file mode 100644 index 000000000000..0950a592cc87 --- /dev/null +++ b/packages/server-utils/test/utils/cronMonitoring.test.ts @@ -0,0 +1,174 @@ +import { getCurrentScope, setCurrentClient } from '@sentry/core'; +import { describe, expect, it, vi } from 'vitest'; +import { cronExpressionToMonitorConfig, shouldMonitorJob, wrapScheduledJob } from '../../src/utils/cronMonitoring'; +import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; + +const toMonitorConfig = (expression: string, timezone: unknown) => + cronExpressionToMonitorConfig(expression, timezone, 'and'); + +describe('cronExpressionToMonitorConfig with node-cron day matching', () => { + it.each([ + ['* * * * *', '* * * * *'], + ['0 30 9 * * 1-5', '30 9 * * 1-5'], + ['0 9 * jan,Jul mon-fri', '0 9 * 1,7 1-5'], + ['0 9 * * sunday', '0 9 * * 0'], + ['0 9 */2 * 1', '0 9 */2 * 1'], + ['@daily', '0 0 * * *'], + ['@Weekly', '0 0 * * 0'], + ['*/15 * * * *', '*/15 * * * *'], + ['0 9 * * sun', '0 9 * * 0'], + ['0 9 * * sat-sun', '0 9 * * 6-7'], + ['0 9 * * fri-sun', '0 9 * * 5-7'], + ['0 9 * * sun-tue', '0 9 * * 0-2'], + ['0 9 * * 6-7', '0 9 * * 6-7'], + ['0 9 * * 5-0', '0 9 * * 5-7'], + ['0 9 * * 5-0/2', '0 9 * * 5-7/2'], + ['0 9 * * 1-3,sat-sun', '0 9 * * 1-3,6-7'], + ])('sends %s as %s', (expression, crontab) => { + expect(toMonitorConfig(expression, 'Europe/Vienna')).toEqual({ + schedule: { type: 'crontab', value: crontab }, + timezone: 'Europe/Vienna', + }); + }); + + it.each([ + '* * * * * *', + '*/10 * * * * *', + '0 9 1 * 1', + '0 9 1-31 * 1', + '0 9 L * *', + '0 9 ? * 1', + '0 9 * * 1#2', + '* * * *', + '0 9 * foo *', + '0 9 * * tue-mon', + '0 9 * * 7-2', + '0 22-2 * * *', + '0 9 * nov-feb *', + ])('sends no config for %s', expression => { + expect(toMonitorConfig(expression, 'UTC')).toBeUndefined(); + }); + + it('sends no config for fixed UTC offsets', () => { + expect(toMonitorConfig('* * * * *', 'GMT+5')).toBeUndefined(); + expect(toMonitorConfig('* * * * *', 'Not/AZone')).toBeUndefined(); + }); + + it('uses the canonical time zone name', () => { + expect(toMonitorConfig('* * * * *', 'europe/vienna')?.timezone).toBe('Europe/Vienna'); + }); + + it('falls back to the local time zone', () => { + expect(toMonitorConfig('* * * * *', undefined)?.timezone).toBe(Intl.DateTimeFormat().resolvedOptions().timeZone); + }); +}); + +describe('cronExpressionToMonitorConfig with cron-parser day matching', () => { + it.each([ + ['0 9 1,15 * mon', '0 9 1,15 * 1'], + ['0 9 */2 * *', '0 9 */2 * *'], + ['0 9 * * 1-5', '0 9 * * 1-5'], + ])('sends %s as %s', (expression, crontab) => { + expect(cronExpressionToMonitorConfig(expression, 'UTC', 'or')?.schedule).toEqual({ + type: 'crontab', + value: crontab, + }); + }); + + it.each(['0 9 */2 * 1', '0 9 1 * */2', '0 9 1-31 * 1', '0 9 15 * 0-6', '0 9 15 * 1-7'])( + 'sends no config for %s', + expression => { + expect(cronExpressionToMonitorConfig(expression, 'UTC', 'or')).toBeUndefined(); + }, + ); +}); + +describe('shouldMonitorJob', () => { + const job = (): void => {}; + + it('monitors named jobs', () => { + expect(shouldMonitorJob('job', job, undefined)).toBe(true); + }); + + it('skips unnamed jobs and jobs that are not functions', () => { + expect(shouldMonitorJob(undefined, job, undefined)).toBe(false); + expect(shouldMonitorJob('', job, undefined)).toBe(false); + expect(shouldMonitorJob('job', './job.js', undefined)).toBe(false); + }); + + it('skips excluded jobs', () => { + expect(shouldMonitorJob('job', job, ['job'])).toBe(false); + expect(shouldMonitorJob('job-2', job, ['job'])).toBe(true); + expect(shouldMonitorJob('debug-job', job, [/^debug-/])).toBe(false); + }); + + it('skips jobs that `Sentry.cron.instrument*` already monitors', () => { + const monitored = Object.assign((): void => {}, { [Symbol.for('sentry.cron.monitored')]: true }); + expect(shouldMonitorJob('job', monitored, undefined)).toBe(false); + }); +}); + +describe('wrapScheduledJob', () => { + it('runs each job in its own trace', () => { + const job = wrapScheduledJob( + () => getCurrentScope().getPropagationContext().traceId, + 'job', + toMonitorConfig('* * * * *', 'UTC'), + 'auto.function.test', + ); + const outerTraceId = getCurrentScope().getPropagationContext().traceId; + const firstTraceId = job(); + const secondTraceId = job(); + + expect(firstTraceId).not.toBe(secondTraceId); + expect(firstTraceId).not.toBe(outerTraceId); + }); + + it('sends no check-ins for jobs without a monitor config', () => { + const client = new TestClient(getDefaultTestClientOptions()); + setCurrentClient(client); + client.init(); + const captureCheckIn = vi.fn(() => 'check-in-id'); + Object.assign(client, { captureCheckIn }); + + const job = wrapScheduledJob(() => 'done', 'job', undefined, 'auto.function.test'); + + expect(job()).toBe('done'); + expect(captureCheckIn).not.toHaveBeenCalled(); + + wrapScheduledJob(() => {}, 'job', toMonitorConfig('* * * * *', 'UTC'), 'auto.function.test')(); + expect(captureCheckIn).toHaveBeenCalled(); + }); + + it('captures errors from jobs without a monitor config', async () => { + const client = new TestClient(getDefaultTestClientOptions()); + setCurrentClient(client); + client.init(); + const captureCheckIn = vi.fn(() => 'check-in-id'); + Object.assign(client, { captureCheckIn }); + const captureException = vi.spyOn(client, 'captureException'); + + const syncError = new Error('sync'); + const job = wrapScheduledJob( + () => { + throw syncError; + }, + 'job', + undefined, + 'auto.function.test', + ); + expect(() => job()).toThrow(syncError); + + const asyncError = new Error('async'); + const asyncJob = wrapScheduledJob(() => Promise.reject(asyncError), 'job', undefined, 'auto.function.test'); + await expect(asyncJob()).rejects.toBe(asyncError); + + expect(captureException).toHaveBeenCalledWith( + syncError, + expect.objectContaining({ mechanism: { handled: false, type: 'auto.function.test' } }), + expect.anything(), + ); + expect(captureException).toHaveBeenCalledWith(asyncError, expect.anything(), expect.anything()); + expect(captureCheckIn).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index b2d128847df8..686ce210b2d7 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -40,6 +40,7 @@ export { flush, functionToStringIntegration, genericPoolIntegration, + nodeCronIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 270abc2ae43c..7d55e5f41ec9 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -38,6 +38,7 @@ export { flush, functionToStringIntegration, genericPoolIntegration, + nodeCronIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, From 6d48bfa9491b97f0a1fae57f1c1c45b02e3956e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 08:28:24 +0100 Subject: [PATCH 048/169] feat(deps): Bump dataloader from 2.2.2 to 2.2.3 (#25891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [dataloader](https://github.com/graphql/dataloader) from 2.2.2 to 2.2.3.
Release notes

Sourced from dataloader's releases.

v2.2.3

What's Changed

New Contributors

Full Changelog: https://github.com/graphql/dataloader/compare/v2.2.2...v2.2.3

Changelog

Sourced from dataloader's changelog.

2.2.3

Patch Changes

  • #342 38fedd4 Thanks @​abendi! - Ensure cacheKeyFn is not called when caching is disabled, since the key is not utilized in that case.
Commits
  • 5e851ce chore: update release instructions
  • 35729a6 ci: update codecov to use token (#370)
  • f65532e ci: update node versions to run test on
  • f8cfc4d Bump word-wrap from 1.2.3 to 1.2.4 (#345)
  • 77c2cd7 Update Green Donut link (#361)
  • 38fedd4 perf: call cacheKeyFn only when it is needed (if caching) (#342)
  • 18da9f3 Synchronized linting in README.md (#358)
  • 003c045 Update readme badge to use GitHub CI instead of unused Travis (#352)
  • 55c33d4 Fix correct path of function when click on mobile links (#351)
  • d336bd1 Bump minimatch from 3.0.4 to 3.1.2 (#324)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-packages/node-integration-tests/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 2ea842433e12..691f60732c18 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -69,7 +69,7 @@ "consola": "^3.2.3", "cors": "^2.8.5", "cron": "^3.1.6", - "dataloader": "2.2.2", + "dataloader": "2.2.3", "express": "^4.21.2", "fastify": "^5.12.7", "generic-pool": "^3.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e197899faf36..ca7ef98c28e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -707,8 +707,8 @@ importers: specifier: ^3.1.6 version: 3.1.6 dataloader: - specifier: 2.2.2 - version: 2.2.2 + specifier: 2.2.3 + version: 2.2.3 express: specifier: ^4.21.2 version: 4.22.1(supports-color@10.0.0) @@ -11107,8 +11107,8 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - dataloader@2.2.2: - resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==} + dataloader@2.2.3: + resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} dax-sh@0.43.2: resolution: {integrity: sha512-uULa1sSIHgXKGCqJ/pA0zsnzbHlVnuq7g8O2fkHokWFNwEGIhh5lAJlxZa1POG5En5ba7AU4KcBAvGQWMMf8rg==} @@ -30040,7 +30040,7 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - dataloader@2.2.2: {} + dataloader@2.2.3: {} dax-sh@0.43.2: dependencies: From 98965752b51ec08db3c340c3503c433048dd81f0 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 30 Oct 2026 04:10:39 -0400 Subject: [PATCH 049/169] test(e2e): Rename nuxt-5 middleware files to avoid Nitro's dotted suffixes (#25923) Nitro `3.0.261029-rc` treats the second dot in `01.first.ts` as a reserved suffix (nitrojs/nitro#4845), which broke the nuxt-5 build and kept the middleware from loading in dev. I just renamed the middlewares to `01-first.ts` and so on to workaround it. --- .../middleware/{01.first.ts => 01-first.ts} | 0 .../middleware/{02.second.ts => 02-second.ts} | 0 .../middleware/{03.auth.ts => 03-auth.ts} | 0 .../middleware/{04.hooks.ts => 04-hooks.ts} | 0 .../{05.array-hooks.ts => 05-array-hooks.ts} | 0 ...erver-first.ts => 06-nuxt-server-first.ts} | 0 ...-server-auth.ts => 07-nuxt-server-auth.ts} | 0 .../nuxt-5/tests/middleware.test.ts | 42 +++++++++---------- 8 files changed, 21 insertions(+), 21 deletions(-) rename dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/{01.first.ts => 01-first.ts} (100%) rename dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/{02.second.ts => 02-second.ts} (100%) rename dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/{03.auth.ts => 03-auth.ts} (100%) rename dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/{04.hooks.ts => 04-hooks.ts} (100%) rename dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/{05.array-hooks.ts => 05-array-hooks.ts} (100%) rename dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/{06.nuxt-server-first.ts => 06-nuxt-server-first.ts} (100%) rename dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/{07.nuxt-server-auth.ts => 07-nuxt-server-auth.ts} (100%) diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/01.first.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/01-first.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/01.first.ts rename to dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/01-first.ts diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/02.second.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/02-second.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/02.second.ts rename to dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/02-second.ts diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/03.auth.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/03-auth.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/03.auth.ts rename to dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/03-auth.ts diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/04.hooks.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/04-hooks.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/04.hooks.ts rename to dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/04-hooks.ts diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/05.array-hooks.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/05-array-hooks.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/05.array-hooks.ts rename to dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/05-array-hooks.ts diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/06.nuxt-server-first.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/06-nuxt-server-first.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/06.nuxt-server-first.ts rename to dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/06-nuxt-server-first.ts diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/07.nuxt-server-auth.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/07-nuxt-server-auth.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/07.nuxt-server-auth.ts rename to dev-packages/e2e-tests/test-applications/nuxt-5/server/middleware/nuxt-server/07-nuxt-server-auth.ts diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/middleware.test.ts index e2c12def7864..8add88c82f06 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/middleware.test.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/middleware.test.ts @@ -8,19 +8,19 @@ import { IMPORT_SURFACES } from './importSurfaces'; // simple middleware has a copy. const MIDDLEWARE = { nitro: { - names: ['01.first', '02.second', '03.auth', '04.hooks', '05.array-hooks'], - simpleNames: ['01.first', '02.second', '03.auth'], + names: ['01-first', '02-second', '03-auth', '04-hooks', '05-array-hooks'], + simpleNames: ['01-first', '02-second', '03-auth'], // 3 simple + 2 hooks (middleware+handler) + 3 array hooks (2 middleware + 1 handler) spanCount: 8, - authName: '03.auth', + authName: '03-auth', authErrorQuery: 'throwError=true', authErrorMessage: 'Auth middleware error', }, 'nuxt/server': { - names: ['06.nuxt-server-first', '07.nuxt-server-auth'], - simpleNames: ['06.nuxt-server-first', '07.nuxt-server-auth'], + names: ['06-nuxt-server-first', '07-nuxt-server-auth'], + simpleNames: ['06-nuxt-server-first', '07-nuxt-server-auth'], spanCount: 2, - authName: '07.nuxt-server-auth', + authName: '07-nuxt-server-auth', authErrorQuery: 'throwNuxtServerError=true', authErrorMessage: 'nuxt/server auth middleware error', }, @@ -127,17 +127,17 @@ test.describe('Server Middleware Instrumentation (nitro hooks)', () => { expect(response.status()).toBe(200); const spans = await spansPromise; - const middlewareSpan = findMiddlewareSpan(spans, '04.hooks', 'middleware'); - const handlerSpan = findMiddlewareSpan(spans, '04.hooks', 'handler'); + const middlewareSpan = findMiddlewareSpan(spans, '04-hooks', 'middleware'); + const handlerSpan = findMiddlewareSpan(spans, '04-hooks', 'handler'); // h3 v2 has no `onBeforeResponse`, so a hooks object gives only `middleware` and `handler` spans. - expect(spans.filter(span => span.attributes['nuxt.middleware.name']?.value === '04.hooks')).toHaveLength(2); + expect(spans.filter(span => span.attributes['nuxt.middleware.name']?.value === '04-hooks')).toHaveLength(2); - expect(middlewareSpan?.name).toBe('04.hooks.middleware'); + expect(middlewareSpan?.name).toBe('04-hooks.middleware'); // h3 v2 always stores `middleware` as an array, so even a single hook has an index. expect(middlewareSpan?.attributes['nuxt.middleware.hook.index']?.value).toBe(0); - expect(handlerSpan?.name).toBe('04.hooks'); + expect(handlerSpan?.name).toBe('04-hooks'); expect(handlerSpan?.attributes['nuxt.middleware.hook.index']).toBeUndefined(); }); @@ -148,15 +148,15 @@ test.describe('Server Middleware Instrumentation (nitro hooks)', () => { expect(response.status()).toBe(200); const spans = await spansPromise; - const arrayHooksSpans = spans.filter(span => span.attributes['nuxt.middleware.name']?.value === '05.array-hooks'); - const middleware0Span = findMiddlewareSpan(spans, '05.array-hooks', 'middleware', 0); - const middleware1Span = findMiddlewareSpan(spans, '05.array-hooks', 'middleware', 1); - const handlerSpan = findMiddlewareSpan(spans, '05.array-hooks', 'handler'); + const arrayHooksSpans = spans.filter(span => span.attributes['nuxt.middleware.name']?.value === '05-array-hooks'); + const middleware0Span = findMiddlewareSpan(spans, '05-array-hooks', 'middleware', 0); + const middleware1Span = findMiddlewareSpan(spans, '05-array-hooks', 'middleware', 1); + const handlerSpan = findMiddlewareSpan(spans, '05-array-hooks', 'handler'); expect(arrayHooksSpans).toHaveLength(3); - expect(middleware0Span?.name).toBe('05.array-hooks.middleware'); - expect(middleware1Span?.name).toBe('05.array-hooks.middleware'); - expect(handlerSpan?.name).toBe('05.array-hooks'); + expect(middleware0Span?.name).toBe('05-array-hooks.middleware'); + expect(middleware1Span?.name).toBe('05-array-hooks.middleware'); + expect(handlerSpan?.name).toBe('05-array-hooks'); expect(handlerSpan?.attributes['nuxt.middleware.hook.index']).toBeUndefined(); }); @@ -171,7 +171,7 @@ test.describe('Server Middleware Instrumentation (nitro hooks)', () => { const [spans, errorEvent] = await Promise.all([spansPromise, errorEventPromise]); - expect(findMiddlewareSpan(spans, '04.hooks', 'middleware')?.status).toBe('error'); + expect(findMiddlewareSpan(spans, '04-hooks', 'middleware')?.status).toBe('error'); expect(errorEvent.exception?.values?.[0]?.value).toBe('OnRequest hook error'); }); @@ -186,8 +186,8 @@ test.describe('Server Middleware Instrumentation (nitro hooks)', () => { const [spans, errorEvent] = await Promise.all([spansPromise, errorEventPromise]); - expect(findMiddlewareSpan(spans, '05.array-hooks', 'middleware', 0)?.status).toBe('ok'); - expect(findMiddlewareSpan(spans, '05.array-hooks', 'middleware', 1)?.status).toBe('error'); + expect(findMiddlewareSpan(spans, '05-array-hooks', 'middleware', 0)?.status).toBe('ok'); + expect(findMiddlewareSpan(spans, '05-array-hooks', 'middleware', 1)?.status).toBe('error'); expect(errorEvent.exception?.values?.[0]?.value).toBe('OnRequest[1] hook error'); }); }); From fb53f31370d1907141cf300c45cc0fb1a90aa230 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:40:11 +0100 Subject: [PATCH 050/169] chore(deps-dev): Bump @rollup/plugin-terser from 0.4.4 to 1.0.2 (#25890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@rollup/plugin-terser](https://github.com/rollup/plugins/tree/HEAD/packages/terser) from 0.4.4 to 1.0.2.
Changelog

Sourced from @​rollup/plugin-terser's changelog.

v1.0.2

2026-10-21

Updates

  • terser: update terser to ^5.49.2 (#2019)

v1.0.1

2026-10-21

Bugfixes

  • terser: share nameCache between chunks by serializing minify tasks (#2020)

v1.0.0

2026-03-05

Breaking Changes

  • terser!: upgrade serialize-javascript to v7 and node to v20 (#1968)
Commits
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for @​rollup/plugin-terser since your current version.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@rollup/plugin-terser&package-manager=npm_and_yarn&previous-version=0.4.4&new-version=1.0.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Charly Gomez --- package.json | 2 +- pnpm-lock.yaml | 33 ++++++++------------------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 087310513a66..7e027c9b17a1 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-replace": "^5.0.5", - "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-terser": "^1.0.2", "@sentry-internal/node-integration-tests": "workspace:*", "@sentry/eslint-plugin-sdk": "workspace:*", "@sentry/sveltekit": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca7ef98c28e0..4a44921bc39b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -187,8 +187,8 @@ importers: specifier: ^5.0.5 version: 5.0.7(rollup@4.62.3) '@rollup/plugin-terser': - specifier: ^0.4.4 - version: 0.4.4(rollup@4.62.3) + specifier: ^1.0.2 + version: 1.0.2(rollup@4.62.3) '@sentry-internal/node-integration-tests': specifier: workspace:* version: link:dev-packages/node-integration-tests @@ -8070,17 +8070,8 @@ packages: rollup: optional: true - '@rollup/plugin-terser@0.4.4': - resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-terser@1.0.0': - resolution: {integrity: sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==} + '@rollup/plugin-terser@1.0.2': + resolution: {integrity: sha512-+UihHnuLnQDQ/9wog7ym+7oawCXkjTIo0WwkK7s//0mkktTN0VhYk6fGVxH+NtGzW064w5RCGgreMDxRVhHCzw==} engines: {node: '>=20.0.0'} peerDependencies: rollup: ^2.0.0||^3.0.0||^4.0.0 @@ -26215,15 +26206,7 @@ snapshots: optionalDependencies: rollup: 4.62.3 - '@rollup/plugin-terser@0.4.4(rollup@4.62.3)': - dependencies: - serialize-javascript: 6.0.2 - smob: 1.4.1 - terser: 5.51.2 - optionalDependencies: - rollup: 4.62.3 - - '@rollup/plugin-terser@1.0.0(rollup@4.62.3)': + '@rollup/plugin-terser@1.0.2(rollup@4.62.3)': dependencies: serialize-javascript: 7.0.5 smob: 1.4.1 @@ -34902,7 +34885,7 @@ snapshots: '@rollup/plugin-json': 6.1.0(rollup@4.62.3) '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.3) '@rollup/plugin-replace': 6.0.3(rollup@4.62.3) - '@rollup/plugin-terser': 1.0.0(rollup@4.62.3) + '@rollup/plugin-terser': 1.0.2(rollup@4.62.3) '@vercel/nft': 1.11.0(encoding@0.1.13)(rollup@4.62.3)(supports-color@10.0.0) archiver: 7.0.1 c12: 3.3.4(magicast@0.5.4) @@ -35011,7 +34994,7 @@ snapshots: '@rollup/plugin-json': 6.1.0(rollup@4.62.3) '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.3) '@rollup/plugin-replace': 6.0.3(rollup@4.62.3) - '@rollup/plugin-terser': 1.0.0(rollup@4.62.3) + '@rollup/plugin-terser': 1.0.2(rollup@4.62.3) '@vercel/nft': 1.11.0(encoding@0.1.13)(rollup@4.62.3)(supports-color@10.0.0) archiver: 7.0.1 c12: 3.3.4(magicast@0.5.4) @@ -35120,7 +35103,7 @@ snapshots: '@rollup/plugin-json': 6.1.0(rollup@4.62.3) '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.3) '@rollup/plugin-replace': 6.0.3(rollup@4.62.3) - '@rollup/plugin-terser': 1.0.0(rollup@4.62.3) + '@rollup/plugin-terser': 1.0.2(rollup@4.62.3) '@vercel/nft': 1.11.0(encoding@0.1.13)(rollup@4.62.3)(supports-color@10.0.0) archiver: 7.0.1 c12: 3.3.4(magicast@0.5.4) From 365e7611eecfd26828a991732f02d9147bdd3cd0 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 30 Oct 2026 10:22:23 +0100 Subject: [PATCH 051/169] ref(server-utils): Remove `requiresParentSpan` in favor of `onlyIfParent` (#25926) **Why:** Use core's existing `onlyIfParent` option consistently instead of a separate tracing-channel parent guard, so we get client reports for spans dropped because of missing parents. Alternatively, we have considered to simply add client reports to the existing `requiresParentSpan` drop logic. However, this doesn't work because `getSpan` can still decide afterwards to not create spans for certain operations. This would result in dropped spans client reports for ops that we do not even instrument. **How to review:** The `requiresParentSpan` option is removed from the implementation in `packages/server-utils/src/tracing-channel.ts`. The rest of the PR is replacing any call sites with `onlyIfParent` from our core span APIs and minor adjustments to unit tests. There should be no behavioral change. Closes #25875 --------- Co-authored-by: OpenAI Codex --- .../server/integrations/tracing-channel.ts | 4 +- .../remix/test/server/tracing-channel.test.ts | 5 +- .../src/integrations/dataloader.ts | 10 +-- .../src/integrations/hono/honoIntegration.ts | 2 +- .../src/integrations/mongodb/index.ts | 54 +++++------- .../src/integrations/mongodb/mongodb-span.ts | 1 + .../src/integrations/mysql2/index.ts | 49 +++++------ .../mysql2/mysql2-dc-subscriber.ts | 88 +++++++++---------- .../server-utils/src/integrations/neon.ts | 15 ++-- .../src/integrations/postgres-js.ts | 5 +- .../server-utils/src/integrations/postgres.ts | 6 +- .../src/integrations/prisma/orchestrion.ts | 7 +- .../redis/ioredis-channel-subscriber.ts | 30 +++---- packages/server-utils/src/tracing-channel.ts | 13 +-- .../mysql2/mysql2-dc-subscriber.test.ts | 7 +- .../test/integrations/neon.test.ts | 4 +- .../integrations/postgres/postgres.test.ts | 12 +-- .../integrations/prisma-orchestrion.test.ts | 4 +- 18 files changed, 132 insertions(+), 184 deletions(-) diff --git a/packages/remix/src/server/integrations/tracing-channel.ts b/packages/remix/src/server/integrations/tracing-channel.ts index 8e93813a19dd..65e6bda2e463 100644 --- a/packages/remix/src/server/integrations/tracing-channel.ts +++ b/packages/remix/src/server/integrations/tracing-channel.ts @@ -192,6 +192,7 @@ function subscribeCallRouteLoader(): void { // With span streaming, a `function` span is named after the function it wraps. The route id // stays on `match.route.id`. name: client && hasSpanStreamingEnabled(client) ? 'loader' : description, + onlyIfParent: true, attributes: { [SENTRY_ORIGIN]: ORIGIN, [SENTRY_OP]: FUNCTION, @@ -203,7 +204,6 @@ function subscribeCallRouteLoader(): void { }); }, { - requiresParentSpan: true, beforeSpanEnd: (span, data) => setResponseStatus(span, data.result), }, ); @@ -231,6 +231,7 @@ function subscribeCallRouteAction(formDataCapture: FormDataCapture | undefined): // With span streaming, a `function` span is named after the function it wraps. The route id // stays on `match.route.id`. name: client && hasSpanStreamingEnabled(client) ? 'action' : description, + onlyIfParent: true, attributes: { [SENTRY_ORIGIN]: ORIGIN, [SENTRY_OP]: FUNCTION, @@ -242,7 +243,6 @@ function subscribeCallRouteAction(formDataCapture: FormDataCapture | undefined): }); }, { - requiresParentSpan: true, beforeSpanEnd: (span, data) => setResponseStatus(span, data.result), // Hold the span end until the (already in-flight) form-data read resolves, then apply the // attributes and end (which sets the response status via `beforeSpanEnd`). On error, or when diff --git a/packages/remix/test/server/tracing-channel.test.ts b/packages/remix/test/server/tracing-channel.test.ts index c11200f643b1..f31f9c693439 100644 --- a/packages/remix/test/server/tracing-channel.test.ts +++ b/packages/remix/test/server/tracing-channel.test.ts @@ -27,7 +27,6 @@ describe('remixIntegration (Orchestrion-based)', () => { beforeEach(() => { span = makeSpan(); startInactiveSpanSpy = vi.spyOn(SentryCore, 'startInactiveSpan').mockReturnValue(span); - // A truthy active span by default, so the `requiresParentSpan` gate passes. getActiveSpanSpy = vi.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({} as Span); }); @@ -129,13 +128,13 @@ describe('remixIntegration (Orchestrion-based)', () => { expect(span.end).toHaveBeenCalledTimes(1); }); - it('callRouteLoader: does not create a span without an active parent span', async () => { + it('callRouteLoader: does not create a recording span without an active parent span', async () => { getActiveSpanSpy.mockReturnValue(undefined); const ctx = { arguments: [{ routeId: 'x', request: makeRequest(), params: {} }] }; await tracingChannel(remixChannels.REMIX_CALL_ROUTE_LOADER).tracePromise(async () => ({ status: 200 }), ctx); - expect(startInactiveSpanSpy).not.toHaveBeenCalled(); + expect(startInactiveSpanSpy).toHaveBeenCalledWith(expect.objectContaining({ onlyIfParent: true })); }); it('callRouteAction: builds an ACTION span and captures the configured form-data keys', async () => { diff --git a/packages/server-utils/src/integrations/dataloader.ts b/packages/server-utils/src/integrations/dataloader.ts index 5fb50a577325..63983e9b32b3 100644 --- a/packages/server-utils/src/integrations/dataloader.ts +++ b/packages/server-utils/src/integrations/dataloader.ts @@ -179,9 +179,7 @@ function subscribeConstruct(): void { function subscribeLoad(): void { const channel = diagnosticsChannel.tracingChannel(CHANNELS.DATALOADER_LOAD); - bindTracingChannelToSpan(channel, data => startInactiveSpanFor(data.self, 'load', data.arguments[0]), { - requiresParentSpan: true, - }); + bindTracingChannelToSpan(channel, data => startInactiveSpanFor(data.self, 'load', data.arguments[0])); channel.end.subscribe(message => { const data = message as TracingChannelPayloadWithSpan; @@ -194,10 +192,8 @@ function subscribeLoad(): void { } function subscribeSimpleOperation(channelName: ChannelName, operation: Operation): void { - bindTracingChannelToSpan( - diagnosticsChannel.tracingChannel(channelName), - data => startInactiveSpanFor(data.self, operation, data.arguments[0]), - { requiresParentSpan: true }, + bindTracingChannelToSpan(diagnosticsChannel.tracingChannel(channelName), data => + startInactiveSpanFor(data.self, operation, data.arguments[0]), ); } diff --git a/packages/server-utils/src/integrations/hono/honoIntegration.ts b/packages/server-utils/src/integrations/hono/honoIntegration.ts index 575462d6d018..d5ef285cdf62 100644 --- a/packages/server-utils/src/integrations/hono/honoIntegration.ts +++ b/packages/server-utils/src/integrations/hono/honoIntegration.ts @@ -111,13 +111,13 @@ function instrumentInternalRequests(): void { ).toUpperCase(); return startInactiveSpan({ name: `${method} ${extractPathname(input)}`, + onlyIfParent: true, attributes: { [SENTRY_OP]: HTTP_SERVER, [SENTRY_ORIGIN]: INTERNAL_REQUEST_ORIGIN, }, }); }, - { requiresParentSpan: true }, ); } diff --git a/packages/server-utils/src/integrations/mongodb/index.ts b/packages/server-utils/src/integrations/mongodb/index.ts index 593638025c6f..b27a3c46c933 100644 --- a/packages/server-utils/src/integrations/mongodb/index.ts +++ b/packages/server-utils/src/integrations/mongodb/index.ts @@ -64,23 +64,17 @@ function instrumentMongodb(): void { * (both publish to this channel). */ function subscribeV4Command(): void { - bindTracingChannelToSpan( - diagnosticsChannel.tracingChannel(CHANNELS.MONGODB_COMMAND), - data => { - const args = data.arguments ?? []; - const ns = args[0] as MongodbNamespace | undefined; - const cmd = args[1] as Record | undefined; - // Skip handshake/heartbeat commands (matches otel). - if (!ns || !cmd || typeof cmd !== 'object' || cmd.ismaster || cmd.hello) { - return undefined; - } - const operation = Object.keys(cmd)[0]; - return startMongoSpan(getV4SpanAttributes(data.self, ns, cmd, operation, ORIGIN)); - }, - // Matches otel's `shouldSkipInstrumentation`: only trace when there is - // an active parent span, to avoid emitting orphaned mongodb spans. - { requiresParentSpan: true }, - ); + bindTracingChannelToSpan(diagnosticsChannel.tracingChannel(CHANNELS.MONGODB_COMMAND), data => { + const args = data.arguments ?? []; + const ns = args[0] as MongodbNamespace | undefined; + const cmd = args[1] as Record | undefined; + // Skip handshake/heartbeat commands (matches otel). + if (!ns || !cmd || typeof cmd !== 'object' || cmd.ismaster || cmd.hello) { + return undefined; + } + const operation = Object.keys(cmd)[0]; + return startMongoSpan(getV4SpanAttributes(data.self, ns, cmd, operation, ORIGIN)); + }); } /** @@ -150,21 +144,17 @@ function subscribeV3Wireprotocol(): void { } function bindV3(channelName: string, extract: (args: unknown[]) => V3CallInfo | undefined): void { - bindTracingChannelToSpan( - diagnosticsChannel.tracingChannel(channelName), - data => { - const args = data.arguments; - if (!args) { - return undefined; - } - const info = extract(args); - if (!info || typeof info.ns !== 'string') { - return undefined; - } - return startMongoSpan(getV3SpanAttributes(info.ns, info.topology, info.command, info.operation, ORIGIN)); - }, - { requiresParentSpan: true }, - ); + bindTracingChannelToSpan(diagnosticsChannel.tracingChannel(channelName), data => { + const args = data.arguments; + if (!args) { + return undefined; + } + const info = extract(args); + if (!info || typeof info.ns !== 'string') { + return undefined; + } + return startMongoSpan(getV3SpanAttributes(info.ns, info.topology, info.command, info.operation, ORIGIN)); + }); } /** diff --git a/packages/server-utils/src/integrations/mongodb/mongodb-span.ts b/packages/server-utils/src/integrations/mongodb/mongodb-span.ts index 99d1778e758d..7c243081295c 100644 --- a/packages/server-utils/src/integrations/mongodb/mongodb-span.ts +++ b/packages/server-utils/src/integrations/mongodb/mongodb-span.ts @@ -235,6 +235,7 @@ export function startMongoSpan(attributes: SpanAttributes): Span { return startInactiveSpan({ name, + onlyIfParent: true, attributes: { [SENTRY_OP]: DB, [SENTRY_KIND]: 'client', diff --git a/packages/server-utils/src/integrations/mysql2/index.ts b/packages/server-utils/src/integrations/mysql2/index.ts index 5f337d21a41d..f8aa296266f9 100644 --- a/packages/server-utils/src/integrations/mysql2/index.ts +++ b/packages/server-utils/src/integrations/mysql2/index.ts @@ -74,34 +74,31 @@ function instrumentMysql2Orchestrion(): void { } function subscribeQueryChannel(channelName: ChannelName): void { - bindTracingChannelToSpan( - diagnosticsChannel.tracingChannel(channelName), - data => { - const statement = getQueryText(data.arguments); - const connectionAttributes = getConnectionAttributes(data.self?.config); - const { queryText, querySummary } = sanitizeSqlQueryWithSummary(statement, 'mysql'); + bindTracingChannelToSpan(diagnosticsChannel.tracingChannel(channelName), data => { + const statement = getQueryText(data.arguments); + const connectionAttributes = getConnectionAttributes(data.self?.config); + const { queryText, querySummary } = sanitizeSqlQueryWithSummary(statement, 'mysql'); - const client = getClient(); - const name = - client && hasSpanStreamingEnabled(client) - ? querySummary || (connectionAttributes[DB_NAMESPACE] as string | undefined) || DB_SYSTEM_VALUE_MYSQL - : (queryText ?? 'mysql2.query'); + const client = getClient(); + const name = + client && hasSpanStreamingEnabled(client) + ? querySummary || (connectionAttributes[DB_NAMESPACE] as string | undefined) || DB_SYSTEM_VALUE_MYSQL + : (queryText ?? 'mysql2.query'); - return startInactiveSpan({ - name, - attributes: { - [SENTRY_KIND]: 'client', - [SENTRY_ORIGIN]: ORIGIN, - [SENTRY_OP]: DB, - [DB_SYSTEM_NAME]: DB_SYSTEM_VALUE_MYSQL, - [DB_QUERY_TEXT]: queryText || undefined, - [DB_QUERY_SUMMARY]: querySummary, - ...connectionAttributes, - }, - }); - }, - { requiresParentSpan: true }, - ); + return startInactiveSpan({ + name, + onlyIfParent: true, + attributes: { + [SENTRY_KIND]: 'client', + [SENTRY_ORIGIN]: ORIGIN, + [SENTRY_OP]: DB, + [DB_SYSTEM_NAME]: DB_SYSTEM_VALUE_MYSQL, + [DB_QUERY_TEXT]: queryText || undefined, + [DB_QUERY_SUMMARY]: querySummary, + ...connectionAttributes, + }, + }); + }); } /** diff --git a/packages/server-utils/src/integrations/mysql2/mysql2-dc-subscriber.ts b/packages/server-utils/src/integrations/mysql2/mysql2-dc-subscriber.ts index 72d065956713..2a5cc75fd56c 100644 --- a/packages/server-utils/src/integrations/mysql2/mysql2-dc-subscriber.ts +++ b/packages/server-utils/src/integrations/mysql2/mysql2-dc-subscriber.ts @@ -92,56 +92,50 @@ export function subscribeMysql2DiagnosticChannels(tracingChannel: MySQL2TracingC } function setupQueryChannel(tracingChannel: MySQL2TracingChannelFactory, channelName: string): void { - bindTracingChannelToSpan( - tracingChannel(channelName), - data => { - // mysql2 does not sanitize its channel payload, so the statement may carry - // raw user values (on the `query` channel they are inlined). Strip every - // literal before it leaves the process; `values` is never attached. - const { queryText, querySummary } = sanitizeSqlQueryWithSummary(data.query, 'mysql'); - const operation = queryText?.match(SQL_OPERATION_RE)?.[1]?.toUpperCase(); + bindTracingChannelToSpan(tracingChannel(channelName), data => { + // mysql2 does not sanitize its channel payload, so the statement may carry + // raw user values (on the `query` channel they are inlined). Strip every + // literal before it leaves the process; `values` is never attached. + const { queryText, querySummary } = sanitizeSqlQueryWithSummary(data.query, 'mysql'); + const operation = queryText?.match(SQL_OPERATION_RE)?.[1]?.toUpperCase(); - const client = getClient(); - const name = - client && hasSpanStreamingEnabled(client) - ? querySummary || data.database || DB_SYSTEM_NAME_VALUE_MYSQL - : queryText || 'mysql2.query'; + const client = getClient(); + const name = + client && hasSpanStreamingEnabled(client) + ? querySummary || data.database || DB_SYSTEM_NAME_VALUE_MYSQL + : queryText || 'mysql2.query'; - return startInactiveSpan({ - name, - attributes: { - [SENTRY_ORIGIN]: ORIGIN, - [SENTRY_OP]: DB, - [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MYSQL, - [DB_QUERY_TEXT]: queryText, - [DB_QUERY_SUMMARY]: querySummary, - [DB_OPERATION_NAME]: operation, - [DB_NAMESPACE]: data.database || undefined, - [SERVER_ADDRESS]: data.serverAddress, - [SERVER_PORT]: data.serverPort, - }, - }); - }, - { requiresParentSpan: true }, - ); + return startInactiveSpan({ + name, + onlyIfParent: true, + attributes: { + [SENTRY_ORIGIN]: ORIGIN, + [SENTRY_OP]: DB, + [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MYSQL, + [DB_QUERY_TEXT]: queryText, + [DB_QUERY_SUMMARY]: querySummary, + [DB_OPERATION_NAME]: operation, + [DB_NAMESPACE]: data.database || undefined, + [SERVER_ADDRESS]: data.serverAddress, + [SERVER_PORT]: data.serverPort, + }, + }); + }); } function setupConnectChannel(tracingChannel: MySQL2TracingChannelFactory, channelName: string, spanName: string): void { - bindTracingChannelToSpan( - tracingChannel(channelName), - data => { - return startInactiveSpan({ - name: spanName, - attributes: { - [SENTRY_ORIGIN]: ORIGIN, - [SENTRY_OP]: DB, - [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MYSQL, - [DB_NAMESPACE]: data.database || undefined, - [SERVER_ADDRESS]: data.serverAddress, - [SERVER_PORT]: data.serverPort, - }, - }); - }, - { requiresParentSpan: true }, - ); + bindTracingChannelToSpan(tracingChannel(channelName), data => { + return startInactiveSpan({ + name: spanName, + onlyIfParent: true, + attributes: { + [SENTRY_ORIGIN]: ORIGIN, + [SENTRY_OP]: DB, + [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MYSQL, + [DB_NAMESPACE]: data.database || undefined, + [SERVER_ADDRESS]: data.serverAddress, + [SERVER_PORT]: data.serverPort, + }, + }); + }); } diff --git a/packages/server-utils/src/integrations/neon.ts b/packages/server-utils/src/integrations/neon.ts index f7e66ff953a2..e4130d14942c 100644 --- a/packages/server-utils/src/integrations/neon.ts +++ b/packages/server-utils/src/integrations/neon.ts @@ -60,15 +60,11 @@ function instrumentNeon(): void { // the pg span shape. bindPgQueryChannel(CHANNELS.NEON_QUERY, { origin: ORIGIN }); - bindTracingChannelToSpan( - diagnosticsChannel.tracingChannel(CHANNELS.NEON_HTTP_QUERY), - data => { - const span = startInactiveSpan(httpQuerySpanOptions(data.arguments)); - httpQuerySpans.add(span); - return span; - }, - { requiresParentSpan: true }, - ); + bindTracingChannelToSpan(diagnosticsChannel.tracingChannel(CHANNELS.NEON_HTTP_QUERY), data => { + const span = startInactiveSpan(httpQuerySpanOptions(data.arguments)); + httpQuerySpans.add(span); + return span; + }); // The connection string is resolved inside the HTTP executor, so the query span is active // when this settles. The executor's own context never sees the string. @@ -109,6 +105,7 @@ function httpQuerySpanOptions(args: unknown[]): Parameters)[QUERY_FROM_INSTRUMENTED_SQL]) { return undefined; } @@ -292,6 +291,7 @@ function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void { // `sentry.kind: client` matches the mysql/pg channel subscribers. const span = startInactiveSpan({ name, + onlyIfParent: requireParentSpan !== false, attributes: { [SENTRY_OP]: DB, [SENTRY_KIND]: 'client', @@ -324,7 +324,6 @@ function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void { return span; }, { - requiresParentSpan: requireParentSpan !== false, deferSpanEnd({ data }) { // `handle` is async: its promise settles on dispatch (asyncEnd), long // before the query does. The resolve/reject wrappers own the ending. diff --git a/packages/server-utils/src/integrations/postgres.ts b/packages/server-utils/src/integrations/postgres.ts index 6ef1346a5ee5..3231e0e60a06 100644 --- a/packages/server-utils/src/integrations/postgres.ts +++ b/packages/server-utils/src/integrations/postgres.ts @@ -141,6 +141,7 @@ function subscribeQueryLikeChannel( const spanOptions = getSpanOptions(data); return startInactiveSpan({ ...spanOptions, + onlyIfParent: true, attributes: { ...spanOptions.attributes, [SENTRY_KIND]: 'client' }, }); }, @@ -151,9 +152,6 @@ function subscribeQueryLikeChannel( // `query` can return a streamable `Submittable`, so only it defers. deferStreamedResult ? { - // Only instrument under an active span, leaving the context untouched otherwise - // (e.g. connects issued during app startup). - requiresParentSpan: true, // Streamable `Submittable` (e.g. `client.query(new Query())`) // returns an emitter that orchestrion stores on `ctx.result` while // firing no async events; the query isn't done until the emitter @@ -180,7 +178,7 @@ function subscribeQueryLikeChannel( return true; }, } - : { requiresParentSpan: true }, + : undefined, ); } diff --git a/packages/server-utils/src/integrations/prisma/orchestrion.ts b/packages/server-utils/src/integrations/prisma/orchestrion.ts index de209f694d71..a9c3bea5ac6d 100644 --- a/packages/server-utils/src/integrations/prisma/orchestrion.ts +++ b/packages/server-utils/src/integrations/prisma/orchestrion.ts @@ -52,11 +52,7 @@ export function instrumentPrismaV8(options: PrismaV8InstrumentationOptions): voi bindTracingChannelToSpan( diagnosticsChannel.tracingChannel(channelName), data => startOperationSpan(method, data), - { - // A stray query outside a request must not become a segment of its own. - requiresParentSpan: true, - ...(isLazy ? { deferSpanEnd: deferSpanEndUntilConsumed } : {}), - }, + isLazy ? { deferSpanEnd: deferSpanEndUntilConsumed } : undefined, ); } } @@ -72,6 +68,7 @@ function startOperationSpan(method: string, data: PrismaTerminalChannelContext): const span = startInactiveSpan({ name: OPERATION_SPAN_NAME, + onlyIfParent: true, parentSpan, attributes: { [SENTRY_ORIGIN]: PRISMA_ORIGIN, diff --git a/packages/server-utils/src/integrations/redis/ioredis-channel-subscriber.ts b/packages/server-utils/src/integrations/redis/ioredis-channel-subscriber.ts index d918d080f749..9426e26c6add 100644 --- a/packages/server-utils/src/integrations/redis/ioredis-channel-subscriber.ts +++ b/packages/server-utils/src/integrations/redis/ioredis-channel-subscriber.ts @@ -93,6 +93,7 @@ export function startIORedisCommandSpan( return startInactiveSpan({ name: cacheProperties?.name ?? streamedName ?? statement, + onlyIfParent: true, attributes: { ...attributes, ...cacheProperties?.attributes }, }); } @@ -112,8 +113,6 @@ export function instrumentIoredis(options: RedisCacheOptions): void { ); bindTracingChannelToSpan(commandChannel, data => startIORedisCommandSpan(data, options), { - // ioredis' `requireParentSpan` default: only create a span under an active span. - requiresParentSpan: true, beforeSpanEnd(span, data) { if (!('error' in data)) { applyCacheResponseAttributes(span, data.result); @@ -121,19 +120,16 @@ export function instrumentIoredis(options: RedisCacheOptions): void { }, }); - bindTracingChannelToSpan( - connectChannel, - data => { - const { host, port } = getConnectionOptions(data.self); - return startInactiveSpan({ - name: 'connect', - attributes: { - [SENTRY_KIND]: 'client', - ...connectionAttributes(host, port), - [SENTRY_OP]: DB, - }, - }); - }, - { requiresParentSpan: true }, - ); + bindTracingChannelToSpan(connectChannel, data => { + const { host, port } = getConnectionOptions(data.self); + return startInactiveSpan({ + name: 'connect', + onlyIfParent: true, + attributes: { + [SENTRY_KIND]: 'client', + ...connectionAttributes(host, port), + [SENTRY_OP]: DB, + }, + }); + }); } diff --git a/packages/server-utils/src/tracing-channel.ts b/packages/server-utils/src/tracing-channel.ts index 075057f47c31..859b80738af7 100644 --- a/packages/server-utils/src/tracing-channel.ts +++ b/packages/server-utils/src/tracing-channel.ts @@ -8,7 +8,6 @@ import { SPAN_STATUS_ERROR, getAsyncContextStrategy, getMainCarrier, - getActiveSpan, spanIsIgnored, getRootSpan, withActiveSpan, @@ -72,12 +71,6 @@ export interface TracingChannelLifeCycleOptions { /** Ends the span: `end()` on success, `end(error)` on failure (which marks `data` as errored). Idempotent. */ end: (error?: unknown) => void; }) => boolean; - - /** - * Apply span/scope binding only if there is a parent span, similar to returning `undefined` in the `getSpan` callback. - * If you need to perform some conditional checking on the parent span before deciding, do it in the `getSpan` callback. - */ - requiresParentSpan?: boolean; } /** Returned by {@link bindTracingChannelToSpan}: the bound channel plus a teardown handle. */ @@ -120,7 +113,7 @@ export function bindTracingChannelToSpan( getSpan: (data: TracingChannelPayloadWithSpan) => Span | undefined, opts?: TracingChannelLifeCycleOptions, ): TracingChannelBindingHandle { - const handle = bindSpanToChannelStore(channel, getSpan, opts); + const handle = bindSpanToChannelStore(channel, getSpan); const beforeSpanEnd = opts?.beforeSpanEnd; const deferSpanEnd = opts?.deferSpanEnd; @@ -224,7 +217,6 @@ export function bindTracingChannelToSpan( export function bindSpanToChannelStore( channel: TracingChannel, getSpan: (data: TracingChannelPayloadWithSpan) => Span | undefined, - opts?: Pick, ): TracingChannelBindingHandle { // Grabs the tracing channel binding defined by the AsyncContext strategy implementation const binding = getAsyncContextStrategy(getMainCarrier()).getTracingChannelBinding?.(); @@ -253,8 +245,7 @@ export function bindSpanToChannelStore( // callback-style channels (see `_sentryCallerStore`). data._sentryCallerStore = asyncLocalStorage.getStore(); - const shouldGetSpan = !opts?.requiresParentSpan || getActiveSpan(); - const span = shouldGetSpan ? getSpan(data) : undefined; + const span = getSpan(data); if (!span) { // Leave the active context untouched so nested operations keep parenting to the enclosing span. return data._sentryCallerStore as TData; diff --git a/packages/server-utils/test/integrations/mysql2/mysql2-dc-subscriber.test.ts b/packages/server-utils/test/integrations/mysql2/mysql2-dc-subscriber.test.ts index f414f8ee1bf0..dec09eb63d28 100644 --- a/packages/server-utils/test/integrations/mysql2/mysql2-dc-subscriber.test.ts +++ b/packages/server-utils/test/integrations/mysql2/mysql2-dc-subscriber.test.ts @@ -102,7 +102,7 @@ function installTestAsyncContextStrategy(): void { /** * Drives a channel's `tracePromise` inside an enclosing span and captures the span bound by the - * subscriber. The subscriber only creates a span when there is an enclosing active span, so the + * subscriber. The subscriber only creates a recording span when there is an enclosing active span, so the * helper always establishes one and returns its id for parenting assertions. */ async function traceOperation( @@ -168,7 +168,7 @@ describe('subscribeMysql2DiagnosticChannels', () => { vi.clearAllMocks(); }); - it('does not create a span when there is no enclosing active span', async () => { + it('does not create a recording span when there is no enclosing active span', async () => { const channel = tracingChannel(MYSQL2_DC_CHANNEL_QUERY); let span: Span | undefined; @@ -179,7 +179,8 @@ describe('subscribeMysql2DiagnosticChannels', () => { { query: 'SELECT 1' }, ); - expect(span).toBeUndefined(); + expect(span).toBeDefined(); + expect(span?.isRecording()).toBe(false); }); describe('query channel', () => { diff --git a/packages/server-utils/test/integrations/neon.test.ts b/packages/server-utils/test/integrations/neon.test.ts index 661a65cc7389..6c5ac05fe9d0 100644 --- a/packages/server-utils/test/integrations/neon.test.ts +++ b/packages/server-utils/test/integrations/neon.test.ts @@ -154,13 +154,13 @@ describe('neonIntegration', () => { expect(span.end).toHaveBeenCalledTimes(1); }); - it('does not start a span without a parent span', async () => { + it('does not create a recording span without a parent span', async () => { getActiveSpanSpy.mockReturnValue(undefined); const ctx: ChannelContext = { arguments: ['SELECT 1'], self: { connectionParameters: CONNECTION } }; await tracingChannel(CHANNELS.NEON_QUERY).tracePromise(async () => ({ rows: [] }), ctx); - expect(startInactiveSpanSpy).not.toHaveBeenCalled(); + expect(startInactiveSpanSpy).toHaveBeenCalledWith(expect.objectContaining({ onlyIfParent: true })); }); }); diff --git a/packages/server-utils/test/integrations/postgres/postgres.test.ts b/packages/server-utils/test/integrations/postgres/postgres.test.ts index db64ef9e137c..9af1c1a5c851 100644 --- a/packages/server-utils/test/integrations/postgres/postgres.test.ts +++ b/packages/server-utils/test/integrations/postgres/postgres.test.ts @@ -63,13 +63,6 @@ function installTestAsyncContextStrategy(): void { }); } -// The subscriber builds spans via `startInactiveSpan` and gates on -// `getActiveSpan`. We spy both: `getActiveSpan` to satisfy the -// requireParentSpan gate, and `startInactiveSpan` to capture the span -// options the subscriber builds (name + raw attributes) and to track the -// span's lifecycle. The final `db` op / SQL description come from the -// SDK's `inferDbSpanData` processor, which isn't wired up here. That's -// covered by the integration test. function makeSpan(): Span { return { end: vi.fn(), setStatus: vi.fn(), setAttributes: vi.fn() } as unknown as Span; } @@ -98,7 +91,6 @@ describe('postgresIntegration', () => { beforeEach(() => { span = makeSpan(); startInactiveSpanSpy = vi.spyOn(SentryCore, 'startInactiveSpan').mockReturnValue(span); - // A truthy active span by default, so the requireParentSpan gate passes. getActiveSpanSpy = vi.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({} as Span); }); @@ -251,12 +243,12 @@ describe('postgresIntegration', () => { ); }); - it('requireParentSpan: does not create a span when there is no active span', async () => { + it('does not create a recording span without an active parent span', async () => { getActiveSpanSpy.mockReturnValue(undefined); const ctx: OrchestrionChannelContext = { arguments: ['SELECT 1'], self: { connectionParameters: CONNECTION } }; await tracingChannel(CHANNELS.PG_QUERY).tracePromise(async () => ({ rows: [] }), ctx); - expect(startInactiveSpanSpy).not.toHaveBeenCalled(); + expect(startInactiveSpanSpy).toHaveBeenCalledWith(expect.objectContaining({ onlyIfParent: true })); }); }); diff --git a/packages/server-utils/test/integrations/prisma-orchestrion.test.ts b/packages/server-utils/test/integrations/prisma-orchestrion.test.ts index 78a1e3bf1988..cbc8cc142c6c 100644 --- a/packages/server-utils/test/integrations/prisma-orchestrion.test.ts +++ b/packages/server-utils/test/integrations/prisma-orchestrion.test.ts @@ -206,13 +206,13 @@ describe('instrumentPrismaV8', () => { expect(span.end).toHaveBeenCalledTimes(1); }); - it('does not open a span without a parent', async () => { + it('does not create a recording span without a parent', async () => { await tracingChannel(CHANNELS.PRISMA_CREATE).tracePromise(async () => ({ id: 1 }), { arguments: [], self: { modelName: 'User' }, }); - expect(startInactiveSpanSpy).not.toHaveBeenCalled(); + expect(startInactiveSpanSpy).toHaveBeenCalledWith(expect.objectContaining({ onlyIfParent: true })); }); it('async terminal: marks the span as errored when the promise rejects', async () => { From b0560b7beac0249b1d736767ff3b7f6e08a8a5eb Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:06:53 +0100 Subject: [PATCH 052/169] ref: Pass optional span attributes directly instead of conditional spreads (#25878) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** `SentrySpan.setAttribute` drops `undefined` values, and `SpanAttributes` already allows `undefined`. That makes patterns like `...(provider ? { [GEN_AI_PROVIDER_NAME]: provider } : {})` unnecessary when the attributes are passed at span creation, so this passes the value directly. It also trims the browser bundle a little. I only changed attribute objects passed when a span is created, where the condition just checks the value itself. I left these alone on purpose: - `setAttributes` on spans that already exist. There, `undefined` *deletes* the attribute (for example the nextjs/ember/tanstack route updates and the MCP `initialize` update). - `sentry.op` / `sentry.origin` / `sentry.segment.name.source`. An explicit `undefined` overrides the core defaults (the effect tracer already has a comment about this). - Values typed `string | null` (sveltekit `route.id`). `null` is not a valid attribute value. - Ember route names and Angular decorator component names, where `''` is a real case that should stay omitted. Ember has tests for this. - Non-attribute objects (envelope headers, HTTP headers, MCP helpers typed `Record`) and conditions on something other than the value, like `hasSpanStreaming`, `recordInputs` or `source === 'route'`. **Unsure about:** a falsy check like `x ? {…} : {}` also skipped `''`. Now an empty string would be set as an attribute. I don't think that can happen at the sites I changed, but it's worth a quick look. via **Francesco Gringl-Novy**. -- [View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3AD0CA8FXFXAS%3A1793278469.396419) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AD0CA8FXFXAS%3A1793278469.396419/?project=4510944073809921) --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Francesco Gringl-Novy --- packages/angular/src/tracing.ts | 4 +--- .../src/performance/interactions.ts | 8 ++++---- .../src/tracing/browserTracingIntegration.ts | 2 +- .../worker/instrumentQueueProducer.ts | 2 +- .../instrumentations/worker/instrumentR2.ts | 2 +- .../appRouterRoutingInstrumentation.ts | 8 ++++---- .../pagesRouterRoutingInstrumentation.ts | 2 +- .../src/server/createServerInstrumentation.ts | 2 +- packages/react/src/profiler.tsx | 10 +++++----- packages/react/src/tanstackrouter.ts | 4 ++-- .../v3/client/browserTracingIntegration.ts | 2 +- packages/server-utils/src/ai/flue/index.ts | 4 ++-- packages/server-utils/src/ai/flue/utils.ts | 8 ++++---- .../server-utils/src/ai/pi-durable/models.ts | 12 ++++++------ .../server-utils/src/ai/pi-durable/tools.ts | 4 ++-- .../server-utils/src/ai/typesafe/index.ts | 2 +- .../integrations/express/instrumentation.ts | 2 +- .../server-utils/src/integrations/mysql.ts | 6 +++--- .../vercel-ai/vercel-ai-dc-subscriber.ts | 19 ++++++++++--------- packages/solid/src/tanstackrouter.ts | 4 ++-- packages/svelte/src/performance.ts | 4 ++-- packages/vue/src/tanstackrouter.ts | 4 ++-- packages/vue/src/tracing.ts | 4 ++-- 23 files changed, 59 insertions(+), 60 deletions(-) diff --git a/packages/angular/src/tracing.ts b/packages/angular/src/tracing.ts index 3a50bf232339..c5884695121c 100644 --- a/packages/angular/src/tracing.ts +++ b/packages/angular/src/tracing.ts @@ -159,9 +159,7 @@ export class TraceService implements OnDestroy { [SENTRY_OP]: ROUTER, [SENTRY_ORIGIN]: 'auto.ui.angular', [URL_FULL]: strippedUrl, - ...(navigationEvent.navigationTrigger && { - navigationTrigger: navigationEvent.navigationTrigger, - }), + navigationTrigger: navigationEvent.navigationTrigger, }, }), ) || null; diff --git a/packages/browser-utils/src/performance/interactions.ts b/packages/browser-utils/src/performance/interactions.ts index 33913a40c619..3112c7b6a4ac 100644 --- a/packages/browser-utils/src/performance/interactions.ts +++ b/packages/browser-utils/src/performance/interactions.ts @@ -217,10 +217,10 @@ function registerInteractionListener( [SENTRY_SEGMENT_NAME_SOURCE]: hasSpanStreaming ? streamedNameSource : latestRoute.source || 'url', [SENTRY_ORIGIN]: 'auto.browser.interactions', ...(hasSpanStreaming && { [SENTRY_SEGMENT_NAME]: streamedName }), - ...(latestRoute.routeId && { [ROUTER_NAVIGATION_ROUTE_ID]: latestRoute.routeId }), - ...(latestRoute.urlTemplate && { [URL_TEMPLATE]: latestRoute.urlTemplate }), - ...(latestRoute.httpRoute && { [HTTP_ROUTE]: latestRoute.httpRoute }), - ...(latestRoute.urlPath && { [URL_PATH]: latestRoute.urlPath }), + [ROUTER_NAVIGATION_ROUTE_ID]: latestRoute.routeId, + [URL_TEMPLATE]: latestRoute.urlTemplate, + [HTTP_ROUTE]: latestRoute.httpRoute, + [URL_PATH]: latestRoute.urlPath, ...(latestRoute.urlFull && { [URL_FULL]: filterCollectedUrl(latestRoute.urlFull) }), }, }, diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index b02a5d5ebcca..e21b40a86f27 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -367,7 +367,7 @@ export const browserTracingIntegration = ((options: Partial( [MESSAGING_DESTINATION_NAME]: bindingName, [MESSAGING_OPERATION_TYPE]: 'send', [MESSAGING_OPERATION_NAME]: 'send', - ...(messageCount !== undefined && { [MESSAGING_BATCH_MESSAGE_COUNT]: messageCount }), + [MESSAGING_BATCH_MESSAGE_COUNT]: messageCount, [MESSAGING_MESSAGE_BODY_SIZE]: bodySize, [SENTRY_OP]: QUEUE_PUBLISH, [SENTRY_ORIGIN]: ORIGIN, diff --git a/packages/cloudflare/src/instrumentations/worker/instrumentR2.ts b/packages/cloudflare/src/instrumentations/worker/instrumentR2.ts index a2ffdf72e50a..afc740cc250f 100644 --- a/packages/cloudflare/src/instrumentations/worker/instrumentR2.ts +++ b/packages/cloudflare/src/instrumentations/worker/instrumentR2.ts @@ -64,7 +64,7 @@ function createSpanOptions(bindingName: string, r2Op: R2OperationKey, key?: stri attributes: { [CLOUDFLARE_R2_OPERATION]: operation, [CLOUDFLARE_R2_BUCKET]: bindingName, - ...(requestKey !== undefined && { [CLOUDFLARE_R2_REQUEST_KEY]: requestKey }), + [CLOUDFLARE_R2_REQUEST_KEY]: requestKey, ...(isR2ListOptions(key) && key.prefix !== undefined && { [CLOUDFLARE_R2_REQUEST_PREFIX]: key.prefix }), ...(isR2ListOptions(key) && key.delimiter !== undefined && { [CLOUDFLARE_R2_REQUEST_DELIMITER]: key.delimiter }), [SENTRY_OP]: op, diff --git a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts index ba6e5c88986e..d48f48cde5ea 100644 --- a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts @@ -150,7 +150,7 @@ export function appRouterInstrumentPageLoad(client: Client): void { [SENTRY_SEGMENT_NAME_SOURCE]: parameterizedPathname ? 'route' : 'url', [URL_PATH]: WINDOW.location.pathname, [URL_FULL]: documentUrl, - ...(parameterizedPathname && { [URL_TEMPLATE]: parameterizedPathname }), + [URL_TEMPLATE]: parameterizedPathname, }, }; @@ -258,7 +258,7 @@ export function appRouterInstrumentNavigation(client: Client): void { [SENTRY_SEGMENT_NAME_SOURCE]: parameterizedPathname ? 'route' : 'url', // oxlint-disable-next-line typescript/no-deprecated -- Preserve the existing attribute name. [NAVIGATION_TYPE]: `router.${navigationType}`, - ...(parameterizedPathname && { [URL_TEMPLATE]: parameterizedPathname }), + [URL_TEMPLATE]: parameterizedPathname, }, }, { url: getAbsoluteUrl(normalizedHref) }, @@ -296,7 +296,7 @@ export function appRouterInstrumentNavigation(client: Client): void { [SENTRY_SEGMENT_NAME_SOURCE]: parameterizedPathname ? 'route' : 'url', // oxlint-disable-next-line typescript/no-deprecated -- Preserve the existing attribute name. [NAVIGATION_TYPE]: traversal?.navigationType ?? 'browser.popstate', - ...(parameterizedPathname && { [URL_TEMPLATE]: parameterizedPathname }), + [URL_TEMPLATE]: parameterizedPathname, }, }, // The full location rather than just the pathname, so the span's `url.full` keeps the @@ -394,7 +394,7 @@ function patchRouter(client: Client, router: NextRouter, currentNavigationSpanRe [SENTRY_SEGMENT_NAME_SOURCE]: parameterizedPathname ? 'route' : 'url', // oxlint-disable-next-line typescript/no-deprecated -- Preserve the existing attribute name. [NAVIGATION_TYPE]: `router.${routerFunctionName}`, - ...(parameterizedPathname && { [URL_TEMPLATE]: parameterizedPathname }), + [URL_TEMPLATE]: parameterizedPathname, }, }, { url: getAbsoluteUrl(normalizedHref) }, diff --git a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts index 7335d0465bda..c37599e69fcb 100644 --- a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts @@ -109,7 +109,7 @@ export function pagesRouterInstrumentPageLoad(client: Client): void { [SENTRY_OP]: PAGELOAD, [SENTRY_ORIGIN]: 'auto.pageload.nextjs.pages_router_instrumentation', [SENTRY_SEGMENT_NAME_SOURCE]: route ? 'route' : 'url', - ...(route && { [URL_TEMPLATE]: route }), + [URL_TEMPLATE]: route, ...(params && { ...params }), }, }, diff --git a/packages/react-router/src/server/createServerInstrumentation.ts b/packages/react-router/src/server/createServerInstrumentation.ts index ebf16d85c87d..892663c37a46 100644 --- a/packages/react-router/src/server/createServerInstrumentation.ts +++ b/packages/react-router/src/server/createServerInstrumentation.ts @@ -240,7 +240,7 @@ export function createSentryServerInstrumentation( [SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api', 'react_router.route.id': routeId, [HTTP_ROUTE]: routePattern, - ...(middlewareName && { 'react_router.middleware.name': middlewareName }), + 'react_router.middleware.name': middlewareName, 'react_router.middleware.index': middlewareIndex, }, }, diff --git a/packages/react/src/profiler.tsx b/packages/react/src/profiler.tsx index 508d9e508bdf..a81a50199c7a 100644 --- a/packages/react/src/profiler.tsx +++ b/packages/react/src/profiler.tsx @@ -66,7 +66,7 @@ class Profiler extends React.Component { attributes: { [SENTRY_OP]: UI_MOUNT, [SENTRY_ORIGIN]: 'auto.ui.react.profiler', - ...(componentName && { [UI_COMPONENT_NAME]: componentName }), + [UI_COMPONENT_NAME]: componentName || undefined, ...(hasSpanStreaming && { [SENTRY_DESCRIPTION]: description }), }, }); @@ -102,7 +102,7 @@ class Profiler extends React.Component { attributes: { [SENTRY_OP]: UI_UPDATE, [SENTRY_ORIGIN]: 'auto.ui.react.profiler', - ...(componentName && { [UI_COMPONENT_NAME]: componentName }), + [UI_COMPONENT_NAME]: componentName || undefined, 'ui.react.changed_props': changedProps, ...(hasSpanStreaming && { [SENTRY_DESCRIPTION]: description }), }, @@ -142,7 +142,7 @@ class Profiler extends React.Component { attributes: { [SENTRY_OP]: UI_RENDER, [SENTRY_ORIGIN]: 'auto.ui.react.profiler', - ...(componentName && { [UI_COMPONENT_NAME]: componentName }), + [UI_COMPONENT_NAME]: componentName || undefined, ...(hasSpanStreaming && { [SENTRY_DESCRIPTION]: description }), }, }); @@ -229,7 +229,7 @@ function useProfiler( attributes: { [SENTRY_OP]: UI_MOUNT, [SENTRY_ORIGIN]: 'auto.ui.react.profiler', - ...(componentName && { [UI_COMPONENT_NAME]: componentName }), + [UI_COMPONENT_NAME]: componentName || undefined, ...(hasSpanStreaming && { [SENTRY_DESCRIPTION]: description }), }, }); @@ -257,7 +257,7 @@ function useProfiler( attributes: { [SENTRY_OP]: UI_RENDER, [SENTRY_ORIGIN]: 'auto.ui.react.profiler', - ...(componentName && { [UI_COMPONENT_NAME]: componentName }), + [UI_COMPONENT_NAME]: componentName || undefined, ...(hasSpanStreaming && { [SENTRY_DESCRIPTION]: description }), }, }); diff --git a/packages/react/src/tanstackrouter.ts b/packages/react/src/tanstackrouter.ts index 41eec4694aae..e7f1c20f8e2e 100644 --- a/packages/react/src/tanstackrouter.ts +++ b/packages/react/src/tanstackrouter.ts @@ -106,7 +106,7 @@ export function tanstackRouterBrowserTracingIntegration( [SENTRY_OP]: PAGELOAD, [SENTRY_ORIGIN]: 'auto.pageload.react.tanstack_router', [SENTRY_SEGMENT_NAME_SOURCE]: routeMatch ? 'route' : 'url', - ...(routeMatch && { [URL_TEMPLATE]: routeMatch.routeId }), + [URL_TEMPLATE]: routeMatch?.routeId, ...routeMatchToParamSpanAttributes(routeMatch), }, }); @@ -163,7 +163,7 @@ export function tanstackRouterBrowserTracingIntegration( [SENTRY_OP]: NAVIGATION, [SENTRY_ORIGIN]: 'auto.navigation.react.tanstack_router', [SENTRY_SEGMENT_NAME_SOURCE]: routeMatch ? 'route' : 'url', - ...(routeMatch && { [URL_TEMPLATE]: routeMatch.routeId }), + [URL_TEMPLATE]: routeMatch?.routeId, ...routeMatchToParamSpanAttributes(routeMatch), }, }, diff --git a/packages/remix/src/v3/client/browserTracingIntegration.ts b/packages/remix/src/v3/client/browserTracingIntegration.ts index 5e07ca200934..3142c834c966 100644 --- a/packages/remix/src/v3/client/browserTracingIntegration.ts +++ b/packages/remix/src/v3/client/browserTracingIntegration.ts @@ -107,7 +107,7 @@ function instrumentNavigationApi(client: Client): void { name: route ?? (hasSpanStreamingEnabled(client) ? NAVIGATION_SPAN_NAME_FALLBACK : pathname), attributes: { [SENTRY_SEGMENT_NAME_SOURCE]: route ? 'route' : 'url', - ...(route && { [URL_TEMPLATE]: route }), + [URL_TEMPLATE]: route, [SENTRY_ORIGIN]: 'auto.navigation.remix_v3', }, }, diff --git a/packages/server-utils/src/ai/flue/index.ts b/packages/server-utils/src/ai/flue/index.ts index d4856f95f2ac..b5f969c8a16e 100644 --- a/packages/server-utils/src/ai/flue/index.ts +++ b/packages/server-utils/src/ai/flue/index.ts @@ -144,8 +144,8 @@ export function createFlueInstrumentation(options: FlueOptions = {}): FlueInstru attributes: { [SENTRY_ORIGIN]: FLUE_ORIGIN, [GEN_AI_OPERATION_NAME]: 'invoke_agent', - ...(ctx.agentName ? { [GEN_AI_AGENT_NAME]: ctx.agentName } : {}), - ...(ctx.conversationId ? { [GEN_AI_CONVERSATION_ID]: ctx.conversationId } : {}), + [GEN_AI_AGENT_NAME]: ctx.agentName, + [GEN_AI_CONVERSATION_ID]: ctx.conversationId, }, }, async (span: Span) => { diff --git a/packages/server-utils/src/ai/flue/utils.ts b/packages/server-utils/src/ai/flue/utils.ts index 7f22256bf319..399a2aec3580 100644 --- a/packages/server-utils/src/ai/flue/utils.ts +++ b/packages/server-utils/src/ai/flue/utils.ts @@ -123,10 +123,10 @@ export function startTurnSpan(observation: FlueObservation, turnSpans: SpanTrack attributes: { [SENTRY_ORIGIN]: FLUE_ORIGIN, [GEN_AI_OPERATION_NAME]: 'chat', - ...(observation.conversationId ? { [GEN_AI_CONVERSATION_ID]: observation.conversationId } : {}), + [GEN_AI_CONVERSATION_ID]: observation.conversationId, // No conventional attribute for this; it is the only way to tell a compaction turn from a // user-facing one. - ...(observation.purpose ? { 'flue.turn.purpose': observation.purpose } : {}), + 'flue.turn.purpose': observation.purpose, }, }), ); @@ -207,8 +207,8 @@ export function startToolSpan(observation: FlueObservation, toolSpans: SpanTrack attributes: { [SENTRY_ORIGIN]: FLUE_ORIGIN, [GEN_AI_OPERATION_NAME]: 'execute_tool', - ...(toolName ? { [GEN_AI_TOOL_NAME]: toolName } : {}), - ...(observation.conversationId ? { [GEN_AI_CONVERSATION_ID]: observation.conversationId } : {}), + [GEN_AI_TOOL_NAME]: toolName, + [GEN_AI_CONVERSATION_ID]: observation.conversationId, ...(recordInputs && observation.args !== undefined ? { [GEN_AI_TOOL_CALL_ARGUMENTS]: stringify(observation.args) } : {}), diff --git a/packages/server-utils/src/ai/pi-durable/models.ts b/packages/server-utils/src/ai/pi-durable/models.ts index b50a5cdb0420..196973159d70 100644 --- a/packages/server-utils/src/ai/pi-durable/models.ts +++ b/packages/server-utils/src/ai/pi-durable/models.ts @@ -117,14 +117,14 @@ function traceModelRequest( attributes: { [SENTRY_ORIGIN]: PI_DURABLE_ORIGIN, [GEN_AI_OPERATION_NAME]: 'chat', - ...(model.provider ? { [GEN_AI_PROVIDER_NAME]: model.provider } : {}), - ...(model.id ? { [GEN_AI_REQUEST_MODEL]: model.id } : {}), - ...(conversationId ? { [GEN_AI_CONVERSATION_ID]: conversationId } : {}), + [GEN_AI_PROVIDER_NAME]: model.provider, + [GEN_AI_REQUEST_MODEL]: model.id, + [GEN_AI_CONVERSATION_ID]: conversationId, ...getServerAttributes(model), ...(streaming ? { [GEN_AI_RESPONSE_STREAMING]: true } : {}), - ...(callOptions?.temperature !== undefined ? { [GEN_AI_REQUEST_TEMPERATURE]: callOptions.temperature } : {}), - ...(callOptions?.maxTokens !== undefined ? { [GEN_AI_REQUEST_MAX_TOKENS]: callOptions.maxTokens } : {}), - ...(callOptions?.reasoning ? { [GEN_AI_REQUEST_REASONING_LEVEL]: callOptions.reasoning } : {}), + [GEN_AI_REQUEST_TEMPERATURE]: callOptions?.temperature, + [GEN_AI_REQUEST_MAX_TOKENS]: callOptions?.maxTokens, + [GEN_AI_REQUEST_REASONING_LEVEL]: callOptions?.reasoning, ...(recordInputs && context ? getRequestContentAttributes(context) : {}), }, }, diff --git a/packages/server-utils/src/ai/pi-durable/tools.ts b/packages/server-utils/src/ai/pi-durable/tools.ts index 8ccf21155549..6a6ced8f8787 100644 --- a/packages/server-utils/src/ai/pi-durable/tools.ts +++ b/packages/server-utils/src/ai/pi-durable/tools.ts @@ -122,8 +122,8 @@ export function instrumentTool(tool: PiTool, runs: PiRuns, options: GenAiOptions [SENTRY_ORIGIN]: PI_DURABLE_ORIGIN, [GEN_AI_OPERATION_NAME]: 'execute_tool', [GEN_AI_TOOL_NAME]: tool.name, - ...(tool.description ? { [GEN_AI_TOOL_DESCRIPTION]: tool.description } : {}), - ...(api?.callId ? { [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: api.callId } : {}), + [GEN_AI_TOOL_DESCRIPTION]: tool.description, + [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: api?.callId, ...(api?.conversationId !== undefined ? { [GEN_AI_CONVERSATION_ID]: toSentryConversationId(runs, api.conversationId) } : {}), diff --git a/packages/server-utils/src/ai/typesafe/index.ts b/packages/server-utils/src/ai/typesafe/index.ts index 65d24e6fc3f8..7bffea993239 100644 --- a/packages/server-utils/src/ai/typesafe/index.ts +++ b/packages/server-utils/src/ai/typesafe/index.ts @@ -50,7 +50,7 @@ function getRequestAttributes( [SENTRY_ORIGIN]: TYPESAFE_ORIGIN, [GEN_AI_OPERATION_NAME]: 'decide', [GEN_AI_PROVIDER_NAME]: TYPESAFE_PROVIDER_NAME, - ...(model ? { [GEN_AI_REQUEST_MODEL]: model } : {}), + [GEN_AI_REQUEST_MODEL]: model, ...(recordInputs ? { [GEN_AI_INPUT_MESSAGES]: getEvaluationInputMessages(request) } : {}), }; } diff --git a/packages/server-utils/src/integrations/express/instrumentation.ts b/packages/server-utils/src/integrations/express/instrumentation.ts index a3d079c6bd2b..cac3ede931ff 100644 --- a/packages/server-utils/src/integrations/express/instrumentation.ts +++ b/packages/server-utils/src/integrations/express/instrumentation.ts @@ -344,7 +344,7 @@ function getSpanForLayer(data: HandleChannelContext, options: ExpressIntegration [SENTRY_OP]: EXPRESS_TYPE_TO_SPAN_OP[type], [ATTR_EXPRESS_NAME]: name, [ATTR_EXPRESS_TYPE]: type, - ...(matchedRoute ? { [HTTP_ROUTE]: matchedRoute } : {}), + [HTTP_ROUTE]: matchedRoute, }, }); diff --git a/packages/server-utils/src/integrations/mysql.ts b/packages/server-utils/src/integrations/mysql.ts index de538d52afd7..1a68350361e6 100644 --- a/packages/server-utils/src/integrations/mysql.ts +++ b/packages/server-utils/src/integrations/mysql.ts @@ -98,9 +98,9 @@ function instrumentMysql(): void { [SENTRY_ORIGIN]: 'auto.db.mysql', // oxlint-disable-next-line typescript/no-deprecated -- Preserve the existing attribute name. [DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database), - ...(database ? { [DB_NAMESPACE]: database } : {}), - ...(user ? { [DB_USER]: user } : {}), - ...(queryText ? { [DB_QUERY_TEXT]: queryText } : {}), + [DB_NAMESPACE]: database, + [DB_USER]: user, + [DB_QUERY_TEXT]: queryText, [DB_QUERY_SUMMARY]: querySummary, [SERVER_ADDRESS]: host, [SERVER_PORT]: portIsNumber ? portNumber : undefined, diff --git a/packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts b/packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts index 2943aa7dddb2..f7525a94e9dd 100644 --- a/packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts +++ b/packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts @@ -453,10 +453,11 @@ export function createSpanFromMessage( const baseAttributes: SpanAttributes = { [SENTRY_ORIGIN]: ORIGIN, ...telemetryMetadataAttributes(event.telemetryMetadata), - ...(provider ? { [GEN_AI_PROVIDER_NAME]: provider, [VERCEL_AI_MODEL_PROVIDER_ATTRIBUTE]: provider } : {}), - ...(modelId ? { [GEN_AI_REQUEST_MODEL]: modelId } : {}), - ...(maxRetries !== undefined ? { [VERCEL_AI_SETTINGS_MAX_RETRIES_ATTRIBUTE]: maxRetries } : {}), - ...(conversationId ? { [GEN_AI_CONVERSATION_ID]: conversationId } : {}), + [GEN_AI_PROVIDER_NAME]: provider, + [VERCEL_AI_MODEL_PROVIDER_ATTRIBUTE]: provider, + [GEN_AI_REQUEST_MODEL]: modelId, + [VERCEL_AI_SETTINGS_MAX_RETRIES_ATTRIBUTE]: maxRetries, + [GEN_AI_CONVERSATION_ID]: conversationId, }; switch (type) { @@ -547,7 +548,7 @@ function buildInvokeAgentSpan( ...baseAttributes, [VERCEL_AI_OPERATION_ID_ATTRIBUTE]: operationId, [GEN_AI_RESPONSE_STREAMING]: isStream, - ...(functionId ? { [GEN_AI_FUNCTION_ID]: functionId } : {}), + [GEN_AI_FUNCTION_ID]: functionId, ...(recordInputs ? buildInputMessageAttributes(event) : {}), }); if (isStream && callId) { @@ -591,11 +592,11 @@ function buildToolSpan( recordInputs && toolName ? resolveToolDescription(asString(event.callId), toolName, event.tools) : undefined; return startGenAiSpan('execute_tool', toolName, { [SENTRY_ORIGIN]: ORIGIN, - ...(toolName ? { [GEN_AI_TOOL_NAME]: toolName } : {}), - ...(toolCallId ? { [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: toolCallId } : {}), - ...(description ? { [GEN_AI_TOOL_DESCRIPTION]: description } : {}), + [GEN_AI_TOOL_NAME]: toolName, + [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: toolCallId, + [GEN_AI_TOOL_DESCRIPTION]: description, ...(recordInputs && toolInput !== undefined ? { [GEN_AI_TOOL_CALL_ARGUMENTS]: stringify(toolInput) } : {}), - ...(conversationId ? { [GEN_AI_CONVERSATION_ID]: conversationId } : {}), + [GEN_AI_CONVERSATION_ID]: conversationId, }); } diff --git a/packages/solid/src/tanstackrouter.ts b/packages/solid/src/tanstackrouter.ts index fd64dce25235..ae8d8eb278a6 100644 --- a/packages/solid/src/tanstackrouter.ts +++ b/packages/solid/src/tanstackrouter.ts @@ -99,7 +99,7 @@ export function tanstackRouterBrowserTracingIntegration( [SENTRY_OP]: PAGELOAD, [SENTRY_ORIGIN]: 'auto.pageload.solid.tanstack_router', [SENTRY_SEGMENT_NAME_SOURCE]: routeMatch ? 'route' : 'url', - ...(routeMatch && { [URL_TEMPLATE]: routeMatch.routeId }), + [URL_TEMPLATE]: routeMatch?.routeId, ...routeMatchToParamSpanAttributes(routeMatch), }, }); @@ -158,7 +158,7 @@ export function tanstackRouterBrowserTracingIntegration( [SENTRY_OP]: NAVIGATION, [SENTRY_ORIGIN]: 'auto.navigation.solid.tanstack_router', [SENTRY_SEGMENT_NAME_SOURCE]: routeMatch ? 'route' : 'url', - ...(routeMatch && { [URL_TEMPLATE]: routeMatch.routeId }), + [URL_TEMPLATE]: routeMatch?.routeId, ...routeMatchToParamSpanAttributes(routeMatch), }, }, diff --git a/packages/svelte/src/performance.ts b/packages/svelte/src/performance.ts index 59f7c1b8174a..d05bd8f92f02 100644 --- a/packages/svelte/src/performance.ts +++ b/packages/svelte/src/performance.ts @@ -65,7 +65,7 @@ function recordInitSpan(componentName: string | undefined, description: string): attributes: { [SENTRY_OP]: UI_MOUNT, [SENTRY_ORIGIN]: 'auto.ui.svelte', - ...(componentName && { [UI_COMPONENT_NAME]: componentName }), + [UI_COMPONENT_NAME]: componentName || undefined, ...(hasSpanStreaming && { [SENTRY_DESCRIPTION]: description }), }, }); @@ -87,7 +87,7 @@ function recordUpdateSpans(componentName: string | undefined, description: strin attributes: { [SENTRY_OP]: UI_UPDATE, [SENTRY_ORIGIN]: 'auto.ui.svelte', - ...(componentName && { [UI_COMPONENT_NAME]: componentName }), + [UI_COMPONENT_NAME]: componentName || undefined, ...(hasSpanStreaming && { [SENTRY_DESCRIPTION]: description }), }, }); diff --git a/packages/vue/src/tanstackrouter.ts b/packages/vue/src/tanstackrouter.ts index f485c6cb3fd3..3d704405041f 100644 --- a/packages/vue/src/tanstackrouter.ts +++ b/packages/vue/src/tanstackrouter.ts @@ -105,7 +105,7 @@ export function tanstackRouterBrowserTracingIntegration( [SENTRY_OP]: PAGELOAD, [SENTRY_ORIGIN]: 'auto.pageload.vue.tanstack_router', [SENTRY_SEGMENT_NAME_SOURCE]: routeMatch ? 'route' : 'url', - ...(routeMatch && { [URL_TEMPLATE]: routeMatch.routeId }), + [URL_TEMPLATE]: routeMatch?.routeId, ...routeMatchToParamSpanAttributes(routeMatch), }, }); @@ -165,7 +165,7 @@ export function tanstackRouterBrowserTracingIntegration( [SENTRY_OP]: NAVIGATION, [SENTRY_ORIGIN]: 'auto.navigation.vue.tanstack_router', [SENTRY_SEGMENT_NAME_SOURCE]: routeMatch ? 'route' : 'url', - ...(routeMatch && { [URL_TEMPLATE]: routeMatch.routeId }), + [URL_TEMPLATE]: routeMatch?.routeId, ...routeMatchToParamSpanAttributes(routeMatch), }, }, diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index 822ef2909a45..a447828e6c11 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -146,7 +146,7 @@ export const createTracingMixins = (options: Partial = {}): Mixi attributes: { [SENTRY_OP]: UI_RENDER, [SENTRY_ORIGIN]: 'auto.ui.vue', - ...(conventionComponentName && { [UI_COMPONENT_NAME]: conventionComponentName }), + [UI_COMPONENT_NAME]: conventionComponentName || undefined, ...(hasSpanStreaming && { [SENTRY_DESCRIPTION]: description }), }, onlyIfParent: true, @@ -197,7 +197,7 @@ export const createTracingMixins = (options: Partial = {}): Mixi attributes: { [SENTRY_OP]: VUE_OPERATION_TO_SPAN_OP[operation], [SENTRY_ORIGIN]: 'auto.ui.vue', - ...(conventionComponentName && { [UI_COMPONENT_NAME]: conventionComponentName }), + [UI_COMPONENT_NAME]: conventionComponentName || undefined, ...(hasSpanStreaming && { [SENTRY_DESCRIPTION]: description }), }, // UI spans should only be created if there is an active root span (transaction) From 8e61e32371e346b36bb22884fccb9ea5866c2907 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 30 Oct 2026 13:11:46 +0100 Subject: [PATCH 053/169] test(node-integration-tests): Install single-version suite dependencies as package dependencies (#25936) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** The typesafe, Bedrock and MCP SDK v1 suites each test a single version that doesn't conflict with anything at the package root. For them, the unlocked per-call `npm install` into a tmp dir (network on every run, unpinned ranges, retries) buys nothing. Installing them once through pnpm makes them locked and faster. It's the same change as #25933 for Flue and pi-durable. For mcp v1, we do not need additional dependencies either, because the package names are actually different for v1 and v2 so we can just install them side-by-side. **How to review:** Hide whitespace. Dropping the options argument re-indents the test bodies, and the assertions are unchanged. **Unsure about:** - `pnpm dedupe` (enforced in CI) moves the shared `@aws-sdk` internals forward a few patches (`@aws-sdk/core` 3.978.1 → 3.978.7, `@smithy/core` 3.35.2 → 3.37.0), because the Bedrock client needs newer ones. - `@google/genai`'s optional `@modelcontextprotocol/sdk` peer now resolves to 1.30.0 instead of 1.32.2. - The aws-serverless unit tests and the aws-serverless, MCP and google-genai suites pass. - `tedious` stays per-suite: the suite tests both the root v19 and v20, so it is a multi-version suite. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- .../node-integration-tests/package.json | 3 + .../suites/aws-serverless/bedrock/test.ts | 16 +- .../tracing/mcp-handler-exact-once/test.ts | 5 +- .../mcp-server/manual-instrumentation/test.ts | 16 +- .../suites/tracing/mcp-server/v1/test.ts | 92 ++-- .../suites/tracing/typesafe/test.ts | 220 ++++---- pnpm-lock.yaml | 480 ++++++++++-------- 7 files changed, 436 insertions(+), 396 deletions(-) diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 691f60732c18..563b661fed20 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -27,6 +27,7 @@ "dependencies": { "@anthropic-ai/sdk": "0.133.0", "@apollo/server": "^5.5.0", + "@aws-sdk/client-bedrock-runtime": "^3.1046.0", "@aws-sdk/client-dynamodb": "^3.1046.0", "@aws-sdk/client-kinesis": "^3.1146.0", "@aws-sdk/client-lambda": "^3.1046.0", @@ -46,6 +47,7 @@ "@langchain/openai": "^0.5.0", "@mistralai/mistralai": "3.3.0", "@modelcontextprotocol/client": "^2.0.0", + "@modelcontextprotocol/sdk": "1.32.2", "@modelcontextprotocol/server": "^2.0.0", "@nestjs/common": "^11", "@nestjs/core": "^11", @@ -62,6 +64,7 @@ "@types/mongodb": "^3.6.20", "@types/mysql": "^2.15.21", "@types/pg": "^8.6.5", + "@typesafe-ai/sdk": "^0.6.0", "ai": "^4.3.16", "amqplib": "^0.10.9", "body-parser": "^2.3.0", diff --git a/dev-packages/node-integration-tests/suites/aws-serverless/bedrock/test.ts b/dev-packages/node-integration-tests/suites/aws-serverless/bedrock/test.ts index 0a86d5232552..018326729159 100644 --- a/dev-packages/node-integration-tests/suites/aws-serverless/bedrock/test.ts +++ b/dev-packages/node-integration-tests/suites/aws-serverless/bedrock/test.ts @@ -57,15 +57,9 @@ describe('awsIntegration - Bedrock', () => { cleanupChildProcesses(); }); - createEsmAndCjsTests( - __dirname, - 'scenario.mjs', - 'instrument.mjs', - (createTestRunner, test) => { - test('auto-instruments Bedrock Converse and InvokeModel', { timeout: 90_000 }, async () => { - await createTestRunner().ignore('event').expect({ span: assertBedrockSpans }).start().completed(); - }); - }, - { additionalDependencies: { '@aws-sdk/client-bedrock-runtime': '^3.1046.0' } }, - ); + createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createTestRunner, test) => { + test('auto-instruments Bedrock Converse and InvokeModel', { timeout: 90_000 }, async () => { + await createTestRunner().ignore('event').expect({ span: assertBedrockSpans }).start().completed(); + }); + }); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/mcp-handler-exact-once/test.ts b/dev-packages/node-integration-tests/suites/tracing/mcp-handler-exact-once/test.ts index 61c403f602ce..341d26feea75 100644 --- a/dev-packages/node-integration-tests/suites/tracing/mcp-handler-exact-once/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/mcp-handler-exact-once/test.ts @@ -34,9 +34,6 @@ describe.each(['v1', 'v2'])('MCP TypeScript SDK %s', sdk => { expect(root?.attributes['test.mcp.handlers_verified']).toEqual({ type: 'integer', value: 4 }); }); }, - { - additionalDependencies: sdk === 'v1' ? { '@modelcontextprotocol/sdk': '1.30.0' } : undefined, - copyPaths: ['run.cjs'], - }, + { copyPaths: ['run.cjs'] }, ); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/mcp-server/manual-instrumentation/test.ts b/dev-packages/node-integration-tests/suites/tracing/mcp-server/manual-instrumentation/test.ts index ecf2f6e2ef7c..be7a77eb0065 100644 --- a/dev-packages/node-integration-tests/suites/tracing/mcp-server/manual-instrumentation/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/mcp-server/manual-instrumentation/test.ts @@ -73,15 +73,9 @@ describe('MCP server spans (streamed, manual instrumentation)', () => { }); }); - createEsmAndCjsTests( - __dirname, - 'scenario-v1.mjs', - 'instrument.mjs', - (createTestRunner, test) => { - test('captures an MCP v1 initialize request queued before transport start once', async () => { - await createTestRunner().unordered().expect({ span: assertInitializeSpan }).start().completed(); - }); - }, - { additionalDependencies: { '@modelcontextprotocol/sdk': '1.30.0' } }, - ); + createEsmAndCjsTests(__dirname, 'scenario-v1.mjs', 'instrument.mjs', (createTestRunner, test) => { + test('captures an MCP v1 initialize request queued before transport start once', async () => { + await createTestRunner().unordered().expect({ span: assertInitializeSpan }).start().completed(); + }); + }); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/mcp-server/v1/test.ts b/dev-packages/node-integration-tests/suites/tracing/mcp-server/v1/test.ts index 178858873426..2d0e427ddb36 100644 --- a/dev-packages/node-integration-tests/suites/tracing/mcp-server/v1/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/mcp-server/v1/test.ts @@ -25,55 +25,49 @@ describe('MCP server spans (streamed, auto-instrumentation, v1)', () => { cleanupChildProcesses(); }); - createEsmAndCjsTests( - __dirname, - 'scenario.mjs', - 'instrument.mjs', - (createTestRunner, test) => { - test('auto-instruments the initialize handshake', async () => { - await createTestRunner() - .unordered() - .expect({ - span: container => { - const initialize = mcpSpan(container, 'initialize'); - expect(initialize.name).toBe('initialize'); - expect(initialize.attributes['sentry.origin']).toEqual({ - type: 'string', - value: 'auto.function.mcp_server', - }); - }, - }) - .start() - .completed(); - }); + createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createTestRunner, test) => { + test('auto-instruments the initialize handshake', async () => { + await createTestRunner() + .unordered() + .expect({ + span: container => { + const initialize = mcpSpan(container, 'initialize'); + expect(initialize.name).toBe('initialize'); + expect(initialize.attributes['sentry.origin']).toEqual({ + type: 'string', + value: 'auto.function.mcp_server', + }); + }, + }) + .start() + .completed(); + }); - test('auto-instruments a resource read', async () => { - await createTestRunner() - .unordered() - .expect({ - span: container => { - const resource = mcpSpan(container, 'resources/read'); - expect(resource.name).toBe('resources/read'); - expect(resource.attributes['mcp.resource.uri']?.value).toBe('echo://foobar'); - }, - }) - .start() - .completed(); - }); + test('auto-instruments a resource read', async () => { + await createTestRunner() + .unordered() + .expect({ + span: container => { + const resource = mcpSpan(container, 'resources/read'); + expect(resource.name).toBe('resources/read'); + expect(resource.attributes['mcp.resource.uri']?.value).toBe('echo://foobar'); + }, + }) + .start() + .completed(); + }); - test('auto-instruments a tool call', async () => { - await createTestRunner() - .unordered() - .expect({ - span: container => { - const tool = mcpSpan(container, 'tools/call'); - expect(tool.name).toBe('tools/call echo'); - }, - }) - .start() - .completed(); - }); - }, - { additionalDependencies: { '@modelcontextprotocol/sdk': '1.30.0' } }, - ); + test('auto-instruments a tool call', async () => { + await createTestRunner() + .unordered() + .expect({ + span: container => { + const tool = mcpSpan(container, 'tools/call'); + expect(tool.name).toBe('tools/call echo'); + }, + }) + .start() + .completed(); + }); + }); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/typesafe/test.ts b/dev-packages/node-integration-tests/suites/tracing/typesafe/test.ts index 0640f7282c7d..ab0b4b68bae2 100644 --- a/dev-packages/node-integration-tests/suites/tracing/typesafe/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/typesafe/test.ts @@ -24,135 +24,113 @@ describe('TypeSafe integration', () => { ['automatic', 'scenario.mjs', 'instrument.mjs', 'instrument-no-recording.mjs'], ['manual', 'scenario-manual.mjs', 'instrument-manual.mjs', 'instrument-manual-no-recording.mjs'], ])('%s instrumentation', (_, scenarioFile, instrumentFile, noRecordingInstrumentFile) => { - createEsmAndCjsTests( - __dirname, - scenarioFile, - instrumentFile, - (createRunner, test) => { - test('creates decide spans for systemOne', async () => { - await createRunner() - .unordered() - .expect({ - span: container => { - const decideSpans = container.items.filter(span => span.attributes[SENTRY_OP]?.value === GEN_AI_DECIDE); - expect(decideSpans).toHaveLength(4); - for (const span of decideSpans) { - expect(span.attributes[SENTRY_ORIGIN]?.value).toBe('auto.ai.typesafe'); - expect(span.attributes[GEN_AI_OPERATION_NAME]?.value).toBe('decide'); - expect(span.attributes[GEN_AI_PROVIDER_NAME]?.value).toBe('typesafe'); - } + createEsmAndCjsTests(__dirname, scenarioFile, instrumentFile, (createRunner, test) => { + test('creates decide spans for systemOne', async () => { + await createRunner() + .unordered() + .expect({ + span: container => { + const decideSpans = container.items.filter(span => span.attributes[SENTRY_OP]?.value === GEN_AI_DECIDE); + expect(decideSpans).toHaveLength(4); + for (const span of decideSpans) { + expect(span.attributes[SENTRY_ORIGIN]?.value).toBe('auto.ai.typesafe'); + expect(span.attributes[GEN_AI_OPERATION_NAME]?.value).toBe('decide'); + expect(span.attributes[GEN_AI_PROVIDER_NAME]?.value).toBe('typesafe'); + } - const defaultModelSpan = decideSpans.find(span => span.name === 'decide jev-latest')!; - expect(defaultModelSpan).toBeDefined(); - expect(defaultModelSpan.status).toBe('ok'); - expect(defaultModelSpan.attributes[GEN_AI_REQUEST_MODEL]?.value).toBe('jev-latest'); - expect(defaultModelSpan.attributes[GEN_AI_RESPONSE_MODEL]?.value).toBe('jev-1.13.0'); - expect(defaultModelSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(275); - expect(defaultModelSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(20); - expect(defaultModelSpan.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBe(295); - expect(JSON.parse(defaultModelSpan.attributes[GEN_AI_INPUT_MESSAGES]?.value as string)).toEqual([ - { - type: 'evaluation', - state: 'I cannot log in, and I also want a refund for last month.', - questions: { - authIssue: { type: 'noul', instructions: 'Is there a login problem?' }, - department: { - type: 'choice', - instructions: 'Which team should handle this?', - criteria: { billing: 'Charges and refunds', technical: 'Bugs and outages' }, - }, - urgency: { - type: 'score', - instructions: 'How urgent is this ticket?', - criteria: ['low', 'medium', 'high'], - }, + const defaultModelSpan = decideSpans.find(span => span.name === 'decide jev-latest')!; + expect(defaultModelSpan).toBeDefined(); + expect(defaultModelSpan.status).toBe('ok'); + expect(defaultModelSpan.attributes[GEN_AI_REQUEST_MODEL]?.value).toBe('jev-latest'); + expect(defaultModelSpan.attributes[GEN_AI_RESPONSE_MODEL]?.value).toBe('jev-1.13.0'); + expect(defaultModelSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBe(275); + expect(defaultModelSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBe(20); + expect(defaultModelSpan.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBe(295); + expect(JSON.parse(defaultModelSpan.attributes[GEN_AI_INPUT_MESSAGES]?.value as string)).toEqual([ + { + type: 'evaluation', + state: 'I cannot log in, and I also want a refund for last month.', + questions: { + authIssue: { type: 'noul', instructions: 'Is there a login problem?' }, + department: { + type: 'choice', + instructions: 'Which team should handle this?', + criteria: { billing: 'Charges and refunds', technical: 'Bugs and outages' }, + }, + urgency: { + type: 'score', + instructions: 'How urgent is this ticket?', + criteria: ['low', 'medium', 'high'], }, }, - ]); - expect(JSON.parse(defaultModelSpan.attributes[GEN_AI_OUTPUT_MESSAGES]?.value as string)).toEqual([ - { - type: 'evaluation', - answers: { - authIssue: { type: 'noul', noul: 0.98 }, - department: { - type: 'choice', - choice: 'billing', - confidence: 0.28, - probabilities: { billing: 0.64, technical: 0.36 }, - }, - urgency: { - type: 'score', - score: 1.58, - confidence: 0.9, - legend: { 0: 'low', 1: 'medium', 2: 'high' }, - probabilities: { 0: 0, 1: 0.42, 2: 0.58 }, - }, + }, + ]); + expect(JSON.parse(defaultModelSpan.attributes[GEN_AI_OUTPUT_MESSAGES]?.value as string)).toEqual([ + { + type: 'evaluation', + answers: { + authIssue: { type: 'noul', noul: 0.98 }, + department: { + type: 'choice', + choice: 'billing', + confidence: 0.28, + probabilities: { billing: 0.64, technical: 0.36 }, + }, + urgency: { + type: 'score', + score: 1.58, + confidence: 0.9, + legend: { 0: 'low', 1: 'medium', 2: 'high' }, + probabilities: { 0: 0, 1: 0.42, 2: 0.58 }, }, }, - ]); + }, + ]); - const asResponseSpan = decideSpans.find(span => span.name === 'decide jev-1.13')!; - expect(asResponseSpan).toBeDefined(); - expect(asResponseSpan.status).toBe('ok'); - expect(asResponseSpan.attributes[GEN_AI_RESPONSE_MODEL]?.value).toBe('jev-1.13.0'); + const asResponseSpan = decideSpans.find(span => span.name === 'decide jev-1.13')!; + expect(asResponseSpan).toBeDefined(); + expect(asResponseSpan.status).toBe('ok'); + expect(asResponseSpan.attributes[GEN_AI_RESPONSE_MODEL]?.value).toBe('jev-1.13.0'); - const errorSpan = decideSpans.find(span => span.name === 'decide error-model')!; - expect(errorSpan).toBeDefined(); - expect(errorSpan.status).toBe('error'); - expect(errorSpan.attributes[GEN_AI_RESPONSE_MODEL]).toBeUndefined(); - expect(errorSpan.attributes[GEN_AI_OUTPUT_MESSAGES]).toBeUndefined(); + const errorSpan = decideSpans.find(span => span.name === 'decide error-model')!; + expect(errorSpan).toBeDefined(); + expect(errorSpan.status).toBe('error'); + expect(errorSpan.attributes[GEN_AI_RESPONSE_MODEL]).toBeUndefined(); + expect(errorSpan.attributes[GEN_AI_OUTPUT_MESSAGES]).toBeUndefined(); - const validationErrorSpan = decideSpans.find(span => span.name === 'decide validation-error')!; - expect(validationErrorSpan).toBeDefined(); - expect(validationErrorSpan.status).toBe('error'); - expect(validationErrorSpan.attributes[GEN_AI_OUTPUT_MESSAGES]).toBeUndefined(); - }, - }) - .start() - .completed(); - }); - }, - { - additionalDependencies: { - '@typesafe-ai/sdk': '^0.6.0', - }, - }, - ); + const validationErrorSpan = decideSpans.find(span => span.name === 'decide validation-error')!; + expect(validationErrorSpan).toBeDefined(); + expect(validationErrorSpan.status).toBe('error'); + expect(validationErrorSpan.attributes[GEN_AI_OUTPUT_MESSAGES]).toBeUndefined(); + }, + }) + .start() + .completed(); + }); + }); - createEsmAndCjsTests( - __dirname, - scenarioFile, - noRecordingInstrumentFile, - (createRunner, test) => { - test('does not record inputs or outputs when recording is off', async () => { - await createRunner() - .unordered() - .expect({ - span: container => { - const decideSpans = container.items.filter(span => span.attributes[SENTRY_OP]?.value === GEN_AI_DECIDE); - expect(decideSpans).toHaveLength(4); - expect(decideSpans.filter(span => span.status === 'error')).toHaveLength(2); - for (const span of decideSpans) { - expect(span.attributes[GEN_AI_INPUT_MESSAGES]).toBeUndefined(); - expect(span.attributes[GEN_AI_OUTPUT_MESSAGES]).toBeUndefined(); - // State, questions and answers must not come back through another attribute (e.g. an error message). - // Only the attributes are checked (timestamps could match a number), and `probabilities` only - // occurs in answers. - expect(JSON.stringify(span.attributes)).not.toMatch( - /cannot log in|Charges and refunds|probabilities/, - ); - } - }, - }) - .start() - .completed(); - }); - }, - { - additionalDependencies: { - '@typesafe-ai/sdk': '^0.6.0', - }, - }, - ); + createEsmAndCjsTests(__dirname, scenarioFile, noRecordingInstrumentFile, (createRunner, test) => { + test('does not record inputs or outputs when recording is off', async () => { + await createRunner() + .unordered() + .expect({ + span: container => { + const decideSpans = container.items.filter(span => span.attributes[SENTRY_OP]?.value === GEN_AI_DECIDE); + expect(decideSpans).toHaveLength(4); + expect(decideSpans.filter(span => span.status === 'error')).toHaveLength(2); + for (const span of decideSpans) { + expect(span.attributes[GEN_AI_INPUT_MESSAGES]).toBeUndefined(); + expect(span.attributes[GEN_AI_OUTPUT_MESSAGES]).toBeUndefined(); + // State, questions and answers must not come back through another attribute (e.g. an error message). + // Only the attributes are checked (timestamps could match a number), and `probabilities` only + // occurs in answers. + expect(JSON.stringify(span.attributes)).not.toMatch(/cannot log in|Charges and refunds|probabilities/); + } + }, + }) + .start() + .completed(); + }); + }); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a44921bc39b..f0fecf4b4ca2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -462,7 +462,7 @@ importers: version: 1.4.21(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@4.6.7) '@langchain/openai': specifier: ^1.6.2 - version: 1.6.2(@aws-sdk/credential-provider-node@3.972.84)(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3) + version: 1.6.2(@aws-sdk/credential-provider-node@3.972.90)(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3) '@langchain/typesafe': specifier: ^0.0.2 version: 0.0.2(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(langchain@1.5.16(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ws@8.21.3)) @@ -580,6 +580,9 @@ importers: '@apollo/server': specifier: ^5.5.0 version: 5.5.0(graphql@16.12.0)(supports-color@10.0.0) + '@aws-sdk/client-bedrock-runtime': + specifier: ^3.1046.0 + version: 3.1159.0 '@aws-sdk/client-dynamodb': specifier: ^3.1046.0 version: 3.1068.0 @@ -637,6 +640,9 @@ importers: '@modelcontextprotocol/client': specifier: ^2.0.0 version: 2.0.0 + '@modelcontextprotocol/sdk': + specifier: 1.32.2 + version: 1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7) '@modelcontextprotocol/server': specifier: ^2.0.0 version: 2.0.0 @@ -685,6 +691,9 @@ importers: '@types/pg': specifier: ^8.6.5 version: 8.20.0 + '@typesafe-ai/sdk': + specifier: ^0.6.0 + version: 0.6.0 ai: specifier: ^4.3.16 version: 4.3.16(react@18.3.1)(zod@4.6.7) @@ -2946,6 +2955,10 @@ packages: resolution: {integrity: sha512-zOXUUnilC6lgCsQtp77p/QNPmRlTES9Xi6tlDwbR6kfC/kz5PCzZckgHWm5z+8DskdwuMAbFDq61x3zr10GEEQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/client-bedrock-runtime@3.1159.0': + resolution: {integrity: sha512-Zj744tR9wNs+jjvb7C8zDBDxjJhGJSMwIHnOc9zdsDdrRirai4CtR5iQ5BMP1qfazjH6jYKCxGpZC1BOUnslKg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/client-cognito-identity@3.980.0': resolution: {integrity: sha512-nLgMW2drTzv+dTo3ORCcotQPcrUaTQ+xoaDTdSaUXdZO7zbbVyk7ysE5GDTnJdZWcUjHOSB8xfNQhOTTNVPhFw==} engines: {node: '>=20.0.0'} @@ -2986,44 +2999,44 @@ packages: resolution: {integrity: sha512-hatCfVf61RsSO5Qjrf7JKln4KiPDprXNJhpx9DNOmyeW1v+i7KNhbawk4d1wAnQ72YciOfhAhyRFec6UZqcnzQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.978.1': - resolution: {integrity: sha512-LbY9aGsEiznDWmUc30Nwv3aIX/+dbwTx8KfS0yOC3NPYMO+O91e6jkT1azf34FwjOndq8/Q+RcVVZz5xnerwdg==} + '@aws-sdk/core@3.978.7': + resolution: {integrity: sha512-QsYdn5xhvEBzcDC6bgKuvPv87kFtXK/3f5NqKfR+eok5AAn7RaZnkgBKncX+UW3ZMcjSELyAMATN0iqgZmvWVg==} engines: {node: '>=20.0.0'} '@aws-sdk/credential-provider-cognito-identity@3.972.3': resolution: {integrity: sha512-dW/DqTk90XW7hIngqntAVtJJyrkS51wcLhGz39lOMe0TlSmZl+5R/UGnAZqNbXmWuJHLzxe+MLgagxH41aTsAQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.72': - resolution: {integrity: sha512-xTKO/FWJPozTIXbozVnVGoNBhaGba8TBcx+KyUjRVeOlXE+dUc7GTR1cLvu0uTdIdmemzaFbqqCshXeZA1fZew==} + '@aws-sdk/credential-provider-env@3.972.78': + resolution: {integrity: sha512-d8V47OxkM/FKe7bVfk5Rga4aNGUaQNk3ip1PFIYhxtup9r8SuNIHLs4HfBAmHT25ZbzowluMHp833UXfFMQbhA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.74': - resolution: {integrity: sha512-u91E/hT8f4d1xy0Jl7VG4nVKJ3lxbrZkoBTeSVoJdWBiSEUMwMS/9+e0H/aJVQV//Lt5wuzP+E69v4aRSsNTmw==} + '@aws-sdk/credential-provider-http@3.972.80': + resolution: {integrity: sha512-OQqanXiRhnl/wBIJc3TtA7TYmnN7VO0mP95eVoHsLXtPwHz6HeccMpi7cqtnhHw+K7p6VIoAt2IK4M9Ixd9uGg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.973.17': - resolution: {integrity: sha512-ged4KXdBkvIC81bLvNHHuQKdKak/VXhQTR1NWYTTqW0474nlmsxy9O/vlgTIohDDWH3xpBdtVMZRyjb+DnocDA==} + '@aws-sdk/credential-provider-ini@3.973.23': + resolution: {integrity: sha512-pTTRK7xoi8NNbrcjVUSd9YdMTUxiWEPfk8newBQCzV+VS5STh5sDPAy3iO29fSqipqNuCpILJcD6wAawxHvG+w==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.79': - resolution: {integrity: sha512-L+Z85anONJd8MaiuraO4wRxATCdEejBZ3K3eymzWI5JPXa9sOS9CkIm72PBKqXKX+Z9p9NGMX5AIMXm0LEflgw==} + '@aws-sdk/credential-provider-login@3.972.85': + resolution: {integrity: sha512-UHjnTeS+5OBFfRL+BQwCGJPJvnzjhGD6dIwncBJDa+ziyQjPUte9Q0I4BCkybGrHotnyiiluXj13bgelP14J5Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.84': - resolution: {integrity: sha512-oHt854odINVwzwsh+c5x69j0ajm4DbqqqVJ+O1ECsCIZeMDAbzFpXItaqP7UZstJj/ATdTk/KFSH0LaNAgV+kA==} + '@aws-sdk/credential-provider-node@3.972.90': + resolution: {integrity: sha512-+McgOQuAUze95AumVmoOBRp++Z8IjsZ0qOASb6+igHdKNzA0FiIf5kbTFaZ64txfd9KTBDpgvlEDGQE41lSq/A==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.72': - resolution: {integrity: sha512-rLIp2xbMjX/k9/od7APpqq1ZgXXnV0pOL1Th3ZsL8Wu0TRtBsDTVS8iPqcfRFcHakFxPvR04OSTv2ka2qOb/2A==} + '@aws-sdk/credential-provider-process@3.972.78': + resolution: {integrity: sha512-bzB92SriflYf6LVRJcDv/a9I87tmkdmdE8hAqT1oRGKFkEH2rxxHeHZVK/4vsiRsFi44cUj0J9+bUSmqYX+26w==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.973.16': - resolution: {integrity: sha512-IGihaJfFZYacJJr/odqILCoK7W/mvrZ7cuK7ECn3sAu4vLC6u0V8bS7mCGbdugJ8Aum2tnvqmx0F2MRFp2rn9g==} + '@aws-sdk/credential-provider-sso@3.973.22': + resolution: {integrity: sha512-MXUkWHgcf6G+TowL+QPvn4kJTEYKd9qHKMV6eNGXSA5lilDgGRrP3EXPHChZ3eZ7z+oprn/ZvOovLoS2WR3D8g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.78': - resolution: {integrity: sha512-/y9WvNtlcPBGLR0qc1a+9J/xtYZfVczvLUOuXaVWylzttH7ewsxwHtjmiJSolNrVSDorIxHGHMU61CbonRkmwA==} + '@aws-sdk/credential-provider-web-identity@3.972.84': + resolution: {integrity: sha512-6I3zYcDHt5ZM0J4gEuRgCHgN505k5haUiiPM46iIQgrHm2p70zpnssHFVYqHYfgJe3Mf+a5+nsF3BZmW4xcZNg==} engines: {node: '>=20.0.0'} '@aws-sdk/credential-providers@3.983.0': @@ -3038,10 +3051,18 @@ packages: resolution: {integrity: sha512-LkwS3ZOUNL5kHzmz3dDx8lE3HOhZmf2VGjbJ/tMUZJYWWl3J0RJTZM7RFz1MLt06WDVvlShcAjY/RzhYlqLL7g==} engines: {node: '>=20.0.0'} + '@aws-sdk/eventstream-handler-node@3.972.39': + resolution: {integrity: sha512-V+AvQ+KFWApgm9134igy78cgoCEAFfV8K3KmlVi511vjkRWAgvy0tGUePrZsER+9EvrmUWEsrA2NBQspiGVBMQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-endpoint-discovery@3.972.18': resolution: {integrity: sha512-1vKJt/6MBB/MBMRM3qzCMdW70syJY8u2DH+dq7yCnPn7wVJmyeAzAa/sK1lIbbYh8BVLbM5FspsT4zbe885gOw==} engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-eventstream@3.972.35': + resolution: {integrity: sha512-JAyjfoYxHCXNnCXtbTviEpb1+gxe8tsWQ3URwZIT1Bqhrm4fgDDuCA72n4HVnHMNutBhZLXnYgc97ehkhfGpMA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-flexible-checksums@3.974.30': resolution: {integrity: sha512-OaIhub+3yTgfFWPzKO8OzOZFIMUoJaiS5v67y3spQg7SoULGoMx4jKVBbE+uhnzkiZXQ+rEDS0RqrK4/aD1yJw==} engines: {node: '>=20.0.0'} @@ -3070,28 +3091,32 @@ packages: resolution: {integrity: sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==} engines: {node: '>=20.0.0'} + '@aws-sdk/middleware-websocket@3.972.60': + resolution: {integrity: sha512-IgxzXK1c0r/2yjC/qJc06OZPzQ3FSxuIc/Au6chqMb0H60k1+sHIOgjcqsxl1BxUTGQr//BU5A3niytx0IvICA==} + engines: {node: '>= 14.0.0'} + '@aws-sdk/nested-clients@3.983.0': resolution: {integrity: sha512-4bUzDkJlSPwfegO23ZSBrheuTI8UyAgNzptm1K6fZAIOIc1vnFl12TonecbssAfmM0/UdyTn5QDomwEfIdmJkQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.46': - resolution: {integrity: sha512-oRxtBcka/JGHGs9l9p9IVajGoTP8vTPmoAzdHGy4Qcy9P5vPnDf6nhIeM/COQNY9k/OahImTRaLkHftoXvfcmQ==} + '@aws-sdk/nested-clients@3.997.52': + resolution: {integrity: sha512-/bNl6b3VEoWQg3Z8yHt3gzYjSFoOOv7+eUglzITmGP8H7jovN8c9NoOI+6bU25+lSLvyRpJOs4n8YSaX+BcUPg==} engines: {node: '>=20.0.0'} '@aws-sdk/region-config-resolver@3.972.13': resolution: {integrity: sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.47': - resolution: {integrity: sha512-Zk08macMvQTHzQJCLJVkOlviVoqwYMrpXv4lmLN7b7sAbiMoOK7Go0NYdR5UeF+MW8LIbRmwrNy9u/5VvX1U5g==} + '@aws-sdk/signature-v4-multi-region@3.996.50': + resolution: {integrity: sha512-Ob9GCZ3YnGPws696zTYW/rLFYHa9QLh6DgTCnkHb+0jhHNBgHWM8Ui2b+yrIjkfDsRVsglyrOey6tOOG/VWxMA==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1138.0': - resolution: {integrity: sha512-GpyAr0DD63YOEmYFM6Df+gJuIgC92MMTiBK4FTKfxii5MJ9ge20epR7LyroulscYlG89J+ZB2ivFDPjvfQhzdw==} + '@aws-sdk/token-providers@3.1159.0': + resolution: {integrity: sha512-IJvghXXcEpqTWGjrjJwxBdEecHeXp+t9FaybRf8Zf0wYW84KN1snXBsYOdfDBBx78ChwfJxH8KIHqmhNZuDLcw==} engines: {node: '>=20.0.0'} - '@aws-sdk/types@3.974.6': - resolution: {integrity: sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==} + '@aws-sdk/types@3.975.0': + resolution: {integrity: sha512-WQlhs6juByzWOzj5fwxOx0mPJaEqMcG/sDpl3Id61B3XhKE63xY+0QWcnG+sWR/sCfprmreyUo1lM7IKZmHMzw==} engines: {node: '>=20.0.0'} '@aws-sdk/util-endpoints@3.980.0': @@ -3122,8 +3147,8 @@ packages: aws-crt: optional: true - '@aws-sdk/xml-builder@3.972.41': - resolution: {integrity: sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==} + '@aws-sdk/xml-builder@3.972.42': + resolution: {integrity: sha512-smX8/gNP8Kjq+CWTBMZQLQ912vOQjiWExzwzvMO+eujPB5RY8IbtF0AGdUUXHKN7KoDWK9dIcuW0WG269kiP6w==} engines: {node: '>=20.0.0'} '@aws/lambda-invoke-store@0.2.3': @@ -8326,8 +8351,8 @@ packages: resolution: {integrity: sha512-TzDZcAnhTyAHbXVxWZo7/tEcrIeFq20IBk8So3OLOetWpR8EwY/yEqBMBFaJMeyEiREDq4NfEl+qO3OAUD+vbQ==} engines: {node: '>=18.0.0'} - '@smithy/core@3.35.2': - resolution: {integrity: sha512-ejTukBuP/Pg3t9N8Ux+0zKsXeHWm7p6W3HbXFOyltKasSxZwCFmvTut7kBdZSCF3m9y1bXXyVu6p2xuMFcsTyQ==} + '@smithy/core@3.37.0': + resolution: {integrity: sha512-97imTcCyiPd8k0e9+ts/Sp0W65Dn9Ti357J7M09Ov4tI8Q1JDJVFSoAJtjEVKqfHEr3Pdo2jfolqKCaaiszH0g==} engines: {node: '>=18.0.0'} '@smithy/credential-provider-imds@4.5.2': @@ -8382,6 +8407,10 @@ packages: resolution: {integrity: sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==} engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.13.0': + resolution: {integrity: sha512-/GGcPoNJ2nzGytdAanpbx7mpp9Coa8LP+/Rpmbktr8YeLuIXs9B0SEz+X3AeonHdyzOIFJdKbN7jOZ+Tj+pMGw==} + engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.2.14': resolution: {integrity: sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==} engines: {node: '>=18.0.0'} @@ -9075,6 +9104,10 @@ packages: '@types/yargs@15.0.19': resolution: {integrity: sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==} + '@typesafe-ai/sdk@0.6.0': + resolution: {integrity: sha512-IddX+Q0XM+VagOUZFeP7wZjaO4SHMdvnh2zEBdrZZnXedWI3BNK1lKhMx3ayrkFWvVLbVcUHJy6AVZlY+e6Jaw==} + engines: {node: '>=20'} + '@typescript-eslint/eslint-plugin@5.62.0': resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -20210,20 +20243,20 @@ snapshots: '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 tslib: 2.8.1 '@aws-crypto/sha1-browser@5.2.0': dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@aws-sdk/util-locate-window': 3.535.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -20233,7 +20266,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@aws-sdk/util-locate-window': 3.535.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -20241,7 +20274,7 @@ snapshots: '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 tslib: 2.8.1 '@aws-crypto/supports-web-crypto@5.2.0': @@ -20250,7 +20283,7 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -20259,9 +20292,24 @@ snapshots: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/client-bedrock-runtime@3.1159.0': + dependencies: + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/eventstream-handler-node': 3.972.39 + '@aws-sdk/middleware-eventstream': 3.972.35 + '@aws-sdk/middleware-websocket': 3.972.60 + '@aws-sdk/token-providers': 3.1159.0 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.13.0 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -20269,19 +20317,19 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 '@aws-sdk/middleware-user-agent': 3.972.38 '@aws-sdk/region-config-resolver': 3.972.13 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@aws-sdk/util-endpoints': 3.980.0 '@aws-sdk/util-user-agent-browser': 3.972.10 '@aws-sdk/util-user-agent-node': 3.973.24 '@smithy/config-resolver': 4.4.17 - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/hash-node': 4.2.14 '@smithy/invalid-dependency': 4.2.14 @@ -20314,19 +20362,19 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 '@aws-sdk/middleware-user-agent': 3.972.38 '@aws-sdk/region-config-resolver': 3.972.13 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@aws-sdk/util-endpoints': 3.983.0 '@aws-sdk/util-user-agent-browser': 3.972.10 '@aws-sdk/util-user-agent-node': 3.973.24 '@smithy/config-resolver': 4.4.17 - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/hash-node': 4.2.14 '@smithy/invalid-dependency': 4.2.14 @@ -20359,12 +20407,12 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 '@aws-sdk/dynamodb-codec': 3.973.20 '@aws-sdk/middleware-endpoint-discovery': 3.972.18 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 @@ -20372,10 +20420,10 @@ snapshots: '@aws-sdk/client-kinesis@3.1146.0': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 @@ -20385,10 +20433,10 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 @@ -20399,13 +20447,13 @@ snapshots: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 '@aws-sdk/middleware-flexible-checksums': 3.974.30 '@aws-sdk/middleware-sdk-s3': 3.972.51 - '@aws-sdk/signature-v4-multi-region': 3.996.47 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/signature-v4-multi-region': 3.996.50 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 @@ -20415,10 +20463,10 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 @@ -20428,10 +20476,10 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 @@ -20441,10 +20489,10 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 @@ -20454,22 +20502,22 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 '@aws-sdk/middleware-sdk-sqs': 3.972.30 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/node-http-handler': 4.12.1 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/core@3.978.1': + '@aws-sdk/core@3.978.7': dependencies: - '@aws-sdk/types': 3.974.6 - '@aws-sdk/xml-builder': 3.972.41 + '@aws-sdk/types': 3.975.0 + '@aws-sdk/xml-builder': 3.972.42 '@aws/lambda-invoke-store': 0.3.0 - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/signature-v4': 5.7.4 '@smithy/types': 4.19.0 bowser: 2.11.0 @@ -20478,7 +20526,7 @@ snapshots: '@aws-sdk/credential-provider-cognito-identity@3.972.3': dependencies: '@aws-sdk/client-cognito-identity': 3.980.0 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/property-provider': 4.2.14 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -20486,107 +20534,107 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-env@3.972.72': + '@aws-sdk/credential-provider-env@3.972.78': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.74': + '@aws-sdk/credential-provider-http@3.972.80': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 - '@smithy/node-http-handler': 4.12.1 + '@smithy/node-http-handler': 4.13.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.973.17': - dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/credential-provider-env': 3.972.72 - '@aws-sdk/credential-provider-http': 3.972.74 - '@aws-sdk/credential-provider-login': 3.972.79 - '@aws-sdk/credential-provider-process': 3.972.72 - '@aws-sdk/credential-provider-sso': 3.973.16 - '@aws-sdk/credential-provider-web-identity': 3.972.78 - '@aws-sdk/nested-clients': 3.997.46 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/credential-provider-ini@3.973.23': + dependencies: + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-env': 3.972.78 + '@aws-sdk/credential-provider-http': 3.972.80 + '@aws-sdk/credential-provider-login': 3.972.85 + '@aws-sdk/credential-provider-process': 3.972.78 + '@aws-sdk/credential-provider-sso': 3.973.22 + '@aws-sdk/credential-provider-web-identity': 3.972.84 + '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/credential-provider-imds': 4.5.2 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-login@3.972.79': + '@aws-sdk/credential-provider-login@3.972.85': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/nested-clients': 3.997.46 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-node@3.972.84': + '@aws-sdk/credential-provider-node@3.972.90': dependencies: - '@aws-sdk/credential-provider-env': 3.972.72 - '@aws-sdk/credential-provider-http': 3.972.74 - '@aws-sdk/credential-provider-ini': 3.973.17 - '@aws-sdk/credential-provider-process': 3.972.72 - '@aws-sdk/credential-provider-sso': 3.973.16 - '@aws-sdk/credential-provider-web-identity': 3.972.78 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/credential-provider-env': 3.972.78 + '@aws-sdk/credential-provider-http': 3.972.80 + '@aws-sdk/credential-provider-ini': 3.973.23 + '@aws-sdk/credential-provider-process': 3.972.78 + '@aws-sdk/credential-provider-sso': 3.973.22 + '@aws-sdk/credential-provider-web-identity': 3.972.84 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/credential-provider-imds': 4.5.2 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-process@3.972.72': + '@aws-sdk/credential-provider-process@3.972.78': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.973.16': + '@aws-sdk/credential-provider-sso@3.973.22': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/nested-clients': 3.997.46 - '@aws-sdk/token-providers': 3.1138.0 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/token-providers': 3.1159.0 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.972.78': + '@aws-sdk/credential-provider-web-identity@3.972.84': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/nested-clients': 3.997.46 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 '@aws-sdk/credential-providers@3.983.0': dependencies: '@aws-sdk/client-cognito-identity': 3.983.0 - '@aws-sdk/core': 3.978.1 + '@aws-sdk/core': 3.978.7 '@aws-sdk/credential-provider-cognito-identity': 3.972.3 - '@aws-sdk/credential-provider-env': 3.972.72 - '@aws-sdk/credential-provider-http': 3.972.74 - '@aws-sdk/credential-provider-ini': 3.973.17 - '@aws-sdk/credential-provider-login': 3.972.79 - '@aws-sdk/credential-provider-node': 3.972.84 - '@aws-sdk/credential-provider-process': 3.972.72 - '@aws-sdk/credential-provider-sso': 3.973.16 - '@aws-sdk/credential-provider-web-identity': 3.972.78 + '@aws-sdk/credential-provider-env': 3.972.78 + '@aws-sdk/credential-provider-http': 3.972.80 + '@aws-sdk/credential-provider-ini': 3.973.23 + '@aws-sdk/credential-provider-login': 3.972.85 + '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/credential-provider-process': 3.972.78 + '@aws-sdk/credential-provider-sso': 3.973.22 + '@aws-sdk/credential-provider-web-identity': 3.972.84 '@aws-sdk/nested-clients': 3.983.0 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/config-resolver': 4.4.17 - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/credential-provider-imds': 4.5.2 '@smithy/node-config-provider': 4.3.14 '@smithy/property-provider': 4.2.14 @@ -20598,8 +20646,8 @@ snapshots: '@aws-sdk/dynamodb-codec@3.973.20': dependencies: - '@aws-sdk/core': 3.978.1 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -20608,11 +20656,25 @@ snapshots: mnemonist: 0.38.3 tslib: 2.8.1 + '@aws-sdk/eventstream-handler-node@3.972.39': + dependencies: + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + '@aws-sdk/middleware-endpoint-discovery@3.972.18': dependencies: '@aws-sdk/endpoint-cache': 3.972.7 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-eventstream@3.972.35': + dependencies: + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -20623,7 +20685,7 @@ snapshots: '@aws-sdk/middleware-host-header@3.972.10': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -20631,14 +20693,14 @@ snapshots: '@aws-sdk/middleware-logger@3.972.10': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/types': 4.19.0 tslib: 2.8.1 optional: true '@aws-sdk/middleware-recursion-detection@3.972.11': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@aws/lambda-invoke-store': 0.2.3 '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.19.0 @@ -20647,48 +20709,58 @@ snapshots: '@aws-sdk/middleware-sdk-s3@3.972.51': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/signature-v4-multi-region': 3.996.47 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/signature-v4-multi-region': 3.996.50 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 '@aws-sdk/middleware-sdk-sqs@3.972.30': dependencies: - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 '@aws-sdk/middleware-user-agent@3.972.38': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/types': 3.975.0 '@aws-sdk/util-endpoints': 3.996.8 - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.19.0 '@smithy/util-retry': 4.3.8 tslib: 2.8.1 optional: true + '@aws-sdk/middleware-websocket@3.972.60': + dependencies: + '@aws-sdk/core': 3.978.7 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/signature-v4': 5.7.4 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + '@aws-sdk/nested-clients@3.983.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.1 + '@aws-sdk/core': 3.978.7 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 '@aws-sdk/middleware-user-agent': 3.972.38 '@aws-sdk/region-config-resolver': 3.972.13 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@aws-sdk/util-endpoints': 3.983.0 '@aws-sdk/util-user-agent-browser': 3.972.10 '@aws-sdk/util-user-agent-node': 3.973.24 '@smithy/config-resolver': 4.4.17 - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 '@smithy/hash-node': 4.2.14 '@smithy/invalid-dependency': 4.2.14 @@ -20717,50 +20789,50 @@ snapshots: - aws-crt optional: true - '@aws-sdk/nested-clients@3.997.46': + '@aws-sdk/nested-clients@3.997.52': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/signature-v4-multi-region': 3.996.47 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/signature-v4-multi-region': 3.996.50 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 - '@smithy/node-http-handler': 4.12.1 + '@smithy/node-http-handler': 4.13.0 '@smithy/types': 4.19.0 tslib: 2.8.1 '@aws-sdk/region-config-resolver@3.972.13': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/config-resolver': 4.4.17 '@smithy/node-config-provider': 4.3.14 '@smithy/types': 4.19.0 tslib: 2.8.1 optional: true - '@aws-sdk/signature-v4-multi-region@3.996.47': + '@aws-sdk/signature-v4-multi-region@3.996.50': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/signature-v4': 5.7.4 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1138.0': + '@aws-sdk/token-providers@3.1159.0': dependencies: - '@aws-sdk/core': 3.978.1 - '@aws-sdk/nested-clients': 3.997.46 - '@aws-sdk/types': 3.974.6 - '@smithy/core': 3.35.2 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/types@3.974.6': + '@aws-sdk/types@3.975.0': dependencies: '@smithy/types': 4.19.0 tslib: 2.8.1 '@aws-sdk/util-endpoints@3.980.0': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/types': 4.19.0 '@smithy/url-parser': 4.2.14 '@smithy/util-endpoints': 3.4.2 @@ -20769,7 +20841,7 @@ snapshots: '@aws-sdk/util-endpoints@3.983.0': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/types': 4.19.0 '@smithy/url-parser': 4.2.14 '@smithy/util-endpoints': 3.4.2 @@ -20778,7 +20850,7 @@ snapshots: '@aws-sdk/util-endpoints@3.996.8': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/types': 4.19.0 '@smithy/url-parser': 4.2.14 '@smithy/util-endpoints': 3.4.2 @@ -20791,7 +20863,7 @@ snapshots: '@aws-sdk/util-user-agent-browser@3.972.10': dependencies: - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/types': 4.19.0 bowser: 2.11.0 tslib: 2.8.1 @@ -20800,14 +20872,14 @@ snapshots: '@aws-sdk/util-user-agent-node@3.973.24': dependencies: '@aws-sdk/middleware-user-agent': 3.972.38 - '@aws-sdk/types': 3.974.6 + '@aws-sdk/types': 3.975.0 '@smithy/node-config-provider': 4.3.14 '@smithy/types': 4.19.0 '@smithy/util-config-provider': 4.2.2 tslib: 2.8.1 optional: true - '@aws-sdk/xml-builder@3.972.41': + '@aws-sdk/xml-builder@3.972.42': dependencies: '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -24267,11 +24339,11 @@ snapshots: transitivePeerDependencies: - ws - '@langchain/openai@1.6.2(@aws-sdk/credential-provider-node@3.972.84)(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)': + '@langchain/openai@1.6.2(@aws-sdk/credential-provider-node@3.972.90)(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)': dependencies: '@langchain/core': 1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3) js-tiktoken: 1.0.21 - openai: 7.31.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + openai: 7.31.0(@aws-sdk/credential-provider-node@3.972.90)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) zod: 4.6.7 transitivePeerDependencies: - '@aws-sdk/credential-provider-node' @@ -26441,20 +26513,20 @@ snapshots: tslib: 2.8.1 optional: true - '@smithy/core@3.35.2': + '@smithy/core@3.37.0': dependencies: '@smithy/types': 4.19.0 tslib: 2.8.1 '@smithy/credential-provider-imds@4.5.2': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 '@smithy/fetch-http-handler@5.8.0': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -26490,7 +26562,7 @@ snapshots: '@smithy/middleware-endpoint@4.4.32': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/middleware-serde': 4.2.20 '@smithy/node-config-provider': 4.3.14 '@smithy/shared-ini-file-loader': 4.4.9 @@ -26502,7 +26574,7 @@ snapshots: '@smithy/middleware-retry@4.5.7': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/node-config-provider': 4.3.14 '@smithy/protocol-http': 5.3.14 '@smithy/service-error-classification': 4.3.1 @@ -26516,7 +26588,7 @@ snapshots: '@smithy/middleware-serde@4.2.20': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/protocol-http': 5.3.14 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -26538,7 +26610,13 @@ snapshots: '@smithy/node-http-handler@4.12.1': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.13.0': + dependencies: + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -26573,13 +26651,13 @@ snapshots: '@smithy/signature-v4@5.7.4': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 '@smithy/smithy-client@4.12.13': dependencies: - '@smithy/core': 3.35.2 + '@smithy/core': 3.37.0 '@smithy/middleware-endpoint': 4.4.32 '@smithy/middleware-stack': 4.2.14 '@smithy/protocol-http': 5.3.14 @@ -27477,6 +27555,8 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + '@typesafe-ai/sdk@0.6.0': {} + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0(supports-color@10.0.0))(supports-color@10.0.0)(typescript@7.0.2))(eslint@8.57.0(supports-color@10.0.0))(supports-color@10.0.0)(typescript@7.0.2)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -35817,9 +35897,9 @@ snapshots: ws: 8.21.3 zod: 4.6.7 - openai@7.31.0(@aws-sdk/credential-provider-node@3.972.84)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7): + openai@7.31.0(@aws-sdk/credential-provider-node@3.972.90)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7): optionalDependencies: - '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/credential-provider-node': 3.972.90 '@smithy/signature-v4': 5.7.4 undici: 7.24.8 ws: 8.21.3 From 8592e7ef59ad96315608992d2ba6fcf3c6c42e05 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 30 Oct 2026 13:12:55 +0100 Subject: [PATCH 054/169] test(e2e): Move OpenAI and Anthropic send-to-sentry cases into gen-ai-libraries (#25841) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** The test that sends what some ai integrations send to sentry are not really necessary. most of this is covered elsewhere already. Streaming is the one case where the real wire format matters, so only the streamed OpenAI and Anthropic chats are moved to the existing gen-ai-libraries E2E test, and they now run on Cloudflare too. In addition, an optional "latest" variant is added for this too to test all the genai libraries in their latest version. **How to review:** Start with `tests/chat-stream.test.ts`. The `[DONE]`-frame workaround in `libraries.ts` comes from the Anthropic app. Anthropic SDK 0.136+ needs it when going through OpenRouter. **Unsure about:** `@anthropic-ai/sdk` 1.x isn't instrumented yet (the orchestrion config covers `<1`), so `(latest)` pins Anthropic to the newest 0.x and every other SDK uses `@latest`. The pin can go once #25750 lands. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- .../gen-ai-libraries/package.json | 10 +- .../gen-ai-libraries/src/entry.cloudflare.ts | 19 +- .../gen-ai-libraries/src/entry.node.ts | 12 ++ .../gen-ai-libraries/src/libraries.ts | 92 +++++++++- .../tests/chat-stream.test.ts | 59 ++++++ .../node-anthropic-send-to-sentry/.gitignore | 5 - .../package.json | 36 ---- .../playwright.config.ts | 28 --- .../pnpm-workspace.yaml | 1 - .../node-anthropic-send-to-sentry/src/app.ts | 165 ----------------- .../src/instrument.ts | 10 - .../tests/send-to-sentry.test.ts | 151 --------------- .../tests/utils/sentry-api.ts | 131 ------------- .../tsconfig.json | 13 -- .../node-openai-send-to-sentry/.gitignore | 5 - .../node-openai-send-to-sentry/package.json | 38 ---- .../playwright.config.ts | 24 --- .../pnpm-workspace.yaml | 1 - .../node-openai-send-to-sentry/src/app.ts | 151 --------------- .../src/instrument.ts | 10 - .../tests/send-to-sentry.test.ts | 172 ------------------ .../node-openai-send-to-sentry/tsconfig.json | 13 -- 22 files changed, 181 insertions(+), 965 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/gen-ai-libraries/tests/chat-stream.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/.gitignore delete mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/playwright.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/pnpm-workspace.yaml delete mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/src/app.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/src/instrument.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tests/send-to-sentry.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tests/utils/sentry-api.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/.gitignore delete mode 100644 dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/playwright.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/pnpm-workspace.yaml delete mode 100644 dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/src/app.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/src/instrument.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/tests/send-to-sentry.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/package.json b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/package.json index 35f5df09e3e3..3d0ff3e11d38 100644 --- a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/package.json +++ b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/package.json @@ -1,6 +1,6 @@ { "name": "gen-ai-libraries", - "description": "Real gen_ai spans for every instrumented AI library (OpenAI, Anthropic, Mistral, Together, Vercel AI), each driven through OpenRouter with a chat query and a tool call, on Node and on Cloudflare", + "description": "Real gen_ai spans for every instrumented AI library (OpenAI, Anthropic, Mistral, Together, Vercel AI), each driven through OpenRouter with a chat query and a tool call, plus streamed OpenAI and Anthropic chats, on Node and on Cloudflare", "version": "1.0.0", "private": true, "type": "module", @@ -13,7 +13,8 @@ "test:build": "pnpm install", "test:build:cloudflare": "pnpm install && vite build", "test:assert": "pnpm test", - "test:assert:cloudflare": "RUNTIME=cloudflare pnpm test" + "test:assert:cloudflare": "RUNTIME=cloudflare pnpm test", + "test:build-latest": "pnpm install && pnpm add openai@latest together-ai@latest ai@latest @openrouter/ai-sdk-provider@latest '@anthropic-ai/sdk@<1' @mistralai/mistralai@latest" }, "dependencies": { "@anthropic-ai/sdk": "0.63.0", @@ -32,6 +33,7 @@ "@cloudflare/workers-types": "^4.20260426.0", "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", + "@sentry/conventions": "0.30.0", "@types/express": "^4.17.21", "@types/node": "^18.19.1", "tsx": "4.21.0", @@ -46,6 +48,10 @@ "build-command": "pnpm test:build:cloudflare", "assert-command": "pnpm test:assert:cloudflare", "label": "gen-ai-libraries (cloudflare)" + }, + { + "build-command": "pnpm test:build-latest", + "label": "gen-ai-libraries (latest)" } ] } diff --git a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/entry.cloudflare.ts b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/entry.cloudflare.ts index 265b8122f0df..10ddd8d13a03 100644 --- a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/entry.cloudflare.ts +++ b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/entry.cloudflare.ts @@ -1,10 +1,24 @@ // The Cloudflare variant: the same libraries and routes as the Node entry, but instrumented by the // `@sentry/cloudflare/vite` bundler plugin (build-time channel injection) and run on workerd. import * as Sentry from '@sentry/cloudflare'; +import type { Library } from './libraries'; import { libraries } from './libraries'; const byId = new Map(libraries.map(library => [library.id, library])); +function getCall(library: Library, action: string | undefined): ((apiKey: string) => Promise) | undefined { + switch (action) { + case 'chat': + return library.chat; + case 'tools': + return library.tools; + case 'chat-stream': + return library.chatStream; + default: + return undefined; + } +} + export default Sentry.withSentry( (env: Env) => ({ dsn: env.E2E_TEST_DSN, @@ -17,8 +31,9 @@ export default Sentry.withSentry( const url = new URL(request.url); const [, id, action] = url.pathname.split('/'); const library = id ? byId.get(id) : undefined; + const call = library && getCall(library, action); - if (!library || (action !== 'chat' && action !== 'tools')) { + if (!call) { return new Response('Not found', { status: 404 }); } @@ -29,7 +44,7 @@ export default Sentry.withSentry( try { const spanName = action === 'tools' ? 'ai-tool-workflow' : 'ai-workflow'; - const result = await Sentry.startSpan({ name: spanName, op: 'function' }, () => library[action](apiKey)); + const result = await Sentry.startSpan({ name: spanName, op: 'function' }, () => call(apiKey)); return Response.json({ result }); } catch (error) { return Response.json({ message: (error as Error).message }, { status: 500 }); diff --git a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/entry.node.ts b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/entry.node.ts index 726924dbd69d..95a405dfa4d0 100644 --- a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/entry.node.ts +++ b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/entry.node.ts @@ -33,6 +33,18 @@ for (const library of libraries) { next(error); } }); + + const chatStream = library.chatStream; + if (chatStream) { + app.get(`/${library.id}/chat-stream`, async (_req, res, next) => { + try { + const answer = await Sentry.startSpan({ name: 'ai-workflow', op: 'function' }, () => chatStream(apiKey)); + res.send({ answer }); + } catch (error) { + next(error); + } + }); + } } Sentry.setupExpressErrorHandler(app); diff --git a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/libraries.ts b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/libraries.ts index 1b1490e7e9b4..dd002c976962 100644 --- a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/libraries.ts +++ b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/libraries.ts @@ -60,6 +60,51 @@ export interface Library { origin?: string; chat: (apiKey: string) => Promise; tools: (apiKey: string) => Promise; + /** A streamed chat query; only set for the libraries whose streaming is tested. */ + chatStream?: (apiKey: string) => Promise; +} + +const openAiClient = (apiKey: string): OpenAI => new OpenAI({ apiKey, baseURL: OPENROUTER_V1 }); + +// OpenRouter's Anthropic skin authenticates with a bearer token, so the key goes in `authToken` +// (Authorization: Bearer) rather than `apiKey` (x-api-key). +const anthropicClient = (apiKey: string): Anthropic => + new Anthropic({ authToken: apiKey, baseURL: OPENROUTER_BASE, fetch: fetchWithoutDone }); + +/** + * OpenRouter ends each stream with an `event: data` / `data: [DONE]` frame, which is not part of + * Anthropic's protocol. Since 0.136.0 the SDK JSON-parses every named event, so this frame + * makes it throw. Drop the frame before the SDK sees it. + */ +async function fetchWithoutDone(input: string | URL | Request, init?: RequestInit): Promise { + const response = await fetch(input, init); + if (!response.body || !response.headers.get('content-type')?.includes('text/event-stream')) { + return response; + } + + let buffer = ''; + const keepFrame = (frame: string): boolean => !/^data: \[DONE\]$/m.test(frame); + const body = response.body.pipeThrough(new TextDecoderStream()).pipeThrough( + new TransformStream({ + transform(chunk, controller) { + buffer += chunk; + const frames = buffer.split(/(?<=\r?\n\r?\n)/); + buffer = /\r?\n\r?\n$/.test(frames[frames.length - 1] ?? '') ? '' : (frames.pop() ?? ''); + controller.enqueue(frames.filter(keepFrame).join('')); + }, + flush(controller) { + if (keepFrame(buffer)) { + controller.enqueue(buffer); + } + }, + }), + ); + + return new Response(body.pipeThrough(new TextEncoderStream()), { + status: response.status, + statusText: response.statusText, + headers: response.headers, + }); } /** Chat + forced tool call for an OpenAI-compatible chat-completions client (OpenAI, Together). */ @@ -100,7 +145,28 @@ function openAiCompatible( } export const libraries: Library[] = [ - openAiCompatible('openai', 'openai', 'auto.ai.openai', apiKey => new OpenAI({ apiKey, baseURL: OPENROUTER_V1 })), + { + ...openAiCompatible('openai', 'openai', 'auto.ai.openai', openAiClient), + chatStream: async apiKey => { + const stream = await openAiClient(apiKey).chat.completions.create({ + model: MODEL, + messages: [ + { role: 'system', content: SYSTEM }, + { role: 'user', content: CHAT_PROMPT }, + ], + temperature: 0, + max_tokens: 32, + stream: true, + // Ask for the usage in the final chunk, so the streamed span carries token counts. + stream_options: { include_usage: true }, + }); + let answer = ''; + for await (const chunk of stream) { + answer += chunk.choices[0]?.delta.content ?? ''; + } + return answer; + }, + }, openAiCompatible( 'together', 'together_ai', @@ -146,10 +212,7 @@ export const libraries: Library[] = [ provider: 'anthropic', origin: 'auto.ai.anthropic', chat: async apiKey => { - // OpenRouter's Anthropic skin authenticates with a bearer token, so the key goes in `authToken` - // (Authorization: Bearer) rather than `apiKey` (x-api-key). - const client = new Anthropic({ authToken: apiKey, baseURL: OPENROUTER_BASE }); - const message = await client.messages.create({ + const message = await anthropicClient(apiKey).messages.create({ model: MODEL, max_tokens: 32, temperature: 0, @@ -160,8 +223,7 @@ export const libraries: Library[] = [ return first && first.type === 'text' ? first.text : ''; }, tools: async apiKey => { - const client = new Anthropic({ authToken: apiKey, baseURL: OPENROUTER_BASE }); - const message = await client.messages.create({ + const message = await anthropicClient(apiKey).messages.create({ model: MODEL, max_tokens: 64, messages: [{ role: 'user', content: WEATHER_PROMPT }], @@ -180,6 +242,22 @@ export const libraries: Library[] = [ }); return (message.content ?? []).filter(block => block.type === 'tool_use'); }, + chatStream: async apiKey => { + const stream = await anthropicClient(apiKey).messages.create({ + model: MODEL, + max_tokens: 32, + system: SYSTEM, + messages: [{ role: 'user', content: CHAT_PROMPT }], + stream: true, + }); + let answer = ''; + for await (const event of stream) { + if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') { + answer += event.delta.text; + } + } + return answer; + }, }, { diff --git a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/tests/chat-stream.test.ts b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/tests/chat-stream.test.ts new file mode 100644 index 000000000000..2a68b3f6e5f4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/tests/chat-stream.test.ts @@ -0,0 +1,59 @@ +import { expect, test } from '@playwright/test'; +import { + GEN_AI_PROVIDER_NAME, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_USAGE_OUTPUT_TOKENS, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import type { SerializedStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpans } from '@sentry-internal/test-utils'; +import { APP } from './constants'; +import type { LibraryUnderTest } from './utils'; +import { attr, describeTree, expectCommonChatAttributes, isModelCallSpan, LIBRARIES } from './utils'; + +// Streaming is where OpenRouter's real wire format can diverge from the mocked responses in +// node-integration-tests, so it is only covered here. + +const OPENAI = LIBRARIES.find(library => library.id === 'openai')!; +const ANTHROPIC = LIBRARIES.find(library => library.id === 'anthropic')!; + +const isStreamedModelCallSpan = (span: SerializedStreamedSpan, library: LibraryUnderTest): boolean => + isModelCallSpan(span, library) && attr(span, GEN_AI_RESPONSE_STREAMING) === true; + +async function requestChatStream( + baseURL: string | undefined, + library: LibraryUnderTest, +): Promise { + const spansPromise = collectStreamedSpans(APP, spansOfTrace => + spansOfTrace.some(span => isStreamedModelCallSpan(span, library)), + ); + + const response = await fetch(`${baseURL}/${library.id}/chat-stream`); + expect(response.status, await response.text()).toBe(200); + + const spans = await spansPromise; + const modelSpan = spans.find(span => isStreamedModelCallSpan(span, library)); + expect(modelSpan, `expected a streamed ${library.op} span in:\n${describeTree(spans)}`).toBeDefined(); + return modelSpan!; +} + +test('openai: a streamed chat completion records its token usage', async ({ baseURL }) => { + const span = await requestChatStream(baseURL, OPENAI); + + // The usage only arrives in the final chunk, which the streaming instrumentation has to pick up. + expectCommonChatAttributes(span, OPENAI); + expect(attr(span, GEN_AI_RESPONSE_FINISH_REASONS)).toContain('stop'); +}); + +test('anthropic: a streamed message records its output usage', async ({ baseURL }) => { + const span = await requestChatStream(baseURL, ANTHROPIC); + + // Not `expectCommonChatAttributes`: the instrumentation takes the input usage from `message_start`, + // where OpenRouter reports 0, and ignores the real count in `message_delta`. + expect(span.status).toBe('ok'); + expect(attr(span, GEN_AI_USAGE_OUTPUT_TOKENS)).toBeGreaterThan(0); + expect(attr(span, GEN_AI_RESPONSE_FINISH_REASONS)).toContain('end_turn'); + expect(attr(span, GEN_AI_PROVIDER_NAME)).toBe(ANTHROPIC.provider); + expect(attr(span, SENTRY_ORIGIN)).toBe(ANTHROPIC.origin); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/.gitignore b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/.gitignore deleted file mode 100644 index 795f6b6d40d1..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -pnpm-lock.yaml -dist -test-results -playwright-report diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/package.json deleted file mode 100644 index 9f7d8ae4fa75..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "node-anthropic-send-to-sentry", - "description": "The Anthropic integration as a user runs it: a plain Sentry.init on an express app, real chat, streaming, stream-helper and tool-call requests through OpenRouter, and the gen_ai spans read back from a real Sentry project", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "tsc", - "start": "node --import ./dist/instrument.js dist/app.js", - "test": "playwright test", - "clean": "npx rimraf node_modules dist pnpm-lock.yaml", - "test:build": "pnpm install && pnpm build", - "test:build-latest": "pnpm install && pnpm add @anthropic-ai/sdk@latest && pnpm build", - "test:assert": "pnpm test" - }, - "dependencies": { - "@anthropic-ai/sdk": "0.63.0", - "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "express": "^4.21.2" - }, - "devDependencies": { - "@playwright/test": "1.64.1", - "@types/express": "^4.17.21", - "@types/node": "^24.0.0", - "typescript": "~5.9.0" - }, - "sentryTest": { - "optional": true, - "optionalVariants": [ - { - "build-command": "pnpm test:build-latest", - "label": "node-anthropic-send-to-sentry (latest)" - } - ] - } -} diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/playwright.config.ts deleted file mode 100644 index ef4764ce8d73..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/playwright.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { defineConfig } from '@playwright/test'; - -const port = 3030; - -export default defineConfig({ - testDir: './tests', - /* - * Spans take ~2min to become queryable via the trace endpoint, and each poll has its own 180s - * budget. The first test polls twice in a row (the model span, then its parent), so the ceiling - * has to hold two polls back to back. - */ - timeout: 400_000, - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: 0, - // Every test spends most of its time polling Sentry, so run them all at once. - workers: '100%', - reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', - use: { - baseURL: `http://localhost:${port}`, - }, - webServer: { - command: 'pnpm start', - port, - stdout: 'pipe', - stderr: 'pipe', - }, -}); diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/pnpm-workspace.yaml deleted file mode 100644 index a1c031fc3310..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/pnpm-workspace.yaml +++ /dev/null @@ -1 +0,0 @@ -minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/src/app.ts b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/src/app.ts deleted file mode 100644 index 693f1ef4f3b3..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/src/app.ts +++ /dev/null @@ -1,165 +0,0 @@ -// `instrument.ts` is preloaded via `node --import`, so Sentry is already initialised here. -import * as Sentry from '@sentry/node'; -import Anthropic from '@anthropic-ai/sdk'; -import express from 'express'; - -const apiKey = process.env.E2E_OPENROUTER_API_KEY; -if (!apiKey) { - throw new Error('E2E_OPENROUTER_API_KEY is not set'); -} - -// OpenRouter serves an Anthropic-compatible `/api/v1/messages`, so the stock client only needs a -// different base URL. It authenticates with a bearer token, so the key goes in `authToken` -// (Authorization: Bearer) rather than `apiKey` (x-api-key). The model is incidental: what is under -// test is the SDK's own request/response code path, which is what Sentry instruments. -const client = new Anthropic({ authToken: apiKey, baseURL: 'https://openrouter.ai/api', fetch: fetchWithoutDone }); - -/** - * OpenRouter ends each stream with an OpenAI-style `data: [DONE]` frame, which is not part of - * Anthropic's protocol. Since 0.136.0 the SDK JSON-parses every named event, so this frame - * makes it throw. Drop the frame before the SDK sees it. - */ -async function fetchWithoutDone(input: string | URL | Request, init?: RequestInit): Promise { - const response = await fetch(input, init); - if (!response.body || !response.headers.get('content-type')?.includes('text/event-stream')) { - return response; - } - - let buffer = ''; - const keepFrame = (frame: string): boolean => !/^data: \[DONE\]$/m.test(frame); - const body = response.body.pipeThrough(new TextDecoderStream()).pipeThrough( - new TransformStream({ - transform(chunk, controller) { - buffer += chunk; - const frames = buffer.split(/(?<=\r?\n\r?\n)/); - buffer = /\r?\n\r?\n$/.test(frames[frames.length - 1] ?? '') ? '' : (frames.pop() ?? ''); - controller.enqueue(frames.filter(keepFrame).join('')); - }, - flush(controller) { - if (keepFrame(buffer)) { - controller.enqueue(buffer); - } - }, - }), - ); - - return new Response(body.pipeThrough(new TextEncoderStream()), { - status: response.status, - statusText: response.statusText, - headers: response.headers, - }); -} - -const MODEL = 'openai/gpt-4o-mini'; - -const SHORT_ANSWER = 'Answer in at most five words.'; -const CHAT_PROMPT = `What is the capital of France? ${SHORT_ANSWER}`; -// Deliberately does not name the tool: `tool_choice` forces the call. -const WEATHER_PROMPT = `What is the weather in Paris? ${SHORT_ANSWER}`; -const SYSTEM = 'You are a helpful assistant used by an automated test.'; - -const WEATHER_TOOL = { - name: 'get_weather', - description: 'Get the current weather for a city.', - input_schema: { - type: 'object' as const, - properties: { city: { type: 'string', description: 'The city name' } }, - required: ['city'], - }, -}; - -const app = express(); - -/** The trace this request is recorded under, so the test can look its spans up in Sentry. */ -function currentTraceId(): string | undefined { - return Sentry.getActiveSpan()?.spanContext().traceId; -} - -/** The text of a message's first text block. */ -function textOf(message: Anthropic.Message): string { - const first = message.content[0]; - return first?.type === 'text' ? first.text : ''; -} - -// Each route makes one call through the `@anthropic-ai/sdk` client and answers with the trace id, so -// the test can find the request's spans in Sentry. - -app.get('/chat', async (_req, res, next) => { - try { - const message = await client.messages.create({ - model: MODEL, - max_tokens: 32, - system: SYSTEM, - messages: [{ role: 'user', content: CHAT_PROMPT }], - }); - res.send({ traceId: currentTraceId(), answer: textOf(message) }); - } catch (error) { - next(error); - } -}); - -// `messages.create({ stream: true })` returns an async iterable of events the caller drains. -app.get('/chat-stream', async (_req, res, next) => { - try { - const stream = await client.messages.create({ - model: MODEL, - max_tokens: 32, - system: SYSTEM, - messages: [{ role: 'user', content: CHAT_PROMPT }], - stream: true, - }); - - let answer = ''; - for await (const event of stream) { - if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') { - answer += event.delta.text; - } - } - res.send({ traceId: currentTraceId(), answer }); - } catch (error) { - next(error); - } -}); - -// `messages.stream()` is the SDK's streaming helper: it accumulates the events into the final -// message, and the integration instruments it separately from `create`. -app.get('/stream-helper', async (_req, res, next) => { - try { - const message = await client.messages - .stream({ - model: MODEL, - max_tokens: 32, - system: SYSTEM, - messages: [{ role: 'user', content: CHAT_PROMPT }], - }) - .finalMessage(); - res.send({ traceId: currentTraceId(), answer: textOf(message) }); - } catch (error) { - next(error); - } -}); - -app.get('/tools', async (_req, res, next) => { - try { - const message = await client.messages.create({ - model: MODEL, - max_tokens: 64, - messages: [{ role: 'user', content: WEATHER_PROMPT }], - tools: [WEATHER_TOOL], - tool_choice: { type: 'tool', name: 'get_weather' }, - }); - res.send({ traceId: currentTraceId(), toolUse: message.content.filter(block => block.type === 'tool_use') }); - } catch (error) { - next(error); - } -}); - -app.use((error: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => { - res.status(500).send({ message: error.message }); -}); - -const port = Number(process.env.PORT ?? 3030); -app.listen(port, () => { - // eslint-disable-next-line no-console - console.log(`node-anthropic-send-to-sentry listening on port ${port}`); -}); diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/src/instrument.ts deleted file mode 100644 index 7d031b0e6d8e..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/src/instrument.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as Sentry from '@sentry/node'; - -// The setup from the docs, nothing more: preloaded with `node --import`, no tunnel, so the spans go to -// the real Sentry project behind `E2E_TEST_DSN`, where the tests read them back. The Anthropic integration -// is on by default; its runtime channel injection is what picks up the `@anthropic-ai/sdk` client in app.ts. -Sentry.init({ - dsn: process.env.E2E_TEST_DSN, - environment: 'qa', // dynamic sampling bias to keep transactions - tracesSampleRate: 1, -}); diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tests/send-to-sentry.test.ts b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tests/send-to-sentry.test.ts deleted file mode 100644 index e7a135c2368c..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tests/send-to-sentry.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { expect, test } from '@playwright/test'; -import type { TraceItem } from './utils/sentry-api'; -import { - EVENT_POLLING_OPTIONS, - fetchSpanAttributes, - fetchTrace, - findSpanInTrace, - flattenTrace, -} from './utils/sentry-api'; - -// Mirrors src/app.ts. -const MODEL = 'openai/gpt-4o-mini'; - -type Attributes = Record; - -/** Sends one request to the app and returns the trace its request span was recorded under. */ -async function requestTrace(baseURL: string | undefined, path: string): Promise { - const response = await fetch(`${baseURL}${path}`); - const body = await response.text(); - expect(response.status, `${path} answered ${response.status}: ${body}`).toBe(200); - - const { traceId } = JSON.parse(body) as { traceId?: string }; - expect(traceId, `${path} did not report a trace id: ${body}`).toMatch(/^[0-9a-f]{32}$/); - - console.log(`${path}: https://${process.env.E2E_TEST_SENTRY_ORG_SLUG}.sentry.io/explore/traces/trace/${traceId}/`); - return traceId!; -} - -/** - * Polls Sentry until the trace holds a span with `op`, then returns that span and its attributes. The - * trace endpoint only carries the span's op, name and place in the tree; the attributes need a second - * lookup, which is polled as well since the two can land apart. - */ -async function waitForSpan(traceId: string, op: string): Promise<{ span: TraceItem; attributes: Attributes }> { - let found: { span: TraceItem; attributes: Attributes } | undefined; - - await expect - .poll(async () => { - const span = await findSpanInTrace(traceId, op); - const attributes = span?.event_id ? await fetchSpanAttributes(traceId, span.event_id) : undefined; - found = span && attributes ? { span, attributes } : undefined; - return found; - }, EVENT_POLLING_OPTIONS) - .toBeDefined(); - - return found!; -} - -/** Whether the model-call span sits somewhere below the request span (`http.server`) of the trace. */ -async function isModelSpanUnderRequestSpan(traceId: string, modelSpanId: string): Promise { - const requestSpan = flattenTrace(await fetchTrace(traceId)).find(item => item.op === 'http.server'); - return flattenTrace(requestSpan?.children ?? []).some(item => item.event_id === modelSpanId); -} - -/** - * The attributes every successful model-call span carries once Sentry has stored it, whatever the - * model answers. Model-dependent values (token counts, response id and model) are checked for shape, - * not content. Note the names are the stored ones: `origin` and `span.status` rather than the - * `sentry.*` attributes the SDK sends. - */ -function expectModelCallAttributes(span: TraceItem, attributes: Attributes): void { - expect(span.description).toBe(`chat ${MODEL}`); - expect(attributes).toMatchObject({ - 'span.op': 'gen_ai.chat', - 'span.status': 'ok', - origin: 'auto.ai.anthropic', - 'gen_ai.operation.name': 'chat', - 'gen_ai.provider.name': 'anthropic', - 'gen_ai.request.model': MODEL, - }); - expect(typeof attributes['gen_ai.response.id']).toBe('string'); - expect(typeof attributes['gen_ai.response.model']).toBe('string'); - expect(attributes['gen_ai.usage.output_tokens']).toBeGreaterThan(0); - expect(attributes['gen_ai.usage.total_tokens']).toBeGreaterThan(0); -} - -/** The prompt and the answer, which the SDK records by default and which have to survive the trip. */ -function expectRecordedConversation(attributes: Attributes): void { - // The answer is sent as `gen_ai.response.text` and stored as `gen_ai.output.messages`. - expect(attributes['gen_ai.system_instructions']).toContain('automated test'); - expect(attributes['gen_ai.input.messages']).toContain('capital of France'); - expect(typeof attributes['gen_ai.output.messages']).toBe('string'); -} - -/** `gen_ai.response.finish_reasons` is a JSON array, only recorded for streamed calls. */ -function finishReasons(attributes: Attributes): string[] { - const raw = attributes['gen_ai.response.finish_reasons']; - expect(typeof raw, 'gen_ai.response.finish_reasons').toBe('string'); - return JSON.parse(raw as string); -} - -test('Sends a message to Sentry as a gen_ai.chat span under the request span', async ({ baseURL }) => { - const traceId = await requestTrace(baseURL, '/chat'); - - const { span, attributes } = await waitForSpan(traceId, 'gen_ai.chat'); - - expectModelCallAttributes(span, attributes); - expect(attributes['gen_ai.usage.input_tokens']).toBeGreaterThan(0); - expect(attributes['gen_ai.response.streaming']).toBeUndefined(); - expectRecordedConversation(attributes); - - // The request span is the segment; it ends last, so it can land after its children. - await expect.poll(() => isModelSpanUnderRequestSpan(traceId, span.event_id!), EVENT_POLLING_OPTIONS).toBe(true); -}); - -test('Sends a streamed message to Sentry with its token usage', async ({ baseURL }) => { - const traceId = await requestTrace(baseURL, '/chat-stream'); - - const { span, attributes } = await waitForSpan(traceId, 'gen_ai.chat'); - - expectModelCallAttributes(span, attributes); - expect(attributes['gen_ai.response.streaming']).toBe(true); - expect(finishReasons(attributes)).toContain('end_turn'); - expectRecordedConversation(attributes); - // The input usage is not asserted: the instrumentation takes it from `message_start`, where - // OpenRouter reports 0, and ignores the real count OpenRouter (and the Anthropic API) put in - // `message_delta`. Output usage comes from `message_delta` and is covered by the shared checks. -}); - -test('Sends a message made with the stream helper to Sentry', async ({ baseURL }) => { - const traceId = await requestTrace(baseURL, '/stream-helper'); - - const { span, attributes } = await waitForSpan(traceId, 'gen_ai.chat'); - - // `messages.stream()` calls `create` underneath, which must not produce a second span. The parent - // lands last, so count after it; the short poll only rides out a dropped connection. - await expect.poll(() => isModelSpanUnderRequestSpan(traceId, span.event_id!), EVENT_POLLING_OPTIONS).toBe(true); - await expect - .poll(async () => flattenTrace(await fetchTrace(traceId)).filter(item => item.op === 'gen_ai.chat').length, { - timeout: 30_000, - intervals: [5_000], - }) - .toBe(1); - expectModelCallAttributes(span, attributes); - expect(attributes['gen_ai.response.streaming']).toBe(true); - expect(finishReasons(attributes)).toContain('end_turn'); - expectRecordedConversation(attributes); -}); - -test('Sends a message that returned a tool use to Sentry', async ({ baseURL }) => { - const traceId = await requestTrace(baseURL, '/tools'); - - const { span, attributes } = await waitForSpan(traceId, 'gen_ai.chat'); - - expectModelCallAttributes(span, attributes); - expect(attributes['gen_ai.usage.input_tokens']).toBeGreaterThan(0); - expect(attributes['gen_ai.tool.definitions']).toContain('get_weather'); - expect(attributes['gen_ai.input.messages']).toContain('weather in Paris'); - // The SDK also sends the returned tool use as `gen_ai.response.tool_calls`, but that attribute is - // not stored under that name, and stop reasons are only recorded for streamed calls. -}); diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tests/utils/sentry-api.ts b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tests/utils/sentry-api.ts deleted file mode 100644 index 521b830eefcb..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tests/utils/sentry-api.ts +++ /dev/null @@ -1,131 +0,0 @@ -const authToken = process.env.E2E_TEST_AUTH_TOKEN; -const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; -const sentryTestProject = process.env.E2E_TEST_SENTRY_PROJECT; - -/** - * Spans only become queryable once they have made it through to EAP, which takes - * noticeably longer than the error pipeline (~2min vs ~20s when this was measured). - */ -export const EVENT_POLLING_OPTIONS = { timeout: 180_000, intervals: [5_000] }; - -/** - * A node of the span tree returned by the organization trace endpoint. Spans, errors and - * occurrences all share this shape and are discriminated by `event_type`. - */ -export interface TraceItem { - /** On a span this is the span id. */ - event_id?: string; - event_type?: 'span' | 'error' | 'occurrence' | 'uptime_check'; - op?: string; - /** On a span this is the span name. */ - description?: string; - children?: TraceItem[]; - errors?: TraceItem[]; - occurrences?: TraceItem[]; -} - -/** - * One request to the Sentry API with the E2E token. Returns `undefined` when the connection - * fails or drops mid-response: over the minutes a test polls, the API closes the odd - * connection ("TypeError: terminated"), and an error thrown inside `expect.poll` fails the - * test instead of letting it retry, so a network error is treated like a "not there yet". - */ -async function fetchSentryApi(what: string, path: string): Promise<{ status: number; body: string } | undefined> { - try { - const response = await fetch(`https://sentry.io/api/0/${path}`, { - headers: { Authorization: `Bearer ${authToken}` }, - }); - return { status: response.status, body: await response.text() }; - } catch (error) { - console.log(`${what} failed: ${error}`); - return undefined; - } -} - -export async function fetchTrace(traceId: string): Promise { - const what = `Trace lookup for ${traceId}`; - const response = await fetchSentryApi(what, `organizations/${sentryTestOrgSlug}/trace/${traceId}/?statsPeriod=1h`); - if (!response) { - return []; - } - - // The trace endpoint is org scoped, so the auth token needs `org:read` on top of the - // project scopes the other assertions rely on. That never resolves by waiting, so fail - // loudly instead of polling until the timeout and reporting it as a missing event. - if (response.status === 401 || response.status === 403) { - throw new Error( - `${what} was rejected with ${response.status}: ${response.body}. ` + - 'E2E_TEST_AUTH_TOKEN needs the `org:read` scope.', - ); - } - - // Empty traces and the occasional rate limit are expected while polling, so treat anything - // else that is not a success as "not there yet" -- but log it, since a rejected request and - // a trace that has not landed are otherwise indistinguishable. - if (response.status !== 200) { - console.log(`${what} returned ${response.status}: ${response.body}`); - return []; - } - - return JSON.parse(response.body); -} - -/** - * Errors attach to whichever span was active when they were captured, and relocate from the - * top level into that span once it lands, so a given event can surface at any depth. - */ -export function flattenTrace(items: TraceItem[]): TraceItem[] { - return items.flatMap(item => [ - item, - ...flattenTrace(item.children ?? []), - ...flattenTrace(item.errors ?? []), - ...flattenTrace(item.occurrences ?? []), - ]); -} - -export async function findSpanInTrace(traceId: string, op: string): Promise { - return flattenTrace(await fetchTrace(traceId)).find(item => item.op === op); -} - -/** - * The attributes of one span, keyed by name, as Sentry stores them. The trace endpoint only carries - * a span's op, name and place in the tree; the attributes come from the project trace-items - * endpoint. Returns `undefined` while the span has not landed. - * - * Ingest normalises what the SDK sent: `sentry.origin` comes back as `origin`, `sentry.op` as - * `span.op`, `gen_ai.response.text` as `gen_ai.output.messages`, and some numbers are indexed as - * tags (`tags[,number]`). Integers are returned as strings and are converted here; everything - * else is passed through as is. - */ -export async function fetchSpanAttributes( - traceId: string, - spanId: string, -): Promise | undefined> { - const what = `Span lookup for ${spanId}`; - const response = await fetchSentryApi( - what, - `projects/${sentryTestOrgSlug}/${sentryTestProject}/trace-items/${spanId}/?trace_id=${traceId}&item_type=spans`, - ); - if (!response) { - return undefined; - } - - if (response.status === 401 || response.status === 403) { - throw new Error( - `${what} was rejected with ${response.status}: ${response.body}. ` + - 'E2E_TEST_AUTH_TOKEN needs the `org:read` scope.', - ); - } - - if (response.status !== 200) { - if (response.status !== 404) { - console.log(`${what} returned ${response.status}: ${response.body}`); - } - return undefined; - } - - const { attributes } = JSON.parse(response.body) as { attributes?: { name: string; type: string; value: unknown }[] }; - return Object.fromEntries( - (attributes ?? []).map(({ name, type, value }) => [name, type === 'int' ? Number(value) : value]), - ); -} diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tsconfig.json deleted file mode 100644 index 795574fd9df2..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "es2022", - "module": "nodenext", - "moduleResolution": "nodenext", - "outDir": "dist", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "types": ["node"] - }, - "include": ["src/**/*.ts"] -} diff --git a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/.gitignore b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/.gitignore deleted file mode 100644 index 795f6b6d40d1..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -pnpm-lock.yaml -dist -test-results -playwright-report diff --git a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/package.json deleted file mode 100644 index f94e4d8cf2ba..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "node-openai-send-to-sentry", - "description": "The OpenAI integration as a user runs it: a plain Sentry.init on an express app, real chat, streaming, tool-call, Responses API and embeddings requests through OpenRouter, and the gen_ai spans read back from a real Sentry project", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "tsc", - "start": "node --import ./dist/instrument.js dist/app.js", - "test": "playwright test", - "clean": "npx rimraf node_modules dist pnpm-lock.yaml", - "test:build": "pnpm install && pnpm build", - "test:build-latest": "pnpm install && pnpm add openai@latest && pnpm build", - "test:assert": "pnpm test" - }, - "dependencies": { - "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "express": "^4.21.2", - "openai": "7.5.0" - }, - "devDependencies": { - "@playwright/test": "1.64.1", - "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", - "@types/express": "^4.17.21", - "@types/node": "^24.0.0", - "sentry": "~0.44.1", - "typescript": "~5.9.0" - }, - "sentryTest": { - "optional": true, - "optionalVariants": [ - { - "build-command": "pnpm test:build-latest", - "label": "node-openai-send-to-sentry (latest)" - } - ] - } -} diff --git a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/playwright.config.ts b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/playwright.config.ts deleted file mode 100644 index e7621a23cc8b..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/playwright.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineConfig } from '@playwright/test'; - -const port = 3030; - -export default defineConfig({ - testDir: './tests', - /* Each test runs two Sentry polls of up to 180s each, after a live model call. */ - timeout: 400_000, - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: 0, - // Every test spends most of its time polling Sentry, so run them all at once. - workers: '100%', - reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', - use: { - baseURL: `http://localhost:${port}`, - }, - webServer: { - command: 'pnpm start', - port, - stdout: 'pipe', - stderr: 'pipe', - }, -}); diff --git a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/pnpm-workspace.yaml deleted file mode 100644 index a1c031fc3310..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/pnpm-workspace.yaml +++ /dev/null @@ -1 +0,0 @@ -minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/src/app.ts b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/src/app.ts deleted file mode 100644 index ac99c6d18eeb..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/src/app.ts +++ /dev/null @@ -1,151 +0,0 @@ -// `instrument.ts` is preloaded via `node --import`, so Sentry is already initialised here. -import * as Sentry from '@sentry/node'; -import express from 'express'; -import OpenAI from 'openai'; - -const apiKey = process.env.E2E_OPENROUTER_API_KEY; -if (!apiKey) { - throw new Error('E2E_OPENROUTER_API_KEY is not set'); -} - -// OpenRouter serves the OpenAI API shape (chat completions, responses, embeddings), so the stock -// client only needs a different base URL. The model is incidental: what is under test is the SDK's own -// request/response code path, which is what Sentry instruments. -const client = new OpenAI({ apiKey, baseURL: 'https://openrouter.ai/api/v1' }); - -const CHAT_MODEL = 'openai/gpt-4o-mini'; -const EMBEDDINGS_MODEL = 'openai/text-embedding-3-small'; - -const SHORT_ANSWER = 'Answer in at most five words.'; -const CHAT_PROMPT = `What is the capital of France? ${SHORT_ANSWER}`; -// Deliberately does not name the tool: `tool_choice: 'required'` forces the call. -const WEATHER_PROMPT = `What is the weather in Paris? ${SHORT_ANSWER}`; -const SYSTEM = 'You are a helpful assistant used by an automated test.'; - -const WEATHER_TOOL = { - type: 'function' as const, - function: { - name: 'get_weather', - description: 'Get the current weather for a city.', - parameters: { - type: 'object', - properties: { city: { type: 'string', description: 'The city name' } }, - required: ['city'], - }, - }, -}; - -const app = express(); - -/** The trace this request is recorded under, so the test can look its spans up in Sentry. */ -function currentTraceId(): string | undefined { - return Sentry.getActiveSpan()?.spanContext().traceId; -} - -// Each route makes one call through the `openai` client and answers with the trace id, so the test can -// find the request's spans in Sentry. - -app.get('/chat', async (_req, res, next) => { - try { - const completion = await client.chat.completions.create({ - model: CHAT_MODEL, - messages: [ - { role: 'system', content: SYSTEM }, - { role: 'user', content: CHAT_PROMPT }, - ], - temperature: 0, - max_tokens: 32, - }); - res.send({ - traceId: currentTraceId(), - answer: completion.choices[0]?.message.content ?? '', - }); - } catch (error) { - next(error); - } -}); - -app.get('/chat-stream', async (_req, res, next) => { - try { - const stream = await client.chat.completions.create({ - model: CHAT_MODEL, - messages: [ - { role: 'system', content: SYSTEM }, - { role: 'user', content: CHAT_PROMPT }, - ], - temperature: 0, - max_tokens: 32, - stream: true, - // Ask for the usage in the final chunk, so the streamed span carries token counts. - stream_options: { include_usage: true }, - }); - - let answer = ''; - for await (const chunk of stream) { - answer += chunk.choices[0]?.delta.content ?? ''; - } - res.send({ traceId: currentTraceId(), answer }); - } catch (error) { - next(error); - } -}); - -app.get('/tools', async (_req, res, next) => { - try { - const completion = await client.chat.completions.create({ - model: CHAT_MODEL, - messages: [{ role: 'user', content: WEATHER_PROMPT }], - tools: [WEATHER_TOOL], - tool_choice: 'required', - max_tokens: 64, - }); - res.send({ - traceId: currentTraceId(), - toolCalls: completion.choices[0]?.message.tool_calls ?? [], - }); - } catch (error) { - next(error); - } -}); - -app.get('/responses', async (_req, res, next) => { - try { - const response = await client.responses.create({ - model: CHAT_MODEL, - instructions: SYSTEM, - input: CHAT_PROMPT, - temperature: 0, - max_output_tokens: 32, - }); - res.send({ traceId: currentTraceId(), answer: response.output_text }); - } catch (error) { - next(error); - } -}); - -app.get('/embeddings', async (_req, res, next) => { - try { - const embedding = await client.embeddings.create({ - model: EMBEDDINGS_MODEL, - input: 'The capital of France is Paris.', - }); - res.send({ - traceId: currentTraceId(), - dimensions: embedding.data[0]?.embedding.length ?? 0, - }); - } catch (error) { - next(error); - } -}); - -Sentry.setupExpressErrorHandler(app); - -app.use((error: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => { - res.status(500).send({ message: error.message }); -}); - -const port = Number(process.env.PORT ?? 3030); -app.listen(port, () => { - // eslint-disable-next-line no-console - console.log(`node-openai-send-to-sentry listening on port ${port}`); -}); diff --git a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/src/instrument.ts deleted file mode 100644 index c6c15df960cb..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/src/instrument.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as Sentry from '@sentry/node'; - -// The setup from the docs, nothing more: preloaded with `node --import`, no tunnel, so the spans go to -// the real Sentry project behind `E2E_TEST_DSN`, where the tests read them back. The OpenAI integration -// is on by default; its runtime channel injection is what picks up the `openai` client in app.ts. -Sentry.init({ - dsn: process.env.E2E_TEST_DSN, - environment: 'qa', // dynamic sampling bias to keep transactions - tracesSampleRate: 1, -}); diff --git a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/tests/send-to-sentry.test.ts b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/tests/send-to-sentry.test.ts deleted file mode 100644 index eee3e75aeafb..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/tests/send-to-sentry.test.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { expect, test } from '@playwright/test'; -import type { TraceItem } from '@sentry-internal/test-utils/cli'; -import { - EVENT_POLLING_OPTIONS, - fetchSpanAttributes, - fetchTrace, - findSpanInTrace, - flattenTrace, -} from '@sentry-internal/test-utils/cli'; - -// Mirrors src/app.ts. -const CHAT_MODEL = 'openai/gpt-4o-mini'; -const EMBEDDINGS_MODEL = 'openai/text-embedding-3-small'; - -type Attributes = Record; - -/** Sends one request to the app and returns the trace its request span was recorded under. */ -async function requestTrace(baseURL: string | undefined, path: string): Promise { - const response = await fetch(`${baseURL}${path}`); - const body = await response.text(); - expect(response.status, `${path} answered ${response.status}: ${body}`).toBe(200); - - const { traceId } = JSON.parse(body) as { traceId?: string }; - expect(traceId, `${path} did not report a trace id: ${body}`).toMatch(/^[0-9a-f]{32}$/); - - console.log(`${path}: https://${process.env.E2E_TEST_SENTRY_ORG_SLUG}.sentry.io/explore/traces/trace/${traceId}/`); - return traceId!; -} - -/** - * Polls Sentry until the trace holds a span with `op`, then returns that span and its attributes. The - * trace endpoint only carries the span's op, name and place in the tree; the attributes need a second - * lookup, which is polled as well since the two can land apart. - */ -async function waitForSpan(traceId: string, op: string): Promise<{ span: TraceItem; attributes: Attributes }> { - let found: { span: TraceItem; attributes: Attributes } | undefined; - - await expect - .poll(() => { - const span = findSpanInTrace(traceId, op); - const attributes = span?.event_id ? fetchSpanAttributes(traceId, span.event_id) : undefined; - found = span && attributes ? { span, attributes } : undefined; - return found; - }, EVENT_POLLING_OPTIONS) - .toBeDefined(); - - return found!; -} - -/** The ids of the spans with `op` below the request span (`http.server`) of the trace. */ -function spanIdsUnderRequestSpan(traceId: string, op: string): string[] { - const requestSpan = flattenTrace(fetchTrace(traceId)).find(item => item.op === 'http.server'); - return flattenTrace(requestSpan?.children ?? []) - .filter(item => item.op === op) - .map(item => item.event_id!); -} - -/** - * The request span is the segment; it ends last, so it can land after its children. Expecting exactly - * one span also catches the call being instrumented twice. - */ -async function expectOnlySpanUnderRequestSpan(traceId: string, op: string, span: TraceItem): Promise { - await expect.poll(() => spanIdsUnderRequestSpan(traceId, op), EVENT_POLLING_OPTIONS).toEqual([span.event_id]); -} - -/** - * The attributes every successful model-call span carries once Sentry has stored it, whatever the - * model answers. Model-dependent values (token counts, response id and model) are checked for shape, - * not content. Note the names are the stored ones: `origin` and `span.status` rather than the - * `sentry.*` attributes the SDK sends. - */ -function expectModelCallAttributes(attributes: Attributes, operation: 'chat' | 'embeddings', model: string): void { - expect(attributes).toMatchObject({ - 'span.op': `gen_ai.${operation}`, - 'span.status': 'ok', - origin: 'auto.ai.openai', - 'gen_ai.operation.name': operation, - 'gen_ai.provider.name': 'openai', - 'gen_ai.request.model': model, - }); - // OpenAI embeddings responses carry no id, so only chat calls are guaranteed one. - if (operation === 'chat') { - expect(typeof attributes['gen_ai.response.id']).toBe('string'); - } - expect(typeof attributes['gen_ai.response.model']).toBe('string'); - // The Sentry API returns int attributes as strings. - expect(Number(attributes['gen_ai.usage.input_tokens'])).toBeGreaterThan(0); - expect(Number(attributes['gen_ai.usage.total_tokens'])).toBeGreaterThan(0); -} - -/** `gen_ai.response.finish_reasons` is a JSON array; a streamed answer can repeat the reason per chunk. */ -function finishReasons(attributes: Attributes): string[] { - return JSON.parse(attributes['gen_ai.response.finish_reasons'] as string); -} - -test('Sends a chat completion to Sentry as a gen_ai.chat span under the request span', async ({ baseURL }) => { - const traceId = await requestTrace(baseURL, '/chat'); - - const { span, attributes } = await waitForSpan(traceId, 'gen_ai.chat'); - - expect(span.description).toBe(`chat ${CHAT_MODEL}`); - expectModelCallAttributes(attributes, 'chat', CHAT_MODEL); - expect(Number(attributes['gen_ai.usage.output_tokens'])).toBeGreaterThan(0); - expect(finishReasons(attributes)).toEqual(['stop']); - expect(attributes['gen_ai.response.streaming']).toBeUndefined(); - - // The SDK records prompts and answers by default, and they have to survive the trip to Sentry. The - // answer is sent as `gen_ai.response.text` and stored as `gen_ai.output.messages`. - expect(attributes['gen_ai.system_instructions']).toContain('automated test'); - expect(attributes['gen_ai.input.messages']).toContain('capital of France'); - expect(attributes['gen_ai.output.messages']).toMatch(/paris/i); - - await expectOnlySpanUnderRequestSpan(traceId, 'gen_ai.chat', span); -}); - -test('Sends a streamed chat completion to Sentry with its token usage', async ({ baseURL }) => { - const traceId = await requestTrace(baseURL, '/chat-stream'); - - const { span, attributes } = await waitForSpan(traceId, 'gen_ai.chat'); - - expect(span.description).toBe(`chat ${CHAT_MODEL}`); - expectModelCallAttributes(attributes, 'chat', CHAT_MODEL); - expect(attributes['gen_ai.response.streaming']).toBe(true); - // The usage only arrives in the final chunk, which the streaming instrumentation has to pick up. - expect(Number(attributes['gen_ai.usage.output_tokens'])).toBeGreaterThan(0); - expect(finishReasons(attributes)).toContain('stop'); - expect(attributes['gen_ai.output.messages']).toMatch(/paris/i); - - await expectOnlySpanUnderRequestSpan(traceId, 'gen_ai.chat', span); -}); - -test('Sends a chat completion that returned a tool call to Sentry', async ({ baseURL }) => { - const traceId = await requestTrace(baseURL, '/tools'); - - const { span, attributes } = await waitForSpan(traceId, 'gen_ai.chat'); - - expectModelCallAttributes(attributes, 'chat', CHAT_MODEL); - expect(finishReasons(attributes)).toEqual(['tool_calls']); - expect(attributes['gen_ai.tool.definitions']).toContain('get_weather'); - // The SDK also sends the returned call as `gen_ai.response.tool_calls`, but that attribute is not - // stored as such, so the finish reason is what proves the tool call made it. - // TODO: Assert the call in `gen_ai.output.messages` once the OpenAI integration records it there. - - await expectOnlySpanUnderRequestSpan(traceId, 'gen_ai.chat', span); -}); - -test('Sends a Responses API call to Sentry as a gen_ai.chat span', async ({ baseURL }) => { - const traceId = await requestTrace(baseURL, '/responses'); - - const { span, attributes } = await waitForSpan(traceId, 'gen_ai.chat'); - - expect(span.description).toBe(`chat ${CHAT_MODEL}`); - expectModelCallAttributes(attributes, 'chat', CHAT_MODEL); - expect(Number(attributes['gen_ai.usage.output_tokens'])).toBeGreaterThan(0); - expect(finishReasons(attributes)).toEqual(['completed']); - expect(attributes['gen_ai.input.messages']).toContain('capital of France'); - expect(attributes['gen_ai.output.messages']).toMatch(/paris/i); - - await expectOnlySpanUnderRequestSpan(traceId, 'gen_ai.chat', span); -}); - -test('Sends an embeddings call to Sentry as a gen_ai.embeddings span', async ({ baseURL }) => { - const traceId = await requestTrace(baseURL, '/embeddings'); - - const { span, attributes } = await waitForSpan(traceId, 'gen_ai.embeddings'); - - expect(span.description).toBe(`embeddings ${EMBEDDINGS_MODEL}`); - expectModelCallAttributes(attributes, 'embeddings', EMBEDDINGS_MODEL); - expect(attributes['gen_ai.embeddings.input']).toContain('Paris'); - - await expectOnlySpanUnderRequestSpan(traceId, 'gen_ai.embeddings', span); -}); diff --git a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/tsconfig.json deleted file mode 100644 index 795574fd9df2..000000000000 --- a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "es2022", - "module": "nodenext", - "moduleResolution": "nodenext", - "outDir": "dist", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "types": ["node"] - }, - "include": ["src/**/*.ts"] -} From 9bc20868cb80b102dd42ac92c1fd0a4eaa99727b Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 30 Oct 2026 13:22:15 +0100 Subject: [PATCH 055/169] test(node-integration-tests): Install Flue and pi-durable as package dependencies (#25933) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** The Flue and pi-durable suites installed their packages through `additionalDependencies`: an unlocked `npm install` into a fresh tmp dir for every `createEsmAndCjsTests` call. That is 3 installs for Flue and 9 for pi-durable, per run and Node version. Measured locally for Flue: ~16s cold / ~3s warm and 210 MB each time. The unpinned `^` ranges also let upstream releases reach CI without review. As root devDependencies they are installed once and pinned by the lockfile. They were kept out because their `engines.node >= 22.19` broke `yarn install` on Node 20. Under pnpm, without `engine-strict`, that is only a warning. Both suites still skip below Node 22. Additionally, we used to use `createEsmAndCjsTests` and then skip for the cjs case. Instead, we can just use `createEsmTests` here. This prepares the split of the pi-ai integration (#25869). **How to review:** Best viewed with whitespace hidden. Dropping the trailing options argument made the formatter re-indent both suites, and switching to `createEsmTests` removed the hand-written CJS skips. No assertions change. The `allowBuilds` entries are for `@google/genai` and `protobufjs`, which the pi packages pull in. **Unsure about:** `pnpm dedupe` (enforced in CI) moves the root `@google/genai` from 1.20.0 to 1.52.0 and bumps some `@aws-sdk`/`@smithy` packages, since pi-ai depends on newer versions. The google-genai and aws-serverless suites pass locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- .../node-integration-tests/package.json | 5 + .../suites/tracing/flue/test.ts | 322 ++--- .../suites/tracing/pi-durable/test.ts | 1282 ++++++++--------- pnpm-lock.yaml | 624 +++++++- pnpm-workspace.yaml | 2 + 5 files changed, 1305 insertions(+), 930 deletions(-) diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 563b661fed20..2965f6567c76 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -36,6 +36,10 @@ "@aws-sdk/client-sfn": "^3.1046.0", "@aws-sdk/client-sns": "^3.1046.0", "@aws-sdk/client-sqs": "^3.1046.0", + "@earendil-works/chord": "^1.4.0", + "@earendil-works/pi-ai": "^1.4.0", + "@earendil-works/pi-durable": "^1.4.0", + "@flue/runtime": "2.0.3", "@google/genai": "^1.20.0", "@growthbook/growthbook": "^1.6.5", "@hapi/hapi": "^21.3.10", @@ -113,6 +117,7 @@ "srvx": "^0.11.22", "tedious": "^19.2.1", "unstorage": "2.0.0-alpha.7", + "valibot": "1.4.2", "winston": "^3.17.0", "ws": "^8.20.1", "yargs": "^16.2.0" diff --git a/dev-packages/node-integration-tests/suites/tracing/flue/test.ts b/dev-packages/node-integration-tests/suites/tracing/flue/test.ts index b750ee653e4f..83c73526b43e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/flue/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/flue/test.ts @@ -14,198 +14,156 @@ import { GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_TOTAL_TOKENS, + SENTRY_OP, + SENTRY_ORIGIN, } from '@sentry/conventions/attributes'; +import { GEN_AI_CHAT, GEN_AI_EXECUTE_TOOL, GEN_AI_INVOKE_AGENT } from '@sentry/conventions/op'; import { afterAll, describe, expect } from 'vitest'; import { supports } from '../../../utils'; -import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; - -// `@flue/runtime` declares `engines.node >= 22.19`, so it can't live in the package's root -// `devDependencies` (that would break `yarn install` on the 20.19 CI matrix). Install it per-suite -// instead, guarded by the `min: 22` skip below. -const FLUE_DEPENDENCIES = { - additionalDependencies: { - '@flue/runtime': '2.0.3', - '@earendil-works/pi-ai': '0.85.1', - valibot: '1.1.0', - }, -}; +import { cleanupChildProcesses, createEsmTests } from '../../../utils/runner'; +// `@flue/runtime` declares `engines.node >= 22.19`. describe.runIf(supports({ min: 22 }))('Flue integration', () => { afterAll(() => { cleanupChildProcesses(); }); - createEsmAndCjsTests( - __dirname, - 'scenario.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - // `@flue/runtime` is ESM-only — its `exports` map has no `require` condition, so there is no - // CJS variant of this scenario to run. - if (mode === 'cjs') { - return; - } - - test('creates the invoke_agent / chat / execute_tool hierarchy', async () => { - await createRunner() - .expect({ - span: container => { - const spans = container.items; - - const root = spans.find(span => span.name === 'flue-test')!; - expect(root.is_segment).toBe(true); - - // Counted rather than looked up: the interceptor skips the submission wrapper - // operation, so one dispatch opens exactly one agent span, and each turn and tool call - // is spanned once. `find` passes just as happily on a duplicate. - expect(spans.filter(span => span.attributes['sentry.origin']?.value === 'auto.ai.flue')).toHaveLength(4); - - const agents = spans.filter(span => span.name === 'invoke_agent Hello'); - const chats = spans.filter(span => span.name === 'chat faux-model'); - const tools = spans.filter(span => span.name === 'execute_tool get_weather'); - expect(agents).toHaveLength(1); - expect(tools).toHaveLength(1); - // One turn asks for the tool, the second answers with its result. - expect(chats).toHaveLength(2); - - const agent = agents[0]!; - expect(agent.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); - expect(agent.attributes[GEN_AI_OPERATION_NAME]?.value).toBe('invoke_agent'); - expect(agent.attributes[GEN_AI_AGENT_NAME]?.value).toBe('Hello'); - expect(agent.parent_span_id).toBe(root.span_id); - - const conversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; - expect(conversationId).toEqual(expect.any(String)); - - // Both turns, not just the first: they leave the provider by different paths (a tool - // call, then a final answer) and resolve their parent through separate tracker lookups. - for (const chat of chats) { - expect(chat.attributes['sentry.op']?.value).toBe('gen_ai.chat'); - expect(chat.attributes['sentry.origin']?.value).toBe('auto.ai.flue'); - expect(chat.attributes[GEN_AI_OPERATION_NAME]?.value).toBe('chat'); - expect(chat.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); - expect(chat.parent_span_id).toBe(agent.span_id); - expect(chat.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBeGreaterThan(0); - expect(chat.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBeGreaterThan(0); - expect(chat.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBeGreaterThan(0); - // Flue computes cost itself; no provider SDK reports it. The faux provider prices - // every model at zero, so this only proves the attribute is mapped. - expect(chat.attributes[GEN_AI_COST_TOTAL_TOKENS]?.value).toEqual(expect.any(Number)); - } - - // pi-ai's `toolUse` is reported as the conventions' `tool_call`. - expect(chats.map(chat => chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).sort()).toEqual([ - '["stop"]', - '["tool_call"]', - ]); - - const tool = tools[0]!; - // The tool has to actually run: a schema mismatch still produces a correctly named and - // parented span, so only the status separates a real call from a rejected one. - expect(tool.status).toBe('ok'); - expect(tool.attributes['sentry.op']?.value).toBe('gen_ai.execute_tool'); - expect(tool.attributes['sentry.origin']?.value).toBe('auto.ai.flue'); - expect(tool.attributes[GEN_AI_TOOL_NAME]?.value).toBe('get_weather'); - - // Tool spans are siblings of `chat` under the agent invocation, matching how Flue's - // own OpenTelemetry adapter projects them. - expect(tool.parent_span_id).toBe(agent.span_id); - }, - }) - .start() - .completed(); - }); - }, - FLUE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario.mjs', - 'instrument-with-pii.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - test('records the messages of the turns in the gen_ai conventions shape', async () => { - await createRunner() - .expect({ - span: container => { - const chats = container.items - .filter(span => span.name === 'chat faux-model') - .sort((a, b) => a.start_timestamp - b.start_timestamp); - expect(chats).toHaveLength(2); - - // Flue sends pi-ai's shape (`toolResult`, `toolCall`); Sentry renders the conventions. - expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_INPUT_MESSAGES]?.value))).toEqual([ - { role: 'user', parts: [{ type: 'text', content: 'What is the weather in Berlin?' }] }, - ]); - expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_OUTPUT_MESSAGES]?.value))).toEqual([ - { - role: 'assistant', - parts: [{ type: 'tool_call', id: 'call_1', name: 'get_weather', arguments: '{"city":"Berlin"}' }], - finish_reason: 'tool_call', - }, - ]); - - const answerInput = JSON.parse(String(chats[1]!.attributes[GEN_AI_INPUT_MESSAGES]?.value)); - expect(answerInput.map((message: { role: string }) => message.role)).toEqual([ - 'user', - 'assistant', - 'tool', - ]); - expect(answerInput[2].parts[0]).toMatchObject({ - type: 'tool_call_response', - id: 'call_1', - name: 'get_weather', - }); - expect(answerInput[2].parts[0].result).toContain('sunny in Berlin'); - expect(chats[1]!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]?.value).toContain('You are a helpful assistant.'); - - // The tool span records the result the model receives, not Flue's internal wrapper. - const tool = container.items.find(span => span.name === 'execute_tool get_weather')!; - expect(tool.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value).toBe('{"city":"Berlin"}'); - expect(tool.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toContain('sunny in Berlin'); - expect(tool.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).not.toContain('details'); - }, - }) - .start() - .completed(); - }); - }, - FLUE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario-separate-requests.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - test('puts each dispatch in the trace of the request that admitted it', async () => { - const expectAgentUnder = (requestName: string) => ({ - span: (container: SerializedStreamedSpanContainer) => { - const request = container.items.find(span => span.name === requestName)!; - expect(request.is_segment).toBe(true); - - const agents = container.items.filter(span => span.name === 'invoke_agent Hello'); + // `@flue/runtime` is ESM-only: its `exports` map has no `require` condition. + createEsmTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('creates the invoke_agent / chat / execute_tool hierarchy', async () => { + await createRunner() + .expect({ + span: container => { + const spans = container.items; + + const root = spans.find(span => span.name === 'flue-test')!; + expect(root.is_segment).toBe(true); + + // Counted rather than looked up: the interceptor skips the submission wrapper + // operation, so one dispatch opens exactly one agent span, and each turn and tool call + // is spanned once. `find` passes just as happily on a duplicate. + expect(spans.filter(span => span.attributes[SENTRY_ORIGIN]?.value === 'auto.ai.flue')).toHaveLength(4); + + const agents = spans.filter(span => span.name === 'invoke_agent Hello'); + const chats = spans.filter(span => span.name === 'chat faux-model'); + const tools = spans.filter(span => span.name === 'execute_tool get_weather'); expect(agents).toHaveLength(1); - expect(agents[0]!.parent_span_id).toBe(request.span_id); + expect(tools).toHaveLength(1); + // One turn asks for the tool, the second answers with its result. + expect(chats).toHaveLength(2); + + const agent = agents[0]!; + expect(agent.attributes[SENTRY_OP]?.value).toBe(GEN_AI_INVOKE_AGENT); + expect(agent.attributes[GEN_AI_OPERATION_NAME]?.value).toBe('invoke_agent'); + expect(agent.attributes[GEN_AI_AGENT_NAME]?.value).toBe('Hello'); + expect(agent.parent_span_id).toBe(root.span_id); + + const conversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; + expect(conversationId).toEqual(expect.any(String)); + + // Both turns, not just the first: they leave the provider by different paths (a tool + // call, then a final answer) and resolve their parent through separate tracker lookups. + for (const chat of chats) { + expect(chat.attributes[SENTRY_OP]?.value).toBe(GEN_AI_CHAT); + expect(chat.attributes[SENTRY_ORIGIN]?.value).toBe('auto.ai.flue'); + expect(chat.attributes[GEN_AI_OPERATION_NAME]?.value).toBe('chat'); + expect(chat.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); + expect(chat.parent_span_id).toBe(agent.span_id); + expect(chat.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBeGreaterThan(0); + expect(chat.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBeGreaterThan(0); + expect(chat.attributes[GEN_AI_USAGE_TOTAL_TOKENS]?.value).toBeGreaterThan(0); + // Flue computes cost itself; no provider SDK reports it. The faux provider prices + // every model at zero, so this only proves the attribute is mapped. + expect(chat.attributes[GEN_AI_COST_TOTAL_TOKENS]?.value).toEqual(expect.any(Number)); + } + + // pi-ai's `toolUse` is reported as the conventions' `tool_call`. + expect(chats.map(chat => chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).sort()).toEqual([ + '["stop"]', + '["tool_call"]', + ]); + + const tool = tools[0]!; + // The tool has to actually run: a schema mismatch still produces a correctly named and + // parented span, so only the status separates a real call from a rejected one. + expect(tool.status).toBe('ok'); + expect(tool.attributes[SENTRY_OP]?.value).toBe(GEN_AI_EXECUTE_TOOL); + expect(tool.attributes[SENTRY_ORIGIN]?.value).toBe('auto.ai.flue'); + expect(tool.attributes[GEN_AI_TOOL_NAME]?.value).toBe('get_weather'); + + // Tool spans are siblings of `chat` under the agent invocation, matching how Flue's + // own OpenTelemetry adapter projects them. + expect(tool.parent_span_id).toBe(agent.span_id); + }, + }) + .start() + .completed(); + }); + }); + + createEsmTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => { + test('records the messages of the turns in the gen_ai conventions shape', async () => { + await createRunner() + .expect({ + span: container => { + const chats = container.items + .filter(span => span.name === 'chat faux-model') + .sort((a, b) => a.start_timestamp - b.start_timestamp); + expect(chats).toHaveLength(2); + + // Flue sends pi-ai's shape (`toolResult`, `toolCall`); Sentry renders the conventions. + expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_INPUT_MESSAGES]?.value))).toEqual([ + { role: 'user', parts: [{ type: 'text', content: 'What is the weather in Berlin?' }] }, + ]); + expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_OUTPUT_MESSAGES]?.value))).toEqual([ + { + role: 'assistant', + parts: [{ type: 'tool_call', id: 'call_1', name: 'get_weather', arguments: '{"city":"Berlin"}' }], + finish_reason: 'tool_call', + }, + ]); + + const answerInput = JSON.parse(String(chats[1]!.attributes[GEN_AI_INPUT_MESSAGES]?.value)); + expect(answerInput.map((message: { role: string }) => message.role)).toEqual(['user', 'assistant', 'tool']); + expect(answerInput[2].parts[0]).toMatchObject({ + type: 'tool_call_response', + id: 'call_1', + name: 'get_weather', + }); + expect(answerInput[2].parts[0].result).toContain('sunny in Berlin'); + expect(chats[1]!.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]?.value).toContain('You are a helpful assistant.'); + + // The tool span records the result the model receives, not Flue's internal wrapper. + const tool = container.items.find(span => span.name === 'execute_tool get_weather')!; + expect(tool.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value).toBe('{"city":"Berlin"}'); + expect(tool.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toContain('sunny in Berlin'); + expect(tool.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).not.toContain('details'); }, - }); - - await createRunner() - .unordered() - .expect(expectAgentUnder('request-a')) - .expect(expectAgentUnder('request-b')) - .start() - .completed(); + }) + .start() + .completed(); + }); + }); + + createEsmTests(__dirname, 'scenario-separate-requests.mjs', 'instrument.mjs', (createRunner, test) => { + test('puts each dispatch in the trace of the request that admitted it', async () => { + const expectAgentUnder = (requestName: string) => ({ + span: (container: SerializedStreamedSpanContainer) => { + const request = container.items.find(span => span.name === requestName)!; + expect(request.is_segment).toBe(true); + + const agents = container.items.filter(span => span.name === 'invoke_agent Hello'); + expect(agents).toHaveLength(1); + expect(agents[0]!.parent_span_id).toBe(request.span_id); + }, }); - }, - FLUE_DEPENDENCIES, - ); + + await createRunner() + .unordered() + .expect(expectAgentUnder('request-a')) + .expect(expectAgentUnder('request-b')) + .start() + .completed(); + }); + }); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/pi-durable/test.ts b/dev-packages/node-integration-tests/suites/tracing/pi-durable/test.ts index 7bfd7947bb9f..f26aa34b63d7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/pi-durable/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/pi-durable/test.ts @@ -23,18 +23,7 @@ import { GEN_AI_CHAT, GEN_AI_EXECUTE_TOOL, GEN_AI_INVOKE_AGENT } from '@sentry/c import { afterAll, describe, expect } from 'vitest'; import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../packages/server-utils/src/ai/core/gen-ai-attributes'; import { supports } from '../../../utils'; -import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; - -// The pi packages declare `engines.node >= 22.19`, so they can't live in the package's root -// `devDependencies` (that would break `yarn install` on the 20.19 CI matrix). Install them per-suite -// instead, guarded by the `min: 22` skip below. -const PI_DURABLE_DEPENDENCIES = { - additionalDependencies: { - '@earendil-works/pi-durable': '^1.2.0', - '@earendil-works/pi-ai': '^1.2.0', - '@earendil-works/chord': '^1.2.0', - }, -}; +import { cleanupChildProcesses, createEsmTests } from '../../../utils/runner'; // The provider session id that pi-durable stores with each conversation, a UUIDv7. const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; @@ -48,702 +37,613 @@ const CONTENT_ATTRIBUTES = [ GEN_AI_TOOL_CALL_RESULT, ]; +// The pi packages declare `engines.node >= 22.19`. describe.runIf(supports({ min: 22 }))('pi-durable integration', () => { afterAll(() => { cleanupChildProcesses(); }); - createEsmAndCjsTests( - __dirname, - 'scenario.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - // The pi packages are ESM-only: their `exports` maps have no `require` condition. - if (mode === 'cjs') { - return; - } - - test('traces each run as its own invoke_agent trace with chat and execute_tool children', async () => { - let toolRunTraceId: string | undefined; - let brokenToolSpanId: string | undefined; - let errorTraceId: string | undefined; - let errorSpanId: string | undefined; - const conversationIds: unknown[] = []; - - await createRunner() - .unordered() - .expect({ - span: container => { - const spans = container.items; - const agent = spans.find(span => span.is_segment)!; - expect(agent.name).toBe('invoke_agent'); - expect(spans.filter(span => span.name === 'chat faux-model')).toHaveLength(2); - // One span per run, request and tool call: a tool wrapped twice would add a span. - expect(spans.filter(span => span.attributes[SENTRY_ORIGIN]?.value === 'auto.ai.pi_durable')).toHaveLength( - 5, - ); - - expect(agent.attributes[SENTRY_OP]?.value).toBe(GEN_AI_INVOKE_AGENT); - expect(agent.attributes[SENTRY_ORIGIN]?.value).toBe('auto.ai.pi_durable'); - expect(agent.attributes[GEN_AI_OPERATION_NAME]?.value).toBe('invoke_agent'); - const conversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; - expect(conversationId).toMatch(UUID); - - const chats = spans - .filter(span => span.name === 'chat faux-model') - .sort((a, b) => a.start_timestamp - b.start_timestamp); - for (const chat of chats) { - expect(chat.parent_span_id).toBe(agent.span_id); - expect(chat.attributes[SENTRY_OP]?.value).toBe(GEN_AI_CHAT); - expect(chat.attributes[SENTRY_ORIGIN]?.value).toBe('auto.ai.pi_durable'); - expect(chat.attributes[GEN_AI_PROVIDER_NAME]?.value).toBe('faux'); - expect(chat.attributes[GEN_AI_REQUEST_MODEL]?.value).toBe('faux-model'); - expect(chat.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); - expect(chat.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBeGreaterThan(0); - expect(chat.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBeGreaterThan(0); - // pi-durable sends the prompt and the tools as positional system messages. - expect(chat.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]?.value).toBe('You are a weather assistant.'); - expect( - JSON.parse(String(chat.attributes[GEN_AI_TOOL_DEFINITIONS]?.value)).map( - (tool: { name: string }) => tool.name, - ), - ).toEqual(['get_weather', 'broken']); - } - - // Mapped from pi-ai's shape to the conventions: the tool calls and tool results the - // second request carries are what Sentry's conversation view renders. - expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_INPUT_MESSAGES]?.value))).toEqual([ - { role: 'user', parts: [{ type: 'text', content: 'What is the weather in Berlin?' }] }, - ]); - const answerInput = JSON.parse(String(chats[1]!.attributes[GEN_AI_INPUT_MESSAGES]?.value)); - expect(answerInput.map((message: { role: string }) => message.role)).toEqual([ - 'user', - 'assistant', - 'tool', - 'tool', - ]); - expect(answerInput[1].parts).toEqual([ - { type: 'tool_call', id: 'call_1', name: 'get_weather', arguments: '{"city":"Berlin"}' }, - { type: 'tool_call', id: 'call_2', name: 'broken', arguments: '{}' }, - ]); - expect(answerInput[2].parts).toEqual([ - { - type: 'tool_call_response', - id: 'call_1', - name: 'get_weather', - result: 'It is 21 degrees and sunny in Berlin.', - }, - ]); - expect(chats.map(chat => chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).sort()).toEqual([ - '["stop"]', - '["tool_call"]', - ]); - expect(chats.map(chat => chat.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).join()).toContain( - 'It is 21 degrees and sunny in Berlin.', - ); - - const weather = spans.find(span => span.name === 'execute_tool get_weather')!; - expect(weather.parent_span_id).toBe(agent.span_id); - expect(weather.status).toBe('ok'); - expect(weather.attributes[SENTRY_OP]?.value).toBe(GEN_AI_EXECUTE_TOOL); - expect(weather.attributes[GEN_AI_TOOL_NAME]?.value).toBe('get_weather'); - expect(weather.attributes[GEN_AI_TOOL_DESCRIPTION]?.value).toBe('Get the current weather for a city.'); - expect(weather.attributes[GEN_AI_TOOL_CALL_ID_ATTRIBUTE]?.value).toBe('call_1'); - expect(weather.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value).toBe('{"city":"Berlin"}'); - expect(weather.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toContain('sunny in Berlin'); - expect(weather.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); - - const broken = spans.find(span => span.name === 'execute_tool broken')!; - expect(broken.parent_span_id).toBe(agent.span_id); - expect(broken.status).toBe('error'); - // The error result pi-durable gives the model, not the thrown error. - expect(broken.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toContain('[error] broken tool'); - - toolRunTraceId = agent.trace_id; - brokenToolSpanId = broken.span_id; - conversationIds.push(conversationId); - }, - }) - .expect({ - span: container => { - const spans = container.items; - const agent = spans.find(span => span.is_segment)!; - expect(agent.name).toBe('invoke_agent'); - expect(spans).toHaveLength(2); - - const chat = spans.find(span => span.name === 'chat faux-model')!; + // The pi packages are ESM-only: their `exports` maps have no `require` condition. + createEsmTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('traces each run as its own invoke_agent trace with chat and execute_tool children', async () => { + let toolRunTraceId: string | undefined; + let brokenToolSpanId: string | undefined; + let errorTraceId: string | undefined; + let errorSpanId: string | undefined; + const conversationIds: unknown[] = []; + + await createRunner() + .unordered() + .expect({ + span: container => { + const spans = container.items; + const agent = spans.find(span => span.is_segment)!; + expect(agent.name).toBe('invoke_agent'); + expect(spans.filter(span => span.name === 'chat faux-model')).toHaveLength(2); + // One span per run, request and tool call: a tool wrapped twice would add a span. + expect(spans.filter(span => span.attributes[SENTRY_ORIGIN]?.value === 'auto.ai.pi_durable')).toHaveLength( + 5, + ); + + expect(agent.attributes[SENTRY_OP]?.value).toBe(GEN_AI_INVOKE_AGENT); + expect(agent.attributes[SENTRY_ORIGIN]?.value).toBe('auto.ai.pi_durable'); + expect(agent.attributes[GEN_AI_OPERATION_NAME]?.value).toBe('invoke_agent'); + const conversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; + expect(conversationId).toMatch(UUID); + + const chats = spans + .filter(span => span.name === 'chat faux-model') + .sort((a, b) => a.start_timestamp - b.start_timestamp); + for (const chat of chats) { expect(chat.parent_span_id).toBe(agent.span_id); - expect(chat.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).toContain('Goodbye.'); - - conversationIds.push(agent.attributes[GEN_AI_CONVERSATION_ID]?.value); - }, - }) - .expect({ - span: container => { - // The request that opened the Harness and submitted both inputs holds no agent work. - expect(container.items).toHaveLength(1); - expect(container.items[0]!.name).toBe('pi-durable-request'); - }, - }) - .expect({ - event: event => { - const exception = event.exception?.values?.[0]; - expect(exception?.value).toBe('broken tool'); - expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); - - errorTraceId = event.contexts?.trace?.trace_id; - errorSpanId = event.contexts?.trace?.span_id; - }, - }) - .start() - .completed(); - - // Both runs belong to the root conversation, so they share one conversation id. - expect(conversationIds).toHaveLength(2); - expect(conversationIds[0]).toBe(conversationIds[1]); - // The tool error is reported on the failing call's span, in the run's trace. - expect(errorTraceId).toBe(toolRunTraceId); - expect(errorSpanId).toBe(brokenToolSpanId); - }); - - test('records no message content when the client turns gen_ai recording off', async () => { - await createRunner() - .withEnv({ PI_DURABLE_RECORDING: 'off' }) - .unordered() - .expect({ - span: container => { - const spans = container.items; - expect(spans).toHaveLength(5); - for (const span of spans) { - for (const attribute of CONTENT_ATTRIBUTES) { - expect(span.attributes[attribute]).toBeUndefined(); - } - } - const weather = spans.find(span => span.name === 'execute_tool get_weather')!; - expect(weather.attributes[GEN_AI_TOOL_DESCRIPTION]?.value).toBe('Get the current weather for a city.'); + expect(chat.attributes[SENTRY_OP]?.value).toBe(GEN_AI_CHAT); + expect(chat.attributes[SENTRY_ORIGIN]?.value).toBe('auto.ai.pi_durable'); + expect(chat.attributes[GEN_AI_PROVIDER_NAME]?.value).toBe('faux'); + expect(chat.attributes[GEN_AI_REQUEST_MODEL]?.value).toBe('faux-model'); + expect(chat.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); + expect(chat.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value).toBeGreaterThan(0); + expect(chat.attributes[GEN_AI_USAGE_OUTPUT_TOKENS]?.value).toBeGreaterThan(0); + // pi-durable sends the prompt and the tools as positional system messages. + expect(chat.attributes[GEN_AI_SYSTEM_INSTRUCTIONS]?.value).toBe('You are a weather assistant.'); expect( - spans.find(span => span.name === 'chat faux-model')!.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value, - ).toBeGreaterThan(0); - }, - }) - .expect({ - span: container => { - expect(container.items).toHaveLength(2); - for (const span of container.items) { - for (const attribute of CONTENT_ATTRIBUTES) { - expect(span.attributes[attribute]).toBeUndefined(); - } + JSON.parse(String(chat.attributes[GEN_AI_TOOL_DEFINITIONS]?.value)).map( + (tool: { name: string }) => tool.name, + ), + ).toEqual(['get_weather', 'broken']); + } + + // Mapped from pi-ai's shape to the conventions: the tool calls and tool results the + // second request carries are what Sentry's conversation view renders. + expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_INPUT_MESSAGES]?.value))).toEqual([ + { role: 'user', parts: [{ type: 'text', content: 'What is the weather in Berlin?' }] }, + ]); + const answerInput = JSON.parse(String(chats[1]!.attributes[GEN_AI_INPUT_MESSAGES]?.value)); + expect(answerInput.map((message: { role: string }) => message.role)).toEqual([ + 'user', + 'assistant', + 'tool', + 'tool', + ]); + expect(answerInput[1].parts).toEqual([ + { type: 'tool_call', id: 'call_1', name: 'get_weather', arguments: '{"city":"Berlin"}' }, + { type: 'tool_call', id: 'call_2', name: 'broken', arguments: '{}' }, + ]); + expect(answerInput[2].parts).toEqual([ + { + type: 'tool_call_response', + id: 'call_1', + name: 'get_weather', + result: 'It is 21 degrees and sunny in Berlin.', + }, + ]); + expect(chats.map(chat => chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).sort()).toEqual([ + '["stop"]', + '["tool_call"]', + ]); + expect(chats.map(chat => chat.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).join()).toContain( + 'It is 21 degrees and sunny in Berlin.', + ); + + const weather = spans.find(span => span.name === 'execute_tool get_weather')!; + expect(weather.parent_span_id).toBe(agent.span_id); + expect(weather.status).toBe('ok'); + expect(weather.attributes[SENTRY_OP]?.value).toBe(GEN_AI_EXECUTE_TOOL); + expect(weather.attributes[GEN_AI_TOOL_NAME]?.value).toBe('get_weather'); + expect(weather.attributes[GEN_AI_TOOL_DESCRIPTION]?.value).toBe('Get the current weather for a city.'); + expect(weather.attributes[GEN_AI_TOOL_CALL_ID_ATTRIBUTE]?.value).toBe('call_1'); + expect(weather.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value).toBe('{"city":"Berlin"}'); + expect(weather.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toContain('sunny in Berlin'); + expect(weather.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); + + const broken = spans.find(span => span.name === 'execute_tool broken')!; + expect(broken.parent_span_id).toBe(agent.span_id); + expect(broken.status).toBe('error'); + // The error result pi-durable gives the model, not the thrown error. + expect(broken.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toContain('[error] broken tool'); + + toolRunTraceId = agent.trace_id; + brokenToolSpanId = broken.span_id; + conversationIds.push(conversationId); + }, + }) + .expect({ + span: container => { + const spans = container.items; + const agent = spans.find(span => span.is_segment)!; + expect(agent.name).toBe('invoke_agent'); + expect(spans).toHaveLength(2); + + const chat = spans.find(span => span.name === 'chat faux-model')!; + expect(chat.parent_span_id).toBe(agent.span_id); + expect(chat.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).toContain('Goodbye.'); + + conversationIds.push(agent.attributes[GEN_AI_CONVERSATION_ID]?.value); + }, + }) + .expect({ + span: container => { + // The request that opened the Harness and submitted both inputs holds no agent work. + expect(container.items).toHaveLength(1); + expect(container.items[0]!.name).toBe('pi-durable-request'); + }, + }) + .expect({ + event: event => { + const exception = event.exception?.values?.[0]; + expect(exception?.value).toBe('broken tool'); + expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); + + errorTraceId = event.contexts?.trace?.trace_id; + errorSpanId = event.contexts?.trace?.span_id; + }, + }) + .start() + .completed(); + + // Both runs belong to the root conversation, so they share one conversation id. + expect(conversationIds).toHaveLength(2); + expect(conversationIds[0]).toBe(conversationIds[1]); + // The tool error is reported on the failing call's span, in the run's trace. + expect(errorTraceId).toBe(toolRunTraceId); + expect(errorSpanId).toBe(brokenToolSpanId); + }); + + test('records no message content when the client turns gen_ai recording off', async () => { + await createRunner() + .withEnv({ PI_DURABLE_RECORDING: 'off' }) + .unordered() + .expect({ + span: container => { + const spans = container.items; + expect(spans).toHaveLength(5); + for (const span of spans) { + for (const attribute of CONTENT_ATTRIBUTES) { + expect(span.attributes[attribute]).toBeUndefined(); } - }, - }) - .expect({ - span: container => { - expect(container.items).toHaveLength(1); - expect(container.items[0]!.name).toBe('pi-durable-request'); - }, - }) - .expect({ - event: event => { - expect(event.exception?.values?.[0]?.value).toBe('broken tool'); - }, - }) - .start() - .completed(); - }); - }, - PI_DURABLE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario-tools.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - test('reports app tool failures but not failures of the built-in coding tools', async () => { - const appErrors: unknown[] = []; - - // Ordered, with spans ignored: a captured failure of the built-in `bash` tool would be an - // extra event before the sentinel, which the scenario sends once everything else is sent. - // The two app failures are processed concurrently, so they may arrive in either order. - await createRunner() - .ignore('span') - .expect({ - event: event => { - expect(['app read failed', 'Intentional pi-durable tool failure']).toContain( - event.exception?.values?.[0]?.value, - ); - appErrors.push(event.exception?.values?.[0]?.value); - }, - }) - .expect({ - event: event => { - expect(['app read failed', 'Intentional pi-durable tool failure']).toContain( - event.exception?.values?.[0]?.value, - ); - appErrors.push(event.exception?.values?.[0]?.value); - }, - }) - .expect({ - event: event => { - expect(event.message).toBe('pi-durable tools done'); - }, - }) - .start() - .completed(); - - expect(appErrors.sort()).toEqual(['Intentional pi-durable tool failure', 'app read failed']); - }); - - test('records the tool result the model receives', async () => { - let errorSpanId: string | undefined; - let failingToolSpanId: string | undefined; + } + const weather = spans.find(span => span.name === 'execute_tool get_weather')!; + expect(weather.attributes[GEN_AI_TOOL_DESCRIPTION]?.value).toBe('Get the current weather for a city.'); + expect( + spans.find(span => span.name === 'chat faux-model')!.attributes[GEN_AI_USAGE_INPUT_TOKENS]?.value, + ).toBeGreaterThan(0); + }, + }) + .expect({ + span: container => { + expect(container.items).toHaveLength(2); + for (const span of container.items) { + for (const attribute of CONTENT_ATTRIBUTES) { + expect(span.attributes[attribute]).toBeUndefined(); + } + } + }, + }) + .expect({ + span: container => { + expect(container.items).toHaveLength(1); + expect(container.items[0]!.name).toBe('pi-durable-request'); + }, + }) + .expect({ + event: event => { + expect(event.exception?.values?.[0]?.value).toBe('broken tool'); + }, + }) + .start() + .completed(); + }); + }); - await createRunner() - .unordered() - .expect({ - event: event => { - const exception = event.exception?.values?.[0]; - expect(exception?.value).toBe('Intentional pi-durable tool failure'); - expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); - errorSpanId = event.contexts?.trace?.span_id; - }, - }) - .expect({ - event: event => { - expect(event.exception?.values?.[0]?.value).toBe('app read failed'); - }, - }) - .expect({ - span: container => { - const spans = container.items; - - const bash = spans.find(span => span.name === 'execute_tool bash')!; - expect(bash.status).toBe('error'); - expect(bash.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toMatch(/Command exited with code \d+/); - - const read = spans.find(span => span.name === 'execute_tool read')!; - expect(read.status).toBe('error'); - expect(read.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toContain('app read failed'); - - // The streamed output becomes the result, since the tool returns no content. - const streamer = spans.find(span => span.name === 'execute_tool streamer')!; - expect(streamer.status).toBe('ok'); - expect(streamer.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('line 1\nline 2\n'); - - const secret = spans.find(span => span.name === 'execute_tool secret')!; - expect(secret.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('redacted by afterTool'); - - const failNow = spans.find(span => span.name === 'execute_tool fail_now')!; - expect(failNow.status).toBe('error'); - failingToolSpanId = failNow.span_id; - }, - }) - .start() - .completed(); + createEsmTests(__dirname, 'scenario-tools.mjs', 'instrument.mjs', (createRunner, test) => { + test('reports app tool failures but not failures of the built-in coding tools', async () => { + const appErrors: unknown[] = []; + + // Ordered, with spans ignored: a captured failure of the built-in `bash` tool would be an + // extra event before the sentinel, which the scenario sends once everything else is sent. + // The two app failures are processed concurrently, so they may arrive in either order. + await createRunner() + .ignore('span') + .expect({ + event: event => { + expect(['app read failed', 'Intentional pi-durable tool failure']).toContain( + event.exception?.values?.[0]?.value, + ); + appErrors.push(event.exception?.values?.[0]?.value); + }, + }) + .expect({ + event: event => { + expect(['app read failed', 'Intentional pi-durable tool failure']).toContain( + event.exception?.values?.[0]?.value, + ); + appErrors.push(event.exception?.values?.[0]?.value); + }, + }) + .expect({ + event: event => { + expect(event.message).toBe('pi-durable tools done'); + }, + }) + .start() + .completed(); + + expect(appErrors.sort()).toEqual(['Intentional pi-durable tool failure', 'app read failed']); + }); + + test('records the tool result the model receives', async () => { + let errorSpanId: string | undefined; + let failingToolSpanId: string | undefined; + + await createRunner() + .unordered() + .expect({ + event: event => { + const exception = event.exception?.values?.[0]; + expect(exception?.value).toBe('Intentional pi-durable tool failure'); + expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); + errorSpanId = event.contexts?.trace?.span_id; + }, + }) + .expect({ + event: event => { + expect(event.exception?.values?.[0]?.value).toBe('app read failed'); + }, + }) + .expect({ + span: container => { + const spans = container.items; + + const bash = spans.find(span => span.name === 'execute_tool bash')!; + expect(bash.status).toBe('error'); + expect(bash.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toMatch(/Command exited with code \d+/); + + const read = spans.find(span => span.name === 'execute_tool read')!; + expect(read.status).toBe('error'); + expect(read.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toContain('app read failed'); + + // The streamed output becomes the result, since the tool returns no content. + const streamer = spans.find(span => span.name === 'execute_tool streamer')!; + expect(streamer.status).toBe('ok'); + expect(streamer.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('line 1\nline 2\n'); + + const secret = spans.find(span => span.name === 'execute_tool secret')!; + expect(secret.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('redacted by afterTool'); + + const failNow = spans.find(span => span.name === 'execute_tool fail_now')!; + expect(failNow.status).toBe('error'); + failingToolSpanId = failNow.span_id; + }, + }) + .start() + .completed(); + + expect(errorSpanId).toBe(failingToolSpanId); + }); + }); - expect(errorSpanId).toBe(failingToolSpanId); - }); - }, - PI_DURABLE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario-reports.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - test('captures failures pi-durable does not propagate, and tags idle compaction with its conversation', async () => { - let runConversationId: unknown; - let compactionConversationId: unknown; + createEsmTests(__dirname, 'scenario-reports.mjs', 'instrument.mjs', (createRunner, test) => { + test('captures failures pi-durable does not propagate, and tags idle compaction with its conversation', async () => { + let runConversationId: unknown; + let compactionConversationId: unknown; + + await createRunner() + .unordered() + .expect({ + event: event => { + const exception = event.exception?.values?.[0]; + expect(exception?.value).toBe('afterResponse hook failed'); + expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); + }, + }) + .expect({ + event: event => { + const exception = event.exception?.values?.[0]; + expect(exception?.value).toBe('card declined'); + expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); + }, + }) + .expect({ + span: container => { + const agent = container.items.find(span => span.is_segment)!; + expect(agent.name).toBe('invoke_agent'); + runConversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; + }, + }) + .expect({ + span: container => { + // A compaction started while idle belongs to no run, so its request is a trace of its own. + expect(container.items).toHaveLength(1); + const summary = container.items[0]!; + expect(summary.is_segment).toBe(true); + expect(summary.attributes[SENTRY_OP]?.value).toBe(GEN_AI_CHAT); + compactionConversationId = summary.attributes[GEN_AI_CONVERSATION_ID]?.value; + }, + }) + .start() + .completed(); + + expect(runConversationId).toMatch(UUID); + expect(compactionConversationId).toBe(runConversationId); + }); + }); - await createRunner() - .unordered() - .expect({ - event: event => { - const exception = event.exception?.values?.[0]; - expect(exception?.value).toBe('afterResponse hook failed'); - expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); - }, - }) - .expect({ - event: event => { - const exception = event.exception?.values?.[0]; - expect(exception?.value).toBe('card declined'); - expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); - }, - }) - .expect({ - span: container => { - const agent = container.items.find(span => span.is_segment)!; - expect(agent.name).toBe('invoke_agent'); - runConversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; - }, - }) - .expect({ - span: container => { - // A compaction started while idle belongs to no run, so its request is a trace of its own. - expect(container.items).toHaveLength(1); - const summary = container.items[0]!; - expect(summary.is_segment).toBe(true); - expect(summary.attributes[SENTRY_OP]?.value).toBe(GEN_AI_CHAT); - compactionConversationId = summary.attributes[GEN_AI_CONVERSATION_ID]?.value; - }, - }) - .start() - .completed(); + createEsmTests(__dirname, 'scenario-subagent.mjs', 'instrument.mjs', (createRunner, test) => { + test('nests subagent runs under the tool call that started them', async () => { + await createRunner() + .unordered() + .expect({ + span: container => { + const spans = container.items; + const parent = spans.find(span => span.is_segment)!; + const delegate = spans.find(span => span.name === 'execute_tool delegate')!; + expect(parent.name).toBe('invoke_agent'); + expect(delegate.parent_span_id).toBe(parent.span_id); + + // The subagent's run is a child of the delegating call, in the same trace, on its own + // conversation. + const child = spans.find(span => span.name === 'invoke_agent' && !span.is_segment)!; + expect(child.parent_span_id).toBe(delegate.span_id); + const parentConversation = String(parent.attributes[GEN_AI_CONVERSATION_ID]?.value); + const childConversation = String(child.attributes[GEN_AI_CONVERSATION_ID]?.value); + expect(parentConversation).toMatch(UUID); + expect(childConversation).toMatch(UUID); + expect(childConversation).not.toBe(parentConversation); + + const childChat = spans.find(span => span.parent_span_id === child.span_id)!; + expect(childChat.name).toBe('chat faux-model'); + expect(childChat.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(childConversation); + expect(delegate.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('Answer: Weather in Vienna?'); + }, + }) + .expect({ + span: container => { + const spans = container.items; + const parent = spans.find(span => span.is_segment)!; + const fork = spans.find(span => span.name === 'execute_tool fork_delegate')!; + const child = spans.find(span => span.name === 'invoke_agent' && !span.is_segment)!; + expect(fork.parent_span_id).toBe(parent.span_id); + expect(child.parent_span_id).toBe(fork.span_id); + expect(fork.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('Answer: Forked task.'); + }, + }) + .expect({ + span: container => { + const spans = container.items; + const batch = spans.find(span => span.name === 'execute_tool batch')!; + const inner = spans.find(span => span.name === 'execute_tool inner')!; + expect(batch.parent_span_id).toBe(spans.find(span => span.is_segment)!.span_id); + // A tool that runs another tool itself: the inner call ends with its own result. + expect(inner.parent_span_id).toBe(batch.span_id); + expect(inner.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('INNER RESULT'); + expect(batch.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('OUTER RESULT'); + }, + }) + .start() + .completed(); + }); + }); - expect(runConversationId).toMatch(UUID); - expect(compactionConversationId).toBe(runConversationId); - }); - }, - PI_DURABLE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario-subagent.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - test('nests subagent runs under the tool call that started them', async () => { - await createRunner() - .unordered() - .expect({ - span: container => { - const spans = container.items; - const parent = spans.find(span => span.is_segment)!; - const delegate = spans.find(span => span.name === 'execute_tool delegate')!; - expect(parent.name).toBe('invoke_agent'); - expect(delegate.parent_span_id).toBe(parent.span_id); - - // The subagent's run is a child of the delegating call, in the same trace, on its own - // conversation. - const child = spans.find(span => span.name === 'invoke_agent' && !span.is_segment)!; - expect(child.parent_span_id).toBe(delegate.span_id); - const parentConversation = String(parent.attributes[GEN_AI_CONVERSATION_ID]?.value); - const childConversation = String(child.attributes[GEN_AI_CONVERSATION_ID]?.value); - expect(parentConversation).toMatch(UUID); - expect(childConversation).toMatch(UUID); - expect(childConversation).not.toBe(parentConversation); - - const childChat = spans.find(span => span.parent_span_id === child.span_id)!; - expect(childChat.name).toBe('chat faux-model'); - expect(childChat.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(childConversation); - expect(delegate.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('Answer: Weather in Vienna?'); - }, - }) - .expect({ - span: container => { - const spans = container.items; - const parent = spans.find(span => span.is_segment)!; - const fork = spans.find(span => span.name === 'execute_tool fork_delegate')!; - const child = spans.find(span => span.name === 'invoke_agent' && !span.is_segment)!; - expect(fork.parent_span_id).toBe(parent.span_id); - expect(child.parent_span_id).toBe(fork.span_id); - expect(fork.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('Answer: Forked task.'); - }, - }) - .expect({ - span: container => { - const spans = container.items; - const batch = spans.find(span => span.name === 'execute_tool batch')!; - const inner = spans.find(span => span.name === 'execute_tool inner')!; - expect(batch.parent_span_id).toBe(spans.find(span => span.is_segment)!.span_id); - // A tool that runs another tool itself: the inner call ends with its own result. - expect(inner.parent_span_id).toBe(batch.span_id); - expect(inner.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('INNER RESULT'); - expect(batch.attributes[GEN_AI_TOOL_CALL_RESULT]?.value).toBe('OUTER RESULT'); - }, - }) - .start() - .completed(); - }); - }, - PI_DURABLE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario-retention.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - // Bun does not implement `v8.setFlagsFromString`, which the scenario needs to force GC. - test.runIf(supports({ runtimes: ['node', 'deno'] }))( - 'does not keep the traces of tool calls alive for subagent conversations that never run', - async () => { - await createRunner() - .ignore('span') - .expect({ event: { message: 'pi-durable retention', extra: { toolCalls: 3, alive: 0 } } }) - .start() - .completed(); - }, - ); - }, - PI_DURABLE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario-restart-abort.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - test('starts no run for an abort handler in a Harness that never saw the run', async () => { - await createRunner() - .ignore('span') - .expect({ - event: { - message: 'pi-durable restart abort', - extra: { status: 'unanswered', reason: 'aborted', runsStarted: 0 }, - }, - }) - .start() - .completed(); - }); - }, - PI_DURABLE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario-interrupted.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - test('reports a provider that throws as a fault, and nothing for failed, aborted or closed runs', async () => { + createEsmTests(__dirname, 'scenario-retention.mjs', 'instrument.mjs', (createRunner, test) => { + // Bun does not implement `v8.setFlagsFromString`, which the scenario needs to force GC. + test.runIf(supports({ runtimes: ['node', 'deno'] }))( + 'does not keep the traces of tool calls alive for subagent conversations that never run', + async () => { await createRunner() .ignore('span') - .expect({ - event: event => { - const exception = event.exception?.values?.[0]; - expect(exception?.value).toBe('provider broke'); - expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); - }, - }) - .expect({ - event: event => { - expect(event.message).toBe('pi-durable interrupted done'); - }, - }) + .expect({ event: { message: 'pi-durable retention', extra: { toolCalls: 3, alive: 0 } } }) .start() .completed(); - }); + }, + ); + }); - test('ends every run that does not end with an answer', async () => { - await createRunner() - .unordered() - .expect({ - span: container => { - // 1. The model request failed: the run and the request are errors. - const chat = container.items.find(span => span.name === 'chat faux-model')!; - expect(chat.attributes[GEN_AI_INPUT_MESSAGES]?.value).toContain('Fail please.'); - expect(chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).toBe('["error"]'); - expect(chat.status).toBe('error'); - expect(chat.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('internal_error'); - const agent = container.items.find(span => span.is_segment)!; - expect(agent.name).toBe('invoke_agent'); - expect(agent.status).toBe('error'); - expect(agent.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('model_error'); - }, - }) - .expect({ - span: container => { - // 2. The provider threw: the generation faulted. - const chat = container.items.find(span => span.name === 'chat faux-model')!; - expect(chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toBeUndefined(); - expect(chat.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('internal_error'); - const agent = container.items.find(span => span.is_segment)!; - expect(agent.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('internal_error'); - }, - }) - .expect({ - span: container => { - // 3. Aborted while the tool ran: cancelled, which streamed spans report as ok. - const slow = container.items.find(span => span.name === 'execute_tool slow')!; - expect(slow.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value).toBe('{"job":"abort"}'); - expect(slow.status).toBe('ok'); - expect(container.items.find(span => span.is_segment)!.status).toBe('ok'); - }, - }) - .expect({ - span: container => { - // 4. Aborted while the model request ran. - const chat = container.items.find(span => span.name === 'chat faux-model')!; - expect(chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).toBe('["aborted"]'); - expect(container.items).toHaveLength(2); - }, - }) - .expect({ - span: container => { - // 5. The Harness closed while the tool ran: the run still ends, so the trace has a segment. - const slow = container.items.find(span => span.name === 'execute_tool slow')!; - expect(slow.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value).toBe('{"job":"close"}'); - const agent = container.items.find(span => span.is_segment)!; - expect(agent.name).toBe('invoke_agent'); - expect(slow.parent_span_id).toBe(agent.span_id); - }, - }) - .expect({ - span: container => { - // 6. The run failed, and the Harness closed right after `wait()`, before the commit that - // ended the run resolved: the run keeps its own status. - const chat = container.items.find(span => span.name === 'chat faux-model')!; - expect(chat.attributes[GEN_AI_INPUT_MESSAGES]?.value).toContain('Fail, then close.'); - const agent = container.items.find(span => span.is_segment)!; - expect(agent.status).toBe('error'); - expect(agent.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('model_error'); - }, - }) - .start() - .completed(); - }); - }, - PI_DURABLE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario-concurrent.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - test('keeps concurrent runs apart and out of the scopes of the requests that submitted them', async () => { - const runTraceIds = new Set(); - const errorTraceIds = new Set(); + createEsmTests(__dirname, 'scenario-restart-abort.mjs', 'instrument.mjs', (createRunner, test) => { + test('starts no run for an abort handler in a Harness that never saw the run', async () => { + await createRunner() + .ignore('span') + .expect({ + event: { + message: 'pi-durable restart abort', + extra: { status: 'unanswered', reason: 'aborted', runsStarted: 0 }, + }, + }) + .start() + .completed(); + }); + }); - await createRunner() - .unordered() - .expect({ - span: container => { - const spans = container.items; - const agent = spans.find(span => span.is_segment)!; - expect(agent.name).toBe('invoke_agent'); - const chats = spans.filter(span => span.name === 'chat faux-model'); - expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_INPUT_MESSAGES]?.value))[0].parts[0].content).toBe( - 'request A', - ); - // The run's own conversation id, not the one the request set on its scope. - const conversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; - expect(conversationId).toMatch(UUID); - for (const span of spans) { - expect(span.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); - expect(span.attributes['request']).toBeUndefined(); - } - expect( - spans.find(span => span.name === 'execute_tool work')!.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value, - ).toBe('{"tag":"request A"}'); - runTraceIds.add(agent.trace_id); - }, - }) - .expect({ - span: container => { - const spans = container.items; - const agent = spans.find(span => span.is_segment)!; - expect(agent.name).toBe('invoke_agent'); - const chats = spans.filter(span => span.name === 'chat faux-model'); - expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_INPUT_MESSAGES]?.value))[0].parts[0].content).toBe( - 'request B', - ); - const conversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; - expect(conversationId).toMatch(UUID); - for (const span of spans) { - expect(span.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); - expect(span.attributes['request']).toBeUndefined(); - } - expect( - spans.find(span => span.name === 'execute_tool work')!.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value, - ).toBe('{"tag":"request B"}'); - runTraceIds.add(agent.trace_id); - }, - }) - .expect({ - event: event => { - expect(event.exception?.values?.[0]?.value).toBe('work failed for request A'); - expect(event.tags?.request).toBeUndefined(); - errorTraceIds.add(String(event.contexts?.trace?.trace_id)); - }, - }) - .expect({ - event: event => { - expect(event.exception?.values?.[0]?.value).toBe('work failed for request B'); - expect(event.tags?.request).toBeUndefined(); - errorTraceIds.add(String(event.contexts?.trace?.trace_id)); - }, - }) - .start() - .completed(); + createEsmTests(__dirname, 'scenario-interrupted.mjs', 'instrument.mjs', (createRunner, test) => { + test('reports a provider that throws as a fault, and nothing for failed, aborted or closed runs', async () => { + await createRunner() + .ignore('span') + .expect({ + event: event => { + const exception = event.exception?.values?.[0]; + expect(exception?.value).toBe('provider broke'); + expect(exception?.mechanism).toEqual({ type: 'auto.ai.pi_durable', handled: false }); + }, + }) + .expect({ + event: event => { + expect(event.message).toBe('pi-durable interrupted done'); + }, + }) + .start() + .completed(); + }); + + test('ends every run that does not end with an answer', async () => { + await createRunner() + .unordered() + .expect({ + span: container => { + // 1. The model request failed: the run and the request are errors. + const chat = container.items.find(span => span.name === 'chat faux-model')!; + expect(chat.attributes[GEN_AI_INPUT_MESSAGES]?.value).toContain('Fail please.'); + expect(chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).toBe('["error"]'); + expect(chat.status).toBe('error'); + expect(chat.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('internal_error'); + const agent = container.items.find(span => span.is_segment)!; + expect(agent.name).toBe('invoke_agent'); + expect(agent.status).toBe('error'); + expect(agent.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('model_error'); + }, + }) + .expect({ + span: container => { + // 2. The provider threw: the generation faulted. + const chat = container.items.find(span => span.name === 'chat faux-model')!; + expect(chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]).toBeUndefined(); + expect(chat.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('internal_error'); + const agent = container.items.find(span => span.is_segment)!; + expect(agent.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('internal_error'); + }, + }) + .expect({ + span: container => { + // 3. Aborted while the tool ran: cancelled, which streamed spans report as ok. + const slow = container.items.find(span => span.name === 'execute_tool slow')!; + expect(slow.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value).toBe('{"job":"abort"}'); + expect(slow.status).toBe('ok'); + expect(container.items.find(span => span.is_segment)!.status).toBe('ok'); + }, + }) + .expect({ + span: container => { + // 4. Aborted while the model request ran. + const chat = container.items.find(span => span.name === 'chat faux-model')!; + expect(chat.attributes[GEN_AI_RESPONSE_FINISH_REASONS]?.value).toBe('["aborted"]'); + expect(container.items).toHaveLength(2); + }, + }) + .expect({ + span: container => { + // 5. The Harness closed while the tool ran: the run still ends, so the trace has a segment. + const slow = container.items.find(span => span.name === 'execute_tool slow')!; + expect(slow.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value).toBe('{"job":"close"}'); + const agent = container.items.find(span => span.is_segment)!; + expect(agent.name).toBe('invoke_agent'); + expect(slow.parent_span_id).toBe(agent.span_id); + }, + }) + .expect({ + span: container => { + // 6. The run failed, and the Harness closed right after `wait()`, before the commit that + // ended the run resolved: the run keeps its own status. + const chat = container.items.find(span => span.name === 'chat faux-model')!; + expect(chat.attributes[GEN_AI_INPUT_MESSAGES]?.value).toContain('Fail, then close.'); + const agent = container.items.find(span => span.is_segment)!; + expect(agent.status).toBe('error'); + expect(agent.attributes[SENTRY_STATUS_MESSAGE]?.value).toBe('model_error'); + }, + }) + .start() + .completed(); + }); + }); - // Each tool error belongs to its run's trace. - expect(errorTraceIds).toEqual(runTraceIds); - }); - }, - PI_DURABLE_DEPENDENCIES, - ); - - createEsmAndCjsTests( - __dirname, - 'scenario-compaction.mjs', - 'instrument.mjs', - (createRunner, test, mode) => { - if (mode === 'cjs') { - return; - } - - test('keeps a compaction that pi-durable starts for a run inside that run', async () => { - await createRunner() - .unordered() - .expect({ - span: container => { - const spans = container.items; - const summary = spans.find(span => - String(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).includes('Summary'), - )!; - const agent = spans.find(span => span.is_segment)!; - expect(agent.name).toBe('invoke_agent'); - expect(summary.parent_span_id).toBe(agent.span_id); - // pi-durable completes the summary without streaming; the run's own request streams. - expect(summary.attributes[GEN_AI_RESPONSE_STREAMING]).toBeUndefined(); - const answer = spans.find(span => - String(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).includes('Third answer'), - )!; - expect(answer.parent_span_id).toBe(agent.span_id); - expect(answer.attributes[GEN_AI_RESPONSE_STREAMING]?.value).toBe(true); - }, - }) - .expect({ - span: container => expect(container.items.find(span => span.is_segment)!.name).toBe('invoke_agent'), - }) - .expect({ - span: container => expect(container.items.find(span => span.is_segment)!.name).toBe('invoke_agent'), - }) - .start() - .completed(); - }); - }, - PI_DURABLE_DEPENDENCIES, - ); + createEsmTests(__dirname, 'scenario-concurrent.mjs', 'instrument.mjs', (createRunner, test) => { + test('keeps concurrent runs apart and out of the scopes of the requests that submitted them', async () => { + const runTraceIds = new Set(); + const errorTraceIds = new Set(); + + await createRunner() + .unordered() + .expect({ + span: container => { + const spans = container.items; + const agent = spans.find(span => span.is_segment)!; + expect(agent.name).toBe('invoke_agent'); + const chats = spans.filter(span => span.name === 'chat faux-model'); + expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_INPUT_MESSAGES]?.value))[0].parts[0].content).toBe( + 'request A', + ); + // The run's own conversation id, not the one the request set on its scope. + const conversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; + expect(conversationId).toMatch(UUID); + for (const span of spans) { + expect(span.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); + expect(span.attributes['request']).toBeUndefined(); + } + expect( + spans.find(span => span.name === 'execute_tool work')!.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value, + ).toBe('{"tag":"request A"}'); + runTraceIds.add(agent.trace_id); + }, + }) + .expect({ + span: container => { + const spans = container.items; + const agent = spans.find(span => span.is_segment)!; + expect(agent.name).toBe('invoke_agent'); + const chats = spans.filter(span => span.name === 'chat faux-model'); + expect(JSON.parse(String(chats[0]!.attributes[GEN_AI_INPUT_MESSAGES]?.value))[0].parts[0].content).toBe( + 'request B', + ); + const conversationId = agent.attributes[GEN_AI_CONVERSATION_ID]?.value; + expect(conversationId).toMatch(UUID); + for (const span of spans) { + expect(span.attributes[GEN_AI_CONVERSATION_ID]?.value).toBe(conversationId); + expect(span.attributes['request']).toBeUndefined(); + } + expect( + spans.find(span => span.name === 'execute_tool work')!.attributes[GEN_AI_TOOL_CALL_ARGUMENTS]?.value, + ).toBe('{"tag":"request B"}'); + runTraceIds.add(agent.trace_id); + }, + }) + .expect({ + event: event => { + expect(event.exception?.values?.[0]?.value).toBe('work failed for request A'); + expect(event.tags?.request).toBeUndefined(); + errorTraceIds.add(String(event.contexts?.trace?.trace_id)); + }, + }) + .expect({ + event: event => { + expect(event.exception?.values?.[0]?.value).toBe('work failed for request B'); + expect(event.tags?.request).toBeUndefined(); + errorTraceIds.add(String(event.contexts?.trace?.trace_id)); + }, + }) + .start() + .completed(); + + // Each tool error belongs to its run's trace. + expect(errorTraceIds).toEqual(runTraceIds); + }); + }); + + createEsmTests(__dirname, 'scenario-compaction.mjs', 'instrument.mjs', (createRunner, test) => { + test('keeps a compaction that pi-durable starts for a run inside that run', async () => { + await createRunner() + .unordered() + .expect({ + span: container => { + const spans = container.items; + const summary = spans.find(span => + String(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).includes('Summary'), + )!; + const agent = spans.find(span => span.is_segment)!; + expect(agent.name).toBe('invoke_agent'); + expect(summary.parent_span_id).toBe(agent.span_id); + // pi-durable completes the summary without streaming; the run's own request streams. + expect(summary.attributes[GEN_AI_RESPONSE_STREAMING]).toBeUndefined(); + const answer = spans.find(span => + String(span.attributes[GEN_AI_OUTPUT_MESSAGES]?.value).includes('Third answer'), + )!; + expect(answer.parent_span_id).toBe(agent.span_id); + expect(answer.attributes[GEN_AI_RESPONSE_STREAMING]?.value).toBe(true); + }, + }) + .expect({ + span: container => expect(container.items.find(span => span.is_segment)!.name).toBe('invoke_agent'), + }) + .expect({ + span: container => expect(container.items.find(span => span.is_segment)!.name).toBe('invoke_agent'), + }) + .start() + .completed(); + }); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0fecf4b4ca2..ca8b7afe45e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -453,7 +453,7 @@ importers: version: 0.133.0(zod@4.6.7) '@google/genai': specifier: ^1.20.0 - version: 1.20.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(encoding@0.1.13)(supports-color@10.0.0) + version: 1.52.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0) '@langchain/core': specifier: ^1.2.17 version: 1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3) @@ -607,9 +607,21 @@ importers: '@aws-sdk/client-sqs': specifier: ^3.1046.0 version: 3.1068.0 + '@earendil-works/chord': + specifier: ^1.4.0 + version: 1.4.0 + '@earendil-works/pi-ai': + specifier: ^1.4.0 + version: 1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + '@earendil-works/pi-durable': + specifier: ^1.4.0 + version: 1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + '@flue/runtime': + specifier: 2.0.3 + version: 2.0.3(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)(typescript@7.0.2)(ws@8.21.3)(zod@4.6.7) '@google/genai': specifier: ^1.20.0 - version: 1.20.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(encoding@0.1.13)(supports-color@10.0.0) + version: 1.52.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0) '@growthbook/growthbook': specifier: ^1.6.5 version: 1.6.5 @@ -838,6 +850,9 @@ importers: unstorage: specifier: 2.0.0-alpha.7 version: 2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@3.7.4)(ofetch@1.5.1) + valibot: + specifier: 1.4.2 + version: 1.4.2(typescript@7.0.2) winston: specifier: ^3.17.0 version: 3.19.0 @@ -2157,7 +2172,7 @@ importers: version: 29.7.0 jsdom-worker: specifier: ^0.3.0 - version: 0.3.0(node-fetch@2.7.0(encoding@0.1.13)) + version: 0.3.0(node-fetch@3.3.2) packages/replay-worker: dependencies: @@ -2779,6 +2794,15 @@ packages: '@angular/platform-browser': 14.3.0 rxjs: ^6.5.3 || ^7.4.0 + '@anthropic-ai/sdk@0.129.0': + resolution: {integrity: sha512-MH7LB20kNpGLUpPTe2OG5XvL/KhX8HUO9XyBaFjgFk6TLS4Xjt0VPIuMKywe7POKKPslX+QxSlVok9e+8kG5Nw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + '@anthropic-ai/sdk@0.133.0': resolution: {integrity: sha512-jlqUN8p8ESTJj+GJnA4KjzbEfF1waEoGzRT8jwytlYGFFgv+/DUQOxlYGvYw10UzhD1kwLqYzxvA3FrTB2YWuQ==} hasBin: true @@ -2797,6 +2821,15 @@ packages: zod: optional: true + '@anthropic-ai/sdk@0.91.1': + resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + '@apm-js-collab/code-transformer-bundler-plugins@0.7.5': resolution: {integrity: sha512-/nmTAee5mBly9QiIQSGIKITv9TcEyLTQJFbYjfyWCEfYofxQgurMrT5fLDIRneSPqICucRTxgPMHmc6/q7cgfw==} engines: {node: '>=18.0.0'} @@ -2955,6 +2988,14 @@ packages: resolution: {integrity: sha512-zOXUUnilC6lgCsQtp77p/QNPmRlTES9Xi6tlDwbR6kfC/kz5PCzZckgHWm5z+8DskdwuMAbFDq61x3zr10GEEQ==} engines: {node: '>=20.0.0'} + '@aws-sdk/client-bedrock-runtime@3.1048.0': + resolution: {integrity: sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-bedrock-runtime@3.1127.0': + resolution: {integrity: sha512-IDl/lrPb90aH+pZFHGNDmgH9nAUQj5PlZH1sJ3w7RikctyjHSnY3oNjZhrLoaBoQn/rNK0zsP6OHEqEhj2tdLA==} + engines: {node: '>=20.0.0'} + '@aws-sdk/client-bedrock-runtime@3.1159.0': resolution: {integrity: sha512-Zj744tR9wNs+jjvb7C8zDBDxjJhGJSMwIHnOc9zdsDdrRirai4CtR5iQ5BMP1qfazjH6jYKCxGpZC1BOUnslKg==} engines: {node: '>=20.0.0'} @@ -3111,6 +3152,14 @@ packages: resolution: {integrity: sha512-Ob9GCZ3YnGPws696zTYW/rLFYHa9QLh6DgTCnkHb+0jhHNBgHWM8Ui2b+yrIjkfDsRVsglyrOey6tOOG/VWxMA==} engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1048.0': + resolution: {integrity: sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1127.0': + resolution: {integrity: sha512-Dv2TMWBshJ+tF6ahs2Sy5bh4Iabsd4GAQqVvE9XZmYmnoaVbpS2QKIKE/HRacc7bTtbjEEvP+laGzHvHlf1CiQ==} + engines: {node: '>=20.0.0'} + '@aws-sdk/token-providers@3.1159.0': resolution: {integrity: sha512-IJvghXXcEpqTWGjrjJwxBdEecHeXp+t9FaybRf8Zf0wYW84KN1snXBsYOdfDBBx78ChwfJxH8KIHqmhNZuDLcw==} engines: {node: '>=20.0.0'} @@ -4389,6 +4438,36 @@ packages: '@dxup/unimport@0.1.2': resolution: {integrity: sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ==} + '@earendil-works/chord@1.4.0': + resolution: {integrity: sha512-dTbgyz4xgDS5KAUFx1bFENqj0Cr1N3tYxJJOYStc6g6HS78FSBZydyoCwLoddiQRF+VgRS0fpgAi+iKxjZ3BQA==} + engines: {node: '>=22.19.0'} + + '@earendil-works/pi-agent-core@0.83.0': + resolution: {integrity: sha512-RorGp9OH5l3ElpuC5a5ZQ2eWcchZGXflXRzVGkV99y3y6tT+LLNyxoYIdVKvTKWEObwhExeQbTH0fI2tE4iX4g==} + engines: {node: '>=22.19.0'} + + '@earendil-works/pi-ai@0.83.0': + resolution: {integrity: sha512-m3IZD4g3er0V8TC9+Vpgw/sjTKqcJlkcIBy/JvsgRubuuik3tAVzyugUg4rVrShIkkOT69mEd34NEqKUIsl6JQ==} + engines: {node: '>=22.19.0'} + hasBin: true + + '@earendil-works/pi-ai@1.4.0': + resolution: {integrity: sha512-hQM/C4+1w8rHYACzDDf+0vQMnEnpzIop1tptIERVgR3ZeT/hHSzBNG8PaWPgxWQjqSeKCyBZSyp8uTT6eKK1pQ==} + engines: {node: '>=22.19.0'} + hasBin: true + + '@earendil-works/pi-codemode@1.4.0': + resolution: {integrity: sha512-23CNAXpS9DuLzPMyYuvrXVVio46x4YhffCgCqaWmF7F5H8PooqjrQvX4ucF6M0BhUd+x2tV6r1J0dv8cCME4Cg==} + engines: {node: '>=22.19.0'} + + '@earendil-works/pi-durable@1.4.0': + resolution: {integrity: sha512-EtIyXH1MBSXdJSkodmy+CYXuwfve3gPrD4GNCZ0XmuRWLPupwRlNQ7fdaXjRcLoKb6SWkT/yEzK+sMmrqvYK+Q==} + engines: {node: '>=22.19.0'} + + '@earendil-works/pi-telemetry@1.4.0': + resolution: {integrity: sha512-MLIfowwUYIY65jSjMX82paZOrX/NTvLk6D08h+3Jq42a99RzUOewzuCR0zOlPul3D6UBfddat76VT6LvsHEs1w==} + engines: {node: '>=22.19.0'} + '@edge-runtime/primitives@6.0.0': resolution: {integrity: sha512-FqoxaBT+prPBHBwE1WXS1ocnu/VLTQyZ6NMUBAdbP7N2hsFTTxMC/jMu2D/8GAlMQfxeuppcPuCUk/HO3fpIvA==} engines: {node: '>=18'} @@ -5298,6 +5377,10 @@ packages: '@fastify/proxy-addr@5.1.0': resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} + '@flue/runtime@2.0.3': + resolution: {integrity: sha512-RfWyZG9x2hlDb1264XTESX42tzzG3AA8er3XjaTIxIMh28pTD91zKd9Jh6PFXKeTkZegLsGiJKDxmiCddlyeug==} + engines: {node: '>=22.19.0'} + '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} @@ -5349,11 +5432,20 @@ packages: resolution: {integrity: sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==} engines: {node: '>=10'} - '@google/genai@1.20.0': - resolution: {integrity: sha512-QdShxO9LX35jFogy3iKprQNqgKKveux4H2QjOnyIvyHRuGi6PHiz3fjNf8Y0VPY8o5V2fHqR2XqiSVoz7yZs0w==} + '@google/genai@1.52.0': + resolution: {integrity: sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==} engines: {node: '>=20.0.0'} peerDependencies: - '@modelcontextprotocol/sdk': ^1.11.4 + '@modelcontextprotocol/sdk': ^1.25.2 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + + '@google/genai@2.21.0': + resolution: {integrity: sha512-+PDtco2/Z0ONdzCGekCoCT+O1VJS9xJQNN4XzQpXG/t3El/SWWMkCWlFRO1KmivOHPa4Q0VjUYu1HBKCZ/v33Q==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.2 peerDependenciesMeta: '@modelcontextprotocol/sdk': optional: true @@ -6114,6 +6206,14 @@ packages: engines: {node: '>=18'} hasBin: true + '@mistralai/mistralai@2.2.6': + resolution: {integrity: sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@mistralai/mistralai@3.3.0': resolution: {integrity: sha512-b5NSkQ8lFBxwZfA4Vj5oHxs7Jw7VrZRI2E9g95sigQiTi3NsQ//o6y3nDXjKUPWjksfagjH3STEFCYBg19SMvg==} peerDependencies: @@ -8309,6 +8409,9 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@silvia-odwyer/photon@0.3.3': + resolution: {integrity: sha512-8BhUjEch4slwRe8uXnaA4vcA5uiiOTT90UMsxulOj2gN98X1p0q9Z4Ysk4DkD05uNgbR9XoSqtZ37w+33w4QKQ==} + '@simple-dom/interface@1.4.0': resolution: {integrity: sha512-l5qumKFWU0S+4ZzMaLXFU8tQZsicHEMEyAxI5kDFGhJsRqDwe0a7/iPA/GdxlGyDKseQQAgIz5kzU7eXTrlSpA==} @@ -8411,6 +8514,10 @@ packages: resolution: {integrity: sha512-/GGcPoNJ2nzGytdAanpbx7mpp9Coa8LP+/Rpmbktr8YeLuIXs9B0SEz+X3AeonHdyzOIFJdKbN7jOZ+Tj+pMGw==} engines: {node: '>=18.0.0'} + '@smithy/node-http-handler@4.7.3': + resolution: {integrity: sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==} + engines: {node: '>=18.0.0'} + '@smithy/property-provider@4.2.14': resolution: {integrity: sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==} engines: {node: '>=18.0.0'} @@ -9356,6 +9463,11 @@ packages: peerDependencies: vue: '>=3.5.18' + '@valibot/to-json-schema@1.8.0': + resolution: {integrity: sha512-a0M+uwCuQZEPAo65NYkFSJ14O5c213KoSZmPdoCfuFvUhfULi4T2Z6Tpjv6lTVMW9RoLm74RpRAjNEa43KcN+g==} + peerDependencies: + valibot: ^1.5.0 + '@vercel/nft@1.11.0': resolution: {integrity: sha512-m1QFg+U+3yPOnP1xSYJ73UIRxLOXdts1JOhiOiyPYqEsALgrXFFINvgUaD6R6iNvaBFAjHllBCbkfx4FuOdpaA==} engines: {node: '>=20'} @@ -9732,6 +9844,10 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + agent-base@9.0.0: + resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} + engines: {node: '>= 20'} + agentkeepalive@4.2.1: resolution: {integrity: sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==} engines: {node: '>= 8.0.0'} @@ -9897,6 +10013,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + argparse@3.0.4: + resolution: {integrity: sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA==} + args@5.0.3: resolution: {integrity: sha512-h6k/zfFgusnv3i5TU08KQkVKuCPBtL/PWQbWkHUxvJrZ2nAyeaUupneemcrgn1xmqxPQsPIzwkUhOpoqPDRZuA==} engines: {node: '>= 6.0.0'} @@ -11111,6 +11230,10 @@ packages: resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} engines: {node: '>= 6'} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + data-urls@4.0.0: resolution: {integrity: sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==} engines: {node: '>=14'} @@ -12407,6 +12530,10 @@ packages: fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + fflate@0.4.9: resolution: {integrity: sha512-zdxgIEddhfsyCaWpJ2SdXEP8ZMrKJ6+5jl4OupODcywU0IhRk6gdXuVGcPICyfx2H97hVK7xmJtRLPjkxAX8Vw==} @@ -12529,6 +12656,10 @@ packages: resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} @@ -12624,17 +12755,17 @@ packages: resolution: {integrity: sha512-Ms7fNifGv0XVU+6eIyL9LB7RVESeML9+cMvkwGS70xyD6w2Z80wl6RiqiJ9k1KFlJCUTQqFFc8tXmPQfSKUe8g==} engines: {node: '>=10'} - gaxios@6.7.1: - resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} - engines: {node: '>=14'} + gaxios@7.3.1: + resolution: {integrity: sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ==} + engines: {node: '>=18'} gcp-metadata@4.2.1: resolution: {integrity: sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==} engines: {node: '>=10'} - gcp-metadata@6.1.1: - resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} - engines: {node: '>=14'} + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} @@ -12803,16 +12934,16 @@ packages: engines: {node: '>=0.6.0'} hasBin: true + google-auth-library@10.9.1: + resolution: {integrity: sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw==} + engines: {node: '>=18'} + google-auth-library@7.0.3: resolution: {integrity: sha512-6wJNYqY1QUr5I2lWaUkkzOT2b9OCNhNQrdFOt/bsBbGb7T7NCdEvrBsXraUm+KTUGk2xGlQ7m9RgUd4Llcw8NQ==} engines: {node: '>=10'} - google-auth-library@9.15.1: - resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} - engines: {node: '>=14'} - - google-logging-utils@0.0.2: - resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} engines: {node: '>=14'} google-p12-pem@3.1.4: @@ -12853,10 +12984,6 @@ packages: resolution: {integrity: sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==} engines: {node: '>=10'} - gtoken@7.1.0: - resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} - engines: {node: '>=14.0.0'} - gzip-size@7.0.0: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -13090,6 +13217,10 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + http-proxy-agent@9.1.0: + resolution: {integrity: sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==} + engines: {node: '>= 20'} + http-proxy-middleware@2.0.10: resolution: {integrity: sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==} engines: {node: '>=12.0.0'} @@ -13119,6 +13250,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + https-proxy-agent@9.1.0: + resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} + engines: {node: '>= 20'} + httpxy@0.5.1: resolution: {integrity: sha512-JPhqYiixe1A1I+MXDewWDZqeudBGU8Q9jCHYN8ML+779RQzLjTi78HBvWz4jMxUD6h2/vUL12g4q/mFM0OUw1A==} @@ -13683,6 +13818,10 @@ packages: resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true + js-yaml@5.5.0: + resolution: {integrity: sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg==} + hasBin: true + jsdoc-type-pratt-parser@4.1.0: resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} engines: {node: '>=12.0.0'} @@ -13948,6 +14087,9 @@ packages: launch-editor@2.14.1: resolution: {integrity: sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==} + layerr@3.0.0: + resolution: {integrity: sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==} + lazystream@1.0.1: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} @@ -15107,6 +15249,11 @@ packages: resolution: {integrity: sha512-Si/bzYiKRHOB8/a99T2+SDGN582ONDMSTlJr5oCkT6GtnqPjZ2s10eoQRYkW9ZHwjVxONL+W8Fb+qR0AHMQsdg==} engines: {node: '>=20'} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} @@ -15119,6 +15266,10 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-forge@1.4.0: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} @@ -15421,6 +15572,42 @@ packages: zod: optional: true + openai@6.26.0: + resolution: {integrity: sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + openai@7.19.0: + resolution: {integrity: sha512-MX2s3u2L5racTO0CC/SWpCOasJQBCJrqLKXK+l82cAhdeF8mPMBEe/gxMm0ZFa2xpKpOFLRjxv5afYEZbBXmbQ==} + engines: {node: '>=22.0.0'} + peerDependencies: + '@aws-sdk/credential-provider-node': '>=3.972.0 <4' + '@smithy/hash-node': '>=4.3.0 <5' + '@smithy/signature-v4': '>=5.4.0 <6' + undici: '>=5 <9' + ws: ^8.21.0 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@aws-sdk/credential-provider-node': + optional: true + '@smithy/hash-node': + optional: true + '@smithy/signature-v4': + optional: true + undici: + optional: true + ws: + optional: true + zod: + optional: true + openai@7.31.0: resolution: {integrity: sha512-ZGAzauuYb0DdB53qVbIR+X8rdmNtWgiElrrMthbZCVreLcIsQJBwzAIWUbpB2FkQu2Cj6OJ9zJR1EKMUYn4/SQ==} engines: {node: '>=22.0.0'} @@ -15700,6 +15887,9 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + partial-json@0.1.7: + resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} + pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} @@ -16496,10 +16686,23 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + proxy-addr@2.0.8: resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} engines: {node: '>= 0.10'} + proxy-agent-negotiate@1.1.0: + resolution: {integrity: sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==} + engines: {node: '>= 20'} + peerDependencies: + kerberos: ^2.0.0 + peerDependenciesMeta: + kerberos: + optional: true + proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} @@ -16554,6 +16757,9 @@ packages: quick-temp@0.1.8: resolution: {integrity: sha512-YsmIFfD9j2zaFwJkzI6eMG7y0lQP7YeWzgtFgNl38pGWZBSXJooZbOWwkcRot7Vt0Fg9L23pX0tqWU3VvLDsiA==} + quickjs-wasi@3.6.2: + resolution: {integrity: sha512-FCqGtGOrMgzUiIrMNMA2YnsOxCNwo31dzqXvclXUC6xeT35NJLKXQJsvbeCTjvoFAwZgEAPg8U6+KAPDGXn8Mg==} + quote-unquote@1.0.0: resolution: {integrity: sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==} @@ -18304,6 +18510,12 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} + typebox@1.3.27: + resolution: {integrity: sha512-zu+jc1pcy4UiNThxikUr36f0Rybk9PEeCg/NE6adeWr/SKsdNO4EzZHYRDlv2YCVAfj3Odq3dESSo/jNyoBXzA==} + + typebox@1.3.7: + resolution: {integrity: sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -18376,6 +18588,10 @@ packages: resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} engines: {node: '>=18'} + ulidx@2.4.1: + resolution: {integrity: sha512-xY7c8LPyzvhvew0Fn+Ek3wBC9STZAuDI/Y5andCKi9AX6/jvfaX45PhsDX8oxgPL0YFp0Jhr8qWMbS/p9375Xg==} + engines: {node: '>=16'} + ultrahtml@1.7.0: resolution: {integrity: sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==} @@ -20038,6 +20254,13 @@ snapshots: rxjs: 7.8.2 tslib: 2.8.1 + '@anthropic-ai/sdk@0.129.0(zod@4.6.7)': + dependencies: + json-schema-to-ts: 3.1.1 + standardwebhooks: 1.1.1 + optionalDependencies: + zod: 4.6.7 + '@anthropic-ai/sdk@0.133.0(zod@4.6.7)': dependencies: json-schema-to-ts: 3.1.1 @@ -20051,6 +20274,12 @@ snapshots: optionalDependencies: zod: 4.6.7 + '@anthropic-ai/sdk@0.91.1(zod@4.6.7)': + dependencies: + json-schema-to-ts: 3.1.1 + optionalDependencies: + zod: 4.6.7 + '@apm-js-collab/code-transformer-bundler-plugins@0.7.5(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: '@apm-js-collab/code-transformer': 0.19.0 @@ -20298,6 +20527,38 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 + '@aws-sdk/client-bedrock-runtime@3.1048.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/eventstream-handler-node': 3.972.39 + '@aws-sdk/middleware-eventstream': 3.972.35 + '@aws-sdk/middleware-websocket': 3.972.60 + '@aws-sdk/token-providers': 3.1048.0 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/client-bedrock-runtime@3.1127.0': + dependencies: + '@aws-sdk/core': 3.978.7 + '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/eventstream-handler-node': 3.972.39 + '@aws-sdk/middleware-eventstream': 3.972.35 + '@aws-sdk/middleware-websocket': 3.972.60 + '@aws-sdk/token-providers': 3.1127.0 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + '@aws-sdk/client-bedrock-runtime@3.1159.0': dependencies: '@aws-sdk/core': 3.978.7 @@ -20816,6 +21077,24 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 + '@aws-sdk/token-providers@3.1048.0': + dependencies: + '@aws-sdk/core': 3.978.7 + '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1127.0': + dependencies: + '@aws-sdk/core': 3.978.7 + '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + '@aws-sdk/token-providers@3.1159.0': dependencies: '@aws-sdk/core': 3.978.7 @@ -22875,6 +23154,100 @@ snapshots: '@dxup/unimport@0.1.2': {} + '@earendil-works/chord@1.4.0': + dependencies: + '@earendil-works/pi-telemetry': 1.4.0 + typebox: 1.3.27 + + '@earendil-works/pi-agent-core@0.83.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)(ws@8.21.3)(zod@4.6.7)': + dependencies: + '@earendil-works/pi-ai': 0.83.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)(ws@8.21.3)(zod@4.6.7) + diff: 8.0.4 + ignore: 7.0.5 + typebox: 1.3.7 + yaml: 2.9.0 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-ai@0.83.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)(ws@8.21.3)(zod@4.6.7)': + dependencies: + '@anthropic-ai/sdk': 0.91.1(zod@4.6.7) + '@aws-sdk/client-bedrock-runtime': 3.1048.0 + '@google/genai': 1.52.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0) + '@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.0 + '@smithy/node-http-handler': 4.7.3 + http-proxy-agent: 7.0.2(supports-color@10.0.0) + https-proxy-agent: 7.0.6(supports-color@10.0.0) + openai: 6.26.0(ws@8.21.3)(zod@4.6.7) + partial-json: 0.1.7 + typebox: 1.3.7 + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-ai@1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7)': + dependencies: + '@anthropic-ai/sdk': 0.129.0(zod@4.6.7) + '@aws-sdk/client-bedrock-runtime': 3.1127.0 + '@earendil-works/pi-telemetry': 1.4.0 + '@google/genai': 2.21.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0) + '@smithy/node-http-handler': 4.12.1 + http-proxy-agent: 9.1.0(supports-color@10.0.0) + https-proxy-agent: 9.1.0(supports-color@10.0.0) + openai: 7.19.0(@aws-sdk/credential-provider-node@3.972.90)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + partial-json: 0.1.7 + typebox: 1.3.27 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - '@modelcontextprotocol/sdk' + - '@smithy/hash-node' + - '@smithy/signature-v4' + - bufferutil + - kerberos + - supports-color + - undici + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-codemode@1.4.0': + dependencies: + quickjs-wasi: 3.6.2 + + '@earendil-works/pi-durable@1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7)': + dependencies: + '@earendil-works/chord': 1.4.0 + '@earendil-works/pi-ai': 1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + '@earendil-works/pi-codemode': 1.4.0 + '@earendil-works/pi-telemetry': 1.4.0 + '@silvia-odwyer/photon': 0.3.3 + diff: 8.0.4 + typebox: 1.3.27 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - '@modelcontextprotocol/sdk' + - '@smithy/hash-node' + - '@smithy/signature-v4' + - bufferutil + - kerberos + - supports-color + - undici + - utf-8-validate + - ws + - zod + + '@earendil-works/pi-telemetry@1.4.0': {} + '@edge-runtime/primitives@6.0.0': {} '@edge-runtime/types@4.0.0': @@ -23542,6 +23915,26 @@ snapshots: '@fastify/forwarded': 3.0.1 ipaddr.js: 2.4.0 + '@flue/runtime@2.0.3(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)(typescript@7.0.2)(ws@8.21.3)(zod@4.6.7)': + dependencies: + '@earendil-works/pi-agent-core': 0.83.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)(ws@8.21.3)(zod@4.6.7) + '@earendil-works/pi-ai': 0.83.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)(ws@8.21.3)(zod@4.6.7) + '@hono/node-server': 2.0.10(hono@4.13.11) + '@modelcontextprotocol/client': 2.0.0 + '@valibot/to-json-schema': 1.8.0(valibot@1.4.2(typescript@7.0.2)) + hono: 4.13.11 + js-yaml: 5.5.0 + ulidx: 2.4.1 + valibot: 1.4.2(typescript@7.0.2) + transitivePeerDependencies: + - '@modelcontextprotocol/sdk' + - bufferutil + - supports-color + - typescript + - utf-8-validate + - ws + - zod + '@gar/promisify@1.1.3': {} '@glimmer/env@0.1.7': {} @@ -23634,15 +24027,29 @@ snapshots: '@google-cloud/promisify@2.0.3': {} - '@google/genai@1.20.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(encoding@0.1.13)(supports-color@10.0.0)': + '@google/genai@1.52.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)': dependencies: - google-auth-library: 9.15.1(encoding@0.1.13)(supports-color@10.0.0) + google-auth-library: 10.9.1(supports-color@10.0.0) + p-retry: 4.6.2 + protobufjs: 7.6.6 + ws: 8.21.3 + optionalDependencies: + '@modelcontextprotocol/sdk': 1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@google/genai@2.21.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)': + dependencies: + google-auth-library: 10.9.1(supports-color@10.0.0) + p-retry: 4.6.2 + protobufjs: 7.6.6 ws: 8.21.3 optionalDependencies: '@modelcontextprotocol/sdk': 1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7) transitivePeerDependencies: - bufferutil - - encoding - supports-color - utf-8-validate @@ -24391,6 +24798,18 @@ snapshots: - encoding - supports-color + '@mistralai/mistralai@2.2.6(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/semantic-conventions': 1.43.0 + ws: 8.21.3 + zod: 4.6.7 + zod-to-json-schema: 3.25.2(zod@4.6.7) + optionalDependencies: + '@opentelemetry/api': 1.9.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@mistralai/mistralai@3.3.0(@aws-sdk/client-s3@3.1068.0)(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@cfworker/json-schema@4.1.1)(@opentelemetry/api@1.9.0)(supports-color@10.0.0)': dependencies: '@modelcontextprotocol/sdk': 1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7) @@ -26455,6 +26874,8 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@silvia-odwyer/photon@0.3.3': {} + '@simple-dom/interface@1.4.0': {} '@simple-git/args-pathspec@1.0.3': {} @@ -26620,6 +27041,12 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 + '@smithy/node-http-handler@4.7.3': + dependencies: + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + '@smithy/property-provider@4.2.14': dependencies: '@smithy/types': 4.19.0 @@ -27793,6 +28220,10 @@ snapshots: unhead: 2.1.17 vue: 3.5.44(typescript@7.0.2) + '@valibot/to-json-schema@1.8.0(valibot@1.4.2(typescript@7.0.2))': + dependencies: + valibot: 1.4.2(typescript@7.0.2) + '@vercel/nft@1.11.0(encoding@0.1.13)(rollup@4.62.3)(supports-color@10.0.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13)(supports-color@10.0.0) @@ -28379,6 +28810,8 @@ snapshots: agent-base@7.1.3: {} + agent-base@9.0.0: {} + agentkeepalive@4.2.1(supports-color@10.0.0): dependencies: debug: 4.3.4(supports-color@10.0.0) @@ -28550,6 +28983,8 @@ snapshots: argparse@2.0.1: {} + argparse@3.0.4: {} + args@5.0.3: dependencies: camelcase: 5.0.0 @@ -30074,6 +30509,8 @@ snapshots: data-uri-to-buffer@3.0.1: {} + data-uri-to-buffer@4.0.1: {} + data-urls@4.0.0: dependencies: abab: 2.0.6 @@ -31625,6 +32062,11 @@ snapshots: fecha@4.2.3: {} + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + fflate@0.4.9: {} fflate@0.8.3: {} @@ -31790,6 +32232,10 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + forwarded-parse@2.1.2: {} forwarded@0.2.0: {} @@ -31922,15 +32368,12 @@ snapshots: - encoding - supports-color - gaxios@6.7.1(encoding@0.1.13)(supports-color@10.0.0): + gaxios@7.3.1(supports-color@10.0.0): dependencies: extend: 3.0.2 https-proxy-agent: 7.0.6(supports-color@10.0.0) - is-stream: 2.0.1 - node-fetch: 2.7.0(encoding@0.1.13) - uuid: 9.0.1 + node-fetch: 3.3.2 transitivePeerDependencies: - - encoding - supports-color gcp-metadata@4.2.1(encoding@0.1.13)(supports-color@10.0.0): @@ -31941,13 +32384,12 @@ snapshots: - encoding - supports-color - gcp-metadata@6.1.1(encoding@0.1.13)(supports-color@10.0.0): + gcp-metadata@8.1.2(supports-color@10.0.0): dependencies: - gaxios: 6.7.1(encoding@0.1.13)(supports-color@10.0.0) - google-logging-utils: 0.0.2 + gaxios: 7.3.1(supports-color@10.0.0) + google-logging-utils: 1.1.3 json-bigint: 1.0.0 transitivePeerDependencies: - - encoding - supports-color generate-function@2.3.1: @@ -32156,34 +32598,33 @@ snapshots: dependencies: minimist: 1.2.8 - google-auth-library@7.0.3(encoding@0.1.13)(supports-color@10.0.0): + google-auth-library@10.9.1(supports-color@10.0.0): dependencies: - arrify: 2.0.1 base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - fast-text-encoding: 1.0.3 - gaxios: 4.2.0(encoding@0.1.13)(supports-color@10.0.0) - gcp-metadata: 4.2.1(encoding@0.1.13)(supports-color@10.0.0) - gtoken: 5.2.1(encoding@0.1.13)(supports-color@10.0.0) + gaxios: 7.3.1(supports-color@10.0.0) + gcp-metadata: 8.1.2(supports-color@10.0.0) + google-logging-utils: 1.1.3 jws: 4.0.1 - lru-cache: 6.0.0 transitivePeerDependencies: - - encoding - supports-color - google-auth-library@9.15.1(encoding@0.1.13)(supports-color@10.0.0): + google-auth-library@7.0.3(encoding@0.1.13)(supports-color@10.0.0): dependencies: + arrify: 2.0.1 base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 6.7.1(encoding@0.1.13)(supports-color@10.0.0) - gcp-metadata: 6.1.1(encoding@0.1.13)(supports-color@10.0.0) - gtoken: 7.1.0(encoding@0.1.13)(supports-color@10.0.0) + fast-text-encoding: 1.0.3 + gaxios: 4.2.0(encoding@0.1.13)(supports-color@10.0.0) + gcp-metadata: 4.2.1(encoding@0.1.13)(supports-color@10.0.0) + gtoken: 5.2.1(encoding@0.1.13)(supports-color@10.0.0) jws: 4.0.1 + lru-cache: 6.0.0 transitivePeerDependencies: - encoding - supports-color - google-logging-utils@0.0.2: {} + google-logging-utils@1.1.3: {} google-p12-pem@3.1.4: dependencies: @@ -32220,14 +32661,6 @@ snapshots: - encoding - supports-color - gtoken@7.1.0(encoding@0.1.13)(supports-color@10.0.0): - dependencies: - gaxios: 6.7.1(encoding@0.1.13)(supports-color@10.0.0) - jws: 4.0.1 - transitivePeerDependencies: - - encoding - - supports-color - gzip-size@7.0.0: dependencies: duplexer: 0.1.2 @@ -32596,6 +33029,15 @@ snapshots: transitivePeerDependencies: - supports-color + http-proxy-agent@9.1.0(supports-color@10.0.0): + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@10.0.0) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + http-proxy-middleware@2.0.10(@types/express@4.17.21)(debug@4.4.3(supports-color@10.0.0)): dependencies: '@types/http-proxy': 1.17.14 @@ -32661,6 +33103,15 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@9.1.0(supports-color@10.0.0): + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@10.0.0) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + httpxy@0.5.1: {} human-signals@1.1.1: {} @@ -33199,14 +33650,18 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@5.5.0: + dependencies: + argparse: 3.0.4 + jsdoc-type-pratt-parser@4.1.0: {} jsdoc-type-pratt-parser@7.2.0: {} - jsdom-worker@0.3.0(node-fetch@2.7.0(encoding@0.1.13)): + jsdom-worker@0.3.0(node-fetch@3.3.2): dependencies: mitt: 3.0.1 - node-fetch: 2.7.0(encoding@0.1.13) + node-fetch: 3.3.2 uuid-v4: 0.1.0 jsdom@21.1.2(supports-color@10.0.0): @@ -33553,6 +34008,8 @@ snapshots: picocolors: 1.1.1 shell-quote: 1.12.0 + layerr@3.0.0: {} + lazystream@1.0.1: dependencies: readable-stream: 2.3.8 @@ -35311,6 +35768,8 @@ snapshots: node-cron@4.6.0: {} + node-domexception@1.0.0: {} + node-fetch-native@1.6.7: {} node-fetch@2.7.0(encoding@0.1.13): @@ -35319,6 +35778,12 @@ snapshots: optionalDependencies: encoding: 0.1.13 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-forge@1.4.0: {} node-gyp-build-optional-packages@5.2.2: @@ -35897,6 +36362,19 @@ snapshots: ws: 8.21.3 zod: 4.6.7 + openai@6.26.0(ws@8.21.3)(zod@4.6.7): + optionalDependencies: + ws: 8.21.3 + zod: 4.6.7 + + openai@7.19.0(@aws-sdk/credential-provider-node@3.972.90)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7): + optionalDependencies: + '@aws-sdk/credential-provider-node': 3.972.90 + '@smithy/signature-v4': 5.7.4 + undici: 7.24.8 + ws: 8.21.3 + zod: 4.6.7 + openai@7.31.0(@aws-sdk/credential-provider-node@3.972.90)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7): optionalDependencies: '@aws-sdk/credential-provider-node': 3.972.90 @@ -36299,6 +36777,8 @@ snapshots: parseurl@1.3.3: {} + partial-json@0.1.7: {} + pascal-case@3.1.2: dependencies: no-case: 3.0.4 @@ -37268,11 +37748,27 @@ snapshots: property-information@7.1.0: {} + protobufjs@7.6.6: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 20.19.43 + long: 5.3.2 + proxy-addr@2.0.8: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-agent-negotiate@1.1.0: {} + proxy-from-env@2.1.0: {} proxy@2.1.1(supports-color@10.0.0): @@ -37327,6 +37823,8 @@ snapshots: rimraf: 2.7.1 underscore.string: 3.3.6 + quickjs-wasi@3.6.2: {} + quote-unquote@1.0.0: {} radix3@1.1.2: {} @@ -39425,6 +39923,10 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + typebox@1.3.27: {} + + typebox@1.3.7: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -39512,6 +40014,10 @@ snapshots: uint8array-extras@1.4.0: {} + ulidx@2.4.1: + dependencies: + layerr: 3.0.0 + ultrahtml@1.7.0: {} unbox-primitive@1.1.0: @@ -39978,6 +40484,10 @@ snapshots: optionalDependencies: typescript: 6.0.3 + valibot@1.4.2(typescript@7.0.2): + optionalDependencies: + typescript: 7.0.2 + validate-html-nesting@1.2.2: {} validate-npm-package-license@3.0.4: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f0da0985135e..69f59d9ab5e3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -99,6 +99,7 @@ packageExtensions: # `false` only for scripts that just print a message. allowBuilds: '@apollo/protobufjs': true + '@google/genai': false '@nestjs/core': false '@parcel/watcher': true '@prisma/client': true @@ -112,6 +113,7 @@ allowBuilds: nice-napi: true nx: true prisma: true + protobufjs: false sharp: true vue-demi: true workerd: true From e1daae79b55337dc78789c5fb552c19a87876b10 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 30 Oct 2026 13:37:27 +0100 Subject: [PATCH 056/169] chore: Remove unused test and build dependencies (#25945) **Why:** Remove six unused dependencies left behind by deleted build helpers and tests. **How to review:** Six declarations across four packages, plus lockfile pruning. Co-authored-by: OpenAI Codex --- .../browser-integration-tests/package.json | 1 - dev-packages/rollup-utils/package.json | 4 - .../size-limit-gh-action/package.json | 2 - packages/replay-internal/package.json | 1 - pnpm-lock.yaml | 101 ------------------ 5 files changed, 109 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 714fe69a262f..00f833a08d8c 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -62,7 +62,6 @@ "@sentry/rrweb": "2.45.0", "@sentry/browser": "11.10.0", "@sentry/replay": "11.10.0", - "@sentry/opentelemetry": "11.10.0", "@sentry/conventions": "0.30.0", "@supabase/supabase-js": "2.109.0", "axios": "1.20.0", diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json index 2d94d1c9d467..1b6e8af1f86a 100644 --- a/dev-packages/rollup-utils/package.json +++ b/dev-packages/rollup-utils/package.json @@ -8,10 +8,6 @@ "license": "MIT", "private": true, "main": "./index.mjs", - "dependencies": { - "acorn": "^8.7.0", - "recast": "^0.24.0" - }, "devDependencies": { "eslint-plugin-regexp": "^3.1.0" }, diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json index 2fac041a0d7d..eddcb6c91547 100644 --- a/dev-packages/size-limit-gh-action/package.json +++ b/dev-packages/size-limit-gh-action/package.json @@ -18,8 +18,6 @@ "@actions/core": "1.10.1", "@actions/exec": "1.1.1", "@actions/github": "^5.0.0", - "@actions/glob": "0.7.0", - "@actions/io": "3.0.2", "bytes-iec": "3.1.1", "markdown-table": "3.0.3" } diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index 1f766902b1d6..88cc236a18f2 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -78,7 +78,6 @@ "@sentry/rrweb": "2.45.0", "@sentry/rrweb-snapshot": "2.45.0", "fflate": "0.8.3", - "jest-matcher-utils": "^29.0.0", "jsdom-worker": "^0.3.0" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca8b7afe45e5..369bdf1cadad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -300,9 +300,6 @@ importers: '@sentry/conventions': specifier: 0.30.0 version: 0.30.0 - '@sentry/opentelemetry': - specifier: 11.10.0 - version: link:../../packages/opentelemetry '@sentry/replay': specifier: 11.10.0 version: link:../../packages/replay-internal @@ -928,13 +925,6 @@ importers: version: 3.0.3 dev-packages/rollup-utils: - dependencies: - acorn: - specifier: ^8.7.0 - version: 8.18.0 - recast: - specifier: ^0.24.0 - version: 0.24.0 devDependencies: eslint-plugin-regexp: specifier: ^3.1.0 @@ -954,12 +944,6 @@ importers: '@actions/github': specifier: ^5.0.0 version: 5.1.1(encoding@0.1.13) - '@actions/glob': - specifier: 0.7.0 - version: 0.7.0 - '@actions/io': - specifier: 3.0.2 - version: 3.0.2 bytes-iec: specifier: 3.1.1 version: 3.1.1 @@ -2167,9 +2151,6 @@ importers: fflate: specifier: 0.8.3 version: 0.8.3 - jest-matcher-utils: - specifier: ^29.0.0 - version: 29.7.0 jsdom-worker: specifier: ^0.3.0 version: 0.3.0(node-fetch@3.3.2) @@ -2608,9 +2589,6 @@ packages: '@actions/github@9.0.0': resolution: {integrity: sha512-yJ0RoswsAaKcvkmpCE4XxBRiy/whH2SdTBHWzs0gi4wkqTDhXMChjSdqBz/F4AeiDlP28rQqL33iHb+kjAMX6w==} - '@actions/glob@0.7.0': - resolution: {integrity: sha512-+7s3wM+cXapDLmLL1NVWHawqcJOZzXZy2df/VhNn8DnZtS/x83iTCKaUn9F0llur4h3CII0AilvKKH4CMPL8Gw==} - '@actions/http-client@2.2.3': resolution: {integrity: sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==} @@ -6048,10 +6026,6 @@ packages: resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jest/types@26.6.2': resolution: {integrity: sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==} engines: {node: '>= 10.14.2'} @@ -8421,9 +8395,6 @@ packages: '@simple-git/argv-parser@1.1.1': resolution: {integrity: sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==} - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@sindresorhus/is@7.0.2': resolution: {integrity: sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==} engines: {node: '>=18'} @@ -11522,10 +11493,6 @@ packages: diff-match-patch@1.0.5: resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@5.2.2: resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} engines: {node: '>=0.3.1'} @@ -13767,18 +13734,6 @@ packages: resolution: {integrity: sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==} engines: {node: '>=14'} - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} @@ -16607,10 +16562,6 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - pretty-ms@7.0.1: resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} engines: {node: '>=10'} @@ -16808,9 +16759,6 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -16920,10 +16868,6 @@ packages: resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} engines: {node: '>= 4'} - recast@0.24.0: - resolution: {integrity: sha512-dN08tw9Kl2i8oDBy16NhxBh6qOqsEqmGYpX1xaxPmgz6/6ukopSuysLfvLbieuDMY57jEz6eTmNz6lqH54kuqQ==} - engines: {node: '>= 22'} - rechoir@0.8.0: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} @@ -19960,11 +19904,6 @@ snapshots: '@octokit/request-error': 7.1.0 undici: 6.24.1 - '@actions/glob@0.7.0': - dependencies: - '@actions/core': 3.0.0 - minimatch: 10.2.5 - '@actions/http-client@2.2.3': dependencies: tunnel: 0.0.6 @@ -24569,10 +24508,6 @@ snapshots: '@jest/diff-sequences@30.0.1': {} - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - '@jest/types@26.6.2': dependencies: '@types/istanbul-lib-coverage': 2.0.6 @@ -26884,8 +26819,6 @@ snapshots: dependencies: '@simple-git/args-pathspec': 1.0.3 - '@sinclair/typebox@0.27.8': {} - '@sindresorhus/is@7.0.2': {} '@sindresorhus/merge-streams@4.0.0': {} @@ -30790,8 +30723,6 @@ snapshots: diff-match-patch@1.0.5: {} - diff-sequences@29.6.3: {} - diff@5.2.2: {} diff@8.0.4: {} @@ -33601,22 +33532,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jest-diff@29.7.0: - dependencies: - chalk: 4.1.2 - diff-sequences: 29.6.3 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-get-type@29.6.3: {} - - jest-matcher-utils@29.7.0: - dependencies: - chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - jest-worker@27.5.1: dependencies: '@types/node': 20.19.43 @@ -37681,12 +37596,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - pretty-ms@7.0.1: dependencies: parse-ms: 2.1.0 @@ -37881,8 +37790,6 @@ snapshots: react-is@17.0.2: {} - react-is@18.3.1: {} - react-refresh@0.14.2: {} react-router-dom@6.30.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -38037,14 +37944,6 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 - recast@0.24.0: - dependencies: - ast-types: 0.16.1 - esprima: 4.0.1 - source-map: 0.6.1 - tiny-invariant: 1.3.3 - tslib: 2.8.1 - rechoir@0.8.0: dependencies: resolve: 1.22.12 From 2f5a2f93c7d63868bd773cb8e9407422e8a6fc97 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 30 Oct 2026 13:41:32 +0100 Subject: [PATCH 057/169] chore(node-integration-tests): Remove unused dependencies (#25942) **Why:** Noticed there is a bunch of leftover dependencies being installed in our node integration suites that weren't cleaned out after deleting suites or updating helpers etc, removing all of these so we can safe some install time. Co-authored-by: OpenAI Codex --- .../node-integration-tests/package.json | 14 - pnpm-lock.yaml | 362 ------------------ 2 files changed, 376 deletions(-) diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 2965f6567c76..4b6af19c037d 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -53,9 +53,6 @@ "@modelcontextprotocol/client": "^2.0.0", "@modelcontextprotocol/sdk": "1.32.2", "@modelcontextprotocol/server": "^2.0.0", - "@nestjs/common": "^11", - "@nestjs/core": "^11", - "@nestjs/platform-express": "^11", "@prisma/adapter-pg": "7.9.1", "@prisma/client": "6.15.0", "@sentry/aws-serverless": "11.10.0", @@ -65,9 +62,6 @@ "@sentry/server-runtime-injection": "11.10.0", "@sentry/server-utils": "11.10.0", "@smithy/node-http-handler": "4.12.1", - "@types/mongodb": "^3.6.20", - "@types/mysql": "^2.15.21", - "@types/pg": "^8.6.5", "@typesafe-ai/sdk": "^0.6.0", "ai": "^4.3.16", "amqplib": "^0.10.9", @@ -85,7 +79,6 @@ "h3": "2.0.1-rc.22", "graphql-tag": "^2.12.7", "hono": "^4.13.7", - "http-terminator": "^3.2.0", "ioredis": "5.10.1", "ioredis-5": "npm:ioredis@^5.11.0", "ioredis-6": "npm:ioredis@^6.0.0", @@ -111,9 +104,7 @@ "redis-4": "npm:redis@^4.6.14", "redis-5": "npm:redis@~5.11.0", "redis-5-tracing": "npm:redis@^5.12.0", - "reflect-metadata": "0.2.2", "together-ai": "0.54.0", - "rxjs": "^7.8.2", "srvx": "^0.11.22", "tedious": "^19.2.1", "unstorage": "2.0.0-alpha.7", @@ -125,18 +116,13 @@ "devDependencies": { "@hapi/boom": "^10.0.1", "@neondatabase/serverless": "1.2.0", - "@opentelemetry/instrumentation-http": "0.220.0", "@sentry-internal/rollup-utils": "11.10.0", "@sentry/conventions": "0.30.0", "@sentry-internal/test-utils": "11.10.0", "@sentry/node-native": "11.10.0", - "@types/amqplib": "^0.10.5", "@types/express": "^4.17.21", - "@types/node-cron": "^3.0.11", "@types/node-schedule": "^2.1.7", "esbuild": "0.28.1", - "eslint-plugin-regexp": "^3.1.0", - "globby": "11", "react": "^18.3.1", "winston-transport": "^4.9.0", "zod": "^4.6.7" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 369bdf1cadad..61e98910ce63 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -655,15 +655,6 @@ importers: '@modelcontextprotocol/server': specifier: ^2.0.0 version: 2.0.0 - '@nestjs/common': - specifier: ^11 - version: 11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0) - '@nestjs/core': - specifier: ^11 - version: 11.1.19(@nestjs/common@11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0))(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/platform-express': - specifier: ^11 - version: 11.1.19(@nestjs/common@11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0))(@nestjs/core@11.1.19)(supports-color@10.0.0) '@prisma/adapter-pg': specifier: 7.9.1 version: 7.9.1 @@ -691,15 +682,6 @@ importers: '@smithy/node-http-handler': specifier: 4.12.1 version: 4.12.1 - '@types/mongodb': - specifier: ^3.6.20 - version: 3.6.20 - '@types/mysql': - specifier: ^2.15.21 - version: 2.15.27 - '@types/pg': - specifier: ^8.6.5 - version: 8.20.0 '@typesafe-ai/sdk': specifier: ^0.6.0 version: 0.6.0 @@ -751,9 +733,6 @@ importers: hono: specifier: ^4.13.7 version: 4.13.11 - http-terminator: - specifier: ^3.2.0 - version: 3.2.0 ioredis: specifier: 5.10.1 version: 5.10.1(supports-color@10.0.0) @@ -829,12 +808,6 @@ importers: redis-5-tracing: specifier: npm:redis@^5.12.0 version: redis@5.12.1(@opentelemetry/api@1.9.0) - reflect-metadata: - specifier: 0.2.2 - version: 0.2.2 - rxjs: - specifier: ^7.8.2 - version: 7.8.2 srvx: specifier: ^0.11.22 version: 0.11.22 @@ -866,9 +839,6 @@ importers: '@neondatabase/serverless': specifier: 1.2.0 version: 1.2.0 - '@opentelemetry/instrumentation-http': - specifier: 0.220.0 - version: 0.220.0(@opentelemetry/api@1.9.0)(supports-color@10.0.0) '@sentry-internal/rollup-utils': specifier: 11.10.0 version: link:../rollup-utils @@ -881,27 +851,15 @@ importers: '@sentry/node-native': specifier: 11.10.0 version: link:../../packages/node-native - '@types/amqplib': - specifier: ^0.10.5 - version: 0.10.5 '@types/express': specifier: ^4.17.21 version: 4.17.21 - '@types/node-cron': - specifier: ^3.0.11 - version: 3.0.11 '@types/node-schedule': specifier: ^2.1.7 version: 2.1.7 esbuild: specifier: 0.28.1 version: 0.28.1 - eslint-plugin-regexp: - specifier: ^3.1.0 - version: 3.1.0 - globby: - specifier: '11' - version: 11.1.0 react: specifier: ^18.3.1 version: 18.3.1 @@ -6311,19 +6269,6 @@ packages: class-validator: optional: true - '@nestjs/common@11.1.19': - resolution: {integrity: sha512-qeiTt2tv+e5QyDKqG8HlVZb2wx64FEaSGFJouqTSRs+kG44iTfl3xlz1XqVped+rihx4hmjWgL5gkhtdK3E6+Q==} - peerDependencies: - class-transformer: '>=0.4.1' - class-validator: '>=0.13.2' - reflect-metadata: ^0.1.12 || ^0.2.0 - rxjs: ^7.1.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true - '@nestjs/core@10.4.15': resolution: {integrity: sha512-UBejmdiYwaH6fTsz2QFBlC1cJHM+3UDeLZN+CiP9I1fRv2KlBZsmozGLbV5eS1JAVWJB4T5N5yQ0gjN8ZvcS2w==} peerDependencies: @@ -6341,30 +6286,6 @@ packages: '@nestjs/websockets': optional: true - '@nestjs/core@11.1.19': - resolution: {integrity: sha512-6nJkWa2efrYi+XlU686J9y5L7OvxpLVjT0T/sxRKE7Jvpffiihelup4WSvLvRhdHDjj/5SuoWEwqReXAaaeHmw==} - engines: {node: '>= 20'} - peerDependencies: - '@nestjs/common': ^11.0.0 - '@nestjs/microservices': ^11.0.0 - '@nestjs/platform-express': ^11.0.0 - '@nestjs/websockets': ^11.0.0 - reflect-metadata: ^0.1.12 || ^0.2.0 - rxjs: ^7.1.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true - '@nestjs/websockets': - optional: true - - '@nestjs/platform-express@11.1.19': - resolution: {integrity: sha512-Vpdv8jyCQdThfoTx+UTn+DRYr6H6X02YUqcpZ3qP6G3ZUwtVp7eS+hoQPGd4UuCnlnFG8Wqr2J9bGEzQdi1rIg==} - peerDependencies: - '@nestjs/common': ^11.0.0 - '@nestjs/core': ^11.0.0 - '@next/env@15.5.28': resolution: {integrity: sha512-xxW5EBI8fMPuVTPBW7LL980IRbuDXlbkUAwp9DDhtb4Lnich3nJ13uYpvrX4VE74hWPf6AvHHAMmGCidcHjbfg==} @@ -6551,11 +6472,6 @@ packages: peerDependencies: nuxt: ^3.21.11 - '@nuxt/opencollective@0.4.1': - resolution: {integrity: sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==} - engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} - hasBin: true - '@nuxt/schema@3.21.11': resolution: {integrity: sha512-5Couz53Pl/SgTwtMQ9jlxVIC6wZk9aAbWyeOD7D6D1UIP5DzV/yV5lPDY/kM0nmiqPSzJf9WLTdJgB2gNm0dOA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -6737,10 +6653,6 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@opentelemetry/api-logs@0.220.0': - resolution: {integrity: sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==} - engines: {node: '>=8.0.0'} - '@opentelemetry/api@1.10.0': resolution: {integrity: sha512-lMkyes8iHrG+mk6WH7X70MdCLI2RS0FwNBY1PyQ8UemaijE2m5mv5A0Niy3HtbYTFrgkUEQjFMXmSAk2hQDuPQ==} engines: {node: '>=8.0.0'} @@ -6749,24 +6661,6 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@opentelemetry/core@2.9.0': - resolution: {integrity: sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' - - '@opentelemetry/instrumentation-http@0.220.0': - resolution: {integrity: sha512-Szt4dO2Boz2CDr38DaSw/lnqwhwKl+IAdgNGEGgSm2Anb+fwPtIAGmIwkhsLLN69QQZQE96JxjMKYY4rlRkYKw==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - - '@opentelemetry/instrumentation@0.220.0': - resolution: {integrity: sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==} - engines: {node: ^18.19.0 || >=20.6.0} - peerDependencies: - '@opentelemetry/api': ^1.3.0 - '@opentelemetry/semantic-conventions@1.43.0': resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} engines: {node: '>=14'} @@ -8906,9 +8800,6 @@ packages: '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - '@types/amqplib@0.10.5': - resolution: {integrity: sha512-/cSykxROY7BWwDoi4Y4/jLAuZTshZxd8Ey1QYa/VaXriMotBDoou7V/twJiOSHzU6t1Kp1AHAUXGCgqq+6DNeg==} - '@types/aria-query@4.2.1': resolution: {integrity: sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==} @@ -8942,10 +8833,6 @@ packages: '@types/braces@3.0.5': resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} - '@types/bson@4.2.0': - resolution: {integrity: sha512-ELCPqAdroMdcuxqwMgUpifQyRoTpyYCNr1V9xKyF40VsBobsj+BbWNRvwGchMgBPGqkw655ypkjj2MEF5ywVwg==} - deprecated: This is a stub types definition. bson provides its own type definitions, so you do not need this installed. - '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -9054,9 +8941,6 @@ packages: '@types/minimatch@3.0.5': resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} - '@types/mongodb@3.6.20': - resolution: {integrity: sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==} - '@types/ms@0.7.32': resolution: {integrity: sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==} @@ -9066,9 +8950,6 @@ packages: '@types/nlcst@2.0.3': resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} - '@types/node-cron@3.0.11': - resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==} - '@types/node-fetch@2.6.13': resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} @@ -9952,9 +9833,6 @@ packages: app-module-path@2.2.0: resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==} - append-field@1.0.0: - resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} - aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} @@ -10387,10 +10265,6 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - boolean@3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} @@ -10549,10 +10423,6 @@ packages: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} - busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - bytes-iec@3.1.1: resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==} engines: {node: '>= 0.8'} @@ -10927,9 +10797,6 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concat-stream@2.0.0: - resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} - confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -11372,10 +11239,6 @@ packages: defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} - delay@5.0.0: - resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} - engines: {node: '>=10'} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -12409,10 +12272,6 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-json-stringify@2.7.13: - resolution: {integrity: sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA==} - engines: {node: '>= 10.0.0'} - fast-json-stringify@6.4.0: resolution: {integrity: sha512-ibRCQ0GZKJIQ+P3Et1h0LhPgp3PMTYk0MH8O+kW3lNYsvmaQww5Nn3f1jf73Q0jR1Yz3a1CDP4/NZD3vOajWJQ==} @@ -12426,10 +12285,6 @@ packages: resolution: {integrity: sha512-99jPl8JkCSCa4VlboNU1XuL98ijm74Pm9CGo6H4BoMVoVh1uhguQcvwLgXDT8Vkl2qj/UEQ0J9gD8beHjTFk1w==} hasBin: true - fast-printf@1.6.9: - resolution: {integrity: sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==} - engines: {node: '>=10.0'} - fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} @@ -12627,9 +12482,6 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} - forwarded-parse@2.1.2: - resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} - forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -13205,10 +13057,6 @@ packages: resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - http-terminator@3.2.0: - resolution: {integrity: sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g==} - engines: {node: '>=14'} - https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -13301,10 +13149,6 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@3.0.0: - resolution: {integrity: sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==} - engines: {node: '>=18'} - import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} @@ -14179,10 +14023,6 @@ packages: '@parcel/watcher': optional: true - load-esm@1.0.3: - resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} - engines: {node: '>=13.2.0'} - load-yaml-file@0.2.0: resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} engines: {node: '>=6'} @@ -14984,10 +14824,6 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - multer@2.1.1: - resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} - engines: {node: '>= 10.16.0'} - multicast-dns@7.2.5: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true @@ -15765,10 +15601,6 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - p-wait-for@3.2.0: - resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==} - engines: {node: '>=8'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -17009,10 +16841,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - require-in-the-middle@8.0.1: - resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} - engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} - requirejs-config-file@4.0.0: resolution: {integrity: sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==} engines: {node: '>=10.13.0'} @@ -17144,10 +16972,6 @@ packages: engines: {node: 20 || >=22} hasBin: true - roarr@7.15.0: - resolution: {integrity: sha512-CV9WefQfUXTX6wr8CrEMhfNef3sjIt9wNhE/5PNu4tNWsaoDNDXqq+OGn/RW9A1UPb0qc7FQlswXRaJJJsqn8A==} - engines: {node: '>=12.0'} - rolldown@1.2.11: resolution: {integrity: sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -17393,9 +17217,6 @@ packages: semifies@1.0.0: resolution: {integrity: sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==} - semver-compare@1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -17842,10 +17663,6 @@ packages: stream-to-array@2.3.0: resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - streamx@2.22.1: resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} @@ -17856,10 +17673,6 @@ packages: resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} engines: {node: '>=0.10.0'} - string-similarity@4.0.4: - resolution: {integrity: sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - string-width@2.1.1: resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} engines: {node: '>=4'} @@ -18434,10 +18247,6 @@ packages: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -18479,9 +18288,6 @@ packages: typed-assert@1.0.9: resolution: {integrity: sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==} - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typesafe-path@0.2.2: resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} @@ -24854,18 +24660,6 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 - '@nestjs/common@11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0)': - dependencies: - file-type: 21.3.4(supports-color@10.0.0) - iterare: 1.2.1 - load-esm: 1.0.3 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 - tslib: 2.8.1 - uid: 2.0.2 - transitivePeerDependencies: - - supports-color - '@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2))(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: '@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -24880,32 +24674,6 @@ snapshots: transitivePeerDependencies: - encoding - '@nestjs/core@11.1.19(@nestjs/common@11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0))(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': - dependencies: - '@nestjs/common': 11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0) - '@nuxt/opencollective': 0.4.1 - fast-safe-stringify: 2.1.1 - iterare: 1.2.1 - path-to-regexp: 8.4.2 - reflect-metadata: 0.2.2 - rxjs: 7.8.2 - tslib: 2.8.1 - uid: 2.0.2 - optionalDependencies: - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0))(@nestjs/core@11.1.19)(supports-color@10.0.0) - - '@nestjs/platform-express@11.1.19(@nestjs/common@11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0))(@nestjs/core@11.1.19)(supports-color@10.0.0)': - dependencies: - '@nestjs/common': 11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@10.0.0))(@nestjs/platform-express@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - cors: 2.8.6 - express: 5.2.1(supports-color@10.0.0) - multer: 2.1.1 - path-to-regexp: 8.4.2 - tslib: 2.8.1 - transitivePeerDependencies: - - supports-color - '@next/env@15.5.28': {} '@next/swc-darwin-arm64@15.5.28': @@ -25417,10 +25185,6 @@ snapshots: - webpack - xml2js - '@nuxt/opencollective@0.4.1': - dependencies: - consola: 3.4.2 - '@nuxt/schema@3.21.11': dependencies: '@vue/shared': 3.5.44 @@ -25673,38 +25437,10 @@ snapshots: '@open-draft/until@2.1.0': {} - '@opentelemetry/api-logs@0.220.0': - dependencies: - '@opentelemetry/api': 1.10.0 - '@opentelemetry/api@1.10.0': {} '@opentelemetry/api@1.9.0': {} - '@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.43.0 - - '@opentelemetry/instrumentation-http@0.220.0(@opentelemetry/api@1.9.0)(supports-color@10.0.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.9.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.0)(supports-color@10.0.0) - '@opentelemetry/semantic-conventions': 1.43.0 - forwarded-parse: 2.1.2 - transitivePeerDependencies: - - supports-color - - '@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.0)(supports-color@10.0.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.220.0 - import-in-the-middle: 3.0.0 - require-in-the-middle: 8.0.1(supports-color@10.0.0) - transitivePeerDependencies: - - supports-color - '@opentelemetry/semantic-conventions@1.43.0': {} '@oslojs/encoding@1.1.0': {} @@ -27599,10 +27335,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@types/amqplib@0.10.5': - dependencies: - '@types/node': 20.19.43 - '@types/aria-query@4.2.1': {} '@types/aria-query@5.0.4': {} @@ -27643,10 +27375,6 @@ snapshots: '@types/braces@3.0.5': {} - '@types/bson@4.2.0': - dependencies: - bson: 7.2.0 - '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -27769,11 +27497,6 @@ snapshots: '@types/minimatch@3.0.5': {} - '@types/mongodb@3.6.20': - dependencies: - '@types/bson': 4.2.0 - '@types/node': 20.19.43 - '@types/ms@0.7.32': {} '@types/mysql@2.15.27': @@ -27784,8 +27507,6 @@ snapshots: dependencies: '@types/unist': 3.0.0 - '@types/node-cron@3.0.11': {} - '@types/node-fetch@2.6.13': dependencies: '@types/node': 20.19.43 @@ -28877,8 +28598,6 @@ snapshots: app-module-path@2.2.0: {} - append-field@1.0.0: {} - aproba@2.0.0: {} archiver-utils@5.0.2: @@ -29478,8 +29197,6 @@ snapshots: boolbase@1.0.0: {} - boolean@3.2.0: {} - bottleneck@2.19.5: {} bowser@2.11.0: {} @@ -29731,10 +29448,6 @@ snapshots: dependencies: run-applescript: 7.0.0 - busboy@1.6.0: - dependencies: - streamsearch: 1.1.0 - bytes-iec@3.1.1: {} bytes@3.1.2: {} @@ -30130,13 +29843,6 @@ snapshots: concat-map@0.0.1: {} - concat-stream@2.0.0: - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 - typedarray: 0.0.6 - confbox@0.1.8: {} confbox@0.2.4: {} @@ -30614,8 +30320,6 @@ snapshots: defu@6.1.7: {} - delay@5.0.0: {} - delayed-stream@1.0.0: {} delegates@1.0.0: {} @@ -31870,13 +31574,6 @@ snapshots: fast-json-stable-stringify@2.1.0: {} - fast-json-stringify@2.7.13: - dependencies: - ajv: 6.12.6 - deepmerge: 4.3.1 - rfdc: 1.4.1 - string-similarity: 4.0.4 - fast-json-stringify@6.4.0: dependencies: '@fastify/merge-json-schemas': 0.2.1 @@ -31901,10 +31598,6 @@ snapshots: dependencies: cac: 7.0.0 - fast-printf@1.6.9: - dependencies: - boolean: 3.2.0 - fast-querystring@1.1.2: dependencies: fast-decode-uri-component: 1.0.1 @@ -32167,8 +31860,6 @@ snapshots: dependencies: fetch-blob: 3.2.0 - forwarded-parse@2.1.2: {} - forwarded@0.2.0: {} fraction.js@5.3.4: {} @@ -32991,13 +32682,6 @@ snapshots: http-shutdown@1.2.2: {} - http-terminator@3.2.0: - dependencies: - delay: 5.0.0 - p-wait-for: 3.2.0 - roarr: 7.15.0 - type-fest: 2.19.0 - https-proxy-agent@5.0.1(supports-color@10.0.0): dependencies: agent-base: 6.0.2(supports-color@10.0.0) @@ -33103,13 +32787,6 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@3.0.0: - dependencies: - acorn: 8.18.0 - acorn-import-attributes: 1.9.5(acorn@8.18.0) - cjs-module-lexer: 2.2.0 - module-details-from-path: 1.0.4 - import-meta-resolve@4.2.0: {} impound@1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): @@ -34081,8 +33758,6 @@ snapshots: transitivePeerDependencies: - srvx - load-esm@1.0.3: {} - load-yaml-file@0.2.0: dependencies: graceful-fs: 4.2.11 @@ -35061,13 +34736,6 @@ snapshots: muggle-string@0.4.1: {} - multer@2.1.1: - dependencies: - append-field: 1.0.0 - busboy: 1.6.0 - concat-stream: 2.0.0 - type-is: 1.6.18 - multicast-dns@7.2.5: dependencies: dns-packet: 5.6.1 @@ -36591,10 +36259,6 @@ snapshots: p-try@2.2.0: {} - p-wait-for@3.2.0: - dependencies: - p-timeout: 3.2.0 - package-json-from-dist@1.0.1: {} package-manager-detector@1.9.0: {} @@ -38158,13 +37822,6 @@ snapshots: require-from-string@2.0.2: {} - require-in-the-middle@8.0.1(supports-color@10.0.0): - dependencies: - debug: 4.4.3(supports-color@10.0.0) - module-details-from-path: 1.0.4 - transitivePeerDependencies: - - supports-color - requirejs-config-file@4.0.0: dependencies: esprima: 4.0.1 @@ -38302,15 +37959,6 @@ snapshots: glob: 13.0.6 package-json-from-dist: 1.0.1 - roarr@7.15.0: - dependencies: - boolean: 3.2.0 - fast-json-stringify: 2.7.13 - fast-printf: 1.6.9 - globalthis: 1.0.4 - safe-stable-stringify: 2.5.0 - semver-compare: 1.0.0 - rolldown@1.2.11: dependencies: '@oxc-project/types': 0.151.0 @@ -38616,8 +38264,6 @@ snapshots: semifies@1.0.0: {} - semver-compare@1.0.0: {} - semver@5.7.2: {} semver@6.3.1: {} @@ -39215,8 +38861,6 @@ snapshots: dependencies: any-promise: 1.3.0 - streamsearch@1.1.0: {} - streamx@2.22.1: dependencies: fast-fifo: 1.3.2 @@ -39228,8 +38872,6 @@ snapshots: strict-uri-encode@1.1.0: {} - string-similarity@4.0.4: {} - string-width@2.1.1: dependencies: is-fullwidth-code-point: 2.0.0 @@ -39803,8 +39445,6 @@ snapshots: type-fest@0.7.1: {} - type-fest@2.19.0: {} - type-fest@4.41.0: {} type-fest@5.4.2: @@ -39861,8 +39501,6 @@ snapshots: typed-assert@1.0.9: {} - typedarray@0.0.6: {} - typesafe-path@0.2.2: {} typescript-auto-import-cache@0.3.6: From 446679d18d434211b242b8c274860e93396d675b Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 30 Oct 2026 13:50:43 +0100 Subject: [PATCH 058/169] chore: Remove unused eslint-plugin-regexp dependencies (#25944) **Why:** Remove leftover eslint-plugin-regexp declarations from seven packages now that linting uses Oxlint. **How to review:** Manifest removals and regenerated lockfile only. Co-authored-by: OpenAI Codex --- .../browser-integration-tests/package.json | 1 - .../clear-cache-gh-action/package.json | 3 --- .../cloudflare-integration-tests/package.json | 1 - dev-packages/e2e-tests/package.json | 1 - .../package.json | 3 --- dev-packages/rollup-utils/package.json | 3 --- dev-packages/test-utils/package.json | 3 +-- pnpm-lock.yaml | 26 +------------------ 8 files changed, 2 insertions(+), 39 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 00f833a08d8c..416639132e92 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -77,7 +77,6 @@ "@sentry/rrweb-types": "^2.45.0", "@sentry/wasm": "11.10.0", "@types/node": "^20.19.0", - "eslint-plugin-regexp": "^3.1.0", "glob": "^13.0.6" } } diff --git a/dev-packages/clear-cache-gh-action/package.json b/dev-packages/clear-cache-gh-action/package.json index a6ffee8b7ba8..75ea139df37c 100644 --- a/dev-packages/clear-cache-gh-action/package.json +++ b/dev-packages/clear-cache-gh-action/package.json @@ -16,8 +16,5 @@ "dependencies": { "@actions/core": "1.10.1", "@actions/github": "^5.0.0" - }, - "devDependencies": { - "eslint-plugin-regexp": "^3.1.0" } } diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json index 1b72feaa1ef7..1d336b48e677 100644 --- a/dev-packages/cloudflare-integration-tests/package.json +++ b/dev-packages/cloudflare-integration-tests/package.json @@ -37,7 +37,6 @@ "@opentelemetry/api": "^1.9.1", "@sentry-internal/test-utils": "11.10.0", "@sentry/conventions": "0.30.0", - "eslint-plugin-regexp": "^3.1.0", "prisma": "6.15.0", "vite": "^8.3.1", "vitest": "^4.1.11", diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index af77f36a6fb6..8e25d9e26997 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -20,7 +20,6 @@ "clean:pnpm": "pnpm store prune" }, "devDependencies": { - "eslint-plugin-regexp": "^3.1.0", "rimraf": "^6.1.3" } } diff --git a/dev-packages/external-contributor-gh-action/package.json b/dev-packages/external-contributor-gh-action/package.json index 3a1b935b59ff..805b4fe96872 100644 --- a/dev-packages/external-contributor-gh-action/package.json +++ b/dev-packages/external-contributor-gh-action/package.json @@ -15,8 +15,5 @@ }, "dependencies": { "@actions/core": "1.10.1" - }, - "devDependencies": { - "eslint-plugin-regexp": "^3.1.0" } } diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json index 1b6e8af1f86a..9d5b1d2d0d60 100644 --- a/dev-packages/rollup-utils/package.json +++ b/dev-packages/rollup-utils/package.json @@ -8,8 +8,5 @@ "license": "MIT", "private": true, "main": "./index.mjs", - "devDependencies": { - "eslint-plugin-regexp": "^3.1.0" - }, "type": "module" } diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json index c4a642459c0b..287581fbc397 100644 --- a/dev-packages/test-utils/package.json +++ b/dev-packages/test-utils/package.json @@ -69,7 +69,6 @@ "@sentry-internal/rollup-utils": "11.10.0", "@sentry/core": "11.10.0", "@types/express": "^4.17.21", - "@types/ws": "^8.18.1", - "eslint-plugin-regexp": "^3.1.0" + "@types/ws": "^8.18.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61e98910ce63..c58c9044d4b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,9 +343,6 @@ importers: '@types/node': specifier: ^20.19.0 version: 20.19.43 - eslint-plugin-regexp: - specifier: ^3.1.0 - version: 3.1.0 glob: specifier: ^13.0.6 version: 13.0.6 @@ -438,10 +435,6 @@ importers: '@actions/github': specifier: ^5.0.0 version: 5.1.1(encoding@0.1.13) - devDependencies: - eslint-plugin-regexp: - specifier: ^3.1.0 - version: 3.1.0 dev-packages/cloudflare-integration-tests: dependencies: @@ -509,9 +502,6 @@ importers: '@sentry/conventions': specifier: 0.30.0 version: 0.30.0 - eslint-plugin-regexp: - specifier: ^3.1.0 - version: 3.1.0 prisma: specifier: 6.15.0 version: 6.15.0(magicast@0.3.5)(typescript@7.0.2) @@ -552,9 +542,6 @@ importers: dev-packages/e2e-tests: devDependencies: - eslint-plugin-regexp: - specifier: ^3.1.0 - version: 3.1.0 rimraf: specifier: ^6.1.3 version: 6.1.3 @@ -564,10 +551,6 @@ importers: '@actions/core': specifier: 1.10.1 version: 1.10.1 - devDependencies: - eslint-plugin-regexp: - specifier: ^3.1.0 - version: 3.1.0 dev-packages/node-integration-tests: dependencies: @@ -882,11 +865,7 @@ importers: specifier: 3.0.3 version: 3.0.3 - dev-packages/rollup-utils: - devDependencies: - eslint-plugin-regexp: - specifier: ^3.1.0 - version: 3.1.0 + dev-packages/rollup-utils: {} dev-packages/size-limit-gh-action: dependencies: @@ -933,9 +912,6 @@ importers: '@types/ws': specifier: ^8.18.1 version: 8.18.1 - eslint-plugin-regexp: - specifier: ^3.1.0 - version: 3.1.0 packages/angular: dependencies: From f948bcf3e926845dff16174eb882fdd885754c2a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 30 Oct 2026 13:56:12 +0100 Subject: [PATCH 059/169] test(node-integration-tests): Install Prisma suite dependencies as package dependencies (#25943) **Why:** Move the Prisma v8 runtime into locked package dependencies and align the shared v7 adapter with the version its suite tests (the entry we had before for this was dead). Co-authored-by: OpenAI Codex --- .../node-integration-tests/package.json | 3 +- .../suites/tracing/prisma-orm-v7/test.ts | 1 - .../suites/tracing/prisma-orm-v8/test.ts | 4 - pnpm-lock.yaml | 634 ++++++++++++++++-- 4 files changed, 594 insertions(+), 48 deletions(-) diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 4b6af19c037d..39eb221ba423 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -53,8 +53,9 @@ "@modelcontextprotocol/client": "^2.0.0", "@modelcontextprotocol/sdk": "1.32.2", "@modelcontextprotocol/server": "^2.0.0", - "@prisma/adapter-pg": "7.9.1", + "@prisma/adapter-pg": "7.2.0", "@prisma/client": "6.15.0", + "@prisma/orm-postgres": "8.0.0-rc.17", "@sentry/aws-serverless": "11.10.0", "@sentry/core": "11.10.0", "@sentry/hono": "11.10.0", 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 index 9e11425ee539..8f6c8d78d34e 100644 --- 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 @@ -106,7 +106,6 @@ describe('Prisma ORM v7 Tests', () => { }, { additionalDependencies: { - '@prisma/adapter-pg': '7.2.0', '@prisma/client': '7.2.0', pg: '^8.11.0', prisma: '7.2.0', diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v8/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v8/test.ts index 6831e3123397..17263c38c517 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v8/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v8/test.ts @@ -33,7 +33,6 @@ function getOperationSpans(container: SerializedStreamedSpanContainer): Serializ // Prisma 8 has no tracing surface: the operation spans come from the orchestrion channels the runtime hook // injects, and the `pg` spans underneath stand in for v7's `db_query` spans. -// Prisma 8 requires Node 22.12 (`engines` of its `@prisma/cli-engine` peer), and on Node 20 it does not install. describe.runIf(supports({ min: 22 }))('Prisma ORM v8 Tests', () => { describeWithDockerCompose('Prisma ORM v8', { workingDirectory: [__dirname] }, () => { createEsmAndCjsTests( @@ -100,9 +99,6 @@ describe.runIf(supports({ min: 22 }))('Prisma ORM v8 Tests', () => { testInstrumentedOperations(); }, { - additionalDependencies: { - '@prisma/orm-postgres': '8.0.0-rc.17', - }, copyPaths: ['prisma'], }, ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c58c9044d4b3..fa9e6df7f0b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -379,7 +379,7 @@ importers: version: 2.15.27 '@types/pg': specifier: ^8.20.0 - version: 8.20.0 + version: 8.20.4 bun-types: specifier: ^1.2.9 version: 1.2.9 @@ -639,11 +639,14 @@ importers: specifier: ^2.0.0 version: 2.0.0 '@prisma/adapter-pg': - specifier: 7.9.1 - version: 7.9.1 + specifier: 7.2.0 + version: 7.2.0 '@prisma/client': specifier: 6.15.0 version: 6.15.0(prisma@6.15.0(magicast@0.3.5)(typescript@7.0.2))(typescript@7.0.2) + '@prisma/orm-postgres': + specifier: 8.0.0-rc.17 + version: 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) '@sentry/aws-serverless': specifier: 11.10.0 version: link:../../packages/aws-serverless @@ -2850,6 +2853,12 @@ packages: resolution: {integrity: sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA==} engines: {node: '>=16'} + '@ark/schema@0.56.8': + resolution: {integrity: sha512-R05TOe85oQBGFFw2zKMuZpqkXibLKuC/aJt6VwFn5Cjc4x3NkWfhvGRpRDycNkj3ddcHnOPtIsmjVowJO9Yxdw==} + + '@ark/util@0.56.8': + resolution: {integrity: sha512-XLiQyGfpJcws1zCtp2Cva8s1oheCdFn1Rt6JSyC4NnkDrpwCJnlaiawuuOc1Z97vMH+jEzW5vyyf0xOju5wUQA==} + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -4499,6 +4508,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} @@ -4529,6 +4544,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.15.18': resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} @@ -4565,6 +4586,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} @@ -4595,6 +4622,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} @@ -4625,6 +4658,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} @@ -4655,6 +4694,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} @@ -4685,6 +4730,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} @@ -4715,6 +4766,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} @@ -4745,6 +4802,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} @@ -4775,6 +4838,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} @@ -4805,6 +4874,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.15.18': resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} engines: {node: '>=12'} @@ -4847,6 +4922,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} @@ -4877,6 +4958,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} @@ -4907,6 +4994,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} @@ -4937,6 +5030,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} @@ -4967,6 +5066,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} @@ -4997,6 +5102,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.12': resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} @@ -5021,6 +5132,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} @@ -5051,6 +5168,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} @@ -5075,6 +5198,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} @@ -5105,6 +5234,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} @@ -5129,6 +5264,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} @@ -5159,6 +5300,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} @@ -5189,6 +5336,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} @@ -5219,6 +5372,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} @@ -5249,6 +5408,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6328,6 +6493,10 @@ packages: '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + '@noble/hashes@2.4.0': + resolution: {integrity: sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==} + engines: {node: '>= 20.19.0'} + '@nodable/entities@2.1.0': resolution: {integrity: sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==} @@ -7545,8 +7714,8 @@ packages: '@prisma/adapter-d1@6.15.0': resolution: {integrity: sha512-ozsANVFfmPyxa4SBQnGITQLSQ2aOVaRRV75CXBaDrhWU+zB+0Df3QUPF4vURSgskhdfUyOo35pzUOf4msPoGuQ==} - '@prisma/adapter-pg@7.9.1': - resolution: {integrity: sha512-Ho2RK1KanQxLNSC0sR5bpiiVep10sWPLXCcxK+KXfI/Q69TMRbiafSvLPv3V9snimX72rMCqGlyJ4sBO4lKTAw==} + '@prisma/adapter-pg@7.2.0': + resolution: {integrity: sha512-euIdQ13cRB2wZ3jPsnDnFhINquo1PYFPCg6yVL8b2rp3EdinQHsX9EDdCtRr489D5uhphcRk463OdQAFlsCr0w==} '@prisma/client@6.15.0': resolution: {integrity: sha512-wR2LXUbOH4cL/WToatI/Y2c7uzni76oNFND7+23ypLllBmIS8e3ZHhO+nud9iXSXKFt1SoM3fTZvHawg63emZw==} @@ -7566,14 +7735,14 @@ packages: '@prisma/debug@6.15.0': resolution: {integrity: sha512-y7cSeLuQmyt+A3hstAs6tsuAiVXSnw9T55ra77z0nbNkA8Lcq9rNcQg6PI00by/+WnE/aMRJ/W7sZWn2cgIy1g==} - '@prisma/debug@7.9.1': - resolution: {integrity: sha512-/cpVZ4itxtcgB8GHBvZtcmuEjq+lWsLrRJxFMbwZrT1RIdtuKmUm7PPGo/wzfbYpBrk+9WmmBE8CHJw2rybKDQ==} + '@prisma/debug@7.2.0': + resolution: {integrity: sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==} '@prisma/driver-adapter-utils@6.15.0': resolution: {integrity: sha512-tzcMG1eEBM3IJ8TBHo4jGeoUaalctqGXbrvxIoZb8jSEtgR82IUhdVyHHLVTlT8MdrHovcQJJ3jfcQfJARRnaQ==} - '@prisma/driver-adapter-utils@7.9.1': - resolution: {integrity: sha512-vmHehG7nn/heW32DXXpp13DxxAxVVe6n250oEt3dOL2E/4bt3olktKZN0mzSuxMMronyMSkbeW2uCOn3F4g8RQ==} + '@prisma/driver-adapter-utils@7.2.0': + resolution: {integrity: sha512-gzrUcbI9VmHS24Uf+0+7DNzdIw7keglJsD5m/MHxQOU68OhGVzlphQRobLiDMn8CHNA2XN8uugwKjudVtnfMVQ==} '@prisma/engines-version@6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb': resolution: {integrity: sha512-a/46aK5j6L3ePwilZYEgYDPrhBQ/n4gYjLxT5YncUTJJNRnTCVjPF86QdzUOLRdYjCLfhtZp9aum90W0J+trrg==} @@ -7587,6 +7756,52 @@ packages: '@prisma/get-platform@6.15.0': resolution: {integrity: sha512-Jbb+Xbxyp05NSR1x2epabetHiXvpO8tdN2YNoWoA/ZsbYyxxu/CO/ROBauIFuMXs3Ti+W7N7SJtWsHGaWte9Rg==} + '@prisma/orm-family-sql@8.0.0-rc.17': + resolution: {integrity: sha512-KbdapnNyIX44hwgxvBV9Zl8qjIbYAm6VgM7uW7u6fDlx1QP41L7s5INxaADdoceqgWTo/KUqVKLpMq7RcGByzg==} + peerDependencies: + typescript: '>=5.9' + peerDependenciesMeta: + typescript: + optional: true + + '@prisma/orm-framework@8.0.0-rc.17': + resolution: {integrity: sha512-KfircGILFXGoBPPg6I4caNF/tpVe+2UF6kMO+PF/KfOwMsFh11mR5nEijiSqy+5l7oVWGsT0GNz2inysbg+UWg==} + peerDependencies: + typescript: '>=5.9' + peerDependenciesMeta: + typescript: + optional: true + + '@prisma/orm-postgres@8.0.0-rc.17': + resolution: {integrity: sha512-1jwZH/APF9222PFufvvcBoQXcfEXjmXjqJ1uEU7PMUaxHbL1FwwDcFxHmjujSt9mFOopGSvr5b5BkIjNWbUinw==} + peerDependencies: + temporal-polyfill: ^1.0.4 + typescript: '>=5.9' + peerDependenciesMeta: + typescript: + optional: true + + '@prisma/orm-target-postgres@8.0.0-rc.17': + resolution: {integrity: sha512-SAE42aFV8ulzNmkJFKk16zVa3CxjTroGllBTkGBgnC2zEiNnBebIXHo7DmSWC70iMM6qFSDL0g2pmwAMwDIGEg==} + peerDependencies: + temporal-polyfill: ^1.0.4 + typescript: '>=5.9' + peerDependenciesMeta: + typescript: + optional: true + + '@prisma/orm-toolchain@8.0.0-rc.17': + resolution: {integrity: sha512-iG1knDO9Hg1/b5NOj74T7Bx5d2imJVq+qSrKDD94nwjOyvIZ5ygKSzVq2hb0KoAjae8PONgznSOBRMA8Kh+p4Q==} + peerDependencies: + '@prisma/cli-engine': 0.7.0 + typescript: '>=5.9' + vite: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + vite: + optional: true + '@protobuf-ts/plugin-framework@2.9.4': resolution: {integrity: sha512-9nuX1kjdMliv+Pes8dQCKyVhjKgNNfwxVHg+tx3fLXSfZZRcUHMc1PMwB9/vTvc6gBKt9QGz5ERqSqZc0++E9A==} @@ -8941,11 +9156,14 @@ packages: '@types/node@20.19.43': resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} + '@types/node@26.1.2': + resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} + '@types/parse-json@4.0.0': resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} - '@types/pg@8.20.0': - resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} + '@types/pg@8.20.4': + resolution: {integrity: sha512-Jz7UDOlIiFJuacC0TlBoLyNtmwlA/wpIyPDd3tvUqlRM+HzkWy2xUgpFpaXtbfTAFF6sIGq5lsCDBdJnhky1Xg==} '@types/prop-types@15.7.3': resolution: {integrity: sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==} @@ -9296,6 +9514,10 @@ packages: peerDependencies: valibot: ^1.5.0 + '@vercel/detect-agent@1.2.7': + resolution: {integrity: sha512-5tWaC9V+u4SHcSMKBz9FQb3Dhl/1A2lQ6QqHovu8uQeb8dTyjEevpJMANm/uQEn/usYB63fpdjWYjkJ8/bGtjA==} + engines: {node: '>=14'} + '@vercel/nft@1.11.0': resolution: {integrity: sha512-m1QFg+U+3yPOnP1xSYJ73UIRxLOXdts1JOhiOiyPYqEsALgrXFFINvgUaD6R6iNvaBFAjHllBCbkfx4FuOdpaA==} engines: {node: '>=20'} @@ -9791,8 +10013,8 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} ansis@4.3.1: @@ -9859,6 +10081,12 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + arkregex@0.0.14: + resolution: {integrity: sha512-G3I/uCtWE0IKQiPh0qqmmAFDdtjZKZQ5mNPR6gJBeAMZ7VqYCpoaDeXnLju2Zo6ywExtmxZJELeVFb0MPDjPyA==} + + arktype@2.2.9: + resolution: {integrity: sha512-Cja0IMEn19rB35on9da1i1z6QMasuS7ULffXbetCry09jvCGYT8Bi1KgYiqri5FymF4nQAXkM90yksYkgSK2EA==} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -10612,6 +10840,9 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clipanion@4.0.0-rc.4: + resolution: {integrity: sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==} + clipboardy@4.0.0: resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==} engines: {node: '>=18'} @@ -10635,6 +10866,9 @@ packages: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} + closest-match@1.3.3: + resolution: {integrity: sha512-RSdHrZwNOvt2uMQgqJDJdM/I+5MlJ1tQJEXYrbRjSMXWiCRo06g2hwObJ7+WKt2J9ySK9/pJ0Q2vbL+BPkofDA==} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -11964,6 +12198,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -12588,8 +12827,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + get-east-asian-width@1.7.0: + resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} engines: {node: '>=18'} get-intrinsic@1.3.0: @@ -13705,6 +13944,9 @@ packages: jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsondiffpatch@0.6.0: resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -15751,6 +15993,11 @@ packages: pg-connection-string@2.6.1: resolution: {integrity: sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==} + pg-cursor@2.22.1: + resolution: {integrity: sha512-X10ldv3wwBONQkOSwgWEbaEITMGLd92V2Zd+xsFPhDt3nhhU3hpAT08SERBChnIFmomczLax6lKXLJrHPPXfxA==} + peerDependencies: + pg: ^8 + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -16346,8 +16593,8 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.9.10: + resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==} engines: {node: '>=14'} hasBin: true @@ -17665,6 +17912,10 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string-width@8.3.0: + resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} + engines: {node: '>=20'} + string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} @@ -17705,8 +17956,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-bom-string@1.0.0: @@ -18158,6 +18409,9 @@ packages: resolution: {integrity: sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==} engines: {node: '>=18'} + ts-toolbelt@9.6.0: + resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -18207,6 +18461,9 @@ packages: turbo-stream@2.4.1: resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} + typanion@3.14.0: + resolution: {integrity: sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -18360,6 +18617,9 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + undici@5.29.0: resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} engines: {node: '>=14.0'} @@ -18412,6 +18672,10 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + uniku@0.5.0: + resolution: {integrity: sha512-giSrg7xqM5YWkSlyheulHgTTInhYh/m0cFZOOuChi/TO87hKlxmZLllETlvDw/lPB54NIs1iW/x2rr0y2yFXHg==} + engines: {node: '>=20.19.0'} + unimport@6.4.0: resolution: {integrity: sha512-JJOOuNMFq8b4ZPBKwQUxEcba4MplskDzYI1Lvrf8rJfWphZTWvPNXWa493qsPngHUmub89w6C7j+SeLWTE/UIQ==} engines: {node: '>=18.12.0'} @@ -19131,6 +19395,10 @@ packages: vscode-languageserver-types@3.18.0: resolution: {integrity: sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==} + vscode-languageserver@10.1.0: + resolution: {integrity: sha512-9gEWpXkYGXoqG7pBnE8O8hx/yP7+Aabn4+peQ3KDicQv6qunHSWyLTud3OF0w4S2+HfDD+5HqYKiXQW9HAU6mA==} + hasBin: true + vscode-languageserver@9.0.1: resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} hasBin: true @@ -19457,6 +19725,10 @@ packages: '@cloudflare/workers-types': optional: true + wrap-ansi@10.0.2: + resolution: {integrity: sha512-6OLRNZqRntVGBm26ghZ5eZhCuWWyOkRCSn8XsOIuq5stpCE/tjLbdozd93naILZig0lmD8ANfRwnhn0DLeXGlg==} + engines: {node: '>=20'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -20137,6 +20409,12 @@ snapshots: '@apollo/utils.withrequired@3.0.0': {} + '@ark/schema@0.56.8': + dependencies: + '@ark/util': 0.56.8 + + '@ark/util@0.56.8': {} + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -23213,6 +23491,9 @@ snapshots: '@esbuild/aix-ppc64@0.28.1': optional: true + '@esbuild/aix-ppc64@0.28.2': + optional: true + '@esbuild/android-arm64@0.21.5': optional: true @@ -23228,6 +23509,9 @@ snapshots: '@esbuild/android-arm64@0.28.1': optional: true + '@esbuild/android-arm64@0.28.2': + optional: true + '@esbuild/android-arm@0.15.18': optional: true @@ -23246,6 +23530,9 @@ snapshots: '@esbuild/android-arm@0.28.1': optional: true + '@esbuild/android-arm@0.28.2': + optional: true + '@esbuild/android-x64@0.21.5': optional: true @@ -23261,6 +23548,9 @@ snapshots: '@esbuild/android-x64@0.28.1': optional: true + '@esbuild/android-x64@0.28.2': + optional: true + '@esbuild/darwin-arm64@0.21.5': optional: true @@ -23276,6 +23566,9 @@ snapshots: '@esbuild/darwin-arm64@0.28.1': optional: true + '@esbuild/darwin-arm64@0.28.2': + optional: true + '@esbuild/darwin-x64@0.21.5': optional: true @@ -23291,6 +23584,9 @@ snapshots: '@esbuild/darwin-x64@0.28.1': optional: true + '@esbuild/darwin-x64@0.28.2': + optional: true + '@esbuild/freebsd-arm64@0.21.5': optional: true @@ -23306,6 +23602,9 @@ snapshots: '@esbuild/freebsd-arm64@0.28.1': optional: true + '@esbuild/freebsd-arm64@0.28.2': + optional: true + '@esbuild/freebsd-x64@0.21.5': optional: true @@ -23321,6 +23620,9 @@ snapshots: '@esbuild/freebsd-x64@0.28.1': optional: true + '@esbuild/freebsd-x64@0.28.2': + optional: true + '@esbuild/linux-arm64@0.21.5': optional: true @@ -23336,6 +23638,9 @@ snapshots: '@esbuild/linux-arm64@0.28.1': optional: true + '@esbuild/linux-arm64@0.28.2': + optional: true + '@esbuild/linux-arm@0.21.5': optional: true @@ -23351,6 +23656,9 @@ snapshots: '@esbuild/linux-arm@0.28.1': optional: true + '@esbuild/linux-arm@0.28.2': + optional: true + '@esbuild/linux-ia32@0.21.5': optional: true @@ -23366,6 +23674,9 @@ snapshots: '@esbuild/linux-ia32@0.28.1': optional: true + '@esbuild/linux-ia32@0.28.2': + optional: true + '@esbuild/linux-loong64@0.15.18': optional: true @@ -23387,6 +23698,9 @@ snapshots: '@esbuild/linux-loong64@0.28.1': optional: true + '@esbuild/linux-loong64@0.28.2': + optional: true + '@esbuild/linux-mips64el@0.21.5': optional: true @@ -23402,6 +23716,9 @@ snapshots: '@esbuild/linux-mips64el@0.28.1': optional: true + '@esbuild/linux-mips64el@0.28.2': + optional: true + '@esbuild/linux-ppc64@0.21.5': optional: true @@ -23417,6 +23734,9 @@ snapshots: '@esbuild/linux-ppc64@0.28.1': optional: true + '@esbuild/linux-ppc64@0.28.2': + optional: true + '@esbuild/linux-riscv64@0.21.5': optional: true @@ -23432,6 +23752,9 @@ snapshots: '@esbuild/linux-riscv64@0.28.1': optional: true + '@esbuild/linux-riscv64@0.28.2': + optional: true + '@esbuild/linux-s390x@0.21.5': optional: true @@ -23447,6 +23770,9 @@ snapshots: '@esbuild/linux-s390x@0.28.1': optional: true + '@esbuild/linux-s390x@0.28.2': + optional: true + '@esbuild/linux-x64@0.21.5': optional: true @@ -23462,6 +23788,9 @@ snapshots: '@esbuild/linux-x64@0.28.1': optional: true + '@esbuild/linux-x64@0.28.2': + optional: true + '@esbuild/netbsd-arm64@0.25.12': optional: true @@ -23474,6 +23803,9 @@ snapshots: '@esbuild/netbsd-arm64@0.28.1': optional: true + '@esbuild/netbsd-arm64@0.28.2': + optional: true + '@esbuild/netbsd-x64@0.21.5': optional: true @@ -23489,6 +23821,9 @@ snapshots: '@esbuild/netbsd-x64@0.28.1': optional: true + '@esbuild/netbsd-x64@0.28.2': + optional: true + '@esbuild/openbsd-arm64@0.25.12': optional: true @@ -23501,6 +23836,9 @@ snapshots: '@esbuild/openbsd-arm64@0.28.1': optional: true + '@esbuild/openbsd-arm64@0.28.2': + optional: true + '@esbuild/openbsd-x64@0.21.5': optional: true @@ -23516,6 +23854,9 @@ snapshots: '@esbuild/openbsd-x64@0.28.1': optional: true + '@esbuild/openbsd-x64@0.28.2': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true @@ -23528,6 +23869,9 @@ snapshots: '@esbuild/openharmony-arm64@0.28.1': optional: true + '@esbuild/openharmony-arm64@0.28.2': + optional: true + '@esbuild/sunos-x64@0.21.5': optional: true @@ -23543,6 +23887,9 @@ snapshots: '@esbuild/sunos-x64@0.28.1': optional: true + '@esbuild/sunos-x64@0.28.2': + optional: true + '@esbuild/win32-arm64@0.21.5': optional: true @@ -23558,6 +23905,9 @@ snapshots: '@esbuild/win32-arm64@0.28.1': optional: true + '@esbuild/win32-arm64@0.28.2': + optional: true + '@esbuild/win32-ia32@0.21.5': optional: true @@ -23573,6 +23923,9 @@ snapshots: '@esbuild/win32-ia32@0.28.1': optional: true + '@esbuild/win32-ia32@0.28.2': + optional: true + '@esbuild/win32-x64@0.21.5': optional: true @@ -23588,6 +23941,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true + '@esbuild/win32-x64@0.28.2': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@8.57.0(supports-color@10.0.0))': dependencies: eslint: 8.57.0(supports-color@10.0.0) @@ -24269,7 +24625,7 @@ snapshots: dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -24686,6 +25042,8 @@ snapshots: dependencies: eslint-scope: 5.1.1 + '@noble/hashes@2.4.0': {} + '@nodable/entities@2.1.0': {} '@nodelib/fs.scandir@2.1.5': @@ -25892,10 +26250,9 @@ snapshots: '@prisma/driver-adapter-utils': 6.15.0 ky: 1.7.5 - '@prisma/adapter-pg@7.9.1': + '@prisma/adapter-pg@7.2.0': dependencies: - '@prisma/driver-adapter-utils': 7.9.1 - '@types/pg': 8.20.0 + '@prisma/driver-adapter-utils': 7.2.0 pg: 8.20.0 postgres-array: 3.0.4 transitivePeerDependencies: @@ -25917,15 +26274,15 @@ snapshots: '@prisma/debug@6.15.0': {} - '@prisma/debug@7.9.1': {} + '@prisma/debug@7.2.0': {} '@prisma/driver-adapter-utils@6.15.0': dependencies: '@prisma/debug': 6.15.0 - '@prisma/driver-adapter-utils@7.9.1': + '@prisma/driver-adapter-utils@7.2.0': dependencies: - '@prisma/debug': 7.9.1 + '@prisma/debug': 7.2.0 '@prisma/engines-version@6.15.0-5.85179d7826409ee107a6ba334b5e305ae3fba9fb': {} @@ -25946,6 +26303,101 @@ snapshots: dependencies: '@prisma/debug': 6.15.0 + '@prisma/orm-family-sql@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + dependencies: + '@prisma/orm-framework': 8.0.0-rc.17(typescript@7.0.2) + '@prisma/orm-toolchain': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@standard-schema/spec': 1.1.0 + arktype: 2.2.9 + pathe: 2.0.3 + pluralize: 8.0.0 + tinyglobby: 0.2.17 + ts-toolbelt: 9.6.0 + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - '@prisma/cli-engine' + - magicast + - vite + + '@prisma/orm-framework@8.0.0-rc.17(typescript@7.0.2)': + dependencies: + '@standard-schema/spec': 1.1.0 + arktype: 2.2.9 + pathe: 2.0.3 + tinyglobby: 0.2.17 + uniku: 0.5.0 + optionalDependencies: + typescript: 7.0.2 + + '@prisma/orm-postgres@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + dependencies: + '@prisma/orm-family-sql': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@prisma/orm-framework': 8.0.0-rc.17(typescript@7.0.2) + '@prisma/orm-target-postgres': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@prisma/orm-toolchain': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@types/node': 26.1.2 + '@types/pg': 8.20.4 + arktype: 2.2.9 + pathe: 2.0.3 + pg: 8.22.0 + tinyglobby: 0.2.17 + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - '@prisma/cli-engine' + - magicast + - pg-native + - vite + + '@prisma/orm-target-postgres@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + dependencies: + '@prisma/orm-family-sql': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@prisma/orm-framework': 8.0.0-rc.17(typescript@7.0.2) + '@prisma/orm-toolchain': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@standard-schema/spec': 1.1.0 + '@types/node': 26.1.2 + '@types/pg': 8.20.4 + arktype: 2.2.9 + pathe: 2.0.3 + pg: 8.22.0 + pg-cursor: 2.22.1(pg@8.22.0) + postgres-array: 2.0.0 + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - '@prisma/cli-engine' + - magicast + - pg-native + - vite + + '@prisma/orm-toolchain@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + dependencies: + '@prisma/orm-framework': 8.0.0-rc.17(typescript@7.0.2) + '@vercel/detect-agent': 1.2.7 + arktype: 2.2.9 + c12: 3.3.4(magicast@0.3.5) + ci-info: 4.4.0 + clipanion: 4.0.0-rc.4 + closest-match: 1.3.3 + colorette: 2.0.20 + esbuild: 0.28.2 + jsonc-parser: 3.3.1 + package-manager-detector: 1.9.0 + pathe: 2.0.3 + prettier: 3.9.10 + string-width: 8.3.0 + strip-ansi: 7.2.0 + tinyglobby: 0.2.17 + vscode-languageserver: 10.1.0 + vscode-languageserver-textdocument: 1.0.12 + wrap-ansi: 10.0.2 + optionalDependencies: + typescript: 7.0.2 + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + transitivePeerDependencies: + - magicast + '@protobuf-ts/plugin-framework@2.9.4': dependencies: '@protobuf-ts/runtime': 2.9.4 @@ -26013,7 +26465,7 @@ snapshots: pathe: 1.1.2 picocolors: 1.1.1 pkg-types: 2.3.1 - prettier: 3.8.1 + prettier: 3.9.10 react-refresh: 0.14.2 react-router: 7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) semver: 7.8.5 @@ -27502,9 +27954,13 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@26.1.2': + dependencies: + undici-types: 8.3.0 + '@types/parse-json@4.0.0': {} - '@types/pg@8.20.0': + '@types/pg@8.20.4': dependencies: '@types/node': 20.19.43 pg-protocol: 1.15.0 @@ -27854,6 +28310,8 @@ snapshots: dependencies: valibot: 1.4.2(typescript@7.0.2) + '@vercel/detect-agent@1.2.7': {} + '@vercel/nft@1.11.0(encoding@0.1.13)(rollup@4.62.3)(supports-color@10.0.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13)(supports-color@10.0.0) @@ -28561,7 +29019,7 @@ snapshots: ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} ansis@4.3.1: {} @@ -28635,6 +29093,16 @@ snapshots: aria-query@5.3.2: {} + arkregex@0.0.14: + dependencies: + '@ark/util': 0.56.8 + + arktype@2.2.9: + dependencies: + '@ark/schema': 0.56.8 + '@ark/util': 0.56.8 + arkregex: 0.0.14 + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -29445,6 +29913,23 @@ snapshots: optionalDependencies: magicast: 0.3.5 + c12@3.3.4(magicast@0.3.5): + dependencies: + chokidar: 5.0.0 + confbox: 0.2.4 + defu: 6.1.7 + dotenv: 17.4.2 + exsolve: 1.1.3 + giget: 3.3.1 + jiti: 2.7.0 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.1.0 + pkg-types: 2.3.1 + rc9: 3.1.0 + optionalDependencies: + magicast: 0.3.5 + c12@3.3.4(magicast@0.5.4): dependencies: chokidar: 5.0.0 @@ -29669,6 +30154,10 @@ snapshots: client-only@0.0.1: {} + clipanion@4.0.0-rc.4: + dependencies: + typanion: 3.14.0 + clipboardy@4.0.0: dependencies: execa: 8.0.1 @@ -29697,6 +30186,8 @@ snapshots: clone@2.1.2: {} + closest-match@1.3.3: {} + clsx@2.1.1: {} cluster-key-slot@1.1.1: {} @@ -31154,6 +31645,35 @@ snapshots: '@esbuild/win32-ia32': 0.28.1 '@esbuild/win32-x64': 0.28.1 + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -32011,7 +32531,7 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} + get-east-asian-width@1.7.0: {} get-intrinsic@1.3.0: dependencies: @@ -33403,6 +33923,8 @@ snapshots: jsonc-parser@3.2.0: {} + jsonc-parser@3.3.1: {} + jsondiffpatch@0.6.0: dependencies: '@types/diff-match-patch': 1.0.36 @@ -34840,7 +35362,7 @@ snapshots: find-cache-dir: 3.3.2 glob: 8.1.0 injection-js: 2.4.0 - jsonc-parser: 3.2.0 + jsonc-parser: 3.3.1 less: 4.2.0(supports-color@10.0.0) ora: 5.4.1 postcss: 8.5.31 @@ -35481,7 +36003,7 @@ snapshots: npm-run-all2@6.2.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 cross-spawn: 7.0.6 memorystream: 0.3.1 minimatch: 9.0.9 @@ -35997,7 +36519,7 @@ snapshots: log-symbols: 6.0.0 stdin-discarder: 0.2.2 string-width: 7.2.0 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 os-tmpdir@1.0.2: {} @@ -36410,6 +36932,10 @@ snapshots: pg-connection-string@2.6.1: {} + pg-cursor@2.22.1(pg@8.22.0): + dependencies: + pg: 8.22.0 + pg-int8@1.0.1: {} pg-pool@3.14.0(pg@8.16.0): @@ -37212,7 +37738,7 @@ snapshots: prettier@2.8.8: {} - prettier@3.8.1: {} + prettier@3.9.10: {} pretty-bytes@5.6.0: {} @@ -38863,13 +39389,18 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.0 + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + string-width@8.3.0: + dependencies: + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 string.prototype.trim@1.2.10: dependencies: @@ -38927,7 +39458,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -39371,6 +39902,8 @@ snapshots: '@ts-graphviz/common': 2.1.5 '@ts-graphviz/core': 2.0.7 + ts-toolbelt@9.6.0: {} + tsconfck@3.1.6(typescript@7.0.2): optionalDependencies: typescript: 7.0.2 @@ -39411,6 +39944,8 @@ snapshots: turbo-stream@2.4.1: {} + typanion@3.14.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -39581,6 +40116,8 @@ snapshots: undici-types@6.21.0: {} + undici-types@8.3.0: {} + undici@5.29.0: dependencies: '@fastify/busboy': 2.0.0 @@ -39632,6 +40169,10 @@ snapshots: trough: 2.1.0 vfile: 6.0.3 + uniku@0.5.0: + dependencies: + '@noble/hashes': 2.4.0 + unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: acorn: 8.18.0 @@ -40462,6 +41003,10 @@ snapshots: vscode-languageserver-types@3.18.0: {} + vscode-languageserver@10.1.0: + dependencies: + vscode-languageserver-protocol: 3.18.2 + vscode-languageserver@9.0.1: dependencies: vscode-languageserver-protocol: 3.17.5 @@ -40967,6 +41512,11 @@ snapshots: - bufferutil - utf-8-validate + wrap-ansi@10.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 8.3.0 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -40975,15 +41525,15 @@ snapshots: wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 wrap-ansi@9.0.2: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 wrappy@1.0.2: {} From b52901e0d95415c2026dfa890bfe20be86fd54b8 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 30 Oct 2026 14:15:51 +0100 Subject: [PATCH 060/169] feat(server-utils): Set `db.operation.name` on SQL spans (#25850) I noticed this when bumping junior from v10 to v11, that we are not setting these in some of our db instrumentation. **Why:** pg, mysql, mysql2, tedious, neon, Prisma and Nuxt's db0 spans had `db.query.summary` but no `db.operation.name`, while postgres.js, knex, MongoDB, Redis etc. already set it. That made grouping/alerting by operation inconsistent across DB integrations. The operation is parsed from the sanitized query by a shared helper next to the summary logic, keeping the original case as OTel recommends. **How to review:** Start with `getSqlOperationName` in `utils/sql.ts`; the integration changes are one-liners. Neon batches follow the OTel rule (`BATCH ` if all statements share one operation, else `BATCH`). **Unsure about:** For `WITH` queries I report the main statement's keyword (the first one outside the parenthesized CTE bodies) rather than `WITH`. Depth counting is safe because it runs on the sanitized query, which has no literals or comments left. tedious `callProcedure` is left without an operation. postgres.js still uppercases its value; I didn't migrate it to avoid a behavior change here. --------- Co-authored-by: Claude Opus 5.5 --- .../suites/tracing/mysql/test.ts | 2 + .../suites/tracing/neon/test.ts | 42 +++++++-- .../suites/tracing/postgres/test.ts | 25 +++++ .../src/runtime/utils/instrumentDatabase.ts | 11 ++- .../server-utils/src/integrations/mysql.ts | 4 +- .../src/integrations/mysql2/index.ts | 4 +- .../server-utils/src/integrations/neon.ts | 10 ++ .../server-utils/src/integrations/postgres.ts | 4 +- .../src/integrations/prisma/tracing-helper.ts | 13 ++- .../server-utils/src/integrations/tedious.ts | 8 +- packages/server-utils/src/utils/sql.ts | 49 +++++++++- packages/server-utils/test/utils/sql.test.ts | 93 +++++++++++++++++-- 12 files changed, 234 insertions(+), 31 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts b/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts index 2a9cb3c3010a..a83b218ec2da 100644 --- a/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts @@ -1,5 +1,6 @@ import { DB_CONNECTION_STRING, + DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -129,6 +130,7 @@ describe('mysql auto instrumentation', () => { ...COMMON_ATTRIBUTES, [DB_QUERY_TEXT]: { type: 'string', value: queryText }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }, ...COMMON_SPAN_PROPS, }); diff --git a/dev-packages/node-integration-tests/suites/tracing/neon/test.ts b/dev-packages/node-integration-tests/suites/tracing/neon/test.ts index 485e6dbfac26..531885d20d3e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/neon/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/neon/test.ts @@ -2,6 +2,7 @@ import type { SerializedStreamedSpan } from '@sentry/core'; import { DB_NAMESPACE, DB_OPERATION_BATCH_SIZE, + DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -34,11 +35,13 @@ const CONNECTION_ATTRIBUTES = { function expectedDbSpan({ name, + operation, statement, status = 'ok', batchSize, }: { name: string; + operation: string; statement: string; status?: 'ok' | 'error'; batchSize?: number; @@ -51,6 +54,7 @@ function expectedDbSpan({ [SENTRY_ORIGIN]: attr(ORIGIN), [DB_QUERY_TEXT]: attr(statement), [DB_QUERY_SUMMARY]: attr(name), + [DB_OPERATION_NAME]: attr(operation), ...(batchSize ? { [DB_OPERATION_BATCH_SIZE]: attr(batchSize, 'integer') } : {}), }), is_segment: false, @@ -87,20 +91,30 @@ describeWithDockerCompose('neon auto instrumentation', { workingDirectory: [__di const neonSpans = getNeonSpans(spans); expect(neonSpans.every(span => span.parent_span_id === segment!.span_id)).toBe(true); expect(neonSpans).toEqual([ - expectedDbSpan({ name: 'CREATE TABLE "User"', statement: CREATE_USER_TABLE_STATEMENT }), - expectedDbSpan({ name: 'INSERT "User"', statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)' }), - expectedDbSpan({ name: 'SELECT "User"', statement: 'SELECT * FROM "User" WHERE "name" = $1' }), + expectedDbSpan({ name: 'CREATE TABLE "User"', operation: 'CREATE', statement: CREATE_USER_TABLE_STATEMENT }), + expectedDbSpan({ + name: 'INSERT "User"', + operation: 'INSERT', + statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', + }), + expectedDbSpan({ + name: 'SELECT "User"', + operation: 'SELECT', + statement: 'SELECT * FROM "User" WHERE "name" = $1', + }), expectedDbSpan({ name: 'SELECT "User"; SELECT "User"', + operation: 'BATCH SELECT', statement: 'SELECT "email" FROM "User"; SELECT "name" FROM "User"', batchSize: 2, }), expectedDbSpan({ name: 'SELECT "does_not_exist_table"', + operation: 'SELECT', statement: 'SELECT * FROM "does_not_exist_table"', status: 'error', }), - expectedDbSpan({ name: 'DROP TABLE "User"', statement: 'DROP TABLE "User"' }), + expectedDbSpan({ name: 'DROP TABLE "User"', operation: 'DROP', statement: 'DROP TABLE "User"' }), ]); }); }); @@ -121,15 +135,20 @@ describeWithDockerCompose('neon auto instrumentation', { workingDirectory: [__di const neonSpans = getNeonSpans(spans); expect(neonSpans.every(span => span.parent_span_id === segment!.span_id)).toBe(true); expect(neonSpans).toEqual([ - expectedDbSpan({ name: 'CREATE TABLE "User"', statement: CREATE_USER_TABLE_STATEMENT }), - expectedDbSpan({ name: 'INSERT "User"', statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)' }), - expectedDbSpan({ name: 'SELECT "User"', statement: 'SELECT * FROM "User"' }), + expectedDbSpan({ name: 'CREATE TABLE "User"', operation: 'CREATE', statement: CREATE_USER_TABLE_STATEMENT }), + expectedDbSpan({ + name: 'INSERT "User"', + operation: 'INSERT', + statement: 'INSERT INTO "User" ("email", "name") VALUES ($1, $2)', + }), + expectedDbSpan({ name: 'SELECT "User"', operation: 'SELECT', statement: 'SELECT * FROM "User"' }), expectedDbSpan({ name: 'SELECT "does_not_exist_table"', + operation: 'SELECT', statement: 'SELECT * FROM "does_not_exist_table"', status: 'error', }), - expectedDbSpan({ name: 'DROP TABLE "User"', statement: 'DROP TABLE "User"' }), + expectedDbSpan({ name: 'DROP TABLE "User"', operation: 'DROP', statement: 'DROP TABLE "User"' }), ]); }); }); @@ -154,10 +173,15 @@ describeWithDockerCompose('neon auto instrumentation', { workingDirectory: [__di expect(neonSpans).toEqual([ expectedDbSpan({ name: 'SELECT pg_catalog.pg_user', + operation: 'SELECT', statement: 'SELECT "email", "name" FROM pg_catalog.pg_user LIMIT ?', status: 'error', }), - expectedDbSpan({ name: 'SELECT pg_catalog.pg_user', statement: 'SELECT "usename" FROM pg_catalog.pg_user' }), + expectedDbSpan({ + name: 'SELECT pg_catalog.pg_user', + operation: 'SELECT', + statement: 'SELECT "usename" FROM pg_catalog.pg_user', + }), ]); }); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts index 4dd383254d1f..ae2e256e33b1 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgres/test.ts @@ -1,6 +1,7 @@ import { DB_CONNECTION_STRING, DB_NAMESPACE, + DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -87,6 +88,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, }, name: 'INSERT "User"', status: 'ok', @@ -101,6 +103,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }, name: 'SELECT "User"', status: 'ok', @@ -114,6 +117,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT "User"', status: 'ok', @@ -126,6 +130,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "does_not_exist_table"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT "does_not_exist_table"', status: 'error', @@ -144,6 +149,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ 'CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))', }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'CREATE' }, }, }, { @@ -156,6 +162,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'DROP TABLE "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'DROP' }, }, }, ]), @@ -218,6 +225,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, }, name: 'INSERT "User"', status: 'ok', @@ -232,6 +240,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }, name: 'SELECT "User"', status: 'ok', @@ -250,6 +259,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ 'CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))', }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'CREATE' }, }, }, { @@ -262,6 +272,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "User"' }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'DROP TABLE "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'DROP' }, }, }, ]), @@ -300,6 +311,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', status: 'ok', @@ -359,6 +371,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', status: 'ok', @@ -397,6 +410,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', status: 'ok', @@ -444,6 +458,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "NativeUser"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, [DB_CONNECTION_STRING]: { type: 'string', @@ -463,6 +478,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "NativeUser"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, [SERVER_ADDRESS]: { type: 'string', value: '127.0.0.1' }, [DB_CONNECTION_STRING]: { type: 'string', @@ -490,6 +506,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ 'CREATE TABLE "NativeUser" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))', }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'CREATE TABLE "NativeUser"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'CREATE' }, }, }, { @@ -506,6 +523,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: QUERY_ORIGIN }, [DB_QUERY_TEXT]: { type: 'string', value: 'DROP TABLE "NativeUser"' }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'DROP TABLE "NativeUser"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'DROP' }, }, }, ]), @@ -568,6 +586,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, }), name: 'INSERT "User"', status: 'ok', @@ -581,6 +600,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT "User"', status: 'ok', @@ -592,6 +612,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "does_not_exist_table"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT "does_not_exist_table"', status: 'error', @@ -629,6 +650,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', status: 'ok', @@ -700,6 +722,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT', status: 'ok', @@ -747,6 +770,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'INSERT' }, }), name: 'INSERT "User"', status: 'ok', @@ -759,6 +783,7 @@ describeWithDockerCompose('postgres auto instrumentation', { workingDirectory: [ [SENTRY_ORIGIN]: { type: 'string', value: ORIGIN }, [SENTRY_OP]: { type: 'string', value: DB }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "User"' }, + [DB_OPERATION_NAME]: { type: 'string', value: 'SELECT' }, }), name: 'SELECT "User"', status: 'ok', diff --git a/packages/nuxt/src/runtime/utils/instrumentDatabase.ts b/packages/nuxt/src/runtime/utils/instrumentDatabase.ts index 4c525e005869..5efa767e5b1d 100644 --- a/packages/nuxt/src/runtime/utils/instrumentDatabase.ts +++ b/packages/nuxt/src/runtime/utils/instrumentDatabase.ts @@ -16,7 +16,13 @@ import { flushIfServerless } from '@sentry/core/server'; import { sanitizeSqlQuery, sanitizeSqlQueryWithSummary, type SqlDialect } from '@sentry/server-utils'; import type { Database, PreparedStatement } from 'db0'; import { type DatabaseConnectionConfig, type DatabaseSpanData, getDatabaseSpanData } from './database-span-data'; -import { DB_NAMESPACE, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME } from '@sentry/conventions/attributes'; +import { + DB_NAMESPACE, + DB_OPERATION_NAME, + DB_QUERY_SUMMARY, + DB_QUERY_TEXT, + DB_SYSTEM_NAME, +} from '@sentry/conventions/attributes'; type MaybeInstrumentedDatabase = Database & { __sentry_instrumented__?: boolean; @@ -261,7 +267,7 @@ function createStartSpanOptions( data: DatabaseSpanData, dialect: SqlDialect | undefined, ): StartSpanOptions { - const { queryText, querySummary } = sanitizeSqlQueryWithSummary(query, dialect); + const { queryText, querySummary, operationName } = sanitizeSqlQueryWithSummary(query, dialect); const client = getClient(); const name = @@ -274,6 +280,7 @@ function createStartSpanOptions( attributes: { [DB_QUERY_TEXT]: queryText, [DB_QUERY_SUMMARY]: querySummary, + [DB_OPERATION_NAME]: operationName, [SENTRY_ORIGIN]: NUXT_DATABASE_ORIGIN, [SENTRY_OP]: DB_QUERY, ...data, diff --git a/packages/server-utils/src/integrations/mysql.ts b/packages/server-utils/src/integrations/mysql.ts index 1a68350361e6..599edf59392f 100644 --- a/packages/server-utils/src/integrations/mysql.ts +++ b/packages/server-utils/src/integrations/mysql.ts @@ -1,6 +1,7 @@ import * as diagnosticsChannel from '../utils/diagnosticsChannel'; import { DB_NAMESPACE, + DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -81,7 +82,7 @@ function instrumentMysql(): void { // handler with the caller's context lost. `deferSpanEnd` replays this scope onto the emitter. data._sentryCallerScope = getCurrentScope(); - const { queryText, querySummary } = sanitizeSqlQueryWithSummary(sql, 'mysql'); + const { queryText, querySummary, operationName } = sanitizeSqlQueryWithSummary(sql, 'mysql'); const client = getClient(); const name = @@ -102,6 +103,7 @@ function instrumentMysql(): void { [DB_USER]: user, [DB_QUERY_TEXT]: queryText, [DB_QUERY_SUMMARY]: querySummary, + [DB_OPERATION_NAME]: operationName, [SERVER_ADDRESS]: host, [SERVER_PORT]: portIsNumber ? portNumber : undefined, }, diff --git a/packages/server-utils/src/integrations/mysql2/index.ts b/packages/server-utils/src/integrations/mysql2/index.ts index f8aa296266f9..88bc7cb0ab98 100644 --- a/packages/server-utils/src/integrations/mysql2/index.ts +++ b/packages/server-utils/src/integrations/mysql2/index.ts @@ -18,6 +18,7 @@ import { mysql2ModuleNames } from '../../orchestrion/config/mysql2'; import { invokeOrchestrionInstrumentation } from '../../orchestrion/instrumentation'; import { DB_NAMESPACE, + DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -77,7 +78,7 @@ function subscribeQueryChannel(channelName: ChannelName): void { bindTracingChannelToSpan(diagnosticsChannel.tracingChannel(channelName), data => { const statement = getQueryText(data.arguments); const connectionAttributes = getConnectionAttributes(data.self?.config); - const { queryText, querySummary } = sanitizeSqlQueryWithSummary(statement, 'mysql'); + const { queryText, querySummary, operationName } = sanitizeSqlQueryWithSummary(statement, 'mysql'); const client = getClient(); const name = @@ -95,6 +96,7 @@ function subscribeQueryChannel(channelName: ChannelName): void { [DB_SYSTEM_NAME]: DB_SYSTEM_VALUE_MYSQL, [DB_QUERY_TEXT]: queryText || undefined, [DB_QUERY_SUMMARY]: querySummary, + [DB_OPERATION_NAME]: operationName, ...connectionAttributes, }, }); diff --git a/packages/server-utils/src/integrations/neon.ts b/packages/server-utils/src/integrations/neon.ts index e4130d14942c..349fb7d7ab84 100644 --- a/packages/server-utils/src/integrations/neon.ts +++ b/packages/server-utils/src/integrations/neon.ts @@ -1,6 +1,7 @@ import * as diagnosticsChannel from '../utils/diagnosticsChannel'; import { DB_OPERATION_BATCH_SIZE, + DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -101,6 +102,7 @@ function httpQuerySpanOptions(args: unknown[]): Parameters sanitizeSqlQueryWithSummary(text)); const queryText = joinDefined(statements.map(s => s.queryText)); const querySummary = joinDefined(statements.map(s => s.querySummary)); + const operationName = batchSize === undefined ? statements[0]?.operationName : getBatchOperationName(statements); const name = querySummary || DB_SYSTEM_POSTGRESQL; return { @@ -113,11 +115,19 @@ function httpQuerySpanOptions(args: unknown[]): Parameters` when every statement shares one operation, plain `BATCH` otherwise. +function getBatchOperationName(statements: { operationName: string | undefined }[]): string { + const operations = new Set(statements.map(s => s.operationName?.toUpperCase())); + const [operation] = operations; + return operations.size === 1 && operation ? `BATCH ${operation}` : 'BATCH'; +} + function joinDefined(parts: (string | undefined)[]): string | undefined { const defined = parts.filter((part): part is string => !!part); return defined.length ? defined.join('; ') : undefined; diff --git a/packages/server-utils/src/integrations/postgres.ts b/packages/server-utils/src/integrations/postgres.ts index 3231e0e60a06..ed39a15bf1c7 100644 --- a/packages/server-utils/src/integrations/postgres.ts +++ b/packages/server-utils/src/integrations/postgres.ts @@ -1,6 +1,7 @@ import * as diagnosticsChannel from '../utils/diagnosticsChannel'; import { DB_NAMESPACE, + DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -189,7 +190,7 @@ function querySpanOptions( const params = (ctx.self as { connectionParameters?: PgConnectionParams } | undefined)?.connectionParameters ?? {}; const queryConfig = extractQueryConfig(ctx.arguments); const client = getClient(); - const { queryText, querySummary } = sanitizeSqlQueryWithSummary(queryConfig?.text); + const { queryText, querySummary, operationName } = sanitizeSqlQueryWithSummary(queryConfig?.text); const name = client && hasSpanStreamingEnabled(client) ? querySummary || params.database || DB_SYSTEM_POSTGRESQL @@ -203,6 +204,7 @@ function querySpanOptions( [SENTRY_ORIGIN]: origin, [DB_QUERY_TEXT]: queryText || undefined, [DB_QUERY_SUMMARY]: querySummary, + [DB_OPERATION_NAME]: operationName, [ATTR_PG_PLAN]: typeof queryConfig?.name === 'string' ? queryConfig.name : undefined, }, }; diff --git a/packages/server-utils/src/integrations/prisma/tracing-helper.ts b/packages/server-utils/src/integrations/prisma/tracing-helper.ts index 218e7c788134..8527efee244d 100644 --- a/packages/server-utils/src/integrations/prisma/tracing-helper.ts +++ b/packages/server-utils/src/integrations/prisma/tracing-helper.ts @@ -26,6 +26,7 @@ import { import { DEBUG_BUILD } from '../../debug-build'; import type { EngineSpan, ExtendedSpanOptions, SpanCallback, TracingHelper } from './types'; import { + DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_STATEMENT, @@ -35,7 +36,13 @@ import { SENTRY_OP, SENTRY_ORIGIN, } from '@sentry/conventions/attributes'; -import { getSqlQuerySummary, sanitizeSqlQuery, type SqlDialect, toSqlDialect } from '../../utils/sql'; +import { + getSqlOperationName, + getSqlQuerySummary, + sanitizeSqlQuery, + type SqlDialect, + toSqlDialect, +} from '../../utils/sql'; // Reading `process.env` can throw in runtimes that gate env access (e.g. Deno without `--allow-env`) // and `process` may be absent altogether (edge runtimes), so this degrades to `false` in those cases. @@ -117,7 +124,9 @@ function buildSpanAttributes(name: string, attributes: Record | if (statement) { // Sanitized before summarizing, so that a string literal containing `from`/`join` can't leak a // value into the summary. - merged[DB_QUERY_SUMMARY] = getSqlQuerySummary(sanitizeSqlQuery(statement, getSqlDialect(merged))); + const queryText = sanitizeSqlQuery(statement, getSqlDialect(merged)); + merged[DB_QUERY_SUMMARY] = getSqlQuerySummary(queryText); + merged[DB_OPERATION_NAME] ??= getSqlOperationName(queryText); } return merged; diff --git a/packages/server-utils/src/integrations/tedious.ts b/packages/server-utils/src/integrations/tedious.ts index 825ababebd68..b584d8582699 100644 --- a/packages/server-utils/src/integrations/tedious.ts +++ b/packages/server-utils/src/integrations/tedious.ts @@ -14,6 +14,7 @@ import { } from '@sentry/core'; import { DB_NAMESPACE, + DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -29,7 +30,7 @@ import type { OrchestrionChannelContext } from '../orchestrion/types'; import { CHANNELS } from '../orchestrion/channels'; import { tediousModuleNames } from '../orchestrion/config/tedious'; import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation'; -import { getSqlQuerySummary, sanitizeSqlQuery } from '../utils/sql'; +import { getSqlOperationName, getSqlQuerySummary, sanitizeSqlQuery } from '../utils/sql'; // NOTE: this uses the same name as the OTel integration by design. When orchestrion injection is active, // `_init` swaps the OTel `Tedious` integration out of the defaults and appends this one (matched by name). @@ -132,7 +133,9 @@ function subscribeQuery(channelName: string, operation: string): void { const databaseName = connection[currentDatabaseSymbol]; const sql = extractSql(request); const queryText = sql ? sanitizeSqlQuery(sql, 'mssql') : undefined; - const querySummary = queryText && operation !== 'callProcedure' ? getSqlQuerySummary(queryText) : undefined; + const isProcedureCall = operation === 'callProcedure'; + const querySummary = queryText && !isProcedureCall ? getSqlQuerySummary(queryText) : undefined; + const operationName = isProcedureCall ? undefined : getSqlOperationName(queryText); const attributes: SpanAttributes = { [SENTRY_OP]: DB, @@ -144,6 +147,7 @@ function subscribeQuery(channelName: string, operation: string): void { [DB_USER]: connection.config?.userName ?? connection.config?.authentication?.options?.userName, [DB_QUERY_TEXT]: queryText, [DB_QUERY_SUMMARY]: querySummary, + [DB_OPERATION_NAME]: operationName, [ATTR_DB_SQL_TABLE]: request.table, [SERVER_ADDRESS]: connection.config?.server, [SERVER_PORT]: connection.config?.options?.port, diff --git a/packages/server-utils/src/utils/sql.ts b/packages/server-utils/src/utils/sql.ts index f8ecdb4a9078..22198b805a67 100644 --- a/packages/server-utils/src/utils/sql.ts +++ b/packages/server-utils/src/utils/sql.ts @@ -103,6 +103,43 @@ export function getSqlQuerySummary(query: string | undefined): string | undefine return truncate(query.trim().split(/\s+/)[0] ?? query); } +const LEADING_KEYWORD_RE = /^\s*(?:\(\s*)*([A-Za-z]+)\b/; +const CTE_MAIN_STATEMENT_RE = /\(|\)|\b(SELECT|INSERT|UPDATE|DELETE|MERGE|VALUES|TABLE)\b/gi; + +/** + * Derives `db.operation.name` from a SQL query: the first operation keyword, in its original case. + * + * @see https://opentelemetry.io/docs/specs/semconv/registry/attributes/db/#db-operation-name + */ +export function getSqlOperationName(query: string | undefined): string | undefined { + const match = query ? LEADING_KEYWORD_RE.exec(query) : null; + const keyword = match?.[1]; + if (!query || !match || !keyword) { + return undefined; + } + // Scanned from after `WITH`, so parentheses wrapping the whole query don't shift the depth. + return keyword.toUpperCase() === 'WITH' ? getCteMainOperationName(query.slice(match[0].length)) : keyword; +} + +/** + * The operation of a `WITH` query is its main statement: the first operation keyword outside the + * CTE bodies, which are all parenthesized. Counting parentheses is safe on a sanitized query, which + * has no literals or comments left. + */ +function getCteMainOperationName(query: string): string | undefined { + let depth = 0; + for (const match of query.matchAll(CTE_MAIN_STATEMENT_RE)) { + if (match[0] === '(') { + depth++; + } else if (match[0] === ')') { + depth--; + } else if (depth === 0) { + return match[1]; + } + } + return undefined; +} + function extractTableNames(sql: string): string[] { const tables: string[] = []; TOKEN_RE.lastIndex = 0; @@ -366,6 +403,7 @@ export function sanitizeSqlQuery(sqlQuery: string | undefined, dialect: SqlDiale // whether it is looking at SQL syntax or at a user-supplied value. stripLiteralsAndComments(sqlQuery, dialect) .replace(/;\s*$/, '') // Remove trailing semicolons + .replace(/^[\s;]+/, '') // Remove leading semicolons (T-SQL's `;WITH`) // Collapse whitespace to a single space (after removing comments) .replace(/\s+/g, ' ') .trim() // Remove extra spaces and trim @@ -385,14 +423,15 @@ export function sanitizeSqlQuery(sqlQuery: string | undefined, dialect: SqlDiale } /** - * Sanitizes a collected SQL statement and derives the matching `db.query.summary`, the pair the SQL - * integrations attach to their spans. Both come back `undefined` when there is no statement, so an - * empty query omits the attributes instead of reporting the sanitizer's fallback text. + * Sanitizes a collected SQL statement and derives the matching `db.query.summary` and + * `db.operation.name`, the attributes the SQL integrations attach to their spans. All come back + * `undefined` when there is no statement, so an empty query omits the attributes instead of + * reporting the sanitizer's fallback text. */ export function sanitizeSqlQueryWithSummary( sqlQuery: string | undefined, dialect?: SqlDialect, -): { queryText: string | undefined; querySummary: string | undefined } { +): { queryText: string | undefined; querySummary: string | undefined; operationName: string | undefined } { const queryText = sqlQuery ? sanitizeSqlQuery(sqlQuery, dialect) : undefined; - return { queryText, querySummary: getSqlQuerySummary(queryText) }; + return { queryText, querySummary: getSqlQuerySummary(queryText), operationName: getSqlOperationName(queryText) }; } diff --git a/packages/server-utils/test/utils/sql.test.ts b/packages/server-utils/test/utils/sql.test.ts index 50ed62d4a50e..dfd1e2cbe814 100644 --- a/packages/server-utils/test/utils/sql.test.ts +++ b/packages/server-utils/test/utils/sql.test.ts @@ -1,5 +1,11 @@ import { describe, expect, it } from 'vitest'; -import { getSqlQuerySummary, sanitizeSqlQuery, sanitizeSqlQueryWithSummary, toSqlDialect } from '../../src/utils/sql'; +import { + getSqlOperationName, + getSqlQuerySummary, + sanitizeSqlQuery, + sanitizeSqlQueryWithSummary, + toSqlDialect, +} from '../../src/utils/sql'; describe('getSqlQuerySummary', () => { it.each([undefined, ''])('returns undefined for %j', input => { @@ -283,6 +289,39 @@ describe('getSqlQuerySummary', () => { }); }); +describe('getSqlOperationName', () => { + it.each([undefined, '', ' ', '?'])('returns undefined for %j', input => { + expect(getSqlOperationName(input)).toBeUndefined(); + }); + + it.each([ + ['SELECT * FROM users WHERE id = ?', 'SELECT'], + ['select count(*) from orders', 'select'], + [' INSERT INTO users (name) VALUES (?)', 'INSERT'], + ['UPDATE users SET name = ? WHERE id = ?', 'UPDATE'], + ['DELETE FROM users WHERE id = ?', 'DELETE'], + ['CREATE TABLE IF NOT EXISTS users (id INT)', 'CREATE'], + ['(SELECT * FROM users) UNION (SELECT * FROM admins)', 'SELECT'], + ['BEGIN', 'BEGIN'], + ['PRAGMA table_info(users)', 'PRAGMA'], + ])('%j => %j', (input, expected) => { + expect(getSqlOperationName(input)).toBe(expected); + }); + + it.each([ + ['WITH active AS (SELECT * FROM users WHERE active = ?) SELECT * FROM active', 'SELECT'], + ['with moved AS (DELETE FROM queue RETURNING *) insert into archive SELECT * FROM moved', 'insert'], + ['WITH RECURSIVE t(n) AS (VALUES (?)) SELECT n FROM t', 'SELECT'], + ['WITH a AS (SELECT ?), b (x, y) AS (SELECT * FROM a) UPDATE t SET x = (SELECT x FROM b)', 'UPDATE'], + ['WITH x AS (VALUES (?)) TABLE x', 'TABLE'], + ['WITH x AS (SELECT ?)', undefined], + ['(WITH x AS (SELECT ?) SELECT * FROM x)', 'SELECT'], + ['( (with x AS (SELECT ?) delete FROM t WHERE id IN (SELECT * FROM x)) )', 'delete'], + ])('takes the main statement of a WITH query: %j => %j', (input, expected) => { + expect(getSqlOperationName(input)).toBe(expected); + }); +}); + describe('sanitizeSqlQuery', () => { describe('passthrough (no literals)', () => { it.each([ @@ -320,13 +359,20 @@ describe('sanitizeSqlQuery', () => { }); }); - describe('trailing semicolon removal', () => { + describe('semicolon removal', () => { it.each([ ['SELECT * FROM users;', 'SELECT * FROM users'], ['SELECT * FROM users; ', 'SELECT * FROM users'], ])('removes trailing semicolon: %p', (input, expected) => { expect(sanitizeSqlQuery(input)).toBe(expected); }); + + it.each([ + [';SELECT * FROM users', 'SELECT * FROM users'], + [' ; ;WITH x AS (SELECT ?) SELECT * FROM x', 'WITH x AS (SELECT ?) SELECT * FROM x'], + ])('removes leading semicolons: %p', (input, expected) => { + expect(sanitizeSqlQuery(input)).toBe(expected); + }); }); describe('$n placeholder preservation (OTEL compliance)', () => { @@ -853,6 +899,7 @@ describe('sanitizeSqlQueryWithSummary', () => { expect(sanitizeSqlQueryWithSummary("SELECT * FROM users WHERE email = 'jane@example.com'")).toEqual({ queryText: 'SELECT * FROM users WHERE email = ?', querySummary: 'SELECT users', + operationName: 'SELECT', }); }); @@ -860,12 +907,30 @@ describe('sanitizeSqlQueryWithSummary', () => { expect(sanitizeSqlQueryWithSummary('SELECT * FROM users WHERE email = "jane@example.com"', 'mysql')).toEqual({ queryText: 'SELECT * FROM users WHERE email = ?', querySummary: 'SELECT users', + operationName: 'SELECT', + }); + }); + + it("names the operation of T-SQL's `;WITH` queries", () => { + expect( + sanitizeSqlQueryWithSummary(";WITH x AS (SELECT name FROM users) SELECT * FROM x WHERE name = 'a'", 'mssql'), + ).toMatchObject({ + queryText: 'WITH x AS (SELECT name FROM users) SELECT * FROM x WHERE name = ?', + operationName: 'SELECT', }); }); - it('returns undefined for both when there is no statement', () => { - expect(sanitizeSqlQueryWithSummary(undefined)).toEqual({ queryText: undefined, querySummary: undefined }); - expect(sanitizeSqlQueryWithSummary('')).toEqual({ queryText: undefined, querySummary: undefined }); + it('returns undefined for all when there is no statement', () => { + expect(sanitizeSqlQueryWithSummary(undefined)).toEqual({ + queryText: undefined, + querySummary: undefined, + operationName: undefined, + }); + expect(sanitizeSqlQueryWithSummary('')).toEqual({ + queryText: undefined, + querySummary: undefined, + operationName: undefined, + }); }); }); @@ -891,6 +956,7 @@ describe('sanitizeSqlQueryWithSummary', () => { expect(sanitizeSqlQueryWithSummary("SELECT * FROM users WHERE email = 'jane@example.com'")).toEqual({ queryText: 'SELECT * FROM users WHERE email = ?', querySummary: 'SELECT users', + operationName: 'SELECT', }); }); @@ -903,6 +969,7 @@ describe('sanitizeSqlQueryWithSummary', () => { expect(sanitizeSqlQueryWithSummary(input, dialect)).toEqual({ queryText: 'SELECT * FROM users WHERE email = ?', querySummary: 'SELECT users', + operationName: 'SELECT', }); }); @@ -910,6 +977,7 @@ describe('sanitizeSqlQueryWithSummary', () => { expect(sanitizeSqlQueryWithSummary("SELECT * FROM [users] WHERE [email] = 'jane@example.com'", 'mssql')).toEqual({ queryText: 'SELECT * FROM [users] WHERE [email] = ?', querySummary: 'SELECT [users]', + operationName: 'SELECT', }); }); @@ -917,11 +985,20 @@ describe('sanitizeSqlQueryWithSummary', () => { expect(sanitizeSqlQueryWithSummary("SELECT * FROM users WHERE bio = 'from secret_table'")).toEqual({ queryText: 'SELECT * FROM users WHERE bio = ?', querySummary: 'SELECT users', + operationName: 'SELECT', }); }); - it('returns undefined for both when there is no statement', () => { - expect(sanitizeSqlQueryWithSummary(undefined)).toEqual({ queryText: undefined, querySummary: undefined }); - expect(sanitizeSqlQueryWithSummary('')).toEqual({ queryText: undefined, querySummary: undefined }); + it('returns undefined for all when there is no statement', () => { + expect(sanitizeSqlQueryWithSummary(undefined)).toEqual({ + queryText: undefined, + querySummary: undefined, + operationName: undefined, + }); + expect(sanitizeSqlQueryWithSummary('')).toEqual({ + queryText: undefined, + querySummary: undefined, + operationName: undefined, + }); }); }); From 7aca628e78f89c852f449d5e15ba2b42c915b79a Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 30 Oct 2026 14:15:52 +0100 Subject: [PATCH 061/169] feat(server-utils): Move to new `getSqlQueryAttributes` helper for consistency (#25927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** `sanitizeSqlQueryWithSummary` grew a third value with `db.operation.name`, and every SQL integration copied the result into the same three attributes by hand. `getSqlQueryAttributes` returns them keyed by attribute name, so integrations spread it and the keys can't drift. The mysql2 diagnostics-channel subscriber and postgres.js drop their own operation regexes for the shared helper, so their `db.operation.name` keeps the statement's case and resolves CTEs like the other integrations (postgres.js still prefers the server-reported command once a query succeeds). postgres.js also no longer reports the sanitizer's `Unknown SQL Query` placeholder as `db.query.text` when there is no query; its public `requestHook` gets `''` in that case. **How to review:** Start with `utils/sql.ts`, then the Prisma tracing helper. Prisma keeps its own `db.query.text`/`db.operation.name` and only falls back to ours; `db.query.summary` is always ours, so the streamed span name stays dialect-aware across Prisma versions. Prisma 5 only reports `db.statement`, so its spans now also get a sanitized `db.query.text` (see the v5 test). Tedious clears the summary and operation name for procedure calls, as before. **Unsure about:** `@sentry/server-utils` exports the renamed helper, but the package is internal (no SemVer guarantee) and Nuxt pins it exactly, so I didn't keep an alias. Stacked on #25850. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- .../suites/tracing/prisma-orm-v5/test.ts | 26 +++-- .../src/runtime/utils/instrumentDatabase.ts | 20 ++-- packages/server-utils/src/exports.ts | 2 +- .../server-utils/src/integrations/knex.ts | 11 +- .../server-utils/src/integrations/mysql.ts | 13 +-- .../src/integrations/mysql2/index.ts | 15 ++- .../mysql2/mysql2-dc-subscriber.ts | 17 +-- .../server-utils/src/integrations/neon.ts | 15 +-- .../src/integrations/postgres-js.ts | 21 ++-- .../server-utils/src/integrations/postgres.ts | 13 +-- .../src/integrations/postgresjs.ts | 31 ++---- .../src/integrations/prisma/tracing-helper.ts | 28 ++--- .../server-utils/src/integrations/tedious.ts | 21 ++-- packages/server-utils/src/utils/sql.ts | 26 +++-- .../test/integrations/prisma.test.ts | 64 +++++++++-- packages/server-utils/test/utils/sql.test.ts | 101 ++++++------------ 16 files changed, 205 insertions(+), 219 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts index 4c528b7c3aa5..66a5787cd6a1 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v5/test.ts @@ -1,5 +1,6 @@ import { DB_QUERY_SUMMARY, + DB_QUERY_TEXT, DB_STATEMENT, DB_SYSTEM, SENTRY_KIND, @@ -15,6 +16,13 @@ afterAll(() => { cleanupChildProcesses(); }); +// Prisma 5 only reports the raw `db.statement`; `db.query.text` is the SDK's sanitized copy of it. +const INSERT_USER_QUERY_TEXT = + 'INSERT INTO "public"."User" ("createdAt","email","name") VALUES ($1,$2,$3) RETURNING "public"."User"."id", "public"."User"."createdAt", "public"."User"."email", "public"."User"."name"'; +const SELECT_USER_QUERY_TEXT = + 'SELECT "public"."User"."id", "public"."User"."createdAt", "public"."User"."email", "public"."User"."name" FROM "public"."User" WHERE ?=? OFFSET $1'; +const DELETE_USER_QUERY_TEXT = 'DELETE FROM "public"."User" WHERE "public"."User"."email"::text LIKE $1'; + const ADDITIONAL_DEPENDENCIES = { '@prisma/client': '5.22.0', prisma: '5.22.0', @@ -82,6 +90,7 @@ function expectPrismaV5Spans(container: SerializedStreamedSpanContainer): void { expect.objectContaining({ attributes: expect.objectContaining({ [DB_STATEMENT]: { type: 'string', value: expect.stringContaining('INSERT INTO') }, + [DB_QUERY_TEXT]: { type: 'string', value: INSERT_USER_QUERY_TEXT }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'INSERT "public"."User"' }, [DB_SYSTEM]: { type: 'string', value: 'postgresql' }, [SENTRY_KIND]: { type: 'string', value: 'client' }, @@ -94,6 +103,7 @@ function expectPrismaV5Spans(container: SerializedStreamedSpanContainer): void { expect.objectContaining({ attributes: expect.objectContaining({ [DB_STATEMENT]: { type: 'string', value: expect.stringContaining('SELECT') }, + [DB_QUERY_TEXT]: { type: 'string', value: SELECT_USER_QUERY_TEXT }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'SELECT "public"."User"' }, [DB_SYSTEM]: { type: 'string', value: 'postgresql' }, [SENTRY_KIND]: { type: 'string', value: 'client' }, @@ -106,6 +116,7 @@ function expectPrismaV5Spans(container: SerializedStreamedSpanContainer): void { expect.objectContaining({ attributes: expect.objectContaining({ [DB_STATEMENT]: { type: 'string', value: expect.stringContaining('DELETE') }, + [DB_QUERY_TEXT]: { type: 'string', value: DELETE_USER_QUERY_TEXT }, [DB_QUERY_SUMMARY]: { type: 'string', value: 'DELETE "public"."User"' }, [DB_SYSTEM]: { type: 'string', value: 'postgresql' }, [SENTRY_KIND]: { type: 'string', value: 'client' }, @@ -124,15 +135,16 @@ function expectPrismaV5Spans(container: SerializedStreamedSpanContainer): void { querySpans.map(span => ({ name: span.name, summary: span.attributes[DB_QUERY_SUMMARY]?.value, + text: span.attributes[DB_QUERY_TEXT]?.value, })), ).toEqual([ - { name: 'INSERT "public"."User"', summary: 'INSERT "public"."User"' }, - { name: 'SELECT "public"."User"', summary: 'SELECT "public"."User"' }, - { name: 'BEGIN', summary: 'BEGIN' }, - { name: 'INSERT "public"."User"', summary: 'INSERT "public"."User"' }, - { name: 'SELECT "public"."User"', summary: 'SELECT "public"."User"' }, - { name: 'COMMIT', summary: 'COMMIT' }, - { name: 'DELETE "public"."User"', summary: 'DELETE "public"."User"' }, + { name: 'INSERT "public"."User"', summary: 'INSERT "public"."User"', text: INSERT_USER_QUERY_TEXT }, + { name: 'SELECT "public"."User"', summary: 'SELECT "public"."User"', text: SELECT_USER_QUERY_TEXT }, + { name: 'BEGIN', summary: 'BEGIN', text: 'BEGIN' }, + { name: 'INSERT "public"."User"', summary: 'INSERT "public"."User"', text: INSERT_USER_QUERY_TEXT }, + { name: 'SELECT "public"."User"', summary: 'SELECT "public"."User"', text: SELECT_USER_QUERY_TEXT }, + { name: 'COMMIT', summary: 'COMMIT', text: 'COMMIT' }, + { name: 'DELETE "public"."User"', summary: 'DELETE "public"."User"', text: DELETE_USER_QUERY_TEXT }, ]); // The raw engine span name must never leak through. diff --git a/packages/nuxt/src/runtime/utils/instrumentDatabase.ts b/packages/nuxt/src/runtime/utils/instrumentDatabase.ts index 5efa767e5b1d..946a09146723 100644 --- a/packages/nuxt/src/runtime/utils/instrumentDatabase.ts +++ b/packages/nuxt/src/runtime/utils/instrumentDatabase.ts @@ -13,16 +13,10 @@ import { type StartSpanOptions, } from '@sentry/core'; import { flushIfServerless } from '@sentry/core/server'; -import { sanitizeSqlQuery, sanitizeSqlQueryWithSummary, type SqlDialect } from '@sentry/server-utils'; +import { getSqlQueryAttributes, sanitizeSqlQuery, type SqlDialect } from '@sentry/server-utils'; import type { Database, PreparedStatement } from 'db0'; import { type DatabaseConnectionConfig, type DatabaseSpanData, getDatabaseSpanData } from './database-span-data'; -import { - DB_NAMESPACE, - DB_OPERATION_NAME, - DB_QUERY_SUMMARY, - DB_QUERY_TEXT, - DB_SYSTEM_NAME, -} from '@sentry/conventions/attributes'; +import { DB_NAMESPACE, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME } from '@sentry/conventions/attributes'; type MaybeInstrumentedDatabase = Database & { __sentry_instrumented__?: boolean; @@ -267,20 +261,18 @@ function createStartSpanOptions( data: DatabaseSpanData, dialect: SqlDialect | undefined, ): StartSpanOptions { - const { queryText, querySummary, operationName } = sanitizeSqlQueryWithSummary(query, dialect); + const queryAttributes = getSqlQueryAttributes(query, dialect); const client = getClient(); const name = client && hasSpanStreamingEnabled(client) - ? querySummary || (data[DB_NAMESPACE] as string | undefined) || DB_SPAN_NAME_FALLBACK - : (queryText ?? DB_SPAN_NAME_FALLBACK); + ? queryAttributes[DB_QUERY_SUMMARY] || (data[DB_NAMESPACE] as string | undefined) || DB_SPAN_NAME_FALLBACK + : (queryAttributes[DB_QUERY_TEXT] ?? DB_SPAN_NAME_FALLBACK); return { name, attributes: { - [DB_QUERY_TEXT]: queryText, - [DB_QUERY_SUMMARY]: querySummary, - [DB_OPERATION_NAME]: operationName, + ...queryAttributes, [SENTRY_ORIGIN]: NUXT_DATABASE_ORIGIN, [SENTRY_OP]: DB_QUERY, ...data, diff --git a/packages/server-utils/src/exports.ts b/packages/server-utils/src/exports.ts index 799ed7c69d18..c22a5f605d68 100644 --- a/packages/server-utils/src/exports.ts +++ b/packages/server-utils/src/exports.ts @@ -5,7 +5,7 @@ export type { ServerRequestHandlerConfig } from './utils/wrapServerRequestHandle export { setAsyncLocalStorageAsyncContextStrategy } from './async-context'; export { openTelemetryIntegration, getOtlpTracesEndpoint } from './opentelemetry'; export * from './ai'; -export { getSqlQuerySummary, sanitizeSqlQuery, sanitizeSqlQueryWithSummary } from './utils/sql'; +export { getSqlQuerySummary, sanitizeSqlQuery, getSqlQueryAttributes } from './utils/sql'; export type { SqlDialect } from './utils/sql'; export { instrumentPostgresJsSql } from './integrations/postgresjs'; export type { PostgresConnectionContext } from './integrations/postgresjs'; diff --git a/packages/server-utils/src/integrations/knex.ts b/packages/server-utils/src/integrations/knex.ts index bdc3d4dac16f..4fce7d37673e 100644 --- a/packages/server-utils/src/integrations/knex.ts +++ b/packages/server-utils/src/integrations/knex.ts @@ -34,7 +34,7 @@ import { DEBUG_BUILD } from '../debug-build'; import type { OrchestrionChannelContext } from '../orchestrion/types'; import { CHANNELS } from '../orchestrion/channels'; import { bindTracingChannelToSpan } from '../tracing-channel'; -import { sanitizeSqlQueryWithSummary, toSqlDialect } from '../utils/sql'; +import { getSqlQueryAttributes, toSqlDialect } from '../utils/sql'; // NOTE: this uses the same name as the OTel integration by design. `@sentry/node`'s `knexIntegration` // picks this subscriber over the vendored OTel path when orchestrion injection is active. @@ -165,7 +165,7 @@ function subscribeQuery(): void { const dbSystem = mapSystem(client?.driverName); const dialect = toSqlDialect(client?.driverName); - const { queryText: dbStatement, querySummary } = sanitizeSqlQueryWithSummary( + const queryAttributes = getSqlQueryAttributes( query?.sql ? truncate(query.sql, MAX_QUERY_LENGTH) : undefined, dialect, ); @@ -173,6 +173,7 @@ function subscribeQuery(): void { [SENTRY_OP]: DB, [SENTRY_KIND]: 'client', [SENTRY_ORIGIN]: ORIGIN, + ...queryAttributes, 'knex.version': data.moduleVersion, [DB_SYSTEM_NAME]: dbSystem, [ATTR_DB_SQL_TABLE]: table, @@ -182,15 +183,13 @@ function subscribeQuery(): void { [SERVER_ADDRESS]: connection?.host ?? extractHostFromConnectionString(connectionString), [SERVER_PORT]: connection?.port ?? extractPortFromConnectionString(connectionString), [NETWORK_TRANSPORT]: connection?.filename === ':memory:' ? 'inproc' : undefined, - [DB_QUERY_TEXT]: dbStatement, - [DB_QUERY_SUMMARY]: querySummary, }; const sentryClient = getClient(); const spanName = sentryClient && hasSpanStreamingEnabled(sentryClient) - ? querySummary || getSecondaryStreamName(dbSystem, dbNameSpace, operation, table) - : (dbStatement ?? getName(dbNameSpace, operation, table) ?? 'knex.query'); + ? queryAttributes[DB_QUERY_SUMMARY] || getSecondaryStreamName(dbSystem, dbNameSpace, operation, table) + : (queryAttributes[DB_QUERY_TEXT] ?? getName(dbNameSpace, operation, table) ?? 'knex.query'); return startInactiveSpan({ name: spanName, diff --git a/packages/server-utils/src/integrations/mysql.ts b/packages/server-utils/src/integrations/mysql.ts index 599edf59392f..2d7105005ec1 100644 --- a/packages/server-utils/src/integrations/mysql.ts +++ b/packages/server-utils/src/integrations/mysql.ts @@ -1,7 +1,6 @@ import * as diagnosticsChannel from '../utils/diagnosticsChannel'; import { DB_NAMESPACE, - DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -24,7 +23,7 @@ import { hasSpanStreamingEnabled, startInactiveSpan, } from '@sentry/core'; -import { sanitizeSqlQueryWithSummary } from '../utils/sql'; +import { getSqlQueryAttributes } from '../utils/sql'; import type { OrchestrionChannelContext } from '../orchestrion/types'; import { CHANNELS } from '../orchestrion/channels'; import { bindTracingChannelToSpan } from '../tracing-channel'; @@ -82,13 +81,13 @@ function instrumentMysql(): void { // handler with the caller's context lost. `deferSpanEnd` replays this scope onto the emitter. data._sentryCallerScope = getCurrentScope(); - const { queryText, querySummary, operationName } = sanitizeSqlQueryWithSummary(sql, 'mysql'); + const queryAttributes = getSqlQueryAttributes(sql, 'mysql'); const client = getClient(); const name = client && hasSpanStreamingEnabled(client) - ? querySummary || database || DB_SYSTEM_NAME_VALUE_MYSQL - : (queryText ?? 'mysql.query'); + ? queryAttributes[DB_QUERY_SUMMARY] || database || DB_SYSTEM_NAME_VALUE_MYSQL + : (queryAttributes[DB_QUERY_TEXT] ?? 'mysql.query'); return startInactiveSpan({ name, @@ -101,9 +100,7 @@ function instrumentMysql(): void { [DB_CONNECTION_STRING]: getJDBCString(host, portIsNumber ? portNumber : undefined, database), [DB_NAMESPACE]: database, [DB_USER]: user, - [DB_QUERY_TEXT]: queryText, - [DB_QUERY_SUMMARY]: querySummary, - [DB_OPERATION_NAME]: operationName, + ...queryAttributes, [SERVER_ADDRESS]: host, [SERVER_PORT]: portIsNumber ? portNumber : undefined, }, diff --git a/packages/server-utils/src/integrations/mysql2/index.ts b/packages/server-utils/src/integrations/mysql2/index.ts index 88bc7cb0ab98..45b04f1797c5 100644 --- a/packages/server-utils/src/integrations/mysql2/index.ts +++ b/packages/server-utils/src/integrations/mysql2/index.ts @@ -8,7 +8,7 @@ import { startInactiveSpan, waitForTracingChannelBinding, } from '@sentry/core'; -import { sanitizeSqlQueryWithSummary } from '../../utils/sql'; +import { getSqlQueryAttributes } from '../../utils/sql'; import { subscribeMysql2DiagnosticChannels } from './mysql2-dc-subscriber'; import type { OrchestrionChannelContext } from '../../orchestrion/types'; import type { ChannelName } from '../../orchestrion/channels'; @@ -18,7 +18,6 @@ import { mysql2ModuleNames } from '../../orchestrion/config/mysql2'; import { invokeOrchestrionInstrumentation } from '../../orchestrion/instrumentation'; import { DB_NAMESPACE, - DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -78,13 +77,15 @@ function subscribeQueryChannel(channelName: ChannelName): void { bindTracingChannelToSpan(diagnosticsChannel.tracingChannel(channelName), data => { const statement = getQueryText(data.arguments); const connectionAttributes = getConnectionAttributes(data.self?.config); - const { queryText, querySummary, operationName } = sanitizeSqlQueryWithSummary(statement, 'mysql'); + const queryAttributes = getSqlQueryAttributes(statement, 'mysql'); const client = getClient(); const name = client && hasSpanStreamingEnabled(client) - ? querySummary || (connectionAttributes[DB_NAMESPACE] as string | undefined) || DB_SYSTEM_VALUE_MYSQL - : (queryText ?? 'mysql2.query'); + ? queryAttributes[DB_QUERY_SUMMARY] || + (connectionAttributes[DB_NAMESPACE] as string | undefined) || + DB_SYSTEM_VALUE_MYSQL + : (queryAttributes[DB_QUERY_TEXT] ?? 'mysql2.query'); return startInactiveSpan({ name, @@ -94,9 +95,7 @@ function subscribeQueryChannel(channelName: ChannelName): void { [SENTRY_ORIGIN]: ORIGIN, [SENTRY_OP]: DB, [DB_SYSTEM_NAME]: DB_SYSTEM_VALUE_MYSQL, - [DB_QUERY_TEXT]: queryText || undefined, - [DB_QUERY_SUMMARY]: querySummary, - [DB_OPERATION_NAME]: operationName, + ...queryAttributes, ...connectionAttributes, }, }); diff --git a/packages/server-utils/src/integrations/mysql2/mysql2-dc-subscriber.ts b/packages/server-utils/src/integrations/mysql2/mysql2-dc-subscriber.ts index 2a5cc75fd56c..2377c0681fd6 100644 --- a/packages/server-utils/src/integrations/mysql2/mysql2-dc-subscriber.ts +++ b/packages/server-utils/src/integrations/mysql2/mysql2-dc-subscriber.ts @@ -1,7 +1,6 @@ import type { TracingChannel } from 'node:diagnostics_channel'; import { DB_NAMESPACE, - DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -13,7 +12,7 @@ import { import { DB } from '@sentry/conventions/op'; import { getClient, hasSpanStreamingEnabled, startInactiveSpan } from '@sentry/core'; import { bindTracingChannelToSpan } from '../../tracing-channel'; -import { sanitizeSqlQueryWithSummary } from '../../utils/sql'; +import { getSqlQueryAttributes } from '../../utils/sql'; // Channel names published by mysql2 >= 3.20.0 (see mysql2 `lib/tracing.js`). // Hardcoded so the subscriber does not have to import mysql2 — the channels @@ -26,9 +25,6 @@ export const MYSQL2_DC_CHANNEL_POOL_CONNECT = 'mysql2:pool:connect'; const ORIGIN = 'auto.db.mysql2.native_channel'; const DB_SYSTEM_NAME_VALUE_MYSQL = 'mysql'; -// Leading keyword of a SQL statement (SELECT, INSERT, …) → `db.operation.name`. -const SQL_OPERATION_RE = /^\s*(\w+)/; - /** * Shape of the context object mysql2 >= 3.20.0 publishes on its query/execute * tracing channels (see mysql2 `lib/base/connection.js`). @@ -96,14 +92,13 @@ function setupQueryChannel(tracingChannel: MySQL2TracingChannelFactory, channelN // mysql2 does not sanitize its channel payload, so the statement may carry // raw user values (on the `query` channel they are inlined). Strip every // literal before it leaves the process; `values` is never attached. - const { queryText, querySummary } = sanitizeSqlQueryWithSummary(data.query, 'mysql'); - const operation = queryText?.match(SQL_OPERATION_RE)?.[1]?.toUpperCase(); + const queryAttributes = getSqlQueryAttributes(data.query, 'mysql'); const client = getClient(); const name = client && hasSpanStreamingEnabled(client) - ? querySummary || data.database || DB_SYSTEM_NAME_VALUE_MYSQL - : queryText || 'mysql2.query'; + ? queryAttributes[DB_QUERY_SUMMARY] || data.database || DB_SYSTEM_NAME_VALUE_MYSQL + : (queryAttributes[DB_QUERY_TEXT] ?? 'mysql2.query'); return startInactiveSpan({ name, @@ -112,9 +107,7 @@ function setupQueryChannel(tracingChannel: MySQL2TracingChannelFactory, channelN [SENTRY_ORIGIN]: ORIGIN, [SENTRY_OP]: DB, [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MYSQL, - [DB_QUERY_TEXT]: queryText, - [DB_QUERY_SUMMARY]: querySummary, - [DB_OPERATION_NAME]: operation, + ...queryAttributes, [DB_NAMESPACE]: data.database || undefined, [SERVER_ADDRESS]: data.serverAddress, [SERVER_PORT]: data.serverPort, diff --git a/packages/server-utils/src/integrations/neon.ts b/packages/server-utils/src/integrations/neon.ts index 349fb7d7ab84..3a33aef801d7 100644 --- a/packages/server-utils/src/integrations/neon.ts +++ b/packages/server-utils/src/integrations/neon.ts @@ -12,7 +12,7 @@ import { import { DB } from '@sentry/conventions/op'; import type { IntegrationFn, Span } from '@sentry/core'; import { defineIntegration, getActiveSpan, startInactiveSpan } from '@sentry/core'; -import { sanitizeSqlQueryWithSummary } from '../utils/sql'; +import { getSqlQueryAttributes } from '../utils/sql'; import { CHANNELS } from '../orchestrion/channels'; import { neonModuleNames } from '../orchestrion/config/neon'; import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation'; @@ -99,10 +99,11 @@ function httpQuerySpanOptions(args: unknown[]): Parameters !!text) - .map(text => sanitizeSqlQueryWithSummary(text)); - const queryText = joinDefined(statements.map(s => s.queryText)); - const querySummary = joinDefined(statements.map(s => s.querySummary)); - const operationName = batchSize === undefined ? statements[0]?.operationName : getBatchOperationName(statements); + .map(text => getSqlQueryAttributes(text)); + const queryText = joinDefined(statements.map(s => s[DB_QUERY_TEXT])); + const querySummary = joinDefined(statements.map(s => s[DB_QUERY_SUMMARY])); + const operationName = + batchSize === undefined ? statements[0]?.[DB_OPERATION_NAME] : getBatchOperationName(statements); const name = querySummary || DB_SYSTEM_POSTGRESQL; return { @@ -122,8 +123,8 @@ function httpQuerySpanOptions(args: unknown[]): Parameters` when every statement shares one operation, plain `BATCH` otherwise. -function getBatchOperationName(statements: { operationName: string | undefined }[]): string { - const operations = new Set(statements.map(s => s.operationName?.toUpperCase())); +function getBatchOperationName(statements: ReturnType[]): string { + const operations = new Set(statements.map(s => s[DB_OPERATION_NAME]?.toUpperCase())); const [operation] = operations; return operations.size === 1 && operation ? `BATCH ${operation}` : 'BATCH'; } diff --git a/packages/server-utils/src/integrations/postgres-js.ts b/packages/server-utils/src/integrations/postgres-js.ts index 92e813e82ce3..ab595d277c47 100644 --- a/packages/server-utils/src/integrations/postgres-js.ts +++ b/packages/server-utils/src/integrations/postgres-js.ts @@ -21,11 +21,10 @@ import { startInactiveSpan, } from '@sentry/core'; import { DEBUG_BUILD } from '../debug-build'; -import { getSqlQuerySummary, sanitizeSqlQuery } from '../utils/sql'; +import { getSqlQueryAttributes } from '../utils/sql'; import { _buildConnectionContext, _getConnectionAttributes, - _getOperationName, _reconstructQuery, type PostgresConnectionContext, } from './postgresjs'; @@ -165,7 +164,7 @@ function attachConnectionAttributesFromChannel(message: PostgresJsQueryContext): * there. Wrapping happens at handle-start because `reject` can fire * synchronously during dispatch and `cursor()` reassigns both before executing. */ -function wrapQuerySettlement(data: PostgresJsQueryContext, span: Span, sanitizedSqlQuery: string): void { +function wrapQuerySettlement(data: PostgresJsQueryContext, span: Span, operationName: string | undefined): void { const query = data.self; if (!query) { return; @@ -184,7 +183,7 @@ function wrapQuerySettlement(data: PostgresJsQueryContext, span: Span, sanitized try { const command = (resolveArgs[0] as { command?: string } | undefined)?.command; // Re-set the operation name with the server-reported command, which is more reliable than the query text. - span.setAttribute(DB_OPERATION_NAME, _getOperationName(sanitizedSqlQuery, command)); + span.setAttribute(DB_OPERATION_NAME, command || operationName); span.end(); } catch (e) { DEBUG_BUILD && debug.error('[instrumentation:postgresjs] error ending span in resolve:', e); @@ -272,10 +271,8 @@ function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void { return undefined; } - const fullQuery = _reconstructQuery(query.strings); - const sanitizedSqlQuery = sanitizeSqlQuery(fullQuery); - - const querySummary = getSqlQuerySummary(sanitizedSqlQuery); + const queryAttributes = getSqlQueryAttributes(_reconstructQuery(query.strings)); + const sanitizedSqlQuery = queryAttributes[DB_QUERY_TEXT] ?? ''; const client = getClient(); @@ -285,7 +282,7 @@ function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void { const name = client && hasSpanStreamingEnabled(client) - ? querySummary || context?.ATTR_DB_NAMESPACE || DB_SYSTEM_NAME_POSTGRES + ? queryAttributes[DB_QUERY_SUMMARY] || context?.ATTR_DB_NAMESPACE || DB_SYSTEM_NAME_POSTGRES : sanitizedSqlQuery || 'postgresjs.query'; // `sentry.kind: client` matches the mysql/pg channel subscribers. @@ -297,9 +294,7 @@ function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void { [SENTRY_KIND]: 'client', [SENTRY_ORIGIN]: ORIGIN, [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_POSTGRES, - [DB_QUERY_TEXT]: sanitizedSqlQuery, - [DB_QUERY_SUMMARY]: querySummary, - [DB_OPERATION_NAME]: _getOperationName(sanitizedSqlQuery), + ...queryAttributes, }, }); @@ -319,7 +314,7 @@ function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void { } } - wrapQuerySettlement(data, span, sanitizedSqlQuery); + wrapQuerySettlement(data, span, queryAttributes[DB_OPERATION_NAME]); return span; }, diff --git a/packages/server-utils/src/integrations/postgres.ts b/packages/server-utils/src/integrations/postgres.ts index ed39a15bf1c7..182d4f2752cf 100644 --- a/packages/server-utils/src/integrations/postgres.ts +++ b/packages/server-utils/src/integrations/postgres.ts @@ -1,7 +1,6 @@ import * as diagnosticsChannel from '../utils/diagnosticsChannel'; import { DB_NAMESPACE, - DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, @@ -24,7 +23,7 @@ import { hasSpanStreamingEnabled, startInactiveSpan, } from '@sentry/core'; -import { sanitizeSqlQueryWithSummary } from '../utils/sql'; +import { getSqlQueryAttributes } from '../utils/sql'; import type { OrchestrionChannelContext } from '../orchestrion/types'; import { CHANNELS } from '../orchestrion/channels'; import { bindTracingChannelToSpan } from '../tracing-channel'; @@ -190,11 +189,11 @@ function querySpanOptions( const params = (ctx.self as { connectionParameters?: PgConnectionParams } | undefined)?.connectionParameters ?? {}; const queryConfig = extractQueryConfig(ctx.arguments); const client = getClient(); - const { queryText, querySummary, operationName } = sanitizeSqlQueryWithSummary(queryConfig?.text); + const queryAttributes = getSqlQueryAttributes(queryConfig?.text); const name = client && hasSpanStreamingEnabled(client) - ? querySummary || params.database || DB_SYSTEM_POSTGRESQL - : (queryText ?? SPAN_QUERY_FALLBACK); + ? queryAttributes[DB_QUERY_SUMMARY] || params.database || DB_SYSTEM_POSTGRESQL + : (queryAttributes[DB_QUERY_TEXT] ?? SPAN_QUERY_FALLBACK); return { name, @@ -202,9 +201,7 @@ function querySpanOptions( [SENTRY_OP]: DB, ...(connectionString ? getPgConnectionAttributes(params) : getConnectionAttributes(params)), [SENTRY_ORIGIN]: origin, - [DB_QUERY_TEXT]: queryText || undefined, - [DB_QUERY_SUMMARY]: querySummary, - [DB_OPERATION_NAME]: operationName, + ...queryAttributes, [ATTR_PG_PLAN]: typeof queryConfig?.name === 'string' ? queryConfig.name : undefined, }, }; diff --git a/packages/server-utils/src/integrations/postgresjs.ts b/packages/server-utils/src/integrations/postgresjs.ts index c8f11e709f48..3b42f0c80f50 100644 --- a/packages/server-utils/src/integrations/postgresjs.ts +++ b/packages/server-utils/src/integrations/postgresjs.ts @@ -15,7 +15,7 @@ import { startSpanManual, } from '@sentry/core'; import { DEBUG_BUILD } from '../debug-build'; -import { getSqlQuerySummary, sanitizeSqlQuery } from '../utils/sql'; +import { getSqlQueryAttributes } from '../utils/sql'; import { DB_NAMESPACE, DB_OPERATION_NAME, @@ -31,8 +31,6 @@ import { } from '@sentry/conventions/attributes'; import { DB } from '@sentry/conventions/op'; -const SQL_OPERATION_REGEX = /^(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER)/i; - export type PostgresConnectionContext = { ATTR_DB_NAMESPACE?: string; ATTR_SERVER_ADDRESS?: string; @@ -246,11 +244,10 @@ function _wrapSingleQueryHandle( return originalHandle.apply(this, args); } - const fullQuery = _reconstructQuery(query.strings); - const sanitizedSqlQuery = sanitizeSqlQuery(fullQuery); + const queryAttributes = getSqlQueryAttributes(_reconstructQuery(query.strings)); + const sanitizedSqlQuery = queryAttributes[DB_QUERY_TEXT] ?? ''; const client = getClient(); - const querySummary = getSqlQuerySummary(sanitizedSqlQuery); const connectionContext = sqlInstance ? ((sqlInstance as Record)[CONNECTION_CONTEXT_SYMBOL] as PostgresConnectionContext | undefined) @@ -258,7 +255,7 @@ function _wrapSingleQueryHandle( const name = client && hasSpanStreamingEnabled(client) - ? querySummary || connectionContext?.ATTR_DB_NAMESPACE || 'postgres' + ? queryAttributes[DB_QUERY_SUMMARY] || connectionContext?.ATTR_DB_NAMESPACE || 'postgres' : sanitizedSqlQuery || 'postgresjs.query'; return startSpanManual( @@ -268,9 +265,7 @@ function _wrapSingleQueryHandle( [SENTRY_OP]: DB, [SENTRY_ORIGIN]: 'auto.db.postgresjs', [DB_SYSTEM_NAME]: 'postgres', - [DB_QUERY_TEXT]: sanitizedSqlQuery, - [DB_QUERY_SUMMARY]: querySummary, - [DB_OPERATION_NAME]: _getOperationName(sanitizedSqlQuery), + ...queryAttributes, ...(connectionContext && _getConnectionAttributes(connectionContext)), }, }, @@ -293,7 +288,7 @@ function _wrapSingleQueryHandle( apply: (resolveTarget, resolveThisArg, resolveArgs: [{ command?: string }]) => { try { // Re-set the operation name with the server-reported command, which is more reliable than the query text. - span.setAttribute(DB_OPERATION_NAME, _getOperationName(sanitizedSqlQuery, resolveArgs?.[0]?.command)); + span.setAttribute(DB_OPERATION_NAME, resolveArgs?.[0]?.command || queryAttributes[DB_OPERATION_NAME]); span.end(); } catch (e) { DEBUG_BUILD && debug.error('Error ending span in resolve callback:', e); @@ -396,20 +391,6 @@ export function _getConnectionAttributes(connectionContext: PostgresConnectionCo return attributes; } -/** - * Extracts the DB operation name from a SQL query, preferring the server-reported `command`. - * - * @internal Exported for the diagnostics-channel integration. - */ -export function _getOperationName(sanitizedQuery: string | undefined, command?: string): string | undefined { - if (command) { - return command; - } - // Fallback: extract operation from the SQL query - const operationMatch = sanitizedQuery?.match(SQL_OPERATION_REGEX); - return operationMatch?.[1]?.toUpperCase(); -} - /** * Builds a {@link PostgresConnectionContext} from postgres.js' parsed options * (which store `host`/`port` as arrays). Defaults to 'localhost'/5432. diff --git a/packages/server-utils/src/integrations/prisma/tracing-helper.ts b/packages/server-utils/src/integrations/prisma/tracing-helper.ts index 8527efee244d..9d26d09cf5b8 100644 --- a/packages/server-utils/src/integrations/prisma/tracing-helper.ts +++ b/packages/server-utils/src/integrations/prisma/tracing-helper.ts @@ -26,7 +26,6 @@ import { import { DEBUG_BUILD } from '../../debug-build'; import type { EngineSpan, ExtendedSpanOptions, SpanCallback, TracingHelper } from './types'; import { - DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_STATEMENT, @@ -36,13 +35,7 @@ import { SENTRY_OP, SENTRY_ORIGIN, } from '@sentry/conventions/attributes'; -import { - getSqlOperationName, - getSqlQuerySummary, - sanitizeSqlQuery, - type SqlDialect, - toSqlDialect, -} from '../../utils/sql'; +import { getSqlQueryAttributes, type SqlDialect, toSqlDialect } from '../../utils/sql'; // Reading `process.env` can throw in runtimes that gate env access (e.g. Deno without `--allow-env`) // and `process` may be absent altogether (edge runtimes), so this degrades to `false` in those cases. @@ -103,9 +96,17 @@ function registerPrismaSpan(id: string, span: Span): void { * Older Prisma versions emit `prisma:engine:db_query` spans without a db system, so it's backfilled here. */ function buildSpanAttributes(name: string, attributes: Record | undefined): SpanAttributes { + const spanAttributes: SpanAttributes = (attributes as SpanAttributes) || {}; + const statement = getSqlStatement(name, spanAttributes); + const queryAttributes = getSqlQueryAttributes(statement, getSqlDialect(spanAttributes)); + const merged: SpanAttributes = { - ...(attributes as SpanAttributes | undefined), + ...queryAttributes, + ...spanAttributes, [SENTRY_ORIGIN]: PRISMA_ORIGIN, + // Always ours, so the summary (and the streamed span name built from it) is dialect-aware and + // consistent across Prisma versions, even if Prisma starts reporting its own. + [DB_QUERY_SUMMARY]: queryAttributes[DB_QUERY_SUMMARY], }; // Prisma itself emits the deprecated `db.system` on older versions, so both spellings are checked @@ -120,15 +121,6 @@ function buildSpanAttributes(name: string, attributes: Record | merged[SENTRY_OP] = 'db'; } - const statement = getSqlStatement(name, merged); - if (statement) { - // Sanitized before summarizing, so that a string literal containing `from`/`join` can't leak a - // value into the summary. - const queryText = sanitizeSqlQuery(statement, getSqlDialect(merged)); - merged[DB_QUERY_SUMMARY] = getSqlQuerySummary(queryText); - merged[DB_OPERATION_NAME] ??= getSqlOperationName(queryText); - } - return merged; } diff --git a/packages/server-utils/src/integrations/tedious.ts b/packages/server-utils/src/integrations/tedious.ts index b584d8582699..b75080aa7471 100644 --- a/packages/server-utils/src/integrations/tedious.ts +++ b/packages/server-utils/src/integrations/tedious.ts @@ -30,7 +30,7 @@ import type { OrchestrionChannelContext } from '../orchestrion/types'; import { CHANNELS } from '../orchestrion/channels'; import { tediousModuleNames } from '../orchestrion/config/tedious'; import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation'; -import { getSqlOperationName, getSqlQuerySummary, sanitizeSqlQuery } from '../utils/sql'; +import { getSqlQueryAttributes } from '../utils/sql'; // NOTE: this uses the same name as the OTel integration by design. When orchestrion injection is active, // `_init` swaps the OTel `Tedious` integration out of the defaults and appends this one (matched by name). @@ -132,10 +132,12 @@ function subscribeQuery(channelName: string, operation: string): void { const databaseName = connection[currentDatabaseSymbol]; const sql = extractSql(request); - const queryText = sql ? sanitizeSqlQuery(sql, 'mssql') : undefined; - const isProcedureCall = operation === 'callProcedure'; - const querySummary = queryText && !isProcedureCall ? getSqlQuerySummary(queryText) : undefined; - const operationName = isProcedureCall ? undefined : getSqlOperationName(queryText); + const queryAttributes = getSqlQueryAttributes(sql, 'mssql'); + if (operation === 'callProcedure') { + // `sql` is the procedure name here rather than a statement, so there is nothing to summarize. + queryAttributes[DB_QUERY_SUMMARY] = undefined; + queryAttributes[DB_OPERATION_NAME] = undefined; + } const attributes: SpanAttributes = { [SENTRY_OP]: DB, @@ -145,9 +147,7 @@ function subscribeQuery(channelName: string, operation: string): void { [DB_NAMESPACE]: databaseName, // `>=4` uses the `authentication` object; older versions expose `userName` directly. [DB_USER]: connection.config?.userName ?? connection.config?.authentication?.options?.userName, - [DB_QUERY_TEXT]: queryText, - [DB_QUERY_SUMMARY]: querySummary, - [DB_OPERATION_NAME]: operationName, + ...queryAttributes, [ATTR_DB_SQL_TABLE]: request.table, [SERVER_ADDRESS]: connection.config?.server, [SERVER_PORT]: connection.config?.options?.port, @@ -158,8 +158,9 @@ function subscribeQuery(channelName: string, operation: string): void { const span = startInactiveSpan({ name: client && hasSpanStreamingEnabled(client) - ? querySummary || getLowCardinalitySecondarySpanName(operation, databaseName, sql, request.table) - : queryText || getSecondarySpanName(operation, databaseName, request.table), + ? queryAttributes[DB_QUERY_SUMMARY] || + getLowCardinalitySecondarySpanName(operation, databaseName, sql, request.table) + : queryAttributes[DB_QUERY_TEXT] || getSecondarySpanName(operation, databaseName, request.table), attributes, onlyIfParent: true, }); diff --git a/packages/server-utils/src/utils/sql.ts b/packages/server-utils/src/utils/sql.ts index 22198b805a67..7b7d0da67efc 100644 --- a/packages/server-utils/src/utils/sql.ts +++ b/packages/server-utils/src/utils/sql.ts @@ -1,3 +1,5 @@ +import { DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT } from '@sentry/conventions/attributes'; + const MAX_SUMMARY_LENGTH = 255; // A single identifier: quoted (`"..."`, `'...'`, or MySQL backticks) or bare. The quoted forms have @@ -423,15 +425,23 @@ export function sanitizeSqlQuery(sqlQuery: string | undefined, dialect: SqlDiale } /** - * Sanitizes a collected SQL statement and derives the matching `db.query.summary` and - * `db.operation.name`, the attributes the SQL integrations attach to their spans. All come back - * `undefined` when there is no statement, so an empty query omits the attributes instead of - * reporting the sanitizer's fallback text. + * Sanitizes a collected SQL statement into the query attributes the SQL integrations attach to + * their spans: `db.query.text`, `db.query.summary` and `db.operation.name`. All are `undefined` + * when there is no statement, so an empty query omits the attributes instead of reporting the + * sanitizer's fallback text. */ -export function sanitizeSqlQueryWithSummary( +export function getSqlQueryAttributes( sqlQuery: string | undefined, dialect?: SqlDialect, -): { queryText: string | undefined; querySummary: string | undefined; operationName: string | undefined } { - const queryText = sqlQuery ? sanitizeSqlQuery(sqlQuery, dialect) : undefined; - return { queryText, querySummary: getSqlQuerySummary(queryText), operationName: getSqlOperationName(queryText) }; +): { + [DB_QUERY_TEXT]: string | undefined; + [DB_QUERY_SUMMARY]: string | undefined; + [DB_OPERATION_NAME]: string | undefined; +} { + const queryText = (sqlQuery && sanitizeSqlQuery(sqlQuery, dialect)) || undefined; + return { + [DB_QUERY_TEXT]: queryText, + [DB_QUERY_SUMMARY]: getSqlQuerySummary(queryText), + [DB_OPERATION_NAME]: getSqlOperationName(queryText), + }; } diff --git a/packages/server-utils/test/integrations/prisma.test.ts b/packages/server-utils/test/integrations/prisma.test.ts index 2d02718b6d1e..967fcd9d827f 100644 --- a/packages/server-utils/test/integrations/prisma.test.ts +++ b/packages/server-utils/test/integrations/prisma.test.ts @@ -1,3 +1,11 @@ +import { + DB_OPERATION_NAME, + DB_QUERY_SUMMARY, + DB_QUERY_TEXT, + DB_STATEMENT, + DB_SYSTEM, + DB_SYSTEM_NAME, +} from '@sentry/conventions/attributes'; import type { Span } from '@sentry/core'; import { Client, createTransport, getCurrentScope, initAndBind, resolvedSyncPromise, spanToJSON } from '@sentry/core'; import { afterEach, describe, expect, it } from 'vitest'; @@ -77,11 +85,11 @@ describe('instrumentPrisma', () => { instrumentPrisma(); const span = runDbQuerySpan({ - 'db.system.name': 'postgresql', - 'db.query.text': 'SELECT * FROM "public"."User" WHERE "bio" = $1', + [DB_SYSTEM_NAME]: 'postgresql', + [DB_QUERY_TEXT]: 'SELECT * FROM "public"."User" WHERE "bio" = $1', }); - expect(spanToJSON(span).attributes['db.query.summary']).toBe('SELECT "public"."User"'); + expect(spanToJSON(span).attributes[DB_QUERY_SUMMARY]).toBe('SELECT "public"."User"'); }); it.each(['mysql', 'mariadb'])('sanitizes double-quoted string literals as literals on %s', (system: string) => { @@ -91,11 +99,55 @@ describe('instrumentPrisma', () => { // On MySQL `"..."` is a string literal, so treating it as a quoted identifier would let the // `FROM` inside a user-supplied value read as a second table. const span = runDbQuerySpan({ - 'db.system.name': system, - 'db.query.text': 'SELECT * FROM `User` WHERE bio = "x FROM secret_table"', + [DB_SYSTEM_NAME]: system, + [DB_QUERY_TEXT]: 'SELECT * FROM `User` WHERE bio = "x FROM secret_table"', }); - expect(spanToJSON(span).attributes['db.query.summary']).toBe('SELECT `User`'); + expect(spanToJSON(span).attributes[DB_QUERY_SUMMARY]).toBe('SELECT `User`'); + }); + + it('overrides a summary reported by Prisma with the one derived from the statement', () => { + initTestClient(); + instrumentPrisma(); + + const span = runDbQuerySpan({ + [DB_SYSTEM_NAME]: 'postgresql', + [DB_QUERY_TEXT]: 'SELECT * FROM "public"."User" WHERE "bio" = $1', + [DB_QUERY_SUMMARY]: 'prisma summary', + }); + + expect(spanToJSON(span).attributes[DB_QUERY_SUMMARY]).toBe('SELECT "public"."User"'); + }); + }); + + describe('db.query.text', () => { + it('backfills the sanitized statement when Prisma only reports db.statement', () => { + initTestClient(); + instrumentPrisma(); + + const span = runDbQuerySpan({ + [DB_SYSTEM]: 'postgresql', + [DB_STATEMENT]: 'SELECT * FROM "public"."User" WHERE "bio" = \'secret\' LIMIT 10', + }); + + expect(spanToJSON(span).attributes).toMatchObject({ + [DB_STATEMENT]: 'SELECT * FROM "public"."User" WHERE "bio" = \'secret\' LIMIT 10', + [DB_QUERY_TEXT]: 'SELECT * FROM "public"."User" WHERE "bio" = ? LIMIT ?', + [DB_QUERY_SUMMARY]: 'SELECT "public"."User"', + [DB_OPERATION_NAME]: 'SELECT', + }); + }); + + it("keeps Prisma's own db.query.text", () => { + initTestClient(); + instrumentPrisma(); + + const span = runDbQuerySpan({ + [DB_SYSTEM_NAME]: 'postgresql', + [DB_QUERY_TEXT]: 'SELECT * FROM "public"."User" WHERE 1=1 OFFSET $1', + }); + + expect(spanToJSON(span).attributes[DB_QUERY_TEXT]).toBe('SELECT * FROM "public"."User" WHERE 1=1 OFFSET $1'); }); }); diff --git a/packages/server-utils/test/utils/sql.test.ts b/packages/server-utils/test/utils/sql.test.ts index dfd1e2cbe814..580f1b4c847a 100644 --- a/packages/server-utils/test/utils/sql.test.ts +++ b/packages/server-utils/test/utils/sql.test.ts @@ -1,9 +1,10 @@ +import { DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT } from '@sentry/conventions/attributes'; import { describe, expect, it } from 'vitest'; import { getSqlOperationName, getSqlQuerySummary, + getSqlQueryAttributes, sanitizeSqlQuery, - sanitizeSqlQueryWithSummary, toSqlDialect, } from '../../src/utils/sql'; @@ -894,46 +895,6 @@ describe('sanitizeSqlQuery', () => { }); }); -describe('sanitizeSqlQueryWithSummary', () => { - it('returns the sanitized statement and its summary', () => { - expect(sanitizeSqlQueryWithSummary("SELECT * FROM users WHERE email = 'jane@example.com'")).toEqual({ - queryText: 'SELECT * FROM users WHERE email = ?', - querySummary: 'SELECT users', - operationName: 'SELECT', - }); - }); - - it('passes the dialect through to the sanitizer', () => { - expect(sanitizeSqlQueryWithSummary('SELECT * FROM users WHERE email = "jane@example.com"', 'mysql')).toEqual({ - queryText: 'SELECT * FROM users WHERE email = ?', - querySummary: 'SELECT users', - operationName: 'SELECT', - }); - }); - - it("names the operation of T-SQL's `;WITH` queries", () => { - expect( - sanitizeSqlQueryWithSummary(";WITH x AS (SELECT name FROM users) SELECT * FROM x WHERE name = 'a'", 'mssql'), - ).toMatchObject({ - queryText: 'WITH x AS (SELECT name FROM users) SELECT * FROM x WHERE name = ?', - operationName: 'SELECT', - }); - }); - - it('returns undefined for all when there is no statement', () => { - expect(sanitizeSqlQueryWithSummary(undefined)).toEqual({ - queryText: undefined, - querySummary: undefined, - operationName: undefined, - }); - expect(sanitizeSqlQueryWithSummary('')).toEqual({ - queryText: undefined, - querySummary: undefined, - operationName: undefined, - }); - }); -}); - describe('toSqlDialect', () => { it.each([ ['mysql', 'mysql'], @@ -951,12 +912,12 @@ describe('toSqlDialect', () => { }); }); -describe('sanitizeSqlQueryWithSummary', () => { +describe('getSqlQueryAttributes', () => { it('returns the sanitized statement and its summary', () => { - expect(sanitizeSqlQueryWithSummary("SELECT * FROM users WHERE email = 'jane@example.com'")).toEqual({ - queryText: 'SELECT * FROM users WHERE email = ?', - querySummary: 'SELECT users', - operationName: 'SELECT', + expect(getSqlQueryAttributes("SELECT * FROM users WHERE email = 'jane@example.com'")).toEqual({ + [DB_QUERY_TEXT]: 'SELECT * FROM users WHERE email = ?', + [DB_QUERY_SUMMARY]: 'SELECT users', + [DB_OPERATION_NAME]: 'SELECT', }); }); @@ -966,39 +927,43 @@ describe('sanitizeSqlQueryWithSummary', () => { ['standard' as const, 'SELECT * FROM users WHERE email = $$jane@example.com$$'], ['standard' as const, "SELECT * FROM users WHERE email = N'jane@example.com'"], ])('passes the %s dialect through to the sanitizer: %p', (dialect, input) => { - expect(sanitizeSqlQueryWithSummary(input, dialect)).toEqual({ - queryText: 'SELECT * FROM users WHERE email = ?', - querySummary: 'SELECT users', - operationName: 'SELECT', + expect(getSqlQueryAttributes(input, dialect)).toEqual({ + [DB_QUERY_TEXT]: 'SELECT * FROM users WHERE email = ?', + [DB_QUERY_SUMMARY]: 'SELECT users', + [DB_OPERATION_NAME]: 'SELECT', }); }); it('passes the mssql dialect through, so a quote inside a bracketed name stays part of the name', () => { - expect(sanitizeSqlQueryWithSummary("SELECT * FROM [users] WHERE [email] = 'jane@example.com'", 'mssql')).toEqual({ - queryText: 'SELECT * FROM [users] WHERE [email] = ?', - querySummary: 'SELECT [users]', - operationName: 'SELECT', + expect(getSqlQueryAttributes("SELECT * FROM [users] WHERE [email] = 'jane@example.com'", 'mssql')).toEqual({ + [DB_QUERY_TEXT]: 'SELECT * FROM [users] WHERE [email] = ?', + [DB_QUERY_SUMMARY]: 'SELECT [users]', + [DB_OPERATION_NAME]: 'SELECT', }); }); - it('derives the summary from the sanitized statement, not the raw one', () => { - expect(sanitizeSqlQueryWithSummary("SELECT * FROM users WHERE bio = 'from secret_table'")).toEqual({ - queryText: 'SELECT * FROM users WHERE bio = ?', - querySummary: 'SELECT users', - operationName: 'SELECT', + it("names the operation of T-SQL's `;WITH` queries", () => { + expect( + getSqlQueryAttributes(";WITH x AS (SELECT name FROM users) SELECT * FROM x WHERE name = 'a'", 'mssql'), + ).toMatchObject({ + [DB_QUERY_TEXT]: 'WITH x AS (SELECT name FROM users) SELECT * FROM x WHERE name = ?', + [DB_OPERATION_NAME]: 'SELECT', }); }); - it('returns undefined for all when there is no statement', () => { - expect(sanitizeSqlQueryWithSummary(undefined)).toEqual({ - queryText: undefined, - querySummary: undefined, - operationName: undefined, + it('derives the summary from the sanitized statement, not the raw one', () => { + expect(getSqlQueryAttributes("SELECT * FROM users WHERE bio = 'from secret_table'")).toEqual({ + [DB_QUERY_TEXT]: 'SELECT * FROM users WHERE bio = ?', + [DB_QUERY_SUMMARY]: 'SELECT users', + [DB_OPERATION_NAME]: 'SELECT', }); - expect(sanitizeSqlQueryWithSummary('')).toEqual({ - queryText: undefined, - querySummary: undefined, - operationName: undefined, + }); + + it.each([undefined, '', ' ', ';'])('returns undefined for all when there is no statement: %j', input => { + expect(getSqlQueryAttributes(input)).toEqual({ + [DB_QUERY_TEXT]: undefined, + [DB_QUERY_SUMMARY]: undefined, + [DB_OPERATION_NAME]: undefined, }); }); }); From 0041bc69bcadae2082ef24ddf94133249497bb06 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 30 Oct 2026 15:28:56 +0100 Subject: [PATCH 062/169] feat(node): Add `awsLambdaWebIntegration` for Lambda Web Functions (#25355) This adds a new `awsLambdaWebIntegration` to the Node SDK for instrumenting [Lambda Web Functions](https://docs.aws.amazon.com/lambda/latest/dg/web-functions.html). Web Functions run standard Node HTTP servers like Express or Hono, without Lambda-specific handlers or adapters. Enriches event contexts and segment spans with AWS metadata, request IDs, endpoint names, and cold-start information. Closes #25317 conventions PR for new attributes: https://github.com/getsentry/sentry-conventions/pull/715 --------- Co-authored-by: Codex --- .../node-suites/excludes.ts | 1 + .../node-suites/excludes.ts | 9 +- .../scripts/consistentExports.ts | 1 + .../aws-lambda-web/requests/instrument.mjs | 8 + .../aws-lambda-web/requests/scenario.mjs | 28 +++ .../suites/aws-lambda-web/requests/test.ts | 111 +++++++++++ packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/index.ts | 1 + .../node/src/integrations/awsLambdaWeb.ts | 76 ++++++++ packages/node/src/integrations/context.ts | 3 +- packages/node/src/sdk/index.ts | 2 + .../test/integrations/awsLambdaWeb.test.ts | 181 ++++++++++++++++++ 14 files changed, 421 insertions(+), 3 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/aws-lambda-web/requests/instrument.mjs create mode 100644 dev-packages/node-integration-tests/suites/aws-lambda-web/requests/scenario.mjs create mode 100644 dev-packages/node-integration-tests/suites/aws-lambda-web/requests/test.ts create mode 100644 packages/node/src/integrations/awsLambdaWeb.ts create mode 100644 packages/node/test/integrations/awsLambdaWeb.test.ts diff --git a/dev-packages/bun-integration-tests/node-suites/excludes.ts b/dev-packages/bun-integration-tests/node-suites/excludes.ts index 30c08744ef1d..dff3e151b26e 100644 --- a/dev-packages/bun-integration-tests/node-suites/excludes.ts +++ b/dev-packages/bun-integration-tests/node-suites/excludes.ts @@ -7,6 +7,7 @@ // `node:sqlite`, which `flue` needs, and the Vercel keep-alive, which needs `http.server.response.finish`. const NODE_ONLY = [ 'suites/anr/test.ts', + 'suites/aws-lambda-web/**', 'suites/aws-serverless/**', 'suites/breadcrumbs/**', 'suites/child-process/test.ts', diff --git a/dev-packages/deno-integration-tests/node-suites/excludes.ts b/dev-packages/deno-integration-tests/node-suites/excludes.ts index bed8f796a588..e6d5906bc2c8 100644 --- a/dev-packages/deno-integration-tests/node-suites/excludes.ts +++ b/dev-packages/deno-integration-tests/node-suites/excludes.ts @@ -2,8 +2,13 @@ // fails on Deno runs only on the other runtimes, with `test.runIf(supports({ runtimes }))` in the // Node suite, and is not listed here. -// Node-only features: ANR and native thread watchdogs, child process and worker thread breadcrumbs. -const NODE_ONLY = ['suites/anr/test.ts', 'suites/breadcrumbs/**', 'suites/thread-blocked-native/test.ts']; +// Node-only features: ANR and native thread watchdogs, child process and worker thread breadcrumbs, and the AWS Lambda Node runtime. +const NODE_ONLY = [ + 'suites/anr/test.ts', + 'suites/aws-lambda-web/**', + 'suites/breadcrumbs/**', + 'suites/thread-blocked-native/test.ts', +]; // `@sentry/node` instruments `fetch` through undici's diagnostics channels, which Deno's `fetch` // does not publish. `@sentry/deno` has its own `fetchIntegration` for this. These suites check diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index 6bee27e997bc..446589a1db0c 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -51,6 +51,7 @@ const DEPENDENTS: Dependent[] = [ exports: Object.keys(SentryBun), ignoreExports: [ // not supported in bun: + 'awsLambdaWebIntegration', 'NodeClient', 'NODE_VERSION', 'childProcessIntegration', diff --git a/dev-packages/node-integration-tests/suites/aws-lambda-web/requests/instrument.mjs b/dev-packages/node-integration-tests/suites/aws-lambda-web/requests/instrument.mjs new file mode 100644 index 000000000000..d5824acab7ba --- /dev/null +++ b/dev-packages/node-integration-tests/suites/aws-lambda-web/requests/instrument.mjs @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@example.com/1', + tracesSampleRate: process.env.NO_TRACING ? undefined : 1, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/aws-lambda-web/requests/scenario.mjs b/dev-packages/node-integration-tests/suites/aws-lambda-web/requests/scenario.mjs new file mode 100644 index 000000000000..c3fea461fdd1 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/aws-lambda-web/requests/scenario.mjs @@ -0,0 +1,28 @@ +import * as Sentry from '@sentry/node'; +import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests'; +import express from 'express'; + +const app = express(); +let releaseFirst; +const secondRequestStarted = new Promise(resolve => { + releaseFirst = resolve; +}); + +app.get('/:request', async (req, res) => { + if (req.params.request === 'first') { + process.stdout.write('FIRST_STARTED\n'); + await secondRequestStarted; + } else { + releaseFirst(); + } + + Sentry.captureException(new Error(req.params.request)); + Sentry.logger.info('Request handled'); + Sentry.metrics.count('request_count', 1); + res.send({ ok: true }); + setImmediate(() => { + void Sentry.flush(); + }); +}); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/aws-lambda-web/requests/test.ts b/dev-packages/node-integration-tests/suites/aws-lambda-web/requests/test.ts new file mode 100644 index 000000000000..270e9777f8d7 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/aws-lambda-web/requests/test.ts @@ -0,0 +1,111 @@ +import { + AWS_LAMBDA_WEB_ENDPOINT_NAME, + CLOUD_PLATFORM, + CLOUD_PROVIDER, + CLOUD_REGION, + FAAS_COLDSTART, + FAAS_INVOCATION_ID, + FAAS_MAX_MEMORY, + SENTRY_OP, +} from '@sentry/conventions/attributes'; +import { expect, vi } from 'vitest'; +import { createEsmAndCjsTests } from '../../../utils/runner'; + +createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test.each([ + { name: 'default tracing', tracing: true }, + { name: 'tracing disabled', tracing: false }, + ])('$name: isolates concurrent requests and leaves logs and metrics unenriched', async ({ tracing }) => { + const runner = createRunner() + .withEnv({ + AWS_LAMBDA_HTTP_ENDPOINT: '0.0.0.0:3000', + AWS_REGION: 'us-east-1', + AWS_LAMBDA_FUNCTION_MEMORY_SIZE: '2048', + AWS_LAMBDA_INITIALIZATION_TYPE: 'native-http', + AWS_EXECUTION_ENV: 'AWS_Lambda_nodejs24.x-lite', + NO_TRACING: tracing ? '' : '1', + }) + .unordered(); + + const spans = []; + + for (const request of ['first', 'second']) { + runner.expect({ + event: { + exception: { values: [expect.objectContaining({ value: request })] }, + contexts: { + cloud_resource: { + [CLOUD_PROVIDER]: 'aws', + [CLOUD_PLATFORM]: 'aws_lambda', + [CLOUD_REGION]: 'us-east-1', + }, + 'aws.lambda': { + memory_size: 2_147_483_648, + initialization_type: 'native-http', + cold_start: request === 'first', + aws_request_id: request, + endpoint_name: `${request}-endpoint`, + }, + }, + }, + }); + + if (tracing) { + spans.push({ + request, + collected: runner.collectStreamedSpansUntilSegment( + span => span.attributes[FAAS_INVOCATION_ID]?.value === request, + ), + }); + } + } + + const assertUnenriched = (items: { attributes?: Record }[]): void => { + expect(items).toHaveLength(2); + for (const item of items) { + expect(Object.keys(item.attributes || {}).filter(key => /^(aws\.lambda\.|faas\.|cloud\.)/.test(key))).toEqual( + [], + ); + } + }; + + runner.expect({ log: container => assertUnenriched(container.items) }); + runner.expect({ trace_metric: container => assertUnenriched(container.items) }); + + const running = runner.start(); + const first = running.makeRequest('get', '/first', { + headers: { 'lambda-web-request-id': 'first', 'lambda-web-endpoint-name': 'first-endpoint' }, + }); + await vi.waitFor(() => expect(running.getLogs().join('\n')).toContain('FIRST_STARTED'), { timeout: 10_000 }); + + await Promise.all([ + first, + running.makeRequest('get', '/second', { + headers: { 'lambda-web-request-id': 'second', 'lambda-web-endpoint-name': 'second-endpoint' }, + }), + ]); + await running.completed(); + + const segmentSpans = []; + + for (const { request, collected } of spans) { + const items = await collected; + segmentSpans.push(...items.filter(item => item.is_segment)); + + expect(items.find(item => item.is_segment)).toMatchObject({ + name: 'GET /:request', + attributes: { + [SENTRY_OP]: { type: 'string', value: 'http.server' }, + [CLOUD_PLATFORM]: { type: 'string', value: 'aws_lambda' }, + [CLOUD_REGION]: { type: 'string', value: 'us-east-1' }, + [FAAS_MAX_MEMORY]: { type: 'integer', value: 2_147_483_648 }, + [FAAS_COLDSTART]: { type: 'boolean', value: request === 'first' }, + [FAAS_INVOCATION_ID]: { type: 'string', value: request }, + [AWS_LAMBDA_WEB_ENDPOINT_NAME]: { type: 'string', value: `${request}-endpoint` }, + }, + }); + } + + expect(segmentSpans).toHaveLength(tracing ? 2 : 0); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 0db5cfe2ef90..a52a7477f6bc 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -95,6 +95,7 @@ export { nativeNodeFetchIntegration, NodeClient, nodeContextIntegration, + awsLambdaWebIntegration, onUncaughtExceptionIntegration, onUnhandledRejectionIntegration, mistralAIIntegration, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 9df4b06ff240..0803fca702f5 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -78,6 +78,7 @@ export { type NodeRuntimeMetricsOptions, contextLinesIntegration, nodeContextIntegration, + awsLambdaWebIntegration, localVariablesIntegration, requestDataIntegration, functionToStringIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 35eddeb055a7..1f6e6979bee0 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -78,6 +78,7 @@ export { type NodeRuntimeMetricsOptions, contextLinesIntegration, nodeContextIntegration, + awsLambdaWebIntegration, localVariablesIntegration, requestDataIntegration, fsIntegration, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 5ddd71b4b9f4..7fdeb4ab4df2 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -1,4 +1,5 @@ export { httpIntegration } from './integrations/http'; +export { awsLambdaWebIntegration } from './integrations/awsLambdaWeb'; export { nativeNodeFetchIntegration } from './integrations/node-fetch'; export { fsIntegration } from './integrations/fs'; // oxlint-disable-next-line typescript/no-deprecated diff --git a/packages/node/src/integrations/awsLambdaWeb.ts b/packages/node/src/integrations/awsLambdaWeb.ts new file mode 100644 index 000000000000..42b3c5e5d86e --- /dev/null +++ b/packages/node/src/integrations/awsLambdaWeb.ts @@ -0,0 +1,76 @@ +import { + AWS_LAMBDA_INITIALIZATION_TYPE, + AWS_LAMBDA_WEB_ENDPOINT_NAME, + FAAS_COLDSTART, + FAAS_INVOCATION_ID, + FAAS_MAX_MEMORY, +} from '@sentry/conventions/attributes'; +import type { IntegrationFn, SpanAttributes } from '@sentry/core'; +import { defineIntegration, getIsolationScope, safeSetSpanJSONAttributes } from '@sentry/core'; + +const INTEGRATION_NAME = 'AwsLambdaWeb'; + +let requestHandled = false; + +const _awsLambdaWebIntegration = (() => { + // Lambda Web Functions run ordinary HTTP servers without a Lambda handler to wrap. + // We read their request metadata through the `httpServerRequest` hook. Since this hook + // runs for all HTTP servers, we bail out early when we are not in a Lambda Web Function. + // https://docs.aws.amazon.com/lambda/latest/dg/web-functions-revisions-env-vars.html + if (!process.env.AWS_LAMBDA_HTTP_ENDPOINT) { + return { name: INTEGRATION_NAME }; + } + + const memory = Number(process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE) * 1024 * 1024; + const lambdaContext = { + memory_size: Number.isFinite(memory) && memory > 0 ? memory : undefined, + initialization_type: process.env.AWS_LAMBDA_INITIALIZATION_TYPE, + }; + const environmentAttributes = { + [FAAS_MAX_MEMORY]: lambdaContext.memory_size, + [AWS_LAMBDA_INITIALIZATION_TYPE]: lambdaContext.initialization_type, + }; + + return { + name: INTEGRATION_NAME, + setup(client) { + client.on('httpServerRequest', (_request, _response, request) => { + const coldStart = !requestHandled; + requestHandled = true; + const requestId = request.headers?.['lambda-web-request-id']; + const endpointName = request.headers?.['lambda-web-endpoint-name']; + const scope = getIsolationScope(); + scope.setContext('aws.lambda', { + ...scope.getScopeData().contexts['aws.lambda'], + cold_start: coldStart, + aws_request_id: typeof requestId === 'string' ? requestId : undefined, + endpoint_name: typeof endpointName === 'string' ? endpointName : undefined, + }); + }); + }, + processEvent(event) { + event.contexts = { + ...event.contexts, + 'aws.lambda': { ...lambdaContext, ...event.contexts?.['aws.lambda'] }, + }; + return event; + }, + processSegmentSpan(span) { + const requestContext = getIsolationScope().getScopeData().contexts['aws.lambda']; + const attributes: SpanAttributes = { ...environmentAttributes }; + if (typeof requestContext?.cold_start === 'boolean') { + attributes[FAAS_COLDSTART] = requestContext.cold_start; + } + if (typeof requestContext?.aws_request_id === 'string') { + attributes[FAAS_INVOCATION_ID] = requestContext.aws_request_id; + } + if (typeof requestContext?.endpoint_name === 'string') { + attributes[AWS_LAMBDA_WEB_ENDPOINT_NAME] = requestContext.endpoint_name; + } + safeSetSpanJSONAttributes(span, attributes); + }, + }; +}) satisfies IntegrationFn; + +/** Enrich AWS Lambda Web Function spans and error contexts with platform metadata. */ +export const awsLambdaWebIntegration = defineIntegration(_awsLambdaWebIntegration); diff --git a/packages/node/src/integrations/context.ts b/packages/node/src/integrations/context.ts index ef967476b57f..b1fa7d36598c 100644 --- a/packages/node/src/integrations/context.ts +++ b/packages/node/src/integrations/context.ts @@ -548,10 +548,11 @@ function getCloudResourceContext(): CloudResourceContext | undefined { }; } else if (process.env.AWS_REGION) { // https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html + const executionEnv = process.env.AWS_EXECUTION_ENV; return { [CLOUD_PROVIDER]: 'aws', [CLOUD_REGION]: process.env.AWS_REGION, - [CLOUD_PLATFORM]: process.env.AWS_EXECUTION_ENV, + [CLOUD_PLATFORM]: executionEnv?.startsWith('AWS_Lambda') ? 'aws_lambda' : executionEnv, }; } else if (process.env.GCP_PROJECT) { // https://cloud.google.com/composer/docs/how-to/managing/environment-variables#reserved_variables diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 57cf3871256a..ccd4601bfc8b 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -20,6 +20,7 @@ import { import { isMainThread, parentPort } from 'node:worker_threads'; import { detectOrchestrionSetup, getErrorIntegrations, getTracingIntegrations } from '@sentry/server-utils'; import { DEBUG_BUILD } from '../debug-build'; +import { awsLambdaWebIntegration } from '../integrations/awsLambdaWeb'; import { childProcessIntegration } from '../integrations/childProcess'; import { consoleIntegration } from '../integrations/console'; import { nodeContextIntegration } from '../integrations/context'; @@ -66,6 +67,7 @@ function getBaseDefaultIntegrations(): Integration[] { // Event Info contextLinesIntegration(), localVariablesIntegration(), + awsLambdaWebIntegration(), nodeContextIntegration(), childProcessIntegration(), workerThreadsIntegration(), diff --git a/packages/node/test/integrations/awsLambdaWeb.test.ts b/packages/node/test/integrations/awsLambdaWeb.test.ts new file mode 100644 index 000000000000..cd3b30c1b716 --- /dev/null +++ b/packages/node/test/integrations/awsLambdaWeb.test.ts @@ -0,0 +1,181 @@ +import { + AWS_LAMBDA_INITIALIZATION_TYPE, + AWS_LAMBDA_WEB_ENDPOINT_NAME, + CLOUD_REGION, + FAAS_COLDSTART, + FAAS_INVOCATION_ID, + FAAS_MAX_MEMORY, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import type { Integration } from '@sentry/core'; +import { getCurrentScope, getIsolationScope, SentrySpan, spanToJSON, withIsolationScope } from '@sentry/core'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { NodeClient } from '../../src/sdk/client'; + +describe('awsLambdaWebIntegration', () => { + let integration: Integration; + let client: NodeClient; + + beforeEach(async () => { + vi.resetModules(); + setAsyncLocalStorageAsyncContextStrategy(); + vi.stubEnv('AWS_LAMBDA_HTTP_ENDPOINT', '0.0.0.0:3000'); + vi.stubEnv('AWS_REGION', 'us-east-1'); + vi.stubEnv('AWS_LAMBDA_FUNCTION_MEMORY_SIZE', '2048'); + vi.stubEnv('AWS_LAMBDA_INITIALIZATION_TYPE', 'native-http'); + const { awsLambdaWebIntegration } = await import('../../src/integrations/awsLambdaWeb'); + integration = awsLambdaWebIntegration(); + client = new NodeClient({ + dsn: 'https://public@example.com/1', + integrations: [], + stackParser: () => [], + transport: () => ({ send: () => Promise.resolve({}), flush: () => Promise.resolve(true) }), + }); + getCurrentScope().setClient(client); + }); + + afterEach(async () => { + client.dispose(); + vi.restoreAllMocks(); + await client.close(); + getCurrentScope().setClient(undefined); + getIsolationScope().setContext('aws.lambda', null); + vi.unstubAllEnvs(); + }); + + it('is inert outside Web Functions', async () => { + vi.stubEnv('AWS_LAMBDA_HTTP_ENDPOINT', undefined); + const { awsLambdaWebIntegration } = await import('../../src/integrations/awsLambdaWeb'); + const disabled = awsLambdaWebIntegration(); + + expect(disabled).toEqual({ name: 'AwsLambdaWeb' }); + }); + + it('preserves request and user context while adding environment metadata', () => { + const event = { + contexts: { 'aws.lambda': { aws_request_id: 'request' }, cloud_resource: { [CLOUD_REGION]: 'custom' } }, + }; + expect(integration.processEvent!(event, {}, client)).toEqual({ + contexts: { + 'aws.lambda': { aws_request_id: 'request', memory_size: 2_147_483_648, initialization_type: 'native-http' }, + cloud_resource: { [CLOUD_REGION]: 'custom' }, + }, + }); + }); + + it('preserves existing Lambda context while replacing request-specific values', () => { + integration.setup!(client); + + withIsolationScope(scope => { + scope.setContext('aws.lambda', { + function_name: 'checkout', + revision_id: 'rev-current', + aws_request_id: 'previous-request', + endpoint_name: 'previous-endpoint', + cold_start: false, + }); + client.emit( + 'httpServerRequest', + {}, + {}, + { headers: { 'lambda-web-request-id': 'current-request', 'lambda-web-endpoint-name': 'prod' } }, + ); + + expect(scope.getScopeData().contexts['aws.lambda']).toEqual({ + function_name: 'checkout', + revision_id: 'rev-current', + aws_request_id: 'current-request', + endpoint_name: 'prod', + cold_start: true, + }); + }); + }); + + it('preserves explicit root span attributes while filling missing metadata', () => { + integration.setup!(client); + + withIsolationScope(() => { + client.emit('httpServerRequest', {}, {}, { headers: { 'lambda-web-request-id': 'request' } }); + const span = new SentrySpan({ + name: 'request', + attributes: { [CLOUD_REGION]: 'custom-region', [FAAS_COLDSTART]: false, [FAAS_MAX_MEMORY]: 0 }, + }); + const spanJSON = spanToJSON(span); + integration.processSegmentSpan!(spanJSON, client); + + expect(spanJSON.attributes).toEqual({ + [SENTRY_ORIGIN]: 'manual', + [CLOUD_REGION]: 'custom-region', + [FAAS_COLDSTART]: false, + [FAAS_INVOCATION_ID]: 'request', + [FAAS_MAX_MEMORY]: 0, + [AWS_LAMBDA_INITIALIZATION_TYPE]: 'native-http', + }); + }); + }); + + it('clears inherited request identifiers when headers are absent', () => { + integration.setup!(client); + + withIsolationScope(scope => { + scope.setContext('aws.lambda', { revision_id: 'rev-current', aws_request_id: 'old', endpoint_name: 'old' }); + client.emit('httpServerRequest', {}, {}, { headers: {} }); + const span = new SentrySpan({ name: 'request' }); + const spanJSON = spanToJSON(span); + integration.processSegmentSpan!(spanJSON, client); + + expect(scope.getScopeData().contexts['aws.lambda']).toEqual({ revision_id: 'rev-current', cold_start: true }); + expect(spanJSON.attributes).not.toHaveProperty([FAAS_INVOCATION_ID]); + expect(spanJSON.attributes).not.toHaveProperty([AWS_LAMBDA_WEB_ENDPOINT_NAME]); + }); + }); + + it('adds static metadata to segment spans outside requests without overwriting attributes', () => { + const span = spanToJSON(new SentrySpan({ name: 'startup', attributes: { [CLOUD_REGION]: 'custom-region' } })); + + integration.processSegmentSpan!(span, client); + + expect(span.attributes).toEqual({ + [SENTRY_ORIGIN]: 'manual', + [CLOUD_REGION]: 'custom-region', + [FAAS_MAX_MEMORY]: 2_147_483_648, + [AWS_LAMBDA_INITIALIZATION_TYPE]: 'native-http', + }); + }); + + it.each([undefined, '', 'invalid', '0', '-1', 'Infinity', '1e308'])( + 'omits invalid memory size %s from spans and error contexts', + async memory => { + vi.stubEnv('AWS_LAMBDA_FUNCTION_MEMORY_SIZE', memory); + const { awsLambdaWebIntegration } = await import('../../src/integrations/awsLambdaWeb'); + const integration = awsLambdaWebIntegration(); + const span = spanToJSON(new SentrySpan({ name: 'startup' })); + const event = { contexts: { 'aws.lambda': {} } }; + + integration.processSegmentSpan!(span, client); + integration.processEvent!(event, {}, client); + + expect(span.attributes).not.toHaveProperty([FAAS_MAX_MEMORY]); + expect(event.contexts['aws.lambda']).toHaveProperty('memory_size', undefined); + }, + ); + + it('omits unavailable metadata and does not infer function identity', async () => { + vi.stubEnv('AWS_REGION', undefined); + vi.stubEnv('AWS_LAMBDA_FUNCTION_MEMORY_SIZE', 'invalid'); + vi.stubEnv('AWS_LAMBDA_INITIALIZATION_TYPE', undefined); + const { awsLambdaWebIntegration } = await import('../../src/integrations/awsLambdaWeb'); + const minimal = awsLambdaWebIntegration(); + minimal.setup!(client); + withIsolationScope(scope => { + client.emit('httpServerRequest', {}, {}, { headers: {} }); + expect(scope.getScopeData().contexts['aws.lambda']).toEqual({ cold_start: true }); + }); + expect(minimal.processEvent!({}, {}, client)).toEqual({ + contexts: { + 'aws.lambda': {}, + }, + }); + }); +}); From 1ed55d2a7c2f1359d76befd71e4019318a93b6ec Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 30 Oct 2026 15:30:41 +0100 Subject: [PATCH 063/169] feat(nextjs): Mark pageloads of cached pages (#25318) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Next.js serves a page from its cache (an ISR page, or a `cacheComponents` shell that was prerendered at runtime), the pageload starts its own trace and has no server span. That is correct, but in Sentry it looks like missing server instrumentation. While Next.js prerenders a document, the server now writes a `sentry-cache-origin` meta tag with the `sentry-trace` value of the prerender (instead of nothing). The client never continues it: it sets `page.server_rendering.type` on the pageload (`static` when the whole page came from the cache, `partially_static` when parts of it rendered for this request) and links the prerender with a `cache_origin` link, both at span start, so `tracesSampler` and `ignoreSpans` see them. Like a `use cache` hit, the pageload links only a sampled prerender. So the fixes of #25141 still hold: a cached page does not attach its visitors to a request that was not theirs, and a `sampled=0` value in the tag cannot drop their pageloads. Pages from `next build` get no tag, because the SDK does not run during the build. The attribute and the tag are named without Next.js in them on purpose: other frameworks with prerendering (Nuxt, SvelteKit, Astro, React Router) can set the same attribute. `page.server_rendering.type` is being added to `@sentry/conventions` in getsentry/sentry-conventions#740; `cache_origin` is already a documented `sentry.link.type` value and is what the cache lifecycle view in Sentry reads. Refs JS-3886 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 Co-authored-by: s1gr1d <32902192+s1gr1d@users.noreply.github.com> --- CHANGELOG.md | 4 + .../app/runtime-shell/page.tsx | 27 ++++ .../app/slow-hole/page.tsx | 24 ++++ .../nextjs-16-cacheComponents/package.json | 1 + .../cacheOrigin-pageloadConnection.spec.ts | 118 ++++++++++++++++-- .../app/runtime-shell/page.tsx | 27 ++++ .../package.json | 1 + .../cacheOrigin-pageloadConnection.spec.ts | 91 ++++++++++++-- .../nextjs-16/tests/isr-routes.test.ts | 3 +- .../src/client/browserTracingIntegration.ts | 2 +- .../appRouterPageloadInstrumentation.ts | 112 +++++++++++++++++ .../appRouterRoutingInstrumentation.ts | 95 +------------- .../routing/nextRoutingInstrumentation.ts | 3 +- .../src/client/routing/traceMetaTags.ts | 73 +++++++++-- packages/nextjs/src/common/traceMetaTags.ts | 6 + .../getFinalConfigObjectUtils.ts | 4 +- .../nextjs/src/server/nextSentryPropagator.ts | 17 ++- .../appRouterRoutingInstrumentation.test.ts | 116 +++++++++++++++-- .../nextjs/test/client/traceMetaTags.test.ts | 66 +++++++++- .../nextjs/test/cloudflare/withSentry.test.ts | 2 +- .../maybeSetClientTraceMetadataOption.test.ts | 13 +- .../nextRoutingInstrumentation.test.ts | 11 +- .../test/server/nextSentryPropagator.test.ts | 20 +-- 23 files changed, 676 insertions(+), 160 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/runtime-shell/page.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-hole/page.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/runtime-shell/page.tsx create mode 100644 packages/nextjs/src/client/routing/appRouterPageloadInstrumentation.ts create mode 100644 packages/nextjs/src/common/traceMetaTags.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index eac3bceb49b1..fff7847dd506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ `@SentryCron` now sends the schedule and time zone from the method's `@Cron()` decorator, so Sentry creates the monitor on the first run. If you use `@SentryCron` without a `schedule`, Sentry now creates missing monitors, which are billed, and overwrites schedules and time zones set by hand. Pass `fromCronDecorator: false` to turn this off. +- **feat(nextjs): Mark pageloads of cached pages ([#25318](https://github.com/getsentry/sentry-javascript/pull/25318))** + + When Next.js serves a page from its cache, for example an ISR page or a `cacheComponents` shell, the pageload now has the `page.server_rendering.type` attribute. It is `static` if the whole page came from the cache, and `partially_static` if parts of it rendered for this request. Then the pageload stays connected to this request, as before. If the request that rendered the cached page was sampled, the pageload also links to it. So in Sentry, a cached page no longer looks like missing server instrumentation. + Work in this release was contributed by @lazerg and @chiliec. Thank you for your contributions! ## 11.10.0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/runtime-shell/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/runtime-shell/page.tsx new file mode 100644 index 000000000000..1cf4a1dd4663 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/runtime-shell/page.tsx @@ -0,0 +1,27 @@ +import { cacheLife } from 'next/cache'; +import { headers } from 'next/headers'; +import { Suspense } from 'react'; + +// The shell goes stale after a second, so a later visit makes the server prerender it again at +// runtime. An `expire` below 5 minutes would make Next.js leave this out of the shell. +async function RenderedAt() { + 'use cache'; + cacheLife({ revalidate: 1, expire: 3600 }); + return

{Date.now()}

; +} + +async function RequestContent() { + await headers(); + return

Rendered for this request

; +} + +export default function Page() { + return ( + <> + + Loading...

}> + +
+ + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-hole/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-hole/page.tsx new file mode 100644 index 000000000000..201e4102cb0d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/slow-hole/page.tsx @@ -0,0 +1,24 @@ +import { headers } from 'next/headers'; +import Link from 'next/link'; +import { Suspense } from 'react'; + +// The dynamic part, and with it the trace meta tag, streams in seconds after the shell, so a test +// can navigate while the pageload still waits for the tag. +async function SlowContent() { + await headers(); + await new Promise(resolve => setTimeout(resolve, 4000)); + return

Slow content

; +} + +export default function Page() { + return ( + <> + + Static shell + + Loading...

}> + +
+ + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json index eba44d444994..2534a55b6b69 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", + "@sentry/conventions": "0.27.0", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts index 900ce43679e9..f06c4da961cd 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts @@ -1,5 +1,6 @@ import type { Page } from '@playwright/test'; import { expect, test } from '@playwright/test'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { waitForTransaction } from '@sentry-internal/test-utils'; const APP = 'nextjs-16-cacheComponents'; @@ -7,7 +8,8 @@ const APP = 'nextjs-16-cacheComponents'; // The server only writes `sentry-trace` meta tags for a render that happened for the current request, // never while it prerenders a shell. So a tag in the document can always be continued; the only // question is when it arrives. The resumed part of a prerendered shell arrives after the SDK started, -// which the pageload instrumentation waits for. +// which the pageload instrumentation waits for. A shell prerendered at runtime carries a +// `sentry-cache-origin` tag instead, which marks the pageload as static and links the prerender. test('connects the pageload trace to the runtime server request', async ({ page }) => { // The shell is prerendered, but the page's dynamic part (`DynamicContent` awaits `headers()`) @@ -30,7 +32,6 @@ test('connects the pageload trace to the runtime server request', async ({ page expect(serverTx.contexts?.trace?.trace_id).toBeTruthy(); expect(pageloadTx.contexts?.trace?.trace_id).toBe(serverTx.contexts?.trace?.trace_id); - expect(pageloadTx.contexts?.trace?.links).toBeUndefined(); }); test('connects the pageload trace to a blocking render', async ({ page }) => { @@ -58,10 +59,11 @@ test('does not share a trace between visitors of a shell generated at request ti test.skip(process.env.TEST_ENV !== 'production', 'Shells are only cached in production builds'); // `[id]` only lists `test` in `generateStaticParams`, so the first visit prerenders and caches the - // shell for this id. The prerender writes no trace tag, so every visitor starts a fresh trace. + // shell for this id. The prerender writes no trace tag, only a `sentry-cache-origin` tag with its own + // trace, so every visitor starts a fresh trace that links the prerender and is marked as static. const id = `shell-${Date.now()}`; - const visit = async (): Promise<{ traceId: string | undefined; links: unknown }> => { + const visit = async (): Promise<{ traceId: string | undefined; links: unknown; serverRenderingType: unknown }> => { const context = await browser.newContext(); const page: Page = await context.newPage(); @@ -75,11 +77,16 @@ test('does not share a trace between visitors of a shell generated at request ti await page.goto(`/${id}`); await expect(page.locator('#result')).toHaveText(`Hello, ${id}!`); expect(await page.locator('meta[name="sentry-trace"]').count()).toBe(0); + expect(await page.locator('meta[name="sentry-cache-origin"]').count()).toBe(1); const pageloadTx = await pageloadTxPromise; await context.close(); - return { traceId: pageloadTx.contexts?.trace?.trace_id, links: pageloadTx.contexts?.trace?.links }; + return { + traceId: pageloadTx.contexts?.trace?.trace_id, + links: pageloadTx.contexts?.trace?.links, + serverRenderingType: pageloadTx.contexts?.trace?.data?.['page.server_rendering.type'], + }; }; const first = await visit(); @@ -88,8 +95,73 @@ test('does not share a trace between visitors of a shell generated at request ti expect(first.traceId).toBeTruthy(); expect(second.traceId).toBeTruthy(); expect(second.traceId).not.toBe(first.traceId); - expect(first.links).toBeUndefined(); - expect(second.links).toBeUndefined(); + expect(first.serverRenderingType).toBe('static'); + expect(second.serverRenderingType).toBe('static'); + + // Both link the request that prerendered the shell, which is the first visitor's own request. The + // client cannot tell the first visit from later ones, so that pageload starts a fresh trace too. + const linkOf = (links: unknown): { trace_id: string; attributes?: Record } => { + const link = (links as Array<{ trace_id: string; attributes?: Record }>)[0]; + expect(link?.attributes?.[SENTRY_LINK_TYPE]).toBe('cache_origin'); + return link!; + }; + expect(linkOf(first.links).trace_id).toBe(linkOf(second.links).trace_id); + expect(linkOf(second.links).trace_id).not.toBe(second.traceId); +}); + +test('continues the request trace on a shell prerendered at runtime and links the prerender', async ({ + browser, + page, + baseURL, +}) => { + test.skip(process.env.TEST_ENV !== 'production', 'Shells are only cached in production builds'); + + // The shell from the build carries no `sentry-cache-origin` tag. Wait until a visit serves the + // shell that the server prerendered again at runtime. + const warmup = await browser.newPage(); + await warmup.goto('/runtime-shell'); + const renderedAtBuild = await warmup.locator('#rendered-at').textContent(); + await expect + .poll( + async () => { + await warmup.waitForTimeout(1500); + await warmup.goto('/runtime-shell'); + return warmup.locator('#rendered-at').textContent(); + }, + { timeout: 30_000 }, + ) + .not.toBe(renderedAtBuild); + await warmup.close(); + + const pageloadTxPromise = waitForTransaction(APP, transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'pageload' && + transactionEvent.contexts?.trace?.data?.['url.full'] === `${baseURL}/runtime-shell?visit=last` + ); + }); + + await page.goto('/runtime-shell?visit=last'); + await expect(page.locator('#request-content')).toHaveText('Rendered for this request'); + const [requestTraceId] = (await page.locator('meta[name="sentry-trace"]').getAttribute('content'))!.split('-'); + const [prerenderTraceId, prerenderSpanId] = (await page + .locator('meta[name="sentry-cache-origin"]') + .getAttribute('content'))!.split('-'); + + const pageloadTx = await pageloadTxPromise; + + // Only the shell came from the cache. The rest of the page rendered for this request, and the + // pageload continues that trace. + expect(pageloadTx.contexts?.trace?.trace_id).toBe(requestTraceId); + expect(pageloadTx.contexts?.trace?.data?.['page.server_rendering.type']).toBe('partially_static'); + expect(pageloadTx.contexts?.trace?.links).toEqual([ + { + trace_id: prerenderTraceId, + span_id: prerenderSpanId, + sampled: true, + attributes: { [SENTRY_LINK_TYPE]: 'cache_origin' }, + }, + ]); + expect(prerenderTraceId).not.toBe(requestTraceId); }); test('starts a fresh trace on a fully static page', async ({ page }) => { @@ -105,8 +177,38 @@ test('starts a fresh trace on a fully static page', async ({ page }) => { const pageloadTx = await pageloadTxPromise; - // A build-time shell carries no trace context at all. + // A build-time shell carries no trace context and no marker, the SDK does not run during the build. expect(await page.locator('meta[name="sentry-trace"]').count()).toBe(0); + expect(await page.locator('meta[name="sentry-cache-origin"]').count()).toBe(0); expect(pageloadTx.contexts?.trace?.trace_id).toBeTruthy(); expect(pageloadTx.contexts?.trace?.links).toBeUndefined(); + expect(pageloadTx.contexts?.trace?.data?.['page.server_rendering.type']).toBeUndefined(); +}); + +test('keeps a navigation that starts while the pageload waits for the trace meta tag', async ({ page }) => { + test.skip(process.env.TEST_ENV !== 'production', 'Only a prerendered shell makes the trace meta tag arrive late'); + + const pageloadTxPromise = waitForTransaction(APP, transactionEvent => { + return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.transaction === '/slow-hole'; + }); + + const navigationTxPromise = waitForTransaction(APP, transactionEvent => { + return transactionEvent.contexts?.trace?.op === 'navigation' && transactionEvent.transaction === '/static-shell'; + }); + + await page.goto('/slow-hole', { waitUntil: 'commit' }); + await page.waitForFunction(() => !!(window as { next?: { router?: unknown } }).next?.router); + + // The resumed part, which carries the tag, has not arrived yet, so the pageload is still waiting. + await expect(page.locator('#slow-fallback')).toBeVisible(); + expect(await page.locator('meta[name="sentry-trace"]').count()).toBe(0); + + await page.locator('#navigate').click(); + await expect(page.locator('#static-shell')).toHaveText('Static shell'); + + const [pageloadTx, navigationTx] = await Promise.all([pageloadTxPromise, navigationTxPromise]); + + // The navigation ends the pageload like any pageload it interrupts, and runs to its own idle timeout. + expect(pageloadTx.contexts?.trace?.data?.['sentry.idle_span_finish_reason']).toBe('cancelled'); + expect(navigationTx.contexts?.trace?.data?.['sentry.idle_span_finish_reason']).toBe('idleTimeout'); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/runtime-shell/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/runtime-shell/page.tsx new file mode 100644 index 000000000000..1cf4a1dd4663 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/runtime-shell/page.tsx @@ -0,0 +1,27 @@ +import { cacheLife } from 'next/cache'; +import { headers } from 'next/headers'; +import { Suspense } from 'react'; + +// The shell goes stale after a second, so a later visit makes the server prerender it again at +// runtime. An `expire` below 5 minutes would make Next.js leave this out of the shell. +async function RenderedAt() { + 'use cache'; + cacheLife({ revalidate: 1, expire: 3600 }); + return

{Date.now()}

; +} + +async function RequestContent() { + await headers(); + return

Rendered for this request

; +} + +export default function Page() { + return ( + <> + + Loading...

}> + +
+ + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/package.json index 24c37deeba72..c78827f5595f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@playwright/test": "1.64.1", "@sentry-internal/test-utils": "file:../../packed/sentry-internal-test-utils-packed.tgz", + "@sentry/conventions": "0.27.0", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts index 89f9dc32d26c..3d1512789f82 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/tests/cacheOrigin-pageloadConnection.spec.ts @@ -1,5 +1,6 @@ import type { Page } from '@playwright/test'; import { expect, test } from '@playwright/test'; +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; const APP = 'nextjs-16-streaming-cacheComponents'; @@ -7,7 +8,8 @@ const APP = 'nextjs-16-streaming-cacheComponents'; // The server only writes `sentry-trace` meta tags for a render that happened for the current request, // never while it prerenders a shell. So a tag in the document can always be continued; the only // question is when it arrives. The resumed part of a prerendered shell arrives after the SDK started, -// which the pageload instrumentation waits for. +// which the pageload instrumentation waits for. A shell prerendered at runtime carries a +// `sentry-cache-origin` tag instead, which marks the pageload as static and links the prerender. test('connects the pageload trace to the runtime server request', async ({ page }) => { // The shell is prerendered, but the page's dynamic part (`DynamicContent` awaits `headers()`) @@ -28,7 +30,6 @@ test('connects the pageload trace to the runtime server request', async ({ page expect(serverSpan.trace_id).toBeTruthy(); expect(pageloadSpan.trace_id).toBe(serverSpan.trace_id); - expect(pageloadSpan.links).toBeUndefined(); }); test('connects the pageload trace to a blocking render', async ({ page }) => { @@ -56,10 +57,11 @@ test('does not share a trace between visitors of a shell generated at request ti test.skip(process.env.TEST_ENV !== 'production', 'Shells are only cached in production builds'); // `[id]` only lists `test` in `generateStaticParams`, so the first visit prerenders and caches the - // shell for this id. The prerender writes no trace tag, so every visitor starts a fresh trace. + // shell for this id. The prerender writes no trace tag, only a `sentry-cache-origin` tag with its own + // trace, so every visitor starts a fresh trace that links the prerender and is marked as static. const id = `shell-${Date.now()}`; - const visit = async (): Promise<{ traceId: string | undefined; links: unknown }> => { + const visit = async (): Promise<{ traceId: string | undefined; links: unknown; serverRenderingType: unknown }> => { const context = await browser.newContext(); const page: Page = await context.newPage(); @@ -70,11 +72,16 @@ test('does not share a trace between visitors of a shell generated at request ti await page.goto(`/${id}`); await expect(page.locator('#result')).toHaveText(`Hello, ${id}!`); expect(await page.locator('meta[name="sentry-trace"]').count()).toBe(0); + expect(await page.locator('meta[name="sentry-cache-origin"]').count()).toBe(1); const pageloadSpan = await pageloadSpanPromise; await context.close(); - return { traceId: pageloadSpan.trace_id, links: pageloadSpan.links }; + return { + traceId: pageloadSpan.trace_id, + links: pageloadSpan.links, + serverRenderingType: pageloadSpan.attributes['page.server_rendering.type']?.value, + }; }; const first = await visit(); @@ -83,8 +90,74 @@ test('does not share a trace between visitors of a shell generated at request ti expect(first.traceId).toBeTruthy(); expect(second.traceId).toBeTruthy(); expect(second.traceId).not.toBe(first.traceId); - expect(first.links).toBeUndefined(); - expect(second.links).toBeUndefined(); + expect(first.serverRenderingType).toBe('static'); + expect(second.serverRenderingType).toBe('static'); + + // Both link the request that prerendered the shell, which is the first visitor's own request. The + // client cannot tell the first visit from later ones, so that pageload starts a fresh trace too. + const linkOf = (links: unknown): { trace_id: string; attributes?: Record } => { + const link = (links as Array<{ trace_id: string; attributes?: Record }>)[0]; + expect(link?.attributes?.[SENTRY_LINK_TYPE]?.value).toBe('cache_origin'); + return link!; + }; + expect(linkOf(first.links).trace_id).toBe(linkOf(second.links).trace_id); + expect(linkOf(second.links).trace_id).not.toBe(second.traceId); +}); + +test('continues the request trace on a shell prerendered at runtime and links the prerender', async ({ + browser, + page, + baseURL, +}) => { + test.skip(process.env.TEST_ENV !== 'production', 'Shells are only cached in production builds'); + + // The shell from the build carries no `sentry-cache-origin` tag. Wait until a visit serves the + // shell that the server prerendered again at runtime. + const warmup = await browser.newPage(); + await warmup.goto('/runtime-shell'); + const renderedAtBuild = await warmup.locator('#rendered-at').textContent(); + await expect + .poll( + async () => { + await warmup.waitForTimeout(1500); + await warmup.goto('/runtime-shell'); + return warmup.locator('#rendered-at').textContent(); + }, + { timeout: 30_000 }, + ) + .not.toBe(renderedAtBuild); + await warmup.close(); + + const pageloadSpanPromise = waitForStreamedSpan(APP, span => { + return ( + getSpanOp(span) === 'pageload' && + span.is_segment && + span.attributes['url.full']?.value === `${baseURL}/runtime-shell?visit=last` + ); + }); + + await page.goto('/runtime-shell?visit=last'); + await expect(page.locator('#request-content')).toHaveText('Rendered for this request'); + const [requestTraceId] = (await page.locator('meta[name="sentry-trace"]').getAttribute('content'))!.split('-'); + const [prerenderTraceId, prerenderSpanId] = (await page + .locator('meta[name="sentry-cache-origin"]') + .getAttribute('content'))!.split('-'); + + const pageloadSpan = await pageloadSpanPromise; + + // Only the shell came from the cache. The rest of the page rendered for this request, and the + // pageload continues that trace. + expect(pageloadSpan.trace_id).toBe(requestTraceId); + expect(pageloadSpan.attributes['page.server_rendering.type']?.value).toBe('partially_static'); + expect(pageloadSpan.links).toEqual([ + { + trace_id: prerenderTraceId, + span_id: prerenderSpanId, + sampled: true, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'cache_origin' } }, + }, + ]); + expect(prerenderTraceId).not.toBe(requestTraceId); }); test('starts a fresh trace on a fully static page', async ({ page }) => { @@ -100,10 +173,12 @@ test('starts a fresh trace on a fully static page', async ({ page }) => { const pageloadSpan = await pageloadSpanPromise; - // A build-time shell carries no trace context at all. + // A build-time shell carries no trace context and no marker, the SDK does not run during the build. expect(await page.locator('meta[name="sentry-trace"]').count()).toBe(0); + expect(await page.locator('meta[name="sentry-cache-origin"]').count()).toBe(0); expect(pageloadSpan.trace_id).toBeTruthy(); expect(pageloadSpan.links).toBeUndefined(); + expect(pageloadSpan.attributes['page.server_rendering.type']).toBeUndefined(); }); test('keeps a navigation that starts while the pageload waits for the trace meta tag', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/isr-routes.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/isr-routes.test.ts index 5a210e8426c6..98de20bf60f3 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/isr-routes.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/isr-routes.test.ts @@ -51,7 +51,7 @@ test.describe('prerendered ISR pages', () => { await expect(page.locator('meta[name="baggage"]')).toHaveCount(0); }); - test('does not carry trace meta tags on an ISR page that was prerendered again at runtime', async ({ page }) => { + test('carries only the cache origin on an ISR page that was prerendered again at runtime', async ({ page }) => { // The first visit serves the shell from the build. Once its revalidation window passed, a visit serves the stale // shell and makes the server prerender the page again, inside that request, with the SDK running. The visits after // that serve the shell from that prerender. @@ -71,6 +71,7 @@ test.describe('prerendered ISR pages', () => { await expect(page.locator('meta[name="sentry-trace"]')).toHaveCount(0); await expect(page.locator('meta[name="baggage"]')).toHaveCount(0); + await expect(page.locator('meta[name="sentry-cache-origin"]')).toHaveCount(1); }); }); diff --git a/packages/nextjs/src/client/browserTracingIntegration.ts b/packages/nextjs/src/client/browserTracingIntegration.ts index acd961a77c8e..0ca0cbec4a15 100644 --- a/packages/nextjs/src/client/browserTracingIntegration.ts +++ b/packages/nextjs/src/client/browserTracingIntegration.ts @@ -1,6 +1,6 @@ import type { Integration } from '@sentry/core'; import { browserTracingIntegration as originalBrowserTracingIntegration, isBotUserAgent } from '@sentry/react'; -import { settlePendingPageloadWait } from './routing/appRouterRoutingInstrumentation'; +import { settlePendingPageloadWait } from './routing/appRouterPageloadInstrumentation'; import { nextRouterInstrumentNavigation, nextRouterInstrumentPageLoad } from './routing/nextRoutingInstrumentation'; /** diff --git a/packages/nextjs/src/client/routing/appRouterPageloadInstrumentation.ts b/packages/nextjs/src/client/routing/appRouterPageloadInstrumentation.ts new file mode 100644 index 000000000000..20e7a0e727e0 --- /dev/null +++ b/packages/nextjs/src/client/routing/appRouterPageloadInstrumentation.ts @@ -0,0 +1,112 @@ +import { + SENTRY_OP, + SENTRY_ORIGIN, + SENTRY_SEGMENT_NAME_SOURCE, + URL_FULL, + URL_PATH, + URL_TEMPLATE, +} from '@sentry/conventions/attributes'; +import { PAGELOAD } from '@sentry/conventions/op'; +import type { Client, Span, StartSpanOptions } from '@sentry/core'; +import { + filterCollectedUrl, + getCurrentScope, + hasSpanStreamingEnabled, + PAGELOAD_SPAN_NAME_FALLBACK, +} from '@sentry/core'; +import { resolveCurrentRoute, startBrowserTracingPageLoadSpan, WINDOW } from '@sentry/react'; +import { stripTrailingSlash } from './parameterization'; +import type { TraceMetaTagValues, TraceMetaTagWait } from './traceMetaTags'; +import { + addTraceMetaTagLink, + canWaitForTraceMetaTag, + readTraceMetaTags, + waitForTraceMetaTag, + withCacheOrigin, +} from './traceMetaTags'; + +/** + * How long the pageload start may wait for the resumed part of a Cache Components document. The wait + * normally ends with `DOMContentLoaded`; the cap only matters for a response the server keeps open. + */ +const PAGELOAD_TRACE_META_TAG_TIMEOUT_MS = 10_000; + +let pendingPageloadWait: TraceMetaTagWait | undefined; + +/** + * Starts a pageload that is still waiting for its trace meta tag with a fresh trace. Called when a + * navigation starts, before the navigation span is created, so the navigation ends the pageload like + * any pageload it interrupts, and the pageload is still recorded. + */ +export function settlePendingPageloadWait(): void { + pendingPageloadWait?.giveUp(); +} + +/** + * Starts the pageload span with the request data of the document. The span may start after a + * navigation already changed the location, so the browser SDK would otherwise read the new one. + */ +function startPageloadSpan( + client: Client, + spanOptions: StartSpanOptions, + documentUrl: string, + traceMetaTags?: TraceMetaTagValues, +): Span | undefined { + const span = startBrowserTracingPageLoadSpan(client, withCacheOrigin(spanOptions, traceMetaTags), traceMetaTags); + const scope = getCurrentScope(); + const { normalizedRequest } = scope.getScopeData().sdkProcessingMetadata; + if (normalizedRequest) { + scope.setSDKProcessingMetadata({ normalizedRequest: { ...normalizedRequest, url: documentUrl } }); + } + return span; +} + +/** Instruments the Next.js app router for pageloads. */ +export function appRouterInstrumentPageLoad(client: Client): void { + const documentUrl = filterCollectedUrl(WINDOW.location.href, client); + const pathname = stripTrailingSlash(WINDOW.location.pathname); + const parameterizedPathname = resolveCurrentRoute(client); + const spanOptions: StartSpanOptions = { + // With span streaming, span names have to be low cardinality, so we can't fall back to the URL. + name: parameterizedPathname ?? (hasSpanStreamingEnabled(client) ? PAGELOAD_SPAN_NAME_FALLBACK : pathname), + // pageload should always start at timeOrigin (and needs to be in s, not ms) + attributes: { + [SENTRY_OP]: PAGELOAD, + [SENTRY_ORIGIN]: 'auto.pageload.nextjs.app_router_instrumentation', + [SENTRY_SEGMENT_NAME_SOURCE]: parameterizedPathname ? 'route' : 'url', + [URL_PATH]: WINDOW.location.pathname, + [URL_FULL]: documentUrl, + [URL_TEMPLATE]: parameterizedPathname, + }, + }; + + // A tag that is already in the document was rendered for this request and can be continued. A + // Cache Components document that resumes a prerendered shell gets its tag with the resumed part, + // which usually arrives after the SDK started. Waiting for it lets the pageload continue the server + // trace instead of linking it; the span's start is backdated to the time origin either way. + const traceMetaTags = readTraceMetaTags(); + if (traceMetaTags || !canWaitForTraceMetaTag()) { + startPageloadSpan(client, spanOptions, documentUrl, traceMetaTags); + return; + } + + pendingPageloadWait = waitForTraceMetaTag( + values => { + pendingPageloadWait = undefined; + startPageloadSpan(client, spanOptions, documentUrl, values); + }, + () => { + pendingPageloadWait = undefined; + const span = startPageloadSpan(client, spanOptions, documentUrl); + // Only reachable through the cap or a navigation while the document still streams: a tag that + // shows up after this can no longer be continued, but the server request can still be linked. + if (span) { + waitForTraceMetaTag( + values => span.isRecording() && addTraceMetaTagLink(span, values), + () => undefined, + ); + } + }, + PAGELOAD_TRACE_META_TAG_TIMEOUT_MS, + ); +} diff --git a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts index d48f48cde5ea..f2f933bc11bf 100644 --- a/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/appRouterRoutingInstrumentation.ts @@ -1,25 +1,20 @@ -import type { Client, Span, StartSpanOptions } from '@sentry/core'; +import type { Client, Span } from '@sentry/core'; import { - getCurrentScope, GLOBAL_OBJ, hasSpanStreamingEnabled, isObjectLike, NAVIGATION_SPAN_NAME_FALLBACK, - PAGELOAD_SPAN_NAME_FALLBACK, filterCollectedUrl, timestampInSeconds, } from '@sentry/core'; import { startBrowserTracingNavigationSpan, - startBrowserTracingPageLoadSpan, WINDOW, getAbsoluteUrl, resolveCurrentRoute, resolveRoute, } from '@sentry/react'; import { stripTrailingSlash } from './parameterization'; -import type { TraceMetaTagValues, TraceMetaTagWait } from './traceMetaTags'; -import { addTraceMetaTagLink, canWaitForTraceMetaTag, readTraceMetaTags, waitForTraceMetaTag } from './traceMetaTags'; import { SENTRY_OP, SENTRY_SEGMENT_NAME_SOURCE, @@ -29,7 +24,7 @@ import { SENTRY_ORIGIN, NAVIGATION_TYPE, } from '@sentry/conventions/attributes'; -import { NAVIGATION, PAGELOAD } from '@sentry/conventions/op'; +import { NAVIGATION } from '@sentry/conventions/op'; function setNavigationSpanUrlAttributes(span: Span, urlPath: string, urlOrPath: string): void { span.setAttributes({ @@ -99,92 +94,6 @@ let navigationRoutingMode: 'router-patch' | 'transition-start-hook' = 'router-pa const currentRouterPatchingNavigationSpanRef: NavigationSpanRef = { current: undefined }; -/** - * How long the pageload start may wait for the resumed part of a Cache Components document. The wait - * normally ends with `DOMContentLoaded`; the cap only matters for a response the server keeps open. - */ -const PAGELOAD_TRACE_META_TAG_TIMEOUT_MS = 10_000; - -let pendingPageloadWait: TraceMetaTagWait | undefined; - -/** - * Starts a pageload that is still waiting for its trace meta tag with a fresh trace. Called when a - * navigation starts, before the navigation span is created, so the navigation ends the pageload like - * any pageload it interrupts, and the pageload is still recorded. - */ -export function settlePendingPageloadWait(): void { - pendingPageloadWait?.giveUp(); -} - -/** - * Starts the pageload span with the request data of the document. The span may start after a - * navigation already changed the location, so the browser SDK would otherwise read the new one. - */ -function startPageloadSpan( - client: Client, - spanOptions: StartSpanOptions, - documentUrl: string, - traceMetaTags?: TraceMetaTagValues, -): Span | undefined { - const span = startBrowserTracingPageLoadSpan(client, spanOptions, traceMetaTags); - const scope = getCurrentScope(); - const { normalizedRequest } = scope.getScopeData().sdkProcessingMetadata; - if (normalizedRequest) { - scope.setSDKProcessingMetadata({ normalizedRequest: { ...normalizedRequest, url: documentUrl } }); - } - return span; -} - -/** Instruments the Next.js app router for pageloads. */ -export function appRouterInstrumentPageLoad(client: Client): void { - const documentUrl = filterCollectedUrl(WINDOW.location.href, client); - const pathname = stripTrailingSlash(WINDOW.location.pathname); - const parameterizedPathname = resolveCurrentRoute(client); - const spanOptions: StartSpanOptions = { - // With span streaming, span names have to be low cardinality, so we can't fall back to the URL. - name: parameterizedPathname ?? (hasSpanStreamingEnabled(client) ? PAGELOAD_SPAN_NAME_FALLBACK : pathname), - // pageload should always start at timeOrigin (and needs to be in s, not ms) - attributes: { - [SENTRY_OP]: PAGELOAD, - [SENTRY_ORIGIN]: 'auto.pageload.nextjs.app_router_instrumentation', - [SENTRY_SEGMENT_NAME_SOURCE]: parameterizedPathname ? 'route' : 'url', - [URL_PATH]: WINDOW.location.pathname, - [URL_FULL]: documentUrl, - [URL_TEMPLATE]: parameterizedPathname, - }, - }; - - // A tag that is already in the document was rendered for this request and can be continued. A - // Cache Components document that resumes a prerendered shell gets its tag with the resumed part, - // which usually arrives after the SDK started. Waiting for it lets the pageload continue the server - // trace instead of linking it; the span's start is backdated to the time origin either way. - const traceMetaTags = readTraceMetaTags(); - if (traceMetaTags || !canWaitForTraceMetaTag()) { - startPageloadSpan(client, spanOptions, documentUrl, traceMetaTags); - return; - } - - pendingPageloadWait = waitForTraceMetaTag( - values => { - pendingPageloadWait = undefined; - startPageloadSpan(client, spanOptions, documentUrl, values); - }, - () => { - pendingPageloadWait = undefined; - const span = startPageloadSpan(client, spanOptions, documentUrl); - // Only reachable through the cap or a navigation while the document still streams: a tag that - // shows up after this can no longer be continued, but the server request can still be linked. - if (span) { - waitForTraceMetaTag( - values => span.isRecording() && addTraceMetaTagLink(span, values), - () => undefined, - ); - } - }, - PAGELOAD_TRACE_META_TAG_TIMEOUT_MS, - ); -} - interface NavigationSpanRef { current: Span | undefined; } diff --git a/packages/nextjs/src/client/routing/nextRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/nextRoutingInstrumentation.ts index f9a31f3f0e79..e453ac6e6871 100644 --- a/packages/nextjs/src/client/routing/nextRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/nextRoutingInstrumentation.ts @@ -1,6 +1,7 @@ import type { Client } from '@sentry/core'; import { WINDOW } from '@sentry/react'; -import { appRouterInstrumentNavigation, appRouterInstrumentPageLoad } from './appRouterRoutingInstrumentation'; +import { appRouterInstrumentNavigation } from './appRouterRoutingInstrumentation'; +import { appRouterInstrumentPageLoad } from './appRouterPageloadInstrumentation'; import { pagesRouterInstrumentNavigation } from './pagesRouterNavigationInstrumentation'; import { pagesRouterInstrumentPageLoad } from './pagesRouterRoutingInstrumentation'; diff --git a/packages/nextjs/src/client/routing/traceMetaTags.ts b/packages/nextjs/src/client/routing/traceMetaTags.ts index 8e1138000ec5..0d85189e8b4b 100644 --- a/packages/nextjs/src/client/routing/traceMetaTags.ts +++ b/packages/nextjs/src/client/routing/traceMetaTags.ts @@ -1,7 +1,7 @@ import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; -import type { Span } from '@sentry/core'; -import { extractTraceparentData } from '@sentry/core'; -import { GLOBAL_OBJ } from '@sentry/core'; +import type { Span, SpanLink, StartSpanOptions } from '@sentry/core'; +import { extractTraceparentData, GLOBAL_OBJ } from '@sentry/core'; +import { CACHE_ORIGIN_META_TAG_NAME } from '../../common/traceMetaTags'; import { WINDOW } from '@sentry/react'; // `@opentelemetry/api` `TraceFlags`, inlined because core does not export its copies. @@ -9,12 +9,16 @@ const TRACE_FLAG_SAMPLED = 0x1; const TRACE_FLAG_NONE = 0x0; const SERVER_REQUEST_LINK_TYPE = 'server_request'; +const CACHE_ORIGIN_LINK_TYPE = 'cache_origin'; export interface TraceMetaTagValues { sentryTrace: string; baggage: string | undefined; } +// Not in `@sentry/conventions` yet: https://github.com/getsentry/sentry-conventions/pull/740 +const PAGE_SERVER_RENDERING_TYPE = 'page.server_rendering.type'; + function getDocument(): Document | undefined { return WINDOW.document; } @@ -131,22 +135,65 @@ export function waitForTraceMetaTag( return { giveUp: () => settle(onGiveUp) }; } -/** - * Adds a span link to the span described by a `sentry-trace` value. Used when the tag arrives after - * the pageload span exists, which can only link the server request, not continue it. - */ -export function addTraceMetaTagLink(span: Span, { sentryTrace }: TraceMetaTagValues): void { +/** The span link to the span a `sentry-trace` value describes, if the value is one. */ +function traceMetaTagLink(sentryTrace: string, linkType: string): SpanLink | undefined { const traceparent = extractTraceparentData(sentryTrace); if (!traceparent?.traceId || !traceparent.parentSpanId) { - return; + return undefined; } - - span.addLink({ + return { context: { traceId: traceparent.traceId, spanId: traceparent.parentSpanId, traceFlags: traceparent.parentSampled ? TRACE_FLAG_SAMPLED : TRACE_FLAG_NONE, }, - attributes: { [SENTRY_LINK_TYPE]: SERVER_REQUEST_LINK_TYPE }, - }); + attributes: { [SENTRY_LINK_TYPE]: linkType }, + }; +} + +/** + * Adds a span link to the span described by a `sentry-trace` value. Used when the tag arrives after + * the pageload span exists, which can only link the server request, not continue it. + */ +export function addTraceMetaTagLink(span: Span, { sentryTrace }: TraceMetaTagValues): void { + const link = traceMetaTagLink(sentryTrace, SERVER_REQUEST_LINK_TYPE); + if (link) { + span.addLink(link); + } +} + +/** + * How the server rendered a document with a `sentry-cache-origin` tag. With trace meta tags of this + * request, only the shell was reused. Unset while a resumed part can still bring those tags. + */ +function getServerRenderingType(traceMetaTags: TraceMetaTagValues | undefined): string | undefined { + if (traceMetaTags) { + return 'partially_static'; + } + return canWaitForTraceMetaTag() ? undefined : 'static'; +} + +/** + * Marks a pageload whose document carries a `sentry-cache-origin` tag with how the server rendered + * it, and links the render that the page reuses. Applied at span start, so `tracesSampler` and + * `ignoreSpans` see both. Like a `use cache` hit, the pageload links only a sampled render. + */ +export function withCacheOrigin(spanOptions: StartSpanOptions, traceMetaTags?: TraceMetaTagValues): StartSpanOptions { + const document = getDocument(); + const cacheOrigin = document && lastMetaContent(document, CACHE_ORIGIN_META_TAG_NAME); + if (!cacheOrigin) { + return spanOptions; + } + + const link = traceMetaTagLink(cacheOrigin, CACHE_ORIGIN_LINK_TYPE); + const isSampled = link?.context.traceFlags === TRACE_FLAG_SAMPLED; + const serverRenderingType = getServerRenderingType(traceMetaTags); + return { + ...spanOptions, + attributes: { + ...spanOptions.attributes, + ...(serverRenderingType && { [PAGE_SERVER_RENDERING_TYPE]: serverRenderingType }), + }, + links: link && isSampled ? [...(spanOptions.links ?? []), link] : spanOptions.links, + }; } diff --git a/packages/nextjs/src/common/traceMetaTags.ts b/packages/nextjs/src/common/traceMetaTags.ts new file mode 100644 index 000000000000..90ae909bf962 --- /dev/null +++ b/packages/nextjs/src/common/traceMetaTags.ts @@ -0,0 +1,6 @@ +/** + * Name of the meta tag the server writes in place of `sentry-trace` while Next.js prerenders a document. + * Its content is the `sentry-trace` value of the prerender. The client turns it into a `cache_origin` + * link and never continues it, because the document is served to every later visitor. + */ +export const CACHE_ORIGIN_META_TAG_NAME = 'sentry-cache-origin'; diff --git a/packages/nextjs/src/config/withSentryConfig/getFinalConfigObjectUtils.ts b/packages/nextjs/src/config/withSentryConfig/getFinalConfigObjectUtils.ts index 7ca66c51d1f7..a90529b8d24d 100644 --- a/packages/nextjs/src/config/withSentryConfig/getFinalConfigObjectUtils.ts +++ b/packages/nextjs/src/config/withSentryConfig/getFinalConfigObjectUtils.ts @@ -1,3 +1,4 @@ +import { CACHE_ORIGIN_META_TAG_NAME } from '../../common/traceMetaTags'; import { debug, isMatchingPattern, parseSemver } from '@sentry/core'; import { getSentryRelease } from '@sentry/node'; import * as fs from 'fs'; @@ -194,12 +195,13 @@ export function maybeSetClientTraceMetadataOption( incomingUserNextConfigObject.experimental.clientTraceMetadata = [ 'baggage', 'sentry-trace', + CACHE_ORIGIN_META_TAG_NAME, ...(incomingUserNextConfigObject.experimental?.clientTraceMetadata || []), ]; } } else { getBuildLogger(silent).log( - "[@sentry/nextjs] The Sentry SDK was not able to determine your Next.js version. If you are using Next.js version 15 or greater, please add `experimental.clientTraceMetadata: ['sentry-trace', 'baggage']` to your Next.js config to enable pageload tracing for App Router.", + "[@sentry/nextjs] The Sentry SDK was not able to determine your Next.js version. If you are using Next.js version 15 or greater, please add `experimental.clientTraceMetadata: ['sentry-trace', 'baggage', 'sentry-cache-origin']` to your Next.js config to enable pageload tracing for App Router.", ); } } diff --git a/packages/nextjs/src/server/nextSentryPropagator.ts b/packages/nextjs/src/server/nextSentryPropagator.ts index 1fd3c3438307..b583beff3465 100644 --- a/packages/nextjs/src/server/nextSentryPropagator.ts +++ b/packages/nextjs/src/server/nextSentryPropagator.ts @@ -1,12 +1,14 @@ import type { Context, TextMapSetter, TracerProvider } from '@opentelemetry/api'; -import { propagation } from '@opentelemetry/api'; +import { defaultTextMapSetter, propagation } from '@opentelemetry/api'; import { SentryPropagator } from '@sentry/opentelemetry'; +import { CACHE_ORIGIN_META_TAG_NAME } from '../common/traceMetaTags'; import { isPrerenderContext, markPrerendersOnTracerProvider } from './prerenderContext'; /** * A `SentryPropagator` that does not hand out the trace meta tags of a document while Next.js * prerenders it. Trace context baked into a prerendered shell would be replayed to every visitor the - * shell is served to, so the browser could continue a trace that never handled its request. + * shell is served to, so the browser could continue a trace that never handled its request. The shell + * gets the prerender's trace in a `sentry-cache-origin` tag instead, which the client only links. * * The SDK registers it when it sets up OpenTelemetry. An app that sets up OpenTelemetry itself * registers it in place of `SentryPropagator` and calls `markPrerendersOnTracerProvider` with its @@ -17,10 +19,17 @@ export class NextSentryPropagator extends SentryPropagator { public inject(ctx: Context, carrier: unknown, setter: TextMapSetter): void { // Next.js collects the meta tag values into an array; every other caller, such as the outgoing // request instrumentations, passes a headers object and keeps propagating during a prerender. - if (Array.isArray(carrier) && isPrerenderContext(ctx)) { + if (!Array.isArray(carrier) || !isPrerenderContext(ctx)) { + super.inject(ctx, carrier, setter); return; } - super.inject(ctx, carrier, setter); + + const values: Record = {}; + super.inject(ctx, values, defaultTextMapSetter); + const sentryTrace = values['sentry-trace']; + if (sentryTrace) { + setter.set(carrier, CACHE_ORIGIN_META_TAG_NAME, sentryTrace); + } } } diff --git a/packages/nextjs/test/client/appRouterRoutingInstrumentation.test.ts b/packages/nextjs/test/client/appRouterRoutingInstrumentation.test.ts index 0d8bdded0e0d..25ecbfdd3f80 100644 --- a/packages/nextjs/test/client/appRouterRoutingInstrumentation.test.ts +++ b/packages/nextjs/test/client/appRouterRoutingInstrumentation.test.ts @@ -1,5 +1,5 @@ // @vitest-environment jsdom -import { SENTRY_OP, URL_FULL, URL_PATH } from '@sentry/conventions/attributes'; +import { SENTRY_LINK_TYPE, SENTRY_OP, URL_FULL, URL_PATH } from '@sentry/conventions/attributes'; import { PAGELOAD } from '@sentry/conventions/op'; import type { Client } from '@sentry/core'; import type * as SentryCore from '@sentry/core'; @@ -12,13 +12,16 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import '@sentry/core'; import '@sentry/react'; import '../../src/client/routing/appRouterRoutingInstrumentation'; +import '../../src/client/routing/appRouterPageloadInstrumentation'; import type * as AppRouterInstrumentation from '../../src/client/routing/appRouterRoutingInstrumentation'; +import type * as AppRouterPageload from '../../src/client/routing/appRouterPageloadInstrumentation'; import type * as RouteProvider from '../../src/client/routing/routeProvider'; import type { RouteManifest } from '../../src/config/manifest/types'; type Core = typeof SentryCore; type React = typeof SentryReact; type Instrumentation = typeof AppRouterInstrumentation; +type PageloadInstrumentation = typeof AppRouterPageload; type RouteProviderModule = typeof RouteProvider; interface NextRouter { @@ -67,11 +70,16 @@ async function setup(traceLifecycle: 'stream' | 'static'): Promise<{ router: NextRouter; client: Client; instrumentation: Instrumentation; + pageload: PageloadInstrumentation; }> { vi.resetModules(); const core: Core = await import('@sentry/core'); + // The scopes live on a global carrier that `vi.resetModules()` keeps, so the active span of an + // earlier test would otherwise become the parent of the spans of this one. + core.getMainCarrier().__SENTRY__ = undefined; const react: React = await import('@sentry/react'); const instrumentation: Instrumentation = await import('../../src/client/routing/appRouterRoutingInstrumentation'); + const pageload: PageloadInstrumentation = await import('../../src/client/routing/appRouterPageloadInstrumentation'); const routeProvider: RouteProviderModule = await import('../../src/client/routing/routeProvider'); const client = new react.BrowserClient({ @@ -93,7 +101,7 @@ async function setup(traceLifecycle: 'stream' | 'static'): Promise<{ instrumentation.appRouterInstrumentNavigation(client); await vi.waitFor(() => expect(router.back).not.toBe(originalBack)); - return { core, router, client, instrumentation }; + return { core, router, client, instrumentation, pageload }; } describe('appRouterInstrumentNavigation (router-patch mode)', () => { @@ -269,11 +277,30 @@ describe('appRouterInstrumentNavigation with basePath', () => { describe('appRouterInstrumentPageLoad', () => { const originalReadyState = Object.getOwnPropertyDescriptor(Document.prototype, 'readyState'); + const PRERENDER_TRACE_ID = '11111111111111111111111111111111'; + const PRERENDER_SENTRY_TRACE = `${PRERENDER_TRACE_ID}-1111111111111111-1`; + const PRERENDER_LINK = { + trace_id: PRERENDER_TRACE_ID, + span_id: '1111111111111111', + sampled: true, + attributes: { [SENTRY_LINK_TYPE]: 'cache_origin' }, + }; + + function setReadyState(value: DocumentReadyState): void { + Object.defineProperty(document, 'readyState', { value, configurable: true }); + } + + function appendMeta(parent: HTMLElement, name: string, content: string): void { + const meta = document.createElement('meta'); + meta.setAttribute('name', name); + meta.setAttribute('content', content); + parent.appendChild(meta); + } beforeEach(() => { globalWithNext._sentryRouteManifest = JSON.stringify(manifest); window.history.replaceState({}, '', '/navigation?from=document'); - Object.defineProperty(document, 'readyState', { value: 'loading', configurable: true }); + setReadyState('loading'); vi.spyOn(performance, 'getEntriesByType').mockReturnValue([]); vi.stubEnv('_sentryCacheComponents', 'true'); }); @@ -281,6 +308,9 @@ describe('appRouterInstrumentPageLoad', () => { afterEach(() => { delete globalWithNext.next; delete globalWithNext._sentryRouteManifest; + document.head.innerHTML = ''; + document.body.innerHTML = ''; + vi.useRealTimers(); // @ts-expect-error deleting the instance override restores the prototype getter delete document.readyState; if (originalReadyState) { @@ -291,13 +321,13 @@ describe('appRouterInstrumentPageLoad', () => { }); it('records the URL of the document when the pageload starts after a navigation changed it', async () => { - const { core, instrumentation } = await setup('static'); + const { core, pageload } = await setup('static'); - instrumentation.appRouterInstrumentPageLoad(core.getClient()!); + pageload.appRouterInstrumentPageLoad(core.getClient()!); const spanBeforeSettling = core.getActiveSpan(); window.history.replaceState({}, '', '/navigation/42/router-back'); - instrumentation.settlePendingPageloadWait(); + pageload.settlePendingPageloadWait(); const span = core.getActiveSpan(); expect(span).toBeDefined(); @@ -325,14 +355,82 @@ describe('appRouterInstrumentPageLoad', () => { async ({ cacheComponents }) => { vi.stubEnv('_sentryCacheComponents', cacheComponents); window.history.replaceState({}, '', '/navigation?token=2c4f9e1b&from=document'); - const { core, instrumentation } = await setup('static'); + const { core, pageload } = await setup('static'); - instrumentation.appRouterInstrumentPageLoad(core.getClient()!); - instrumentation.settlePendingPageloadWait(); + pageload.appRouterInstrumentPageLoad(core.getClient()!); + pageload.settlePendingPageloadWait(); const filteredUrl = 'http://localhost:3000/navigation?token=[Filtered]&from=document'; expect(core.spanToJSON(core.getActiveSpan()!).attributes?.[URL_FULL]).toBe(filteredUrl); expect(core.getCurrentScope().getScopeData().sdkProcessingMetadata.normalizedRequest?.url).toBe(filteredUrl); }, ); + + it('starts a new trace for a static page and links the prerender as cache origin', async () => { + const { core, pageload } = await setup('static'); + setReadyState('complete'); + appendMeta(document.head, 'sentry-cache-origin', PRERENDER_SENTRY_TRACE); + + pageload.appRouterInstrumentPageLoad(core.getClient()!); + + const spanJson = core.spanToJSON(core.getActiveSpan()!); + expect(spanJson.attributes?.[SENTRY_OP]).toBe(PAGELOAD); + expect(spanJson.attributes?.['page.server_rendering.type']).toBe('static'); + expect(spanJson.trace_id).not.toBe(PRERENDER_TRACE_ID); + expect(spanJson.links).toEqual([PRERENDER_LINK]); + }); + + // A shell that was prerendered at runtime keeps its tag, and the part resumed for this request + // brings its own `sentry-trace` tag. + it('continues the server trace of a resumed cached shell and links the prerender as cache origin', async () => { + const { core, pageload } = await setup('static'); + setReadyState('complete'); + appendMeta(document.head, 'sentry-cache-origin', PRERENDER_SENTRY_TRACE); + appendMeta(document.body, 'sentry-trace', '22222222222222222222222222222222-2222222222222222-1'); + + pageload.appRouterInstrumentPageLoad(core.getClient()!); + + const spanJson = core.spanToJSON(core.getActiveSpan()!); + expect(spanJson.trace_id).toBe('22222222222222222222222222222222'); + expect(spanJson.parent_span_id).toBe('2222222222222222'); + expect(spanJson.attributes?.['page.server_rendering.type']).toBe('partially_static'); + expect(spanJson.links).toEqual([PRERENDER_LINK]); + }); + + it('links the prerender without a type when a navigation starts the pageload before the resumed part', async () => { + const { core, pageload } = await setup('static'); + appendMeta(document.head, 'sentry-cache-origin', PRERENDER_SENTRY_TRACE); + + pageload.appRouterInstrumentPageLoad(core.getClient()!); + pageload.settlePendingPageloadWait(); + + const spanJson = core.spanToJSON(core.getActiveSpan()!); + expect(spanJson.attributes?.[SENTRY_OP]).toBe(PAGELOAD); + expect(spanJson.attributes).not.toHaveProperty('page.server_rendering.type'); + expect(spanJson.links).toEqual([PRERENDER_LINK]); + }); + + it('links the server request when its trace meta tag arrives after the wait gave up', async () => { + const { core, pageload } = await setup('static'); + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }); + + pageload.appRouterInstrumentPageLoad(core.getClient()!); + vi.advanceTimersByTime(10_000); + const span = core.getActiveSpan()!; + appendMeta(document.body, 'sentry-trace', '22222222222222222222222222222222-2222222222222222-1'); + // MutationObserver callbacks run as microtasks. + await Promise.resolve(); + + const spanJson = core.spanToJSON(span); + expect(spanJson.attributes?.[SENTRY_OP]).toBe(PAGELOAD); + expect(spanJson.trace_id).not.toBe('22222222222222222222222222222222'); + expect(spanJson.links).toEqual([ + { + trace_id: '22222222222222222222222222222222', + span_id: '2222222222222222', + sampled: true, + attributes: { [SENTRY_LINK_TYPE]: 'server_request' }, + }, + ]); + }); }); diff --git a/packages/nextjs/test/client/traceMetaTags.test.ts b/packages/nextjs/test/client/traceMetaTags.test.ts index 4b9fde700e92..695ad7c558bb 100644 --- a/packages/nextjs/test/client/traceMetaTags.test.ts +++ b/packages/nextjs/test/client/traceMetaTags.test.ts @@ -1,12 +1,14 @@ // @vitest-environment jsdom -import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; -import type { Span } from '@sentry/core'; +import { SENTRY_LINK_TYPE, SENTRY_OP } from '@sentry/conventions/attributes'; +import { PAGELOAD } from '@sentry/conventions/op'; +import type { Span, StartSpanOptions } from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { addTraceMetaTagLink, canWaitForTraceMetaTag, readTraceMetaTags, waitForTraceMetaTag, + withCacheOrigin, } from '../../src/client/routing/traceMetaTags'; const HEAD_TRACE = '11111111111111111111111111111111-1111111111111111-1'; @@ -219,3 +221,63 @@ describe('addTraceMetaTagLink', () => { expect(span.addLink).not.toHaveBeenCalled(); }); }); + +describe('withCacheOrigin', () => { + const PAGELOAD_OPTIONS: StartSpanOptions = { name: '/products/:id', attributes: { [SENTRY_OP]: PAGELOAD } }; + const PRERENDER_LINK = { + context: { traceId: '11111111111111111111111111111111', spanId: '1111111111111111', traceFlags: 1 }, + attributes: { [SENTRY_LINK_TYPE]: 'cache_origin' }, + }; + + it('returns the options unchanged for a document without a sentry-cache-origin tag', () => { + appendMeta(document.head, 'sentry-trace', HEAD_TRACE); + + expect(withCacheOrigin(PAGELOAD_OPTIONS, { sentryTrace: HEAD_TRACE, baggage: undefined })).toBe(PAGELOAD_OPTIONS); + }); + + it('marks a fully reused page as static and links a sampled render as cache origin', () => { + appendMeta(document.head, 'sentry-cache-origin', HEAD_TRACE); + + expect(withCacheOrigin(PAGELOAD_OPTIONS)).toEqual({ + name: '/products/:id', + attributes: { [SENTRY_OP]: PAGELOAD, 'page.server_rendering.type': 'static' }, + links: [PRERENDER_LINK], + }); + }); + + it('marks a page as partially static when the pageload continues the trace of this request', () => { + appendMeta(document.head, 'sentry-cache-origin', HEAD_TRACE); + + expect(withCacheOrigin(PAGELOAD_OPTIONS, { sentryTrace: BODY_TRACE, baggage: undefined })).toEqual({ + name: '/products/:id', + attributes: { [SENTRY_OP]: PAGELOAD, 'page.server_rendering.type': 'partially_static' }, + links: [PRERENDER_LINK], + }); + }); + + // A navigation or the wait cap can start the pageload before the resumed part arrives. + it('leaves the type unset while a resumed part can still bring the trace of this request', () => { + appendMeta(document.head, 'sentry-cache-origin', HEAD_TRACE); + setReadyState('loading'); + vi.spyOn(performance, 'getEntriesByType').mockReturnValue([]); + + expect(withCacheOrigin(PAGELOAD_OPTIONS)).toEqual({ + name: '/products/:id', + attributes: { [SENTRY_OP]: PAGELOAD }, + links: [PRERENDER_LINK], + }); + }); + + it.each([ + { tag: 'holds an unsampled trace', content: '11111111111111111111111111111111-1111111111111111-0' }, + { tag: 'holds a trace without a sampling decision', content: '11111111111111111111111111111111-1111111111111111' }, + { tag: 'holds no sentry-trace value', content: 'prerendered' }, + ])('marks the page without a link when the tag $tag', ({ content }) => { + appendMeta(document.head, 'sentry-cache-origin', content); + + expect(withCacheOrigin(PAGELOAD_OPTIONS)).toEqual({ + name: '/products/:id', + attributes: { [SENTRY_OP]: PAGELOAD, 'page.server_rendering.type': 'static' }, + }); + }); +}); diff --git a/packages/nextjs/test/cloudflare/withSentry.test.ts b/packages/nextjs/test/cloudflare/withSentry.test.ts index d24bb75f3f61..46bc97e520be 100644 --- a/packages/nextjs/test/cloudflare/withSentry.test.ts +++ b/packages/nextjs/test/cloudflare/withSentry.test.ts @@ -287,7 +287,7 @@ describe('withSentry', () => { props: {}, } as never); - expect(carrier).toEqual([]); + expect(carrier).toEqual([{ key: 'sentry-cache-origin', value: expect.stringMatching(/-1$/) }]); expect(requestCarrier['sentry-trace']).toMatch(/^[0-9a-f]{32}-[0-9a-f]{16}-1$/); }); diff --git a/packages/nextjs/test/config/maybeSetClientTraceMetadataOption.test.ts b/packages/nextjs/test/config/maybeSetClientTraceMetadataOption.test.ts index 6de4a3a697fd..3dd60f97f848 100644 --- a/packages/nextjs/test/config/maybeSetClientTraceMetadataOption.test.ts +++ b/packages/nextjs/test/config/maybeSetClientTraceMetadataOption.test.ts @@ -3,22 +3,27 @@ import type { NextConfigObject } from '../../src/config/types'; import { maybeSetClientTraceMetadataOption } from '../../src/config/withSentryConfig/getFinalConfigObjectUtils'; describe('maybeSetClientTraceMetadataOption', () => { - it('adds sentry-trace and baggage to clientTraceMetadata on supported Next.js versions', () => { + it('adds baggage, sentry-trace, and sentry-cache-origin to clientTraceMetadata on supported Next.js versions', () => { const config: NextConfigObject = {}; maybeSetClientTraceMetadataOption(config, '15.0.0'); - expect(config.experimental?.clientTraceMetadata).toEqual(['baggage', 'sentry-trace']); + expect(config.experimental?.clientTraceMetadata).toEqual(['baggage', 'sentry-trace', 'sentry-cache-origin']); }); it('preserves user-provided clientTraceMetadata entries', () => { const config: NextConfigObject = { experimental: { clientTraceMetadata: ['my-custom-key'] } }; maybeSetClientTraceMetadataOption(config, '15.0.0'); - expect(config.experimental?.clientTraceMetadata).toEqual(['baggage', 'sentry-trace', 'my-custom-key']); + expect(config.experimental?.clientTraceMetadata).toEqual([ + 'baggage', + 'sentry-trace', + 'sentry-cache-origin', + 'my-custom-key', + ]); }); it('enables trace meta tags when Cache Components is enabled', () => { // The server withholds the tags while it prerenders, see `nextSentryPropagator.ts`. const config: NextConfigObject = { cacheComponents: true }; maybeSetClientTraceMetadataOption(config, '16.0.0'); - expect(config.experimental?.clientTraceMetadata).toEqual(['baggage', 'sentry-trace']); + expect(config.experimental?.clientTraceMetadata).toEqual(['baggage', 'sentry-trace', 'sentry-cache-origin']); }); }); diff --git a/packages/nextjs/test/performance/nextRoutingInstrumentation.test.ts b/packages/nextjs/test/performance/nextRoutingInstrumentation.test.ts index 4b6b117e2117..bc107683f4ba 100644 --- a/packages/nextjs/test/performance/nextRoutingInstrumentation.test.ts +++ b/packages/nextjs/test/performance/nextRoutingInstrumentation.test.ts @@ -2,10 +2,8 @@ import type { Client } from '@sentry/core'; import { WINDOW } from '@sentry/react'; import { JSDOM } from 'jsdom'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { - appRouterInstrumentNavigation, - appRouterInstrumentPageLoad, -} from '../../src/client/routing/appRouterRoutingInstrumentation'; +import { appRouterInstrumentNavigation } from '../../src/client/routing/appRouterRoutingInstrumentation'; +import { appRouterInstrumentPageLoad } from '../../src/client/routing/appRouterPageloadInstrumentation'; import { nextRouterInstrumentNavigation, nextRouterInstrumentPageLoad, @@ -13,9 +11,12 @@ import { import { pagesRouterInstrumentNavigation } from '../../src/client/routing/pagesRouterNavigationInstrumentation'; import { pagesRouterInstrumentPageLoad } from '../../src/client/routing/pagesRouterRoutingInstrumentation'; +vi.mock('../../src/client/routing/appRouterPageloadInstrumentation', () => ({ + appRouterInstrumentPageLoad: vi.fn(), +})); + vi.mock('../../src/client/routing/appRouterRoutingInstrumentation', () => ({ appRouterInstrumentNavigation: vi.fn(), - appRouterInstrumentPageLoad: vi.fn(), })); vi.mock('../../src/client/routing/pagesRouterNavigationInstrumentation', () => ({ pagesRouterInstrumentNavigation: vi.fn(), diff --git a/packages/nextjs/test/server/nextSentryPropagator.test.ts b/packages/nextjs/test/server/nextSentryPropagator.test.ts index 03c2e40e4e2d..d437b1b8a164 100644 --- a/packages/nextjs/test/server/nextSentryPropagator.test.ts +++ b/packages/nextjs/test/server/nextSentryPropagator.test.ts @@ -82,32 +82,34 @@ describe('NextSentryPropagator', () => { }); }); - it('hands out nothing while a route is prerendered', () => { + it('hands out only the trace of the prerender as cache origin while a route is prerendered', () => { trace.getTracer('next.js').startActiveSpan('prerender route (app) /[id]', PRERENDER_SPAN_OPTIONS, span => { expect(isPrerenderContext(context.active())).toBe(true); - expect(inject()).toEqual({}); + expect(inject()).toEqual({ + 'sentry-cache-origin': `${span.spanContext().traceId}-${span.spanContext().spanId}-1`, + }); span.end(); }); }); it('keeps propagating to outgoing requests while a route is prerendered', () => { trace.getTracer('next.js').startActiveSpan('prerender route (app) /[id]', PRERENDER_SPAN_OPTIONS, span => { - expect(inject()).toEqual({}); + expect(inject()).toEqual({ 'sentry-cache-origin': expect.stringMatching(/-1$/) }); expect(injectHeaders()['sentry-trace']).toBe(`${span.spanContext().traceId}-${span.spanContext().spanId}-1`); span.end(); }); }); - it('hands out nothing inside a span nested in a prerender', () => { + it('hands out only the cache origin inside a span nested in a prerender', () => { trace.getTracer('next.js').startActiveSpan('prerender route (app) /[id]', PRERENDER_SPAN_OPTIONS, span => { startSpan({ name: 'server component' }, () => { - expect(inject()).toEqual({}); + expect(Object.keys(inject())).toEqual(['sentry-cache-origin']); }); span.end(); }); }); - it('hands out nothing while a route is prerendered in an unsampled trace', () => { + it('hands out only the cache origin while a route is prerendered in an unsampled trace', () => { setup(0); trace.getTracer('next.js').startActiveSpan('GET /[id]', span => { @@ -118,7 +120,7 @@ describe('NextSentryPropagator', () => { .getTracer('next.js') .startActiveSpan('prerender route (app) /[id]', PRERENDER_SPAN_OPTIONS, prerenderSpan => { expect(prerenderSpan.isRecording()).toBe(false); - expect(inject()).toEqual({}); + expect(inject()).toEqual({ 'sentry-cache-origin': expect.stringMatching(/-0$/) }); prerenderSpan.end(); }); span.end(); @@ -129,7 +131,7 @@ describe('NextSentryPropagator', () => { const span = trace.getTracer('next.js').startSpan('prerender route (app) /[id]', PRERENDER_SPAN_OPTIONS); context.with(trace.setSpan(context.active(), span), () => { expect(isPrerenderContext(context.active())).toBe(true); - expect(inject()).toEqual({}); + expect(Object.keys(inject())).toEqual(['sentry-cache-origin']); }); span.end(); @@ -145,7 +147,7 @@ describe('NextSentryPropagator', () => { .getTracer('next.js') .startActiveSpan('prerender route (app) /[id]', PRERENDER_SPAN_OPTIONS, context.active(), span => { expect(isPrerenderContext(context.active())).toBe(true); - expect(inject()).toEqual({}); + expect(Object.keys(inject())).toEqual(['sentry-cache-origin']); span.end(); }); }); From 214ac9ae665b86b6f8fe4a0f2b54d2ff31c4d344 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 30 Oct 2026 15:35:15 +0100 Subject: [PATCH 064/169] chore: Prune unused regexp dependencies from lockfile (#25948) **Why:** Prune unused packages left behind by #25944 so the lockfile deduplication check passes on develop. Co-authored-by: OpenAI Codex --- pnpm-lock.yaml | 55 -------------------------------------------------- 1 file changed, 55 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa9e6df7f0b7..4bdc4bbc393f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10976,10 +10976,6 @@ packages: resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} engines: {node: '>= 12.0.0'} - comment-parser@1.4.7: - resolution: {integrity: sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==} - engines: {node: '>= 12.0.0'} - commenting@1.1.0: resolution: {integrity: sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA==} @@ -12279,12 +12275,6 @@ packages: peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - eslint-plugin-regexp@3.1.0: - resolution: {integrity: sha512-qGXIC3DIKZHcK1H9A9+Byz9gmndY6TTSRkSMTZpNXdyCw2ObSehRgccJv35n9AdUakEjQp5VFNLas6BMXizCZg==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - peerDependencies: - eslint: '>=9.38.0' - eslint-plugin-simple-import-sort@14.0.0: resolution: {integrity: sha512-NUJO0+XFCkk+o5EsAJruTgnfMEpeWrPWeJS15UVF60GgXmqz1BJ9/3hzlvG7lkL8Bubzos5cCLptThbFfPnSMQ==} peerDependencies: @@ -13840,10 +13830,6 @@ packages: resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} engines: {node: '>=12.0.0'} - jsdoc-type-pratt-parser@7.2.0: - resolution: {integrity: sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==} - engines: {node: '>=20.0.0'} - jsdom-worker@0.3.0: resolution: {integrity: sha512-nlPmN0i93+e6vxzov8xqLMR+MBs/TAYeSviehivzqovHH0AgooVx9pQ/otrygASppPvdR+V9Jqx5SMe8+FcADg==} peerDependencies: @@ -16953,10 +16939,6 @@ packages: redux@4.0.5: resolution: {integrity: sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==} - refa@0.12.1: - resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} @@ -16992,10 +16974,6 @@ packages: regex@5.1.1: resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} - regexp-ast-analysis@0.7.1: - resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -17413,10 +17391,6 @@ packages: webpack: optional: true - scslre@0.3.0: - resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} - engines: {node: ^14.0.0 || >=16.0.0} - scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -30273,8 +30247,6 @@ snapshots: comment-parser@1.4.1: {} - comment-parser@1.4.7: {} - commenting@1.1.0: {} common-ancestor-path@1.0.1: {} @@ -31769,16 +31741,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-regexp@3.1.0: - dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.0(supports-color@10.0.0)) - '@eslint-community/regexpp': 4.12.2 - comment-parser: 1.4.7 - jsdoc-type-pratt-parser: 7.2.0 - refa: 0.12.1 - regexp-ast-analysis: 0.7.1 - scslre: 0.3.0 - eslint-plugin-simple-import-sort@14.0.0(eslint@8.57.0(supports-color@10.0.0)): dependencies: eslint: 8.57.0(supports-color@10.0.0) @@ -33744,8 +33706,6 @@ snapshots: jsdoc-type-pratt-parser@4.1.0: {} - jsdoc-type-pratt-parser@7.2.0: {} - jsdom-worker@0.3.0(node-fetch@3.3.2): dependencies: mitt: 3.0.1 @@ -38160,10 +38120,6 @@ snapshots: loose-envify: 1.4.0 symbol-observable: 1.2.0 - refa@0.12.1: - dependencies: - '@eslint-community/regexpp': 4.12.2 - reflect-metadata@0.1.13: {} reflect-metadata@0.2.2: {} @@ -38202,11 +38158,6 @@ snapshots: dependencies: regex-utilities: 2.3.0 - regexp-ast-analysis@0.7.1: - dependencies: - '@eslint-community/regexpp': 4.12.2 - refa: 0.12.1 - regexp.prototype.flags@1.5.4: dependencies: call-bind: 1.0.8 @@ -38740,12 +38691,6 @@ snapshots: optionalDependencies: webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) - scslre@0.3.0: - dependencies: - '@eslint-community/regexpp': 4.12.2 - refa: 0.12.1 - regexp-ast-analysis: 0.7.1 - scule@1.3.0: {} section-matter@1.0.0: From bcd480503ae3dd17307917c9f0a14418e1c666a8 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Fri, 30 Oct 2026 15:46:19 +0100 Subject: [PATCH 065/169] feat(nextjs): Add `applicationKeyExcludeDependencies` option (#25818) Turbopack injects application-key metadata per module, so tagging bundled dependencies adds repeated code to browser chunks. Add an opt-in `applicationKeyExcludeDependencies` flag for Next.js 16+ Turbopack builds, keeping dependency tagging enabled by default. The option trades filtering coverage for lower bundle overhead: dependency-only chunks may be untagged, so even `exclusively` behaviours can misclassify errors whose stacks stay entirely within those chunks. Document this limitation and discourage using the option with `contains` behaviours. Fixes #25754 --------- Co-authored-by: Codex --- .../nextjs-16/app/third-party-filter/page.tsx | 44 ++++++++++---- .../fixtures/metadata-dependency/index.d.ts | 2 + .../fixtures/metadata-dependency/index.js | 10 ++++ .../fixtures/metadata-dependency/package.json | 7 +++ .../nextjs-16/instrumentation-client.ts | 5 +- .../nextjs-16/next.config.ts | 1 + .../test-applications/nextjs-16/package.json | 6 ++ .../nextjs-16/tests/build-output.test.ts | 21 +++++++ .../tests/third-party-filter.test.ts | 47 ++++++++++++++- .../turbopack/constructTurbopackConfig.ts | 23 ++++---- packages/nextjs/src/config/types.ts | 15 +++++ .../withSentryConfig/getFinalConfigObject.ts | 23 ++++++++ .../constructTurbopackConfig.test.ts | 59 +++++++++++++------ .../test/config/withSentryConfig.test.ts | 35 +++++++++++ 14 files changed, 256 insertions(+), 42 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/index.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/index.js create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/package.json diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/app/third-party-filter/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16/app/third-party-filter/page.tsx index b6b4bea80def..434d4ad3cada 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/app/third-party-filter/page.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/app/third-party-filter/page.tsx @@ -8,17 +8,37 @@ function throwFirstPartyError(): void { export default function Page() { return ( - + <> + + + + ); } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/index.d.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/index.d.ts new file mode 100644 index 000000000000..0071fa0b3ec2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/index.d.ts @@ -0,0 +1,2 @@ +export function createDependencyError(): Error; +export function createAsyncDependencyError(): Promise; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/index.js b/dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/index.js new file mode 100644 index 000000000000..be62d583d1d3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/index.js @@ -0,0 +1,10 @@ +export function createDependencyError() { + return new Error('application-key-dependency-error'); +} + +export function createAsyncDependencyError() { + return new Promise(resolve => { + // Wait until the application's call stack has unwound so the error stack contains only a dependency frame. + queueMicrotask(() => resolve(new Error('application-key-dependency-only-error'))); + }); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/package.json new file mode 100644 index 000000000000..0811417bd955 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/fixtures/metadata-dependency/package.json @@ -0,0 +1,7 @@ +{ + "name": "sentry-metadata-test-dependency", + "version": "1.0.0", + "private": true, + "type": "module", + "main": "index.js" +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/instrumentation-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/instrumentation-client.ts index d0f5b13bb555..f1ad74d2ea85 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/instrumentation-client.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/instrumentation-client.ts @@ -9,7 +9,10 @@ Sentry.init({ integrations: [ Sentry.thirdPartyErrorFilterIntegration({ filterKeys: ['nextjs-16-e2e'], - behaviour: 'apply-tag-if-contains-third-party-frames', + behaviour: + process.env.NEXT_PUBLIC_EXCLUDE_APPLICATION_KEY_DEPENDENCIES === '1' + ? 'apply-tag-if-exclusively-contains-third-party-frames' + : 'apply-tag-if-contains-third-party-frames', }), ], // Verify Log type is available diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts index 6893bd77fdf7..aee951174d24 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/next.config.ts @@ -19,6 +19,7 @@ const nextConfig: NextConfig = { export default withSentryConfig(nextConfig, { silent: true, applicationKey: 'nextjs-16-e2e', + applicationKeyExcludeDependencies: process.env.NEXT_PUBLIC_EXCLUDE_APPLICATION_KEY_DEPENDENCIES === '1', reactComponentAnnotation: { enabled: true, }, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json index 4629e84a780d..330998a1e357 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json @@ -44,6 +44,7 @@ "react": "19.1.0", "react-dom": "19.1.0", "require-in-the-middle": "^8", + "sentry-metadata-test-dependency": "file:../../test-applications/nextjs-16/fixtures/metadata-dependency", "zod": "^3.22.4" }, "devDependencies": { @@ -66,6 +67,11 @@ "sentryTest": { "runner": "namespace-profile-sentry-javascript", "variants": [ + { + "build-command": "NEXT_PUBLIC_EXCLUDE_APPLICATION_KEY_DEPENDENCIES=1 pnpm test:build", + "label": "nextjs-16 (exclude application key dependencies)", + "assert-command": "NEXT_PUBLIC_EXCLUDE_APPLICATION_KEY_DEPENDENCIES=1 pnpm test:assert" + }, { "build-command": "pnpm test:build-webpack", "label": "nextjs-16 (webpack)", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/build-output.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/build-output.test.ts index cc3dfde118af..613590b114ba 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/build-output.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/build-output.test.ts @@ -7,6 +7,27 @@ import { isVinext } from './isVinext'; test.skip(isVinext, 'Checks the build output of Next.js'); +test('excludes dependency metadata while preserving application metadata', () => { + test.skip(isDevMode || process.env.NEXT_PUBLIC_EXCLUDE_APPLICATION_KEY_DEPENDENCIES !== '1'); + + const staticDir = path.join(process.cwd(), '.next', 'static'); + const chunks = fs + .readdirSync(staticDir, { recursive: true }) + .filter(file => String(file).endsWith('.js')) + .map(file => fs.readFileSync(path.join(staticDir, String(file)), 'utf8')); + const dependencyChunks = chunks.filter(chunk => chunk.includes('application-key-dependency-error')); + const applicationChunks = chunks.filter(chunk => chunk.includes('first-party-error')); + + expect(dependencyChunks.length).toBeGreaterThan(0); + expect(applicationChunks.length).toBeGreaterThan(0); + for (const chunk of dependencyChunks) { + expect(chunk).not.toContain('_sentryBundlerPluginAppKey:nextjs-16-e2e'); + } + for (const chunk of applicationChunks) { + expect(chunk).toContain('_sentryBundlerPluginAppKey:nextjs-16-e2e'); + } +}); + test('emits no absolute-path imports into the server output', () => { const leaks = findAbsolutePathImports({ outputDir: path.join(process.cwd(), '.next', 'server') }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/third-party-filter.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/third-party-filter.test.ts index a89a8cf6645d..2c7104a2864d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/third-party-filter.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/third-party-filter.test.ts @@ -1,5 +1,6 @@ import test, { expect } from '@playwright/test'; import { waitForError } from '@sentry-internal/test-utils'; +import { isDevMode } from './isDevMode'; import { isVinext } from './isVinext'; const isWebpackDev = process.env.TEST_ENV === 'development-webpack'; @@ -13,6 +14,7 @@ test('First-party error with React frames should not be tagged as third-party co }); await page.goto('/third-party-filter'); + await page.waitForFunction(() => '__SENTRY__' in window); await page.locator('#first-party-error-btn').click(); const errorEvent = await errorPromise; @@ -22,7 +24,7 @@ test('First-party error with React frames should not be tagged as third-party co // In production, TEST_ENV=production is shared by both turbopack and webpack variants. // Only assert when the build is actually turbopack. if (errorEvent.tags?.turbopack) { - // The integration uses `apply-tag-if-contains-third-party-frames` which tags errors + // By default, the integration uses `apply-tag-if-contains-third-party-frames` which tags errors // if ANY frame is third-party. This error is thrown inside a React onClick handler, // so the stack trace contains React frames from node_modules. These must NOT be // treated as third-party — the module metadata injection must cover node_modules too @@ -30,3 +32,46 @@ test('First-party error with React frames should not be tagged as third-party co expect(errorEvent.tags?.third_party_code).toBeUndefined(); } }); + +test('retains application errors with dependency frames when dependency metadata is excluded', async ({ page }) => { + test.skip(process.env.NEXT_PUBLIC_EXCLUDE_APPLICATION_KEY_DEPENDENCIES !== '1'); + + const errorPromise = waitForError('nextjs-16', event => { + return event.exception?.values?.some(value => value.value === 'application-key-dependency-error') ?? false; + }); + + await page.goto('/third-party-filter'); + await page.waitForFunction(() => '__SENTRY__' in window); + await page.locator('#dependency-error-btn').click(); + + const event = await errorPromise; + const exception = event.exception?.values?.[0]; + expect(exception?.value).toBe('application-key-dependency-error'); + expect(exception?.mechanism).toEqual({ handled: true, type: 'generic' }); + expect(event.tags?.third_party_code).toBeUndefined(); + expect(event.tags?.turbopack).toBe(true); + expect(new Set(exception?.stacktrace?.frames?.map(frame => frame.filename)).size).toBeGreaterThan(1); +}); + +test('classifies dependency-only errors according to dependency metadata inclusion', async ({ page }) => { + test.skip(isVinext || isDevMode, 'Checks dependency-only chunks in production Next.js builds'); + + const errorPromise = waitForError('nextjs-16', event => { + return event.exception?.values?.some(value => value.value === 'application-key-dependency-only-error') ?? false; + }); + + await page.goto('/third-party-filter'); + await page.waitForFunction(() => '__SENTRY__' in window); + await page.locator('#dependency-only-error-btn').click(); + + const event = await errorPromise; + const frames = event.exception?.values?.[0]?.stacktrace?.frames; + expect(frames).toHaveLength(1); + + const filename = frames![0]!.filename; + const chunkResponse = await page.request.get(filename!.replace('app://', '')); + expect(await chunkResponse.text()).toContain('application-key-dependency-only-error'); + expect(event.tags?.third_party_code).toBe( + process.env.NEXT_PUBLIC_EXCLUDE_APPLICATION_KEY_DEPENDENCIES === '1' ? true : undefined, + ); +}); diff --git a/packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts b/packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts index 391dee0e39f1..da0ca895c246 100644 --- a/packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts +++ b/packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts @@ -65,22 +65,25 @@ export function constructTurbopackConfig({ newConfig.rules = safelyAddTurbopackRule(newConfig.rules, { matcher, rule }); } - // Add module metadata injection loader for thirdPartyErrorFilterIntegration support. - // This loader tags modules with `_sentryModuleMetadata` so the integration can tell - // first-party code from third-party code. We intentionally do NOT use - // `condition: { not: 'foreign' }` so that node_modules are also tagged as first-party, - // matching the webpack plugin's BannerPlugin behavior (which injects into all chunks - // without excluding node_modules). The injected code is wrapped in a try-catch IIFE - // so it is safe even for node_modules with strict initialization order. - // We only exclude Next.js build polyfills which contain non-standard syntax that causes - // parse errors when any code is prepended (Turbopack re-parses the loader output). + // Tag modules so thirdPartyErrorFilterIntegration can identify first-party code. Dependencies + // are tagged by default to match webpack's BannerPlugin, which injects metadata into all chunks. + // Opting out with `applicationKeyExcludeDependencies` can leave dependency-only chunks untagged. + // The injected code is wrapped in a try-catch IIFE, so it is safe even for dependencies with + // strict initialization order. Next.js build polyfills are excluded because they contain + // non-standard syntax that causes parse errors when Turbopack re-parses the loader output. // Server and edge modules are skipped because only the browser reads these tags. const applicationKey = userSentryOptions?.applicationKey; if (applicationKey && nextJsVersion && supportsTurbopackRuleCondition(nextJsVersion)) { newConfig.rules = safelyAddTurbopackRule(newConfig.rules, { matcher: '*.{ts,tsx,js,jsx,mjs,cjs}', rule: { - condition: { all: ['browser', { not: { path: /next\/dist\/build\/polyfills/ } }] }, + condition: { + all: [ + 'browser', + { not: { path: /next\/dist\/build\/polyfills/ } }, + ...(userSentryOptions?.applicationKeyExcludeDependencies ? [{ not: 'foreign' as const }] : []), + ], + }, loaders: [ { loader: path.resolve(__dirname, '..', 'loaders', 'moduleMetadataInjectionLoader.js'), diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 4e1e6bc2d9e3..7c990a480e51 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -225,6 +225,21 @@ export type SentryBuildOptions = Omit< */ applicationKey?: string; + /** + * Skip application key metadata injection for dependencies in Turbopack builds with Next.js 16+. + * Uses Turbopack's `foreign` condition, which includes `node_modules` and some Next.js internals. + * Requires `applicationKey`. Has no effect on webpack builds. + * + * This reduces per-module injection overhead, but chunks containing only dependencies may no + * longer be tagged as first-party. Only use with `*-exclusively-contains-third-party-frames` + * behaviours of `thirdPartyErrorFilterIntegration`, and accept that errors whose stacks are + * entirely in untagged chunks can be dropped or tagged as third-party. With `*-contains-*` + * behaviours, even one frame from an untagged chunk can cause this misclassification. + * + * @default false + */ + applicationKeyExcludeDependencies?: boolean; + /** * Metadata that should be associated with the built application. * diff --git a/packages/nextjs/src/config/withSentryConfig/getFinalConfigObject.ts b/packages/nextjs/src/config/withSentryConfig/getFinalConfigObject.ts index e855041202e1..6a2e4b164bb9 100644 --- a/packages/nextjs/src/config/withSentryConfig/getFinalConfigObject.ts +++ b/packages/nextjs/src/config/withSentryConfig/getFinalConfigObject.ts @@ -1,6 +1,8 @@ +import { getBuildLogger } from '../buildLogger'; import type { NextConfigObject, SentryBuildOptions } from '../types'; import { getNextjsVersion } from '../util'; import { setUpBuildTimeVariables } from './buildTime'; +import type { BundlerInfo } from './getFinalConfigObjectBundlerUtils'; import { getBundlerInfo, getServerExternalPackagesPatch, @@ -61,6 +63,7 @@ export function getFinalConfigObject( const bundlerInfo = getBundlerInfo(nextJsVersion); maybeWarnAboutUnsupportedTurbopack(nextJsVersion, bundlerInfo, userSentryOptions.silent); maybeWarnAboutTurbopackModuleMetadata(userSentryOptions, bundlerInfo); + maybeWarnAboutApplicationKeyExcludeDependencies(userSentryOptions, bundlerInfo); maybeWarnAboutUnsupportedRunAfterProductionCompileHook(nextJsVersion, userSentryOptions, bundlerInfo); const turboPackConfig = maybeConstructTurbopackConfig( @@ -108,3 +111,23 @@ export function getFinalConfigObject( ...getTurbopackPatch(bundlerInfo, turboPackConfig), }; } + +function maybeWarnAboutApplicationKeyExcludeDependencies( + userSentryOptions: SentryBuildOptions, + bundlerInfo: BundlerInfo, +): void { + if (!userSentryOptions.applicationKeyExcludeDependencies) { + return; + } + + const logger = getBuildLogger(userSentryOptions.silent); + if (!userSentryOptions.applicationKey) { + logger.warn( + '[@sentry/nextjs] `applicationKeyExcludeDependencies` requires `applicationKey` and has no effect without it.', + ); + } else if (bundlerInfo.isWebpack) { + logger.warn( + '[@sentry/nextjs] `applicationKeyExcludeDependencies` only applies to Turbopack builds and has no effect on webpack builds.', + ); + } +} diff --git a/packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts b/packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts index 3670ff6244e7..39aa27f8760a 100644 --- a/packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts +++ b/packages/nextjs/test/config/turbopack/constructTurbopackConfig.test.ts @@ -990,20 +990,40 @@ describe('moduleMetadataInjection with applicationKey', () => { expect(rule.loaders[0]!.options.applicationKey).toBe('my-top-level-key'); }); - it('should only tag browser modules and exclude Next.js polyfills, not all foreign modules', () => { - const userNextConfig: NextConfigObject = {}; + it.each([undefined, false])( + 'includes dependencies when applicationKeyExcludeDependencies is %s', + applicationKeyExcludeDependencies => { + const userNextConfig: NextConfigObject = {}; + + const result = constructTurbopackConfig({ + userNextConfig, + userSentryOptions: { applicationKey: 'my-app', applicationKeyExcludeDependencies }, + nextJsVersion: '16.0.0', + }); + + const rule = result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}'] as { condition?: unknown; loaders: unknown[] }; + expect(rule.condition).toEqual({ all: ['browser', { not: { path: /next\/dist\/build\/polyfills/ } }] }); + }, + ); + it('excludes dependencies from browser metadata injection when opted in', () => { const result = constructTurbopackConfig({ - userNextConfig, - userSentryOptions: { applicationKey: 'my-app' }, + userNextConfig: {}, + userSentryOptions: { applicationKey: 'my-app', applicationKeyExcludeDependencies: true }, nextJsVersion: '16.0.0', }); - const rule = result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}'] as { condition?: unknown; loaders: unknown[] }; - // Unlike component annotation (which uses { not: 'foreign' }), metadata injection - // must cover node_modules to match the webpack plugin's BannerPlugin behavior. - // Only Next.js build polyfills are excluded because they have non-standard syntax. - expect(rule.condition).toEqual({ all: ['browser', { not: { path: /next\/dist\/build\/polyfills/ } }] }); + expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toEqual({ + condition: { + all: ['browser', { not: { path: /next\/dist\/build\/polyfills/ } }, { not: 'foreign' }], + }, + loaders: [ + { + loader: '/mocked/path/to/moduleMetadataInjectionLoader.js', + options: { applicationKey: 'my-app' }, + }, + ], + }); }); it('should NOT add metadata loader rule when Next.js < 16', () => { @@ -1018,17 +1038,20 @@ describe('moduleMetadataInjection with applicationKey', () => { expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toBeUndefined(); }); - it('should NOT add metadata loader rule when applicationKey is not set', () => { - const userNextConfig: NextConfigObject = {}; + it.each([undefined, true])( + 'does not inject metadata without applicationKey when exclusion is %s', + applicationKeyExcludeDependencies => { + const userNextConfig: NextConfigObject = {}; - const result = constructTurbopackConfig({ - userNextConfig, - userSentryOptions: {}, - nextJsVersion: '16.0.0', - }); + const result = constructTurbopackConfig({ + userNextConfig, + userSentryOptions: { applicationKeyExcludeDependencies }, + nextJsVersion: '16.0.0', + }); - expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toBeUndefined(); - }); + expect(result.rules!['*.{ts,tsx,js,jsx,mjs,cjs}']).toBeUndefined(); + }, + ); it('should NOT add metadata loader rule when nextJsVersion is undefined', () => { const userNextConfig: NextConfigObject = {}; diff --git a/packages/nextjs/test/config/withSentryConfig.test.ts b/packages/nextjs/test/config/withSentryConfig.test.ts index cc749732eb35..9cec2cc71410 100644 --- a/packages/nextjs/test/config/withSentryConfig.test.ts +++ b/packages/nextjs/test/config/withSentryConfig.test.ts @@ -1302,6 +1302,41 @@ describe('withSentryConfig', () => { }); }); + describe('applicationKeyExcludeDependencies warnings', () => { + beforeEach(() => { + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('16.0.0'); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('warns without applicationKey', () => { + vi.spyOn(util, 'detectActiveBundler').mockReturnValue('turbopack'); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + materializeFinalNextConfig(exportedNextConfig, undefined, { applicationKeyExcludeDependencies: true }); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] `applicationKeyExcludeDependencies` requires `applicationKey` and has no effect without it.', + ); + }); + + it('warns on webpack when applicationKey is set', () => { + vi.spyOn(util, 'detectActiveBundler').mockReturnValue('webpack'); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + materializeFinalNextConfig(exportedNextConfig, undefined, { + applicationKey: 'my-app', + applicationKeyExcludeDependencies: true, + }); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] `applicationKeyExcludeDependencies` only applies to Turbopack builds and has no effect on webpack builds.', + ); + }); + }); + describe('turbopack version compatibility warnings', () => { const originalTurbopack = process.env.TURBOPACK; const originalNodeEnv = process.env.NODE_ENV; From 24313af0d24a210431f70d985318ca85da497cdb Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:46:40 +0100 Subject: [PATCH 066/169] chore(dependabot): Ignore major react-router updates (#25892) ## What Dependabot no longer opens major version bumps for react-router packages. Minor and patch bumps still come through. ## Why We keep the react-router dev dependencies on v7 on purpose, so tests cover the older major (see #25887). Co-authored-by: Claude Opus 5.5 Co-authored-by: Nicolas Hrubec Co-authored-by: Martin Sonnberger --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f0fddbffa61e..dc6184d29f3b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,6 +19,13 @@ updates: # 2.110.0 raised the engine requirement to Node >=22, while this repo runs Node 20. - dependency-name: '@supabase/supabase-js' versions: ['>=2.110.0'] + # Keep react-router dev dependencies on v7, so tests cover the older major. + - dependency-name: 'react-router' + update-types: ['version-update:semver-major'] + - dependency-name: 'react-router-dom' + update-types: ['version-update:semver-major'] + - dependency-name: '@react-router/*' + update-types: ['version-update:semver-major'] groups: opentelemetry: patterns: From bc8fdf170009cd3c474b158d9dcf169f1acb9741 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:53:29 +0100 Subject: [PATCH 067/169] ci(deps): Dedupe lockfile on Dependabot PRs (#25911) Dependabot often updates `pnpm-lock.yaml` without deduping, causing `Check lockfile` to fail. This workflow dedupes Dependabot PR lockfiles and pushes the result back through a separate `workflow_run` job. The job with no secrets runs pnpm against the PR; the push job runs no PR code, validates the PR and lockfile artifact, then uses a narrowly scoped Gitflow app token for a non-forced push. This requires the Gitflow app to have write access to `dependabot/**` branches; please confirm reusing it is acceptable. via **Charly Gomez**. -- [View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3AC092LQMHRJN%3A1793288263.606909) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC092LQMHRJN%3A1793288263.606909/?project=4510944073809921) --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Charly Gomez Co-authored-by: Charly Gomez Co-authored-by: Claude Fable 5.1 --- .github/workflows/build.yml | 7 +- .github/workflows/dependabot-dedupe-push.yml | 134 +++++++++++++++++++ .github/workflows/dependabot-dedupe.yml | 63 +++++++++ 3 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/dependabot-dedupe-push.yml create mode 100644 .github/workflows/dependabot-dedupe.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a546292a32ef..0d470f273e39 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1141,13 +1141,16 @@ jobs: # them. optional-e2e.yml reports the results in a PR comment. job_optional_e2e_tests: name: E2E Tests (optional) - # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks + # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks. + # Dependabot PRs are skipped by author, not by `github.actor`: once the Gitflow app pushes the deduped lockfile + # (dependabot-dedupe-push.yml), the actor is the app and the run would otherwise get the E2E secrets. # We need to add the `always()` check here because the previous step has this as well :( # See: https://github.com/actions/runner/issues/2205 if: always() && needs.job_get_metadata.outputs.is_release != 'true' && needs.job_build_tarballs.result == 'success' && needs.job_build.outputs.e2e-matrix-optional != '{"include":[]}' && (github.event_name != 'pull_request' || - github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' + github.event.pull_request.head.repo.full_name == github.repository) && github.event.pull_request.user.login != + 'dependabot[bot]' needs: [job_get_metadata, job_build, job_build_tarballs] # This job only waits for optional-e2e.yml, which can take most of an hour. GitHub runners are free for public # repos and Namespace runners are paid per minute, so the wait runs here and the tests run on Namespace. diff --git a/.github/workflows/dependabot-dedupe-push.yml b/.github/workflows/dependabot-dedupe-push.yml new file mode 100644 index 000000000000..3fce5447b7ba --- /dev/null +++ b/.github/workflows/dependabot-dedupe-push.yml @@ -0,0 +1,134 @@ +name: 'Dependabot: Push deduped lockfile' + +# Pushes the lockfile from `dependabot-dedupe.yml` to the Dependabot PR branch. +# +# This workflow holds the Gitflow app token, so it never runs code from the PR. Everything it +# receives is untrusted: anyone with write access can push to a Dependabot branch, and the run that +# produced the artifact used the PR's version of `dependabot-dedupe.yml`. So it looks the PR up +# through the API, accepts only a single regular `pnpm-lock.yaml` file and commits nothing else. +# +# The app token is limited to `contents: write` on this repository, not to `dependabot/**` branches, so +# nothing from the PR may run before it is minted. Pushing as the app also changes `github.actor` on the +# PR's next CI run, so jobs that keep secrets away from Dependabot must check the PR author, not the actor. + +on: + workflow_run: + workflows: ['Dependabot: Dedupe lockfile'] + types: [completed] + +permissions: + actions: read + contents: read + pull-requests: read + +concurrency: + group: dependabot-dedupe-push-${{ github.event.workflow_run.head_sha }} + cancel-in-progress: false + +jobs: + push: + name: Push deduped lockfile + if: | + github.event.workflow_run.conclusion == 'success' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.head_repository.full_name == github.repository + runs-on: namespace-profile-sentry-javascript-small + timeout-minutes: 5 + env: + GH_TOKEN: ${{ github.token }} + RUN_ID: ${{ github.event.workflow_run.id }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + steps: + - name: Find artifact and Dependabot PR + id: pr + run: | + artifacts=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}/artifacts" \ + --jq '[.artifacts[] | select(.name == "pnpm-lock")] | length') + if [ "$artifacts" != "1" ]; then + echo "No deduped lockfile to push." + exit 0 + fi + + head_ref=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${HEAD_SHA}/pulls" | + jq -r --arg repo "$GITHUB_REPOSITORY" --arg sha "$HEAD_SHA" ' + [.[] | select(.state == "open" + and .base.ref == "develop" + and .user.login == "dependabot[bot]" + and .head.repo.full_name == $repo + and .head.sha == $sha)] + | if length == 1 then .[0].head.ref else empty end') + if [[ ! "$head_ref" =~ ^dependabot/[A-Za-z0-9._/@+-]+$ ]]; then + echo "::error::No open Dependabot PR into develop has head commit ${HEAD_SHA}." + exit 1 + fi + echo "head_ref=${head_ref}" >> "$GITHUB_OUTPUT" + + - name: Download deduped lockfile + if: steps.pr.outputs.head_ref + uses: actions/download-artifact@v8 + with: + name: pnpm-lock + path: ${{ runner.temp }}/pnpm-lock + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + + - name: Check out PR branch + if: steps.pr.outputs.head_ref + uses: actions/checkout@v7 + with: + ref: ${{ github.event.workflow_run.head_sha }} + persist-credentials: false + + # Replace the file instead of writing through it, so a symlinked lockfile in the PR cannot redirect the write. + - name: Apply deduped lockfile + if: steps.pr.outputs.head_ref + id: apply + env: + LOCKFILE_DIR: ${{ runner.temp }}/pnpm-lock + run: | + if [ "$(find "$LOCKFILE_DIR" -mindepth 1 | wc -l)" != "1" ] || [ ! -f "$LOCKFILE_DIR/pnpm-lock.yaml" ] || [ -L "$LOCKFILE_DIR/pnpm-lock.yaml" ]; then + echo "::error::The artifact must contain exactly one regular file named pnpm-lock.yaml." + exit 1 + fi + if [ "$(git ls-files --stage -- pnpm-lock.yaml | cut -d' ' -f1)" != "100644" ]; then + echo "::error::pnpm-lock.yaml in the PR is not a regular file." + exit 1 + fi + rm pnpm-lock.yaml + cp "$LOCKFILE_DIR/pnpm-lock.yaml" pnpm-lock.yaml + + changes=$(git status --porcelain) + if [ -z "$changes" ]; then + echo "Lockfile is already deduped." + exit 0 + fi + if [ "$changes" != " M pnpm-lock.yaml" ]; then + echo "::error::Unexpected changes:" + echo "$changes" + exit 1 + fi + echo "changed=true" >> "$GITHUB_OUTPUT" + + # An app token is needed so the push triggers CI again; pushes made with GITHUB_TOKEN do not. + - name: Generate GitHub App token + if: steps.apply.outputs.changed == 'true' + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ vars.GITFLOW_APP_ID }} + private-key: ${{ secrets.GITFLOW_APP_PRIVATE_KEY }} + permission-contents: write + + # `[dependabot skip]` lets Dependabot keep rebasing the PR over this commit. + # The push is not forced, so it fails if the branch moved past HEAD_SHA in the meantime. + - name: Commit and push + if: steps.apply.outputs.changed == 'true' + env: + APP_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + HEAD_REF: ${{ steps.pr.outputs.head_ref }} + run: | + git config user.name "${APP_SLUG}[bot]" + git config user.email "${APP_SLUG}[bot]@users.noreply.github.com" + git commit -m "chore(deps): Dedupe pnpm-lock.yaml [dependabot skip]" -- pnpm-lock.yaml + git push "https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:refs/heads/${HEAD_REF}" diff --git a/.github/workflows/dependabot-dedupe.yml b/.github/workflows/dependabot-dedupe.yml new file mode 100644 index 000000000000..05fcf2f42448 --- /dev/null +++ b/.github/workflows/dependabot-dedupe.yml @@ -0,0 +1,63 @@ +name: 'Dependabot: Dedupe lockfile' + +# Dependabot updates pnpm-lock.yaml without deduping it, so `Check lockfile` (`pnpm dedupe --check`) +# fails on most of its PRs. This job dedupes the lockfile and uploads it as an artifact. It runs on +# `pull_request`, so it has no secrets, a read-only token and PR-scoped caches. +# `dependabot-dedupe-push.yml` validates the result and pushes it to the PR branch. + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: + - develop + paths: + - 'pnpm-lock.yaml' + +permissions: + contents: read + +concurrency: + group: dependabot-dedupe-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + dedupe: + name: Dedupe lockfile + # Only run on pushes made by Dependabot itself. The deduped commit pushed by `dependabot-dedupe-push.yml` + # also triggers `synchronize`, and a second full `pnpm dedupe` on it would only confirm there is nothing left to do. + if: | + github.event.pull_request.user.login == 'dependabot[bot]' + && github.actor == 'dependabot[bot]' + && github.event.pull_request.head.repo.full_name == github.repository + # PR code runs here, so keep its cache writes away from the cache used by develop, like the PR jobs in build.yml. + runs-on: namespace-profile-sentry-javascript;overrides.cache-tag=sentry-javascript-pr + # `pnpm dedupe` fetches metadata for every package from npm. + timeout-minutes: 20 + steps: + - name: Check out PR branch + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Install pnpm + uses: ./.github/actions/install-pnpm + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version-file: 'package.json' + + - name: Dedupe lockfile + id: dedupe + run: | + pnpm dedupe --ignore-scripts --ignore-pnpmfile + git diff --quiet pnpm-lock.yaml || echo "changed=true" >> "$GITHUB_OUTPUT" + + - name: Upload deduped lockfile + if: steps.dedupe.outputs.changed == 'true' + uses: actions/upload-artifact@v7 + with: + name: pnpm-lock + path: pnpm-lock.yaml + retention-days: 1 From 299a3c3b9df53ee63604986620d4ab6bbeca099d Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 30 Oct 2026 10:55:53 -0400 Subject: [PATCH 068/169] fix(core): Call native toString without page-replaceable lookups in FunctionToString (#25921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `FunctionToString` could overflow the stack, it happens when a page hooks `Function.prototype.apply` or when `toString` gets a huge argument list, this can be fixed by capturing a binding to the native call once and passing no arguments. **Why does a huge argument list trigger this?** V8 only fits so many args on the stack. Our wrapper held two copies (the incoming call and our call to native `toString`), so a huge list overflows where native's single copy fits. This is mentioned on [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#:~:text=But%20beware%3A%20by%20using%20apply()%20(or%20the%20spread%20syntax)%20with%20an%20arbitrarily%20long%20arguments%20list%2C%20you%20run%20the%20risk%20of%20exceeding%20the%20JavaScript%20engine%27s%20argument%20length%20limit.). I added tests for an apply hook that calls toString and a 100k argument call, both overflow before the fix. Related to #22515, which removed the same kind of re-entry through `Reflect.apply`. ---- ⚠️ This is a guess at best at an issue that was reported, no guarantee it fixes it because it seems far fetched that this is what happened. Feel free to close if this is too much. --- .../core/src/integrations/functiontostring.ts | 8 +++-- .../lib/integrations/functiontostring.test.ts | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/core/src/integrations/functiontostring.ts b/packages/core/src/integrations/functiontostring.ts index c915e55d8c3f..2f3a8ce295d3 100644 --- a/packages/core/src/integrations/functiontostring.ts +++ b/packages/core/src/integrations/functiontostring.ts @@ -15,11 +15,15 @@ const _functionToStringIntegration = (() => { setupOnce() { // eslint-disable-next-line @typescript-eslint/unbound-method const originalFunctionToString = Function.prototype.toString; + // Bound once so calls neither look up a page-replaceable `apply` nor copy arguments, which native toString ignores + const callOriginalFunctionToString = Function.prototype.call.bind(originalFunctionToString) as ( + thisArg: unknown, + ) => string; // intrinsics (like Function.prototype) might be immutable in some environments // e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal) try { - Function.prototype.toString = function (this: WrappedFunction, ...args: unknown[]): string { + Function.prototype.toString = function (this: WrappedFunction): string { const originalFunction = getOriginalFunction(this); let unwrappedFunction: WrappedFunction | undefined; @@ -33,7 +37,7 @@ const _functionToStringIntegration = (() => { // `toString` never throws here, so fall back to it to avoid turning harmless introspection into noise. } - return originalFunctionToString.apply(unwrappedFunction ?? this, args); + return callOriginalFunctionToString(unwrappedFunction ?? this); }; } catch { // ignore errors here, just don't patch this diff --git a/packages/core/test/lib/integrations/functiontostring.test.ts b/packages/core/test/lib/integrations/functiontostring.test.ts index 99b54fa1c3ff..e42c83a77611 100644 --- a/packages/core/test/lib/integrations/functiontostring.test.ts +++ b/packages/core/test/lib/integrations/functiontostring.test.ts @@ -84,6 +84,42 @@ describe('FunctionToString', () => { expect(inspectedFunction.toString()).toBe(expected); }); + it('does not recurse when Function.prototype.apply performs a function toString check', () => { + function inspectedFunction(): void {} + + const fts = functionToStringIntegration(); + getClient()?.addIntegration(fts); + const expected = inspectedFunction.toString(); + + const applySpy = vi.spyOn(Function.prototype, 'apply').mockImplementation(function ( + this: Function, + thisArg: unknown, + args?: unknown[], + ) { + this.toString(); + return Reflect.apply(this, thisArg, args ?? []); + }); + let result: unknown; + try { + result = inspectedFunction.toString(); + } catch (error) { + result = error; + } + applySpy.mockRestore(); + + expect(result).toBe(expected); + }); + + it('does not overflow the stack when called with many arguments', () => { + function inspectedFunction(): void {} + + const fts = functionToStringIntegration(); + getClient()?.addIntegration(fts); + const expected = inspectedFunction.toString(); + + expect(Reflect.apply(Function.prototype.toString, inspectedFunction, new Array(100_000))).toBe(expected); + }); + it('falls back to native toString and does not throw when the carrier read throws', () => { const foo = { bar(wat: boolean): boolean { From de967f799ac748214a0f9704aabd1d971dc3f8ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:09:15 +0100 Subject: [PATCH 069/169] ci(deps): Bump anthropics/claude-code-action from 1.0.236 to 1.0.264 (#25952) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) from 1.0.236 to 1.0.264.
Release notes

Sourced from anthropics/claude-code-action's releases.

v1.0.264

What's Changed

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.263...v1.0.264

v1.0.263

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.262...v1.0.263

v1.0.262

What's Changed

New Contributors

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.261...v1.0.262

v1.0.261

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.260...v1.0.261

v1.0.260

What's Changed

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.259...v1.0.260

v1.0.259

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.258...v1.0.259

v1.0.258

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.257...v1.0.258

v1.0.257

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.256...v1.0.257

v1.0.256

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.255...v1.0.256

v1.0.255

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.254...v1.0.255

v1.0.254

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.253...v1.0.254

v1.0.253

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.252...v1.0.253

... (truncated)

Commits
  • d70510b chore: bump Claude Code to 2.1.312 and Agent SDK to 0.3.312
  • 98a9e39 docs: use claude-plugins-official in plugin examples (#1932)
  • 8041f1d chore: bump Claude Code to 2.1.311 and Agent SDK to 0.3.311
  • 925afb5 chore: bump Bun to 1.4.4 (#1916)
  • 16f9f71 chore: bump Claude Code to 2.1.310 and Agent SDK to 0.3.310
  • 8d70a0d Pin the PR version a run checks out (#1915)
  • ae68417 chore: bump Claude Code to 2.1.309 and Agent SDK to 0.3.309
  • 6b6870e chore: bump Claude Code to 2.1.308 and Agent SDK to 0.3.308
  • aa8a773 chore: bump Claude Code to 2.1.307 and Agent SDK to 0.3.307
  • a5a423f chore: bump Claude Code to 2.1.306 and Agent SDK to 0.3.306
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=anthropics/claude-code-action&package-manager=github_actions&previous-version=1.0.236&new-version=1.0.264)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-fix-issue.yml | 2 +- .github/workflows/dependabot-auto-triage.yml | 4 ++-- .github/workflows/track-framework-updates.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-fix-issue.yml b/.github/workflows/auto-fix-issue.yml index 8feb478c6edb..146ab59d415f 100644 --- a/.github/workflows/auto-fix-issue.yml +++ b/.github/workflows/auto-fix-issue.yml @@ -77,7 +77,7 @@ jobs: - name: Try to fix the issue with Claude id: triage - uses: anthropics/claude-code-action@8ce9314fa9a404564fa7e954cd84f25bcba2b829 # v1 + uses: anthropics/claude-code-action@d70510b174a1244958df0b03166a54912811f3e6 # v1 env: ANTHROPIC_BASE_URL: https://openrouter.ai/api with: diff --git a/.github/workflows/dependabot-auto-triage.yml b/.github/workflows/dependabot-auto-triage.yml index 56ebdfe42f2f..c3efead14345 100644 --- a/.github/workflows/dependabot-auto-triage.yml +++ b/.github/workflows/dependabot-auto-triage.yml @@ -155,7 +155,7 @@ jobs: git config user.email "${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" - name: Open batched runtime fix PR via skill - uses: anthropics/claude-code-action@8ce9314fa9a404564fa7e954cd84f25bcba2b829 # v1 + uses: anthropics/claude-code-action@d70510b174a1244958df0b03166a54912811f3e6 # v1 env: ANTHROPIC_BASE_URL: https://openrouter.ai/api with: @@ -244,7 +244,7 @@ jobs: git config user.email "${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" - name: Open batched dev fix PR via skill - uses: anthropics/claude-code-action@8ce9314fa9a404564fa7e954cd84f25bcba2b829 # v1 + uses: anthropics/claude-code-action@d70510b174a1244958df0b03166a54912811f3e6 # v1 env: ANTHROPIC_BASE_URL: https://openrouter.ai/api with: diff --git a/.github/workflows/track-framework-updates.yml b/.github/workflows/track-framework-updates.yml index 3e0172884e7f..9b664d361078 100644 --- a/.github/workflows/track-framework-updates.yml +++ b/.github/workflows/track-framework-updates.yml @@ -43,7 +43,7 @@ jobs: - name: Run Claude digest id: digest - uses: anthropics/claude-code-action@8ce9314fa9a404564fa7e954cd84f25bcba2b829 # v1 + uses: anthropics/claude-code-action@d70510b174a1244958df0b03166a54912811f3e6 # v1 env: ANTHROPIC_BASE_URL: https://openrouter.ai/api with: From 7c6051950766f3bc39d32f80db6f95b1dd4f7eca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:11:32 +0100 Subject: [PATCH 070/169] ci(deps): Bump getsentry/github-workflows/validate-pr from 36c729264d2edc29ebae61950c50e1e9f043ad7e to 01bcb7fe69bb48bacd83e675840e3dc95b33c730 (#25954) Bumps [getsentry/github-workflows/validate-pr](https://github.com/getsentry/github-workflows) from 36c729264d2edc29ebae61950c50e1e9f043ad7e to 01bcb7fe69bb48bacd83e675840e3dc95b33c730.
Changelog

Sourced from getsentry/github-workflows/validate-pr's changelog.

Changelog

Unreleased

Fixes

  • Validate PR - Skip PRs opened by bots of GitHub Apps owned by Sentry (the getsentry organization or the sentry enterprise), such as sentry-junior[bot]. App bot users are never repo collaborators, so the write-access check couldn't recognize them. (#179)

3.4.1

Fixes

  • Danger - Add skip-checkout to preserve a caller's prepared workspace, including generated custom Dangerfiles. Checkout remains enabled by default. (#175)
  • Updater - Preserve CMake and submodule pins ahead of the selected release, while reporting divergent histories and Git errors (#174)
  • Danger - Harden extra-install-packages handling: pass the package list into the container via env var instead of host-shell string interpolation (defense in depth) (#169)
  • Updater - Avoid cleanup races in temporary ancestry repositories by disabling background Git maintenance; preserve original Git errors if cleanup also fails (#177)
  • Sentry-CLI integration test action - Skip the reverse DNS lookup on server start, which made each start take ~35 s on macOS (#178)

3.4.0

Features

  • Validate PR - Action is advisory: it posts a single friendly comment on community PRs that don't reference an issue with maintainer discussion. PRs are not closed and no labels are applied. Recommended trigger is types: [opened].
  • Validate PR - Skip validation for PRs with fewer than 100 lines changed, excluding common lock files (Cargo.lock, yarn.lock, package-lock.json, Pipfile.lock, etc.). Tiny PRs no longer go through the issue-discussion loop.
  • Add validate-pr composite action for validating non-maintainer PRs against contribution guidelines (#153)

Fixes

  • Complete script injection hardening across all actions: move remaining step outputs to env vars, validate Danger version against semver (#152)
  • Updater - Trigger CI for new PRs without changelog updates (#166)
  • Updater - Select the first branch when multiple branches point at HEAD (#165)

Dependencies

3.3.0

Features

  • Updater - Support CMake GIT_TAG with variable references like ${FOO_REF}, resolving and updating the corresponding set() definition (#149)

3.2.1

Fixes

  • Sentry-CLI integration test action - Accept chunked ProGuard uploads for compatibility with Sentry CLI 3.x (#140)

... (truncated)

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/validate-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml index f3c9f955547f..0c19a310e8dc 100644 --- a/.github/workflows/validate-pr.yml +++ b/.github/workflows/validate-pr.yml @@ -10,7 +10,7 @@ jobs: permissions: pull-requests: write steps: - - uses: getsentry/github-workflows/validate-pr@36c729264d2edc29ebae61950c50e1e9f043ad7e + - uses: getsentry/github-workflows/validate-pr@01bcb7fe69bb48bacd83e675840e3dc95b33c730 with: app-id: ${{ vars.SDK_MAINTAINER_BOT_APP_ID }} private-key: ${{ secrets.SDK_MAINTAINER_BOT_PRIVATE_KEY }} From dcf06e93b1c09446a09bb9b81db661fe767f1b99 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:26:51 +0100 Subject: [PATCH 071/169] chore(deps): Enable pnpm autoDedupe (#25961) **Why:** Dependabot updates `pnpm-lock.yaml` without deduping it, so `Check lockfile` (`pnpm dedupe --check`) fails on most of its PRs. With [`autoDedupe`](https://pnpm.io/settings/dependency-resolution#autodedupe) (pnpm 12.6+), `pnpm install`/`add`/`update` dedupe whenever they write the lockfile. Dependabot runs `pnpm update --lockfile-only -r` plus `pnpm install --lockfile-only` with the pinned pnpm and this `pnpm-workspace.yaml`, so its lockfiles should come out deduped already. I reproduced this with pnpm 12.11.2 on a small workspace: without the setting, those commands left a duplicate and `pnpm dedupe --check` failed; with it, the check passed. **How to review:** Just the setting. Running `pnpm install --lockfile-only` with it on develop leaves the lockfile unchanged, and `pnpm dedupe --check` passes. **Unsure about:** Local `pnpm install`/`pnpm add` now dedupe the whole workspace too, so lockfile diffs can be bigger. I'd keep the #25911 dedupe workflow as a backstop until a few Dependabot PRs pass `Check lockfile` without its extra commit. Open Dependabot PRs only pick this up after a rebase. via @msonnb. -- [View Session](https://junior-prod.sentry.dev/conversations/slack%3AC02TB1YBNGJ%3A1793371893.509089) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC02TB1YBNGJ%3A1793371893.509089/?project=4510944073809921) Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Martin Sonnberger <26038247+msonnb@users.noreply.github.com> --- pnpm-workspace.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 69f59d9ab5e3..42cc03476a2d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -69,6 +69,8 @@ strictPeerDependencies: false # The packages depend on each other by exact version, not with `workspace:`. # This links them from the workspace instead of installing them from npm. linkWorkspacePackages: true +# Lockfiles written by Dependabot or `pnpm add` would otherwise fail `pnpm dedupe --check` in CI. +autoDedupe: true overrides: nx>minimatch: 10.2.5 From a3dae70c311447963d73c33d05c6a54e939ea880 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:47:25 +0100 Subject: [PATCH 072/169] chore(deps-dev): Bump tsx from 4.23.0 to 4.23.15 (#25958) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [tsx](https://github.com/privatenumber/tsx) from 4.23.0 to 4.23.15.
Release notes

Sourced from tsx's releases.

v4.23.15

4.23.15 (2026-09-20)

Bug Fixes

  • exclude bare builtins from namespace inheritance (38e1588)
  • expose require.cache and require.extensions to tsImport CommonJS modules (2da3407)
  • make namespaced register() overloads portable for declaration emit (562c434)

This release is also available on:

v4.23.14

4.23.14 (2026-09-20)

Bug Fixes

  • restore the CJS bridge namespace for Node 24 require(esm) under tsImport() (#802) (6e5236b)

This release is also available on:

v4.23.13

4.23.13 (2026-08-30)

Bug Fixes

  • cache: bound shared transform cache memory (#835) (28e1f12)

This release is also available on:

v4.23.12

4.23.12 (2026-08-10)

Bug Fixes

  • shim import.meta when tokens are split by comments or newlines (#829) (ed9d330), closes #828

This release is also available on:

... (truncated)

Commits
  • ca66105 test: fix drive-less file URLs in ESM resolver fixtures
  • 2da3407 fix: expose require.cache and require.extensions to tsImport CommonJS modules
  • 38e1588 fix: exclude bare builtins from namespace inheritance
  • 562c434 fix: make namespaced register() overloads portable for declaration emit
  • edfb1f0 build: upgrade pkgroll and externalize CJS loader reference
  • 70e7828 test: upgrade tinyspy for disposable API
  • 9ed2022 ci: avoid duplicate release notifications
  • 872e77f refactor: use disposables for cleanup
  • 6e5236b fix: restore the CJS bridge namespace for Node 24 require(esm) under tsImport...
  • 28e1f12 fix(cache): bound shared transform cache memory (#835)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 715 ++++++++++++++++++++++++------------------------- 2 files changed, 349 insertions(+), 368 deletions(-) diff --git a/package.json b/package.json index 7e027c9b17a1..dde3b52dcf3a 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "rollup-plugin-license": "^3.7.1", "size-limit": "~12.1.0", "tslib": "^2.4.1", - "tsx": "^4.23.0", + "tsx": "^4.23.15", "typescript": "~7.0.2", "vitest": "^4.1.11", "webpack": "^5.112.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4bdc4bbc393f..cb7ffdabb2c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -268,14 +268,14 @@ importers: specifier: ^2.4.1 version: 2.8.1 tsx: - specifier: ^4.23.0 - version: 4.23.0 + specifier: ^4.23.15 + version: 4.23.15 typescript: specifier: ~7.0.2 version: 7.0.2 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) webpack: specifier: ^5.112.2 version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) @@ -385,7 +385,7 @@ importers: version: 1.2.9 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) dev-packages/bundler-plugin-integration-tests: dependencies: @@ -404,7 +404,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) dev-packages/bundler-tests: dependencies: @@ -419,10 +419,10 @@ importers: version: 4.62.3 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) webpack: specifier: ^5.112.2 version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) @@ -452,7 +452,7 @@ importers: version: 1.4.21(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@4.6.7) '@langchain/openai': specifier: ^1.6.2 - version: 1.6.2(@aws-sdk/credential-provider-node@3.972.90)(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3) + version: 1.6.2(@aws-sdk/credential-provider-node@3.972.89)(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3) '@langchain/typesafe': specifier: ^0.0.2 version: 0.0.2(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(langchain@1.5.16(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(ws@8.21.3)) @@ -486,7 +486,7 @@ importers: devDependencies: '@cloudflare/vite-plugin': specifier: 1.34.0 - version: 1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1)) + version: 1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1)) '@cloudflare/workers-types': specifier: ^4.20260426.0 version: 4.20260519.1 @@ -507,10 +507,10 @@ importers: version: 6.15.0(magicast@0.3.5)(typescript@7.0.2) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) wrangler: specifier: 4.86.0 version: 4.86.0(@cloudflare/workers-types@4.20260519.1) @@ -538,7 +538,7 @@ importers: version: 0.30.0 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) dev-packages/e2e-tests: devDependencies: @@ -562,7 +562,7 @@ importers: version: 5.5.0(graphql@16.12.0)(supports-color@10.0.0) '@aws-sdk/client-bedrock-runtime': specifier: ^3.1046.0 - version: 3.1159.0 + version: 3.1127.0 '@aws-sdk/client-dynamodb': specifier: ^3.1046.0 version: 3.1068.0 @@ -592,10 +592,10 @@ importers: version: 1.4.0 '@earendil-works/pi-ai': specifier: ^1.4.0 - version: 1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + version: 1.4.0(@aws-sdk/credential-provider-node@3.972.89)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) '@earendil-works/pi-durable': specifier: ^1.4.0 - version: 1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + version: 1.4.0(@aws-sdk/credential-provider-node@3.972.89)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) '@flue/runtime': specifier: 2.0.3 version: 2.0.3(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)(typescript@7.0.2)(ws@8.21.3)(zod@4.6.7) @@ -646,7 +646,7 @@ importers: version: 6.15.0(prisma@6.15.0(magicast@0.3.5)(typescript@7.0.2))(typescript@7.0.2) '@prisma/orm-postgres': specifier: 8.0.0-rc.17 - version: 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + version: 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@sentry/aws-serverless': specifier: 11.10.0 version: link:../../packages/aws-serverless @@ -1006,7 +1006,7 @@ importers: version: 4.16.19(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(typescript@7.0.2) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) packages/aws-serverless: dependencies: @@ -1164,7 +1164,7 @@ importers: version: 1.2.11 vitest: specifier: ^4.1.11 - version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@8.1.1))(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@8.1.1))(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) webpack: specifier: 5.112.2 version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) @@ -1204,7 +1204,7 @@ importers: version: 8.18.0 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) wrangler: specifier: 4.61.0 version: 4.61.0(@cloudflare/workers-types@5.20260710.1) @@ -1338,7 +1338,7 @@ importers: version: 4.6.3(@glint/template@1.8.0)(supports-color@10.0.0) '@embroider/vite': specifier: ^1.7.2 - version: 1.7.9(@embroider/core@4.6.3(@glint/template@1.8.0)(supports-color@10.0.0))(@glint/template@1.8.0)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + version: 1.7.9(@embroider/core@4.6.3(@glint/template@1.8.0)(supports-color@10.0.0))(@glint/template@1.8.0)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@glint/ember-tsc': specifier: ^1.5.0 version: 1.10.0(ember-source@6.12.0(rsvp@4.8.5)(supports-color@10.0.0))(supports-color@10.0.0)(typescript@5.8.3) @@ -1353,7 +1353,7 @@ importers: version: 5.8.3 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) packages/esbuild-plugin: dependencies: @@ -1666,7 +1666,7 @@ importers: version: 2.0.1-rc.22(crossws@0.4.10(srvx@0.11.22)) nitro: specifier: ^3.0.260415-beta - version: 3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + version: 3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) srvx: specifier: ^0.11.22 version: 0.11.22 @@ -1728,7 +1728,7 @@ importers: dependencies: '@nuxt/kit': specifier: ^4.6.0 - version: 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) + version: 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) '@sentry/browser': specifier: 11.10.0 version: link:../browser @@ -1762,7 +1762,7 @@ importers: version: 4.20260519.1 '@nuxt/nitro-server': specifier: ^3.21.11 - version: 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + version: 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(6d4a009eb46e35c3af8bc639a43780f2))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) '@nuxt/schema': specifier: ^3.21.11 version: 3.21.11 @@ -1777,22 +1777,22 @@ importers: version: 1.15.11 nitro: specifier: ^3.0.260311-beta - version: 3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + version: 3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) nitropack: specifier: ^2.13.4 - version: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + version: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) nuxi: specifier: ^3.25.1 version: 3.25.1 nuxt: specifier: 3.21.11 - version: 3.21.11(51972c16778dadf3035cea341382f655) + version: 3.21.11(6d4a009eb46e35c3af8bc639a43780f2) unstorage: specifier: ^1.17.5 version: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vue: specifier: ^3.5.41 version: 3.5.44(typescript@7.0.2) @@ -1943,7 +1943,7 @@ importers: devDependencies: '@react-router/dev': specifier: ^7.17.0 - version: 7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0) + version: 7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0) '@react-router/node': specifier: ^7.15.0 version: 7.18.1(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3) @@ -1967,7 +1967,7 @@ importers: version: 7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) packages/remix: dependencies: @@ -2040,7 +2040,7 @@ importers: version: 7.8.5 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) packages/replay-canvas: dependencies: @@ -2153,7 +2153,7 @@ importers: devDependencies: '@apm-js-collab/code-transformer-bundler-plugins': specifier: ^0.7.5 - version: 0.7.5(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + version: 0.7.5(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) '@neondatabase/serverless': specifier: 1.2.0 version: 1.2.0 @@ -2168,7 +2168,7 @@ importers: version: 6.1.4 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) webpack: specifier: ^5.112.2 version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) @@ -2211,10 +2211,10 @@ importers: version: 1.9.11 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + version: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) packages/solidstart: dependencies: @@ -2257,7 +2257,7 @@ importers: version: 1.0.0(solid-js@1.9.11) '@solidjs/start': specifier: ^1.3.2 - version: 1.3.2(a3d6e0cbce53af562c34fef3544b2a36) + version: 1.3.2(8e43603e153b7398bba7d0637cae96a5) '@solidjs/testing-library': specifier: 0.8.5 version: 0.8.5(@solidjs/router@1.0.0(solid-js@1.9.11))(solid-js@1.9.11) @@ -2272,7 +2272,7 @@ importers: version: 20.19.43 nitropack: specifier: ^2.13.4 - version: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + version: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) node-mock-http: specifier: ^1.0.4 version: 1.0.4 @@ -2281,13 +2281,13 @@ importers: version: 1.9.11 vinxi: specifier: ^0.5.11 - version: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) + version: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vite-plugin-solid: specifier: ^2.11.6 - version: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + version: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) packages/svelte: dependencies: @@ -2309,7 +2309,7 @@ importers: version: link:../../dev-packages/rollup-utils '@sveltejs/vite-plugin-svelte': specifier: 1.4.0 - version: 1.4.0(supports-color@10.0.0)(svelte@3.59.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + version: 1.4.0(supports-color@10.0.0)(svelte@3.59.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@testing-library/svelte': specifier: ^3.2.1 version: 3.2.1(svelte@3.59.2) @@ -2318,7 +2318,7 @@ importers: version: 3.59.2 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) packages/sveltekit: dependencies: @@ -2370,10 +2370,10 @@ importers: version: link:../browser '@sveltejs/kit': specifier: ^2.70.2 - version: 2.70.2(@opentelemetry/api@1.10.0)(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)))(svelte@4.2.8)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + version: 2.70.2(@opentelemetry/api@1.10.0)(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)))(svelte@4.2.8)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@sveltejs/vite-plugin-svelte': specifier: ^3.0.0 - version: 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + version: 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@types/node': specifier: ^20.19.0 version: 20.19.43 @@ -2382,7 +2382,7 @@ importers: version: 4.2.8 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) packages/tanstackstart-react: dependencies: @@ -2422,7 +2422,7 @@ importers: version: 20.19.43 vite: specifier: ^8.3.1 - version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) packages/typescript: {} @@ -2917,10 +2917,6 @@ packages: resolution: {integrity: sha512-IDl/lrPb90aH+pZFHGNDmgH9nAUQj5PlZH1sJ3w7RikctyjHSnY3oNjZhrLoaBoQn/rNK0zsP6OHEqEhj2tdLA==} engines: {node: '>=20.0.0'} - '@aws-sdk/client-bedrock-runtime@3.1159.0': - resolution: {integrity: sha512-Zj744tR9wNs+jjvb7C8zDBDxjJhGJSMwIHnOc9zdsDdrRirai4CtR5iQ5BMP1qfazjH6jYKCxGpZC1BOUnslKg==} - engines: {node: '>=20.0.0'} - '@aws-sdk/client-cognito-identity@3.980.0': resolution: {integrity: sha512-nLgMW2drTzv+dTo3ORCcotQPcrUaTQ+xoaDTdSaUXdZO7zbbVyk7ysE5GDTnJdZWcUjHOSB8xfNQhOTTNVPhFw==} engines: {node: '>=20.0.0'} @@ -2961,44 +2957,44 @@ packages: resolution: {integrity: sha512-hatCfVf61RsSO5Qjrf7JKln4KiPDprXNJhpx9DNOmyeW1v+i7KNhbawk4d1wAnQ72YciOfhAhyRFec6UZqcnzQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.978.7': - resolution: {integrity: sha512-QsYdn5xhvEBzcDC6bgKuvPv87kFtXK/3f5NqKfR+eok5AAn7RaZnkgBKncX+UW3ZMcjSELyAMATN0iqgZmvWVg==} + '@aws-sdk/core@3.978.6': + resolution: {integrity: sha512-ykF/MiYoM15dgZwF/WLgjJXVbAK2wU6vjPigeEnOHwyUJ0rdlraO5uekecKNVPkvOnz+oju/5zljViQs1nI1FQ==} engines: {node: '>=20.0.0'} '@aws-sdk/credential-provider-cognito-identity@3.972.3': resolution: {integrity: sha512-dW/DqTk90XW7hIngqntAVtJJyrkS51wcLhGz39lOMe0TlSmZl+5R/UGnAZqNbXmWuJHLzxe+MLgagxH41aTsAQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.78': - resolution: {integrity: sha512-d8V47OxkM/FKe7bVfk5Rga4aNGUaQNk3ip1PFIYhxtup9r8SuNIHLs4HfBAmHT25ZbzowluMHp833UXfFMQbhA==} + '@aws-sdk/credential-provider-env@3.972.77': + resolution: {integrity: sha512-CN6dQCDo32a8n2gvm2HBeqGV5BNI9noG96nTj5bGO2HWtt0Rucu/A8pcnJIqkzILOHSuilnCk0Do8/7h6SOXhQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.80': - resolution: {integrity: sha512-OQqanXiRhnl/wBIJc3TtA7TYmnN7VO0mP95eVoHsLXtPwHz6HeccMpi7cqtnhHw+K7p6VIoAt2IK4M9Ixd9uGg==} + '@aws-sdk/credential-provider-http@3.972.79': + resolution: {integrity: sha512-Netsu0nnmvP2HiN1ZFQBeUu+JehQIuubqz+j2kHDgeSE4HDzniCpIZ7RzoWABq+LTcbCi9W03hywifUagoVIWQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.973.23': - resolution: {integrity: sha512-pTTRK7xoi8NNbrcjVUSd9YdMTUxiWEPfk8newBQCzV+VS5STh5sDPAy3iO29fSqipqNuCpILJcD6wAawxHvG+w==} + '@aws-sdk/credential-provider-ini@3.973.22': + resolution: {integrity: sha512-x7/5h58cqOoZR0RWUZC7v/cJOADQR42iSIQkvU+00HVHR4jh8ZpqEgRPGf2zei59iH5qP40lL3bkq49PuWl71g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.85': - resolution: {integrity: sha512-UHjnTeS+5OBFfRL+BQwCGJPJvnzjhGD6dIwncBJDa+ziyQjPUte9Q0I4BCkybGrHotnyiiluXj13bgelP14J5Q==} + '@aws-sdk/credential-provider-login@3.972.84': + resolution: {integrity: sha512-G9Ty1uIV0laEvEp2KiMbEhsuBAjgaD3KYrwVjAM5VUxpcIuPBqQI5riA7/qzrnSgYiAKQbubbgkwcqCICKCIxg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.90': - resolution: {integrity: sha512-+McgOQuAUze95AumVmoOBRp++Z8IjsZ0qOASb6+igHdKNzA0FiIf5kbTFaZ64txfd9KTBDpgvlEDGQE41lSq/A==} + '@aws-sdk/credential-provider-node@3.972.89': + resolution: {integrity: sha512-O8xj7/sanrnXfFHu/LUujVBiiwn5mknYenU7xt1w6+yvhR1QoDZWXLFh1gLITaFVPtb1jWgEirC+kFNw+//tyw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.78': - resolution: {integrity: sha512-bzB92SriflYf6LVRJcDv/a9I87tmkdmdE8hAqT1oRGKFkEH2rxxHeHZVK/4vsiRsFi44cUj0J9+bUSmqYX+26w==} + '@aws-sdk/credential-provider-process@3.972.77': + resolution: {integrity: sha512-sWA8Jm+ASzkTxoqLizHWRbt8Gj3yZCmZ0hYdvg0uIxuLzWseYcfrDFzaSnkRWlEWSJV38rAWFimdrN0J4NR7Pg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.973.22': - resolution: {integrity: sha512-MXUkWHgcf6G+TowL+QPvn4kJTEYKd9qHKMV6eNGXSA5lilDgGRrP3EXPHChZ3eZ7z+oprn/ZvOovLoS2WR3D8g==} + '@aws-sdk/credential-provider-sso@3.973.21': + resolution: {integrity: sha512-KV2LM8fbYUhDzGEBnPwb+ISNfNlhmnpLS+y6A6VsJpg1EI9aUJ0+dRTxh2GrGdneFH0T4TiuboHxz8i6+M38JA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.84': - resolution: {integrity: sha512-6I3zYcDHt5ZM0J4gEuRgCHgN505k5haUiiPM46iIQgrHm2p70zpnssHFVYqHYfgJe3Mf+a5+nsF3BZmW4xcZNg==} + '@aws-sdk/credential-provider-web-identity@3.972.83': + resolution: {integrity: sha512-iekYilmo2Re51fC7gMkEfZ7jFDXwehSCmXjDESaYQsdgYhsLQFG7rTYr3GBvRIyj+Fkda2NxbZX7hAZDaFD0JQ==} engines: {node: '>=20.0.0'} '@aws-sdk/credential-providers@3.983.0': @@ -3021,8 +3017,8 @@ packages: resolution: {integrity: sha512-1vKJt/6MBB/MBMRM3qzCMdW70syJY8u2DH+dq7yCnPn7wVJmyeAzAa/sK1lIbbYh8BVLbM5FspsT4zbe885gOw==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-eventstream@3.972.35': - resolution: {integrity: sha512-JAyjfoYxHCXNnCXtbTviEpb1+gxe8tsWQ3URwZIT1Bqhrm4fgDDuCA72n4HVnHMNutBhZLXnYgc97ehkhfGpMA==} + '@aws-sdk/middleware-eventstream@3.972.34': + resolution: {integrity: sha512-0IUoHM9N75uMaf70Tj2l+qmOSNw8HaDCsoghPRnewMaaPJjdSxzFaCpPBFDmSWuSlc0gBgv4CsvvvPGoBAD4Bg==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-flexible-checksums@3.974.30': @@ -3053,24 +3049,24 @@ packages: resolution: {integrity: sha512-iz+B29TXcAZsJpwB+AwG/TTGA5l/VnmMZ2UxtiySOZjI6gCdmviXPwdgzcmuazMy16rXoPY4mYCGe7zdNKfx5A==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-websocket@3.972.60': - resolution: {integrity: sha512-IgxzXK1c0r/2yjC/qJc06OZPzQ3FSxuIc/Au6chqMb0H60k1+sHIOgjcqsxl1BxUTGQr//BU5A3niytx0IvICA==} + '@aws-sdk/middleware-websocket@3.972.59': + resolution: {integrity: sha512-lpLmEPrmRSG1ev+LqxkOn1JfVGclnS9b5hvGaSoBgS0JxW2u3QXsJuu+KTHvhdFrRohsqzl70cuiHhyUHFSjYQ==} engines: {node: '>= 14.0.0'} '@aws-sdk/nested-clients@3.983.0': resolution: {integrity: sha512-4bUzDkJlSPwfegO23ZSBrheuTI8UyAgNzptm1K6fZAIOIc1vnFl12TonecbssAfmM0/UdyTn5QDomwEfIdmJkQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.52': - resolution: {integrity: sha512-/bNl6b3VEoWQg3Z8yHt3gzYjSFoOOv7+eUglzITmGP8H7jovN8c9NoOI+6bU25+lSLvyRpJOs4n8YSaX+BcUPg==} + '@aws-sdk/nested-clients@3.997.51': + resolution: {integrity: sha512-DJK4iGIHWU8Jh8ODMvqFYcX9yGBMXV4tSZAvqlHF5EDd8hbmF3eFO9KIWOnA7wcQjPzEkyMMW1/FgZ9RU8oimQ==} engines: {node: '>=20.0.0'} '@aws-sdk/region-config-resolver@3.972.13': resolution: {integrity: sha512-CvJ2ZIjK/jVD/lbOpowBVElJyC1YxLTIJ13yM0AEo0t2v7swOzGjSA6lJGH+DwZXQhcjUjoYwc8bVYCX5MDr1A==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.50': - resolution: {integrity: sha512-Ob9GCZ3YnGPws696zTYW/rLFYHa9QLh6DgTCnkHb+0jhHNBgHWM8Ui2b+yrIjkfDsRVsglyrOey6tOOG/VWxMA==} + '@aws-sdk/signature-v4-multi-region@3.996.49': + resolution: {integrity: sha512-nvuAZNRrRRVCiARRKsYlQ0VnXiEQiq7vXPH+UA+LSo+kN3Djl9eo/V2r0udkw85PnsNshmv2Em1clld5M3j67w==} engines: {node: '>=20.0.0'} '@aws-sdk/token-providers@3.1048.0': @@ -3081,8 +3077,8 @@ packages: resolution: {integrity: sha512-Dv2TMWBshJ+tF6ahs2Sy5bh4Iabsd4GAQqVvE9XZmYmnoaVbpS2QKIKE/HRacc7bTtbjEEvP+laGzHvHlf1CiQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1159.0': - resolution: {integrity: sha512-IJvghXXcEpqTWGjrjJwxBdEecHeXp+t9FaybRf8Zf0wYW84KN1snXBsYOdfDBBx78ChwfJxH8KIHqmhNZuDLcw==} + '@aws-sdk/token-providers@3.1157.0': + resolution: {integrity: sha512-AeAzcwWRP2uANO2TjFpKjRdvOCzgfIu6YQl9Ceibd6E0gnq5dfOzujFnq2aeqgbLg4wRtXbUZE4gekCCPrz/OQ==} engines: {node: '>=20.0.0'} '@aws-sdk/types@3.975.0': @@ -3117,8 +3113,8 @@ packages: aws-crt: optional: true - '@aws-sdk/xml-builder@3.972.42': - resolution: {integrity: sha512-smX8/gNP8Kjq+CWTBMZQLQ912vOQjiWExzwzvMO+eujPB5RY8IbtF0AGdUUXHKN7KoDWK9dIcuW0WG269kiP6w==} + '@aws-sdk/xml-builder@3.972.41': + resolution: {integrity: sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==} engines: {node: '>=20.0.0'} '@aws/lambda-invoke-store@0.2.3': @@ -18423,8 +18419,8 @@ packages: peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - tsx@4.23.0: - resolution: {integrity: sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==} + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} engines: {node: '>=18.0.0'} hasBin: true @@ -20247,7 +20243,7 @@ snapshots: optionalDependencies: zod: 4.6.7 - '@apm-js-collab/code-transformer-bundler-plugins@0.7.5(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': + '@apm-js-collab/code-transformer-bundler-plugins@0.7.5(esbuild@0.28.1)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: '@apm-js-collab/code-transformer': 0.19.0 es-module-lexer: 2.1.0 @@ -20256,7 +20252,7 @@ snapshots: optionalDependencies: esbuild: 0.28.1 rollup: 4.62.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) '@apm-js-collab/code-transformer@0.19.0': @@ -20494,7 +20490,7 @@ snapshots: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.978.7 + '@aws-sdk/core': 3.978.6 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 @@ -20504,11 +20500,11 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/eventstream-handler-node': 3.972.39 - '@aws-sdk/middleware-eventstream': 3.972.35 - '@aws-sdk/middleware-websocket': 3.972.60 + '@aws-sdk/middleware-eventstream': 3.972.34 + '@aws-sdk/middleware-websocket': 3.972.59 '@aws-sdk/token-providers': 3.1048.0 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 @@ -20519,11 +20515,11 @@ snapshots: '@aws-sdk/client-bedrock-runtime@3.1127.0': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/eventstream-handler-node': 3.972.39 - '@aws-sdk/middleware-eventstream': 3.972.35 - '@aws-sdk/middleware-websocket': 3.972.60 + '@aws-sdk/middleware-eventstream': 3.972.34 + '@aws-sdk/middleware-websocket': 3.972.59 '@aws-sdk/token-providers': 3.1127.0 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 @@ -20532,27 +20528,12 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/client-bedrock-runtime@3.1159.0': - dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 - '@aws-sdk/eventstream-handler-node': 3.972.39 - '@aws-sdk/middleware-eventstream': 3.972.35 - '@aws-sdk/middleware-websocket': 3.972.60 - '@aws-sdk/token-providers': 3.1159.0 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/fetch-http-handler': 5.8.0 - '@smithy/node-http-handler': 4.13.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - '@aws-sdk/client-cognito-identity@3.980.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 @@ -20596,8 +20577,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 @@ -20641,8 +20622,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/dynamodb-codec': 3.973.20 '@aws-sdk/middleware-endpoint-discovery': 3.972.18 '@aws-sdk/types': 3.975.0 @@ -20654,8 +20635,8 @@ snapshots: '@aws-sdk/client-kinesis@3.1146.0': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 @@ -20667,8 +20648,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 @@ -20681,11 +20662,11 @@ snapshots: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/middleware-flexible-checksums': 3.974.30 '@aws-sdk/middleware-sdk-s3': 3.972.51 - '@aws-sdk/signature-v4-multi-region': 3.996.50 + '@aws-sdk/signature-v4-multi-region': 3.996.49 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 @@ -20697,8 +20678,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 @@ -20710,8 +20691,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 @@ -20723,8 +20704,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 @@ -20736,8 +20717,8 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 '@aws-sdk/middleware-sdk-sqs': 3.972.30 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 @@ -20746,10 +20727,10 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/core@3.978.7': + '@aws-sdk/core@3.978.6': dependencies: '@aws-sdk/types': 3.975.0 - '@aws-sdk/xml-builder': 3.972.42 + '@aws-sdk/xml-builder': 3.972.41 '@aws/lambda-invoke-store': 0.3.0 '@smithy/core': 3.37.0 '@smithy/signature-v4': 5.7.4 @@ -20768,17 +20749,17 @@ snapshots: - aws-crt optional: true - '@aws-sdk/credential-provider-env@3.972.78': + '@aws-sdk/credential-provider-env@3.972.77': dependencies: - '@aws-sdk/core': 3.978.7 + '@aws-sdk/core': 3.978.6 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.80': + '@aws-sdk/credential-provider-http@3.972.79': dependencies: - '@aws-sdk/core': 3.978.7 + '@aws-sdk/core': 3.978.6 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 @@ -20786,67 +20767,67 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.973.23': + '@aws-sdk/credential-provider-ini@3.973.22': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/credential-provider-env': 3.972.78 - '@aws-sdk/credential-provider-http': 3.972.80 - '@aws-sdk/credential-provider-login': 3.972.85 - '@aws-sdk/credential-provider-process': 3.972.78 - '@aws-sdk/credential-provider-sso': 3.973.22 - '@aws-sdk/credential-provider-web-identity': 3.972.84 - '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-env': 3.972.77 + '@aws-sdk/credential-provider-http': 3.972.79 + '@aws-sdk/credential-provider-login': 3.972.84 + '@aws-sdk/credential-provider-process': 3.972.77 + '@aws-sdk/credential-provider-sso': 3.973.21 + '@aws-sdk/credential-provider-web-identity': 3.972.83 + '@aws-sdk/nested-clients': 3.997.51 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/credential-provider-imds': 4.5.2 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-login@3.972.85': + '@aws-sdk/credential-provider-login@3.972.84': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-node@3.972.90': + '@aws-sdk/credential-provider-node@3.972.89': dependencies: - '@aws-sdk/credential-provider-env': 3.972.78 - '@aws-sdk/credential-provider-http': 3.972.80 - '@aws-sdk/credential-provider-ini': 3.973.23 - '@aws-sdk/credential-provider-process': 3.972.78 - '@aws-sdk/credential-provider-sso': 3.973.22 - '@aws-sdk/credential-provider-web-identity': 3.972.84 + '@aws-sdk/credential-provider-env': 3.972.77 + '@aws-sdk/credential-provider-http': 3.972.79 + '@aws-sdk/credential-provider-ini': 3.973.22 + '@aws-sdk/credential-provider-process': 3.972.77 + '@aws-sdk/credential-provider-sso': 3.973.21 + '@aws-sdk/credential-provider-web-identity': 3.972.83 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/credential-provider-imds': 4.5.2 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-process@3.972.78': + '@aws-sdk/credential-provider-process@3.972.77': dependencies: - '@aws-sdk/core': 3.978.7 + '@aws-sdk/core': 3.978.6 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.973.22': + '@aws-sdk/credential-provider-sso@3.973.21': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/nested-clients': 3.997.52 - '@aws-sdk/token-providers': 3.1159.0 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 + '@aws-sdk/token-providers': 3.1157.0 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.972.84': + '@aws-sdk/credential-provider-web-identity@3.972.83': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 @@ -20855,16 +20836,16 @@ snapshots: '@aws-sdk/credential-providers@3.983.0': dependencies: '@aws-sdk/client-cognito-identity': 3.983.0 - '@aws-sdk/core': 3.978.7 + '@aws-sdk/core': 3.978.6 '@aws-sdk/credential-provider-cognito-identity': 3.972.3 - '@aws-sdk/credential-provider-env': 3.972.78 - '@aws-sdk/credential-provider-http': 3.972.80 - '@aws-sdk/credential-provider-ini': 3.973.23 - '@aws-sdk/credential-provider-login': 3.972.85 - '@aws-sdk/credential-provider-node': 3.972.90 - '@aws-sdk/credential-provider-process': 3.972.78 - '@aws-sdk/credential-provider-sso': 3.973.22 - '@aws-sdk/credential-provider-web-identity': 3.972.84 + '@aws-sdk/credential-provider-env': 3.972.77 + '@aws-sdk/credential-provider-http': 3.972.79 + '@aws-sdk/credential-provider-ini': 3.973.22 + '@aws-sdk/credential-provider-login': 3.972.84 + '@aws-sdk/credential-provider-node': 3.972.89 + '@aws-sdk/credential-provider-process': 3.972.77 + '@aws-sdk/credential-provider-sso': 3.973.21 + '@aws-sdk/credential-provider-web-identity': 3.972.83 '@aws-sdk/nested-clients': 3.983.0 '@aws-sdk/types': 3.975.0 '@smithy/config-resolver': 4.4.17 @@ -20880,7 +20861,7 @@ snapshots: '@aws-sdk/dynamodb-codec@3.973.20': dependencies: - '@aws-sdk/core': 3.978.7 + '@aws-sdk/core': 3.978.6 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -20905,7 +20886,7 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/middleware-eventstream@3.972.35': + '@aws-sdk/middleware-eventstream@3.972.34': dependencies: '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 @@ -20943,8 +20924,8 @@ snapshots: '@aws-sdk/middleware-sdk-s3@3.972.51': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/signature-v4-multi-region': 3.996.50 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/signature-v4-multi-region': 3.996.49 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 @@ -20959,7 +20940,7 @@ snapshots: '@aws-sdk/middleware-user-agent@3.972.38': dependencies: - '@aws-sdk/core': 3.978.7 + '@aws-sdk/core': 3.978.6 '@aws-sdk/types': 3.975.0 '@aws-sdk/util-endpoints': 3.996.8 '@smithy/core': 3.37.0 @@ -20969,9 +20950,9 @@ snapshots: tslib: 2.8.1 optional: true - '@aws-sdk/middleware-websocket@3.972.60': + '@aws-sdk/middleware-websocket@3.972.59': dependencies: - '@aws-sdk/core': 3.978.7 + '@aws-sdk/core': 3.978.6 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 @@ -20983,7 +20964,7 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.978.7 + '@aws-sdk/core': 3.978.6 '@aws-sdk/middleware-host-header': 3.972.10 '@aws-sdk/middleware-logger': 3.972.10 '@aws-sdk/middleware-recursion-detection': 3.972.11 @@ -21023,10 +21004,10 @@ snapshots: - aws-crt optional: true - '@aws-sdk/nested-clients@3.997.52': + '@aws-sdk/nested-clients@3.997.51': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/signature-v4-multi-region': 3.996.50 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/signature-v4-multi-region': 3.996.49 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/fetch-http-handler': 5.8.0 @@ -21043,7 +21024,7 @@ snapshots: tslib: 2.8.1 optional: true - '@aws-sdk/signature-v4-multi-region@3.996.50': + '@aws-sdk/signature-v4-multi-region@3.996.49': dependencies: '@aws-sdk/types': 3.975.0 '@smithy/signature-v4': 5.7.4 @@ -21052,8 +21033,8 @@ snapshots: '@aws-sdk/token-providers@3.1048.0': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 @@ -21061,17 +21042,17 @@ snapshots: '@aws-sdk/token-providers@3.1127.0': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1159.0': + '@aws-sdk/token-providers@3.1157.0': dependencies: - '@aws-sdk/core': 3.978.7 - '@aws-sdk/nested-clients': 3.997.52 + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 '@aws-sdk/types': 3.975.0 '@smithy/core': 3.37.0 '@smithy/types': 4.19.0 @@ -21131,7 +21112,7 @@ snapshots: tslib: 2.8.1 optional: true - '@aws-sdk/xml-builder@3.972.42': + '@aws-sdk/xml-builder@3.972.41': dependencies: '@smithy/types': 4.19.0 tslib: 2.8.1 @@ -22825,12 +22806,12 @@ snapshots: optionalDependencies: workerd: 1.20260426.1 - '@cloudflare/vite-plugin@1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))': + '@cloudflare/vite-plugin@1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))': dependencies: '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260426.1) miniflare: 4.20260426.0 unenv: 2.0.0-rc.24 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) wrangler: 4.86.0(@cloudflare/workers-types@4.20260519.1) ws: 8.18.0 transitivePeerDependencies: @@ -23096,17 +23077,17 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@dxup/nuxt@0.5.6(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(esbuild@0.28.1)(giget@3.3.1)(jiti@2.7.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': + '@dxup/nuxt@0.5.6(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(esbuild@0.28.1)(giget@3.3.1)(jiti@2.7.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: '@dxup/unimport': 0.1.2 - '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) + '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) '@vue/compiler-dom': 3.5.44 chokidar: 5.0.0 knitwork: 1.3.0 magic-string: 1.1.0 pathe: 2.0.3 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) transitivePeerDependencies: - '@farmfe/core' - '@nuxt/schema' @@ -23168,7 +23149,7 @@ snapshots: - ws - zod - '@earendil-works/pi-ai@1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7)': + '@earendil-works/pi-ai@1.4.0(@aws-sdk/credential-provider-node@3.972.89)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7)': dependencies: '@anthropic-ai/sdk': 0.129.0(zod@4.6.7) '@aws-sdk/client-bedrock-runtime': 3.1127.0 @@ -23177,7 +23158,7 @@ snapshots: '@smithy/node-http-handler': 4.12.1 http-proxy-agent: 9.1.0(supports-color@10.0.0) https-proxy-agent: 9.1.0(supports-color@10.0.0) - openai: 7.19.0(@aws-sdk/credential-provider-node@3.972.90)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + openai: 7.19.0(@aws-sdk/credential-provider-node@3.972.89)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) partial-json: 0.1.7 typebox: 1.3.27 transitivePeerDependencies: @@ -23197,10 +23178,10 @@ snapshots: dependencies: quickjs-wasi: 3.6.2 - '@earendil-works/pi-durable@1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7)': + '@earendil-works/pi-durable@1.4.0(@aws-sdk/credential-provider-node@3.972.89)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7)': dependencies: '@earendil-works/chord': 1.4.0 - '@earendil-works/pi-ai': 1.4.0(@aws-sdk/credential-provider-node@3.972.90)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + '@earendil-works/pi-ai': 1.4.0(@aws-sdk/credential-provider-node@3.972.89)(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(@smithy/signature-v4@5.7.4)(supports-color@10.0.0)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) '@earendil-works/pi-codemode': 1.4.0 '@earendil-works/pi-telemetry': 1.4.0 '@silvia-odwyer/photon': 0.3.3 @@ -23230,7 +23211,7 @@ snapshots: '@effect/vitest@4.0.0(effect@4.0.0)(vitest@4.1.11)': dependencies: effect: 4.0.0 - vitest: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vitest: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) '@ember-data/rfc395-data@0.0.4': {} @@ -23398,7 +23379,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@embroider/vite@1.7.9(@embroider/core@4.6.3(@glint/template@1.8.0)(supports-color@10.0.0))(@glint/template@1.8.0)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@embroider/vite@1.7.9(@embroider/core@4.6.3(@glint/template@1.8.0)(supports-color@10.0.0))(@glint/template@1.8.0)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@embroider/core': 4.6.3(@glint/template@1.8.0)(supports-color@10.0.0) @@ -23416,7 +23397,7 @@ snapshots: send: 1.2.1(supports-color@10.0.0) source-map-url: 0.4.1 terser: 5.51.2 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@glint/template' - bufferutil @@ -24793,11 +24774,11 @@ snapshots: transitivePeerDependencies: - ws - '@langchain/openai@1.6.2(@aws-sdk/credential-provider-node@3.972.90)(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)': + '@langchain/openai@1.6.2(@aws-sdk/credential-provider-node@3.972.89)(@langchain/core@1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3))(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)': dependencies: '@langchain/core': 1.2.17(@opentelemetry/api@1.10.0)(openai@5.18.1(ws@8.21.3)(zod@4.6.7))(ws@8.21.3) js-tiktoken: 1.0.21 - openai: 7.31.0(@aws-sdk/credential-provider-node@3.972.90)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) + openai: 7.31.0(@aws-sdk/credential-provider-node@3.972.89)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7) zod: 4.6.7 transitivePeerDependencies: - '@aws-sdk/credential-provider-node' @@ -25121,11 +25102,11 @@ snapshots: '@nuxt/devalue@2.0.2': {} - '@nuxt/devtools-kit@3.4.1(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@nuxt/devtools-kit@3.4.1(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: - '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) + '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) execa: 8.0.1 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@nuxt/schema' - confbox @@ -25149,11 +25130,11 @@ snapshots: pkg-types: 2.3.1 semver: 7.8.5 - '@nuxt/devtools@3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': + '@nuxt/devtools@3.4.1(1d992770f562652d9788c4b38c5dfb97)': dependencies: - '@nuxt/devtools-kit': 3.4.1(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@nuxt/devtools-kit': 3.4.1(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@nuxt/devtools-wizard': 3.4.1 - '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) + '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) '@vue/devtools-core': 8.2.1(vue@3.5.44(typescript@7.0.2)) '@vue/devtools-kit': 8.2.1 birpc: 4.1.0 @@ -25180,9 +25161,9 @@ snapshots: structured-clone-es: 2.0.1 tinyglobby: 0.2.17 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vite-plugin-inspect: 11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) - vite-plugin-vue-tracer: 1.5.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) + vite-plugin-inspect: 11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) + vite-plugin-vue-tracer: 1.5.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) which: 6.0.1 ws: 8.21.3 transitivePeerDependencies: @@ -25246,7 +25227,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))': + '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))': dependencies: consola: 3.4.2 defu: 6.1.7 @@ -25264,7 +25245,7 @@ snapshots: std-env: 4.3.0 tinyexec: 1.3.1 tinyglobby: 0.2.17 - unctx: 3.0.1(magic-string@0.30.21)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) + unctx: 3.0.1(magic-string@0.30.21)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) verkit: 0.5.0 optionalDependencies: '@nuxt/schema': 3.21.11 @@ -25279,7 +25260,7 @@ snapshots: - magic-string - unplugin - '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))': + '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))': dependencies: consola: 3.4.2 defu: 6.1.7 @@ -25297,7 +25278,7 @@ snapshots: std-env: 4.3.0 tinyexec: 1.3.1 tinyglobby: 0.2.17 - unctx: 3.0.1(magic-string@1.1.0)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) + unctx: 3.0.1(magic-string@1.1.0)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) verkit: 0.5.0 optionalDependencies: '@nuxt/schema': 3.21.11 @@ -25312,7 +25293,7 @@ snapshots: - magic-string - unplugin - '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))': + '@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@1.1.0)(mlly@1.8.2)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))': dependencies: consola: 3.4.2 defu: 6.1.7 @@ -25330,7 +25311,7 @@ snapshots: std-env: 4.3.0 tinyexec: 1.3.1 tinyglobby: 0.2.17 - unctx: 3.0.1(magic-string@1.1.0)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) + unctx: 3.0.1(magic-string@1.1.0)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) verkit: 0.5.0 optionalDependencies: '@nuxt/schema': 3.21.11 @@ -25345,7 +25326,7 @@ snapshots: - magic-string - unplugin - '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': + '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(6d4a009eb46e35c3af8bc639a43780f2))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.11(magicast@0.5.4) @@ -25359,11 +25340,11 @@ snapshots: escape-string-regexp: 5.0.0 exsolve: 1.1.3 h3: 1.15.11 - impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.143.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) - nuxt: 3.21.11(51972c16778dadf3035cea341382f655) + nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.143.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + nuxt: 3.21.11(6d4a009eb46e35c3af8bc639a43780f2) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.1 @@ -25419,7 +25400,7 @@ snapshots: - webpack - xml2js - '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': + '@nuxt/nitro-server@3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(6d4a009eb46e35c3af8bc639a43780f2))(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.11(magicast@0.5.4) @@ -25433,11 +25414,11 @@ snapshots: escape-string-regexp: 5.0.0 exsolve: 1.1.3 h3: 1.15.11 - impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) - nuxt: 3.21.11(51972c16778dadf3035cea341382f655) + nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + nuxt: 3.21.11(6d4a009eb46e35c3af8bc639a43780f2) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.1 @@ -25510,12 +25491,12 @@ snapshots: rc9: 3.1.0 std-env: 4.3.0 - '@nuxt/vite-builder@3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0)': + '@nuxt/vite-builder@3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(6d4a009eb46e35c3af8bc639a43780f2))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0)': dependencies: '@nuxt/kit': 3.21.11(magicast@0.5.4) '@rollup/plugin-replace': 6.0.3(rollup@4.62.3) - '@vitejs/plugin-vue': 6.0.8(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) - '@vitejs/plugin-vue-jsx': 5.1.6(supports-color@10.0.0)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) + '@vitejs/plugin-vue': 6.0.8(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) + '@vitejs/plugin-vue-jsx': 5.1.6(supports-color@10.0.0)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) autoprefixer: 10.5.4(postcss@8.5.31) consola: 3.4.2 cssnano: 7.1.9(postcss@8.5.31) @@ -25530,7 +25511,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 3.21.11(51972c16778dadf3035cea341382f655) + nuxt: 3.21.11(6d4a009eb46e35c3af8bc639a43780f2) nypm: 0.6.9 ohash: 2.0.11 pathe: 2.0.3 @@ -25541,9 +25522,9 @@ snapshots: std-env: 4.3.0 ufo: 1.6.4 unenv: 2.0.0-rc.24 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vite-node: 5.3.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vite-plugin-checker: 0.14.5(meow@13.2.0)(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(typescript@7.0.2)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) + vite-node: 5.3.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) + vite-plugin-checker: 0.14.5(meow@13.2.0)(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(typescript@7.0.2)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) vue: 3.5.44(typescript@7.0.2) vue-bundle-renderer: 2.3.2 optionalDependencies: @@ -26277,10 +26258,10 @@ snapshots: dependencies: '@prisma/debug': 6.15.0 - '@prisma/orm-family-sql@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@prisma/orm-family-sql@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@prisma/orm-framework': 8.0.0-rc.17(typescript@7.0.2) - '@prisma/orm-toolchain': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@prisma/orm-toolchain': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@standard-schema/spec': 1.1.0 arktype: 2.2.9 pathe: 2.0.3 @@ -26304,12 +26285,12 @@ snapshots: optionalDependencies: typescript: 7.0.2 - '@prisma/orm-postgres@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@prisma/orm-postgres@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: - '@prisma/orm-family-sql': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@prisma/orm-family-sql': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@prisma/orm-framework': 8.0.0-rc.17(typescript@7.0.2) - '@prisma/orm-target-postgres': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) - '@prisma/orm-toolchain': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@prisma/orm-target-postgres': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) + '@prisma/orm-toolchain': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@types/node': 26.1.2 '@types/pg': 8.20.4 arktype: 2.2.9 @@ -26324,11 +26305,11 @@ snapshots: - pg-native - vite - '@prisma/orm-target-postgres@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@prisma/orm-target-postgres@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: - '@prisma/orm-family-sql': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@prisma/orm-family-sql': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@prisma/orm-framework': 8.0.0-rc.17(typescript@7.0.2) - '@prisma/orm-toolchain': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@prisma/orm-toolchain': 8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@standard-schema/spec': 1.1.0 '@types/node': 26.1.2 '@types/pg': 8.20.4 @@ -26345,7 +26326,7 @@ snapshots: - pg-native - vite - '@prisma/orm-toolchain@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@prisma/orm-toolchain@8.0.0-rc.17(magicast@0.3.5)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@prisma/orm-framework': 8.0.0-rc.17(typescript@7.0.2) '@vercel/detect-agent': 1.2.7 @@ -26368,7 +26349,7 @@ snapshots: wrap-ansi: 10.0.2 optionalDependencies: typescript: 7.0.2 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - magicast @@ -26415,7 +26396,7 @@ snapshots: '@protobufjs/utf8@1.1.1': {} - '@react-router/dev@7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0)': + '@react-router/dev@7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0)': dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@babel/generator': 7.29.8 @@ -26445,8 +26426,8 @@ snapshots: semver: 7.8.5 tinyglobby: 0.2.17 valibot: 1.4.2(typescript@6.0.3) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vite-node: 3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) optionalDependencies: typescript: 6.0.3 wrangler: 4.86.0(@cloudflare/workers-types@4.20260519.1) @@ -27392,11 +27373,11 @@ snapshots: dependencies: solid-js: 1.9.11 - '@solidjs/start@1.3.2(a3d6e0cbce53af562c34fef3544b2a36)': + '@solidjs/start@1.3.2(8e43603e153b7398bba7d0637cae96a5)': dependencies: - '@tanstack/server-functions-plugin': 1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0)) - '@vinxi/server-components': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0)) + '@tanstack/server-functions-plugin': 1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0)) + '@vinxi/server-components': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0)) cookie-es: 2.0.1 defu: 6.1.7 error-stack-parser: 2.1.4 @@ -27408,8 +27389,8 @@ snapshots: source-map-js: 1.2.2 terracotta: 1.0.5(solid-js@1.9.11) tinyglobby: 0.2.17 - vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) - vite-plugin-solid: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) + vite-plugin-solid: 2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - '@testing-library/jest-dom' - solid-js @@ -27464,11 +27445,11 @@ snapshots: dependencies: acorn: 8.18.0 - '@sveltejs/kit@2.70.2(@opentelemetry/api@1.10.0)(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)))(svelte@4.2.8)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/kit@2.70.2(@opentelemetry/api@1.10.0)(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)))(svelte@4.2.8)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.18.0) - '@sveltejs/vite-plugin-svelte': 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@types/cookie': 0.6.0 acorn: 8.18.0 cookie: 0.6.0 @@ -27480,21 +27461,21 @@ snapshots: set-cookie-parser: 3.0.1 sirv: 3.0.2 svelte: 4.2.8 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) optionalDependencies: '@opentelemetry/api': 1.10.0 typescript: 6.0.3 - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)))(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)))(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte': 3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) debug: 4.4.3(supports-color@10.0.0) svelte: 4.2.8 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@1.4.0(supports-color@10.0.0)(svelte@3.59.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@1.4.0(supports-color@10.0.0)(svelte@3.59.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: debug: 4.4.3(supports-color@10.0.0) deepmerge: 4.3.1 @@ -27502,22 +27483,22 @@ snapshots: magic-string: 0.26.7 svelte: 3.59.2 svelte-hmr: 0.15.3(svelte@3.59.2) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vitefu: 0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) + vitefu: 0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)))(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)))(supports-color@10.0.0)(svelte@4.2.8)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) debug: 4.4.3(supports-color@10.0.0) deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.21 svelte: 4.2.8 svelte-hmr: 0.16.0(svelte@4.2.8) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vitefu: 0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) + vitefu: 0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - supports-color @@ -27525,7 +27506,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@tanstack/directive-functions-plugin@1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@tanstack/directive-functions-plugin@1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.29.7(supports-color@10.0.0) @@ -27534,7 +27515,7 @@ snapshots: '@tanstack/router-utils': 1.154.7(supports-color@10.0.0) babel-dead-code-elimination: 1.0.12(supports-color@10.0.0) tiny-invariant: 1.3.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -27571,7 +27552,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/server-functions-plugin@1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@tanstack/server-functions-plugin@1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.29.7(supports-color@10.0.0) @@ -27580,7 +27561,7 @@ snapshots: '@babel/template': 7.29.7 '@babel/traverse': 7.29.8(supports-color@10.0.0) '@babel/types': 7.29.8 - '@tanstack/directive-functions-plugin': 1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@tanstack/directive-functions-plugin': 1.121.21(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) babel-dead-code-elimination: 1.0.12(supports-color@10.0.0) tiny-invariant: 1.3.3 transitivePeerDependencies: @@ -28346,7 +28327,7 @@ snapshots: untun: 0.1.3 uqr: 0.1.3 - '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0))': + '@vinxi/plugin-directives@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0))': dependencies: '@babel/parser': 7.29.9 acorn: 8.18.0 @@ -28357,35 +28338,35 @@ snapshots: magicast: 0.2.11 recast: 0.23.11 tslib: 2.8.1 - vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) + vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) - '@vinxi/server-components@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0))': + '@vinxi/server-components@0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0))': dependencies: - '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0)) + '@vinxi/plugin-directives': 0.5.1(vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0)) acorn: 8.18.0 acorn-loose: 8.4.0 acorn-typescript: 1.4.13(acorn@8.18.0) astring: 1.9.0 magicast: 0.2.11 recast: 0.23.11 - vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) + vinxi: 0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0) - '@vitejs/plugin-vue-jsx@5.1.6(supports-color@10.0.0)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': + '@vitejs/plugin-vue-jsx@5.1.6(supports-color@10.0.0)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.0.0)) '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.0.0))(supports-color@10.0.0) '@rolldown/pluginutils': 1.0.1 '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.7(supports-color@10.0.0))(supports-color@10.0.0) - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vue: 3.5.44(typescript@7.0.2) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@6.0.8(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': + '@vitejs/plugin-vue@6.0.8(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vue: 3.5.44(typescript@7.0.2) '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': @@ -28400,7 +28381,7 @@ snapshots: obug: 2.1.4 std-env: 4.3.0 tinyrainbow: 3.1.1 - vitest: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vitest: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) '@vitest/expect@4.1.11': dependencies: @@ -28411,13 +28392,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.11 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) '@vitest/pretty-format@4.1.11': dependencies: @@ -33247,12 +33228,12 @@ snapshots: import-meta-resolve@4.2.0: {} - impound@1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + impound@1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@jridgewell/trace-mapping': 0.3.31 es-module-lexer: 2.1.0 pathe: 2.0.3 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 transitivePeerDependencies: - '@farmfe/core' @@ -35348,7 +35329,7 @@ snapshots: node-gyp-build: 4.6.0 optional: true - nitro@3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): + nitro@3.0.260311-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.10(srvx@0.11.22) @@ -35369,7 +35350,7 @@ snapshots: giget: 3.3.1 jiti: 2.7.0 rollup: 4.62.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -35401,7 +35382,7 @@ snapshots: - sqlite3 - uploadthing - nitro@3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): + nitro@3.0.260415-beta(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(lru-cache@11.4.0)(miniflare@4.20260426.0)(mongodb@7.1.0(@aws-sdk/credential-providers@3.983.0)(socks@2.8.7))(mysql2@3.23.3(@types/node@20.19.43))(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.10(srvx@0.11.22) @@ -35422,7 +35403,7 @@ snapshots: giget: 3.3.1 jiti: 2.7.0 rollup: 4.62.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -35454,7 +35435,7 @@ snapshots: - sqlite3 - uploadthing - nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.143.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.143.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.3) @@ -35519,7 +35500,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0)) untyped: 2.0.0 @@ -35563,7 +35544,7 @@ snapshots: - vite - webpack - nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.3) @@ -35628,7 +35609,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0)) untyped: 2.0.0 @@ -35672,7 +35653,7 @@ snapshots: - vite - webpack - nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + nitropack@2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.3) @@ -35737,7 +35718,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0)) untyped: 2.0.0 @@ -35997,16 +35978,16 @@ snapshots: nuxi@3.25.1: {} - nuxt@3.21.11(51972c16778dadf3035cea341382f655): + nuxt@3.21.11(6d4a009eb46e35c3af8bc639a43780f2): dependencies: - '@dxup/nuxt': 0.5.6(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(esbuild@0.28.1)(giget@3.3.1)(jiti@2.7.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + '@dxup/nuxt': 0.5.6(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(esbuild@0.28.1)(giget@3.3.1)(jiti@2.7.0)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) '@nuxt/cli': 3.37.0(@nuxt/schema@3.21.11)(@parcel/watcher@2.5.6)(cac@6.7.14)(commander@14.0.3)(magicast@0.5.4)(supports-color@10.0.0) - '@nuxt/devtools': 3.4.1(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@nuxt/schema@3.21.11)(confbox@0.2.4)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(supports-color@10.0.0)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)) + '@nuxt/devtools': 3.4.1(1d992770f562652d9788c4b38c5dfb97) '@nuxt/kit': 3.21.11(magicast@0.5.4) - '@nuxt/nitro-server': 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + '@nuxt/nitro-server': 3.21.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1(supports-color@10.0.0))(magicast@0.5.4)(mysql2@3.23.3(@types/node@20.19.43))(nuxt@3.21.11(6d4a009eb46e35c3af8bc639a43780f2))(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(srvx@0.11.22)(supports-color@10.0.0)(typescript@7.0.2)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) '@nuxt/schema': 3.21.11 '@nuxt/telemetry': 2.8.0(@nuxt/kit@3.21.11(magicast@0.5.4)) - '@nuxt/vite-builder': 3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(51972c16778dadf3035cea341382f655))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0) + '@nuxt/vite-builder': 3.21.11(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(magicast@0.5.4)(meow@13.2.0)(nuxt@3.21.11(6d4a009eb46e35c3af8bc639a43780f2))(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(rolldown@1.2.11)(rollup-plugin-visualizer@6.0.11(rolldown@1.2.11)(rollup@4.62.3))(rollup@4.62.3)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@7.0.2)(vue@3.5.44(typescript@7.0.2))(yaml@2.9.0) '@unhead/vue': 2.1.17(vue@3.5.44(typescript@7.0.2)) '@vue/shared': 3.5.44 c12: 3.3.4(magicast@0.5.4) @@ -36023,7 +36004,7 @@ snapshots: h3: 1.15.11 hookable: 5.5.3 ignore: 7.0.12 - impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) jiti: 2.7.0 klona: 2.0.6 knitwork: 1.3.0 @@ -36050,8 +36031,8 @@ snapshots: ultrahtml: 1.7.0 uncrypto: 0.1.3 unctx: 2.5.0 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.44)(vue-router@4.6.4(vue@3.5.44(typescript@7.0.2)))(vue@3.5.44(typescript@7.0.2)) untyped: 2.0.0 vue: 3.5.44(typescript@7.0.2) @@ -36408,17 +36389,17 @@ snapshots: ws: 8.21.3 zod: 4.6.7 - openai@7.19.0(@aws-sdk/credential-provider-node@3.972.90)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7): + openai@7.19.0(@aws-sdk/credential-provider-node@3.972.89)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7): optionalDependencies: - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/credential-provider-node': 3.972.89 '@smithy/signature-v4': 5.7.4 undici: 7.24.8 ws: 8.21.3 zod: 4.6.7 - openai@7.31.0(@aws-sdk/credential-provider-node@3.972.90)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7): + openai@7.31.0(@aws-sdk/credential-provider-node@3.972.89)(@smithy/signature-v4@5.7.4)(undici@7.24.8)(ws@8.21.3)(zod@4.6.7): optionalDependencies: - '@aws-sdk/credential-provider-node': 3.972.90 + '@aws-sdk/credential-provider-node': 3.972.89 '@smithy/signature-v4': 5.7.4 undici: 7.24.8 ws: 8.21.3 @@ -39879,7 +39860,7 @@ snapshots: tslib: 1.14.1 typescript: 7.0.2 - tsx@4.23.0: + tsx@4.23.15: dependencies: esbuild: 0.28.1 optionalDependencies: @@ -40029,26 +40010,26 @@ snapshots: magic-string: 0.30.21 unplugin: 2.3.11 - unctx@3.0.1(magic-string@0.30.21)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))): + unctx@3.0.1(magic-string@0.30.21)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))): optionalDependencies: magic-string: 0.30.21 oxc-parser: 0.143.0 rolldown: 1.2.11 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) - unctx@3.0.1(magic-string@1.1.0)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))): + unctx@3.0.1(magic-string@1.1.0)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))): optionalDependencies: magic-string: 1.1.0 oxc-parser: 0.143.0 rolldown: 1.2.11 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) - unctx@3.0.1(magic-string@1.1.0)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))): + unctx@3.0.1(magic-string@1.1.0)(oxc-parser@0.152.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))): optionalDependencies: magic-string: 1.1.0 oxc-parser: 0.152.0 rolldown: 1.2.11 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) undefsafe@2.0.5: {} @@ -40118,7 +40099,7 @@ snapshots: dependencies: '@noble/hashes': 2.4.0 - unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.143.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: acorn: 8.18.0 escape-string-regexp: 5.0.0 @@ -40132,7 +40113,7 @@ snapshots: scule: 1.3.0 strip-literal: 4.0.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 optionalDependencies: oxc-parser: 0.143.0 @@ -40147,7 +40128,7 @@ snapshots: - vite - webpack - unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: acorn: 8.18.0 escape-string-regexp: 5.0.0 @@ -40161,7 +40142,7 @@ snapshots: scule: 1.3.0 strip-literal: 4.0.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 optionalDependencies: oxc-parser: 0.152.0 @@ -40176,7 +40157,7 @@ snapshots: - vite - webpack - unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.152.0)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: acorn: 8.18.0 escape-string-regexp: 5.0.0 @@ -40190,7 +40171,7 @@ snapshots: scule: 1.3.0 strip-literal: 4.0.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) unplugin-utils: 0.3.2 optionalDependencies: oxc-parser: 0.152.0 @@ -40317,7 +40298,7 @@ snapshots: picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 - unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 @@ -40326,10 +40307,10 @@ snapshots: esbuild: 0.28.1 rolldown: 1.2.11 rollup: 4.62.3 - vite: 6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) - unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): + unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 @@ -40338,7 +40319,7 @@ snapshots: esbuild: 0.28.1 rolldown: 1.2.11 rollup: 4.62.3 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) webpack: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) unstorage@1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0)): @@ -40521,7 +40502,7 @@ snapshots: '@types/unist': 3.0.0 vfile-message: 4.0.3 - vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0): + vinxi@0.5.11(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(@types/node@20.19.43)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(debug@4.4.3(supports-color@10.0.0))(encoding@0.1.13)(ioredis@5.11.1(supports-color@10.0.0))(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(sass@1.72.0)(srvx@0.11.22)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))(yaml@2.9.0): dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.0.0)) @@ -40542,7 +40523,7 @@ snapshots: hookable: 5.5.3 http-proxy: 1.18.1(debug@4.4.3(supports-color@10.0.0)) micromatch: 4.0.8 - nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) + nitropack: 2.13.4(@azure/storage-blob@12.31.0(supports-color@10.0.0))(@parcel/watcher@2.5.6)(encoding@0.1.13)(mysql2@3.23.3(@types/node@20.19.43))(oxc-parser@0.152.0)(rolldown@1.2.11)(srvx@0.11.22)(supports-color@10.0.0)(vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3)) node-fetch-native: 1.6.7 path-to-regexp: 6.3.0 pathe: 1.1.2 @@ -40555,7 +40536,7 @@ snapshots: unctx: 2.5.0 unenv: 1.10.0 unstorage: 1.17.5(@azure/storage-blob@12.31.0(supports-color@10.0.0))(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.11.1(supports-color@10.0.0)) - vite: 6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) zod: 4.6.7 transitivePeerDependencies: - '@azure/app-configuration' @@ -40608,23 +40589,23 @@ snapshots: - xml2js - yaml - vite-dev-rpc@2.0.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): + vite-dev-rpc@2.0.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: birpc: 4.1.0 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vite-hot-client: 2.2.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) + vite-hot-client: 2.2.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) - vite-hot-client@2.2.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): + vite-hot-client@2.2.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) - vite-node@3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): + vite-node@3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@10.0.0) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -40639,13 +40620,13 @@ snapshots: - tsx - yaml - vite-node@5.3.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): + vite-node@5.3.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: cac: 6.7.14 es-module-lexer: 2.1.0 obug: 2.1.4 pathe: 2.0.3 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -40659,7 +40640,7 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.14.5(meow@13.2.0)(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(typescript@7.0.2)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): + vite-plugin-checker@0.14.5(meow@13.2.0)(optionator@0.9.4)(oxlint@1.89.0(oxlint-tsgolint@7.0.2004))(typescript@7.0.2)(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@babel/code-frame': 7.29.7 chokidar: 5.0.0 @@ -40668,14 +40649,14 @@ snapshots: picomatch: 4.0.7 proper-lockfile: 4.1.2 tiny-invariant: 1.3.3 - vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) optionalDependencies: meow: 13.2.0 optionator: 0.9.4 oxlint: 1.89.0(oxlint-tsgolint@7.0.2004) typescript: 7.0.2 - vite-plugin-inspect@11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): + vite-plugin-inspect@11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))))(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: ansis: 4.3.1 error-stack-parser-es: 1.0.5 @@ -40685,12 +40666,12 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.2 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vite-dev-rpc: 2.0.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) + vite-dev-rpc: 2.0.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) optionalDependencies: - '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) + '@nuxt/kit': 4.6.0(@nuxt/schema@3.21.11)(confbox@0.2.4)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.143.0)(rolldown@1.2.11)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.11)(rollup@4.62.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(webpack@5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3))) - vite-plugin-solid@2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): + vite-plugin-solid@2.11.14(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(supports-color@10.0.0)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@types/babel__core': 7.20.5 @@ -40698,21 +40679,21 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.11 solid-refresh: 0.6.3(solid-js@1.9.11)(supports-color@10.0.0) - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) - vitefu: 1.0.7(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) + vitefu: 1.0.7(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) optionalDependencies: '@testing-library/jest-dom': 6.9.1 transitivePeerDependencies: - supports-color - vite-plugin-vue-tracer@1.5.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)): + vite-plugin-vue-tracer@1.5.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@7.0.2)): dependencies: estree-walker: 3.0.3 exsolve: 1.1.3 magic-string: 1.1.0 pathe: 2.0.3 source-map-js: 1.2.2 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vue: 3.5.44(typescript@7.0.2) vite@5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2): @@ -40729,7 +40710,7 @@ snapshots: stylus: 0.59.0(supports-color@10.0.0) terser: 5.51.2 - vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): + vite@6.4.3(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.7) @@ -40746,10 +40727,10 @@ snapshots: sass: 1.72.0 stylus: 0.59.0(supports-color@10.0.0) terser: 5.51.2 - tsx: 4.23.0 + tsx: 4.23.15 yaml: 2.9.0 - vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): + vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.7) @@ -40766,10 +40747,10 @@ snapshots: sass: 1.72.0 stylus: 0.59.0(supports-color@10.0.0) terser: 5.51.2 - tsx: 4.23.0 + tsx: 4.23.15 yaml: 2.9.0 - vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): + vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -40785,10 +40766,10 @@ snapshots: sass: 1.72.0 stylus: 0.59.0(supports-color@10.0.0) terser: 5.51.2 - tsx: 4.23.0 + tsx: 4.23.15 yaml: 2.9.0 - vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): + vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -40804,25 +40785,25 @@ snapshots: sass: 1.72.0 stylus: 0.59.0(supports-color@8.1.1) terser: 5.51.2 - tsx: 4.23.0 + tsx: 4.23.15 yaml: 2.9.0 - vitefu@0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): + vitefu@0.2.5(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)): optionalDependencies: - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vitefu@1.0.7(vite@5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)): optionalDependencies: vite: 5.4.21(@types/node@20.19.43)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2) - vitefu@1.0.7(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)): + vitefu@1.0.7(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)): optionalDependencies: - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) - vitest@4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): + vitest@4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 @@ -40839,7 +40820,7 @@ snapshots: tinyexec: 1.3.1 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.10.0 @@ -40860,10 +40841,10 @@ snapshots: - tsx - yaml - vitest@4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@8.1.1))(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0): + vitest@4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@8.1.1))(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.11(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.11 '@vitest/runner': 4.1.11 '@vitest/snapshot': 4.1.11 @@ -40880,7 +40861,7 @@ snapshots: tinyexec: 1.3.1 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.0)(yaml@2.9.0) + vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@8.1.1))(sass@1.72.0)(stylus@0.59.0(supports-color@8.1.1))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.10.0 From 9ea3abf419a90859451f5da095ab62be37d6e183 Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Fri, 30 Oct 2026 09:59:17 -0700 Subject: [PATCH 073/169] feat(node): Add nodeScheduleIntegration to monitor node-schedule jobs (#25474) Part of a series adding automatic Crons monitoring for the most-used job schedulers in each SDK. Check-ins for a monitor that doesn't exist yet are dropped, so today each monitor has to be created by hand with its schedule copied from the code. Sending the job's schedule as the monitor config lets Sentry create the monitor and keep it in sync. Adds `nodeScheduleIntegration`, which sends check-ins for every named `scheduleJob` job (`node-schedule` 1.3+ and 2.x) with its cron string and `tz` as the monitor config. It replaces wrapping the module in `Sentry.cron.instrumentNodeSchedule`. Adding the integration is the opt-in. Jobs without a name and one-off `Date` jobs aren't monitored. ```js Sentry.init({ dsn: '...', integrations: [Sentry.nodeScheduleIntegration({ excludeJobs: ['cleanup', /^debug-/] })], }); schedule.scheduleJob('daily-report', { rule: '30 9 * * 1-5', tz: 'Europe/Vienna' }, sendReport); ``` Recurrence rules, rules with a `start` or `end`, and schedules Crons can't express, like every 10 seconds, aren't monitored. Jobs already wrapped by `instrumentNodeSchedule` are skipped. --- .../node-suites/excludes.ts | 1 + .../node-schedule-integration/instrument.mjs | 9 ++ .../node-schedule-integration/scenario.mjs | 35 ++++ .../cron/node-schedule-integration/test.ts | 66 ++++++++ packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/deno/src/index.ts | 1 + packages/elysia/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/cron/node-schedule.ts | 5 +- packages/node/src/index.ts | 2 + packages/node/test/cron.test.ts | 9 ++ packages/remix/src/server/index.ts | 1 + packages/server-utils/src/index.ts | 2 + .../src/integrations/node-schedule.ts | 112 +++++++++++++ .../server-utils/src/orchestrion/channels.ts | 2 + .../src/orchestrion/config/index.ts | 2 + .../src/orchestrion/config/node-schedule.ts | 29 ++++ .../server-utils/src/utils/cronMonitoring.ts | 53 +++++- .../test/integrations/node-schedule.test.ts | 151 ++++++++++++++++++ .../moduleInjectedTransform.test.ts | 2 +- packages/solidstart/src/server/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + 24 files changed, 483 insertions(+), 6 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/cron/node-schedule-integration/instrument.mjs create mode 100644 dev-packages/node-integration-tests/suites/cron/node-schedule-integration/scenario.mjs create mode 100644 dev-packages/node-integration-tests/suites/cron/node-schedule-integration/test.ts create mode 100644 packages/server-utils/src/integrations/node-schedule.ts create mode 100644 packages/server-utils/src/orchestrion/config/node-schedule.ts create mode 100644 packages/server-utils/test/integrations/node-schedule.test.ts diff --git a/dev-packages/bun-integration-tests/node-suites/excludes.ts b/dev-packages/bun-integration-tests/node-suites/excludes.ts index dff3e151b26e..0e868ffeba36 100644 --- a/dev-packages/bun-integration-tests/node-suites/excludes.ts +++ b/dev-packages/bun-integration-tests/node-suites/excludes.ts @@ -66,6 +66,7 @@ const NO_FETCH_INSTRUMENTATION = [ // See https://github.com/getsentry/sentry-javascript/issues/23882 export const NO_AUTO_INSTRUMENTATION = [ 'suites/cron/node-cron/integration/test.ts', + 'suites/cron/node-schedule-integration/test.ts', 'suites/express/**', 'suites/fs-instrumentation/test.ts', 'suites/hono/test.ts', diff --git a/dev-packages/node-integration-tests/suites/cron/node-schedule-integration/instrument.mjs b/dev-packages/node-integration-tests/suites/cron/node-schedule-integration/instrument.mjs new file mode 100644 index 000000000000..a3011f204b5e --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/node-schedule-integration/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', + transport: loggingTransport, + integrations: [Sentry.nodeScheduleIntegration({ excludeJobs: ['excluded-job'] })], +}); diff --git a/dev-packages/node-integration-tests/suites/cron/node-schedule-integration/scenario.mjs b/dev-packages/node-integration-tests/suites/cron/node-schedule-integration/scenario.mjs new file mode 100644 index 000000000000..7cb179a7483a --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/node-schedule-integration/scenario.mjs @@ -0,0 +1,35 @@ +import schedule from 'node-schedule'; + +const excluded = schedule.scheduleJob('excluded-job', '0 0 * * *', () => {}); +const unnamed = schedule.scheduleJob('0 0 * * *', () => {}); +const oneOff = schedule.scheduleJob('one-off-job', Date.now() + 86_400_000, () => {}); +const recurrenceRule = schedule.scheduleJob('rule-job', { hour: 3, minute: 0 }, () => {}); +const ending = schedule.scheduleJob('ending-job', { rule: '0 0 * * *', end: Date.now() + 86_400_000 }, () => {}); +const newJob = new schedule.Job('new-job', () => {}); +newJob.schedule({ rule: '0 0 * * *', tz: 'UTC' }); + +let runs = 0; +const job = schedule.scheduleJob('my-schedule-job', { rule: '30 9 * * 1-5', tz: 'Europe/Vienna' }, () => { + runs++; + if (runs === 2) { + throw new Error('Error in scheduled job'); + } +}); + +// Run the jobs without waiting for their schedule. +excluded.invoke(); +unnamed.invoke(); +oneOff.invoke(); +recurrenceRule.invoke(); +ending.invoke(); +newJob.invoke(); +job.invoke(); +try { + job.invoke(); +} catch { + // node-schedule catches errors when it runs the job itself +} +schedule.rescheduleJob(job, { rule: '0 8 * * *', tz: 'UTC' }); +job.invoke(); + +setTimeout(() => process.exit(), 10_000); diff --git a/dev-packages/node-integration-tests/suites/cron/node-schedule-integration/test.ts b/dev-packages/node-integration-tests/suites/cron/node-schedule-integration/test.ts new file mode 100644 index 000000000000..3a136ea82d02 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/cron/node-schedule-integration/test.ts @@ -0,0 +1,66 @@ +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; + +const MONITOR_CONFIG = { + schedule: { type: 'crontab', value: '30 9 * * 1-5' }, + timezone: 'Europe/Vienna', +}; + +describe('nodeScheduleIntegration', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('sends check-ins for named jobs', async () => { + await createRunner() + .ignore('event') + .expect({ + check_in: { + monitor_slug: 'new-job', + status: 'in_progress', + monitor_config: { schedule: { type: 'crontab', value: '0 0 * * *' }, timezone: 'UTC' }, + }, + }) + .expect({ check_in: { monitor_slug: 'new-job', status: 'ok' } }) + .expect({ + check_in: { monitor_slug: 'my-schedule-job', status: 'in_progress', monitor_config: MONITOR_CONFIG }, + }) + .expect({ check_in: { monitor_slug: 'my-schedule-job', status: 'ok', duration: expect.any(Number) } }) + .expect({ + check_in: { monitor_slug: 'my-schedule-job', status: 'in_progress', monitor_config: MONITOR_CONFIG }, + }) + .expect({ check_in: { monitor_slug: 'my-schedule-job', status: 'error', duration: expect.any(Number) } }) + .expect({ + check_in: { + monitor_slug: 'my-schedule-job', + status: 'in_progress', + monitor_config: { schedule: { type: 'crontab', value: '0 8 * * *' }, timezone: 'UTC' }, + }, + }) + .expect({ check_in: { monitor_slug: 'my-schedule-job', status: 'ok', duration: expect.any(Number) } }) + .start() + .completed(); + }); + + test('captures job errors', async () => { + await createRunner() + .unordered() + .expect({ + event: { + exception: { + values: [ + { + type: 'Error', + value: 'Error in scheduled job', + mechanism: { type: 'auto.function.node-schedule', handled: false }, + }, + ], + }, + }, + }) + .start() + .completed(); + }); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index a52a7477f6bc..822464ade0e1 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -50,6 +50,7 @@ export { functionToStringIntegration, genericPoolIntegration, nodeCronIntegration, + nodeScheduleIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 0803fca702f5..0e684c45dc2d 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -120,6 +120,7 @@ export { fsIntegration, genericPoolIntegration, nodeCronIntegration, + nodeScheduleIntegration, graphqlIntegration, knexIntegration, kafkaIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 3f14e1c6da6f..e33986f88bba 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -144,6 +144,7 @@ export { setupKoaErrorHandler, genericPoolIntegration, nodeCronIntegration, + nodeScheduleIntegration, graphqlIntegration, knexIntegration, kafkaIntegration, diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 52f2a040cd49..9f2aac8b30cb 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -196,6 +196,7 @@ export { postgresJsIntegration, neonIntegration, nodeCronIntegration, + nodeScheduleIntegration, tediousIntegration, eveConversationHook, eveIntegration, diff --git a/packages/elysia/src/index.ts b/packages/elysia/src/index.ts index 8a3da3bf62b5..014bd9145c4a 100644 --- a/packages/elysia/src/index.ts +++ b/packages/elysia/src/index.ts @@ -118,6 +118,7 @@ export { setupKoaErrorHandler, genericPoolIntegration, nodeCronIntegration, + nodeScheduleIntegration, graphqlIntegration, knexIntegration, kafkaIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 1f6e6979bee0..b92984d1b6b6 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -120,6 +120,7 @@ export { firebaseIntegration, genericPoolIntegration, nodeCronIntegration, + nodeScheduleIntegration, graphqlIntegration, knexIntegration, kafkaIntegration, diff --git a/packages/node/src/cron/node-schedule.ts b/packages/node/src/cron/node-schedule.ts index f174ef7c904b..548f60ac48f8 100644 --- a/packages/node/src/cron/node-schedule.ts +++ b/packages/node/src/cron/node-schedule.ts @@ -1,4 +1,4 @@ -import { withMonitor } from '@sentry/core'; +import { addNonEnumerableProperty, withMonitor } from '@sentry/core'; import { replaceCronNames } from './common'; export interface NodeSchedule { @@ -58,6 +58,9 @@ export function instrumentNodeSchedule(lib: T & NodeSchedule): T { ); } + // Tells `nodeScheduleIntegration` that this job is already monitored. + addNonEnumerableProperty(monitoredCallback, Symbol.for('sentry.cron.monitored'), true); + return target.apply(thisArg, [monitorSlug, expression, monitoredCallback]); }, }); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 7fdeb4ab4df2..4cf8b6e4b9eb 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -15,6 +15,8 @@ export { genericPoolIntegration, nodeCronIntegration, type NodeCronIntegrationOptions, + nodeScheduleIntegration, + type NodeScheduleIntegrationOptions, googleGenAIIntegration, graphqlIntegration, groqIntegration, diff --git a/packages/node/test/cron.test.ts b/packages/node/test/cron.test.ts index bf1e83d315e5..f5178dc98e88 100644 --- a/packages/node/test/cron.test.ts +++ b/packages/node/test/cron.test.ts @@ -220,5 +220,14 @@ describe('cron check-ins', () => { "Automatic instrumentation of 'node-schedule' requires the first parameter of 'scheduleJob' to be a job name string and the second parameter to be a crontab string", ); }); + + test('marks the callback as monitored, so nodeScheduleIntegration skips it', () => { + const scheduleWithCheckIn = cron.instrumentNodeSchedule({ + scheduleJob: (_name: unknown, _expression: unknown, callback: unknown) => callback, + }); + + const callback = scheduleWithCheckIn.scheduleJob('my-cron-job', '* * * * *', () => {}) as Record; + expect(callback[Symbol.for('sentry.cron.monitored')]).toBe(true); + }); }); }); diff --git a/packages/remix/src/server/index.ts b/packages/remix/src/server/index.ts index e7d3142e0be2..10fc1a596daa 100644 --- a/packages/remix/src/server/index.ts +++ b/packages/remix/src/server/index.ts @@ -38,6 +38,7 @@ export { functionToStringIntegration, genericPoolIntegration, nodeCronIntegration, + nodeScheduleIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/server-utils/src/index.ts b/packages/server-utils/src/index.ts index acb0ffb47c75..005e8a4a8b79 100644 --- a/packages/server-utils/src/index.ts +++ b/packages/server-utils/src/index.ts @@ -40,6 +40,8 @@ export { dataloaderIntegration } from './integrations/dataloader'; export { genericPoolIntegration } from './integrations/generic-pool'; export { nodeCronIntegration } from './integrations/node-cron'; export type { NodeCronIntegrationOptions } from './integrations/node-cron'; +export { nodeScheduleIntegration } from './integrations/node-schedule'; +export type { NodeScheduleIntegrationOptions } from './integrations/node-schedule'; export { googleGenAIIntegration } from './integrations/google-genai'; export { graphqlIntegration } from './integrations/graphql'; export { hapiIntegration } from './integrations/hapi'; diff --git a/packages/server-utils/src/integrations/node-schedule.ts b/packages/server-utils/src/integrations/node-schedule.ts new file mode 100644 index 000000000000..426c362d0021 --- /dev/null +++ b/packages/server-utils/src/integrations/node-schedule.ts @@ -0,0 +1,112 @@ +import type { IntegrationFn, MonitorConfig } from '@sentry/core'; +import { defineIntegration } from '@sentry/core'; +import { CHANNELS } from '../orchestrion/channels'; +import { nodeScheduleModuleNames } from '../orchestrion/config/node-schedule'; +import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation'; +import type { AnyFn, ScheduleChange } from '../utils/cronMonitoring'; +import { + cronExpressionToMonitorConfig, + shouldMonitorJob, + subscribeToScheduleChange, + updateMonitorConfig, + wrapScheduledJob, +} from '../utils/cronMonitoring'; + +const INTEGRATION_NAME = 'NodeSchedule' as const; + +export interface NodeScheduleIntegrationOptions { + /** Job names not to monitor. Strings must match the name exactly. */ + excludeJobs?: Array; +} + +const _nodeScheduleIntegration = ((options: NodeScheduleIntegrationOptions = {}) => { + return { + name: INTEGRATION_NAME, + setup(client) { + invokeOrchestrionInstrumentation(client, nodeScheduleModuleNames, subscribe, [options], { + requiresTracingChannelBinding: false, + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Sends Crons check-ins for every named `node-schedule` job, whether it's created with `scheduleJob` or `new Job()`. + * The monitor slug is the job name. Jobs without a name, one-off jobs, and jobs with a schedule Crons can't express + * are not monitored. + */ +export const nodeScheduleIntegration = defineIntegration(_nodeScheduleIntegration); + +function subscribe(options: NodeScheduleIntegrationOptions): void { + subscribeToScheduleChange(CHANNELS.NODE_SCHEDULE_SCHEDULE, INTEGRATION_NAME, onSchedule, change => + beforeSchedule(change, options), + ); +} + +interface Job { + name?: unknown; + job?: unknown; + nextInvocation?: () => unknown; +} + +// The name node-schedule gives jobs created without one. +const ANONYMOUS_NAME = /^]/; + +// `job.schedule(spec)` calls that add to runs the job already has pending, rather than replace them. +const addsToPendingRuns = new WeakSet(); + +/** Wraps a named job's method before `job.schedule(spec)` with a recurring spec, unless it's already wrapped. */ +export function beforeSchedule(change: ScheduleChange, options: NodeScheduleIntegrationOptions): void { + const job = change.self as Job | undefined; + if (!job) { + return; + } + if (typeof job.nextInvocation === 'function' && job.nextInvocation() != null) { + addsToPendingRuns.add(change); + } + const name = typeof job.name === 'string' && !ANONYMOUS_NAME.test(job.name) ? job.name : undefined; + if (isOneOffSpec(change.arguments[0]) || !shouldMonitorJob(name, job.job, options.excludeJobs)) { + return; + } + job.job = wrapScheduledJob(job.job as AnyFn, name, undefined, 'auto.function.node-schedule'); +} + +/** + * Sets the spec's config after `job.schedule(spec)`. Without one, from a spec Crons can't express or a job that also + * keeps runs from an earlier spec, the job doesn't check in. + */ +export function onSchedule(change: ScheduleChange): void { + // `schedule` returns false when it scheduled nothing, and `reschedule` then keeps the old schedule. + if (change.result === true) { + const job = change.self as Job | undefined; + updateMonitorConfig(job?.job, addsToPendingRuns.has(change) ? undefined : getMonitorConfig(change.arguments[0])); + } +} + +/** Whether node-schedule runs a job with this spec once: a `Date`, a timestamp, or a date string. */ +export function isOneOffSpec(spec: unknown): boolean { + const { rule } = (spec || {}) as { rule?: unknown }; + const when = typeof spec === 'object' && rule ? rule : spec; + if (typeof when === 'string') { + // `Date.parse` also accepts some crons, like `0 9 * * *`. + return !isCronShaped(when) && !Number.isNaN(Date.parse(when)); + } + return when instanceof Date || typeof when === 'number'; +} + +function isCronShaped(value: string): boolean { + const fields = value.trim().split(/\s+/); + return ( + /^@[a-z]+$/i.test(value.trim()) || + ((fields.length === 5 || fields.length === 6) && fields.every(field => /^[\d*,/?#a-z-]+$/i.test(field))) + ); +} + +// Only cron strings convert; recurrence rules and rules limited by `start` or `end` don't. +export function getMonitorConfig(spec: unknown): MonitorConfig | undefined { + if (typeof spec === 'string') { + return cronExpressionToMonitorConfig(spec, undefined, 'or'); + } + const { rule, tz, start, end } = (spec || {}) as { rule?: unknown; tz?: unknown; start?: unknown; end?: unknown }; + return typeof rule === 'string' && !start && !end ? cronExpressionToMonitorConfig(rule, tz, 'or') : undefined; +} diff --git a/packages/server-utils/src/orchestrion/channels.ts b/packages/server-utils/src/orchestrion/channels.ts index 81f9b34d2e27..67a88bbb3825 100644 --- a/packages/server-utils/src/orchestrion/channels.ts +++ b/packages/server-utils/src/orchestrion/channels.ts @@ -27,6 +27,7 @@ import { mysqlChannels } from './config/mysql'; import { neonChannels } from './config/neon'; import { nestjsChannels } from './config/nestjs'; import { nodeCronChannels } from './config/node-cron'; +import { nodeScheduleChannels } from './config/node-schedule'; import { openaiChannels } from './config/openai'; import { pgChannels } from './config/pg'; import { piDurableChannels } from './config/pi-durable'; @@ -85,6 +86,7 @@ export const CHANNELS = { ...neonChannels, ...nestjsChannels, ...nodeCronChannels, + ...nodeScheduleChannels, ...openaiChannels, ...pgChannels, ...piDurableChannels, diff --git a/packages/server-utils/src/orchestrion/config/index.ts b/packages/server-utils/src/orchestrion/config/index.ts index 56b7bf008c56..bf2c4b50b178 100644 --- a/packages/server-utils/src/orchestrion/config/index.ts +++ b/packages/server-utils/src/orchestrion/config/index.ts @@ -34,6 +34,7 @@ import { neonConfig } from './neon'; import { nestjsConfig } from './nestjs'; import { nitroConfig } from './nitro'; import { nodeCronConfig } from './node-cron'; +import { nodeScheduleConfig } from './node-schedule'; import { openaiConfig } from './openai'; import { pgConfig } from './pg'; import { postgresJsConfig } from './postgres'; @@ -93,6 +94,7 @@ export const SENTRY_INSTRUMENTATIONS: InstrumentationConfig[] = [ ...nestjsConfig, ...nitroConfig, ...nodeCronConfig, + ...nodeScheduleConfig, ...openaiConfig, ...pgConfig, ...piDurableConfig, diff --git a/packages/server-utils/src/orchestrion/config/node-schedule.ts b/packages/server-utils/src/orchestrion/config/node-schedule.ts new file mode 100644 index 000000000000..acb3733a1b69 --- /dev/null +++ b/packages/server-utils/src/orchestrion/config/node-schedule.ts @@ -0,0 +1,29 @@ +import type { InstrumentationConfig } from '../apmTypes'; +import { getModuleNames } from './module-names'; + +// `scheduleJob`, `rescheduleJob` and `job.reschedule` all call `job.schedule(spec)`. `Job` moved from +// `lib/schedule.js` to `lib/Job.js` in 2.0.0. +const schedule = { + channelName: 'schedule', + astQuery: + "AssignmentExpression[left.object.property.name='prototype'][left.property.name='schedule'] > FunctionExpression", + // `methodName` gives subscribers `self` before the call, not just after. `astQuery` still picks the function. + functionQuery: { methodName: 'schedule', kind: 'Sync' }, +} as const; + +export const nodeScheduleConfig = [ + { + ...schedule, + module: { name: 'node-schedule', versionRange: '>=1.3.0 <2', filePath: 'lib/schedule.js' }, + }, + { + ...schedule, + module: { name: 'node-schedule', versionRange: '>=2.0.0 <3', filePath: 'lib/Job.js' }, + }, +] satisfies InstrumentationConfig[]; + +export const nodeScheduleModuleNames = getModuleNames(nodeScheduleConfig); + +export const nodeScheduleChannels = { + NODE_SCHEDULE_SCHEDULE: 'orchestrion:node-schedule:schedule', +} as const; diff --git a/packages/server-utils/src/utils/cronMonitoring.ts b/packages/server-utils/src/utils/cronMonitoring.ts index 975763d3c0a7..aa751eed9442 100644 --- a/packages/server-utils/src/utils/cronMonitoring.ts +++ b/packages/server-utils/src/utils/cronMonitoring.ts @@ -1,5 +1,6 @@ import type { MonitorConfig } from '@sentry/core'; import { + addNonEnumerableProperty, captureException, debug, isThenable, @@ -12,7 +13,7 @@ import * as diagnosticsChannel from './diagnosticsChannel'; export type AnyFn = (this: unknown, ...args: unknown[]) => unknown; -// Set by `Sentry.cron.instrumentNodeCron`, which already sends check-ins. +// Set by `Sentry.cron.instrumentNodeCron`, `Sentry.cron.instrumentNodeSchedule` and `wrapScheduledJob`, which already send check-ins. const MONITORED = Symbol.for('sentry.cron.monitored'); /** @@ -33,7 +34,38 @@ export function subscribeToJobCreation( }); } -/** Whether a job is named, not excluded, and not already monitored by `Sentry.cron.instrumentNodeCron`. */ +/** A call on a schedule-change channel: its arguments, the job it was called on, and what it returned. */ +export interface ScheduleChange { + arguments: unknown[]; + self?: unknown; + result?: unknown; +} + +/** + * Calls `onChange` after each call on a scheduler's schedule-change channel, so it can update the job's monitor config. + * `onStart` gets the same object before the call. Errors are logged and the job keeps its config. + */ +export function subscribeToScheduleChange( + channelName: string, + integrationName: string, + onChange: (change: ScheduleChange) => void, + onStart?: (change: ScheduleChange) => void, +): void { + const channel = diagnosticsChannel.tracingChannel(channelName); + const handle = (handler: (change: ScheduleChange) => void) => (message: unknown) => { + try { + handler(message as ScheduleChange); + } catch (e) { + DEBUG_BUILD && debug.error(`[${integrationName}] Failed to update the cron monitor:`, e); + } + }; + if (onStart) { + channel.start.subscribe(handle(onStart)); + } + channel.end.subscribe(handle(onChange)); +} + +/** Whether a job is named, not excluded, and not already monitored by `Sentry.cron.instrumentNode*` or `wrapScheduledJob`. */ export function shouldMonitorJob( name: unknown, job: unknown, @@ -161,6 +193,8 @@ function toSentryTimezone(timezone: string | undefined): string | undefined { } } +const monitorConfigs = new WeakMap(); + /** * Wraps a scheduled job with check-ins. Errors are captured, because schedulers catch them, and rethrown. * Jobs without a monitor config send no check-ins. @@ -176,7 +210,7 @@ export function wrapScheduledJob( throw error; }; - return function (this: unknown, ...args: unknown[]): unknown { + const wrapped = function (this: unknown, ...args: unknown[]): unknown { const run = (): unknown => { try { const result: unknown = func.apply(this, args); @@ -185,6 +219,17 @@ export function wrapScheduledJob( return captureAndRethrow(e); } }; - return startNewTrace(() => (monitorConfig ? withMonitor(monitorSlug, run, monitorConfig) : run())); + const currentConfig = monitorConfigs.get(wrapped); + return startNewTrace(() => (currentConfig ? withMonitor(monitorSlug, run, currentConfig) : run())); }; + monitorConfigs.set(wrapped, monitorConfig); + addNonEnumerableProperty(wrapped, MONITORED, true); + return wrapped; +} + +/** Sets the monitor config a job from `wrapScheduledJob` sends on its next runs. Other functions are ignored. */ +export function updateMonitorConfig(job: unknown, monitorConfig: MonitorConfig | undefined): void { + if (typeof job === 'function' && monitorConfigs.has(job as AnyFn)) { + monitorConfigs.set(job as AnyFn, monitorConfig); + } } diff --git a/packages/server-utils/test/integrations/node-schedule.test.ts b/packages/server-utils/test/integrations/node-schedule.test.ts new file mode 100644 index 000000000000..7071d955fc8b --- /dev/null +++ b/packages/server-utils/test/integrations/node-schedule.test.ts @@ -0,0 +1,151 @@ +import type { MonitorConfig } from '@sentry/core'; +import { setCurrentClient } from '@sentry/core'; +import { beforeEach, describe, expect, it } from 'vitest'; +import { beforeSchedule, getMonitorConfig, isOneOffSpec, onSchedule } from '../../src/integrations/node-schedule'; +import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; + +describe('isOneOffSpec', () => { + it.each([ + ['a Date', new Date()], + ['a timestamp', 1_793_527_200_000], + ['a date string', '2026-11-01T10:00:00Z'], + ['a long date string', 'Sun Nov 01 2026 10:00:00 GMT+0000'], + ['a Date rule', { rule: new Date(), tz: 'UTC' }], + ])('is true for %s', (_, spec) => { + expect(isOneOffSpec(spec)).toBe(true); + }); + + it.each([ + ['a cron string', '0 9 * * *'], + ['a cron with names', '0 9 * jan mon-fri'], + ['a cron with seconds', '*/10 * * * * *'], + ['a nickname', '@daily'], + ['a cron rule', { rule: '0 9 * * *', tz: 'UTC' }], + ['a recurrence rule', { hour: 3, minute: 0 }], + ])('is false for %s', (_, spec) => { + expect(isOneOffSpec(spec)).toBe(false); + }); +}); + +describe('getMonitorConfig', () => { + it('sends a cron rule with its time zone', () => { + expect(getMonitorConfig({ rule: '30 9 * * 1-5', tz: 'Europe/Vienna' })).toEqual({ + schedule: { type: 'crontab', value: '30 9 * * 1-5' }, + timezone: 'Europe/Vienna', + }); + }); + + it('drops a fixed second', () => { + expect(getMonitorConfig({ rule: '0 30 9 * * *', tz: 'UTC' })?.schedule).toEqual({ + type: 'crontab', + value: '30 9 * * *', + }); + }); + + it.each([ + ['a sub-minute cron', '*/10 * * * * *'], + ['a rule with a start', { rule: '0 9 * * *', start: Date.now() + 1000 }], + ['a rule with an end', { rule: '0 9 * * *', end: Date.now() + 1000 }], + ['a recurrence rule', { hour: 3, minute: 0 }], + ['a UTC offset', { rule: '0 9 * * *', tz: '+02:00' }], + ])('is undefined for %s', (_, spec) => { + expect(getMonitorConfig(spec)).toBeUndefined(); + }); +}); + +describe('job.schedule', () => { + let sentConfigs: Array; + + beforeEach(() => { + sentConfigs = []; + const client = new TestClient(getDefaultTestClientOptions()); + client.captureCheckIn = (_checkIn, monitorConfig) => { + sentConfigs.push(monitorConfig); + return 'check-in-id'; + }; + setCurrentClient(client); + }); + + const createJob = (name = 'my-job') => { + let pending = false; + return { + name, + job: (() => {}) as unknown, + nextInvocation: () => (pending ? new Date() : null), + schedule(spec: unknown) { + const change = { arguments: [spec], self: this, result: undefined as unknown }; + beforeSchedule(change, { excludeJobs: ['excluded-job'] }); + change.result = spec !== 'not a spec'; + pending ||= change.result === true; + onSchedule(change); + return change.result; + }, + reschedule(spec: unknown) { + pending = false; + return this.schedule(spec); + }, + run() { + (this.job as () => void)(); + }, + }; + }; + + it('sends the schedule of a named job', () => { + const job = createJob(); + job.schedule({ rule: '30 9 * * 1-5', tz: 'UTC' }); + job.run(); + + expect(sentConfigs[0]).toEqual({ schedule: { type: 'crontab', value: '30 9 * * 1-5' }, timezone: 'UTC' }); + }); + + it.each([ + ['an unnamed job', createJob(''), '0 9 * * *'], + ['an excluded job', createJob('excluded-job'), '0 9 * * *'], + ['a one-off job', createJob(), new Date(Date.now() + 86_400_000)], + ['a job with a recurrence rule', createJob(), { hour: 3, minute: 0 }], + ])('does not monitor %s', (_, job, spec) => { + job.schedule(spec); + job.run(); + + expect(sentConfigs).toEqual([]); + }); + + it('wraps the job once and sends the new schedule after a reschedule', () => { + const job = createJob(); + job.schedule('0 9 * * *'); + const wrapped = job.job; + job.reschedule({ rule: '30 10 * * 1-5', tz: 'UTC' }); + job.run(); + + expect(job.job).toBe(wrapped); + expect(sentConfigs).toHaveLength(2); + expect(sentConfigs[0]).toEqual({ schedule: { type: 'crontab', value: '30 10 * * 1-5' }, timezone: 'UTC' }); + }); + + it('stops checking in after a reschedule to a spec it cannot convert', () => { + const job = createJob(); + job.schedule('0 9 * * *'); + job.reschedule({ hour: 3, minute: 0 }); + job.run(); + + expect(sentConfigs).toEqual([]); + }); + + it('keeps the old schedule when the reschedule fails', () => { + const job = createJob(); + job.schedule('0 9 * * *'); + job.reschedule('not a spec'); + job.run(); + + expect(sentConfigs[0]?.schedule).toEqual({ type: 'crontab', value: '0 9 * * *' }); + }); + + it('stops checking in when a second schedule adds to the first', () => { + const job = createJob(); + job.schedule('0 9 * * *'); + job.schedule('0 18 * * *'); + job.run(); + + expect(sentConfigs).toEqual([]); + }); +}); diff --git a/packages/server-utils/test/orchestrion/moduleInjectedTransform.test.ts b/packages/server-utils/test/orchestrion/moduleInjectedTransform.test.ts index 5dac489035d1..0feac3d58b47 100644 --- a/packages/server-utils/test/orchestrion/moduleInjectedTransform.test.ts +++ b/packages/server-utils/test/orchestrion/moduleInjectedTransform.test.ts @@ -44,7 +44,7 @@ describe('channel integration definitions', () => { // `@nestjs/*` and `@remix-run/*` are covered by listeners in their own SDK packages. // Each cron monitor is billed, so bundler-only SDKs must not install crons integrations for every app. - const optIn = new Set(['node-cron']); + const optIn = new Set(['node-cron', 'node-schedule']); const uncovered = [...configured].filter( name => !defined.has(name) && !optIn.has(name) && !name.startsWith('@nestjs/') && !name.startsWith('@remix-run/'), ); diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index 686ce210b2d7..4ac8bff68e34 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -41,6 +41,7 @@ export { functionToStringIntegration, genericPoolIntegration, nodeCronIntegration, + nodeScheduleIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 7d55e5f41ec9..ff3d3ac7b5dd 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -39,6 +39,7 @@ export { functionToStringIntegration, genericPoolIntegration, nodeCronIntegration, + nodeScheduleIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, From b63640bbb5d9f42721b4108daa583b8eef7d91e6 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:11:26 +0100 Subject: [PATCH 074/169] Revert "ci(deps): Dedupe lockfile on Dependabot PRs (#25911)" (#25962) **Why:** With `autoDedupe` enabled in #25961, Dependabot now writes an already-deduped lockfile, so the dedupe workflow from #25911 has nothing left to do. After #25961 merged, Dependabot rebased #25955, #25956 and #25957. On all three, `pnpm dedupe` made no changes: the upload step was skipped, no artifact was created and no dedupe commit was pushed. `Check lockfile` passed on #25955 and #25957. #25956 fails `pnpm install` with `ERR_PNPM_IGNORED_BUILDS`, which is a separate problem. **How to review:** This is a clean `git revert` of bc8fdf1. It also restores the optional E2E condition in `build.yml` to the `github.actor` check, since the author-based check was only needed once the dedupe app started pushing to Dependabot branches. via **Charly Gomez**. -- [View Session](https://junior-prod.sentry.dev/conversations/slack%3AC02TB1YBNGJ%3A1793371893.509089) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC02TB1YBNGJ%3A1793371893.509089/?project=4510944073809921) Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Charly Gomez --- .github/workflows/build.yml | 7 +- .github/workflows/dependabot-dedupe-push.yml | 134 ------------------- .github/workflows/dependabot-dedupe.yml | 63 --------- 3 files changed, 2 insertions(+), 202 deletions(-) delete mode 100644 .github/workflows/dependabot-dedupe-push.yml delete mode 100644 .github/workflows/dependabot-dedupe.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d470f273e39..a546292a32ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1141,16 +1141,13 @@ jobs: # them. optional-e2e.yml reports the results in a PR comment. job_optional_e2e_tests: name: E2E Tests (optional) - # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks. - # Dependabot PRs are skipped by author, not by `github.actor`: once the Gitflow app pushes the deduped lockfile - # (dependabot-dedupe-push.yml), the actor is the app and the run would otherwise get the E2E secrets. + # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks # We need to add the `always()` check here because the previous step has this as well :( # See: https://github.com/actions/runner/issues/2205 if: always() && needs.job_get_metadata.outputs.is_release != 'true' && needs.job_build_tarballs.result == 'success' && needs.job_build.outputs.e2e-matrix-optional != '{"include":[]}' && (github.event_name != 'pull_request' || - github.event.pull_request.head.repo.full_name == github.repository) && github.event.pull_request.user.login != - 'dependabot[bot]' + github.event.pull_request.head.repo.full_name == github.repository) && github.actor != 'dependabot[bot]' needs: [job_get_metadata, job_build, job_build_tarballs] # This job only waits for optional-e2e.yml, which can take most of an hour. GitHub runners are free for public # repos and Namespace runners are paid per minute, so the wait runs here and the tests run on Namespace. diff --git a/.github/workflows/dependabot-dedupe-push.yml b/.github/workflows/dependabot-dedupe-push.yml deleted file mode 100644 index 3fce5447b7ba..000000000000 --- a/.github/workflows/dependabot-dedupe-push.yml +++ /dev/null @@ -1,134 +0,0 @@ -name: 'Dependabot: Push deduped lockfile' - -# Pushes the lockfile from `dependabot-dedupe.yml` to the Dependabot PR branch. -# -# This workflow holds the Gitflow app token, so it never runs code from the PR. Everything it -# receives is untrusted: anyone with write access can push to a Dependabot branch, and the run that -# produced the artifact used the PR's version of `dependabot-dedupe.yml`. So it looks the PR up -# through the API, accepts only a single regular `pnpm-lock.yaml` file and commits nothing else. -# -# The app token is limited to `contents: write` on this repository, not to `dependabot/**` branches, so -# nothing from the PR may run before it is minted. Pushing as the app also changes `github.actor` on the -# PR's next CI run, so jobs that keep secrets away from Dependabot must check the PR author, not the actor. - -on: - workflow_run: - workflows: ['Dependabot: Dedupe lockfile'] - types: [completed] - -permissions: - actions: read - contents: read - pull-requests: read - -concurrency: - group: dependabot-dedupe-push-${{ github.event.workflow_run.head_sha }} - cancel-in-progress: false - -jobs: - push: - name: Push deduped lockfile - if: | - github.event.workflow_run.conclusion == 'success' - && github.event.workflow_run.event == 'pull_request' - && github.event.workflow_run.head_repository.full_name == github.repository - runs-on: namespace-profile-sentry-javascript-small - timeout-minutes: 5 - env: - GH_TOKEN: ${{ github.token }} - RUN_ID: ${{ github.event.workflow_run.id }} - HEAD_SHA: ${{ github.event.workflow_run.head_sha }} - steps: - - name: Find artifact and Dependabot PR - id: pr - run: | - artifacts=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}/artifacts" \ - --jq '[.artifacts[] | select(.name == "pnpm-lock")] | length') - if [ "$artifacts" != "1" ]; then - echo "No deduped lockfile to push." - exit 0 - fi - - head_ref=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${HEAD_SHA}/pulls" | - jq -r --arg repo "$GITHUB_REPOSITORY" --arg sha "$HEAD_SHA" ' - [.[] | select(.state == "open" - and .base.ref == "develop" - and .user.login == "dependabot[bot]" - and .head.repo.full_name == $repo - and .head.sha == $sha)] - | if length == 1 then .[0].head.ref else empty end') - if [[ ! "$head_ref" =~ ^dependabot/[A-Za-z0-9._/@+-]+$ ]]; then - echo "::error::No open Dependabot PR into develop has head commit ${HEAD_SHA}." - exit 1 - fi - echo "head_ref=${head_ref}" >> "$GITHUB_OUTPUT" - - - name: Download deduped lockfile - if: steps.pr.outputs.head_ref - uses: actions/download-artifact@v8 - with: - name: pnpm-lock - path: ${{ runner.temp }}/pnpm-lock - run-id: ${{ github.event.workflow_run.id }} - github-token: ${{ github.token }} - - - name: Check out PR branch - if: steps.pr.outputs.head_ref - uses: actions/checkout@v7 - with: - ref: ${{ github.event.workflow_run.head_sha }} - persist-credentials: false - - # Replace the file instead of writing through it, so a symlinked lockfile in the PR cannot redirect the write. - - name: Apply deduped lockfile - if: steps.pr.outputs.head_ref - id: apply - env: - LOCKFILE_DIR: ${{ runner.temp }}/pnpm-lock - run: | - if [ "$(find "$LOCKFILE_DIR" -mindepth 1 | wc -l)" != "1" ] || [ ! -f "$LOCKFILE_DIR/pnpm-lock.yaml" ] || [ -L "$LOCKFILE_DIR/pnpm-lock.yaml" ]; then - echo "::error::The artifact must contain exactly one regular file named pnpm-lock.yaml." - exit 1 - fi - if [ "$(git ls-files --stage -- pnpm-lock.yaml | cut -d' ' -f1)" != "100644" ]; then - echo "::error::pnpm-lock.yaml in the PR is not a regular file." - exit 1 - fi - rm pnpm-lock.yaml - cp "$LOCKFILE_DIR/pnpm-lock.yaml" pnpm-lock.yaml - - changes=$(git status --porcelain) - if [ -z "$changes" ]; then - echo "Lockfile is already deduped." - exit 0 - fi - if [ "$changes" != " M pnpm-lock.yaml" ]; then - echo "::error::Unexpected changes:" - echo "$changes" - exit 1 - fi - echo "changed=true" >> "$GITHUB_OUTPUT" - - # An app token is needed so the push triggers CI again; pushes made with GITHUB_TOKEN do not. - - name: Generate GitHub App token - if: steps.apply.outputs.changed == 'true' - id: app-token - uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 - with: - client-id: ${{ vars.GITFLOW_APP_ID }} - private-key: ${{ secrets.GITFLOW_APP_PRIVATE_KEY }} - permission-contents: write - - # `[dependabot skip]` lets Dependabot keep rebasing the PR over this commit. - # The push is not forced, so it fails if the branch moved past HEAD_SHA in the meantime. - - name: Commit and push - if: steps.apply.outputs.changed == 'true' - env: - APP_TOKEN: ${{ steps.app-token.outputs.token }} - APP_SLUG: ${{ steps.app-token.outputs.app-slug }} - HEAD_REF: ${{ steps.pr.outputs.head_ref }} - run: | - git config user.name "${APP_SLUG}[bot]" - git config user.email "${APP_SLUG}[bot]@users.noreply.github.com" - git commit -m "chore(deps): Dedupe pnpm-lock.yaml [dependabot skip]" -- pnpm-lock.yaml - git push "https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:refs/heads/${HEAD_REF}" diff --git a/.github/workflows/dependabot-dedupe.yml b/.github/workflows/dependabot-dedupe.yml deleted file mode 100644 index 05fcf2f42448..000000000000 --- a/.github/workflows/dependabot-dedupe.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: 'Dependabot: Dedupe lockfile' - -# Dependabot updates pnpm-lock.yaml without deduping it, so `Check lockfile` (`pnpm dedupe --check`) -# fails on most of its PRs. This job dedupes the lockfile and uploads it as an artifact. It runs on -# `pull_request`, so it has no secrets, a read-only token and PR-scoped caches. -# `dependabot-dedupe-push.yml` validates the result and pushes it to the PR branch. - -on: - pull_request: - types: [opened, synchronize, reopened] - branches: - - develop - paths: - - 'pnpm-lock.yaml' - -permissions: - contents: read - -concurrency: - group: dependabot-dedupe-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - dedupe: - name: Dedupe lockfile - # Only run on pushes made by Dependabot itself. The deduped commit pushed by `dependabot-dedupe-push.yml` - # also triggers `synchronize`, and a second full `pnpm dedupe` on it would only confirm there is nothing left to do. - if: | - github.event.pull_request.user.login == 'dependabot[bot]' - && github.actor == 'dependabot[bot]' - && github.event.pull_request.head.repo.full_name == github.repository - # PR code runs here, so keep its cache writes away from the cache used by develop, like the PR jobs in build.yml. - runs-on: namespace-profile-sentry-javascript;overrides.cache-tag=sentry-javascript-pr - # `pnpm dedupe` fetches metadata for every package from npm. - timeout-minutes: 20 - steps: - - name: Check out PR branch - uses: actions/checkout@v7 - with: - ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - - - name: Install pnpm - uses: ./.github/actions/install-pnpm - - - name: Set up Node - uses: actions/setup-node@v7 - with: - node-version-file: 'package.json' - - - name: Dedupe lockfile - id: dedupe - run: | - pnpm dedupe --ignore-scripts --ignore-pnpmfile - git diff --quiet pnpm-lock.yaml || echo "changed=true" >> "$GITHUB_OUTPUT" - - - name: Upload deduped lockfile - if: steps.dedupe.outputs.changed == 'true' - uses: actions/upload-artifact@v7 - with: - name: pnpm-lock - path: pnpm-lock.yaml - retention-days: 1 From c16633ee7f8bb61eb32374f542764ad32524dc43 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 30 Oct 2026 12:06:08 -0700 Subject: [PATCH 075/169] ref(cloudflare): Use the shared request handler wrapper (#25588) Moves Cloudflare's `wrapRequestHandler` onto `wrapServerRequestHandler`, which `Deno.serve` and `Bun.serve` use since the previous PRs. Cloudflare keeps only what is unique to it: client init, the `cf` and cloud resource contexts, the protocol from `cf.httpProtocol`, the `isStreamingResponse` option, `captureErrors`, and the flush through `waitUntil`, which skips the dispose for a 101 response. The helpers that only the old code used (`captureIncomingRequestBody`, `shouldCreateIncomingRequestSpan`, `getIncomingRequestSpanHooks` and `addRequest`) are gone. `wrapRequestHandler` must run on workers without the `nodejs_compat` flag, such as Shopify Oxygen, and every `@sentry/server-utils` entry loads `node:async_hooks`. So the wrapper moves to a new entry, `@sentry/server-utils/request-handler`, with no Node.js imports. Deno and Bun import it from there too. Cloudflare's module graph test allows this one entry, and a new test in server-utils walks the entry's module graph and fails on any Node.js import. The wrapper gets three optional fields: `isStreamingResponse`, `onDone` (called once when the request is done, with the response if there is one), and a `mechanism` that can be left out to not capture errors. For streamed responses, the shared stream monitor replaces Cloudflare's `TransformStream`. Workers with a compatibility date before 2022-11-30 cannot construct a `ReadableStream` without the `streams_enable_constructors` flag, so the wrapper then ends at handler return, as the `TransformStream` fallback did. The monitor now locks the source body only in `start()`, so the original response stays usable in that case. The wrapper builds the new response from the original one, which keeps `encodeBody: 'manual'`. Visible changes for Cloudflare: - Request spans set `client.address` from `CF-Connecting-IP` (or another forwarding header), as on the other runtimes. This follows `dataCollection.userInfo`, which is `true` by default. - Request spans get the response header attributes, and events get the `response` context. - An error from the source of a streamed response is captured (unless `captureErrors` is `false`), and the span gets an error status. - If the user removed `httpServerIntegration`, request bodies follow `dataCollection.httpBodies`. Before, the SDK attached no body. The minified `withSentry` bundle grows by about 2.4 KB (212,385 to 214,853 bytes), under the 211 KiB limit. Most of it is the request session and client address code, which Cloudflare now shares. Fixes #25577 Fixes JS-3975 --------- Co-authored-by: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 10 + .../suites/public-api/startSpan/test.ts | 10 + .../subpath-request-handler/test.ts | 13 +- packages/bun/src/integrations/bunserver.ts | 2 +- .../cloudflare/src/integrations/httpServer.ts | 161 +++--------- packages/cloudflare/src/scope-utils.ts | 8 - .../src/wrapRequestHandlerWithInit.ts | 235 +++--------------- .../test/request-stack-strategy.test.ts | 45 ++++ packages/cloudflare/test/request.test.ts | 20 +- .../test/requestModuleGraph.test.ts | 7 +- .../deno/src/wrap-deno-request-handler.ts | 2 +- packages/server-utils/package.json | 5 + packages/server-utils/rollup.npm.config.mjs | 1 + packages/server-utils/src/exports.ts | 4 +- .../server-utils/src/index.request-handler.ts | 4 + .../server-utils/src/utils/monitorStream.ts | 51 +++- .../src/utils/wrapServerRequestHandler.ts | 109 ++++++-- .../test/requestHandlerEntry.test.ts | 53 ++++ .../test/utils/monitorStream.test.ts | 58 +++++ .../utils/wrapServerRequestHandler.test.ts | 70 +++++- 20 files changed, 486 insertions(+), 382 deletions(-) create mode 100644 packages/cloudflare/test/request-stack-strategy.test.ts create mode 100644 packages/server-utils/src/index.request-handler.ts create mode 100644 packages/server-utils/test/requestHandlerEntry.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fff7847dd506..81399b6e6386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +- **ref(cloudflare): Use the shared request handler wrapper** + + Cloudflare Workers now use the same request handler wrapper as `Deno.serve` and `Bun.serve`. Changes for Cloudflare users: + + - Request spans now set `client.address` from the `CF-Connecting-IP` header (or another forwarding header), as on the other runtimes. This follows `dataCollection.userInfo`, which is `true` by default. To leave it out, set `dataCollection: { userInfo: false }`. + - Request spans now have the response header attributes, and events have the `response` context with the status code. + - When the source of a streamed response fails, the SDK now captures the error and marks the span as failed. With `captureErrors: false`, it does not capture it. + - Request spans now list every `Set-Cookie` header of a response, as on Deno and Bun. + - Without `AsyncLocalStorage` (for example on Shopify Oxygen), events now also get the request URL and method. + - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott ### Important Changes diff --git a/dev-packages/cloudflare-integration-tests/suites/public-api/startSpan/test.ts b/dev-packages/cloudflare-integration-tests/suites/public-api/startSpan/test.ts index 0fa5d410368b..adf52419022f 100644 --- a/dev-packages/cloudflare-integration-tests/suites/public-api/startSpan/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/public-api/startSpan/test.ts @@ -1,5 +1,6 @@ import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; import { + CLIENT_ADDRESS, SENTRY_SEGMENT_NAME_SOURCE, SENTRY_SDK_NAME, SENTRY_SDK_VERSION, @@ -205,6 +206,15 @@ it('sends a streamed span envelope with correct spans for a manually started spa type: 'string', value: 'localhost', }, + // From the `CF-Connecting-IP` header that `wrangler dev` sets. + [CLIENT_ADDRESS]: { + type: 'string', + value: '127.0.0.1', + }, + 'http.response.header.content-type': { + type: 'array', + value: ['text/plain;charset=UTF-8'], + }, 'url.full': { type: 'string', value: expect.stringMatching(/^http:\/\/localhost:.+$/), diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/opentelemetry-tracer/subpath-request-handler/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/opentelemetry-tracer/subpath-request-handler/test.ts index 596ae6c6ef6d..27e35a07270f 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/opentelemetry-tracer/subpath-request-handler/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/opentelemetry-tracer/subpath-request-handler/test.ts @@ -1,16 +1,19 @@ -import type { Event } from '@sentry/core'; +import { HTTP_SERVER } from '@sentry/conventions/op'; import { expect, it } from 'vitest'; import { createRunner } from '../../../../runner'; +import { getSpanOp, getSpansFromEnvelope } from '../../../../spanUtils'; it('does not capture spans emitted through @opentelemetry/api inside wrapRequestHandler from the /request subpath', async ({ signal, }) => { const runner = createRunner(__dirname) .expect(envelope => { - const event = envelope[1]?.[0]?.[1] as Event; - expect(event.transaction).toBe('GET /'); - expect(event.contexts?.trace?.op).toBe('http.server'); - expect(event.spans).toEqual([]); + // Only the request's segment span. The OpenTelemetry span is not captured. + const spans = getSpansFromEnvelope(envelope); + expect(spans).toHaveLength(1); + expect(spans[0]?.is_segment).toBe(true); + expect(spans[0]?.name).toBe('GET /'); + expect(getSpanOp(spans[0]!)).toBe(HTTP_SERVER); }) .start(signal); diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index 887f9582466f..cfdca8dca964 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -2,7 +2,7 @@ import type { Client, Integration, IntegrationFn, MaxRequestBodySize, Span, Span import { debug, defineIntegration, getClient, withIsolationScope } from '@sentry/core'; import type { AssertTrue, CheckServerRequestOptions, ServerRequestOptions } from '@sentry/core/server'; import { URL_PATH_PARAMETER_KEY_BASE } from '@sentry/conventions/attributes'; -import { wrapServerRequestHandler } from '@sentry/server-utils'; +import { wrapServerRequestHandler } from '@sentry/server-utils/request-handler'; import type { Server, ServeOptions } from 'bun'; const INTEGRATION_NAME = 'BunServer' as const; diff --git a/packages/cloudflare/src/integrations/httpServer.ts b/packages/cloudflare/src/integrations/httpServer.ts index 1975f168819e..8f7afaacd29f 100644 --- a/packages/cloudflare/src/integrations/httpServer.ts +++ b/packages/cloudflare/src/integrations/httpServer.ts @@ -1,15 +1,6 @@ -import type { Client, IntegrationFn, MaxRequestBodySize, Span } from '@sentry/core'; -import { - captureBodyFromWinterCGRequest, - defineIntegration, - getIsolationScope, - hasSpansEnabled, - parseStringToURLObject, - safeCallback, -} from '@sentry/core'; -import type { AssertTrue, CheckServerRequestOptions } from '@sentry/core/server'; -import { isStaticAssetRequest } from '@sentry/core/server'; -import { DEBUG_BUILD } from '../debug-build'; +import type { Client, Integration, IntegrationFn, MaxRequestBodySize, Span } from '@sentry/core'; +import { defineIntegration } from '@sentry/core'; +import type { AssertTrue, CheckServerRequestOptions, ServerRequestOptions } from '@sentry/core/server'; const INTEGRATION_NAME = 'HttpServer' as const; @@ -100,21 +91,46 @@ type _ServerRequestOptionsCheck = AssertTrue< > >; -interface HttpServerIntegrationInstance extends HttpServerIntegrationOptions { - name: string; - maxRequestBodySize: MaxRequestBodySize | undefined; +type SupportedServerRequestOptions = Omit< + ServerRequestOptions, + 'sessions' | 'sessionFlushingDelayMS' +>; + +// Every supported shared option is a required key, so a new one fails to compile below until the +// integration passes it on. +type HttpServerRequestOptions = { + [K in keyof Required]: SupportedServerRequestOptions[K]; +}; + +interface HttpServerIntegration extends Integration { + requestOptions: HttpServerRequestOptions; +} + +/** + * The incoming request options of the client's `httpServerIntegration`. If the integration does not + * set `maxRequestBodySize`, `dataCollection.httpBodies` decides. Without the integration, the SDK + * attaches no request body. + */ +export function getHttpServerRequestOptions(client: Client): ServerRequestOptions { + return ( + client.getIntegrationByName(INTEGRATION_NAME)?.requestOptions ?? { + maxRequestBodySize: 'none', + } + ); } -const _httpServerIntegration = ((options: HttpServerIntegrationOptions = {}): HttpServerIntegrationInstance => { +const _httpServerIntegration = ((options: HttpServerIntegrationOptions = {}): HttpServerIntegration => { return { name: INTEGRATION_NAME, - maxRequestBodySize: options.maxRequestBodySize, - ignoreRequestBody: options.ignoreRequestBody, - ignoreIncomingRequests: options.ignoreIncomingRequests, - ignoreStaticAssets: options.ignoreStaticAssets ?? true, - spans: options.spans, - onSpanCreated: options.onSpanCreated, - onSpanEnd: options.onSpanEnd, + requestOptions: { + maxRequestBodySize: options.maxRequestBodySize, + ignoreRequestBody: options.ignoreRequestBody, + ignoreIncomingRequests: options.ignoreIncomingRequests, + ignoreStaticAssets: options.ignoreStaticAssets ?? true, + spans: options.spans, + onSpanCreated: options.onSpanCreated, + onSpanEnd: options.onSpanEnd, + }, }; }) satisfies IntegrationFn; @@ -137,102 +153,3 @@ const _httpServerIntegration = ((options: HttpServerIntegrationOptions = {}): Ht * ``` */ export const httpServerIntegration = defineIntegration(_httpServerIntegration); - -/** - * Capture the request body based on the HttpServer integration config. - * Called internally by `wrapRequestHandler`. - */ -export async function captureIncomingRequestBody(client: Client, request: Request): Promise { - const integration = client.getIntegrationByName(INTEGRATION_NAME); - - if (!integration) { - return; - } - - const configuredBodySize = integration.maxRequestBodySize; - const effectiveBodySize: MaxRequestBodySize = - configuredBodySize ?? - (client.getDataCollectionOptions().httpBodies.includes('incomingRequest') ? 'medium' : 'none'); - - if (effectiveBodySize === 'none') { - return; - } - - // Skip GET and HEAD requests - they don't have bodies - // Also skip OPTIONS, even if they may have a body, they might not give a lot of extra value - if (request.method === 'GET' || request.method === 'HEAD' || request.method === 'OPTIONS') { - return; - } - - const ignoreRequestBody = integration.ignoreRequestBody; - if ( - ignoreRequestBody && - // If the filter throws, skip the body so it cannot leak data the filter was meant to hide. - safeCallback( - DEBUG_BUILD ? 'Error in `ignoreRequestBody`, skipping the body:' : '', - () => ignoreRequestBody(request.url, request), - () => true, - ) - ) { - return; - } - - const isolationScope = getIsolationScope(); - await captureBodyFromWinterCGRequest(request, isolationScope, effectiveBodySize); -} - -/** - * Whether to create a span for the incoming request, based on the HttpServer integration config. - * Called internally by `wrapRequestHandler`. - */ -export function shouldCreateIncomingRequestSpan(client: Client | undefined, request: Request): boolean { - if (!client) { - return false; - } - - const integration = client.getIntegrationByName(INTEGRATION_NAME); - if (!(integration?.spans ?? hasSpansEnabled(client.getOptions()))) { - return false; - } - - const urlObject = parseStringToURLObject(request.url); - const urlPath = urlObject ? `${urlObject.pathname}${urlObject.search}` : '/'; - if (integration?.ignoreStaticAssets && request.method === 'GET' && isStaticAssetRequest(urlPath)) { - return false; - } - - const ignoreIncomingRequests = integration?.ignoreIncomingRequests; - return !( - ignoreIncomingRequests && - safeCallback( - DEBUG_BUILD ? 'Error in `ignoreIncomingRequests`, creating the span:' : '', - () => ignoreIncomingRequests(urlPath, request), - () => false, - ) - ); -} - -/** - * The `onSpanCreated` and `onSpanEnd` hooks of the HttpServer integration, guarded so that a hook - * that throws does not fail the request. Called internally by `wrapRequestHandler`. - */ -export function getIncomingRequestSpanHooks(client: Client | undefined): { - onSpanCreated: (span: Span, request: Request) => void; - onSpanEnd: (span: Span, request: Request, response: Response) => void; -} { - const integration = client?.getIntegrationByName(INTEGRATION_NAME); - return { - onSpanCreated: (span, request) => - safeCallback( - DEBUG_BUILD ? 'Error in `onSpanCreated`:' : '', - () => integration?.onSpanCreated?.(span, request), - () => undefined, - ), - onSpanEnd: (span, request, response) => - safeCallback( - DEBUG_BUILD ? 'Error in `onSpanEnd`:' : '', - () => integration?.onSpanEnd?.(span, request, response), - () => undefined, - ), - }; -} diff --git a/packages/cloudflare/src/scope-utils.ts b/packages/cloudflare/src/scope-utils.ts index 540a4589c1a5..22a3e05aebc7 100644 --- a/packages/cloudflare/src/scope-utils.ts +++ b/packages/cloudflare/src/scope-utils.ts @@ -1,7 +1,6 @@ import { CLOUD_PROVIDER } from '@sentry/conventions/attributes'; import type { CfProperties, IncomingRequestCfProperties } from '@cloudflare/workers-types'; import type { Scope } from '@sentry/core'; -import { winterCGRequestToRequestData } from '@sentry/core'; /** * Set cloud resource context on scope. @@ -20,10 +19,3 @@ export function addCultureContext(scope: Scope, cf: IncomingRequestCfProperties timezone: cf.timezone, }); } - -/** - * Set request data on scope - */ -export function addRequest(scope: Scope, request: Request): void { - scope.setSDKProcessingMetadata({ normalizedRequest: winterCGRequestToRequestData(request) }); -} diff --git a/packages/cloudflare/src/wrapRequestHandlerWithInit.ts b/packages/cloudflare/src/wrapRequestHandlerWithInit.ts index 9c2126b9add2..1f3ddde1dd6d 100644 --- a/packages/cloudflare/src/wrapRequestHandlerWithInit.ts +++ b/packages/cloudflare/src/wrapRequestHandlerWithInit.ts @@ -1,39 +1,15 @@ import type { CfProperties, IncomingRequestCfProperties } from '@cloudflare/workers-types'; -import { - NETWORK_PROTOCOL_NAME, - NETWORK_PROTOCOL_VERSION, - SENTRY_OP, - SENTRY_SEGMENT_NAME_SOURCE, - HTTP_REQUEST_BODY_SIZE, - USER_AGENT_ORIGINAL, -} from '@sentry/conventions/attributes'; -import { HTTP_SERVER } from '@sentry/conventions/op'; -import type { Span } from '@sentry/core'; -import { - captureException, - continueTrace, - getHttpSpanDetailsFromUrlObject, - hasSpanStreamingEnabled, - httpHeadersToSpanAttributes, - HTTP_SPAN_NAME_FALLBACK, - parseStringToURLObject, - safeCallback, - setHttpStatus, - startSpanManual, - winterCGHeadersToDict, -} from '@sentry/core'; -import { classifyResponseStreaming } from '@sentry/core/server'; +import { NETWORK_PROTOCOL_NAME, NETWORK_PROTOCOL_VERSION } from '@sentry/conventions/attributes'; +import type { SpanAttributes } from '@sentry/core'; +import { getIsolationScope, safeCallback } from '@sentry/core'; +import { wrapServerRequestHandler } from '@sentry/server-utils/request-handler'; import { DEBUG_BUILD } from './debug-build'; -import { - captureIncomingRequestBody, - getIncomingRequestSpanHooks, - shouldCreateIncomingRequestSpan, -} from './integrations/httpServer'; +import { getHttpServerRequestOptions } from './integrations/httpServer'; import { flushDeferredChannelEvents } from './orchestrion-deferred-channels'; import type { CloudflareClient, CloudflareOptions } from './client'; import type { ExecutionContextCompat } from './executionContext'; import { flushAndDispose, getOriginalWaitUntil } from './flush'; -import { addCloudResourceContext, addCultureContext, addRequest } from './scope-utils'; +import { addCloudResourceContext, addCultureContext } from './scope-utils'; import { getInvocationState, getInvocationWaitUntil } from './utils/invocationContext'; import { withInvocationIsolationScope } from './utils/invocationScope'; @@ -87,7 +63,6 @@ export function wrapRequestHandlerWithInit( : context ? getOriginalWaitUntil(context).bind(context) : undefined; - const errorMechanismType = getRequestErrorMechanismType(context); const client = initSdk({ ...options, ctx: context }); isolationScope.setClient(client); @@ -97,43 +72,8 @@ export function wrapRequestHandlerWithInit( // request, where workerd allows publish, and init() above has registered subscribers. flushDeferredChannelEvents(); - const urlObject = parseStringToURLObject(request.url); - const [rawName, attributes] = getHttpSpanDetailsFromUrlObject( - urlObject, - 'server', - 'auto.http.cloudflare', - request, - undefined, - client, - ); - // With span streaming, span names have to be low cardinality, so we can't fall back to the URL. - // A `route` source means the name already is (e.g. the `/` path), so it is kept as-is. - const name = - attributes[SENTRY_SEGMENT_NAME_SOURCE] === 'route' || !client || !hasSpanStreamingEnabled(client) - ? rawName - : request.method?.toUpperCase() || HTTP_SPAN_NAME_FALLBACK; - - const contentLength = request.headers.get('content-length'); - if (contentLength) { - attributes[HTTP_REQUEST_BODY_SIZE] = parseInt(contentLength, 10); - } - - const userAgentHeader = request.headers.get('user-agent'); - if (userAgentHeader) { - attributes[USER_AGENT_ORIGINAL] = userAgentHeader; - } - - if (client) { - Object.assign( - attributes, - httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), client.getDataCollectionOptions()), - ); - } - - attributes[SENTRY_OP] = HTTP_SERVER; - addCloudResourceContext(isolationScope); - addRequest(isolationScope, request); + const attributes: SpanAttributes = {}; if (request.cf) { addCultureContext(isolationScope, request.cf); @@ -144,143 +84,50 @@ export function wrapRequestHandlerWithInit( } } - // Do not capture spans for OPTIONS and HEAD requests - if (request.method === 'OPTIONS' || request.method === 'HEAD') { + if (!client) { try { return await handler(); - } catch (e) { - if (captureErrors) { - captureException(e, { mechanism: { handled: false, type: errorMechanismType } }); - } - throw e; } finally { waitUntil?.(flushAndDispose(client)); } } - if (client) { - await captureIncomingRequestBody(client, request); - } - - const { onSpanCreated, onSpanEnd } = getIncomingRequestSpanHooks(client); - - // Runs the handler. Ends the span (if any) and flushes when the response is done. - const respond = async (span: Span | undefined): Promise => { - let res: Response; - - try { - res = await handler(); - if (span) { - setHttpStatus(span, res.status); - } - - // After the handler runs, the span name might have been updated by nested instrumentation - // (e.g., Remix parameterizing routes). The span should already have the correct name - // from that instrumentation, so we don't need to do anything here. - } catch (e) { - span?.end(); - if (captureErrors) { - captureException(e, { mechanism: { handled: false, type: errorMechanismType } }); - } - waitUntil?.(flushAndDispose(client)); - throw e; - } - - let ended = false; - const endSpan = (): void => { - if (ended) return; - - ended = true; - if (span) { - onSpanEnd(span, request, res); - span.end(); - } - }; - - const userClassification = safeCallback( - DEBUG_BUILD ? 'Error in `isStreamingResponse`, using the default classification:' : '', - () => options.isStreamingResponse?.(res, request), - () => undefined, - ); - // An async callback returns a promise, which would otherwise count as `true`. - const isStreaming = - typeof userClassification === 'boolean' ? userClassification : classifyResponseStreaming(res).isStreaming; - - if (isStreaming && res.body) { - try { - let transformerUsed = false; - - const endSpanOnce = (): void => { - if (ended) return; - - endSpan(); + // Note: The span will not have a duration unless I/O happens in the handler. This is + // because of how the cloudflare workers runtime works. + // See: https://developers.cloudflare.com/workers/runtime-apis/performance/ + return wrapServerRequestHandler( + { + request, + client, + // Without `AsyncLocalStorage`, `withInvocationIsolationScope` does not fork, and the scope it + // passes in is not the current one. The request data has to go on the current scope, where + // events captured in the handler read it. + isolationScope: getIsolationScope(), + options: getHttpServerRequestOptions(client), + origin: 'auto.http.cloudflare', + mechanism: captureErrors ? { handled: false, type: getRequestErrorMechanismType(context) } : undefined, + attributes, + isStreamingResponse: response => { + const classification = safeCallback( + DEBUG_BUILD ? 'Error in `isStreamingResponse`, using the default classification:' : '', + () => options.isStreamingResponse?.(response, request), + () => undefined, + ); + // An async callback returns a promise, which would otherwise count as `true`. + return typeof classification === 'boolean' ? classification : undefined; + }, + onDone: response => { + // Don't dispose for protocol upgrades (101 Switching Protocols) - the connection stays alive. + // This includes WebSocket upgrades where webSocketMessage/webSocketClose handlers + // will still be called and may need the client to capture errors. + if (response?.status === 101) { + waitUntil?.(client.flush(2000)); + } else { waitUntil?.(flushAndDispose(client)); - }; - - // Workers with a compatibility date before 2022-11-30 and without the - // `transformstream_enable_standard_constructor` flag (for example under older mini-oxygen - // releases) ignore the transformer, so `flush` and `cancel` would never end the span. Only a - // used transformer runs `start`. - const transform = new TransformStream({ - start() { - transformerUsed = true; - }, - flush() { - // Source stream completed normally. - endSpanOnce(); - }, - cancel() { - // Client disconnected (or downstream cancelled). The `cancel` - // is being called while the response is still considered - // active, so this is a safe place to end the span. - endSpanOnce(); - }, - }); - - if (transformerUsed) { - // Passing the original response as the init keeps `encodeBody: 'manual'`, which a - // Response does not expose as a property. Without it, workerd compresses a - // pre-compressed body a second time. - return new Response(res.body.pipeThrough(transform), res); } - } catch { - // Falls back to ending the span at handler return. - } - } - - // Non-streaming response - end span immediately and return original - endSpan(); - - // Don't dispose for protocol upgrades (101 Switching Protocols) - the connection stays alive. - // This includes WebSocket upgrades where webSocketMessage/webSocketClose handlers - // will still be called and may need the client to capture errors. - if (res.status === 101) { - waitUntil?.(client?.flush(2000)); - } else { - waitUntil?.(flushAndDispose(client)); - } - return res; - }; - - const createSpan = shouldCreateIncomingRequestSpan(client, request); - - return continueTrace( - { sentryTrace: request.headers.get('sentry-trace') || '', baggage: request.headers.get('baggage') }, - () => { - if (!createSpan) { - return respond(undefined); - } - - // Note: This span will not have a duration unless I/O happens in the handler. This is - // because of how the cloudflare workers runtime works. - // See: https://developers.cloudflare.com/workers/runtime-apis/performance/ - - // Use startSpanManual to control when span ends (needed for streaming responses) - return startSpanManual({ name, attributes }, span => { - onSpanCreated(span, request); - return respond(span); - }); + }, }, + () => handler(), ); }, wrapperOptions.context); } diff --git a/packages/cloudflare/test/request-stack-strategy.test.ts b/packages/cloudflare/test/request-stack-strategy.test.ts new file mode 100644 index 000000000000..3e3cda68c479 --- /dev/null +++ b/packages/cloudflare/test/request-stack-strategy.test.ts @@ -0,0 +1,45 @@ +// This file sets no async context strategy, so the SDK uses the stack fallback, as on runtimes +// without `AsyncLocalStorage` (for example Shopify Oxygen, or Workers without `nodejs_compat`). + +import type { Event } from '@sentry/core'; +import * as SentryCore from '@sentry/core'; +import { expect, test, vi } from 'vitest'; +import { httpServerIntegration, wrapRequestHandler } from '../src/request'; + +test('attaches the request data and body to events captured in the handler without AsyncLocalStorage', async () => { + let event: Event | undefined; + const body = JSON.stringify({ payload: 'x'.repeat(4_000) }); + + await wrapRequestHandler( + { + options: { + dsn: 'https://public@dsn.ingest.sentry.io/1337', + cacheClient: false, + integrations: [httpServerIntegration({ maxRequestBodySize: 'small' })], + beforeSend(captured) { + event = captured; + return null; + }, + }, + request: new Request('https://example.com/action', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body, + }), + context: { waitUntil: vi.fn(), passThroughOnException: vi.fn() } as never, + }, + () => { + SentryCore.captureException(new Error('Action Error')); + return new Response('error', { status: 500 }); + }, + ); + + expect(event?.request).toEqual( + expect.objectContaining({ + method: 'POST', + url: 'https://example.com/action', + // `maxRequestBodySize: 'small'` truncates at 1,000 bytes. + data: `${body.slice(0, 997)}...`, + }), + ); +}); diff --git a/packages/cloudflare/test/request.test.ts b/packages/cloudflare/test/request.test.ts index 972180c62a8c..d05ad7420d44 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -677,6 +677,7 @@ describe('withSentry', () => { 'http.response.status_code': 200, 'http.request.body.size': 10, 'http.request.header.content-length': ['10'], + 'http.response.header.content-type': ['text/plain;charset=UTF-8'], }, op: 'http.server', origin: 'auto.http.cloudflare', @@ -1297,13 +1298,16 @@ describe('flushAndDispose', () => { }, ); - test('tears down at handler return when TransformStream ignores the transformer', async () => { - // workerd without `transformstream_enable_standard_constructor` returns an identity stream. + test('tears down at handler return when the ReadableStream constructor is not available', async () => { + const response = new Response('
shell
', { + headers: { 'content-type': 'text/x-component;charset=utf-8' }, + }); + // workerd before the 2022-11-30 compatibility date, without `streams_enable_constructors`. vi.stubGlobal( - 'TransformStream', - class extends TransformStream { + 'ReadableStream', + class { constructor() { - super(); + throw new TypeError('To use the new ReadableStream() constructor, enable streams_enable_constructors.'); } }, ); @@ -1316,16 +1320,14 @@ describe('flushAndDispose', () => { const flushSpy = vi.spyOn(SentryCore.Client.prototype, 'flush').mockResolvedValue(true); - const response = new Response('
shell
', { - headers: { 'content-type': 'text/x-component;charset=utf-8' }, - }); - const result = await wrapRequestHandler( { options, request: new Request('https://example.com'), context }, () => response, ); expect(result).toBe(response); + expect(result.bodyUsed).toBe(false); + expect(result.body?.locked).toBe(false); expect(waitUntil).toHaveBeenCalledTimes(1); flushSpy.mockRestore(); diff --git a/packages/cloudflare/test/requestModuleGraph.test.ts b/packages/cloudflare/test/requestModuleGraph.test.ts index 64827ebc8b5c..8b021862a819 100644 --- a/packages/cloudflare/test/requestModuleGraph.test.ts +++ b/packages/cloudflare/test/requestModuleGraph.test.ts @@ -69,7 +69,12 @@ describe('module graph of `wrapRequestHandler`', () => { it('contains no package that depends on Node.js APIs', () => { // `@sentry/server-utils` subscribes to `node:diagnostics_channel` and `@sentry/node` needs Node.js // throughout. Both are only allowed in `sdk.ts`, `index.ts` and `vite/` (see `.oxlintrc.json`). - expect(externals.filter(specifier => /^@sentry\/(node|server-utils)(\/|$)/.test(specifier))).toEqual([]); + // The `request-handler` entry of `@sentry/server-utils` has no Node.js imports. + const nodePackages = externals.filter( + specifier => + /^@sentry\/(node|server-utils)(\/|$)/.test(specifier) && specifier !== '@sentry/server-utils/request-handler', + ); + expect(nodePackages).toEqual([]); }); it('does not reach `async.ts`, the only shipped module importing `node:async_hooks`', () => { diff --git a/packages/deno/src/wrap-deno-request-handler.ts b/packages/deno/src/wrap-deno-request-handler.ts index 4d24405d58e4..e050c5523a2d 100644 --- a/packages/deno/src/wrap-deno-request-handler.ts +++ b/packages/deno/src/wrap-deno-request-handler.ts @@ -1,7 +1,7 @@ import type { Client } from '@sentry/core'; import { debug, getClient, withIsolationScope } from '@sentry/core'; import type { ServerRequestOptions } from '@sentry/core/server'; -import { wrapServerRequestHandler } from '@sentry/server-utils'; +import { wrapServerRequestHandler } from '@sentry/server-utils/request-handler'; export type RequestHandlerWrapperOptions = { request: Request; diff --git a/packages/server-utils/package.json b/packages/server-utils/package.json index 0b0307509a36..8a19ea2e4746 100644 --- a/packages/server-utils/package.json +++ b/packages/server-utils/package.json @@ -27,6 +27,11 @@ "import": "./build/esm/index.no-diagnostic-channels.js", "require": "./build/cjs/index.no-diagnostic-channels.js" }, + "./request-handler": { + "types": "./build/types/index.request-handler.d.ts", + "import": "./build/esm/index.request-handler.js", + "require": "./build/cjs/index.request-handler.js" + }, "./orchestrion/config": { "types": "./build/types/orchestrion/config/index.d.ts", "import": "./build/esm/orchestrion/config/index.js", diff --git a/packages/server-utils/rollup.npm.config.mjs b/packages/server-utils/rollup.npm.config.mjs index ab0a51d3ef21..74f172ec5215 100644 --- a/packages/server-utils/rollup.npm.config.mjs +++ b/packages/server-utils/rollup.npm.config.mjs @@ -100,6 +100,7 @@ export default [ entrypoints: [ 'src/index.ts', 'src/index.no-diagnostic-channels.ts', + 'src/index.request-handler.ts', 'src/orchestrion/config/index.ts', 'src/orchestrion/bundler/vite.ts', 'src/orchestrion/bundler/rollup.ts', diff --git a/packages/server-utils/src/exports.ts b/packages/server-utils/src/exports.ts index c22a5f605d68..8cd8bf657c95 100644 --- a/packages/server-utils/src/exports.ts +++ b/packages/server-utils/src/exports.ts @@ -1,7 +1,5 @@ // Shared exports not using diagnostics channels export { setHttpServerSpanRouteAttribute } from './utils/setHttpServerSpanRouteAttribute'; -export { wrapServerRequestHandler } from './utils/wrapServerRequestHandler'; -export type { ServerRequestHandlerConfig } from './utils/wrapServerRequestHandler'; export { setAsyncLocalStorageAsyncContextStrategy } from './async-context'; export { openTelemetryIntegration, getOtlpTracesEndpoint } from './opentelemetry'; export * from './ai'; @@ -13,7 +11,7 @@ export type { PostgresConnectionContext } from './integrations/postgresjs'; // Shared, runtime-agnostic Hono instrumentation re-used by the `@sentry/hono` SDK across all runtimes. // Sourced directly from their modules (not the `./integrations/hono` barrel, which also exports the // diagnostics-channel-based `honoIntegration`) so the non-Node adapters can import them from the -// Node-free `@sentry/server-utils/no-diagnostic-channels` entry. +// diagnostics_channel-free `@sentry/server-utils/no-diagnostic-channels` entry. export { applyHonoPatches, earlyPatchHono } from './integrations/hono/applyPatches'; export { createHonoRequestMiddleware } from './integrations/hono/createHonoMiddleware'; export type { CreateHonoRequestMiddlewareOptions } from './integrations/hono/createHonoMiddleware'; diff --git a/packages/server-utils/src/index.request-handler.ts b/packages/server-utils/src/index.request-handler.ts new file mode 100644 index 000000000000..73e0902cc088 --- /dev/null +++ b/packages/server-utils/src/index.request-handler.ts @@ -0,0 +1,4 @@ +// The web `Request` handler wrapper, on its own entry with no Node.js imports, so Cloudflare can use +// it on workers without the `nodejs_compat` compatibility flag. +export { wrapServerRequestHandler } from './utils/wrapServerRequestHandler'; +export type { ServerRequestHandlerConfig } from './utils/wrapServerRequestHandler'; diff --git a/packages/server-utils/src/utils/monitorStream.ts b/packages/server-utils/src/utils/monitorStream.ts index 6b4651f990d1..20a69a15e79a 100644 --- a/packages/server-utils/src/utils/monitorStream.ts +++ b/packages/server-utils/src/utils/monitorStream.ts @@ -1,39 +1,68 @@ -import { isError } from '@sentry/core'; +import { debug, isError } from '@sentry/core'; +import { DEBUG_BUILD } from '../debug-build'; -/** Monitor the source without reading ahead or treating cancellation as a source failure. */ +/** + * Monitor the source without reading ahead or treating cancellation as a source failure. + * + * `onDone` runs once, while the returned stream is still open: before it closes, before it errors, + * or when the consumer cancels it. A runtime can end the request when the response body closes + * (Cloudflare Workers then reject `waitUntil`), so work that `onDone` starts must begin first. + */ export function monitorStream( stream: ReadableStream, onDone: () => void, onError: (error: unknown) => void, ): ReadableStream { - const reader = stream.getReader(); - // A cancelled reader closes normally; only a source failure should be captured as an error. - // A source that stops on `request.signal` errors with an `AbortError` when the client - // disconnects, which is a cancellation too. - void reader.closed.then(onDone, error => { - if (!(isError(error) && error.name === 'AbortError')) { - onError(error); + let reader: ReadableStreamDefaultReader; + let finished = false; + const finish = (error?: { error: unknown }): void => { + if (finished) { + return; } - onDone(); - }); + finished = true; + // A callback that throws must not fail the response body. + try { + // A source that stops on `request.signal` errors with an `AbortError` when the client + // disconnects, which is a cancellation too. + if (error && !(isError(error.error) && error.error.name === 'AbortError')) { + onError(error.error); + } + onDone(); + } catch (e) { + DEBUG_BUILD && debug.error('Error when a monitored stream finished:', e); + } + }; return new ReadableStream( { + // Locks the source only once the stream exists, so a constructor that throws leaves it usable. + start() { + reader = stream.getReader(); + // A source can fail while no one reads from it. A cancelled reader closes normally; only a + // source failure should be captured as an error. + void reader.closed.then( + () => finish(), + error => finish({ error }), + ); + }, async pull(controller) { try { const { done, value } = await reader.read(); if (done) { + finish(); controller.close(); reader.releaseLock(); } else { controller.enqueue(value); } } catch (error) { + finish({ error }); controller.error(error); reader.releaseLock(); } }, async cancel(reason) { + finish(); try { await reader.cancel(reason); } finally { diff --git a/packages/server-utils/src/utils/wrapServerRequestHandler.ts b/packages/server-utils/src/utils/wrapServerRequestHandler.ts index 447764ddf5e5..e519141b0de4 100644 --- a/packages/server-utils/src/utils/wrapServerRequestHandler.ts +++ b/packages/server-utils/src/utils/wrapServerRequestHandler.ts @@ -8,11 +8,12 @@ import { USER_AGENT_ORIGINAL, } from '@sentry/conventions/attributes'; import { HTTP_SERVER } from '@sentry/conventions/op'; -import type { Client, Mechanism, Scope, Span, SpanAttributes } from '@sentry/core'; +import type { Client, Mechanism, Scope, SpanAttributes } from '@sentry/core'; import { captureBodyFromWinterCGRequest, captureException, continueTrace, + getCurrentScope, getHttpSpanDetailsFromUrlObject, hasSpansEnabled, hasSpanStreamingEnabled, @@ -25,6 +26,8 @@ import { startSpanManual, winterCGHeadersToDict, winterCGRequestToRequestData, + withIsolationScope, + withScope, } from '@sentry/core'; import type { ServerRequestOptions } from '@sentry/core/server'; import { @@ -45,14 +48,21 @@ export interface ServerRequestHandlerConfig { options: ServerRequestOptions | undefined; /** Origin of the span, e.g. `auto.http.deno`. */ origin: string; - /** Mechanism of errors that the handler or the response stream throws. */ - mechanism: Mechanism; + /** Mechanism of errors that the handler or the response stream throws. Omit to not capture them. */ + mechanism?: Mechanism; /** The address and port of the socket, if the runtime knows them. */ getSocketAddress?: () => { address: string; port?: number } | undefined; /** The matched route of the server framework, e.g. `/users/:id`. Sets `http.route` and the span name. */ routeName?: string; /** Span attributes that only the runtime knows, e.g. the route parameters. */ attributes?: SpanAttributes; + /** Whether the response streams. Return `undefined` to use the default classification. */ + isStreamingResponse?: (response: Response) => boolean | undefined; + /** + * Called once when the request is done: when the response is returned, when a streamed body + * finishes, or when the handler throws (then without a response). + */ + onDone?: (response: Response | undefined) => void; } /** @@ -74,6 +84,11 @@ export async function wrapServerRequestHandler { const { request, client, isolationScope, options, mechanism } = config; const method = request.method.toUpperCase(); + const captureError = (error: unknown): void => { + if (mechanism) { + captureException(error, { mechanism }); + } + }; const endSession = options?.sessions ? startRequestSession(client, { @@ -81,21 +96,34 @@ export async function wrapServerRequestHandler { + endSession?.(); + config.onDone?.(response); + }; + const isStreamingResponse = (response: Response): boolean => { + const classification = config.isStreamingResponse?.(response); + return typeof classification === 'boolean' ? classification : classifyResponseStreaming(response).isStreaming; + }; const socketAddress = config.getSocketAddress?.(); + // Without `AsyncLocalStorage`, all requests share one isolation scope, so clear what an earlier + // request left on it: the merge keeps its body, and its response context would stay set. isolationScope.setSDKProcessingMetadata({ - normalizedRequest: winterCGRequestToRequestData(request), + normalizedRequest: { ...winterCGRequestToRequestData(request), data: undefined }, ipAddress: socketAddress?.address, }); + isolationScope.setContext('response', null); if (method === 'OPTIONS' || method === 'HEAD') { + let response: R | undefined; try { - return await handler(); + response = await handler(); + return response; } catch (e) { - captureException(e, { mechanism }); + captureError(e); throw e; } finally { - endSession?.(); + finish(response); } } @@ -134,14 +162,20 @@ export async function wrapServerRequestHandler endSession?.()); + const inRequestScope = bindToRequestScope(isolationScope); + return onResponseDone( + response, + !!response && isStreamingResponse(response), + inRequestScope(() => finish(response)), + inRequestScope(captureError), + ); } const [rawName, attributes] = getHttpSpanDetailsFromUrlObject( @@ -206,8 +240,8 @@ export async function wrapServerRequestHandler { + !!response && isStreamingResponse(response), + inRequestScope(() => { if (onSpanEnd && response) { safeCallback( DEBUG_BUILD ? 'Error in `onSpanEnd`:' : '', @@ -231,15 +266,35 @@ export async function wrapServerRequestHandler { + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + captureError(error); + }), ); }); }, ); } +/** + * Returns a function that binds a callback to the request's isolation scope and the current scope. + * + * A streamed body finishes after the handler returns. Without `AsyncLocalStorage` (Cloudflare + * Workers without `nodejs_compat`), its callbacks then run outside the request's scopes, where + * `getClient()` finds no client: the segment span would be sent as a transaction, and a captured + * error would not update the request session. + */ +function bindToRequestScope( + isolationScope: Scope, +): (fn: (...args: A) => void) => (...args: A) => void { + const scope = getCurrentScope(); + return fn => + (...args) => + withIsolationScope(isolationScope, () => withScope(scope, () => fn(...args))); +} + function shouldCreateSpan( client: Client, options: ServerRequestOptions | undefined, @@ -269,17 +324,19 @@ function shouldCreateSpan( */ function onResponseDone( response: R, - mechanism: Mechanism, + isStreaming: boolean, onDone: () => void, - span?: Span, + onStreamError: (error: unknown) => void, ): R { - if (response?.body && !response.body.locked && classifyResponseStreaming(response).isStreaming) { - const body = monitorStream(response.body, onDone, error => { - span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - captureException(error, { mechanism }); - }); - // Passing the original response as the init keeps its status, status text and headers. - return new Response(body, response) as R; + if (isStreaming && response?.body && !response.body.locked) { + try { + // Passing the original response as the init keeps its status, status text and headers, and + // on Cloudflare also `encodeBody: 'manual'`, so workerd does not compress a compressed body again. + return new Response(monitorStream(response.body, onDone, onStreamError), response) as R; + } catch { + // Workers with a compatibility date before 2022-11-30 and without the + // `streams_enable_constructors` flag cannot construct a `ReadableStream`, so end at once. + } } onDone(); return response; diff --git a/packages/server-utils/test/requestHandlerEntry.test.ts b/packages/server-utils/test/requestHandlerEntry.test.ts new file mode 100644 index 000000000000..9df030fa3420 --- /dev/null +++ b/packages/server-utils/test/requestHandlerEntry.test.ts @@ -0,0 +1,53 @@ +import { builtinModules } from 'node:module'; +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +/** + * Cloudflare imports the `request-handler` entry on workers without the `nodejs_compat` compatibility + * flag, where any Node.js import fails at startup. Assert on the module graph itself rather than on + * any bundler's output. + */ +const ENTRY_POINT = resolve(__dirname, '../src/index.request-handler.ts'); +const IMPORT_SPECIFIER_REGEX = /\bfrom\s*['"]([^'"]+)['"]|\bimport\s*['"]([^'"]+)['"]/g; + +function collectExternals(entryPoint: string): string[] { + const modules = new Set(); + const externals = new Set(); + const queue = [entryPoint]; + + while (queue.length) { + const importer = queue.pop() as string; + if (modules.has(importer)) { + continue; + } + modules.add(importer); + + for (const match of readFileSync(importer, 'utf8').matchAll(IMPORT_SPECIFIER_REGEX)) { + const specifier = match[1] ?? (match[2] as string); + if (specifier.startsWith('.')) { + const withoutExtension = join(dirname(importer), specifier); + const resolved = [`${withoutExtension}.ts`, join(withoutExtension, 'index.ts')].find(existsSync); + if (!resolved) { + throw new Error(`Could not resolve '${specifier}' imported from '${importer}'`); + } + queue.push(resolved); + } else { + externals.add(specifier); + } + } + } + + return [...externals].sort(); +} + +describe('module graph of the `request-handler` entry', () => { + it('contains no Node.js built-in module and no package that needs Node.js', () => { + const externals = collectExternals(ENTRY_POINT); + + expect(externals.filter(specifier => specifier.startsWith('node:') || builtinModules.includes(specifier))).toEqual( + [], + ); + expect(externals.filter(specifier => /^@sentry\/(node|server-utils)(\/|$)/.test(specifier))).toEqual([]); + }); +}); diff --git a/packages/server-utils/test/utils/monitorStream.test.ts b/packages/server-utils/test/utils/monitorStream.test.ts index fee33b3cec75..d0921d34833d 100644 --- a/packages/server-utils/test/utils/monitorStream.test.ts +++ b/packages/server-utils/test/utils/monitorStream.test.ts @@ -117,4 +117,62 @@ describe('monitorStream', () => { await vi.waitFor(() => expect(onDone).toHaveBeenCalledTimes(1)); expect(onError).not.toHaveBeenCalled(); }); + + it('calls onDone before the stream closes', async () => { + const calls: string[] = []; + const source = new ReadableStream({ + start(controller) { + controller.enqueue(encode('a')); + controller.close(); + }, + }); + const reader = monitorStream( + source, + () => calls.push('onDone'), + () => calls.push('onError'), + ).getReader(); + void reader.closed.then(() => calls.push('closed')); + + await reader.read(); + await reader.read(); + await new Promise(resolve => setTimeout(resolve, 0)); + + expect(calls).toEqual(['onDone', 'closed']); + }); + + it('calls onDone in the same task as the cancel', async () => { + const onDone = vi.fn(); + const source = new ReadableStream({ + start(controller) { + controller.enqueue(encode('a')); + }, + }); + const reader = monitorStream(source, onDone, vi.fn()).getReader(); + await reader.read(); + + const cancelled = reader.cancel('client disconnected'); + expect(onDone).toHaveBeenCalledTimes(1); + + await cancelled; + await new Promise(resolve => setTimeout(resolve, 0)); + expect(onDone).toHaveBeenCalledTimes(1); + }); + + it('still delivers the body when onDone throws', async () => { + const source = new ReadableStream({ + start(controller) { + controller.enqueue(encode('ok')); + controller.close(); + }, + }); + const body = monitorStream( + source, + () => { + throw new Error('waitUntil failed'); + }, + vi.fn(), + ); + + expect(await new Response(body).text()).toBe('ok'); + }); }); diff --git a/packages/server-utils/test/utils/wrapServerRequestHandler.test.ts b/packages/server-utils/test/utils/wrapServerRequestHandler.test.ts index ff8cf8703fd1..e45e6163eddc 100644 --- a/packages/server-utils/test/utils/wrapServerRequestHandler.test.ts +++ b/packages/server-utils/test/utils/wrapServerRequestHandler.test.ts @@ -1,6 +1,6 @@ import { HTTP_ROUTE, SENTRY_SEGMENT_NAME_SOURCE, URL_PATH_PARAMETER_KEY_BASE } from '@sentry/conventions/attributes'; import type { Event } from '@sentry/core'; -import { getIsolationScope, setCurrentClient, withIsolationScope } from '@sentry/core'; +import { getIsolationScope, Scope, setCurrentClient, withIsolationScope } from '@sentry/core'; import { beforeAll, describe, expect, it, vi } from 'vitest'; import { setAsyncLocalStorageAsyncContextStrategy } from '../../src/async-context'; import type { ServerRequestHandlerConfig } from '../../src/utils/wrapServerRequestHandler'; @@ -90,6 +90,30 @@ describe('wrapServerRequestHandler', () => { expect(data).toBeUndefined(); }); + it('clears the body and response of an earlier request from a shared isolation scope', async () => { + const isolationScope = new Scope(); + const { serve } = setup({ maxRequestBodySize: 'medium' }, { isolationScope }); + + await serve( + new Request('https://example.com/login', { method: 'POST', body: '{"password":"hunter2"}' }), + () => new Response('no', { status: 418 }), + ); + expect(isolationScope.getScopeData().sdkProcessingMetadata.normalizedRequest?.data).toBe('{"password":"hunter2"}'); + expect(isolationScope.getScopeData().contexts.response).toEqual({ status_code: 418 }); + + let normalizedRequest: unknown; + let responseContext: unknown; + await serve(new Request('https://example.com/profile'), () => { + const { sdkProcessingMetadata, contexts } = isolationScope.getScopeData(); + normalizedRequest = sdkProcessingMetadata.normalizedRequest; + responseContext = contexts.response; + return new Response('ok'); + }); + + expect(normalizedRequest).toMatchObject({ method: 'GET', data: undefined }); + expect(responseContext).toBeUndefined(); + }); + it('calls onSpanEnd and ends the span when a streamed response finishes', async () => { const calls: string[] = []; const { transactions, serve } = setup({ @@ -220,4 +244,48 @@ describe('wrapServerRequestHandler', () => { const setCookie = transactions[0]?.contexts?.trace?.data?.['http.response.header.set-cookie'] as string[]; expect(setCookie.map(cookie => cookie.split('=')[0])).toEqual(['first', 'second']); }); + + it('calls onDone once with the response, or without one when the handler throws', async () => { + const done: (number | undefined)[] = []; + const { serve } = setup({}, { onDone: response => done.push(response?.status) }); + + const response = await serve(new Request('https://example.com/ok'), () => new Response('ok', { status: 201 })); + await response.text(); + await expect( + serve(new Request('https://example.com/throws'), () => { + throw new Error('handler failed'); + }), + ).rejects.toThrow('handler failed'); + await serve(new Request('https://example.com/cors', { method: 'OPTIONS' }), () => new Response(null)); + + expect(done).toEqual([201, undefined, 200]); + }); + + it('does not capture errors without a mechanism', async () => { + const { events, serve } = setup({}, { mechanism: undefined }); + + await expect( + serve(new Request('https://example.com/throws'), () => { + throw new Error('handler failed'); + }), + ).rejects.toThrow('handler failed'); + + expect(events).toHaveLength(0); + }); + + it('calls onDone at once when isStreamingResponse returns false', async () => { + const onDone = vi.fn(); + const { serve } = setup({}, { onDone, isStreamingResponse: () => false }); + const source = new ReadableStream({ + start() { + // never closes + }, + }); + const original = new Response(source, { headers: { 'content-type': 'text/event-stream' } }); + + const response = await serve(new Request('https://example.com/events'), () => original); + + expect(response).toBe(original); + expect(onDone).toHaveBeenCalledTimes(1); + }); }); From ef23ed52bc2a1dcc9748ece6dbafa3f5caafe75a Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 30 Oct 2026 12:08:40 -0700 Subject: [PATCH 076/169] feat(server-utils): Record AI SDK v7 runtime and tool context on Vercel AI spans (#25774) On `ai` 7 the `telemetry.metadata` option is gone. Per-call metadata goes in `runtimeContext`, and the app opts keys into telemetry with `telemetry.includeRuntimeContext` (and `includeToolsContext` for tool context). The channel subscriber did not read either field, so there was no way to put request-level data on gen_ai spans. The subscriber now records each included `runtimeContext` key as `vercel.ai.settings.context.` on the operation span and on its `generate_content` and `execute_tool` child spans. It records each included `toolContext` key as `vercel.ai.toolCall.context.` on the `execute_tool` span. The `runtimeContext` names match `@ai-sdk/otel` (`ai.settings.context.*`) under the usual `ai.*` to `vercel.ai.*` renaming. `ai` before 7.0.114 published the full context to the channel and ignored the include lists. A filtered context looks the same as an unfiltered one, so the subscriber only reads context from events that carry `aiVersion`, which `ai` added in 7.0.154. Older 7.x versions record no context, so keys the app did not opt in never leave it. Fixes #24706 Fixes JS-3788 Refs getsentry/sentry-conventions#756 Co-authored-by: Claude Opus 5.5 (1M context) --- .../v6_v7/scenario-runtime-context.mjs | 91 ++++++++++ .../suites/tracing/vercelai/v6_v7/test.ts | 66 ++++++- .../vercel-ai/vercel-ai-dc-subscriber.ts | 40 +++- .../vercel-ai/runtime-context.test.ts | 171 ++++++++++++++++++ 4 files changed, 366 insertions(+), 2 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/scenario-runtime-context.mjs create mode 100644 packages/server-utils/test/integrations/vercel-ai/runtime-context.test.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/scenario-runtime-context.mjs b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/scenario-runtime-context.mjs new file mode 100644 index 000000000000..5cfef0834a8e --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/scenario-runtime-context.mjs @@ -0,0 +1,91 @@ +import * as Sentry from '@sentry/node'; +import { generateText, stepCountIs, streamText, tool } from 'ai'; +import { MockLanguageModelV3, simulateReadableStream } from 'ai/test'; +import { z } from 'zod'; + +const usage = { + inputTokens: { total: 10, noCache: 10, cached: 0 }, + outputTokens: { total: 20, noCache: 20, cached: 0 }, + totalTokens: { total: 30, noCache: 30, cached: 0 }, +}; + +const tools = { + getWeather: tool({ + description: 'Get the current weather for a location', + inputSchema: z.object({ location: z.string() }), + contextSchema: z.object({ units: z.string(), apiKey: z.string() }), + execute: async ({ location }) => `Weather in ${location}: Sunny`, + }), +}; + +async function run() { + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { + let callCount = 0; + await generateText({ + model: new MockLanguageModelV3({ + doGenerate: async () => + callCount++ === 0 + ? { + finishReason: { unified: 'tool-calls', raw: 'tool_calls' }, + usage, + content: [ + { + type: 'tool-call', + toolCallId: 'call-1', + toolName: 'getWeather', + input: JSON.stringify({ location: 'Vienna' }), + }, + ], + warnings: [], + } + : { + finishReason: { unified: 'stop', raw: 'stop' }, + usage, + content: [{ type: 'text', text: 'Sunny' }], + warnings: [], + }, + }), + tools, + stopWhen: stepCountIs(3), + prompt: 'Weather in Vienna?', + runtimeContext: { requestId: 'req_generate', tenantId: 'acme', apiToken: 'secret-runtime' }, + toolsContext: { getWeather: { units: 'metric', apiKey: 'secret-tool' } }, + telemetry: { + isEnabled: true, + functionId: 'support-chat', + includeRuntimeContext: { requestId: true, tenantId: true }, + includeToolsContext: { getWeather: { units: true } }, + }, + }); + + const result = streamText({ + model: new MockLanguageModelV3({ + doStream: async () => ({ + stream: simulateReadableStream({ + chunks: [ + { type: 'stream-start', warnings: [] }, + { type: 'text-start', id: '0' }, + { type: 'text-delta', id: '0', delta: 'Hello!' }, + { type: 'text-end', id: '0' }, + { type: 'finish', finishReason: { unified: 'stop', raw: 'stop' }, usage }, + ], + }), + }), + }), + prompt: 'Hi', + runtimeContext: { requestId: 'req_stream', apiToken: 'secret-runtime' }, + telemetry: { + isEnabled: true, + functionId: 'support-stream', + includeRuntimeContext: { requestId: true }, + }, + }); + for await (const _part of result.fullStream) { + void _part; + } + }); + + await Sentry.flush(2000); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts index ea89bf21ddaf..0ac372b9c59d 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts @@ -26,7 +26,12 @@ import { SENTRY_OP, SENTRY_ORIGIN, } from '@sentry/conventions/attributes'; -import { GEN_AI_DECIDE } from '@sentry/conventions/op'; +import { + GEN_AI_DECIDE, + GEN_AI_EXECUTE_TOOL, + GEN_AI_GENERATE_CONTENT, + GEN_AI_INVOKE_AGENT, +} from '@sentry/conventions/op'; import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests, createEsmTests } from '../../../../utils/runner'; @@ -1429,3 +1434,62 @@ describe('Vercel AI telemetry metadata (v6)', () => { { additionalDependencies: { ai: '^6.0.0' } }, ); }); + +describe('Vercel AI runtime and tool context (v7)', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + createEsmTests( + __dirname, + 'scenario-runtime-context.mjs', + 'instrument.mjs', + (createRunner, test) => { + test('records the context keys included in telemetry as span attributes', async () => { + await createRunner() + .expect({ + span: container => { + const spans = container.items.filter( + span => span.attributes[SENTRY_ORIGIN]?.value === 'auto.ai.vercelai', + ); + // Keys left out of `includeRuntimeContext`/`includeToolsContext` must not reach any span. + expect(JSON.stringify(spans)).not.toMatch(/secret-runtime|secret-tool|apiToken|apiKey/); + + const generateSpans = spans.filter( + span => span.attributes['vercel.ai.settings.context.requestId']?.value === 'req_generate', + ); + expect(generateSpans.map(span => span.attributes[SENTRY_OP]?.value).sort()).toEqual([ + GEN_AI_EXECUTE_TOOL, + GEN_AI_GENERATE_CONTENT, + GEN_AI_GENERATE_CONTENT, + GEN_AI_INVOKE_AGENT, + ]); + for (const span of generateSpans) { + expect(span.attributes['vercel.ai.settings.context.tenantId']).toEqual({ + type: 'string', + value: 'acme', + }); + } + + const toolSpan = generateSpans.find(span => span.attributes[SENTRY_OP]?.value === GEN_AI_EXECUTE_TOOL); + expect(toolSpan?.attributes['vercel.ai.tool_call.context.units']).toEqual({ + type: 'string', + value: 'metric', + }); + + const streamSpans = spans.filter( + span => span.attributes['vercel.ai.settings.context.requestId']?.value === 'req_stream', + ); + expect(streamSpans.map(span => span.attributes[SENTRY_OP]?.value).sort()).toEqual([ + GEN_AI_GENERATE_CONTENT, + GEN_AI_INVOKE_AGENT, + ]); + }, + }) + .start() + .completed(); + }); + }, + { additionalDependencies: { ai: '^7.0.154' } }, + ); +}); diff --git a/packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts b/packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts index f7525a94e9dd..c1daaf73fc84 100644 --- a/packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts +++ b/packages/server-utils/src/integrations/vercel-ai/vercel-ai-dc-subscriber.ts @@ -86,6 +86,8 @@ const VERCEL_AI_OPERATION_ID_ATTRIBUTE = 'vercel.ai.operationId'; const VERCEL_AI_MODEL_PROVIDER_ATTRIBUTE = 'vercel.ai.model.provider'; const VERCEL_AI_SETTINGS_MAX_RETRIES_ATTRIBUTE = 'vercel.ai.settings.maxRetries'; const VERCEL_AI_TELEMETRY_METADATA_ATTRIBUTE_PREFIX = 'vercel.ai.telemetry.metadata.'; +const VERCEL_AI_SETTINGS_CONTEXT_ATTRIBUTE_PREFIX = 'vercel.ai.settings.context.'; +const VERCEL_AI_TOOL_CALL_CONTEXT_ATTRIBUTE_PREFIX = 'vercel.ai.tool_call.context.'; // Tracks the top-level operationId (and whether it streams) per `callId` so a model-call span can // name its `doGenerate`/`doStream` operation the same way the OTel integration does. `isStream` is @@ -108,6 +110,10 @@ const toolDescriptionsByCallId = new Map>(); // before the operation's own span ends. The running token sum lives on the span's own attributes. const invokeAgentSpanByCallId = new Map(); +// Only an operation's start event carries `runtimeContext`; its model-call and tool events copy the +// resulting attributes from here. Cleared with the operation. +const runtimeContextAttributesByCallId = new Map(); + // Only top-level operations own the `callId` → operationId mapping; `step`/`languageModelCall`/ // `executeTool` share the parent's `callId`, so they must not clear it. const ROOT_OPERATION_TYPES = new Set([ @@ -140,6 +146,7 @@ export function clearOperationCallId(callId: string): void { operationIdByCallId.delete(callId); toolDescriptionsByCallId.delete(callId); invokeAgentSpanByCallId.delete(callId); + runtimeContextAttributesByCallId.delete(callId); } /** @@ -450,9 +457,17 @@ export function createSpanFromMessage( getOpenAiConversationId(event.providerOptions) ?? (ROOT_OPERATION_TYPES.has(type) ? undefined : getActiveSpanConversationId()); + const runtimeContextAttributes = + (callId ? runtimeContextAttributesByCallId.get(callId) : undefined) ?? + contextAttributes(event, event.runtimeContext, VERCEL_AI_SETTINGS_CONTEXT_ATTRIBUTE_PREFIX); + if (callId && ROOT_OPERATION_TYPES.has(type)) { + runtimeContextAttributesByCallId.set(callId, runtimeContextAttributes); + } + const baseAttributes: SpanAttributes = { [SENTRY_ORIGIN]: ORIGIN, ...telemetryMetadataAttributes(event.telemetryMetadata), + ...runtimeContextAttributes, [GEN_AI_PROVIDER_NAME]: provider, [VERCEL_AI_MODEL_PROVIDER_ATTRIBUTE]: provider, [GEN_AI_REQUEST_MODEL]: modelId, @@ -473,7 +488,7 @@ export function createSpanFromMessage( return buildModelCallSpan(event, baseAttributes, recordInputs, callId, modelId); case 'executeTool': - return buildToolSpan(event, recordInputs, conversationId); + return buildToolSpan(event, recordInputs, conversationId, runtimeContextAttributes); case 'embed': case 'embedMany': { // `embed` carries a single `value`; `embedMany` a `values` array — both map to the embeddings input. @@ -520,6 +535,26 @@ function telemetryMetadataAttributes(metadata: unknown): Record return attributes; } +/** + * One string attribute per context key, as ``. `ai` < 7.0.114 published the full context to the + * channel, ignoring `telemetry.includeRuntimeContext`/`includeToolsContext`, and a filtered context + * looks the same as an unfiltered one. `aiVersion` (added in 7.0.154) is the only reliable sign that + * the AI SDK filtered it, so without it we record nothing. + */ +function contextAttributes(event: Record, context: unknown, prefix: string): SpanAttributes { + const attributes: SpanAttributes = {}; + if (typeof event.aiVersion !== 'string' || !isObjectLike(context)) { + return attributes; + } + for (const [key, value] of Object.entries(context)) { + const attributeValue = stringify(value); + if (value !== null && attributeValue !== undefined) { + attributes[`${prefix}${key}`] = attributeValue; + } + } + return attributes; +} + /** Start a `gen_ai.` span named ` ` (or just `` when no suffix). */ function startGenAiSpan(operation: GenAiOperation, suffix: string | undefined, attributes: SpanAttributes): Span { return startInactiveSpan({ @@ -581,6 +616,7 @@ function buildToolSpan( event: Record, recordInputs: boolean, conversationId: string | undefined, + runtimeContextAttributes: SpanAttributes, ): Span { const toolCall = isObjectLike(event.toolCall) ? event.toolCall : {}; const toolName = asString(toolCall.toolName); @@ -592,6 +628,8 @@ function buildToolSpan( recordInputs && toolName ? resolveToolDescription(asString(event.callId), toolName, event.tools) : undefined; return startGenAiSpan('execute_tool', toolName, { [SENTRY_ORIGIN]: ORIGIN, + ...runtimeContextAttributes, + ...contextAttributes(event, event.toolContext, VERCEL_AI_TOOL_CALL_CONTEXT_ATTRIBUTE_PREFIX), [GEN_AI_TOOL_NAME]: toolName, [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: toolCallId, [GEN_AI_TOOL_DESCRIPTION]: description, diff --git a/packages/server-utils/test/integrations/vercel-ai/runtime-context.test.ts b/packages/server-utils/test/integrations/vercel-ai/runtime-context.test.ts new file mode 100644 index 000000000000..e8cf74b37080 --- /dev/null +++ b/packages/server-utils/test/integrations/vercel-ai/runtime-context.test.ts @@ -0,0 +1,171 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { getMainCarrier, setCurrentClient, spanToStaticSpanJSON } from '@sentry/core'; +import type { Span } from '@sentry/core'; +import { + clearOperationId, + createSpanFromMessage, + type VercelAiChannelMessage, +} from '../../../src/integrations/vercel-ai/vercel-ai-dc-subscriber'; +import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; + +describe('Vercel AI runtime and tool context', () => { + beforeEach(() => { + getMainCarrier().__SENTRY__ = undefined; + }); + + afterEach(() => { + getMainCarrier().__SENTRY__ = undefined; + }); + + function setupClient(): Span[] { + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1, + }), + ); + setCurrentClient(client); + client.init(); + + const endedSpans: Span[] = []; + client.on('spanEnd', span => endedSpans.push(span)); + return endedSpans; + } + + function spanData(message: VercelAiChannelMessage, endedSpans: Span[]): Record { + createSpanFromMessage(message, {})?.end(); + return spanToStaticSpanJSON(endedSpans.at(-1)!).data; + } + + it('records runtimeContext keys as strings on the operation span', () => { + const endedSpans = setupClient(); + const data = spanData( + { + type: 'generateText', + event: { + aiVersion: '7.0.154', + callId: 'call-1', + runtimeContext: { + requestId: 'req_1', + attempt: 2, + cached: false, + user: { id: 'u1' }, + tags: ['a', 'b'], + empty: null, + missing: undefined, + }, + }, + }, + endedSpans, + ); + + expect(data).toMatchObject({ + 'vercel.ai.settings.context.requestId': 'req_1', + 'vercel.ai.settings.context.attempt': '2', + 'vercel.ai.settings.context.cached': 'false', + 'vercel.ai.settings.context.user': '{"id":"u1"}', + 'vercel.ai.settings.context.tags': '["a","b"]', + }); + expect(data).not.toHaveProperty('vercel.ai.settings.context.empty'); + expect(data).not.toHaveProperty('vercel.ai.settings.context.missing'); + }); + + it.each(['embed', 'embedMany', 'rerank', 'experimental_decide'] as const)( + 'records runtimeContext on %s spans', + type => { + const endedSpans = setupClient(); + const data = spanData( + { type, event: { aiVersion: '7.0.154', runtimeContext: { tenantId: 'acme' } } }, + endedSpans, + ); + expect(data['vercel.ai.settings.context.tenantId']).toBe('acme'); + }, + ); + + it('ignores runtimeContext from an ai version that does not report aiVersion', () => { + const endedSpans = setupClient(); + const data = spanData( + { + type: 'generateText', + event: { callId: 'call-1', runtimeContext: { apiToken: 'secret' } }, + }, + endedSpans, + ); + + expect(JSON.stringify(data)).not.toContain('secret'); + }); + + it('copies runtimeContext to model-call and tool spans of the same operation', () => { + const endedSpans = setupClient(); + const root: VercelAiChannelMessage = { + type: 'generateText', + event: { aiVersion: '7.0.154', callId: 'call-copy', runtimeContext: { tenantId: 'acme' } }, + }; + const rootSpan = createSpanFromMessage(root, {}); + + const modelCallData = spanData( + { type: 'languageModelCall', event: { aiVersion: '7.0.154', callId: 'call-copy', modelId: 'm' } }, + endedSpans, + ); + const toolData = spanData( + { + type: 'executeTool', + event: { + aiVersion: '7.0.154', + callId: 'call-copy', + toolCall: { toolName: 'getWeather', toolCallId: 'tc-1' }, + }, + }, + endedSpans, + ); + expect(modelCallData['vercel.ai.settings.context.tenantId']).toBe('acme'); + expect(toolData['vercel.ai.settings.context.tenantId']).toBe('acme'); + + clearOperationId(root); + rootSpan?.end(); + + const laterData = spanData( + { type: 'languageModelCall', event: { aiVersion: '7.0.154', callId: 'call-copy', modelId: 'm' } }, + endedSpans, + ); + expect(laterData).not.toHaveProperty('vercel.ai.settings.context.tenantId'); + }); + + it('records toolContext keys on the tool span', () => { + const endedSpans = setupClient(); + const data = spanData( + { + type: 'executeTool', + event: { + aiVersion: '7.0.154', + callId: 'call-1', + toolCall: { toolName: 'getWeather', toolCallId: 'tc-1' }, + toolContext: { units: 'metric', region: { code: 'eu' } }, + }, + }, + endedSpans, + ); + + expect(data).toMatchObject({ + 'vercel.ai.tool_call.context.units': 'metric', + 'vercel.ai.tool_call.context.region': '{"code":"eu"}', + }); + }); + + it('ignores toolContext from an ai version that does not report aiVersion', () => { + const endedSpans = setupClient(); + const data = spanData( + { + type: 'executeTool', + event: { + callId: 'call-1', + toolCall: { toolName: 'getWeather', toolCallId: 'tc-1' }, + toolContext: { apiKey: 'secret' }, + }, + }, + endedSpans, + ); + + expect(JSON.stringify(data)).not.toContain('secret'); + }); +}); From e201b4f92b7c930c5c2547328000d5e2e5c20762 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 20:17:36 +0100 Subject: [PATCH 077/169] feat(deps): Bump valibot from 1.4.2 to 1.5.0 (#25957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [valibot](https://github.com/open-circle/valibot) from 1.4.2 to 1.5.0.
Release notes

Sourced from valibot's releases.

v1.5.0

Many thanks to @​tats-u, @​idleberg, @​yslpn, @​francisjohnjohnston-web, @​MaxFreedomPollard, @​mahirhir, @​ItzXynx, @​LeSingh1, @​maxtaran2010, @​ysknsid25, @​cyyynthia, @​spokodev and @​sanjibani for contributing to this release.

  • Add codePoints, maxCodePoints, minCodePoints and notCodePoints validation actions to validate the number of Unicode code points (pull request #888)
  • Add ksuid validation action to validate KSUIDs (pull request #1370)
  • Change Standard Schema properties to use eager initialization for faster schema construction and replace internal _getStandardProps utility with _standardSchema (pull request #1534)
  • Change url action to use URL.canParse when available to avoid constructing URL objects (pull request #1608)
  • Fix stringifyJson action to preserve the dataset value when JSON.stringify returns undefined (pull request #1476)
  • Fix literal schema and value, values, notValue and notValues actions to treat NaN as equal to itself (pull request #1573)
  • Fix intersect schema to merge matching NaN values and invalid dates (pull request #1573)
  • Fix cache and cacheAsync methods to clone the issues of a cached dataset, preventing parent schemas from adding their path item to the same issue on every cache hit (pull request #1620)
  • Fix strictObject, looseObject, objectWithRest and their async variants to correctly handle unknown input keys that collide with Object.prototype members (pull request #1523)
  • Fix intersect and intersectAsync schemas to ignore inherited properties when merging objects and preserve own properties without invoking inherited setters or changing the output prototype (pull request #1621)
  • Fix ulid action to reject ULIDs that exceed the maximum 128-bit value (pull request #1498)
  • Fix email action to reject non-ASCII characters accepted by Unicode case folding (pull request #1075)

v1.5.0 (to-json-schema)

  • Add support for JSON Schema draft-2020-12 and OpenAPI 3.0 Schema Object format
  • Add propertyNames support to record schemas for key validation constraints
  • Add support for toBigint, toBoolean, toDate, toNumber and toString actions for typeMode: 'input'
  • Add new toStandardJsonSchema function to convert Valibot schemas to Standard JSON Schema format
  • Change return type from JSONSchema7 to a custom JsonSchema type
Commits
  • 5016198 chore: prepare Valibot v1.5.0 release (#1627)
  • 1ffb038 fix(intersect): skip inherited prototype props in _merge (#1621)
  • 1c4983f fix(ulid): restrict first character to 0-7 per ULID spec (#1498)
  • 5e449cd perf(url): use URL.canParse for validation (#1608)
  • 4acda1b Make sure that email validators don't match unintentional non-ASCII local par...
  • 95943ab fix: handle object keys that collide with Object.prototype in object schemas ...
  • d65438c fix(cache): clone cached issues to stop issue paths from accumulating (#1620)
  • 3ed804a dependabot: bump the github-actions group across 1 directory with 2 updates (...
  • 0681f43 fix: use SameValueZero equality for special numeric values (#1573)
  • 98ef8b4 Improve perf by changing ~standard implementation (#1534)
  • Additional commits viewable in compare view

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../node-integration-tests/package.json | 2 +- pnpm-lock.yaml | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 39eb221ba423..e8bfe1572ff9 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -109,7 +109,7 @@ "srvx": "^0.11.22", "tedious": "^19.2.1", "unstorage": "2.0.0-alpha.7", - "valibot": "1.4.2", + "valibot": "1.5.0", "winston": "^3.17.0", "ws": "^8.20.1", "yargs": "^16.2.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb7ffdabb2c4..1adb0e8168ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -807,8 +807,8 @@ importers: specifier: 2.0.0-alpha.7 version: 2.0.0-alpha.7(@azure/storage-blob@12.31.0(supports-color@10.0.0))(chokidar@5.0.0)(db0@0.3.4(mysql2@3.23.3(@types/node@20.19.43)))(ioredis@5.10.1(supports-color@10.0.0))(lru-cache@11.4.0)(mongodb@3.7.4)(ofetch@1.5.1) valibot: - specifier: 1.4.2 - version: 1.4.2(typescript@7.0.2) + specifier: 1.5.0 + version: 1.5.0(typescript@7.0.2) winston: specifier: ^3.17.0 version: 3.19.0 @@ -18988,8 +18988,8 @@ packages: deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true - valibot@1.4.2: - resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} + valibot@1.5.0: + resolution: {integrity: sha512-nil6AkP2TChWL43Z5uJ6GTxX01CUA+g8LWUM+N/rB9NBbkUMaUsi9PUNzlUPgoKASgmx9f7eGOYpJ04/fSa6FQ==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -23953,11 +23953,11 @@ snapshots: '@earendil-works/pi-ai': 0.83.0(@modelcontextprotocol/sdk@1.32.2(@cfworker/json-schema@4.1.1)(supports-color@10.0.0)(zod@4.6.7))(supports-color@10.0.0)(ws@8.21.3)(zod@4.6.7) '@hono/node-server': 2.0.10(hono@4.13.11) '@modelcontextprotocol/client': 2.0.0 - '@valibot/to-json-schema': 1.8.0(valibot@1.4.2(typescript@7.0.2)) + '@valibot/to-json-schema': 1.8.0(valibot@1.5.0(typescript@7.0.2)) hono: 4.13.11 js-yaml: 5.5.0 ulidx: 2.4.1 - valibot: 1.4.2(typescript@7.0.2) + valibot: 1.5.0(typescript@7.0.2) transitivePeerDependencies: - '@modelcontextprotocol/sdk' - bufferutil @@ -26425,7 +26425,7 @@ snapshots: react-router: 7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) semver: 7.8.5 tinyglobby: 0.2.17 - valibot: 1.4.2(typescript@6.0.3) + valibot: 1.5.0(typescript@6.0.3) vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) vite-node: 3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) optionalDependencies: @@ -28261,9 +28261,9 @@ snapshots: unhead: 2.1.17 vue: 3.5.44(typescript@7.0.2) - '@valibot/to-json-schema@1.8.0(valibot@1.4.2(typescript@7.0.2))': + '@valibot/to-json-schema@1.8.0(valibot@1.5.0(typescript@7.0.2))': dependencies: - valibot: 1.4.2(typescript@7.0.2) + valibot: 1.5.0(typescript@7.0.2) '@vercel/detect-agent@1.2.7': {} @@ -40460,11 +40460,11 @@ snapshots: uuid@9.0.1: {} - valibot@1.4.2(typescript@6.0.3): + valibot@1.5.0(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 - valibot@1.4.2(typescript@7.0.2): + valibot@1.5.0(typescript@7.0.2): optionalDependencies: typescript: 7.0.2 From c9b4058f7ea9dc76a4e24bcfb524ec9f5ce0b24b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 20:18:22 +0100 Subject: [PATCH 078/169] chore(deps-dev): Bump the react-router group across 1 directory with 3 updates (#25955) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the react-router group with 3 updates in the / directory: [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router), [@react-router/dev](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dev) and [@react-router/node](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-node). Updates `react-router` from 7.18.5 to 7.18.4
Changelog

Sourced from react-router's changelog.

v7.18.4

Patch Changes

  • Correctly escape streamed RSC redirect locations in meta tag attributes (#15492)
  • Avoid unintended document.startViewTransition calls during initial hydration and router.revalidate() calls (#15485)

v7.18.3

Patch Changes

  • Improve route matching performance for long paths (#15423)
  • Improve validation of action request origins (#15420)
  • Add additional URL validation on client side navigations/redirects (#15446)

v7.18.2

Patch Changes

  • Harden RSC CSRF codepaths. (#15353)

v7.18.1

Patch Changes

  • No changes

v7.18.0

Patch Changes

  • Fix server handler prerender responses when using ssr: false and future.v8_trailingSlashAwareDataRequests: true. Avoids false positive "SPA Mode" detection when serving prerendered paths (#15173)
  • Use the ServerRouter nonce for nonce-aware SSR components when they don't provide their own value so strict CSP pages can load them. (#15170)
  • Use turbo-stream to serialize and deserialize Framework Mode hydration errors (#15175)
  • Precompute route branch matchers to avoid recompiling route path regexes during matching (#15186)
  • Use the constructed request URL host when validating action request origins. (#15185)
  • Remove the un-documented custom error serialization logic from Data Mode SSR built-in hydration flows (#15175)
  • Validate protocols in RSC render redirects (#15177)
  • Consolidate url normalization logic and better handle mixed slashes (#15176)

v7.17.0

Minor Changes

  • Ship a subset of the official documentation inside the react-router package (#15121)
    • Markdown docs are now available in node_modules/react-router/docs, letting AI coding agents and the React Router agent skills read official docs locally
    • Excludes auto-generated API docs (api/), community/ content, and tutorials (tutorials/)

v7.16.0

... (truncated)

Commits

Updates `@react-router/dev` from 7.17.0 to 7.18.4
Release notes

Sourced from @​react-router/dev's releases.

v7.18.4

See the changelog for release notes: https://github.com/remix-run/react-router/blob/v7/CHANGELOG.md#v7184

v7.18.3

See the changelog for release notes: https://github.com/remix-run/react-router/blob/v7/CHANGELOG.md#v7183

v7.18.2

See the changelog for release notes: https://github.com/remix-run/react-router/blob/v7/CHANGELOG.md#v7182

v7.18.1

See the changelog for release notes: https://github.com/remix-run/react-router/blob/v7/CHANGELOG.md#v7181

v7.18.0

See the changelog for release notes: https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v7180

Changelog

Sourced from @​react-router/dev's changelog.

v7.18.4

Patch Changes

v7.18.3

Patch Changes

v7.18.2

Patch Changes

v7.18.1

Patch Changes

v7.18.0

Patch Changes

  • Pass Vite server.watch config to child compiler in development mode. (#15178)

  • Ignore external Vite server environments in Framework Mode build hooks (#14883)

    When future.v8_viteEnvironmentApi is enabled, React Router previously treated any non-client Vite environment as its own server build. This caused issues with integrations like Nitro, where plugins can register additional environments.

    Framework Mode build hooks now ignore external server environments and only process the app's own server build.

  • Updated dependencies:

... (truncated)

Commits

Updates `@react-router/node` from 7.18.1 to 7.18.4
Release notes

Sourced from @​react-router/node's releases.

v7.18.4

See the changelog for release notes: https://github.com/remix-run/react-router/blob/v7/CHANGELOG.md#v7184

v7.18.3

See the changelog for release notes: https://github.com/remix-run/react-router/blob/v7/CHANGELOG.md#v7183

v7.18.2

See the changelog for release notes: https://github.com/remix-run/react-router/blob/v7/CHANGELOG.md#v7182

Changelog

Sourced from @​react-router/node's changelog.

v7.18.4

Patch Changes

v7.18.3

Patch Changes

  • fix: Prevent client disconnects during streaming from crashing the Node process (#15437)
  • Updated dependencies:

v7.18.2

Patch Changes

Commits

Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/react-router/package.json | 4 +- packages/react/package.json | 2 +- pnpm-lock.yaml | 61 +++++++++++------------------- 3 files changed, 25 insertions(+), 42 deletions(-) diff --git a/packages/react-router/package.json b/packages/react-router/package.json index ceb178065b5e..07d576d599b0 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -68,14 +68,14 @@ "sentry": "^0.44.0" }, "devDependencies": { - "@react-router/dev": "^7.17.0", + "@react-router/dev": "^7.18.4", "@react-router/node": "^7.15.0", "@sentry-internal/rollup-utils": "11.10.0", "@testing-library/react": "^15.0.5", "@types/node": "^20.19.0", "@types/react": "17.0.3", "react": "^18.3.1", - "react-router": "^7.18.0", + "react-router": "^7.18.4", "vite": "^8.3.1" }, "peerDependencies": { diff --git a/packages/react/package.json b/packages/react/package.json index 9c25dd0d99a2..ad3180f80c6f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -84,7 +84,7 @@ "history-5": "npm:history@4.9.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router": "^7.18.3", + "react-router": "^7.18.4", "react-router-3": "npm:react-router@3.2.0", "react-router-4": "npm:react-router@4.1.0", "react-router-5": "npm:react-router@5.3.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1adb0e8168ef..0af122f23499 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1887,8 +1887,8 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) react-router: - specifier: ^7.18.3 - version: 7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^7.18.4 + version: 7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router-3: specifier: npm:react-router@3.2.0 version: react-router@3.2.0(react@18.3.1) @@ -1942,11 +1942,11 @@ importers: version: 0.44.0 devDependencies: '@react-router/dev': - specifier: ^7.17.0 - version: 7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0) + specifier: ^7.18.4 + version: 7.18.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0) '@react-router/node': specifier: ^7.15.0 - version: 7.18.1(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3) + version: 7.18.4(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3) '@sentry-internal/rollup-utils': specifier: 11.10.0 version: link:../../dev-packages/rollup-utils @@ -1963,8 +1963,8 @@ importers: specifier: ^18.3.1 version: 18.3.1 react-router: - specifier: ^7.18.0 - version: 7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^7.18.4 + version: 7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) vite: specifier: ^8.3.1 version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) @@ -7845,14 +7845,14 @@ packages: '@protobufjs/utf8@1.1.1': resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} - '@react-router/dev@7.17.0': - resolution: {integrity: sha512-+ITMmv/1xUB+QF6ehCCOIVBNBDuKIEE+3JKCt+kTBvxDTk1s49qHbtCA4TzJYge41pNRGtFIiy5VAksLSVJheg==} + '@react-router/dev@7.18.4': + resolution: {integrity: sha512-4sxQa18Znjz1zw1MGK7f97zXjK4NVcNgESvWPPEYScRFAHJWq6Q3/oejDPfYjKWLygsX1yF9jboxbGLw+V9f+g==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@react-router/serve': ^7.17.0 + '@react-router/serve': ^7.18.4 '@vitejs/plugin-rsc': ~0.5.21 - react-router: ^7.17.0 + react-router: ^7.18.4 react-server-dom-webpack: ^19.2.3 typescript: ^5.1.0 || ^6.0.0 vite: ^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -7869,21 +7869,11 @@ packages: wrangler: optional: true - '@react-router/node@7.17.0': - resolution: {integrity: sha512-RYR47qM9gJ8zV8Ntial5Rkgcst2YnwWXt0Ai34FezzkDK6AILpxpVatEzFEhNRwbSh6JO6iweY7XhfM4/K5dBA==} + '@react-router/node@7.18.4': + resolution: {integrity: sha512-PXNpc4fi0xa5Vp+uaYDFbzIEr4ZmLgch/w25AvxGV2eW32BzDh/OLlpGG074M/FKKi2CzDMsTsU8D3cDH4jywg==} engines: {node: '>=20.0.0'} peerDependencies: - react-router: 7.17.0 - typescript: ^5.1.0 || ^6.0.0 - peerDependenciesMeta: - typescript: - optional: true - - '@react-router/node@7.18.1': - resolution: {integrity: sha512-2GhAwa90z/+GMFM8Q5HsgwzakYogbQcZpqpNonhN4YWDRjWkSz+t5jgwNAFvG8ENR8fKGEVEsOHIaNVDfW9Ouw==} - engines: {node: '>=20.0.0'} - peerDependencies: - react-router: 7.18.1 + react-router: 7.18.4 typescript: ^5.1.0 || ^6.0.0 peerDependenciesMeta: typescript: @@ -16828,8 +16818,8 @@ packages: peerDependencies: react: '>=16.8' - react-router@7.18.3: - resolution: {integrity: sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==} + react-router@7.18.4: + resolution: {integrity: sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -26396,7 +26386,7 @@ snapshots: '@protobufjs/utf8@1.1.1': {} - '@react-router/dev@7.17.0(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0)': + '@react-router/dev@7.18.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0)': dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@babel/generator': 7.29.8 @@ -26405,7 +26395,7 @@ snapshots: '@babel/preset-typescript': 7.28.5(@babel/core@7.29.7(supports-color@10.0.0))(supports-color@10.0.0) '@babel/traverse': 7.29.8(supports-color@10.0.0) '@babel/types': 7.29.8 - '@react-router/node': 7.17.0(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3) + '@react-router/node': 7.18.4(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3) '@remix-run/node-fetch-server': 0.13.0 arg: 5.0.2 babel-dead-code-elimination: 1.0.12(supports-color@10.0.0) @@ -26422,7 +26412,7 @@ snapshots: pkg-types: 2.3.1 prettier: 3.9.10 react-refresh: 0.14.2 - react-router: 7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router: 7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) semver: 7.8.5 tinyglobby: 0.2.17 valibot: 1.5.0(typescript@6.0.3) @@ -26446,17 +26436,10 @@ snapshots: - tsx - yaml - '@react-router/node@7.17.0(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3)': - dependencies: - '@mjackson/node-fetch-server': 0.2.0 - react-router: 7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - optionalDependencies: - typescript: 6.0.3 - - '@react-router/node@7.18.1(react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3)': + '@react-router/node@7.18.4(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3)': dependencies: '@mjackson/node-fetch-server': 0.2.0 - react-router: 7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router: 7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) optionalDependencies: typescript: 6.0.3 @@ -37946,7 +37929,7 @@ snapshots: '@remix-run/router': 1.23.3 react: 18.3.1 - react-router@7.18.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: cookie: 1.1.1 react: 18.3.1 From 73b7bb224ef6be750edf67516234545624647cd0 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 30 Oct 2026 22:07:30 +0100 Subject: [PATCH 079/169] chore(ci): Remove nightly sentry-lighthouse upload workflow (#25966) As part of cleanup of an incident, we sunset sentry-lighthouse (at least for the moment), hopefully to be replaced with Kiln in the long run. Therefore stopping the upload of SDK builds to the now dead server. --- .github/workflows/lighthouse.yml | 32 ---- .../lighthouse-react/index.html | 13 -- .../lighthouse-react/package.json | 37 ----- .../lighthouse-react/pnpm-workspace.yaml | 4 - .../lighthouse-react/public/logo.svg | 6 - .../lighthouse-react/src/App.tsx | 60 ------- .../lighthouse-react/src/main.tsx | 157 ------------------ .../lighthouse-react/tsconfig.json | 21 --- .../lighthouse-react/tsconfig.node.json | 10 -- .../lighthouse-react/vite.config.ts | 6 - 10 files changed, 346 deletions(-) delete mode 100644 .github/workflows/lighthouse.yml delete mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/index.html delete mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/pnpm-workspace.yaml delete mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/public/logo.svg delete mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/src/App.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/src/main.tsx delete mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/tsconfig.json delete mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/tsconfig.node.json delete mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/vite.config.ts diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml deleted file mode 100644 index 1073b0c17b51..000000000000 --- a/.github/workflows/lighthouse.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: 'Nightly: Lighthouse' - -# Nightly: builds the Lighthouse fixture app and uploads bundles to the -# lighthouse.sentry.gg lab. Never blocks merges. - -on: - schedule: - - cron: '0 0 * * *' - workflow_dispatch: - -jobs: - build-and-upload: - name: Bundle and upload Lighthouse cells - runs-on: namespace-profile-sentry-javascript-large - timeout-minutes: 30 - env: - LIGHTHOUSE_LAB_URL: ${{ secrets.LIGHTHOUSE_LAB_URL }} - LIGHTHOUSE_UPLOAD_TOKEN: ${{ secrets.LIGHTHOUSE_UPLOAD_TOKEN }} - VITE_E2E_TEST_DSN: 'https://username@domain/123' - steps: - - uses: actions/checkout@v7 - - name: Install pnpm - uses: ./.github/actions/install-pnpm - - uses: actions/setup-node@v7 - with: - node-version-file: 'package.json' - - run: pnpm install --frozen-lockfile - - run: pnpm run build:ci - - run: pnpm run build:tarball - - run: pnpm run test:prepare - working-directory: dev-packages/e2e-tests - - run: node scripts/lighthouse-bundle-and-upload.mjs diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/index.html b/dev-packages/e2e-tests/test-applications/lighthouse-react/index.html deleted file mode 100644 index dbddfb003021..000000000000 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Lighthouse Fixture - - -
- - - diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/package.json b/dev-packages/e2e-tests/test-applications/lighthouse-react/package.json deleted file mode 100644 index b0a9d2858d57..000000000000 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "lighthouse-react-test-app", - "private": true, - "type": "module", - "sentryTest": { - "skip": true - }, - "scripts": { - "build": "vite build", - "build:no-sentry": "vite build --mode no-sentry", - "build:init-only": "vite build --mode init-only", - "build:errors-only": "vite build --mode errors-only", - "build:minimal-integrations": "vite build --mode minimal-integrations", - "build:no-integrations": "vite build --mode no-integrations", - "build:no-browser-api-errors": "vite build --mode no-browser-api-errors", - "build:no-breadcrumbs": "vite build --mode no-breadcrumbs", - "build:no-browser-session": "vite build --mode no-browser-session", - "build:tracing": "vite build --mode tracing", - "build:tracing-lazy-import": "vite build --mode tracing-lazy-import", - "build:tracing-replay": "vite build --mode tracing-replay", - "preview": "vite preview", - "clean": "npx rimraf node_modules pnpm-lock.yaml dist", - "test:build": "pnpm install && pnpm build" - }, - "dependencies": { - "@sentry/react": "file:../../packed/sentry-react-packed.tgz", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.3.0", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react-swc": "^3.7.0", - "typescript": "~5.4.0", - "vite": "^5.4.0" - } -} diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/lighthouse-react/pnpm-workspace.yaml deleted file mode 100644 index dafa2e7eca8c..000000000000 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/pnpm-workspace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -allowBuilds: - '@swc/core': true - esbuild: true -minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/public/logo.svg b/dev-packages/e2e-tests/test-applications/lighthouse-react/public/logo.svg deleted file mode 100644 index a1ee6ba8d243..000000000000 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/public/logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/src/App.tsx b/dev-packages/e2e-tests/test-applications/lighthouse-react/src/App.tsx deleted file mode 100644 index bf6e99e7419d..000000000000 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/src/App.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { useEffect, useState } from 'react'; - -const CARDS = [ - { title: 'Performance', body: 'Measure and optimize your app render times and interaction latency.' }, - { title: 'Accessibility', body: 'Ensure your interface is usable by everyone, including assistive technologies.' }, - { title: 'Best Practices', body: 'Follow modern web development patterns for secure and maintainable code.' }, - { title: 'SEO', body: 'Optimize discoverability with semantic markup and structured metadata.' }, - { title: 'PWA', body: 'Add offline support and installability via service workers and manifests.' }, - { title: 'Security', body: 'Protect users with CSP headers, HTTPS, and input validation.' }, -]; - -export default function App() { - const [count, setCount] = useState(0); - - useEffect(() => { - document.title = `Lighthouse Fixture (${count})`; - }, [count]); - - return ( -
-
- -
- -
- Lighthouse logo -

Lighthouse Fixture

-

- This app exists to measure JavaScript bundle size and runtime cost across three Sentry instrumentation - configurations. Each build mode ships a different level of SDK integration. -

-
- -
- {CARDS.map(card => ( -
-

{card.title}

-

{card.body}

-
- ))} -
- -
{ - e.preventDefault(); - setCount(c => c + 1); - }} - > - - - - -
-
- ); -} diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/src/main.tsx b/dev-packages/e2e-tests/test-applications/lighthouse-react/src/main.tsx deleted file mode 100644 index 352c53c7208b..000000000000 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/src/main.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import * as Sentry from '@sentry/react'; -import { createRoot } from 'react-dom/client'; -import App from './App'; - -const sentryInitStart = performance.now(); - -performance.measure('sentry-sdk-pre-init-duration', { - detail: { mode: import.meta.env.MODE ?? 'unknown_mode' }, - start: 0, - end: sentryInitStart, -}); - -performance.mark('sentry-sdk-init-start', { - detail: { mode: import.meta.env.MODE ?? 'unknown_mode' }, -}); - -if (import.meta.env.MODE === 'tracing-replay') { - Sentry.init({ - dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, - release: 'lighthouse-fixture', - environment: 'qa', - integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], - tracesSampleRate: 1.0, - replaysSessionSampleRate: 1.0, - replaysOnErrorSampleRate: 1.0, - }); -} else if (import.meta.env.MODE === 'tracing') { - // Tracing + errors, but no replay — isolates the replay integration's cost. - Sentry.init({ - dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, - release: 'lighthouse-fixture', - environment: 'qa', - integrations: [Sentry.browserTracingIntegration()], - tracesSampleRate: 1.0, - }); -} else if (import.meta.env.MODE === 'tracing-lazy-import') { - // Tracing + errors, but browsertracing loaded lazily. - // (We don't recommend this setup anywhere and neither will it work well. - // this is purely for testing if it changes anything about overhead.) - Sentry.init({ - dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, - release: 'lighthouse-fixture', - environment: 'qa', - tracesSampleRate: 1.0, - }); - - import('@sentry/react').then(lazySentry => { - Sentry.addIntegration(lazySentry.browserTracingIntegration()); - }); -} else if (import.meta.env.MODE === 'errors-only') { - // Default integrations only — errors are always captured, no tracing or replay. - Sentry.init({ - dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, - release: 'lighthouse-fixture', - environment: 'qa', - }); -} else if (import.meta.env.MODE === 'minimal-integrations') { - // Minimal integratoins setup only (everything necessary to automatically get errors) - Sentry.init({ - dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, - release: 'lighthouse-fixture', - environment: 'qa', - defaultIntegrations: false, - integrations: [ - Sentry.globalHandlersIntegration(), - Sentry.linkedErrorsIntegration(), - Sentry.dedupeIntegration(), - // for good measure, let's include event filters since noise reduction is usually desired - // 99% of this integration's work is done in an event processor, so outside the hot path - Sentry.eventFiltersIntegration(), - ], - }); -} else if (import.meta.env.MODE === 'no-integrations') { - // DSN set but every integration disabled. Isolates the cost of the enabled - // client itself from the default instrumentation that wraps DOM/timer/network APIs. - Sentry.init({ - dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, - release: 'lighthouse-fixture', - environment: 'qa', - defaultIntegrations: false, - integrations: [], - }); -} else if (import.meta.env.MODE === 'no-browser-api-errors') { - // Default integrations minus BrowserApiErrors, which wraps addEventListener/ - // removeEventListener on ~32 prototypes plus setTimeout/setInterval/rAF/XHR. - // Isolates that global monkey-patching cost from the rest of the defaults. - Sentry.init({ - dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, - release: 'lighthouse-fixture', - environment: 'qa', - integrations: defaultIntegrations => - defaultIntegrations.filter(integration => integration.name !== 'BrowserApiErrors'), - }); -} else if (import.meta.env.MODE === 'no-breadcrumbs') { - // Default integrations minus Breadcrumbs, which adds a lot of monkey patching to - // DOM and Network APIs as well as event targets and listeners - Sentry.init({ - dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, - release: 'lighthouse-fixture', - environment: 'qa', - integrations: defaultIntegrations => defaultIntegrations.filter(integration => integration.name !== 'Breadcrumbs'), - }); -} else if (import.meta.env.MODE === 'no-browser-session') { - // Default integrations minus Breadcrumbs, which adds a lot of monkey patching to - // DOM and Network APIs as well as event targets and listeners - Sentry.init({ - dsn: import.meta.env.VITE_E2E_TEST_DSN as string | undefined, - release: 'lighthouse-fixture', - environment: 'qa', - integrations: defaultIntegrations => - defaultIntegrations.filter(integration => integration.name !== 'BrowserSession'), - }); -} else if (import.meta.env.MODE === 'init-only') { - // enabled: false makes the SDK a guaranteed no-op (no transport allocation, - // no DSN warning). We're measuring pure SDK-loading + tree-shaking cost. - Sentry.init({ enabled: false }); -} - -performance.measure('sentry-sdk-init-duration', { - detail: { mode: import.meta.env.MODE ?? 'unknown_mode' }, - start: sentryInitStart, - end: performance.now(), -}); -performance.mark('sentry-sdk-init-end', { - detail: { mode: import.meta.env.MODE ?? 'unknown_mode' }, -}); - -// 'no-sentry' mode: all branches above are statically dead, so Vite drops -// the @sentry/react import entirely from the bundle. - -// Elements tagged with `elementtiming` (e.g. the hero logo) emit a -// PerformanceElementTiming entry when painted. Re-emit each as a -// performance.measure spanning time origin -> render so the element's render -// duration lands on the same timeline as the SDK init marks. -// `PerformanceElementTiming` isn't in this project's TS lib, so type it locally. -type ElementTimingEntry = PerformanceEntry & { - identifier: string; - renderTime: number; - loadTime: number; -}; - -const elementTimingObserver = new PerformanceObserver(list => { - for (const entry of list.getEntries() as ElementTimingEntry[]) { - const renderEnd = entry.renderTime || entry.loadTime; - - performance.measure(`element-timing-${entry.identifier}`, { - detail: { mode: import.meta.env.MODE ?? 'unknown_mode', identifier: entry.identifier }, - start: 0, - end: renderEnd, - }); - } -}); - -elementTimingObserver.observe({ type: 'element', buffered: true }); - -const root = createRoot(document.getElementById('root')!); -root.render(); diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/tsconfig.json b/dev-packages/e2e-tests/test-applications/lighthouse-react/tsconfig.json deleted file mode 100644 index 3934b8f6d676..000000000000 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/tsconfig.node.json b/dev-packages/e2e-tests/test-applications/lighthouse-react/tsconfig.node.json deleted file mode 100644 index 42872c59f5b0..000000000000 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "skipLibCheck": true, - "module": "ESNext", - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/vite.config.ts b/dev-packages/e2e-tests/test-applications/lighthouse-react/vite.config.ts deleted file mode 100644 index 4f312acb1a84..000000000000 --- a/dev-packages/e2e-tests/test-applications/lighthouse-react/vite.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react-swc'; - -export default defineConfig({ - plugins: [react()], -}); From d045e1c26d128f5b9f29e1f5d497bd0df12310eb Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 30 Oct 2026 14:08:01 -0700 Subject: [PATCH 080/169] test(bundler-tests): Guard server runtime entries against build-time tooling (#25724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build tooling keeps leaking into server runtime entries: bundler plugins, orchestrion bundler transforms, Vite, Babel, WASM. Each time, users find it in a different consumer, often in a production deploy (#22794, #23034, #23243, #24775, #25470). Nothing stopped the same shape from coming back. The new test traces each server runtime entry with `@vercel/nft`, the tracer behind Next standalone and the Vercel adapters. It resolves each entry through a fixture, under the export conditions of each runtime, in ESM and CJS. It fails if the trace reaches build-only code, and prints the import chain to it. The trace stops at the first build-time module and does not expand globs. Without that, an entry that does reach build tooling walks all of Vite and Babel, and globs the whole repo, which takes minutes instead of seconds. `@sentry/astro` and `@sentry/solidstart` are known violations: their root entries also re-export the config-time `sentryAstro` and `withSentry`, and must keep doing so until v12. The test fails if either becomes clean, so the list cannot go stale. Refs #25676 --- Stack created with GitHub Stacks CLI • Give Feedback 💬 Co-authored-by: Claude Opus 5.5 (1M context) --- .cursor/BUGBOT.md | 1 + dev-packages/bundler-tests/package.json | 16 + .../tests/runtime-entries.test.ts | 278 ++++++++++++++++++ pnpm-lock.yaml | 43 +++ 4 files changed, 338 insertions(+) create mode 100644 dev-packages/bundler-tests/tests/runtime-entries.test.ts diff --git a/.cursor/BUGBOT.md b/.cursor/BUGBOT.md index 7091a161f177..4a9d0bd65ef4 100644 --- a/.cursor/BUGBOT.md +++ b/.cursor/BUGBOT.md @@ -112,6 +112,7 @@ Keep reviews high-signal. Prefer actionable, high-confidence findings over specu - When any `setTimeout` or `setInterval` timers are started in a code path that can end up in server runtime packages (e.g. `@sentry/core` or `@sentry/node`), flag if neither `timeout.unref()` nor `safeUnref()` are called. Not unref'ing a timer can keep CLI-like applications or node scripts from exiting immediately, due to the process waiting on timers started by the SDK. - Flag Node-only imports (`fs`, `path`, `child_process`, `os`, `net`, `http`, `https`, `node:*`, etc.) in code paths that ship to non-Node runtimes (`@sentry/browser`, `@sentry/cloudflare`, `@sentry/deno`, `@sentry/bun`, or shared code in `@sentry/core` / `@sentry/browser-utils` reachable from those entry points). When the dependency is unavoidable, isolate it behind a runtime check, dynamic `import()`, or a Node-only entry point — never at the top level of a cross-runtime module. +- Flag a new package, or a new `exports` entry in a `package.json` under `packages/`, that can load on a server runtime (Node, Bun, Deno, Cloudflare Workers, Vercel Edge) when its package has no entry in `CASES` or `BUILD_TIME_CASES` in `dev-packages/bundler-tests/tests/runtime-entries.test.ts`. That test checks that server runtime entries do not reach build-time tooling (bundler plugins, Vite, Babel, WASM). For packages it already covers, the test fails on its own when an export is not classified, so do not flag those. Recommend adding the package to `devDependencies` in `dev-packages/bundler-tests/package.json`, and each export to `CASES` (runtime), `BUILD_TIME_CASES` (build-time), or `CLIENT_ENTRIES` (browser). Do not flag browser-only packages. ## What NOT to Flag diff --git a/dev-packages/bundler-tests/package.json b/dev-packages/bundler-tests/package.json index 35d63991cbc8..200bc3ea9668 100644 --- a/dev-packages/bundler-tests/package.json +++ b/dev-packages/bundler-tests/package.json @@ -19,5 +19,21 @@ "vitest": "^4.1.11", "webpack": "^5.112.2" }, + "devDependencies": { + "@sentry/astro": "11.10.0", + "@sentry/bun": "11.10.0", + "@sentry/cloudflare": "11.10.0", + "@sentry/deno": "11.10.0", + "@sentry/nextjs": "11.10.0", + "@sentry/node": "11.10.0", + "@sentry/nuxt": "11.10.0", + "@sentry/react-router": "11.10.0", + "@sentry/remix": "11.10.0", + "@sentry/solidstart": "11.10.0", + "@sentry/sveltekit": "11.10.0", + "@sentry/tanstackstart-react": "11.10.0", + "@sentry/vercel-edge": "11.10.0", + "@vercel/nft": "^1.11.0" + }, "type": "module" } diff --git a/dev-packages/bundler-tests/tests/runtime-entries.test.ts b/dev-packages/bundler-tests/tests/runtime-entries.test.ts new file mode 100644 index 000000000000..5b8d881d286d --- /dev/null +++ b/dev-packages/bundler-tests/tests/runtime-entries.test.ts @@ -0,0 +1,278 @@ +import { describe, expect, test } from 'vitest'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +import { nodeFileTrace, resolve } from '@vercel/nft'; + +/** + * Server runtime entries must not reach build-time tooling. When they do, + * the tooling ends up in server bundles and traced outputs (Next standalone, + * Nitro, workerd, ...), where it breaks in a different way for each + * consumer. `@vercel/nft` is the tracer that Next standalone and the Vercel + * adapters use, so it reports what those consumers would ship. + */ + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT_DIR = path.join(__dirname, '../../..'); +const FIXTURE_DIR = path.join(__dirname, '..', 'dist', 'runtime-entries'); + +const BUILD_TIME_PATTERNS: RegExp[] = [ + /(^|\/)packages\/bundler-plugins\//, + /(^|\/)node_modules\/@sentry\/bundler-plugins(\/|$)/, + /(^|\/)packages\/server-utils\/build\/[^/]+\/orchestrion\/bundler\//, + /(^|\/)node_modules\/(vite|webpack|rollup|esbuild|oxc-parser)\//, + /(^|\/)node_modules\/@(babel|rollup|oxc-parser)\//, + /\.wasm$/, +]; + +const KNOWN_VIOLATIONS = new Set([ + // These root entries also re-export config-time code (`sentryAstro`, + // `withSentry`). They must keep it until v12 for backwards compatibility. + '@sentry/astro', + '@sentry/solidstart', + // Remix 3 has no bundler. Its asset server runs the orchestrion + // transform on browser modules at request time. + '@sentry/remix/v3', + '@sentry/remix/v3/node', +]); + +type Format = 'esm' | 'cjs'; + +interface Case { + specifier: string; + conditions: string[]; + formats: Format[]; +} + +const NODE = ['node']; +const WORKERD = ['workerd', 'worker']; +const BOTH: Format[] = ['esm', 'cjs']; +const ESM: Format[] = ['esm']; + +const CASES: Case[] = [ + { specifier: '@sentry/node', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/node/import', conditions: NODE, formats: ESM }, + { specifier: '@sentry/bun', conditions: ['bun', 'node'], formats: BOTH }, + { specifier: '@sentry/deno', conditions: ['deno', 'node'], formats: ESM }, + { specifier: '@sentry/vercel-edge', conditions: ['edge-light'], formats: BOTH }, + { specifier: '@sentry/cloudflare', conditions: WORKERD, formats: BOTH }, + { specifier: '@sentry/cloudflare', conditions: [...WORKERD, 'development'], formats: BOTH }, + { specifier: '@sentry/cloudflare/request', conditions: WORKERD, formats: BOTH }, + { specifier: '@sentry/cloudflare/orchestrion-diagnostics-channel', conditions: WORKERD, formats: BOTH }, + { specifier: '@sentry/deno/import', conditions: ['deno', 'node'], formats: ESM }, + + { specifier: '@sentry/nextjs', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/nextjs', conditions: ['edge-light'], formats: BOTH }, + { specifier: '@sentry/nextjs', conditions: WORKERD, formats: BOTH }, + { specifier: '@sentry/nextjs/cloudflare', conditions: WORKERD, formats: BOTH }, + { specifier: '@sentry/nextjs/import', conditions: NODE, formats: ESM }, + { specifier: '@sentry/nextjs/tunnel', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/nextjs/tunnel', conditions: ['edge-light'], formats: BOTH }, + { specifier: '@sentry/nextjs/async-storage-shim', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/nextjs/orchestrion-runtime/index', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/nextjs/orchestrion-runtime/register', conditions: NODE, formats: BOTH }, + + { specifier: '@sentry/nuxt', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/nuxt/module/plugins', conditions: NODE, formats: ESM }, + + { specifier: '@sentry/sveltekit', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/sveltekit', conditions: WORKERD, formats: BOTH }, + { specifier: '@sentry/sveltekit', conditions: ['worker'], formats: BOTH }, + { specifier: '@sentry/sveltekit/opentelemetry-api', conditions: NODE, formats: BOTH }, + + { specifier: '@sentry/astro', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/astro/middleware', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/astro/import', conditions: NODE, formats: ESM }, + + { specifier: '@sentry/react-router', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/react-router', conditions: WORKERD, formats: BOTH }, + { specifier: '@sentry/react-router/cloudflare', conditions: WORKERD, formats: BOTH }, + + { specifier: '@sentry/remix', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/remix', conditions: ['worker'], formats: BOTH }, + { specifier: '@sentry/remix/cloudflare', conditions: WORKERD, formats: BOTH }, + { specifier: '@sentry/remix/import', conditions: NODE, formats: ESM }, + { specifier: '@sentry/remix/v3', conditions: NODE, formats: ESM }, + { specifier: '@sentry/remix/v3/node', conditions: NODE, formats: ESM }, + + { specifier: '@sentry/solidstart', conditions: NODE, formats: BOTH }, + + { specifier: '@sentry/tanstackstart-react', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/tanstackstart-react/import', conditions: NODE, formats: ESM }, +]; + +/** + * Build-time entries must reach build-time tooling. If one does not, then + * `BUILD_TIME_PATTERNS` no longer matches the tooling that we use. + */ +const BUILD_TIME_CASES: Case[] = [ + { specifier: '@sentry/node/vite', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/node/rollup', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/node/webpack', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/node/esbuild', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/bun/plugin', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/cloudflare/vite', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/nextjs/config', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/nuxt/module', conditions: NODE, formats: ESM }, + { specifier: '@sentry/sveltekit/vite', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/react-router/vite', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/remix/vite', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/solidstart/vite', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/tanstackstart-react/vite', conditions: NODE, formats: BOTH }, +]; + +/** Browser entries of the packages above. This test does not check them. */ +const CLIENT_ENTRIES = new Set([ + '@sentry/sveltekit/browser-tracing-variant', + '@sentry/remix/v3/client', + '@sentry/solidstart/solidrouter', +]); + +function writeFixture(specifier: string, conditions: string[], format: Format): string { + fs.mkdirSync(FIXTURE_DIR, { recursive: true }); + const name = `${specifier.replace(/[@/]/g, '_')}-${conditions.join('_')}.${format === 'esm' ? 'mjs' : 'cjs'}`; + const file = path.join(FIXTURE_DIR, name); + fs.writeFileSync(file, format === 'esm' ? `import '${specifier}';\n` : `require('${specifier}');\n`); + return file; +} + +function isBuildTime(file: string): boolean { + return BUILD_TIME_PATTERNS.some(pattern => pattern.test(file)); +} + +type Reasons = Awaited>['reasons']; + +// Most entries share `@sentry/node` and its dependencies, so sharing the +// file and analysis cache saves most of the work. +const traceCache = {}; + +/** The shortest import chain from `entry` to `file`, both relative to `ROOT_DIR`. */ +function importChain(reasons: Reasons, entry: string, file: string): string[] { + const importedBy = new Map([[file, file]]); + const queue = [file]; + while (queue.length) { + const current = queue.shift() as string; + if (current === entry) { + const chain = [current]; + for (let link = current; link !== file;) { + link = importedBy.get(link) as string; + chain.push(link); + } + return chain; + } + for (const parent of reasons.get(current)?.parents ?? []) { + if (!importedBy.has(parent)) { + importedBy.set(parent, current); + queue.push(parent); + } + } + } + return [file]; +} + +/** + * Each place where a runtime module imports a build-time module, as the + * import chain from the entry. + */ +async function findBuildTimeImports(fixture: string, conditions: string[]): Promise { + const imports: [importer: string, file: string][] = []; + + const { fileList, reasons } = await nodeFileTrace([fixture], { + base: ROOT_DIR, + conditions, + cache: traceCache, + // A dynamic `fs` path in config code makes nft glob the whole repo. + // That is slow and finds user files, not package code. + analysis: { emitGlobs: false }, + // Stop at the first build-time module, instead of tracing all of Vite + // or Babel. `.wasm` files are assets, so they stay in the trace with + // their importer. + ignore: file => isBuildTime(file) && !file.endsWith('.wasm'), + async resolve(id, parent, job, cjsResolve) { + const resolved = await resolve(id, parent, job, cjsResolve); + for (const file of [resolved].flat()) { + if (isBuildTime(file)) { + imports.push([path.relative(ROOT_DIR, parent), path.relative(ROOT_DIR, file)]); + } + } + return resolved; + }, + }); + + for (const file of fileList) { + if (file.endsWith('.wasm')) { + for (const parent of reasons.get(file)?.parents ?? []) { + imports.push([parent, file]); + } + } + } + + const entry = path.relative(ROOT_DIR, fixture); + const violations = imports.map(([importer, file]) => + importChain(reasons, entry, importer).concat(file).join('\n -> '), + ); + return [...new Set(violations)].sort(); +} + +describe('server runtime entries do not reach build-time tooling', () => { + for (const { specifier, conditions, formats } of CASES) { + for (const format of formats) { + const name = `${specifier} (${format}, conditions: ${conditions.join(', ')})`; + + if (KNOWN_VIOLATIONS.has(specifier)) { + test(`${name} is a known violation`, async () => { + const violations = await findBuildTimeImports(writeFixture(specifier, conditions, format), conditions); + // Remove the entry from KNOWN_VIOLATIONS when this fails. + expect(violations).not.toEqual([]); + }); + } else { + test(name, async () => { + const violations = await findBuildTimeImports(writeFixture(specifier, conditions, format), conditions); + expect(violations).toEqual([]); + }); + } + } + } +}); + +describe('build-time entries reach build-time tooling', () => { + for (const { specifier, conditions, formats } of BUILD_TIME_CASES) { + for (const format of formats) { + test(`${specifier} (${format}, conditions: ${conditions.join(', ')})`, async () => { + const violations = await findBuildTimeImports(writeFixture(specifier, conditions, format), conditions); + expect(violations).not.toEqual([]); + }); + } + } +}); + +test('every export of a tested package is classified', () => { + const classified = new Set([...CASES, ...BUILD_TIME_CASES].map(({ specifier }) => specifier)); + for (const specifier of CLIENT_ENTRIES) { + classified.add(specifier); + } + const packages = new Set([...classified].map(specifier => specifier.split('/').slice(0, 2).join('/'))); + + const unclassified: string[] = []; + for (const pkg of packages) { + const manifest = path.join(ROOT_DIR, 'packages', pkg.replace('@sentry/', ''), 'package.json'); + const { exports } = JSON.parse(fs.readFileSync(manifest, 'utf8')) as { exports: Record }; + for (const subpath of Object.keys(exports)) { + if (subpath === './package.json') { + continue; + } + const specifier = pkg + subpath.slice(1); + const [prefix, suffix] = specifier.split('*'); + const found = + suffix === undefined + ? classified.has(specifier) + : [...classified].some(s => s.startsWith(prefix as string) && s.endsWith(suffix)); + if (!found) { + unclassified.push(specifier); + } + } + } + // Add each one to CASES, BUILD_TIME_CASES, or CLIENT_ENTRIES. + expect(unclassified).toEqual([]); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0af122f23499..462fb89b83ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -426,6 +426,49 @@ importers: webpack: specifier: ^5.112.2 version: 5.112.2(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.28.1)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.5.31)(sharp@0.35.5(@types/node@20.19.43))(svgo@4.1.0)(uglify-js@3.13.3) + devDependencies: + '@sentry/astro': + specifier: 11.10.0 + version: link:../../packages/astro + '@sentry/bun': + specifier: 11.10.0 + version: link:../../packages/bun + '@sentry/cloudflare': + specifier: 11.10.0 + version: link:../../packages/cloudflare + '@sentry/deno': + specifier: 11.10.0 + version: link:../../packages/deno + '@sentry/nextjs': + specifier: 11.10.0 + version: link:../../packages/nextjs + '@sentry/node': + specifier: 11.10.0 + version: link:../../packages/node + '@sentry/nuxt': + specifier: 11.10.0 + version: link:../../packages/nuxt + '@sentry/react-router': + specifier: 11.10.0 + version: link:../../packages/react-router + '@sentry/remix': + specifier: 11.10.0 + version: link:../../packages/remix + '@sentry/solidstart': + specifier: 11.10.0 + version: link:../../packages/solidstart + '@sentry/sveltekit': + specifier: 11.10.0 + version: link:../../packages/sveltekit + '@sentry/tanstackstart-react': + specifier: 11.10.0 + version: link:../../packages/tanstackstart-react + '@sentry/vercel-edge': + specifier: 11.10.0 + version: link:../../packages/vercel-edge + '@vercel/nft': + specifier: ^1.11.0 + version: 1.11.0(encoding@0.1.13)(rollup@4.62.3)(supports-color@10.0.0) dev-packages/clear-cache-gh-action: dependencies: From 3e24a705c0f8cdff3b0fde88cdcfb524f4ada555 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 30 Oct 2026 14:08:02 -0700 Subject: [PATCH 081/169] feat(solidstart): Export `withSentry` from `@sentry/solidstart/vite` (#25725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `withSentry` wraps the SolidStart config at build time. The root entry re-exports it, so the server runtime entry reaches `@sentry/bundler-plugins` and the orchestrion rollup transform. Any tool that traces or bundles the server entry then pulls that tooling in. `withSentry` now also comes from `@sentry/solidstart/vite`, next to the Vite plugin. The root export stays, deprecated, until the next major version, so existing `app.config.ts` files keep working. The E2E apps use the new path. Refs #25676 --- Stack created with GitHub Stacks CLI • Give Feedback 💬 Co-authored-by: Claude Opus 5.5 (1M context) --- .../solidstart-dynamic-import/app.config.ts | 2 +- .../test-applications/solidstart-spa/app.config.ts | 2 +- .../test-applications/solidstart-static/app.config.ts | 2 +- .../solidstart-top-level-import/app.config.ts | 2 +- .../e2e-tests/test-applications/solidstart/app.config.ts | 2 +- packages/solidstart/src/index.server.ts | 9 ++++++++- packages/solidstart/src/index.types.ts | 8 +++++++- packages/solidstart/src/vite/index.ts | 1 + 8 files changed, 21 insertions(+), 7 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/app.config.ts b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/app.config.ts index f41b1cb186ef..9835b34f5592 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/app.config.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/app.config.ts @@ -1,4 +1,4 @@ -import { withSentry } from '@sentry/solidstart'; +import { withSentry } from '@sentry/solidstart/vite'; import { defineConfig } from '@solidjs/start/config'; export default defineConfig( diff --git a/dev-packages/e2e-tests/test-applications/solidstart-spa/app.config.ts b/dev-packages/e2e-tests/test-applications/solidstart-spa/app.config.ts index 103ecb09a469..447d859e9d34 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-spa/app.config.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart-spa/app.config.ts @@ -1,4 +1,4 @@ -import { withSentry } from '@sentry/solidstart'; +import { withSentry } from '@sentry/solidstart/vite'; import { defineConfig } from '@solidjs/start/config'; export default defineConfig( diff --git a/dev-packages/e2e-tests/test-applications/solidstart-static/app.config.ts b/dev-packages/e2e-tests/test-applications/solidstart-static/app.config.ts index 71061cf25d96..f3d634978c16 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-static/app.config.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart-static/app.config.ts @@ -1,4 +1,4 @@ -import { withSentry } from '@sentry/solidstart'; +import { withSentry } from '@sentry/solidstart/vite'; import { defineConfig } from '@solidjs/start/config'; export default defineConfig( diff --git a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/app.config.ts b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/app.config.ts index e4e73e9fc570..f8211b6f0b52 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/app.config.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/app.config.ts @@ -1,4 +1,4 @@ -import { withSentry } from '@sentry/solidstart'; +import { withSentry } from '@sentry/solidstart/vite'; import { defineConfig } from '@solidjs/start/config'; export default defineConfig( diff --git a/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts b/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts index be3bce2116a7..fb663eb94953 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts @@ -1,4 +1,4 @@ -import { withSentry } from '@sentry/solidstart'; +import { withSentry } from '@sentry/solidstart/vite'; import { defineConfig } from '@solidjs/start/config'; export default defineConfig( diff --git a/packages/solidstart/src/index.server.ts b/packages/solidstart/src/index.server.ts index 82b6fe6cbff4..f0ef7dbb60d6 100644 --- a/packages/solidstart/src/index.server.ts +++ b/packages/solidstart/src/index.server.ts @@ -1,2 +1,9 @@ +import { withSentry as _withSentry } from './config'; + export * from './server'; -export * from './config'; + +/** + * @deprecated Import `withSentry` from `@sentry/solidstart/vite` instead. + * This export will be removed in the next major version. + */ +export const withSentry = _withSentry; diff --git a/packages/solidstart/src/index.types.ts b/packages/solidstart/src/index.types.ts index 3ff25e7fa679..b7e71168b171 100644 --- a/packages/solidstart/src/index.types.ts +++ b/packages/solidstart/src/index.types.ts @@ -7,11 +7,17 @@ // exports in this file - which we do below. import type { Client, Integration, Options, StackParser } from '@sentry/core'; import type * as clientSdk from './client'; +import type * as configSdk from './config'; import type * as serverSdk from './server'; export * from './client'; export * from './server'; -export * from './config'; + +/** + * @deprecated Import `withSentry` from `@sentry/solidstart/vite` instead. + * This export will be removed in the next major version. + */ +export declare const withSentry: typeof configSdk.withSentry; /** Initializes Sentry Solid Start SDK */ export declare function init(options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions): Client | undefined; diff --git a/packages/solidstart/src/vite/index.ts b/packages/solidstart/src/vite/index.ts index b23359c06ba5..2289ae888f98 100644 --- a/packages/solidstart/src/vite/index.ts +++ b/packages/solidstart/src/vite/index.ts @@ -1,2 +1,3 @@ export { sentrySolidStart } from './sentrySolidStart'; export type { SentrySolidStartOptions } from './sentrySolidStart'; +export { withSentry } from '../config/withSentry'; From f74a45dfa4eac8872de49b5b7f9d3b4fa0eb1036 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 30 Oct 2026 14:08:02 -0700 Subject: [PATCH 082/169] feat(astro): Add `@sentry/astro/integration` entry for the Astro integration (#25726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The root entry of `@sentry/astro` is both the server runtime SDK and, through its default export, the Astro integration for `astro.config`. The integration imports `@sentry/bundler-plugins/vite`, the orchestrion Vite plugin, `fs` and `createRequire`, so the server runtime entry reaches all of them. Any tool that traces or bundles the server entry then pulls that tooling in. The new `@sentry/astro/integration` entry exports the integration as its default and as `sentryAstro`. The root default export stays, deprecated, until the next major version, so existing `astro.config` files keep working. The E2E apps use the new entry. The new entry has a `node` condition that points at the ESM build, the same as `./middleware`. The CJS build of `@sentry/astro` does not load under the package's `"type": "module"`, and the existing entries avoid it the same way. Fixes #25676 --- Stack created with GitHub Stacks CLI • Give Feedback 💬 Co-authored-by: Claude Opus 5.5 (1M context) --- dev-packages/bundler-tests/tests/runtime-entries.test.ts | 1 + .../e2e-tests/test-applications/astro-4/astro.config.mjs | 2 +- .../astro-5-cf-workers/astro.config.mjs | 2 +- .../e2e-tests/test-applications/astro-5/astro.config.mjs | 2 +- .../astro-6-cf-workers/astro.config.mjs | 2 +- .../e2e-tests/test-applications/astro-6/astro.config.mjs | 2 +- .../astro-7-sourcemaps/astro.config.mjs | 2 +- .../test-applications/astro-7-static/astro.config.mjs | 2 +- .../e2e-tests/test-applications/astro-7/astro.config.mjs | 2 +- packages/astro/package.json | 6 ++++++ packages/astro/rollup.npm.config.mjs | 7 ++++++- packages/astro/src/index.integration.ts | 5 +++++ packages/astro/src/index.server.ts | 9 ++++++++- packages/astro/src/index.types.ts | 1 + packages/astro/test/server/index.server.test.ts | 5 +++-- 15 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 packages/astro/src/index.integration.ts diff --git a/dev-packages/bundler-tests/tests/runtime-entries.test.ts b/dev-packages/bundler-tests/tests/runtime-entries.test.ts index 5b8d881d286d..156102cc6765 100644 --- a/dev-packages/bundler-tests/tests/runtime-entries.test.ts +++ b/dev-packages/bundler-tests/tests/runtime-entries.test.ts @@ -116,6 +116,7 @@ const BUILD_TIME_CASES: Case[] = [ { specifier: '@sentry/nextjs/config', conditions: NODE, formats: BOTH }, { specifier: '@sentry/nuxt/module', conditions: NODE, formats: ESM }, { specifier: '@sentry/sveltekit/vite', conditions: NODE, formats: BOTH }, + { specifier: '@sentry/astro/integration', conditions: NODE, formats: BOTH }, { specifier: '@sentry/react-router/vite', conditions: NODE, formats: BOTH }, { specifier: '@sentry/remix/vite', conditions: NODE, formats: BOTH }, { specifier: '@sentry/solidstart/vite', conditions: NODE, formats: BOTH }, diff --git a/dev-packages/e2e-tests/test-applications/astro-4/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-4/astro.config.mjs index 890d1f737e9a..d0848b0f6d00 100644 --- a/dev-packages/e2e-tests/test-applications/astro-4/astro.config.mjs +++ b/dev-packages/e2e-tests/test-applications/astro-4/astro.config.mjs @@ -1,5 +1,5 @@ import node from '@astrojs/node'; -import sentry from '@sentry/astro'; +import sentry from '@sentry/astro/integration'; import { defineConfig } from 'astro/config'; import spotlightjs from '@spotlightjs/astro'; diff --git a/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/astro.config.mjs index 0473ef424d83..a3687388202b 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/astro.config.mjs +++ b/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/astro.config.mjs @@ -1,5 +1,5 @@ import cloudflare from '@astrojs/cloudflare'; -import sentry from '@sentry/astro'; +import sentry from '@sentry/astro/integration'; // @ts-check import { defineConfig } from 'astro/config'; diff --git a/dev-packages/e2e-tests/test-applications/astro-5/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-5/astro.config.mjs index 061db869d94b..54d1c2bbb8cd 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/astro.config.mjs +++ b/dev-packages/e2e-tests/test-applications/astro-5/astro.config.mjs @@ -1,4 +1,4 @@ -import sentry from '@sentry/astro'; +import sentry from '@sentry/astro/integration'; // @ts-check import { defineConfig } from 'astro/config'; diff --git a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/astro.config.mjs index 4a09ea6bd88e..617cd784f967 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/astro.config.mjs +++ b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/astro.config.mjs @@ -1,4 +1,4 @@ -import sentry from '@sentry/astro'; +import sentry from '@sentry/astro/integration'; // @ts-check import { defineConfig } from 'astro/config'; import cloudflare from '@astrojs/cloudflare'; diff --git a/dev-packages/e2e-tests/test-applications/astro-6/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-6/astro.config.mjs index 061db869d94b..54d1c2bbb8cd 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6/astro.config.mjs +++ b/dev-packages/e2e-tests/test-applications/astro-6/astro.config.mjs @@ -1,4 +1,4 @@ -import sentry from '@sentry/astro'; +import sentry from '@sentry/astro/integration'; // @ts-check import { defineConfig } from 'astro/config'; diff --git a/dev-packages/e2e-tests/test-applications/astro-7-sourcemaps/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-7-sourcemaps/astro.config.mjs index 3b859eeb1c75..53887471a483 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7-sourcemaps/astro.config.mjs +++ b/dev-packages/e2e-tests/test-applications/astro-7-sourcemaps/astro.config.mjs @@ -1,5 +1,5 @@ import node from '@astrojs/node'; -import sentry from '@sentry/astro'; +import sentry from '@sentry/astro/integration'; import { defineConfig } from 'astro/config'; import * as fs from 'node:fs'; diff --git a/dev-packages/e2e-tests/test-applications/astro-7-static/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-7-static/astro.config.mjs index 061db869d94b..54d1c2bbb8cd 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7-static/astro.config.mjs +++ b/dev-packages/e2e-tests/test-applications/astro-7-static/astro.config.mjs @@ -1,4 +1,4 @@ -import sentry from '@sentry/astro'; +import sentry from '@sentry/astro/integration'; // @ts-check import { defineConfig } from 'astro/config'; diff --git a/dev-packages/e2e-tests/test-applications/astro-7/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-7/astro.config.mjs index 49370ed1e18b..7392e48b5cb7 100644 --- a/dev-packages/e2e-tests/test-applications/astro-7/astro.config.mjs +++ b/dev-packages/e2e-tests/test-applications/astro-7/astro.config.mjs @@ -1,4 +1,4 @@ -import sentry from '@sentry/astro'; +import sentry from '@sentry/astro/integration'; // @ts-check import { defineConfig } from 'astro/config'; diff --git a/packages/astro/package.json b/packages/astro/package.json index b505c7ab2b84..58459cd48530 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -32,6 +32,12 @@ "import": "./build/esm/index.client.js", "require": "./build/cjs/index.server.js" }, + "./integration": { + "types": "./build/types/index.integration.d.ts", + "node": "./build/esm/index.integration.js", + "import": "./build/esm/index.integration.js", + "require": "./build/cjs/index.integration.js" + }, "./middleware": { "types": "./build/types/integration/middleware/index.d.ts", "node": "./build/esm/integration/middleware/index.js", diff --git a/packages/astro/rollup.npm.config.mjs b/packages/astro/rollup.npm.config.mjs index e4694afbff88..07a25d8690b1 100644 --- a/packages/astro/rollup.npm.config.mjs +++ b/packages/astro/rollup.npm.config.mjs @@ -2,7 +2,12 @@ import { makeBaseNPMConfig, makeNPMConfigVariants, makeOrchestrionLoader } from const variants = makeNPMConfigVariants( makeBaseNPMConfig({ - entrypoints: ['src/index.server.ts', 'src/index.client.ts', 'src/integration/middleware/index.ts'], + entrypoints: [ + 'src/index.server.ts', + 'src/index.client.ts', + 'src/index.integration.ts', + 'src/integration/middleware/index.ts', + ], packageSpecificConfig: { output: { dynamicImportInCjs: true, diff --git a/packages/astro/src/index.integration.ts b/packages/astro/src/index.integration.ts new file mode 100644 index 000000000000..64465a872d83 --- /dev/null +++ b/packages/astro/src/index.integration.ts @@ -0,0 +1,5 @@ +import { sentryAstro } from './integration'; + +export { sentryAstro }; + +export default sentryAstro; diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 822464ade0e1..6837722ba5b6 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -3,7 +3,7 @@ // Vite puts these exports into a `default` property (Sentry.default) rather than // on the top - level namespace. -import { sentryAstro } from './integration'; +import { sentryAstro as _sentryAstro } from './integration'; import { handleRequest } from './server/middleware'; // Hence, we export everything from the Node SDK explicitly: @@ -219,6 +219,13 @@ export { export { init } from './server/sdk'; +/** + * @deprecated Import the integration from `@sentry/astro/integration` instead. + * This export will be removed in the next major version. + */ +const sentryAstro = _sentryAstro; + +// oxlint-disable-next-line typescript/no-deprecated export default sentryAstro; // This exports the `handleRequest` middleware for manual usage diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts index c08b0b7199f4..c6287009e916 100644 --- a/packages/astro/src/index.types.ts +++ b/packages/astro/src/index.types.ts @@ -46,4 +46,5 @@ export declare const statsigIntegration: typeof clientSdk.statsigIntegration; export declare const unleashIntegration: typeof clientSdk.unleashIntegration; export declare const growthbookIntegration: typeof clientSdk.growthbookIntegration; +// oxlint-disable-next-line typescript/no-deprecated export default sentryAstro; diff --git a/packages/astro/test/server/index.server.test.ts b/packages/astro/test/server/index.server.test.ts index 4a89ebe16a13..6c1ed2de3127 100644 --- a/packages/astro/test/server/index.server.test.ts +++ b/packages/astro/test/server/index.server.test.ts @@ -1,9 +1,10 @@ import { describe, expect, it } from 'vitest'; +import sentryAstroIntegration from '../../src/index.integration'; +// oxlint-disable-next-line typescript/no-deprecated import sentryAstro from '../../src/index.server'; describe('server SDK', () => { it('exports the astro integration as a default export', () => { - const integration = sentryAstro(); - expect(integration.name).toBe('@sentry/astro'); + expect(sentryAstro).toBe(sentryAstroIntegration); }); }); From 34dfa4dbac93331b38ea5d86c200a4c91307a595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Sat, 31 Oct 2026 16:12:02 -0600 Subject: [PATCH 083/169] ci(node): Add optional Node 27 alpha tests (#25984) part of #25983 part of [JS-4052](https://linear.app/getsentry/issue/JS-4052) ## Description This adds the Node 27 alpha (`^27.0.0-0`) to the Node unit test, Node integration test and bundler plugin test matrices. The Node 27 jobs use `continue-on-error`, so a failure in them does not fail the required checks. Before the unit and integration tests, the Node 27 jobs compile `@sentry/node-cpu-profiler` and `@sentry/node-native-stacktrace` from source with `pnpm rebuild`, because the alpha has no prebuilt binaries. This is the same path as an install on a user's machine, so it also works when a new alpha changes the ABI. ## CI decisions The jobs are optional through `continue-on-error`. A failed Node 27 job still shows a red cross on the PR. The alternative is to run them in the optional workflow (`optional-e2e.yml`), which reports failures in a PR comment. Co-authored-by: Claude Opus 5.5 --- .github/workflows/build.yml | 22 +++++++++++++++++++--- docs/supporting-a-new-node-version.md | 3 +++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a546292a32ef..13e6c36d43b0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -571,10 +571,12 @@ jobs: runs-on: namespace-profile-sentry-javascript-large;overrides.cache-tag=sentry-javascript${{ github.event_name == 'pull_request' && '-pr' || '' }} + # Node 27 is an alpha: its ABI and APIs can change, so its failures must not fail the required checks. + continue-on-error: ${{ matrix.node == '^27.0.0-0' }} strategy: fail-fast: false matrix: - node: [20.19, 22, 24, 26] + node: [20.19, 22, 24, 26, '^27.0.0-0'] steps: - name: Check out base commit (${{ github.event.pull_request.base.sha }}) uses: actions/checkout@v7 @@ -591,6 +593,11 @@ jobs: node-version: ${{ matrix.node }} - name: Restore caches uses: ./.github/actions/restore-cache + # The native modules have no prebuilt binaries for the Node 27 alpha, so compile them like an install on a + # user's machine does. + - name: Compile native modules + if: matrix.node == '^27.0.0-0' + run: pnpm rebuild @sentry/node-cpu-profiler @sentry/node-native-stacktrace - name: Run affected tests run: pnpm run test:pr:node --base=${{ github.event.pull_request.base.sha }} @@ -772,10 +779,12 @@ jobs: namespace-profile-sentry-javascript-large;overrides.cache-tag=sentry-javascript${{ github.event_name == 'pull_request' && '-pr' || '' }} timeout-minutes: 20 + # Node 27 is an alpha: its ABI and APIs can change, so its failures must not fail the required checks. + continue-on-error: ${{ matrix.node == '^27.0.0-0' }} strategy: fail-fast: false matrix: - node: [20.19, 22, 24, 26] + node: [20.19, 22, 24, 26, '^27.0.0-0'] shard: [1, 2, 3] steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) @@ -801,6 +810,11 @@ jobs: node-version: ${{ matrix.node }} - name: Restore caches uses: ./.github/actions/restore-cache + # The native modules have no prebuilt binaries for the Node 27 alpha, so compile them like an install on a + # user's machine does. + - name: Compile native modules + if: matrix.node == '^27.0.0-0' + run: pnpm rebuild @sentry/node-cpu-profiler @sentry/node-native-stacktrace - name: Run integration tests working-directory: dev-packages/node-integration-tests @@ -873,10 +887,12 @@ jobs: namespace-profile-sentry-javascript-large;overrides.cache-tag=sentry-javascript${{ github.event_name == 'pull_request' && '-pr' || '' }} timeout-minutes: 15 + # Node 27 is an alpha: its ABI and APIs can change, so its failures must not fail the required checks. + continue-on-error: ${{ matrix.node == '^27.0.0-0' }} strategy: fail-fast: false matrix: - node: [20.19, 22, 24, 26] + node: [20.19, 22, 24, 26, '^27.0.0-0'] steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v7 diff --git a/docs/supporting-a-new-node-version.md b/docs/supporting-a-new-node-version.md index d952742d7503..4720570d1866 100644 --- a/docs/supporting-a-new-node-version.md +++ b/docs/supporting-a-new-node-version.md @@ -87,6 +87,9 @@ Repo: [sentry-javascript](https://github.com/getsentry/sentry-javascript) Reference: [#20710](https://github.com/getsentry/sentry-javascript/pull/20710) 1. Add the version to the CI test matrix in `.github/workflows/build.yml` to every `node: [20.19, 22, 24, 26]` entry. + While the new major is an alpha, the matrix has it as a range (e.g. `'^27.0.0-0'`) with `continue-on-error`, and a + `Compile native modules` step compiles the native modules from source for it. When the major is released, replace + the range with the major and remove `continue-on-error` and that step. 2. Bump the native module dependencies to the versions released in Steps 1 and 2: - [ ] `@sentry-internal/node-cpu-profiler` in `packages/profiling-node` From fd69ba21ea559b9a17527c3ad2e082201bcc0a3d Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Mon, 2 Nov 2026 08:44:28 -0800 Subject: [PATCH 084/169] feat(nestjs): Add an option to monitor every @Cron method (#25471) Part of a series adding automatic Crons monitoring for the most-used job schedulers in each SDK. Check-ins for a monitor that doesn't exist yet are dropped, so today each monitor has to be created by hand with its schedule copied from the code. Sending the job's schedule as the monitor config lets Sentry create the monitor and keep it in sync. Adds `monitorCronJobs` to `nestIntegration`. When it's on, every `@Cron()` method sends check-ins with the schedule and time zone from `@Cron()` as the monitor config, so apps don't need `@SentryCron` on each method. It is off by default. The slug is the `@Cron()` `name`, or `ClassName.methodName` without one. ```ts Sentry.init({ dsn: '...', integrations: [Sentry.nestIntegration({ monitorCronJobs: true, excludeCronJobs: ['cleanup', /^Dev/] })], }); ``` Methods that also have `@SentryCron` keep their slug and send one set of check-ins. Methods with schedules Crons can't express, like every 10 seconds, aren't monitored. It needs `enableRuntimeChannelInjection`, which is on by default. Stacked on https://github.com/getsentry/sentry-javascript/pull/25013. --------- Co-authored-by: Claude Opus 5.5 Co-authored-by: Nicolas Hrubec --- .../nestjs-basic/src/app.controller.ts | 6 + .../nestjs-basic/src/app.service.ts | 6 + .../nestjs-basic/src/instrument.ts | 1 + .../nestjs-basic/tests/cron-decorator.test.ts | 23 ++ packages/nestjs/src/decorators.ts | 5 +- packages/nestjs/src/index.ts | 1 + .../src/integrations/cron-monitoring.ts | 131 +++++++++ packages/nestjs/src/integrations/helpers.ts | 52 +++- packages/nestjs/src/integrations/nest.ts | 9 +- .../integrations/orchestrion-subscriber.ts | 40 ++- .../nestjs/src/integrations/wrap-handlers.ts | 43 ++- .../test/integrations/cron-monitoring.test.ts | 278 ++++++++++++++++++ .../src/orchestrion/config/nestjs.ts | 8 + 13 files changed, 587 insertions(+), 16 deletions(-) create mode 100644 packages/nestjs/src/integrations/cron-monitoring.ts create mode 100644 packages/nestjs/test/integrations/cron-monitoring.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts index 33584dea5e78..5a30cf575da8 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.controller.ts @@ -92,6 +92,12 @@ export class AppController { return {}; } + @Get('test-auto-monitored-cron') + async testAutoMonitoredCron() { + await this.appService.testAutoMonitoredCron(); + return {}; + } + @Get('kill-test-cron/:job') async killTestCron(@Param('job') job: string) { this.appService.killTestCron(job); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts index e9bb0c7f69c6..712f6777ccf5 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.service.ts @@ -120,6 +120,12 @@ export class AppService { console.log('Test derived cron!'); } + // Monitored through `monitorCronJobs`, without `@SentryCron`. + @Cron('0 0 * * *', { name: 'test-auto-monitored-cron', timeZone: 'America/New_York', disabled: true }) + async testAutoMonitoredCron() { + console.log('Test auto-monitored cron!'); + } + async killTestCron(job: string) { this.schedulerRegistry.deleteCronJob(job); } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts index 07454fc8d8c8..7b07f60a3252 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/instrument.ts @@ -5,6 +5,7 @@ Sentry.init({ dsn: process.env.E2E_TEST_DSN, tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, + integrations: [Sentry.nestIntegration({ monitorCronJobs: true })], transportOptions: { // We expect the app to send a lot of events in a short time bufferSize: 1000, diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts index e1d006ccdb5d..2302bd6bdb9c 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tests/cron-decorator.test.ts @@ -91,6 +91,29 @@ test('Sends the schedule and time zone of @Cron with the check-in', async ({ bas ); }); +test('Monitors @Cron methods without @SentryCron when monitorCronJobs is enabled', async ({ baseURL }) => { + const inProgressEnvelopePromise = waitForEnvelopeItem('nestjs-basic', envelope => { + return ( + envelope[0].type === 'check_in' && + envelope[1]['monitor_slug'] === 'test-auto-monitored-cron' && + envelope[1]['status'] === 'in_progress' + ); + }); + + await fetch(`${baseURL}/test-auto-monitored-cron`); + + expect((await inProgressEnvelopePromise)[1]).toEqual( + expect.objectContaining({ + monitor_slug: 'test-auto-monitored-cron', + status: 'in_progress', + monitor_config: { + schedule: { type: 'crontab', value: '0 0 * * *' }, + timezone: 'America/New_York', + }, + }), + ); +}); + test('Sends exceptions to Sentry on error in async cron job', async ({ baseURL }) => { const errorEventPromise = waitForError('nestjs-basic', event => { return ( diff --git a/packages/nestjs/src/decorators.ts b/packages/nestjs/src/decorators.ts index ead5d10338f0..0a74734adb8b 100644 --- a/packages/nestjs/src/decorators.ts +++ b/packages/nestjs/src/decorators.ts @@ -6,7 +6,7 @@ import { startSpan } from '@sentry/node'; import { DEBUG_BUILD } from './debug-build'; import { isExpectedError } from './helpers'; import type { ReflectWithMetadata } from './integrations/helpers'; -import { copyReflectMetadata, getNestScheduleVersion } from './integrations/helpers'; +import { copyReflectMetadata, getNestScheduleVersion, markSentryCronMethod } from './integrations/helpers'; /** * Monitor settings for `@SentryCron` whose schedule and time zone come from the `@Cron()` decorator @@ -62,6 +62,7 @@ export const SentryCron = ( descriptor.value = wrappedMethod; copyFunctionNameAndMetadata({ originalMethod, descriptor }); + markSentryCronMethod(wrappedMethod); return descriptor; }; @@ -138,7 +139,7 @@ function getMonitorConfigFromNestCron(candidates: unknown[]): MonitorConfig | un * Converts the options `@Cron()` stores as metadata into a Sentry monitor config. * Returns `undefined` for schedules Sentry can't represent. */ -function nestCronOptionsToMonitorConfig( +export function nestCronOptionsToMonitorConfig( cronOptions: NestCronOptions, scheduleVersion: string | undefined, ): MonitorConfig | undefined { diff --git a/packages/nestjs/src/index.ts b/packages/nestjs/src/index.ts index 10a3400ee400..683239d3c908 100644 --- a/packages/nestjs/src/index.ts +++ b/packages/nestjs/src/index.ts @@ -4,6 +4,7 @@ export * from '@sentry/node'; export { nestIntegration } from './integrations/nest'; +export type { NestIntegrationOptions } from './integrations/nest'; export { getDefaultIntegrations, init } from './sdk'; diff --git a/packages/nestjs/src/integrations/cron-monitoring.ts b/packages/nestjs/src/integrations/cron-monitoring.ts new file mode 100644 index 000000000000..30c6e74b5f48 --- /dev/null +++ b/packages/nestjs/src/integrations/cron-monitoring.ts @@ -0,0 +1,131 @@ +import type { Client, MonitorConfig } from '@sentry/core'; +import { debug, getClient, stringMatchesSomePattern } from '@sentry/core'; +import { DEBUG_BUILD } from '../debug-build'; +import { nestCronOptionsToMonitorConfig } from '../decorators'; +import { getCronOptions, isSentryCronMethod, markSentryCronMethod } from './helpers'; + +export interface NestCronMonitoringOptions { + /** + * Send check-ins for every `@Cron()` method of `@nestjs/schedule`. The monitor slug is the `name` + * option of `@Cron()`, or `ClassName.methodName`. Methods with `@SentryCron()` keep their own slug. + * Methods whose schedule Sentry can't represent, like every 10 seconds, are not monitored. + * Sentry cuts slugs off at 50 characters, so give `@Cron()` a `name` if the class and method names are long. + * + * Defaults to `false`. + */ + monitorCronJobs?: boolean; + + /** + * Monitor slugs not to monitor when `monitorCronJobs` is enabled. Strings must match the slug exactly. + */ + excludeCronJobs?: Array; +} + +interface CronMonitor { + slug: string; + config: MonitorConfig; +} + +const MONITORING_OPTIONS = new WeakMap(); + +/** Enables `@Cron()` monitoring for the client. */ +export function setCronMonitoringOptions(client: Client, options: NestCronMonitoringOptions): void { + if (options.monitorCronJobs) { + MONITORING_OPTIONS.set(client, options); + } +} + +const CRON_MONITORS = new WeakMap CronMonitor | undefined>(); + +/** + * Sets up `monitorCronJobs` for a method `@Cron()` just decorated, keyed by its `@Cron()` options, which + * decorators above it copy. A `@SentryCron()` below marks the options instead, so the method keeps its own slug. + */ +export function registerCronMonitor( + method: object, + handler: object, + cronArgs: unknown[], + target: unknown, + propertyKey: string | symbol | undefined, + scheduleVersion?: string, +): void { + if (isSentryCronMethod(handler)) { + markSentryCronMethod(method); + return; + } + const options = getCronOptions(method); + if (options) { + CRON_MONITORS.set(options, makeCronMonitorResolver(cronArgs, target, propertyKey, scheduleVersion)); + } +} + +/** The monitor a run of this `@Cron()` method sends check-ins for, if any. */ +export function resolveCronMonitor(method: object): CronMonitor | undefined { + const options = getCronOptions(method); + return options && CRON_MONITORS.get(options)?.(method); +} + +function makeCronMonitorResolver( + cronArgs: unknown[], + target: unknown, + propertyKey: string | symbol | undefined, + scheduleVersion: string | undefined, +): (method: object) => CronMonitor | undefined { + let monitor: CronMonitor | null | undefined; + + return method => { + const client = getClient(); + const options = client && MONITORING_OPTIONS.get(client); + if (!options) { + return undefined; + } + + if (monitor === undefined) { + try { + monitor = isSentryCronMethod(method) ? null : getCronMonitor(cronArgs, target, propertyKey, scheduleVersion); + } catch (e) { + DEBUG_BUILD && debug.error('[Nest] Failed to set up the cron monitor:', e); + monitor = null; + } + } + + if (!monitor || stringMatchesSomePattern(monitor.slug, options.excludeCronJobs, true)) { + return undefined; + } + return monitor; + }; +} + +function getCronMonitor( + [cronTime, cronOptions]: unknown[], + target: unknown, + propertyKey: string | symbol | undefined, + scheduleVersion: string | undefined, +): CronMonitor | null { + // A `Date` runs once, so there is nothing to monitor. + if (typeof cronTime !== 'string') { + return null; + } + + const options = (cronOptions && typeof cronOptions === 'object' ? cronOptions : {}) as Record; + const className = (target as { constructor?: { name?: unknown } } | undefined)?.constructor?.name; + const slug = + typeof options.name === 'string' && options.name + ? options.name + : typeof className === 'string' && className && propertyKey !== undefined + ? `${className}.${String(propertyKey)}` + : undefined; + + if (!slug) { + DEBUG_BUILD && debug.warn('[Nest] Not monitoring a @Cron() method without a name.'); + return null; + } + + const config = nestCronOptionsToMonitorConfig({ ...options, cronTime }, scheduleVersion); + if (!config) { + DEBUG_BUILD && debug.log(`[Nest] Not monitoring ${slug}: Sentry can't represent its schedule.`); + return null; + } + + return { slug, config }; +} diff --git a/packages/nestjs/src/integrations/helpers.ts b/packages/nestjs/src/integrations/helpers.ts index 8c2b5755d910..dd759e13825a 100644 --- a/packages/nestjs/src/integrations/helpers.ts +++ b/packages/nestjs/src/integrations/helpers.ts @@ -9,7 +9,13 @@ import { } from '@sentry/conventions/attributes'; import { FUNCTION, MIDDLEWARE, QUEUE_PROCESS } from '@sentry/conventions/op'; import type { Span } from '@sentry/core'; -import { addNonEnumerableProperty, getClient, hasSpanStreamingEnabled, withActiveSpan } from '@sentry/core'; +import { + addNonEnumerableProperty, + getClient, + hasSpanStreamingEnabled, + isObjectLike, + withActiveSpan, +} from '@sentry/core'; import type { CatchTarget, InjectableTarget, NextFunction, Observable, Subscription } from './types'; /** A function of unknown signature, matching the methods/handlers we wrap. */ @@ -33,17 +39,53 @@ export function markWrapped(fn: AnyFn): void { // `@nestjs/schedule`'s metadata key for the `@Cron()` options. const SCHEDULE_CRON_OPTIONS = 'SCHEDULE_CRON_OPTIONS'; +const SENTRY_CRON_METADATA = 'sentry:nestjs:sentry_cron'; + +/** The options `@Cron()` stored as metadata on `fn`, if any. */ +export function getCronOptions(fn: object): object | undefined { + const R = Reflect as ReflectWithMetadata; + const options = typeof R.getMetadata === 'function' ? R.getMetadata(SCHEDULE_CRON_OPTIONS, fn) : undefined; + return isObjectLike(options) ? options : undefined; +} + +/** + * Marks a method that `@SentryCron()` monitors, and the options of its `@Cron()` if it already has one, so + * `@Cron()` monitoring skips it. Decorators between it and a `@Cron()` above copy the metadata outward. + */ +export function markSentryCronMethod(fn: object): void { + const R = Reflect as ReflectWithMetadata; + if (typeof R.defineMetadata !== 'function') { + return; + } + R.defineMetadata(SENTRY_CRON_METADATA, true, fn); + const cronOptions = getCronOptions(fn); + if (cronOptions) { + R.defineMetadata(SENTRY_CRON_METADATA, true, cronOptions); + } +} + +/** Whether `fn` or its `@Cron()` options are marked by {@link markSentryCronMethod}. */ +export function isSentryCronMethod(fn: object): boolean { + const R = Reflect as ReflectWithMetadata; + if (typeof R.getMetadata !== 'function') { + return false; + } + const cronOptions = getCronOptions(fn); + return ( + R.getMetadata(SENTRY_CRON_METADATA, fn) === true || + (!!cronOptions && R.getMetadata(SENTRY_CRON_METADATA, cronOptions) === true) + ); +} const NEST_SCHEDULE_VERSIONS = new WeakMap(); /** Remembers the `@nestjs/schedule` version whose `@Cron()` stored the options on `method`. */ export function setNestScheduleVersion(method: unknown, version: string | undefined): void { - const R = Reflect as ReflectWithMetadata; - if (typeof method !== 'function' || !version || typeof R.getMetadata !== 'function') { + if (typeof method !== 'function' || !version) { return; } - const cronOptions = R.getMetadata(SCHEDULE_CRON_OPTIONS, method); - if (cronOptions && typeof cronOptions === 'object') { + const cronOptions = getCronOptions(method); + if (cronOptions) { NEST_SCHEDULE_VERSIONS.set(cronOptions, version); } } diff --git a/packages/nestjs/src/integrations/nest.ts b/packages/nestjs/src/integrations/nest.ts index 01ef7d722e9c..853caf825d71 100644 --- a/packages/nestjs/src/integrations/nest.ts +++ b/packages/nestjs/src/integrations/nest.ts @@ -1,16 +1,23 @@ import { defineIntegration } from '@sentry/core'; +import type { NestCronMonitoringOptions } from './cron-monitoring'; +import { setCronMonitoringOptions } from './cron-monitoring'; import { subscribeToNestChannels } from './orchestrion-subscriber'; const INTEGRATION_NAME = 'Nest' as const; +export type NestIntegrationOptions = NestCronMonitoringOptions; + /** * Integration capturing tracing data for NestJS. */ -export const nestIntegration = defineIntegration(() => { +export const nestIntegration = defineIntegration((options: NestIntegrationOptions = {}) => { return { name: INTEGRATION_NAME, setupOnce() { subscribeToNestChannels(); }, + setup(client) { + setCronMonitoringOptions(client, options); + }, }; }); diff --git a/packages/nestjs/src/integrations/orchestrion-subscriber.ts b/packages/nestjs/src/integrations/orchestrion-subscriber.ts index 0f207438d716..53dde4285a7f 100644 --- a/packages/nestjs/src/integrations/orchestrion-subscriber.ts +++ b/packages/nestjs/src/integrations/orchestrion-subscriber.ts @@ -1,20 +1,22 @@ import * as diagnosticsChannel from 'node:diagnostics_channel'; -import { debug, startInactiveSpan, waitForTracingChannelBinding } from '@sentry/core'; +import { addNonEnumerableProperty, debug, startInactiveSpan, waitForTracingChannelBinding } from '@sentry/core'; import type { OrchestrionChannelContext } from '@sentry/server-utils'; import { bindTracingChannelToSpan } from '@sentry/server-utils'; import { nestjsChannels as CHANNELS } from '@sentry/server-utils/orchestrion/config'; import { DEBUG_BUILD } from '../debug-build'; +import { registerCronMonitor } from './cron-monitoring'; import type { AnyFn } from './helpers'; import { isWrapped, markWrapped, setNestScheduleVersion } from './helpers'; import type { CatchTarget, InjectableTarget, MinimalNestJsExecutionContext } from './types'; import { patchCatchTarget, patchInjectableTarget } from './wrap-components'; import { extractQueueName, - MECHANISM_CRON, MECHANISM_INTERVAL, MECHANISM_TIMEOUT, patchMethodDescriptor, patchProcessorTarget, + sendsCronCheckIns, + wrapCronHandler, wrapEventHandler, wrapScheduleHandler, } from './wrap-handlers'; @@ -185,13 +187,43 @@ export function subscribeToNestChannels(): void { // @Cron/@Interval/@Timeout (schedule), @OnEvent (event), @Processor (bullmq) subscribeFactoryDecorator(CHANNELS.NESTJS_SCHEDULE_CRON, (decorator, data) => { - const cronDecorator = makeMethodDecorator(decorator, handler => wrapScheduleHandler(handler, MECHANISM_CRON)); + const cronDecorator = makeMethodDecorator(decorator, handler => wrapCronHandler(handler)); return function (this: unknown, ...args: unknown[]): unknown { + const descriptor = args[2] as PropertyDescriptor | undefined; + const handler: unknown = descriptor?.value; const result = cronDecorator.apply(this, args); - setNestScheduleVersion((args[2] as PropertyDescriptor | undefined)?.value, data.moduleVersion); + const method: unknown = descriptor?.value; + setNestScheduleVersion(method, data.moduleVersion); + if (typeof method === 'function' && typeof handler === 'function' && method !== handler) { + registerCronMonitor( + method, + handler, + [...(data.arguments || [])], + args[0], + args[1] as string | symbol | undefined, + data.moduleVersion, + ); + } return result; }; }); + // `@nestjs/schedule` passes this function to `CronJob` as `onTick`. When the method already sends + // check-ins, mark it the way other monitored jobs are marked, so scheduler integrations skip it. + diagnosticsChannel.tracingChannel(CHANNELS.NESTJS_SCHEDULE_EXPLORER_WRAP).subscribe({ + start: NOOP, + end(data) { + try { + if (typeof data.result === 'function' && sendsCronCheckIns(data.arguments?.[0])) { + addNonEnumerableProperty(data.result, Symbol.for('sentry.cron.monitored'), true); + } + } catch (e) { + DEBUG_BUILD && debug.error('[Nest] Failed to check whether a @Cron() job sends check-ins:', e); + } + }, + asyncStart: NOOP, + asyncEnd: NOOP, + error: NOOP, + }); subscribeFactoryDecorator(CHANNELS.NESTJS_SCHEDULE_INTERVAL, decorator => makeMethodDecorator(decorator, handler => wrapScheduleHandler(handler, MECHANISM_INTERVAL)), ); diff --git a/packages/nestjs/src/integrations/wrap-handlers.ts b/packages/nestjs/src/integrations/wrap-handlers.ts index 62f677635eeb..a9fcb58289d5 100644 --- a/packages/nestjs/src/integrations/wrap-handlers.ts +++ b/packages/nestjs/src/integrations/wrap-handlers.ts @@ -1,6 +1,21 @@ -import { captureException, isObjectLike, isThenable, startSpan, withIsolationScope } from '@sentry/core'; +import { + captureException, + isObjectLike, + isThenable, + startNewTrace, + startSpan, + withIsolationScope, + withMonitor, +} from '@sentry/core'; +import { resolveCronMonitor } from './cron-monitoring'; import type { AnyFn, ReflectWithMetadata } from './helpers'; -import { getBullMQProcessSpanOptions, getEventSpanOptions, isWrapped, markWrapped } from './helpers'; +import { + getBullMQProcessSpanOptions, + getEventSpanOptions, + isSentryCronMethod, + isWrapped, + markWrapped, +} from './helpers'; /** * Span-emitting / error-capturing logic for the `@Cron`/`@Interval`/ @@ -56,6 +71,26 @@ export function wrapScheduleHandler(handler: AnyFn, mechanismType: string): AnyF }; } +/** Whether a `@Cron()` method sends check-ins when it runs, from `monitorCronJobs` or `@SentryCron()`. */ +export function sendsCronCheckIns(method: unknown): boolean { + return typeof method === 'function' && (isSentryCronMethod(method) || !!resolveCronMonitor(method)); +} + +/** + * Wrap a `@Cron` handler like {@link wrapScheduleHandler}, sending check-ins when `monitorCronJobs` is enabled. + */ +export function wrapCronHandler(handler: AnyFn): AnyFn { + const scheduleHandler = wrapScheduleHandler(handler, MECHANISM_CRON); + const wrapped = function (this: unknown, ...args: unknown[]): unknown { + const monitor = resolveCronMonitor(wrapped); + if (!monitor) { + return scheduleHandler.apply(this, args); + } + return startNewTrace(() => withMonitor(monitor.slug, () => scheduleHandler.apply(this, args), monitor.config)); + }; + return wrapped; +} + function eventNameFromEvent(event: unknown): string { if (typeof event === 'string') { return event; @@ -140,11 +175,11 @@ export function patchMethodDescriptor( target: { __SENTRY_INTERNAL__?: boolean } | undefined, propertyKey: string | symbol | undefined, descriptor: PropertyDescriptor | undefined, - wrapHandler: (handler: AnyFn) => AnyFn, + wrapHandler: (handler: AnyFn, target: unknown, propertyKey: string | symbol | undefined) => AnyFn, ): void { const handler = descriptor?.value as AnyFn | undefined; if (descriptor && handler && typeof handler === 'function' && !target?.__SENTRY_INTERNAL__ && !isWrapped(handler)) { - const wrapped = wrapHandler(handler); + const wrapped = wrapHandler(handler, target, propertyKey); Object.defineProperty(wrapped, 'name', { value: handler.name || String(propertyKey), configurable: true, diff --git a/packages/nestjs/test/integrations/cron-monitoring.test.ts b/packages/nestjs/test/integrations/cron-monitoring.test.ts new file mode 100644 index 000000000000..56e2559e0a3a --- /dev/null +++ b/packages/nestjs/test/integrations/cron-monitoring.test.ts @@ -0,0 +1,278 @@ +import 'reflect-metadata'; +import { tracingChannel } from 'node:diagnostics_channel'; +import * as SentryCore from '@sentry/core'; +import { Client, createTransport, initAndBind, resolvedSyncPromise } from '@sentry/core'; +import type { OrchestrionChannelContext } from '@sentry/server-utils'; +import { nestjsChannels as CHANNELS } from '@sentry/server-utils/orchestrion/config'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { SentryCron } from '../../src/decorators'; +import type { NestCronMonitoringOptions } from '../../src/integrations/cron-monitoring'; +import { setCronMonitoringOptions } from '../../src/integrations/cron-monitoring'; +import { subscribeToNestChannels } from '../../src/integrations/orchestrion-subscriber'; + +type AnyFn = (this: unknown, ...args: unknown[]) => unknown; + +class TestClient extends Client { + public eventFromException(): PromiseLike { + return resolvedSyncPromise({}); + } + + public eventFromMessage(): PromiseLike { + return resolvedSyncPromise({}); + } +} + +let captureCheckIn: ReturnType string>>; + +function initClient(options: NestCronMonitoringOptions): void { + //@ts-expect-error - just a mock for the test + initAndBind(TestClient, { + dsn: 'https://username@domain/123', + integrations: [], + stackParser: () => [], + transport: () => createTransport({ recordDroppedEvent: () => undefined }, () => resolvedSyncPromise({})), + }); + const client = SentryCore.getClient() as Client & { captureCheckIn?: unknown }; + captureCheckIn = vi.fn((_checkIn: SentryCore.CheckIn, _config?: unknown) => 'check-in-id'); + client.captureCheckIn = captureCheckIn; + setCronMonitoringOptions(client, options); +} + +// Drives the `@Cron()` factory channel and returns the decorator `@Cron()` would apply. +function cron(cronTime: unknown, options?: Record, moduleVersion?: string): MethodDecorator { + const data: OrchestrionChannelContext = { arguments: [cronTime, options], moduleVersion }; + tracingChannel(CHANNELS.NESTJS_SCHEDULE_CRON).traceSync( + () => (_target: unknown, _key: unknown, descriptor: PropertyDescriptor) => { + Reflect.defineMetadata('SCHEDULE_CRON_OPTIONS', { ...options, cronTime }, descriptor.value); + return descriptor; + }, + data, + ); + return data.result as MethodDecorator; +} + +// A decorator that replaces the method and copies its metadata, as Nest needs to still schedule it. +const wrapMethod: MethodDecorator = (_target, _key, descriptor: PropertyDescriptor) => { + const original = descriptor.value as AnyFn; + descriptor.value = function (this: unknown, ...args: unknown[]): unknown { + return original.apply(this, args); + }; + for (const key of Reflect.getMetadataKeys(original)) { + Reflect.defineMetadata(key, Reflect.getMetadata(key, original), descriptor.value); + } +}; + +function decorate(className: string, decorators: MethodDecorator[]): AnyFn { + const target = Object.create({ constructor: { name: className } }) as object; + const descriptor: PropertyDescriptor = { value: function run(): void {}, configurable: true }; + for (const decorator of [...decorators].reverse()) { + decorator(target, 'run', descriptor); + } + return descriptor.value as AnyFn; +} + +// Drives the channel for the function `@nestjs/schedule` passes to `CronJob` as `onTick`, and returns it. +function cronJobOnTick(method: AnyFn): AnyFn { + const data: { arguments: unknown[]; result?: unknown } = { arguments: [method, {}] }; + tracingChannel<{ arguments: unknown[]; result?: unknown }>(CHANNELS.NESTJS_SCHEDULE_EXPLORER_WRAP).traceSync( + () => async () => {}, + data, + ); + return data.result as AnyFn; +} + +const isMarkedMonitored = (onTick: AnyFn): boolean => + !!(onTick as AnyFn & Record)[Symbol.for('sentry.cron.monitored')]; + +function sentCheckIns(): Array<[string, string, unknown]> { + return captureCheckIn.mock.calls.map(([checkIn, config]) => [checkIn.monitorSlug, checkIn.status, config]); +} + +describe('@Cron() monitoring', () => { + beforeEach(() => { + subscribeToNestChannels(); + }); + + afterEach(() => { + SentryCore.getMainCarrier().__SENTRY__ = undefined; + vi.restoreAllMocks(); + }); + + it('sends check-ins with the schedule, using the @Cron() name as slug', () => { + initClient({ monitorCronJobs: true }); + decorate('Jobs', [cron('0 30 9 * * 1-5', { name: 'daily-report', timeZone: 'Europe/Vienna' })])(); + + expect(sentCheckIns()).toEqual([ + [ + 'daily-report', + 'in_progress', + { schedule: { type: 'crontab', value: '30 9 * * 1-5' }, timezone: 'Europe/Vienna' }, + ], + ['daily-report', 'ok', undefined], + ]); + }); + + it('uses ClassName.methodName without a name', () => { + initClient({ monitorCronJobs: true }); + decorate('Jobs', [cron('* * * * *', { timeZone: 'UTC' })])(); + + expect(sentCheckIns()[0]?.[0]).toBe('Jobs.run'); + }); + + it('sends error check-ins and rethrows', async () => { + initClient({ monitorCronJobs: true }); + vi.spyOn(SentryCore, 'captureException').mockReturnValue('event-id'); + const target = Object.create({ constructor: { name: 'Jobs' } }) as object; + const descriptor: PropertyDescriptor = { + value: async function run(): Promise { + throw new Error('boom'); + }, + }; + cron('* * * * *', { timeZone: 'UTC' })(target, 'run', descriptor); + + await expect((descriptor.value as AnyFn)()).rejects.toThrow('boom'); + expect(sentCheckIns().map(([, status]) => status)).toEqual(['in_progress', 'error']); + }); + + it('starts a new trace for each run', () => { + initClient({ monitorCronJobs: true }); + const traceIds: string[] = []; + const run = decorate('Jobs', [ + cron('* * * * *', { name: 'job', timeZone: 'UTC' }), + (_target, _key, descriptor: PropertyDescriptor) => { + descriptor.value = () => { + traceIds.push(SentryCore.getCurrentScope().getPropagationContext().traceId); + }; + }, + ]); + const bootstrapTraceId = SentryCore.getCurrentScope().getPropagationContext().traceId; + + run(); + run(); + + expect(traceIds).toHaveLength(2); + expect(new Set([bootstrapTraceId, ...traceIds]).size).toBe(3); + }); + + it('skips jobs whose schedule cannot be sent', () => { + initClient({ monitorCronJobs: true }); + decorate('Jobs', [cron('*/10 * * * * *', { name: 'every-10s' })])(); + + expect(captureCheckIn).not.toHaveBeenCalled(); + }); + + it('skips a numeric month on @nestjs/schedule 3', () => { + initClient({ monitorCronJobs: true }); + decorate('Jobs', [cron('0 0 1 1 *', { name: 'yearly', timeZone: 'UTC' }, '3.0.4')])(); + + expect(captureCheckIn).not.toHaveBeenCalled(); + }); + + it('sends a numeric month from @nestjs/schedule 4', () => { + initClient({ monitorCronJobs: true }); + decorate('Jobs', [cron('0 0 1 1 *', { name: 'yearly', timeZone: 'UTC' }, '4.1.2')])(); + + expect(sentCheckIns()[0]).toEqual([ + 'yearly', + 'in_progress', + { schedule: { type: 'crontab', value: '0 0 1 1 *' }, timezone: 'UTC' }, + ]); + }); + + it('does not monitor by default', () => { + initClient({}); + decorate('Jobs', [cron('* * * * *', { name: 'job' })])(); + + expect(captureCheckIn).not.toHaveBeenCalled(); + }); + + it('skips excluded jobs', () => { + initClient({ monitorCronJobs: true, excludeCronJobs: ['job', /^Other\./] }); + decorate('Jobs', [cron('* * * * *', { name: 'job' })])(); + decorate('Other', [cron('* * * * *')])(); + decorate('Jobs', [cron('* * * * *', { name: 'job-2' })])(); + + expect(sentCheckIns().map(([slug]) => slug)).toEqual(['job-2', 'job-2']); + }); + + it('skips one-off jobs', () => { + initClient({ monitorCronJobs: true }); + decorate('Jobs', [cron(new Date(), { name: 'once' })])(); + + expect(captureCheckIn).not.toHaveBeenCalled(); + }); + + it.each([ + ['@Cron() above @SentryCron()', (c: MethodDecorator, s: MethodDecorator) => [c, s]], + ['@SentryCron() above @Cron()', (c: MethodDecorator, s: MethodDecorator) => [s, c]], + ['@Cron(), another decorator, @SentryCron()', (c: MethodDecorator, s: MethodDecorator) => [c, wrapMethod, s]], + ['@SentryCron(), another decorator, @Cron()', (c: MethodDecorator, s: MethodDecorator) => [s, wrapMethod, c]], + ['another decorator, @Cron(), @SentryCron()', (c: MethodDecorator, s: MethodDecorator) => [wrapMethod, c, s]], + ['another decorator, @SentryCron(), @Cron()', (c: MethodDecorator, s: MethodDecorator) => [wrapMethod, s, c]], + ])('only sends the @SentryCron() check-ins with %s', async (_, order) => { + initClient({ monitorCronJobs: true }); + await decorate('Jobs', order(cron('* * * * *', { name: 'job' }), SentryCron('explicit-slug')))(); + + expect(sentCheckIns().map(([slug]) => slug)).toEqual(['explicit-slug', 'explicit-slug']); + }); + + describe('marks the CronJob onTick as monitored', () => { + it('when the method is monitored', () => { + initClient({ monitorCronJobs: true }); + const onTick = cronJobOnTick(decorate('Jobs', [cron('* * * * *', { name: 'job' })])); + + expect(isMarkedMonitored(onTick)).toBe(true); + expect(Object.keys(onTick)).toEqual([]); + }); + + it('when a decorator above @Cron() replaces the method', () => { + initClient({ monitorCronJobs: true }); + const onTick = cronJobOnTick(decorate('Jobs', [wrapMethod, cron('* * * * *', { name: 'job' })])); + + expect(isMarkedMonitored(onTick)).toBe(true); + }); + + it.each([ + ['@SentryCron(), another decorator, @Cron()', (c: MethodDecorator, s: MethodDecorator) => [s, wrapMethod, c]], + ['another decorator, @SentryCron(), @Cron()', (c: MethodDecorator, s: MethodDecorator) => [wrapMethod, s, c]], + ['another decorator, @Cron(), @SentryCron()', (c: MethodDecorator, s: MethodDecorator) => [wrapMethod, c, s]], + ])('when the method has %s', (_, order) => { + initClient({}); + const onTick = cronJobOnTick( + decorate('Jobs', order(cron('* * * * *', { name: 'job' }), SentryCron('explicit-slug'))), + ); + + expect(isMarkedMonitored(onTick)).toBe(true); + }); + + it('not when a decorator above @Cron() replaces an excluded method', () => { + initClient({ monitorCronJobs: true, excludeCronJobs: ['job'] }); + const onTick = cronJobOnTick(decorate('Jobs', [wrapMethod, cron('* * * * *', { name: 'job' })])); + + expect(isMarkedMonitored(onTick)).toBe(false); + }); + + it('when the method has @SentryCron()', () => { + initClient({}); + const onTick = cronJobOnTick(decorate('Jobs', [cron('* * * * *', { name: 'job' }), SentryCron('explicit-slug')])); + + expect(isMarkedMonitored(onTick)).toBe(true); + }); + + it.each([ + ['monitorCronJobs is off', {}], + ['the job is excluded', { monitorCronJobs: true, excludeCronJobs: ['job'] }], + ])('not when %s', (_, options) => { + initClient(options); + const onTick = cronJobOnTick(decorate('Jobs', [cron('* * * * *', { name: 'job' })])); + + expect(isMarkedMonitored(onTick)).toBe(false); + }); + + it('not for other scheduled methods', () => { + initClient({ monitorCronJobs: true }); + + expect(isMarkedMonitored(cronJobOnTick(function interval(): void {}))).toBe(false); + }); + }); +}); diff --git a/packages/server-utils/src/orchestrion/config/nestjs.ts b/packages/server-utils/src/orchestrion/config/nestjs.ts index 344b35913ede..b08971e01726 100644 --- a/packages/server-utils/src/orchestrion/config/nestjs.ts +++ b/packages/server-utils/src/orchestrion/config/nestjs.ts @@ -93,6 +93,13 @@ export const nestjsConfig = [ module: { name: '@nestjs/schedule', versionRange: '>=2.0.0', filePath: 'dist/decorators/timeout.decorator.js' }, functionQuery: { functionName: 'Timeout', kind: 'Sync' }, }, + { + // The function `ScheduleExplorer` returns here is what `@nestjs/schedule` passes to `CronJob` as `onTick`. + // From 6.0.0 it uses `cron` 4.3+, whose jobs have names. + channelName: 'scheduleExplorerWrap', + module: { name: '@nestjs/schedule', versionRange: '>=6.0.0', filePath: 'dist/schedule.explorer.js' }, + functionQuery: { className: 'ScheduleExplorer', methodName: 'wrapFunctionInTryCatchBlocks', kind: 'Sync' }, + }, { // @nestjs/event-emitter @OnEvent: // `const OnEvent = (event, options) => { @@ -138,6 +145,7 @@ export const nestjsChannels = { NESTJS_SCHEDULE_CRON: 'orchestrion:@nestjs/schedule:cronDecorator', NESTJS_SCHEDULE_INTERVAL: 'orchestrion:@nestjs/schedule:intervalDecorator', NESTJS_SCHEDULE_TIMEOUT: 'orchestrion:@nestjs/schedule:timeoutDecorator', + NESTJS_SCHEDULE_EXPLORER_WRAP: 'orchestrion:@nestjs/schedule:scheduleExplorerWrap', NESTJS_ONEVENT: 'orchestrion:@nestjs/event-emitter:onEventDecorator', NESTJS_PROCESSOR: 'orchestrion:@nestjs/bullmq:processorDecorator', } as const; From 501847338cb005de0692d4813c5f9996b222e410 Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Mon, 2 Nov 2026 10:56:02 -0800 Subject: [PATCH 085/169] docs(changelog): Add monitorCronJobs to Unreleased (#25996) I missed the request for a changelog entry in #25471, adding it here --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81399b6e6386..c58c6b24ea5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ ### Important Changes +- **feat(nestjs): Add an option to monitor every @Cron method ([#25471](https://github.com/getsentry/sentry-javascript/pull/25471))** + + Set `monitorCronJobs: true` on `nestIntegration()` to send check-ins for every `@Cron()` method, without adding `@SentryCron` to each one. It's off by default. Each monitored job creates a monitor in Sentry, and monitors are billed. Use `excludeCronJobs` to skip jobs by slug. Methods that also have `@SentryCron` check in once, with the slug and options passed to `@SentryCron`. + - **feat(nestjs): Derive SentryCron monitor config from @Cron ([#25013](https://github.com/getsentry/sentry-javascript/pull/25013))** `@SentryCron` now sends the schedule and time zone from the method's `@Cron()` decorator, so Sentry creates the monitor on the first run. If you use `@SentryCron` without a `schedule`, Sentry now creates missing monitors, which are billed, and overwrites schedules and time zones set by hand. Pass `fromCronDecorator: false` to turn this off. From bbb59b4d60a2145489abd19e74259f6c404a3116 Mon Sep 17 00:00:00 2001 From: Abdulrahman Mohammed <62155637+61465@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:23:53 +0200 Subject: [PATCH 086/169] test(nuxt): cover the client plugin's pages-router instrumentation (#25348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes #13813. Fills in the Nuxt client-plugin unit tests requested by @mydea. Picks up where the dormant draft #24583 (@vansh-nagar, inactive since 2026-02; pinged by @JPeer264 on 2026-10-12 with no reply within the stated window) left off and extends it to all five code paths in `packages/nuxt/src/runtime/plugins/sentry.client.ts`. ## What this covers 1. **Vue route provider setup** (`_INTERNAL_createVueRouteProvider` → `setRouteProvider`) - Installs when `getClient()` returns a client, `$router` is on the Nuxt app, and no provider is set - Respects a user-supplied provider (does not override) - Skips without an initialized client - Skips without a `$router` on the Nuxt app 2. **`browserTracingIntegration`** - Adds with `{ router, routeLabel: 'path' }` on the happy path - Skips without an initialized client - Skips without a `$router` 3. **`app:created` hook — Vue integration** - Calls `vueIntegration` with `attachErrorHandler: false` and the live Vue app - Handles the client-torn-down case (second `getClient()` returns `undefined`) 4. **`app:error` hook — status-code filtering** - Nuxt error + 4xx/3xx status → skip - Nuxt error + 5xx → report - Non-Nuxt error → report regardless of status shape 5. **`vue:error` hook** - Forwards `{ error, instance, info }` to `reportNuxtError` ## Pattern Mocks mirror `packages/nuxt/test/runtime/hooks/captureErrorHook.test.ts`: - `vi.hoisted(() => vi.fn())` for cross-module mock refs - `vi.mock(import('…'), async importOriginal => …)` for `@sentry/core`, `@sentry/vue`, `nuxt/app` - `vi.spyOn(utils, 'reportNuxtError')` for the local `../utils` module Pure vitest, no native dependencies. Runs under the existing `yarn test` command in `packages/nuxt/`. ## Local verification Syntax parse via `tsc --noEmit --skipLibCheck` on the test file passes (only "Cannot find module" warnings remain for workspace packages, which CI resolves via `yarn install`). I was unable to materialise the Yarn/npm workspace node_modules locally on my Windows + Node 24 shell (ETARGET on `whatwg-url@^17.2.0`, same error reported upstream in jsdom's current chain), so I'm relying on your CI matrix to confirm the suite is green across the vitest + jsdom environment configured in `packages/nuxt/vite.config.ts`. ## Notes - All five code paths were taken straight from the current `src/runtime/plugins/sentry.client.ts`; happy to re-shape the test file structure if the preferred house style here is one `describe` per public behaviour rather than per source-code region. - @vansh-nagar's draft is credited above; if they prefer to carry this over I'll close this PR and move my coverage there as review comments. cc @mydea + @JPeer264 — thanks for the pre-blessing on #13813 and for keeping the issue open. 🤖 Used Claude Code for the test scaffolding and to cross-check the mock shapes against existing nuxt tests; every assertion was signed off by me on content. --------- Signed-off-by: Abd Alrahaman Co-authored-by: Nicolas Hrubec Co-authored-by: OpenAI GPT-6 --- .../nuxt/test/runtime/plugins/client.test.ts | 293 ++++++++++++++++-- 1 file changed, 259 insertions(+), 34 deletions(-) diff --git a/packages/nuxt/test/runtime/plugins/client.test.ts b/packages/nuxt/test/runtime/plugins/client.test.ts index 0363ea9c1b94..16890e7be620 100644 --- a/packages/nuxt/test/runtime/plugins/client.test.ts +++ b/packages/nuxt/test/runtime/plugins/client.test.ts @@ -1,58 +1,283 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import plugin from '../../../src/runtime/plugins/sentry.client'; -import { reportNuxtError } from '../../../src/runtime/utils'; +import type { Client } from '@sentry/core'; +import type { isNuxtError as nuxtIsNuxtError, NuxtApp } from 'nuxt/app'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import clientPlugin from '../../../src/runtime/plugins/sentry.client'; +import * as utils from '../../../src/runtime/utils'; -vi.mock('nuxt/app', () => ({ - defineNuxtPlugin: (plugin: unknown) => plugin, - isNuxtError: (error: unknown) => !!error && typeof error === 'object' && '__nuxt_error' in error, +const getClient = vi.hoisted(() => vi.fn<() => Client | undefined>()); +const browserTracingIntegration = vi.hoisted(() => vi.fn()); +const vueIntegration = vi.hoisted(() => vi.fn()); +const createVueRouteProvider = vi.hoisted(() => vi.fn()); +const getRouteProvider = vi.hoisted(() => vi.fn()); +const setRouteProvider = vi.hoisted(() => vi.fn()); + +const nuxtVueOptions = vi.hoisted(() => ({ + trackComponents: ['NuxtApp'], + attachProps: false, +})); +const globalObj = vi.hoisted(() => ({ + _sentryNuxtVueIntegrationOptions: nuxtVueOptions, })); -vi.mock('../../../src/runtime/utils', () => ({ - reportNuxtError: vi.fn(), +vi.mock(import('@sentry/core'), async importOriginal => { + const mod = await importOriginal(); + return { + ...mod, + getClient: getClient as typeof mod.getClient, + GLOBAL_OBJ: globalObj as never, + }; +}); + +vi.mock(import('@sentry/vue'), async importOriginal => { + const mod = await importOriginal(); + return { + ...mod, + browserTracingIntegration, + vueIntegration, + _INTERNAL_createVueRouteProvider: createVueRouteProvider, + getRouteProvider, + setRouteProvider, + }; +}); + +vi.mock(import('nuxt/app'), () => ({ + defineNuxtPlugin: vi.fn(plugin => plugin), + isNuxtError: vi.fn(() => false) as unknown as typeof nuxtIsNuxtError, })); -type Hook = (...args: unknown[]) => void; +type PluginSetup = NonNullable; -async function setupHook(hookName: string): Promise { - const hooks: Record = {}; - const nuxtApp = { hook: (name: string, hook: Hook) => (hooks[name] = hook) }; - await (plugin as unknown as { setup: (app: unknown) => Promise }).setup(nuxtApp); - expect(hooks).toHaveProperty(hookName); - return hooks[hookName]!; +type NuxtPluginShape = { setup: PluginSetup }; + +type MockNuxtApp = NuxtApp & { hook: ReturnType }; + +function makeClient(overrides: Partial = {}): Client { + return { + addIntegration: vi.fn(), + getOptions: vi.fn(() => ({ attachProps: true })), + ...overrides, + } as unknown as Client; } -function nuxtError(statusCode: number): Error { - return Object.assign(new Error(`status ${statusCode}`), { __nuxt_error: true, statusCode }); +function makeNuxtApp(router?: unknown): MockNuxtApp { + return (router === undefined ? { hook: vi.fn() } : { $router: router, hook: vi.fn() }) as unknown as MockNuxtApp; } -describe('sentry client plugin error hooks', () => { - beforeEach(() => { - vi.clearAllMocks(); +describe('Sentry Nuxt client plugin', () => { + afterEach(() => { + vi.resetAllMocks(); + }); + + describe('route provider setup', () => { + it('installs a Vue route provider when a Nuxt router is present and none is set', async () => { + const router = { beforeEach: vi.fn(), onError: vi.fn() }; + const client = makeClient(); + const provider = { resolveRoute: vi.fn(), resolveCurrentRoute: vi.fn() }; + getClient.mockReturnValue(client); + getRouteProvider.mockReturnValue(undefined); + createVueRouteProvider.mockReturnValue(provider); + + await (clientPlugin as NuxtPluginShape).setup(makeNuxtApp(router)); + + expect(createVueRouteProvider).toHaveBeenCalledWith(expect.any(Function)); + expect(setRouteProvider).toHaveBeenCalledWith(provider, client); + }); + + it('does not override a user-supplied route provider', async () => { + const client = makeClient(); + getClient.mockReturnValue(client); + getRouteProvider.mockReturnValue({ resolveRoute: () => '/u', resolveCurrentRoute: () => '/u' }); + + await (clientPlugin as NuxtPluginShape).setup(makeNuxtApp({})); + + expect(createVueRouteProvider).not.toHaveBeenCalled(); + expect(setRouteProvider).not.toHaveBeenCalled(); + }); + + it('skips route provider setup without an initialized client', async () => { + getClient.mockReturnValue(undefined); + + await (clientPlugin as NuxtPluginShape).setup(makeNuxtApp({})); + + expect(createVueRouteProvider).not.toHaveBeenCalled(); + expect(setRouteProvider).not.toHaveBeenCalled(); + }); + + it('skips route provider setup without a $router on the Nuxt app', async () => { + const client = makeClient(); + getClient.mockReturnValue(client); + getRouteProvider.mockReturnValue(undefined); + + await (clientPlugin as NuxtPluginShape).setup(makeNuxtApp()); + + expect(createVueRouteProvider).not.toHaveBeenCalled(); + expect(setRouteProvider).not.toHaveBeenCalled(); + }); }); - it.each(['app:error', 'vue:error'])('does not report 3xx/4xx Nuxt errors from %s', async hookName => { - const hook = await setupHook(hookName); + describe('browser tracing integration', () => { + it('adds browserTracingIntegration with the Nuxt router and path-based labels', async () => { + const router = { beforeEach: vi.fn(), onError: vi.fn() }; + const integration = { name: 'BrowserTracing' }; + const client = makeClient(); + getClient.mockReturnValue(client); + getRouteProvider.mockReturnValue(undefined); + browserTracingIntegration.mockReturnValue(integration); - hook(nuxtError(404)); - hook(nuxtError(301)); + await (clientPlugin as NuxtPluginShape).setup(makeNuxtApp(router)); - expect(reportNuxtError).not.toHaveBeenCalled(); + expect(browserTracingIntegration).toHaveBeenCalledWith({ router, routeLabel: 'path' }); + expect(client.addIntegration).toHaveBeenCalledWith(integration); + }); + + it('does not add browserTracingIntegration without an initialized client', async () => { + getClient.mockReturnValue(undefined); + + await (clientPlugin as NuxtPluginShape).setup(makeNuxtApp({ beforeEach: vi.fn() })); + + expect(browserTracingIntegration).not.toHaveBeenCalled(); + }); + + it('does not add browserTracingIntegration when the Nuxt app has no router', async () => { + const client = makeClient(); + getClient.mockReturnValue(client); + getRouteProvider.mockReturnValue(undefined); + + await (clientPlugin as NuxtPluginShape).setup(makeNuxtApp()); + + expect(browserTracingIntegration).not.toHaveBeenCalled(); + }); }); - it.each(['app:error', 'vue:error'])('reports 5xx Nuxt errors from %s', async hookName => { - const hook = await setupHook(hookName); + describe('app:created hook — Vue integration', () => { + it('adds the Nuxt-specific Vue integration with attachErrorHandler disabled and merges GLOBAL_OBJ-provided options', async () => { + const client = makeClient(); + const integration = { name: 'NuxtVueIntegration' }; + const vueApp = { Symbol: 'app' }; + getClient.mockReturnValue(client); + getRouteProvider.mockReturnValue(undefined); + vueIntegration.mockReturnValue(integration); + + const nuxtApp = makeNuxtApp(); + await (clientPlugin as NuxtPluginShape).setup(nuxtApp); + const appCreatedCall = nuxtApp.hook.mock.calls.find(c => c[0] === 'app:created'); + expect(appCreatedCall).toBeTruthy(); + const appCreatedCb = appCreatedCall![1] as (app: unknown) => void; + appCreatedCb(vueApp); + + // Nuxt-specific: _sentryNuxtVueIntegrationOptions from GLOBAL_OBJ must + // flow into vueIntegration alongside the fixed Nuxt options. + expect(vueIntegration).toHaveBeenCalledWith( + expect.objectContaining({ + app: vueApp, + attachErrorHandler: false, + trackComponents: nuxtVueOptions.trackComponents, + attachProps: nuxtVueOptions.attachProps, + }), + ); + expect(client.addIntegration).toHaveBeenCalledWith(integration); + }); + + it('does not add the Vue integration when the client is gone by the time the hook fires', async () => { + const client = makeClient(); + getClient.mockReturnValueOnce(client); + getRouteProvider.mockReturnValue(undefined); + + const nuxtApp = makeNuxtApp(); + await (clientPlugin as NuxtPluginShape).setup(nuxtApp); + + // Second getClient call (inside the hook) returns undefined — simulates teardown. + getClient.mockReturnValueOnce(undefined); + const appCreatedCb = nuxtApp.hook.mock.calls.find(c => c[0] === 'app:created')?.[1] as (app: unknown) => void; + appCreatedCb({}); + + expect(vueIntegration).not.toHaveBeenCalled(); + }); + }); + + describe.each(['app:error', 'vue:error'] as const)('%s hook — status-code filtering', hookName => { + const fireHook = (nuxtApp: { hook: ReturnType }, error: unknown) => { + const cb = nuxtApp.hook.mock.calls.find(c => c[0] === hookName)?.[1] as ( + e: unknown, + instance?: unknown, + info?: string, + ) => void; + // vue:error carries (error, instance, info); app:error only (error). The + // plugin passes the extra args through for vue:error, so include them + // uniformly here and rely on expect.objectContaining({ error }) below. + cb(error, undefined, 'setup'); + }; + + // The plugin's skip guard reads `error.status || error.statusCode`, so both + // field names in the 3xx/4xx range must be covered. + it.each([{ status: 404 }, { status: 301 }, { statusCode: 404 }, { statusCode: 301 }])( + 'skips reporting when a Nuxt error carries %o', + async errorShape => { + const { isNuxtError } = await import('nuxt/app'); + const reportSpy = vi.spyOn(utils, 'reportNuxtError').mockImplementation(() => {}); + vi.mocked(isNuxtError).mockReturnValue(true); + getClient.mockReturnValue(makeClient()); + getRouteProvider.mockReturnValue(undefined); + + const nuxtApp = makeNuxtApp(); + await (clientPlugin as NuxtPluginShape).setup(nuxtApp); + fireHook(nuxtApp, errorShape); + + expect(reportSpy).not.toHaveBeenCalled(); + }, + ); + + it('reports a Nuxt error when the status code is 5xx', async () => { + const { isNuxtError } = await import('nuxt/app'); + const reportSpy = vi.spyOn(utils, 'reportNuxtError').mockImplementation(() => {}); + vi.mocked(isNuxtError).mockReturnValue(true); + getClient.mockReturnValue(makeClient()); + getRouteProvider.mockReturnValue(undefined); + + const nuxtApp = makeNuxtApp(); + await (clientPlugin as NuxtPluginShape).setup(nuxtApp); + const err = { statusCode: 503, message: 'bad gateway' }; + fireHook(nuxtApp, err); + + expect(reportSpy).toHaveBeenCalledWith(expect.objectContaining({ error: err })); + }); + + // Non-Nuxt error carrying a 4xx statusCode exercises the `isNuxtError` + // guard directly — without the guard this would be skipped as 4xx. + it('reports a non-Nuxt error even when it carries a 4xx statusCode', async () => { + const { isNuxtError } = await import('nuxt/app'); + const reportSpy = vi.spyOn(utils, 'reportNuxtError').mockImplementation(() => {}); + vi.mocked(isNuxtError).mockReturnValue(false); + getClient.mockReturnValue(makeClient()); + getRouteProvider.mockReturnValue(undefined); - hook(nuxtError(503)); + const nuxtApp = makeNuxtApp(); + await (clientPlugin as NuxtPluginShape).setup(nuxtApp); + const err = Object.assign(new Error('boom'), { statusCode: 404 }); + fireHook(nuxtApp, err); - expect(reportNuxtError).toHaveBeenCalledTimes(1); + expect(reportSpy).toHaveBeenCalledWith(expect.objectContaining({ error: err })); + }); }); - it('reports errors that are not Nuxt errors from vue:error', async () => { - const hook = await setupHook('vue:error'); - const error = Object.assign(new Error('boom'), { statusCode: 404 }); + describe('vue:error hook — component instance passthrough', () => { + it('forwards the error, component instance and info to reportNuxtError', async () => { + const reportSpy = vi.spyOn(utils, 'reportNuxtError').mockImplementation(() => {}); + getClient.mockReturnValue(makeClient()); + getRouteProvider.mockReturnValue(undefined); - hook(error, undefined, 'setup function'); + const nuxtApp = makeNuxtApp(); + await (clientPlugin as NuxtPluginShape).setup(nuxtApp); + const vueErrorCb = nuxtApp.hook.mock.calls.find(c => c[0] === 'vue:error')?.[1] as ( + e: unknown, + instance: unknown, + info: string, + ) => void; + const err = new Error('vue boom'); + const instance = { $props: { foo: 1 } }; + vueErrorCb(err, instance, 'render'); - expect(reportNuxtError).toHaveBeenCalledWith({ error, instance: undefined, info: 'setup function' }); + expect(reportSpy).toHaveBeenCalledWith({ error: err, instance, info: 'render' }); + }); }); }); From 36e3e9e2f3321405c1df5706302abca39de0485a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 2 Nov 2026 21:37:38 +0100 Subject: [PATCH 087/169] chore: Refresh Smithy deprecation metadata in lockfile (#25971) **Why:** Fresh installs add upstream deprecation metadata for two Smithy packages, causing the lockfile stability check to fail and blocking CI. Co-authored-by: OpenAI Codex --- pnpm-lock.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 462fb89b83ca..52d0d8082991 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8542,6 +8542,7 @@ packages: '@smithy/core@3.37.0': resolution: {integrity: sha512-97imTcCyiPd8k0e9+ts/Sp0W65Dn9Ti357J7M09Ov4tI8Q1JDJVFSoAJtjEVKqfHEr3Pdo2jfolqKCaaiszH0g==} engines: {node: '>=18.0.0'} + deprecated: Deprecated due to a bug in custom logger data redaction. See https://github.com/aws/aws-sdk-js-v3/issues/8393. '@smithy/credential-provider-imds@4.5.2': resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==} @@ -8630,6 +8631,7 @@ packages: '@smithy/smithy-client@4.12.13': resolution: {integrity: sha512-y/Pcj1V9+qG98gyu1gvftHB7rDpdh+7kIBIggs55yGm3JdtBV8GT8IFF3a1qxZ79QnaJHX9GXzvBG6tAd+czJA==} engines: {node: '>=18.0.0'} + deprecated: Deprecated due to a bug in custom logger data redaction. See https://github.com/aws/aws-sdk-js-v3/issues/8393. '@smithy/types@4.19.0': resolution: {integrity: sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==} From 4a6b7c3ea6b0ea2fdd7fa9261a1932b19c8bb990 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 2 Nov 2026 21:38:59 +0100 Subject: [PATCH 088/169] ci: Include v10 in base-branch metadata (#25977) **Why:** `v10` is missing from the base-branch metadata skipping some workflows. This needs backporting to take effect on the v10 branch: https://github.com/getsentry/sentry-javascript/pull/25978 Co-authored-by: OpenAI Codex --- .github/workflows/ci-metadata.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-metadata.yml b/.github/workflows/ci-metadata.yml index ed114f394a19..f93c93c296c3 100644 --- a/.github/workflows/ci-metadata.yml +++ b/.github/workflows/ci-metadata.yml @@ -11,7 +11,7 @@ on: description: 'Short SHA + commit message for display' value: ${{ jobs.get_metadata.outputs.commit_label }} is_base_branch: - description: 'Whether the ref is develop, v9, or v8' + description: 'Whether the ref is develop, v10, v9, or v8' value: ${{ jobs.get_metadata.outputs.is_base_branch }} is_release: description: 'Whether the ref is a release branch' @@ -81,7 +81,8 @@ jobs: commit_label: '${{ env.COMMIT_SHA }}: ${{ env.COMMIT_MESSAGE }}' # Note: These next three have to be checked as strings ('true'/'false')! is_base_branch: - ${{ github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/v9' || github.ref == 'refs/heads/v8'}} + ${{ github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/v10' || github.ref == 'refs/heads/v9' || + github.ref == 'refs/heads/v8'}} is_release: ${{ startsWith(github.ref, 'refs/heads/release/') }} changed_ci: ${{ steps.changed.outputs.workflow == 'true' }} changed_any_code: ${{ steps.changed.outputs.any_code == 'true' }} From bbb6c119316def8bb2b7f6c702fbb1d2f1cc96c7 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 2 Nov 2026 21:39:16 +0100 Subject: [PATCH 089/169] test(bundler-plugins): Stabilize missing-parser test setup (#25972) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** Load the annotation hooks in `beforeEach` so module loading doesn’t consume the test’s five-second timeout. Import the hooks directly to avoid loading unrelated bundler-plugin code, while resetting module state between tests so each can verify the warning is emitted once. Co-authored-by: OpenAI Codex --- .../component-annotation-parser-unavailable.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/bundler-plugins/test/core/component-annotation-parser-unavailable.test.ts b/packages/bundler-plugins/test/core/component-annotation-parser-unavailable.test.ts index fc60b926ddf9..9d845f28fc49 100644 --- a/packages/bundler-plugins/test/core/component-annotation-parser-unavailable.test.ts +++ b/packages/bundler-plugins/test/core/component-annotation-parser-unavailable.test.ts @@ -1,5 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type * as ComponentAnnotateHooks from '../../src/core/component-annotate-hooks'; import type * as ComponentAnnotation from '../../src/core/component-annotation-oxc'; vi.mock('../../src/core/component-annotation-oxc', async importOriginal => { @@ -12,8 +13,11 @@ vi.mock('../../src/core/component-annotation-oxc', async importOriginal => { const MESSAGE = 'Could not load `oxc-parser` for this platform. React components will not be annotated.'; describe('createComponentNameAnnotateHooks without a parser', () => { - beforeEach(() => { + let hooks: typeof ComponentAnnotateHooks; + + beforeEach(async () => { vi.resetModules(); + hooks = await import('../../src/core/component-annotate-hooks'); }); afterEach(() => { @@ -21,9 +25,8 @@ describe('createComponentNameAnnotateHooks without a parser', () => { }); it('leaves files unchanged and warns once through the logger', async () => { - const { createComponentNameAnnotateHooks } = await import('../../src/core'); const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }; - const { transform } = createComponentNameAnnotateHooks([], false, { logger }); + const { transform } = hooks.createComponentNameAnnotateHooks([], false, { logger }); await expect(transform('export const App = () =>
;', '/src/app.jsx')).resolves.toBeNull(); await expect(transform('export const Other = () => ;', '/src/other.jsx')).resolves.toBeNull(); @@ -33,11 +36,10 @@ describe('createComponentNameAnnotateHooks without a parser', () => { }); it('warns once through the console across hooks created without a logger', async () => { - const { createComponentNameAnnotateHooks } = await import('../../src/core'); const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); for (const id of ['/src/app.jsx', '/src/other.jsx']) { - const { transform } = createComponentNameAnnotateHooks([], false); + const { transform } = hooks.createComponentNameAnnotateHooks([], false); await expect(transform('export const App = () =>
;', id)).resolves.toBeNull(); } From e6ecc73a46019e0b648f18609c65105469998f84 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 2 Nov 2026 21:39:56 +0100 Subject: [PATCH 090/169] chore: Add Bugbot guidance for cold imports in tests (#25976) **Why:** Add Bugbot guidance for avoidable cold imports inside timed test callbacks, a recurring source of CI timeouts addressed in [#25593](https://github.com/getsentry/sentry-javascript/pull/25593), [#25811](https://github.com/getsentry/sentry-javascript/pull/25811), [#25972](https://github.com/getsentry/sentry-javascript/pull/25972), and [#25975](https://github.com/getsentry/sentry-javascript/pull/25975). --------- Co-authored-by: OpenAI Codex --- .cursor/BUGBOT.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.cursor/BUGBOT.md b/.cursor/BUGBOT.md index 4a9d0bd65ef4..afbd17c05ff9 100644 --- a/.cursor/BUGBOT.md +++ b/.cursor/BUGBOT.md @@ -103,6 +103,12 @@ Keep reviews high-signal. Prefer actionable, high-confidence findings over specu - Only waiting for a request, after an action is performed. Instead, start waiting, perform action, await request promise. - Race conditions when waiting on multiple requests. Ensure that waiting checks are unique enough and don't depend on a hard order when there's a chance that telemetry can be sent in arbitrary order. - Timeouts or sleeps in tests. Instead suggest concrete events or other signals to wait on. +- Flag cold module loading inside timed test callbacks or their helpers when + it can safely run during collection or setup. It can exhaust the timeout + before the behavior under test runs. Prefer static imports, narrower + modules, or setup hooks while preserving isolation and mock order. + Preserve imports whose evaluation timing is itself being tested. + Only flag concrete avoidable loading, not dynamic imports in general. - Flag usage of `getFirstEnvelope*`, `getMultipleEnvelope*` or related test helpers in E2E tests. These are NOT reliable anymore. Instead suggest helpers like `waitForTransaction`, `waitForError`, `waitForSpans`, etc. - Flag any new or modified `docker-compose.yml` under `dev-packages/node-integration-tests/suites/` where a service does not define a `healthcheck:`. The runner uses `docker compose up --wait` and relies on healthchecks to know when services are actually ready; without one the test will race the service's startup. - Flag new Cloudflare tests that wrap the worker by hand with `withSentry`, `instrumentDurableObjectWithSentry`, `instrumentAgentWithSentry` or `instrumentWorkflowWithSentry`. This covers new suites under `dev-packages/cloudflare-integration-tests/suites/`, new E2E apps under `dev-packages/e2e-tests/test-applications/` that run on Cloudflare, and new Cloudflare variants of existing E2E apps (an entry in `sentryTest.variants` of the app's `package.json`, for example `hono-4 (cloudflare)`). App names do not always start with `cloudflare-`. Recommend the Sentry Cloudflare Vite plugin instead: `plugins: [cloudflare(), sentryCloudflareVitePlugin()]` in `vite.config.*` (`sentryCloudflareVitePlugin` from `@sentry/cloudflare/vite`), and the SDK options in an `instrument.server.ts` next to the worker entry that default-exports `defineCloudflareOptions(...)`. Do not flag tests whose purpose is to test one of these wrappers, or apps that do not build with Vite (for example Next.js on OpenNext). From e6a7a4c1860865640f1f804199dbf358219e81d5 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Mon, 2 Nov 2026 21:54:03 +0100 Subject: [PATCH 091/169] test(browser): Stabilize long animation frame tests (#25974) **Why:** Mouse-down and click can share a long animation frame, which the SDK attributes to its first script instead of the click handler these tests expect. Waiting for a frame boundary before mouse-up isolates the click. --------- Co-authored-by: OpenAI Codex Co-authored-by: Abdelrahman Awad --- .../test.ts | 14 +++++++++++--- .../long-tasks-and-animation-frame-enabled/test.ts | 9 ++++++--- .../browser-integration-tests/utils/helpers.ts | 9 +++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/test.ts index 5881bf8b3ba0..72fce94d9815 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-static/test.ts @@ -10,7 +10,12 @@ import { import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { + envelopeRequestParser, + shouldSkipTracingTest, + waitForNextFrame, + waitForTransactionRequest, +} from '../../../../utils/helpers'; sentryTest( 'should capture long animation frame for top-level script.', @@ -83,8 +88,11 @@ sentryTest( await page.goto(url); - // trigger long animation frame function - await page.getByRole('button').click(); + // Separate mousedown and click into different frames so LoAF reports the click handler. + await page.getByRole('button').hover(); + await page.mouse.down(); + await waitForNextFrame(page); + await page.mouse.up(); const eventData = envelopeRequestParser(await promise); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts index 6a2638a61c45..93abdb679273 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts @@ -9,7 +9,7 @@ import { import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest, waitForNextFrame } from '../../../../utils/helpers'; import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( @@ -83,8 +83,11 @@ sentryTest('captures long animation frame span for event listener.', async ({ br await page.goto(url); - // trigger long animation frame function - await page.getByRole('button').click(); + // Separate mousedown and click into different frames so LoAF reports the click handler. + await page.getByRole('button').hover(); + await page.mouse.down(); + await waitForNextFrame(page); + await page.mouse.up(); const pageloadSpan = await pageloadSpanPromise; await page.evaluate(() => (window as any).Sentry.flush()); diff --git a/dev-packages/browser-integration-tests/utils/helpers.ts b/dev-packages/browser-integration-tests/utils/helpers.ts index 90b3deae3cb9..60df3fd96eb2 100644 --- a/dev-packages/browser-integration-tests/utils/helpers.ts +++ b/dev-packages/browser-integration-tests/utils/helpers.ts @@ -564,6 +564,15 @@ export async function waitForLcpCandidate(page: Page, selector: string): Promise }); } +/** + * Resolves after the current frame has been presented (double rAF). + */ +export async function waitForNextFrame(page: Page): Promise { + await page.evaluate( + () => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))), + ); +} + export async function hidePage(page: Page, interactionSelector?: string): Promise { // web-vitals processes an interaction's event entries in `requestIdleCallback(..., { timeout: // 1000 })`, and Chromium only reaches idle here once that timeout elapses. Hiding before that From dd9903c8332cd157fbdbb90c9affe27e43570307 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Mon, 2 Nov 2026 16:28:42 -0500 Subject: [PATCH 092/169] test(nuxt): Make the `nuxt-5 (tunnel-dev)` e2e variant optional (#26004) `nitro@3.0.261102-rc` broke loading `nitro/h3` in `nuxt dev`, which failed the required `nuxt-5 (tunnel-dev)` job on every PR, fixed by moving that variant to the optional matrix until Nitro fixes it. --- dev-packages/e2e-tests/test-applications/nuxt-5/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/package.json b/dev-packages/e2e-tests/test-applications/nuxt-5/package.json index bb03b840799c..955eb6c13c2d 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/package.json @@ -47,7 +47,9 @@ "build-command": "pnpm test:build:tunnel-basepath", "assert-command": "pnpm test:assert:tunnel-basepath", "label": "nuxt-5 (tunnel-basepath)" - }, + } + ], + "optionalVariants": [ { "build-command": "pnpm install", "assert-command": "pnpm test:assert:tunnel-dev", From 41894d9267ce61610cbb67f0d0a51c246d74950d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 08:11:55 +0100 Subject: [PATCH 093/169] ci(deps): Bump getsentry/craft from 2.31.2 to 3.4.0 (#25987) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [getsentry/craft](https://github.com/getsentry/craft) from 2.31.2 to 3.4.0.
Release notes

Sourced from getsentry/craft's releases.

3.4.0

Preview release workflows now handle workspace paths precisely, report version policies, and emit structured JSON for developers.

New Features ✨

Preview release PRs across workspaces with scoped path handling, version-policy reporting, and structured JSON output.

3.3.0

Workspace settings expose more package detail, while scoped packing and version bumps prevent unrelated package changes in monorepos.

New Features ✨

Workspace settings now expand package details, while NuGet packing stays scoped to the selected workspace.

Bug Fixes 🐛

Version bumps now target selected release workspaces, preventing unrelated package changes in monorepos.

3.2.0

Contributor attribution now recognizes co-authors and bot assistance while preserving merge-credit rules, and documentation clarifies Craft usage, targets, and GitHub Actions settings.

New Features ✨

Changelog credit now includes co-authors and bot-assisted contributions, while preserving author-only credit for merge commits without co-author trailers.

Documentation 📚

Documentation now separates Craft installation, workflows, configuration, and CLI guidance while clarifying target options and previously missing GitHub Actions settings.

3.1.0

Workspace-aware release labeling, clearer AI summaries, safer dependency handling, and stricter configuration validation improve changelog clarity and upgrade safety.

New Features ✨

GitHub release titles now reflect workspace tag prefixes for versioned changelog headings, while other release titles remain unchanged.

Bug Fixes 🐛

... (truncated)

Changelog

Sourced from getsentry/craft's changelog.

Changelog

3.5.0

New Features ✨

3.4.1

Bug Fixes 🐛

3.4.0

Preview release workflows now handle workspace paths precisely, report version policies, and emit structured JSON for developers.

New Features ✨

Preview release PRs across workspaces with scoped path handling, version-policy reporting, and structured JSON output.

3.3.0

Workspace settings expose more package detail, while scoped packing and version bumps prevent unrelated package changes in monorepos.

New Features ✨

Workspace settings now expand package details, while NuGet packing stays scoped to the selected workspace.

Bug Fixes 🐛

Version bumps now target selected release workspaces, preventing unrelated package changes in monorepos.

3.2.0

Contributor attribution now recognizes co-authors and bot assistance while preserving merge-credit rules, and documentation clarifies Craft usage, targets, and GitHub Actions settings.

New Features ✨

Changelog credit now includes co-authors and bot-assisted contributions, while preserving author-only credit for merge commits without co-author trailers.

... (truncated)

Commits
  • 4a8dba2 release: 3.4.0
  • ef9e8e0 feat(changelog): Preview PRs in every release workspace they land in (#942)
  • 1806f23 meta: Bump new development version
  • de14162 Merge commit '4b763f5419255bb520e3e5a49f5b91426dcd374d'
  • 4b763f5 release: 3.3.0
  • 64ef41a feat(nuget): Scope workspace expansion to the selected release workspace (#940)
  • db768ae feat(config): Expand workspace placeholders in release workspace settings (#939)
  • 2d8180c fix(prepare): Run automatic version bumps in the selected workspace (#938)
  • fbd5229 meta: Bump new development version
  • a5412e8 Merge commit '439259d80235250856fefffa4b47447f9b97f82f'
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=getsentry/craft&package-manager=github_actions&previous-version=2.31.2&new-version=3.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Martin Sonnberger --- .github/workflows/auto-release.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 38802d1c0ad6..c32aba007628 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -55,7 +55,7 @@ jobs: node-version-file: 'package.json' - name: Prepare release - uses: getsentry/craft@25028d0646040cc0a669ae3314cbf884f4c4347a # v2.31.2 + uses: getsentry/craft@4a8dba2a45b09f4e3fcfc86a79c94d0dd93295bb # v3.4.0 if: github.event.pull_request.merged == true && steps.version-regex.outputs.match != '' && steps.get_version.outputs.version != '' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e579c239dcfe..d9e59cbc284b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,7 @@ jobs: with: node-version-file: 'package.json' - name: Prepare release - uses: getsentry/craft@25028d0646040cc0a669ae3314cbf884f4c4347a # v2.31.2 + uses: getsentry/craft@4a8dba2a45b09f4e3fcfc86a79c94d0dd93295bb # v3.4.0 env: GITHUB_TOKEN: ${{ steps.token.outputs.token }} with: From b61bd60143a8806dc4f0e3721132f3e6cbecd02d Mon Sep 17 00:00:00 2001 From: Vladimir Babin Date: Tue, 3 Nov 2026 16:22:55 +0800 Subject: [PATCH 094/169] fix(core): preserve span links through beforeSendSpan (#25566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #25545 ## Problem Setting `beforeSendSpan` — even an identity callback `(span) => span` — drops the root span's `contexts.trace.links` from every transaction, so the `previous_trace` link is lost and the trace view no longer links to the previous trace. In `processBeforeSend`, the root span goes through `beforeSendSpan(convertTransactionEventToSpanJson(event))` and the result is merged back with `merge(event, convertSpanJsonToTransactionEvent(result))`. The two converters in `packages/core/src/utils/transactionEvent.ts` never copied `links`, and `merge` (default depth 2) replaces `contexts.trace` whole, so the links set by `spanToTransactionTraceContext` are discarded. Thanks to @arobison for the detailed root-cause trace in the issue. ## Fix Carry `links` through both conversions. `TraceContext.links` and `SpanJSON.links` are both `SpanLinkJSON[]`, so this is a direct pass-through. ## Tests Extended the existing `transactionEvent.test.ts` round-trip cases (`convertTransactionEventToSpanJson` and `convertSpanJsonToTransactionEvent`) to assert `links` (including a `previous_trace` link) survive. --------- Co-authored-by: Nicolas Hrubec Co-authored-by: Nicolas Hrubec Co-authored-by: GPT-6 --- packages/core/src/utils/transactionEvent.ts | 4 ++- packages/core/test/lib/client.test.ts | 31 ++++++++++++++++++ .../test/lib/utils/transactionEvent.test.ts | 32 +++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/packages/core/src/utils/transactionEvent.ts b/packages/core/src/utils/transactionEvent.ts index 97bf3580ce8c..4a4fe979ab05 100644 --- a/packages/core/src/utils/transactionEvent.ts +++ b/packages/core/src/utils/transactionEvent.ts @@ -6,7 +6,7 @@ import type { SpanJSON } from '../types/span'; * Converts a transaction event to a span JSON object. */ export function convertTransactionEventToSpanJson(event: TransactionEvent): SpanJSON { - const { trace_id, parent_span_id, span_id, status, origin, data, op } = event.contexts?.trace ?? {}; + const { trace_id, parent_span_id, span_id, status, origin, data, op, links } = event.contexts?.trace ?? {}; return { data: data ?? {}, @@ -19,6 +19,7 @@ export function convertTransactionEventToSpanJson(event: TransactionEvent): Span timestamp: event.timestamp, trace_id: trace_id ?? '', origin, + links, profile_id: data?.[SENTRY_PROFILE_ID] as string | undefined, exclusive_time: data?.[SENTRY_EXCLUSIVE_TIME] as number | undefined, measurements: event.measurements, @@ -43,6 +44,7 @@ export function convertSpanJsonToTransactionEvent(span: SpanJSON): TransactionEv op: span.op, status: span.status, origin: span.origin, + links: span.links, data: { ...span.data, ...(span.profile_id && { [SENTRY_PROFILE_ID]: span.profile_id }), diff --git a/packages/core/test/lib/client.test.ts b/packages/core/test/lib/client.test.ts index 0d3117f7ac4a..5d06b33e40d7 100644 --- a/packages/core/test/lib/client.test.ts +++ b/packages/core/test/lib/client.test.ts @@ -1636,6 +1636,37 @@ describe('Client', () => { }); }); + test('keeps root span links on the sent transaction with `beforeSendSpan`', () => { + const beforeSendSpan = withStaticSpan(vi.fn((span: SpanJSON) => span)); + const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN, beforeSendSpan }); + const client = new TestClient(options); + + const links = [ + { + trace_id: '12312012123120121231201212312012', + span_id: '1231201212312012', + sampled: true, + attributes: { 'sentry.link.type': 'previous_trace' }, + }, + ]; + + client.captureEvent({ + transaction: '/animals/are/great', + type: 'transaction', + spans: [], + contexts: { + trace: { + span_id: '9e15bf99fbe4bc80', + trace_id: '86f39e84263a4de99c326acab3bfe3bd', + links, + }, + }, + }); + + expect(beforeSendSpan).toHaveBeenCalledTimes(1); + expect(TestClient.instance!.event!.contexts?.trace?.links).toEqual(links); + }); + test('calls `beforeSendTransaction` and uses the modified event', () => { expect.assertions(2); diff --git a/packages/core/test/lib/utils/transactionEvent.test.ts b/packages/core/test/lib/utils/transactionEvent.test.ts index 03f1e231d975..062ec8aa41dc 100644 --- a/packages/core/test/lib/utils/transactionEvent.test.ts +++ b/packages/core/test/lib/utils/transactionEvent.test.ts @@ -48,6 +48,14 @@ describe('convertTransactionEventToSpanJson', () => { [SENTRY_EXCLUSIVE_TIME]: 123.45, other: 'value', }, + links: [ + { + span_id: 'linked-span', + trace_id: 'linked-trace', + sampled: true, + attributes: { 'sentry.link.type': 'previous_trace' }, + }, + ], }, }, start_timestamp: 1234567800, @@ -74,6 +82,14 @@ describe('convertTransactionEventToSpanJson', () => { origin: 'manual', profile_id: 'profile123', exclusive_time: 123.45, + links: [ + { + span_id: 'linked-span', + trace_id: 'linked-trace', + sampled: true, + attributes: { 'sentry.link.type': 'previous_trace' }, + }, + ], measurements: { fp: { value: 123, unit: 'millisecond' }, }, @@ -142,6 +158,14 @@ describe('convertSpanJsonToTransactionEvent', () => { origin: 'manual', profile_id: 'profile123', exclusive_time: 123.45, + links: [ + { + span_id: 'linked-span', + trace_id: 'linked-trace', + sampled: true, + attributes: { 'sentry.link.type': 'previous_trace' }, + }, + ], measurements: { fp: { value: 123, unit: 'millisecond' }, }, @@ -160,6 +184,14 @@ describe('convertSpanJsonToTransactionEvent', () => { op: 'http', status: 'ok', origin: 'manual', + links: [ + { + span_id: 'linked-span', + trace_id: 'linked-trace', + sampled: true, + attributes: { 'sentry.link.type': 'previous_trace' }, + }, + ], data: { other: 'value', [SENTRY_PROFILE_ID]: 'profile123', From 590d58c7aa6c2230716dbcc610c4d312ed56104c Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 3 Nov 2026 09:36:15 +0100 Subject: [PATCH 095/169] docs(changelog): Restore order of unreleased section in changelog (#26006) **Why:** The order of the unreleased section in the changelog was wrong so the action to credit external contributors couldn't find the right place to insert so it passed but no PR was opened. Fixing the order and adding the missing entry. Co-authored-by: OpenAI Codex --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c58c6b24ea5f..8efb65251493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott + +Work in this release was contributed by @lazerg, @chiliec, and @61465. Thank you for your contributions! + +### Important Changes + - **ref(cloudflare): Use the shared request handler wrapper** Cloudflare Workers now use the same request handler wrapper as `Deno.serve` and `Bun.serve`. Changes for Cloudflare users: @@ -12,10 +18,6 @@ - Request spans now list every `Set-Cookie` header of a response, as on Deno and Bun. - Without `AsyncLocalStorage` (for example on Shopify Oxygen), events now also get the request URL and method. -- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott - -### Important Changes - - **feat(nestjs): Add an option to monitor every @Cron method ([#25471](https://github.com/getsentry/sentry-javascript/pull/25471))** Set `monitorCronJobs: true` on `nestIntegration()` to send check-ins for every `@Cron()` method, without adding `@SentryCron` to each one. It's off by default. Each monitored job creates a monitor in Sentry, and monitors are billed. Use `excludeCronJobs` to skip jobs by slug. Methods that also have `@SentryCron` check in once, with the slug and options passed to `@SentryCron`. @@ -28,8 +30,6 @@ When Next.js serves a page from its cache, for example an ISR page or a `cacheComponents` shell, the pageload now has the `page.server_rendering.type` attribute. It is `static` if the whole page came from the cache, and `partially_static` if parts of it rendered for this request. Then the pageload stays connected to this request, as before. If the request that rendered the cached page was sampled, the pageload also links to it. So in Sentry, a cached page no longer looks like missing server instrumentation. -Work in this release was contributed by @lazerg and @chiliec. Thank you for your contributions! - ## 11.10.0 ### Important Changes From 441f49d06daaeee35ca9afd1df50ac3cbf9f1634 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 3 Nov 2026 09:36:45 +0100 Subject: [PATCH 096/169] chore(dependabot): Remove obsolete update rules (#26007) **Why:** Remove some leftover dependabot rules: - OTel instrumentation ignores only match excluded E2E apps. - `react-router-dom` ignore/group rules only match excluded E2E apps. - Supabase's Node 20 cap is obsolete; browser tests use Node 24. Co-authored-by: GPT-6 --- .github/dependabot.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dc6184d29f3b..234cb38a94fc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,17 +13,10 @@ updates: schedule: interval: 'weekly' ignore: - - dependency-name: '@opentelemetry/instrumentation' - - dependency-name: '@opentelemetry/instrumentation-*' - dependency-name: 'typescript' - # 2.110.0 raised the engine requirement to Node >=22, while this repo runs Node 20. - - dependency-name: '@supabase/supabase-js' - versions: ['>=2.110.0'] # Keep react-router dev dependencies on v7, so tests cover the older major. - dependency-name: 'react-router' update-types: ['version-update:semver-major'] - - dependency-name: 'react-router-dom' - update-types: ['version-update:semver-major'] - dependency-name: '@react-router/*' update-types: ['version-update:semver-major'] groups: @@ -36,7 +29,6 @@ updates: react-router: patterns: - 'react-router' - - 'react-router-dom' - '@react-router/*' versioning-strategy: increase commit-message: From a0ff8aded8a713ee57d018aa728620b8bec1e1b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 09:46:35 +0100 Subject: [PATCH 097/169] ci(deps): Bump anthropics/claude-code-action from 1.0.264 to 1.0.266 (#26008) Bumps [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) from 1.0.264 to 1.0.266.
Release notes

Sourced from anthropics/claude-code-action's releases.

v1.0.266

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.265...v1.0.266

v1.0.265

Full Changelog: https://github.com/anthropics/claude-code-action/compare/v1.0.264...v1.0.265

Commits
  • d535f51 chore: bump Claude Code to 2.1.316 and Agent SDK to 0.3.316
  • 6d5ae52 chore: bump Claude Code to 2.1.314 and Agent SDK to 0.3.314
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=anthropics/claude-code-action&package-manager=github_actions&previous-version=1.0.264&new-version=1.0.266)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-fix-issue.yml | 2 +- .github/workflows/dependabot-auto-triage.yml | 4 ++-- .github/workflows/track-framework-updates.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-fix-issue.yml b/.github/workflows/auto-fix-issue.yml index 146ab59d415f..d832fa29af15 100644 --- a/.github/workflows/auto-fix-issue.yml +++ b/.github/workflows/auto-fix-issue.yml @@ -77,7 +77,7 @@ jobs: - name: Try to fix the issue with Claude id: triage - uses: anthropics/claude-code-action@d70510b174a1244958df0b03166a54912811f3e6 # v1 + uses: anthropics/claude-code-action@d535f5172dec02b204866b393d6b20b9b2aaf98d # v1 env: ANTHROPIC_BASE_URL: https://openrouter.ai/api with: diff --git a/.github/workflows/dependabot-auto-triage.yml b/.github/workflows/dependabot-auto-triage.yml index c3efead14345..5a8f79e2c866 100644 --- a/.github/workflows/dependabot-auto-triage.yml +++ b/.github/workflows/dependabot-auto-triage.yml @@ -155,7 +155,7 @@ jobs: git config user.email "${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" - name: Open batched runtime fix PR via skill - uses: anthropics/claude-code-action@d70510b174a1244958df0b03166a54912811f3e6 # v1 + uses: anthropics/claude-code-action@d535f5172dec02b204866b393d6b20b9b2aaf98d # v1 env: ANTHROPIC_BASE_URL: https://openrouter.ai/api with: @@ -244,7 +244,7 @@ jobs: git config user.email "${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" - name: Open batched dev fix PR via skill - uses: anthropics/claude-code-action@d70510b174a1244958df0b03166a54912811f3e6 # v1 + uses: anthropics/claude-code-action@d535f5172dec02b204866b393d6b20b9b2aaf98d # v1 env: ANTHROPIC_BASE_URL: https://openrouter.ai/api with: diff --git a/.github/workflows/track-framework-updates.yml b/.github/workflows/track-framework-updates.yml index 9b664d361078..d1660afaddef 100644 --- a/.github/workflows/track-framework-updates.yml +++ b/.github/workflows/track-framework-updates.yml @@ -43,7 +43,7 @@ jobs: - name: Run Claude digest id: digest - uses: anthropics/claude-code-action@d70510b174a1244958df0b03166a54912811f3e6 # v1 + uses: anthropics/claude-code-action@d535f5172dec02b204866b393d6b20b9b2aaf98d # v1 env: ANTHROPIC_BASE_URL: https://openrouter.ai/api with: From db1e22f2b3b5de1e8a225cc3877047c583a65717 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 10:21:31 +0100 Subject: [PATCH 098/169] ci(deps): Bump getsentry/craft from 3.4.0 to 3.5.0 (#26011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [getsentry/craft](https://github.com/getsentry/craft) from 3.4.0 to 3.5.0.
Release notes

Sourced from getsentry/craft's releases.

3.5.0

New Features ✨

3.4.1

Bug Fixes 🐛

Changelog

Sourced from getsentry/craft's changelog.

Changelog

3.8.0

Version bumps now update repository files more safely through workspace-aware validation, preventing invalid paths and no-op preparations from writing changes.

New Features ✨

Automatic version bumps now update repository files safely, with workspace-aware configuration and strict validation before writes.

3.7.1

Dry-run versioning is more repeatable, while built-in development bumps preserve output and avoid redundant post-release bumps after pre-releases.

Bug Fixes 🐛

Dry-run version bumps stay isolated and repeat consistently without checkout changes or temporary branch failures.

Internal Changes 🔧

Release configuration now uses built-in development bumps, preserves version output, and skips redundant bumps after pre-releases.

3.7.0

Publishing now automates development-version updates, while changelog filtering keeps meaningful entries and omits generated bump commits.

New Features ✨

Publishing can automatically create, commit, rebase, and push a development-version bump using configured versioning rules.

Bug Fixes 🐛

Changelog filtering preserves real entries by ignoring skip markers in code while excluding Craft’s development-version bump commits.

3.6.0

When matching tags are unavailable, Prepare now derives release-unit versions from project files, enabling versioned releases without those tags.

New Features ✨

... (truncated)

Commits
  • 3f1f02d release: 3.5.0
  • f876000 feat(targets): Add mcp-registry target for the official MCP Registry (#945)
  • 2ef9694 meta: Bump new development version
  • af63a8f Merge commit '90e8e203ad86c8ab8ee134e8d387def13e620e81'
  • 90e8e20 release: 3.4.1
  • 4a3ca28 fix(prepare): Emit the release branch targets for the publish request (#943)
  • b0a248d meta: Bump new development version
  • 85d5701 Merge commit '4a8dba2a45b09f4e3fcfc86a79c94d0dd93295bb'
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=getsentry/craft&package-manager=github_actions&previous-version=3.4.0&new-version=3.5.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/auto-release.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index c32aba007628..aad81a1c4f3b 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -55,7 +55,7 @@ jobs: node-version-file: 'package.json' - name: Prepare release - uses: getsentry/craft@4a8dba2a45b09f4e3fcfc86a79c94d0dd93295bb # v3.4.0 + uses: getsentry/craft@3f1f02d50b08b8c509a28d2cc2abf594e9a28011 # v3.5.0 if: github.event.pull_request.merged == true && steps.version-regex.outputs.match != '' && steps.get_version.outputs.version != '' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9e59cbc284b..ff44869570f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,7 @@ jobs: with: node-version-file: 'package.json' - name: Prepare release - uses: getsentry/craft@4a8dba2a45b09f4e3fcfc86a79c94d0dd93295bb # v3.4.0 + uses: getsentry/craft@3f1f02d50b08b8c509a28d2cc2abf594e9a28011 # v3.5.0 env: GITHUB_TOKEN: ${{ steps.token.outputs.token }} with: From 21ca39b62490111c7a0f05b85d3199f99a7d289e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 10:46:03 +0100 Subject: [PATCH 099/169] chore(deps-dev): Bump wrangler from 4.61.0 to 4.156.0 (#26009) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [wrangler](https://github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler) from 4.61.0 to 4.156.0.
Release notes

Sourced from wrangler's releases.

wrangler@4.156.0

Minor Changes

  • #16532 25802bc Thanks @​simonha9! - Allow the experimental wrangler d1 insights command to run across every D1 database in the account

    The database name is now optional. With no name, the command returns the top queries across every D1 database in the account, and each result gains a databaseId field.

Patch Changes

  • #16369 ec0bea4 Thanks @​MattieTK! - Stop autoconfig in non-interactive runs when a project root holds both static files and a Pages functions directory

    When a project without a detected framework has a functions directory, autoconfig asks whether it is a Cloudflare Pages project. Non-interactive runs (CI and coding agents) assume it is not and continue as a static site. When the static files are in the project root, that deploys the root as static assets, and the Pages Functions (the project's API routes) are not deployed.

    In that case autoconfig now stops with an error explaining that Pages Functions cannot be migrated automatically yet. For Pages Functions it suggests moving the static files into a subdirectory and deploying with wrangler pages deploy, or migrating the Functions to a Worker; for a functions directory that is not Pages Functions, it suggests moving the static files into a subdirectory or adding a configuration file. Projects whose static files are already in a subdirectory, projects detected as Pages or with a detected framework, and interactive runs where the user answers the prompt are unaffected.

  • #16367 b4653b4 Thanks @​MattieTK! - Write a protective .assetsignore when autoconfig uses the project root as the static assets directory

    Autoconfig is meant to write an .assetsignore when the project root itself is the assets directory, so that local files are not uploaded as public assets. The check compared the detected output directory, which is relative ("."), with the absolute project path, so it never matched and no .assetsignore was written. A static project with an index.html at its root (for example after wrangler setup or a zero-config wrangler deploy) then uploaded files such as .env, .dev.vars, .git and node_modules as public assets.

    The check now resolves the assets directory that the generated configuration uses and compares it with the project root, so frameworks that set their own build output directory (such as Nuxt or SvelteKit) do not get an unused root .assetsignore. The generated .assetsignore also covers what a Cloudflare Pages upload skips, alongside the existing .wrangler, .dev.vars* and .env* entries: /_worker.js, /_routes.json and /functions at the root (so nested paths such as blog/functions/index.html are still uploaded), node_modules, .git and .DS_Store at any depth, and .npmrc. If an existing .assetsignore only lists a narrower pattern such as .env, .env* is added so that files like .env.production are skipped too. When the project root of an assets-only project contains a Pages functions directory or _worker.js, autoconfig now warns that it will not be deployed, since it is excluded from the assets and not run as server code.

  • #16532 25802bc Thanks @​simonha9! - Fail the experimental wrangler d1 insights command with an actionable error when the GraphQL Analytics API rejects the request

    Previously the command printed an empty []. Rate limit errors now say how long to wait before retrying, and authorization errors name the "Account Analytics: Read" permission the API token needs.

  • #16013 8b64bec Thanks @​swalker326! - Fix Durable Object self-references in tests and container validation

    Durable Object bindings that explicitly name the Worker itself now resolve to the test runner in @cloudflare/vitest-plugin, for both cloudflare.config.ts and Wrangler configuration files. Named Wrangler environments use their effective Worker name, keeping bindings to the base Worker external. Container validation also accepts these self-references, using the effective deployment name to distinguish local and external bindings.

    Explicit binding targets remain intact during configuration conversion, so deploying with --name or a CI name override preserves the intended Durable Object target.

  • Updated dependencies [15fbaed, d8899ac]:

wrangler@4.155.0

Minor Changes

  • #16536 a95d647 Thanks @​WalshyDev! - Support remote Analytics Engine bindings in local development

    Setting remote: true on an Analytics Engine dataset now sends writeDataPoint() calls from wrangler dev and the Vite plugin to the real dataset. Without it, writes are still dropped locally.

    {
      "analytics_engine_datasets": [
        {
          "binding": "EVENTS",
          "dataset": "my-dataset",
          "remote": true
        }
      ]
    

... (truncated)

Commits
  • b1ced56 Version Packages (#16556)
  • 8b64bec [config] Fix Durable Object self-references without changing deployment targe...
  • 25802bc [wrangler] Run wrangler d1 insights account-wide (#16532)
  • b4653b4 [autoconfig] Write a protective .assetsignore when the project root is the as...
  • a09957a Version Packages (#16553)
  • c7a3373 Revert "Version Packages" (#16554)
  • a95d647 [wrangler] Support remote Analytics Engine bindings in local dev (#16536)
  • 52315bd Version Packages (#16529)
  • dba0e81 [wrangler] Escape terminal controls in tail output (#16526)
  • fc57bc0 [miniflare] Enable local Flagship management without Worker bindings (#16489)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=wrangler&package-manager=npm_and_yarn&previous-version=4.61.0&new-version=4.156.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Andrei <168741329+andreiborza@users.noreply.github.com> --- .../cloudflare-integration-tests/package.json | 2 +- packages/cloudflare/package.json | 2 +- packages/hono/package.json | 2 +- pnpm-lock.yaml | 868 +++--------------- 4 files changed, 125 insertions(+), 749 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json index 1d336b48e677..68cf1cb65464 100644 --- a/dev-packages/cloudflare-integration-tests/package.json +++ b/dev-packages/cloudflare-integration-tests/package.json @@ -40,6 +40,6 @@ "prisma": "6.15.0", "vite": "^8.3.1", "vitest": "^4.1.11", - "wrangler": "4.86.0" + "wrangler": "4.156.0" } } diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 63e75d57b28a..b21f32d674e6 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -81,7 +81,7 @@ "@types/node": "^20.19.0", "acorn": "^8.14.0", "vite": "^8.3.1", - "wrangler": "4.61.0" + "wrangler": "4.156.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/hono/package.json b/packages/hono/package.json index a1238930ecfa..be9722f8256f 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -112,7 +112,7 @@ "@types/node": "^20.19.0", "hono": "4.13.11", "hono-5": "npm:hono@5.0.0-rc.1", - "wrangler": "4.62.0" + "wrangler": "4.156.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52d0d8082991..6ac487eaedde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -529,7 +529,7 @@ importers: devDependencies: '@cloudflare/vite-plugin': specifier: 1.34.0 - version: 1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1)) + version: 1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.156.0(@cloudflare/workers-types@4.20260519.1)(@types/node@20.19.43)) '@cloudflare/workers-types': specifier: ^4.20260426.0 version: 4.20260519.1 @@ -555,8 +555,8 @@ importers: specifier: ^4.1.11 version: 4.1.11(@opentelemetry/api@1.10.0)(@types/node@20.19.43)(@vitest/coverage-v8@4.1.11)(esbuild@0.28.1)(jiti@2.7.0)(jsdom@21.1.2(supports-color@10.0.0))(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) wrangler: - specifier: 4.86.0 - version: 4.86.0(@cloudflare/workers-types@4.20260519.1) + specifier: 4.156.0 + version: 4.156.0(@cloudflare/workers-types@4.20260519.1)(@types/node@20.19.43) dev-packages/deno-integration-tests: dependencies: @@ -1249,8 +1249,8 @@ importers: specifier: ^8.3.1 version: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) wrangler: - specifier: 4.61.0 - version: 4.61.0(@cloudflare/workers-types@5.20260710.1) + specifier: 4.156.0 + version: 4.156.0(@cloudflare/workers-types@5.20260710.1)(@types/node@20.19.43) packages/core: dependencies: @@ -1572,8 +1572,8 @@ importers: specifier: npm:hono@5.0.0-rc.1 version: hono@5.0.0-rc.1 wrangler: - specifier: 4.62.0 - version: 4.62.0(@cloudflare/workers-types@4.20250922.0) + specifier: 4.156.0 + version: 4.156.0(@cloudflare/workers-types@4.20250922.0)(@types/node@20.19.43) packages/integration-shims: dependencies: @@ -1986,7 +1986,7 @@ importers: devDependencies: '@react-router/dev': specifier: ^7.18.4 - version: 7.18.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0) + version: 7.18.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.156.0(@cloudflare/workers-types@5.20260710.1)(@types/node@20.19.43))(yaml@2.9.0) '@react-router/node': specifier: ^7.15.0 version: 7.18.4(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@6.0.3) @@ -4106,26 +4106,21 @@ packages: resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} - '@cloudflare/unenv-preset@2.11.0': - resolution: {integrity: sha512-z3hxFajL765VniNPGV0JRStZolNz63gU3B3AktwoGdDlnQvz5nP+Ah4RL04PONlZQjwmDdGHowEStJ94+RsaJg==} - peerDependencies: - unenv: 2.0.0-rc.24 - workerd: ^1.20260115.0 - peerDependenciesMeta: - workerd: - optional: true + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} - '@cloudflare/unenv-preset@2.12.0': - resolution: {integrity: sha512-NK4vN+2Z/GbfGS4BamtbbVk1rcu5RmqaYGiyHJQrA09AoxdZPHDF3W/EhgI0YSK8p3vRo/VNCtbSJFPON7FWMQ==} + '@cloudflare/unenv-preset@2.16.1': + resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} peerDependencies: unenv: 2.0.0-rc.24 - workerd: ^1.20260115.0 + workerd: '>1.20260305.0 <2.0.0-0' peerDependenciesMeta: workerd: optional: true - '@cloudflare/unenv-preset@2.16.1': - resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} + '@cloudflare/unenv-preset@2.16.2': + resolution: {integrity: sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==} peerDependencies: unenv: 2.0.0-rc.24 workerd: '>1.20260305.0 <2.0.0-0' @@ -4139,34 +4134,16 @@ packages: vite: ^6.1.0 || ^7.0.0 || ^8.0.0 wrangler: ^4.86.0 - '@cloudflare/workerd-darwin-64@1.20260124.0': - resolution: {integrity: sha512-VuqscLhiiVIf7t/dcfkjtT0LKJH+a06KUFwFTHgdTcqyLbFZ44u1SLpOONu5fyva4A9MdaKh9a+Z/tBC1d76nw==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@cloudflare/workerd-darwin-64@1.20260131.0': - resolution: {integrity: sha512-+1X4qErc715NUhJZNhtlpuCxajhD5YNre7Cz50WPMmj+BMUrh9h7fntKEadtrUo5SM2YONY7CDzK7wdWbJJBVA==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - '@cloudflare/workerd-darwin-64@1.20260426.1': resolution: {integrity: sha512-Ch7DqsmYzSQRTY87pZpsGsFVz9VVBnLPnCBOHxKt1HH25a7oMu1w1PbPWqVmE0VerCLsj/TScX7Ob3v6E14TZw==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260124.0': - resolution: {integrity: sha512-PfnjoFooPgRKFUIZcEP9irnn5Y7OgXinjM+IMlKTdEyLWjMblLsbsqAgydf75+ii0715xAeUlWQjZrWdyOZjMw==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20260131.0': - resolution: {integrity: sha512-M84mXR8WEMEBuX4/dL2IQ4wHV/ALwYjx9if5ePZR8rdbD7if/fkEEoMBq0bGS/1gMLRqqCZLstabxHV+g92NNg==} + '@cloudflare/workerd-darwin-64@1.20261023.1': + resolution: {integrity: sha512-lsEx0i7nR0PNG/erUFFHmQmiidEhLfDE5uQmAL9421/y4/ycuxDDOSaxu3VGGYFFPK5sduyUeMmjIlX4CwOBFg==} engines: {node: '>=16'} - cpu: [arm64] + cpu: [x64] os: [darwin] '@cloudflare/workerd-darwin-arm64@1.20260426.1': @@ -4175,17 +4152,11 @@ packages: cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260124.0': - resolution: {integrity: sha512-KSkZl4kwcWeFXI7qsaLlMnKwjgdZwI0OEARjyZpiHCxJCqAqla9XxQKNDscL2Z3qUflIo30i+uteGbFrhzuVGQ==} + '@cloudflare/workerd-darwin-arm64@1.20261023.1': + resolution: {integrity: sha512-5e79fHgabdDD8EgmjOhJ1S+xMsf6DbC7fL7IgTBizMnzOp0P2isDn7pYmUxI9AJZE9FVQ4RUilH/I36YNg+GQQ==} engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@cloudflare/workerd-linux-64@1.20260131.0': - resolution: {integrity: sha512-SWzr48bCL9y5wjkj23tXS6t/6us99EAH9T5TAscMV0hfJFZQt97RY/gaHKyRRjFv6jfJZvk7d4g+OmGeYBnwcg==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] + cpu: [arm64] + os: [darwin] '@cloudflare/workerd-linux-64@1.20260426.1': resolution: {integrity: sha512-C8LlC8uSYzg49y51n++75esxZmMp+Uz1OKHHA/4lkv6rjOTbcHQJuEwSLppjybVIXpv7A8MBhbu9iyCTvyv1mw==} @@ -4193,16 +4164,10 @@ packages: cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260124.0': - resolution: {integrity: sha512-61xjSUNk745EVV4vXZP0KGyLCatcmamfBB+dcdQ8kDr6PrNU4IJ1kuQFSJdjybyDhJRm4TpGVywq+9hREuF7xA==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20260131.0': - resolution: {integrity: sha512-mL0kLPGIBJRPeHS3+erJ2t5dJT3ODhsKvR9aA4BcsY7M30/QhlgJIF6wsgwNisTJ23q8PbobZNHBUKIe8l/E9A==} + '@cloudflare/workerd-linux-64@1.20261023.1': + resolution: {integrity: sha512-VHQ6jKFSihjR2+x1buby8XTdBLLJztVnYiB7OLOTYO8aDXB+c1Rd2410PdgFuFYc4AgFEk6PCJLyCLL5L1KBnw==} engines: {node: '>=16'} - cpu: [arm64] + cpu: [x64] os: [linux] '@cloudflare/workerd-linux-arm64@1.20260426.1': @@ -4211,20 +4176,20 @@ packages: cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260124.0': - resolution: {integrity: sha512-j9O11pwQQV6Vi3peNrJoyIas3SrZHlPj0Ah+z1hDW9o1v35euVBQJw/PuzjPOXxTFUlGQoMJdfzPsO9xP86g7A==} + '@cloudflare/workerd-linux-arm64@1.20261023.1': + resolution: {integrity: sha512-ilpRHzfDN8YFRM5CLRKuFw40x/LJBS89yVdhdQmxehlVDuBqjKpJB++zPFbTgKRRFaNupgnXc8RHktXAIhgCmQ==} engines: {node: '>=16'} - cpu: [x64] - os: [win32] + cpu: [arm64] + os: [linux] - '@cloudflare/workerd-windows-64@1.20260131.0': - resolution: {integrity: sha512-hoQqTFBpP1zntP2OQSpt5dEWbd9vSBliK+G7LmDXjKitPkmkRFo2PB4P9aBRE1edPAIO/fpdoJv928k2HaAn4A==} + '@cloudflare/workerd-windows-64@1.20260426.1': + resolution: {integrity: sha512-d3Xj/IjINRgNVwH+eKhpUn4xkkcEewbWXbOvBlapiirKWh5zl9m0Epi3qOqmjyRYK6MICqIGXg4qZBEt0lxudw==} engines: {node: '>=16'} cpu: [x64] os: [win32] - '@cloudflare/workerd-windows-64@1.20260426.1': - resolution: {integrity: sha512-d3Xj/IjINRgNVwH+eKhpUn4xkkcEewbWXbOvBlapiirKWh5zl9m0Epi3qOqmjyRYK6MICqIGXg4qZBEt0lxudw==} + '@cloudflare/workerd-windows-64@1.20261023.1': + resolution: {integrity: sha512-279WLVjNsMycAwr0EZNiNEf0l2SY6NUtsN80NwZzbTHzakmiN+WSx4w38muxPm+PAU0oSPPAPXKVD+H6Hya8xw==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -4529,18 +4494,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.0': - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} @@ -4565,18 +4518,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.0': - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.28.1': resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} @@ -4607,18 +4548,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.0': - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.28.1': resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} @@ -4643,18 +4572,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.0': - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.28.1': resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} @@ -4679,18 +4596,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.0': - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.28.1': resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} @@ -4715,18 +4620,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.0': - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.28.1': resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} @@ -4751,18 +4644,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.0': - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.28.1': resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} @@ -4787,18 +4668,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.0': - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.28.1': resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} @@ -4823,18 +4692,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.0': - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.28.1': resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} @@ -4859,18 +4716,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.0': - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.28.1': resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} @@ -4895,18 +4740,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.0': - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.28.1': resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} @@ -4943,18 +4776,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.0': - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.28.1': resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} @@ -4979,18 +4800,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.0': - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.28.1': resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} @@ -5015,18 +4824,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.0': - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.28.1': resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} @@ -5051,18 +4848,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.0': - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.28.1': resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} @@ -5087,18 +4872,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.0': - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.28.1': resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} engines: {node: '>=18'} @@ -5123,18 +4896,6 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.0': - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.28.1': resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} engines: {node: '>=18'} @@ -5153,18 +4914,6 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.27.0': - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.28.1': resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} engines: {node: '>=18'} @@ -5189,18 +4938,6 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.0': - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.28.1': resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} engines: {node: '>=18'} @@ -5219,18 +4956,6 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.27.0': - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.28.1': resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} engines: {node: '>=18'} @@ -5255,18 +4980,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.0': - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.28.1': resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} engines: {node: '>=18'} @@ -5285,18 +4998,6 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.27.0': - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.28.1': resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} engines: {node: '>=18'} @@ -5321,18 +5022,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.27.0': - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.28.1': resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} engines: {node: '>=18'} @@ -5357,18 +5046,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.27.0': - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.28.1': resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} engines: {node: '>=18'} @@ -5393,18 +5070,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.0': - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.28.1': resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} engines: {node: '>=18'} @@ -5429,18 +5094,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.27.0': - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.28.1': resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} engines: {node: '>=18'} @@ -12210,16 +11863,6 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.28.1: resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} @@ -14763,21 +14406,15 @@ packages: peerDependencies: webpack: ^5.0.0 - miniflare@4.20260124.0: - resolution: {integrity: sha512-Co8onUh+POwOuLty4myQg+Nzg9/xZ5eAJc1oqYBzRovHd/XIpb5WAnRVaubcfAQJ85awWtF3yXUHCDx6cIaN3w==} - engines: {node: '>=18.0.0'} - hasBin: true - - miniflare@4.20260131.0: - resolution: {integrity: sha512-CtObRzlAzOUpCFH+MgImykxmDNKthrgIYtC+oLC3UGpve6bGLomKUW4u4EorTvzlQFHe66/9m/+AYbBbpzG0mQ==} - engines: {node: '>=18.0.0'} - hasBin: true - miniflare@4.20260426.0: resolution: {integrity: sha512-KM+v76d04qT+NsPfVKVQEgnnuLNE3uzCCl2QKMTJ5OXor5JbBm1vpkQwQ+l7o5ELCrZ74RnyKhJKLiJyUA39Tw==} engines: {node: '>=18.0.0'} hasBin: true + miniflare@5.20261023.3-beta: + resolution: {integrity: sha512-thdXzlktu5GE1o/BlmMAPeJ0Cu6Tu5LpMbRlyfRrpVvMRrhd6p52TMqcITCZZLVzt1UO92VGBkLDhLOpryUAyQ==} + engines: {node: '>=22.0.0'} + minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -18633,14 +18270,14 @@ packages: resolution: {integrity: sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==} engines: {node: '>=18.17'} - undici@7.18.2: - resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==} - engines: {node: '>=20.18.1'} - undici@7.24.8: resolution: {integrity: sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==} engines: {node: '>=20.18.1'} + undici@7.30.1: + resolution: {integrity: sha512-03KrMbjIFDWQR5RNPrSw3bd1r12JpyFQl9/yxjUbJtqkjmllqC5lFjZh6ujeuJCKr8EOQvRW872Oy/az+nLYxg==} + engines: {node: '>=20.18.1'} + unenv@1.10.0: resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==} @@ -19679,18 +19316,13 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - workerd@1.20260124.0: - resolution: {integrity: sha512-JN6voV/fUQK342a39Rl+20YVmtIXZVbpxc7V/m809lUnlTGPy4aa5MI7PMoc+9qExgAEOw9cojvN5zOfqmMWLg==} - engines: {node: '>=16'} - hasBin: true - - workerd@1.20260131.0: - resolution: {integrity: sha512-4zZxOdWeActbRfydQQlj7vZ2ay01AjjNC4K3stjmWC3xZHeXeN3EAROwsWE83SZHhtw4rn18srrhtXoQvQMw3Q==} + workerd@1.20260426.1: + resolution: {integrity: sha512-ELvGgN8c9oo+E6EPyecxk1TEf6/eAK4TxxQTW5mQ87C7jbjCzhMbg0P2ije49UBHV0dkBYPJcJvcklUltipl2A==} engines: {node: '>=16'} hasBin: true - workerd@1.20260426.1: - resolution: {integrity: sha512-ELvGgN8c9oo+E6EPyecxk1TEf6/eAK4TxxQTW5mQ87C7jbjCzhMbg0P2ije49UBHV0dkBYPJcJvcklUltipl2A==} + workerd@1.20261023.1: + resolution: {integrity: sha512-NeofJFyaYNpqKpFRJW3ApuonaerPd6/JWlfTtLDYf2A6ygqrDEFnYgEXmc0hhOxqnJsSM+kXJN6VebPojzRrsg==} engines: {node: '>=16'} hasBin: true @@ -19700,32 +19332,12 @@ packages: workerpool@9.3.4: resolution: {integrity: sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==} - wrangler@4.61.0: - resolution: {integrity: sha512-Kb8NMe1B/HM7/ds3hU+fcV1U7T996vRKJ0UU/qqgNUMwdemTRA+sSaH3mQvQslIBbprHHU81s0huA6fDIcwiaQ==} - engines: {node: '>=20.0.0'} - hasBin: true - peerDependencies: - '@cloudflare/workers-types': ^4.20260124.0 - peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - - wrangler@4.62.0: - resolution: {integrity: sha512-DogP9jifqw85g33BqwF6m21YBW5J7+Ep9IJLgr6oqHU0RkA79JMN5baeWXdmnIWZl+VZh6bmtNtR+5/Djd32tg==} - engines: {node: '>=20.0.0'} - hasBin: true - peerDependencies: - '@cloudflare/workers-types': ^4.20260131.0 - peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - - wrangler@4.86.0: - resolution: {integrity: sha512-9aa/gbF/HiUeeUEwyQpW5LDPBEzyt7iaE6xHwm0vk2Ly8A6J+jh03pzchqVnCCWR832mNyA28MD8oAYt0Kfvlw==} - engines: {node: '>=20.3.0'} + wrangler@4.156.0: + resolution: {integrity: sha512-kVLAShuLiNgE3jcrmXcymDf/iPKDPrSxACe5ueugW/33HeNB9E9CkKYchudCeBwgmsnl4k+1zt4NO7vqb/fRuQ==} + engines: {node: '>=22.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20260426.1 + '@cloudflare/workers-types': ^5.20261023.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -19761,6 +19373,18 @@ packages: utf-8-validate: optional: true + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.21.3: resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} @@ -22823,80 +22447,61 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} - '@cloudflare/unenv-preset@2.11.0(unenv@2.0.0-rc.24)(workerd@1.20260124.0)': - dependencies: - unenv: 2.0.0-rc.24 - optionalDependencies: - workerd: 1.20260124.0 + '@cloudflare/kv-asset-handler@0.5.0': {} - '@cloudflare/unenv-preset@2.12.0(unenv@2.0.0-rc.24)(workerd@1.20260131.0)': + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260426.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260131.0 + workerd: 1.20260426.1 - '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260426.1)': + '@cloudflare/unenv-preset@2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261023.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260426.1 + workerd: 1.20261023.1 - '@cloudflare/vite-plugin@1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))': + '@cloudflare/vite-plugin@1.34.0(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(workerd@1.20260426.1)(wrangler@4.156.0(@cloudflare/workers-types@4.20260519.1)(@types/node@20.19.43))': dependencies: '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260426.1) miniflare: 4.20260426.0 unenv: 2.0.0-rc.24 vite: 8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) - wrangler: 4.86.0(@cloudflare/workers-types@4.20260519.1) + wrangler: 4.156.0(@cloudflare/workers-types@4.20260519.1)(@types/node@20.19.43) ws: 8.18.0 transitivePeerDependencies: - bufferutil - utf-8-validate - workerd - '@cloudflare/workerd-darwin-64@1.20260124.0': - optional: true - - '@cloudflare/workerd-darwin-64@1.20260131.0': - optional: true - '@cloudflare/workerd-darwin-64@1.20260426.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260124.0': - optional: true - - '@cloudflare/workerd-darwin-arm64@1.20260131.0': + '@cloudflare/workerd-darwin-64@1.20261023.1': optional: true '@cloudflare/workerd-darwin-arm64@1.20260426.1': optional: true - '@cloudflare/workerd-linux-64@1.20260124.0': - optional: true - - '@cloudflare/workerd-linux-64@1.20260131.0': + '@cloudflare/workerd-darwin-arm64@1.20261023.1': optional: true '@cloudflare/workerd-linux-64@1.20260426.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20260124.0': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20260131.0': + '@cloudflare/workerd-linux-64@1.20261023.1': optional: true '@cloudflare/workerd-linux-arm64@1.20260426.1': optional: true - '@cloudflare/workerd-windows-64@1.20260124.0': + '@cloudflare/workerd-linux-arm64@1.20261023.1': optional: true - '@cloudflare/workerd-windows-64@1.20260131.0': + '@cloudflare/workerd-windows-64@1.20260426.1': optional: true - '@cloudflare/workerd-windows-64@1.20260426.1': + '@cloudflare/workerd-windows-64@1.20261023.1': optional: true '@cloudflare/workers-types@4.20250214.0': {} @@ -23472,12 +23077,6 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/aix-ppc64@0.27.0': - optional: true - - '@esbuild/aix-ppc64@0.27.3': - optional: true - '@esbuild/aix-ppc64@0.28.1': optional: true @@ -23490,12 +23089,6 @@ snapshots: '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm64@0.27.0': - optional: true - - '@esbuild/android-arm64@0.27.3': - optional: true - '@esbuild/android-arm64@0.28.1': optional: true @@ -23511,12 +23104,6 @@ snapshots: '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-arm@0.27.0': - optional: true - - '@esbuild/android-arm@0.27.3': - optional: true - '@esbuild/android-arm@0.28.1': optional: true @@ -23529,12 +23116,6 @@ snapshots: '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/android-x64@0.27.0': - optional: true - - '@esbuild/android-x64@0.27.3': - optional: true - '@esbuild/android-x64@0.28.1': optional: true @@ -23547,12 +23128,6 @@ snapshots: '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.27.0': - optional: true - - '@esbuild/darwin-arm64@0.27.3': - optional: true - '@esbuild/darwin-arm64@0.28.1': optional: true @@ -23565,12 +23140,6 @@ snapshots: '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/darwin-x64@0.27.0': - optional: true - - '@esbuild/darwin-x64@0.27.3': - optional: true - '@esbuild/darwin-x64@0.28.1': optional: true @@ -23583,12 +23152,6 @@ snapshots: '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.27.0': - optional: true - - '@esbuild/freebsd-arm64@0.27.3': - optional: true - '@esbuild/freebsd-arm64@0.28.1': optional: true @@ -23601,12 +23164,6 @@ snapshots: '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.27.0': - optional: true - - '@esbuild/freebsd-x64@0.27.3': - optional: true - '@esbuild/freebsd-x64@0.28.1': optional: true @@ -23619,12 +23176,6 @@ snapshots: '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm64@0.27.0': - optional: true - - '@esbuild/linux-arm64@0.27.3': - optional: true - '@esbuild/linux-arm64@0.28.1': optional: true @@ -23637,12 +23188,6 @@ snapshots: '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-arm@0.27.0': - optional: true - - '@esbuild/linux-arm@0.27.3': - optional: true - '@esbuild/linux-arm@0.28.1': optional: true @@ -23655,12 +23200,6 @@ snapshots: '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-ia32@0.27.0': - optional: true - - '@esbuild/linux-ia32@0.27.3': - optional: true - '@esbuild/linux-ia32@0.28.1': optional: true @@ -23679,12 +23218,6 @@ snapshots: '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-loong64@0.27.0': - optional: true - - '@esbuild/linux-loong64@0.27.3': - optional: true - '@esbuild/linux-loong64@0.28.1': optional: true @@ -23697,12 +23230,6 @@ snapshots: '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-mips64el@0.27.0': - optional: true - - '@esbuild/linux-mips64el@0.27.3': - optional: true - '@esbuild/linux-mips64el@0.28.1': optional: true @@ -23715,12 +23242,6 @@ snapshots: '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-ppc64@0.27.0': - optional: true - - '@esbuild/linux-ppc64@0.27.3': - optional: true - '@esbuild/linux-ppc64@0.28.1': optional: true @@ -23733,12 +23254,6 @@ snapshots: '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.27.0': - optional: true - - '@esbuild/linux-riscv64@0.27.3': - optional: true - '@esbuild/linux-riscv64@0.28.1': optional: true @@ -23751,12 +23266,6 @@ snapshots: '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-s390x@0.27.0': - optional: true - - '@esbuild/linux-s390x@0.27.3': - optional: true - '@esbuild/linux-s390x@0.28.1': optional: true @@ -23769,12 +23278,6 @@ snapshots: '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/linux-x64@0.27.0': - optional: true - - '@esbuild/linux-x64@0.27.3': - optional: true - '@esbuild/linux-x64@0.28.1': optional: true @@ -23784,12 +23287,6 @@ snapshots: '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.27.0': - optional: true - - '@esbuild/netbsd-arm64@0.27.3': - optional: true - '@esbuild/netbsd-arm64@0.28.1': optional: true @@ -23802,12 +23299,6 @@ snapshots: '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.27.0': - optional: true - - '@esbuild/netbsd-x64@0.27.3': - optional: true - '@esbuild/netbsd-x64@0.28.1': optional: true @@ -23817,12 +23308,6 @@ snapshots: '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.27.0': - optional: true - - '@esbuild/openbsd-arm64@0.27.3': - optional: true - '@esbuild/openbsd-arm64@0.28.1': optional: true @@ -23835,12 +23320,6 @@ snapshots: '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.27.0': - optional: true - - '@esbuild/openbsd-x64@0.27.3': - optional: true - '@esbuild/openbsd-x64@0.28.1': optional: true @@ -23850,12 +23329,6 @@ snapshots: '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.27.0': - optional: true - - '@esbuild/openharmony-arm64@0.27.3': - optional: true - '@esbuild/openharmony-arm64@0.28.1': optional: true @@ -23868,12 +23341,6 @@ snapshots: '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/sunos-x64@0.27.0': - optional: true - - '@esbuild/sunos-x64@0.27.3': - optional: true - '@esbuild/sunos-x64@0.28.1': optional: true @@ -23886,12 +23353,6 @@ snapshots: '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-arm64@0.27.0': - optional: true - - '@esbuild/win32-arm64@0.27.3': - optional: true - '@esbuild/win32-arm64@0.28.1': optional: true @@ -23904,12 +23365,6 @@ snapshots: '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-ia32@0.27.0': - optional: true - - '@esbuild/win32-ia32@0.27.3': - optional: true - '@esbuild/win32-ia32@0.28.1': optional: true @@ -23922,12 +23377,6 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@esbuild/win32-x64@0.27.0': - optional: true - - '@esbuild/win32-x64@0.27.3': - optional: true - '@esbuild/win32-x64@0.28.1': optional: true @@ -26431,7 +25880,7 @@ snapshots: '@protobufjs/utf8@1.1.1': {} - '@react-router/dev@7.18.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1))(yaml@2.9.0)': + '@react-router/dev@7.18.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(react-router@7.18.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.1(@types/node@20.19.43)(esbuild@0.28.1)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0))(wrangler@4.156.0(@cloudflare/workers-types@5.20260710.1)(@types/node@20.19.43))(yaml@2.9.0)': dependencies: '@babel/core': 7.29.7(supports-color@10.0.0) '@babel/generator': 7.29.8 @@ -26465,7 +25914,7 @@ snapshots: vite-node: 3.2.4(@types/node@20.19.43)(jiti@2.7.0)(less@4.2.0(supports-color@10.0.0))(lightningcss@1.33.0)(sass@1.72.0)(stylus@0.59.0(supports-color@10.0.0))(supports-color@10.0.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.0) optionalDependencies: typescript: 6.0.3 - wrangler: 4.86.0(@cloudflare/workers-types@4.20260519.1) + wrangler: 4.156.0(@cloudflare/workers-types@5.20260710.1)(@types/node@20.19.43) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -31539,64 +30988,6 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 - esbuild@0.27.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 - - esbuild@0.27.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 - esbuild@0.28.1: optionalDependencies: '@esbuild/aix-ppc64': 0.28.1 @@ -34888,39 +34279,28 @@ snapshots: schema-utils: 4.8.1(webpack@5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3)) webpack: 5.76.1(clean-css@5.2.2)(cssnano@7.1.9(postcss@8.5.31))(csso@5.0.5)(esbuild@0.15.5)(html-minifier-terser@6.1.0)(lightningcss@1.33.0)(postcss@8.4.31)(uglify-js@3.13.3) - miniflare@4.20260124.0: - dependencies: - '@cspotcode/source-map-support': 0.8.1 - sharp: 0.34.5 - undici: 7.18.2 - workerd: 1.20260124.0 - ws: 8.18.0 - youch: 4.1.0-beta.10 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - miniflare@4.20260131.0: + miniflare@4.20260426.0: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.34.5 - undici: 7.18.2 - workerd: 1.20260131.0 + undici: 7.24.8 + workerd: 1.20260426.1 ws: 8.18.0 youch: 4.1.0-beta.10 transitivePeerDependencies: - bufferutil - utf-8-validate - miniflare@4.20260426.0: + miniflare@5.20261023.3-beta(@types/node@20.19.43): dependencies: '@cspotcode/source-map-support': 0.8.1 - sharp: 0.34.5 - undici: 7.24.8 - workerd: 1.20260426.1 - ws: 8.18.0 + sharp: 0.35.5(@types/node@20.19.43) + undici: 7.30.1 + workerd: 1.20261023.1 + ws: 8.21.0 youch: 4.1.0-beta.10 transitivePeerDependencies: + - '@types/node' - bufferutil - utf-8-validate @@ -38988,7 +38368,6 @@ snapshots: '@img/sharp-win32-ia32': 0.35.5 '@img/sharp-win32-x64': 0.35.5 '@types/node': 20.19.43 - optional: true shebang-command@2.0.0: dependencies: @@ -40078,10 +39457,10 @@ snapshots: undici@6.24.1: {} - undici@7.18.2: {} - undici@7.24.8: {} + undici@7.30.1: {} + unenv@1.10.0: dependencies: consola: 3.4.2 @@ -41387,22 +40766,6 @@ snapshots: wordwrap@1.0.0: {} - workerd@1.20260124.0: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260124.0 - '@cloudflare/workerd-darwin-arm64': 1.20260124.0 - '@cloudflare/workerd-linux-64': 1.20260124.0 - '@cloudflare/workerd-linux-arm64': 1.20260124.0 - '@cloudflare/workerd-windows-64': 1.20260124.0 - - workerd@1.20260131.0: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260131.0 - '@cloudflare/workerd-darwin-arm64': 1.20260131.0 - '@cloudflare/workerd-linux-64': 1.20260131.0 - '@cloudflare/workerd-linux-arm64': 1.20260131.0 - '@cloudflare/workerd-windows-64': 1.20260131.0 - workerd@1.20260426.1: optionalDependencies: '@cloudflare/workerd-darwin-64': 1.20260426.1 @@ -41411,58 +40774,69 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20260426.1 '@cloudflare/workerd-windows-64': 1.20260426.1 + workerd@1.20261023.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20261023.1 + '@cloudflare/workerd-darwin-arm64': 1.20261023.1 + '@cloudflare/workerd-linux-64': 1.20261023.1 + '@cloudflare/workerd-linux-arm64': 1.20261023.1 + '@cloudflare/workerd-windows-64': 1.20261023.1 + workerpool@6.5.1: {} workerpool@9.3.4: {} - wrangler@4.61.0(@cloudflare/workers-types@5.20260710.1): + wrangler@4.156.0(@cloudflare/workers-types@4.20250922.0)(@types/node@20.19.43): dependencies: - '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.11.0(unenv@2.0.0-rc.24)(workerd@1.20260124.0) + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261023.1) blake3-wasm: 2.1.5 - esbuild: 0.27.0 - miniflare: 4.20260124.0 + esbuild: 0.28.2 + miniflare: 5.20261023.3-beta(@types/node@20.19.43) path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260124.0 + workerd: 1.20261023.1 optionalDependencies: - '@cloudflare/workers-types': 5.20260710.1 + '@cloudflare/workers-types': 4.20250922.0 fsevents: 2.3.3 transitivePeerDependencies: + - '@types/node' - bufferutil - utf-8-validate - wrangler@4.62.0(@cloudflare/workers-types@4.20250922.0): + wrangler@4.156.0(@cloudflare/workers-types@4.20260519.1)(@types/node@20.19.43): dependencies: - '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.12.0(unenv@2.0.0-rc.24)(workerd@1.20260131.0) + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261023.1) blake3-wasm: 2.1.5 - esbuild: 0.27.0 - miniflare: 4.20260131.0 + esbuild: 0.28.2 + miniflare: 5.20261023.3-beta(@types/node@20.19.43) path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260131.0 + workerd: 1.20261023.1 optionalDependencies: - '@cloudflare/workers-types': 4.20250922.0 + '@cloudflare/workers-types': 4.20260519.1 fsevents: 2.3.3 transitivePeerDependencies: + - '@types/node' - bufferutil - utf-8-validate - wrangler@4.86.0(@cloudflare/workers-types@4.20260519.1): + wrangler@4.156.0(@cloudflare/workers-types@5.20260710.1)(@types/node@20.19.43): dependencies: - '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260426.1) + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261023.1) blake3-wasm: 2.1.5 - esbuild: 0.27.3 - miniflare: 4.20260426.0 + esbuild: 0.28.2 + miniflare: 5.20261023.3-beta(@types/node@20.19.43) path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260426.1 + workerd: 1.20261023.1 optionalDependencies: - '@cloudflare/workers-types': 4.20260519.1 + '@cloudflare/workers-types': 5.20260710.1 fsevents: 2.3.3 transitivePeerDependencies: + - '@types/node' - bufferutil - utf-8-validate @@ -41493,6 +40867,8 @@ snapshots: ws@8.18.0: {} + ws@8.21.0: {} + ws@8.21.3: {} wsl-utils@0.3.1: From bcb43eae9c821bdc4f3bd4ce622316a048f63398 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 11:06:39 +0100 Subject: [PATCH 100/169] chore(deps-dev): Bump redux from 4.0.5 to 5.0.1 (#25959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [redux](https://github.com/reduxjs/redux) from 4.0.5 to 5.0.1.
Release notes

Sourced from redux's releases.

v5.0.1

This patch release adjusts the isPlainObject util to allow objects created via Object.create(null), and fixes a type issue which accidentally made the store state type non-nullable.

What's Changed

Full Changelog: https://github.com/reduxjs/redux/compare/v5.0.0...v5.0.1

v5.0.0

This major release:

  • Converts the codebase to TypeScript
  • Updates the packaging for better ESM/CJS compatibility and modernizes the build output
  • Requires that action.type must be a string
  • Continues to mark createStore as deprecated
  • Deprecates the AnyAction type in favor of an UnknownAction type that is used everywhere
  • Removes the PreloadedState type in favor of a new generic argument for the Reducer type.

This release has breaking changes.

This release is part of a wave of major versions of all the Redux packages: Redux Toolkit 2.0, Redux core 5.0, React-Redux 9.0, Reselect 5.0, and Redux Thunk 3.0.

For full details on all of the breaking changes and other significant changes to all of those packages, see the "Migrating to RTK 2.0 and Redux 5.0" migration guide in the Redux docs.

[!NOTE] The Redux core, Reselect, and Redux Thunk packages are included as part of Redux Toolkit, and RTK users do not need to manually upgrade them - you'll get them as part of the upgrade to RTK 2.0. (If you're not using Redux Toolkit yet, please start migrating your existing legacy Redux code to use Redux Toolkit today!)

# RTK
npm install @reduxjs/toolkit
yarn add @reduxjs/toolkit

Standalone

npm install redux yarn add redux

Changelog

ESM/CJS Package Compatibility

The biggest theme of the Redux v5 and RTK 2.0 releases is trying to get "true" ESM package publishing compatibility in place, while still supporting CJS in the published package.

The primary build artifact is now an ESM file, dist/redux.mjs. Most build tools should pick this up. There's also a CJS artifact, and a second copy of the ESM file named redux.legacy-esm.js to support Webpack 4 (which does not recognize the exports field in package.json). Additionally, all of the build artifacts now live under ./dist/ in the published package.

Modernized Build Output

We now publish modern JS syntax targeting ES2020, including optional chaining, object spread, and other modern syntax. If you need to

... (truncated)

Commits
Maintainer changes

This version was pushed to npm by phryneas, a new releaser for redux since your current version.


Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Andrei <168741329+andreiborza@users.noreply.github.com> --- packages/react/package.json | 2 +- pnpm-lock.yaml | 19 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index ad3180f80c6f..ffb392889d3b 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -89,7 +89,7 @@ "react-router-4": "npm:react-router@4.1.0", "react-router-5": "npm:react-router@5.3.4", "react-router-6": "npm:react-router@6.30.4", - "redux": "^4.0.5" + "redux": "^5.0.1" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ac487eaedde..c3e6aa123a5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1945,8 +1945,8 @@ importers: specifier: npm:react-router@6.30.4 version: react-router@6.30.4(react@18.3.1) redux: - specifier: ^4.0.5 - version: 4.0.5 + specifier: ^5.0.1 + version: 5.0.1 packages/react-router: dependencies: @@ -16604,8 +16604,8 @@ packages: resolution: {integrity: sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==} engines: {node: '>= 18.19.0'} - redux@4.0.5: - resolution: {integrity: sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==} + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} @@ -17734,10 +17734,6 @@ packages: peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 - symbol-observable@1.2.0: - resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==} - engines: {node: '>=0.10.0'} - symbol-observable@4.0.0: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} engines: {node: '>=0.10'} @@ -37504,10 +37500,7 @@ snapshots: - '@node-rs/xxhash' - '@opentelemetry/api' - redux@4.0.5: - dependencies: - loose-envify: 1.4.0 - symbol-observable: 1.2.0 + redux@5.0.1: {} reflect-metadata@0.1.13: {} @@ -38931,8 +38924,6 @@ snapshots: react: 18.3.1 use-sync-external-store: 1.2.2(react@18.3.1) - symbol-observable@1.2.0: {} - symbol-observable@4.0.0: {} symbol-tree@3.2.4: {} From 9403d911a0ff07e333a8aec5dacf43938a5d9c03 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 3 Nov 2026 11:34:51 +0100 Subject: [PATCH 101/169] fix(deps): Bump glob to 14 and drop the direct dependency from react-router (#26014) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** `npm audit` flags `glob@13` and `minimatch@10` in `@sentry/bundler-plugins` and `@sentry/react-router`. Only glob 14 fixes this. This PR takes over #25981 (@renantatsuo) and #25982 (@nicohrubec) with their commits kept, and adds two fixes on top: - React Router now uses `globFiles` from the bundler plugins instead of its own `glob` dependency. - The default `filesToDeleteAfterUpload` pattern used the raw build directory path. On Windows that contains backslashes, which glob treats as escapes, so source maps were never deleted after upload. The path is now converted to forward slashes. The `browser-integration-tests` dev dependency is bumped as well. glob 13 stays in the lockfile through `nx`, `rimraf` and `@vercel/nft`, which are all dev-only. **How to review:** The bump and `globFiles` commits are unchanged from the original PRs. Look at the forward-slash fix in `handleOnBuildEnd.ts` and the new `glob.test.ts` case that runs the upload patterns against the new minimatch 11 matcher. Closes #25969 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: renantatsuo Co-authored-by: Nicolas Hrubec Co-authored-by: GPT-6 Co-authored-by: Claude Fable 5.1 --- .../browser-integration-tests/package.json | 2 +- .../react-router-sourcemaps/assert-build.ts | 8 +++ packages/bundler-plugins/package.json | 2 +- .../bundler-plugins/test/core/glob.test.ts | 9 ++++ packages/react-router/package.json | 1 - .../src/vite/buildEnd/handleOnBuildEnd.ts | 15 +++--- .../vite/buildEnd/handleOnBuildEnd.test.ts | 51 +++++++++++++----- pnpm-lock.yaml | 52 ++++++++++++++++--- 8 files changed, 111 insertions(+), 29 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 416639132e92..000d5840580e 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -77,6 +77,6 @@ "@sentry/rrweb-types": "^2.45.0", "@sentry/wasm": "11.10.0", "@types/node": "^20.19.0", - "glob": "^13.0.6" + "glob": "^14.0.3" } } diff --git a/dev-packages/e2e-tests/test-applications/react-router-sourcemaps/assert-build.ts b/dev-packages/e2e-tests/test-applications/react-router-sourcemaps/assert-build.ts index 2bc4986220d4..ca6acfb5a6aa 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-sourcemaps/assert-build.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-sourcemaps/assert-build.ts @@ -111,4 +111,12 @@ for (const chunk of chunks) { assert.ok(crossCheckedChunks > 0, 'Expected at least one uploaded chunk to cross-check debug IDs against'); console.log(`${crossCheckedChunks} chunk(s) ship a debug ID that was uploaded\n`); +const remainingClientMaps = fs + .readdirSync(CLIENT_ASSETS_DIR, { recursive: true }) + .filter(file => file.endsWith('.map')); +assert.deepEqual(remainingClientMaps, [], 'Expected client source maps to be deleted after upload'); + +const remainingServerMaps = fs.readdirSync('build/server', { recursive: true }).filter(file => file.endsWith('.map')); +assert.ok(remainingServerMaps.length > 0, 'Expected server source maps outside the deletion pattern to be preserved'); + console.log('All react-router source map assertions passed!'); diff --git a/packages/bundler-plugins/package.json b/packages/bundler-plugins/package.json index 3bb53c945278..60df96e18f71 100644 --- a/packages/bundler-plugins/package.json +++ b/packages/bundler-plugins/package.json @@ -117,7 +117,7 @@ "dependencies": { "@sentry/core": "11.10.0", "dotenv": "^17.4.2", - "glob": "^13.0.6", + "glob": "^14.0.3", "magic-string": "~0.30.8 || ^1.0.0", "oxc-parser": "^0.152.0", "sentry": "^0.45.0", diff --git a/packages/bundler-plugins/test/core/glob.test.ts b/packages/bundler-plugins/test/core/glob.test.ts index 74583dd01c4e..ffbc776fecc6 100644 --- a/packages/bundler-plugins/test/core/glob.test.ts +++ b/packages/bundler-plugins/test/core/glob.test.ts @@ -140,6 +140,15 @@ describe('globFiles', () => { expect(result).toEqual([]); }); + it.skipIf(process.platform === 'win32')('matches files with a query or hash suffix', async () => { + const files = await Promise.all([touch('a.js?v=1'), touch('a.js.map#hash'), touch('b.mjs?v=1#hash')]); + await touch('c.css?v=1'); + + const result = await globFiles(JS_AND_MAP_PATTERNS, { root: tmpDir }); + result.sort(); + expect(result).toEqual(files.sort()); + }); + it('works in nested subdirectories', async () => { const files = await Promise.all([touch('src', 'deep', 'a.js'), touch('src', 'deep', 'a.js.map')]); diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 07d576d599b0..a97bb7ee240e 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -64,7 +64,6 @@ "@sentry/server-runtime-injection": "11.10.0", "@sentry/server-utils": "11.10.0", "@sentry/bundler-plugins": "11.10.0", - "glob": "^13.0.6", "sentry": "^0.44.0" }, "devDependencies": { diff --git a/packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts b/packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts index a4cf0403e248..0f83b4553ecb 100644 --- a/packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts +++ b/packages/react-router/src/vite/buildEnd/handleOnBuildEnd.ts @@ -1,8 +1,9 @@ import { rm } from 'node:fs/promises'; +import * as path from 'node:path'; import type { Config } from '@react-router/dev/config'; import { createSentrySDK } from 'sentry'; +import { globFiles } from '@sentry/bundler-plugins/core'; import type { SentryVitePluginOptions } from '@sentry/bundler-plugins/vite'; -import { glob } from 'glob'; import type { SentryReactRouterBuildOptions } from '../types'; type BuildEndHook = NonNullable; @@ -24,6 +25,11 @@ function getSentryConfig(viteConfig: unknown): SentryReactRouterBuildOptions { return (viteConfig as { sentryConfig: SentryReactRouterBuildOptions }).sentryConfig; } +/** Glob patterns need forward slashes, but React Router resolves `buildDirectory` with the platform separator. */ +function toGlobPath(directory: string): string { + return directory.split(path.sep).join('/'); +} + /** * A build end hook that handles Sentry release creation and source map uploads. * It creates a new Sentry release if configured, uploads source maps to Sentry, @@ -123,7 +129,7 @@ export const sentryOnBuildEnd: BuildEndHook = async ({ reactRouterConfig, viteCo // set a default value no option was set if (typeof updatedFilesToDeleteAfterUpload === 'undefined') { - updatedFilesToDeleteAfterUpload = [`${reactRouterConfig.buildDirectory}/**/*.map`]; + updatedFilesToDeleteAfterUpload = [`${toGlobPath(reactRouterConfig.buildDirectory)}/**/*.map`]; debug && // eslint-disable-next-line no-console console.info( @@ -134,10 +140,7 @@ export const sentryOnBuildEnd: BuildEndHook = async ({ reactRouterConfig, viteCo } if (updatedFilesToDeleteAfterUpload) { try { - const filePathsToDelete = await glob(updatedFilesToDeleteAfterUpload, { - absolute: true, - nodir: true, - }); + const filePathsToDelete = await globFiles(updatedFilesToDeleteAfterUpload); if (debug) { filePathsToDelete.forEach(filePathToDelete => { // eslint-disable-next-line no-console diff --git a/packages/react-router/test/vite/buildEnd/handleOnBuildEnd.test.ts b/packages/react-router/test/vite/buildEnd/handleOnBuildEnd.test.ts index 560552ed278d..548fea3b7145 100644 --- a/packages/react-router/test/vite/buildEnd/handleOnBuildEnd.test.ts +++ b/packages/react-router/test/vite/buildEnd/handleOnBuildEnd.test.ts @@ -1,6 +1,7 @@ import { createSentrySDK } from 'sentry'; import { rm } from 'node:fs/promises'; -import { glob } from 'glob'; +import type * as path from 'node:path'; +import { globFiles } from '@sentry/bundler-plugins/core'; import type { ResolvedConfig } from 'vite'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { sentryOnBuildEnd } from '../../../src/vite/buildEnd/handleOnBuildEnd'; @@ -11,7 +12,7 @@ vi.mock('node:fs/promises', () => { const rm = vi.fn().mockResolvedValue(undefined); return { rm, default: { rm } }; }); -vi.mock('glob'); +vi.mock('@sentry/bundler-plugins/core', () => ({ globFiles: vi.fn() })); type TestConfig = ResolvedConfig & { sentryConfig: SentryReactRouterBuildOptions; @@ -62,7 +63,7 @@ describe('sentryOnBuildEnd', () => { vi.mocked(createSentrySDK) .mockReset() .mockReturnValue(mockSentrySdkInstance as unknown as ReturnType); - vi.mocked(glob).mockResolvedValue(['/build/file1.map', '/build/file2.map']); + vi.mocked(globFiles).mockResolvedValue(['/build/file1.map', '/build/file2.map']); vi.mocked(rm).mockResolvedValue(undefined); }); @@ -126,10 +127,7 @@ describe('sentryOnBuildEnd', () => { directory: '/build', release: '1.2.3', }); - expect(glob).toHaveBeenCalledWith(['./build/custom/**/*.map'], { - absolute: true, - nodir: true, - }); + expect(globFiles).toHaveBeenCalledWith(['./build/custom/**/*.map']); }); it('should upload source maps when enabled', async () => { @@ -196,7 +194,7 @@ describe('sentryOnBuildEnd', () => { expect(mockSentrySdkInstance.sourcemap.inject).not.toHaveBeenCalled(); expect(mockSentrySdkInstance.sourcemap.upload).not.toHaveBeenCalled(); - expect(glob).not.toHaveBeenCalled(); + expect(globFiles).not.toHaveBeenCalled(); expect(rm).not.toHaveBeenCalled(); }); @@ -219,7 +217,37 @@ describe('sentryOnBuildEnd', () => { expect(mockSentrySdkInstance.sourcemap.inject).toHaveBeenCalledWith({ directory: '/build' }); expect(mockSentrySdkInstance.sourcemap.upload).not.toHaveBeenCalled(); - expect(glob).not.toHaveBeenCalled(); + expect(globFiles).not.toHaveBeenCalled(); + }); + + it('deletes the source maps in the build directory by default', async () => { + // @ts-expect-error - mocking the React config + await sentryOnBuildEnd(defaultConfig); + + expect(globFiles).toHaveBeenCalledWith(['/build/**/*.map']); + expect(rm).toHaveBeenCalledWith('/build/file1.map', { force: true }); + expect(rm).toHaveBeenCalledWith('/build/file2.map', { force: true }); + }); + + it('builds the default deletion pattern with forward slashes on Windows', async () => { + vi.doMock('node:path', async importOriginal => ({ + ...(await importOriginal()), + sep: '\\', + })); + const { sentryOnBuildEnd: sentryOnBuildEndWin32 } = await import('../../../src/vite/buildEnd/handleOnBuildEnd'); + const { globFiles: globFilesWin32 } = await import('@sentry/bundler-plugins/core'); + vi.mocked(globFilesWin32).mockResolvedValue([]); + + const config = { + ...defaultConfig, + reactRouterConfig: { ...defaultConfig.reactRouterConfig, buildDirectory: 'C:\\app\\build' }, + }; + + // @ts-expect-error - mocking the React config + await sentryOnBuildEndWin32(config); + + expect(globFilesWin32).toHaveBeenCalledWith(['C:/app/build/**/*.map']); + vi.doUnmock('node:path'); }); it('should delete custom files after upload when specified', async () => { @@ -239,10 +267,7 @@ describe('sentryOnBuildEnd', () => { // @ts-expect-error - mocking the React config await sentryOnBuildEnd(config); - expect(glob).toHaveBeenCalledWith('/custom/**/*.map', { - absolute: true, - nodir: true, - }); + expect(globFiles).toHaveBeenCalledWith('/custom/**/*.map'); }); it('should handle errors during release creation gracefully', async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3e6aa123a5d..f73e767d7302 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -344,8 +344,8 @@ importers: specifier: ^20.19.0 version: 20.19.43 glob: - specifier: ^13.0.6 - version: 13.0.6 + specifier: ^14.0.3 + version: 14.0.3 dev-packages/bun-integration-tests: dependencies: @@ -1163,8 +1163,8 @@ importers: specifier: ^17.4.2 version: 17.4.2 glob: - specifier: ^13.0.6 - version: 13.0.6 + specifier: ^14.0.3 + version: 14.0.3 magic-string: specifier: ~0.30.8 || ^1.0.0 version: 0.30.21 @@ -1977,9 +1977,6 @@ importers: '@sentry/server-utils': specifier: 11.10.0 version: link:../server-utils - glob: - specifier: ^13.0.6 - version: 13.0.6 sentry: specifier: ^0.44.0 version: 0.44.0 @@ -5805,6 +5802,9 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} + '@isaacs/nonrecursive-glob-match@1.0.3': + resolution: {integrity: sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==} + '@istanbuljs/load-nyc-config@1.1.0': resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -10169,6 +10169,10 @@ packages: brace-expansion@2.1.7: resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + brace-expansion@5.0.8: resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==} engines: {node: 20 || >=22} @@ -12566,6 +12570,10 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + glob@14.0.3: + resolution: {integrity: sha512-A1vjIwC3qpdCso45Iw127gnp7sPhqgY8lUQhDrArIs+GbmfaaVPFuoyy7WyLT9EPZCRZ7j7aLdwUhQmHWesEow==} + engines: {node: 18 || 20 || >=22} + glob@5.0.15: resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -14422,6 +14430,10 @@ packages: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} + minimatch@11.0.3: + resolution: {integrity: sha512-iny/40PEGkNJQ5/Pv6z3nm6FbRAby32GeZRjCUJk2WnNDbgPSTcdI+DDvLqsgBdD4cD5sMtMheqPPnehCCa8Nw==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -15601,6 +15613,10 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} + path-scurry@3.0.0: + resolution: {integrity: sha512-1pTbQqH1wIwMi/eleQ5leFTTCkzFsPcgXNjwOA8YGGW/W4/kHT3dssE7Z6NH+zuqNLOzOB0uaD61yl7cAcVhgg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -24069,6 +24085,8 @@ snapshots: dependencies: minipass: 7.1.3 + '@isaacs/nonrecursive-glob-match@1.0.3': {} + '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 @@ -29093,6 +29111,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.3 + brace-expansion@5.0.8: dependencies: balanced-match: 4.0.3 @@ -31973,6 +31995,12 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 + glob@14.0.3: + dependencies: + minimatch: 11.0.3 + minipass: 7.1.3 + path-scurry: 3.0.0 + glob@5.0.15: dependencies: inflight: 1.0.6 @@ -34306,6 +34334,11 @@ snapshots: dependencies: brace-expansion: 5.0.8 + minimatch@11.0.3: + dependencies: + '@isaacs/nonrecursive-glob-match': 1.0.3 + brace-expansion: 5.0.12 + minimatch@3.1.5: dependencies: brace-expansion: 1.1.21 @@ -36240,6 +36273,11 @@ snapshots: lru-cache: 11.4.0 minipass: 7.1.3 + path-scurry@3.0.0: + dependencies: + lru-cache: 11.4.0 + minipass: 7.1.3 + path-to-regexp@0.1.12: {} path-to-regexp@1.9.0: From 883abb5d51795f2027d0e412425b86b554a90f19 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 3 Nov 2026 11:45:25 +0100 Subject: [PATCH 102/169] test(aws-serverless): Move Lambda extension setup outside test timeout (#25975) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** Cold module loading and mock setup can consume the test’s five-second timeout before it reaches the assertions. Run that setup in `beforeEach` so the timeout covers the polling behavior being tested. Co-authored-by: OpenAI Codex --- .../test/lambda-extension-index.test.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/aws-serverless/test/lambda-extension-index.test.ts b/packages/aws-serverless/test/lambda-extension-index.test.ts index 0927b1c75469..76c813538ebb 100644 --- a/packages/aws-serverless/test/lambda-extension-index.test.ts +++ b/packages/aws-serverless/test/lambda-extension-index.test.ts @@ -1,9 +1,12 @@ import { afterEach, beforeEach, expect, test, vi } from 'vitest'; -beforeEach(() => { +let extension: Awaited>; + +beforeEach(async () => { vi.resetModules(); vi.useFakeTimers(); vi.setSystemTime(0); + extension = await setupExtension(); }); afterEach(() => { @@ -24,7 +27,7 @@ async function setupExtension() { test.each(['ECONNRESET', 'ECONNREFUSED', 'EPIPE', 'ETIMEDOUT', 'EAI_AGAIN'])( 'recovers from %s without exiting', async code => { - const { next, exit, log } = await setupExtension(); + const { next, exit, log } = extension; next.mockRejectedValueOnce(Object.assign(new Error('Connection failed'), { code })).mockResolvedValueOnce(); const { POLL_RETRY_DELAYS } = await import('../src/lambda-extension/index'); @@ -41,7 +44,7 @@ test.each(['ECONNRESET', 'ECONNREFUSED', 'EPIPE', 'ETIMEDOUT', 'EAI_AGAIN'])( ); test('logs and exits after exhausting consecutive connection retries', async () => { - const { next, exit, log } = await setupExtension(); + const { next, exit, log } = extension; const error = Object.assign(new Error('Connection reset'), { code: 'ECONNRESET' }); const attempts: number[] = []; next.mockImplementation(async () => { @@ -62,7 +65,7 @@ test('logs and exits after exhausting consecutive connection retries', async () }); test('resets the retry budget after a successful poll', async () => { - const { next, exit } = await setupExtension(); + const { next, exit } = extension; const error = Object.assign(new Error('Connection reset'), { code: 'ECONNRESET' }); next .mockRejectedValueOnce(error) @@ -86,7 +89,7 @@ test.each([ new Error('Failed to advance to next event: Container error'), Object.assign(new Error('Invalid URL'), { code: 'ERR_INVALID_URL' }), ])('logs and exits immediately for $message', async error => { - const { next, log, exit } = await setupExtension(); + const { next, log, exit } = extension; next.mockRejectedValue(error); await import('../src/lambda-extension/index'); From 3ced970252c05d5f23a81a978b351873358b1dbb Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 3 Nov 2026 11:45:43 +0100 Subject: [PATCH 103/169] test(effect): Assert error severities without event ordering (#25973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** Error processing can reorder events, so verify each error’s severity without requiring the warning event to arrive before the fatal event. Co-authored-by: OpenAI Codex --- packages/effect/test/errorReporter.test.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/effect/test/errorReporter.test.ts b/packages/effect/test/errorReporter.test.ts index ca5c6b5e97e3..62e6cb346708 100644 --- a/packages/effect/test/errorReporter.test.ts +++ b/packages/effect/test/errorReporter.test.ts @@ -140,10 +140,17 @@ describe.each([ yield* flush; expect(events).toHaveLength(2); - expect(events[0]?.level).toBe('warning'); - expect(events[0]?.exception?.values?.[0]?.type).toBe('RateLimitError'); - expect(events[1]?.level).toBe('fatal'); - expect(events[1]?.exception?.values?.[0]?.type).toBe('FatalError'); + expect( + events.map(event => ({ + type: event.exception?.values?.[0]?.type, + level: event.level, + })), + ).toEqual( + expect.arrayContaining([ + { type: 'RateLimitError', level: 'warning' }, + { type: 'FatalError', level: 'fatal' }, + ]), + ); }).pipe(Effect.provide(makeLayer())), ); From 541a0d4c567061c569ab8d4bdde4c7cd5b8c5e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20de=20Faria?= Date: Tue, 3 Nov 2026 08:45:13 -0300 Subject: [PATCH 104/169] fix(bundler-plugins): upload literal artifact paths (#25356) Treat detected build artifact paths as literal filenames when selecting source maps for upload. This keeps Nitro chunks such as `_libs/_sentry/node+[...].mjs` in the upload set while preserving user-provided asset and ignore globs. The Nuxt source-map assertions now require these chunks to be uploaded. On Windows, eight existing path assertions and the CLI contract suite's cleanup also fail with unmodified code. Fixes #25321 --------- Co-authored-by: Codex Co-authored-by: Martin Sonnberger --- .../nuxt-5-sourcemaps/assert-build.ts | 6 +- .../assert-build.ts | 15 +--- .../src/core/build-plugin-manager.ts | 3 +- .../rollup/literal-artifact-paths.test.ts | 70 +++++++++++++++++++ 4 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 packages/bundler-plugins/test/rollup/literal-artifact-paths.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/assert-build.ts b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/assert-build.ts index 8c64d688098f..4dc1e34c400a 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/assert-build.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/assert-build.ts @@ -3,7 +3,6 @@ import * as fs from 'fs'; import * as path from 'path'; import { assertSourceMapUpload, - findDebugIdsInFilesWithGlobCharacters, findDebugIdsWithConflictingSourcemaps, findFilesWithForeignSourcemaps, findFilesWithMultipleDebugIds, @@ -73,9 +72,6 @@ const outputDirsWithSourceMaps = [ const debugIdsWithoutSourceMap = new Set( outputDirsWithSourceMaps.flatMap(dir => [...debugIdsOfChunksWithoutSourceMap(dir)]), ); -const debugIdsSkippedByUploader = isStaticBuild - ? [] - : findDebugIdsInFilesWithGlobCharacters({ outputDir: SERVER_OUTPUT }); const { requests, bundles, sourcemaps, uploadedDebugIds } = assertSourceMapUpload({ outputDirs: outputDirsWithSourceMaps, @@ -86,7 +82,7 @@ const { requests, bundles, sourcemaps, uploadedDebugIds } = assertSourceMapUploa // Nitro 3 deletes `sourcesContent` from the server maps unless `experimental.sourcemapMinify` is `false`. The module // sets it to `false`, which is the only reason the server marker survives into `sourcesContent`. markers: { 'client source (Vite plugin)': CLIENT_MARKER, 'server source (Rollup plugin)': SERVER_MARKER }, - debugIdsWithoutSourceMap: [...debugIdsWithoutSourceMap, ...debugIdsSkippedByUploader], + debugIdsWithoutSourceMap: [...debugIdsWithoutSourceMap], }); console.log(`Found ${bundles.length} artifact bundle(s)\n`); diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-sourcemaps/assert-build.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react-sourcemaps/assert-build.ts index 2aa44e66f20e..bb92e6eb459b 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react-sourcemaps/assert-build.ts +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-sourcemaps/assert-build.ts @@ -59,16 +59,6 @@ function sourceMapsIn(dir: string): string[] { .map(entry => path.join(entry.parentPath, entry.name)); } -// TODO: The uploader passes the emitted file paths to `glob` as patterns, so it skips a file whose path has glob -// characters, like Nitro's `_libs/@tanstack/react-router+[...].mjs`. Remove this when the uploader matches paths literally. -function debugIdsOfFilesWithGlobCharacters(dir: string): Set { - return new Set( - emittedJsFiles(dir) - .filter(file => /[[\]{}()*?]/.test(path.relative(dir, file))) - .flatMap(readDebugIds), - ); -} - const requests = loadMockServerResults(); // --- The upload reached Sentry --- @@ -125,10 +115,7 @@ for (const outputDir of [CLIENT_OUTPUT, SERVER_OUTPUT]) { const injectedDebugIds = findInjectedDebugIds({ outputDir }); assert.ok(injectedDebugIds.length > 0, `Expected debug IDs to be injected into ${outputDir}`); - const exemptDebugIds = new Set([ - ...debugIdsOfChunksWithoutSourceMap(outputDir), - ...debugIdsOfFilesWithGlobCharacters(outputDir), - ]); + const exemptDebugIds = debugIdsOfChunksWithoutSourceMap(outputDir); const unuploaded = injectedDebugIds.filter(debugId => !uploadedDebugIds.has(debugId) && !exemptDebugIds.has(debugId)); assert.deepEqual(unuploaded, [], `Expected every debug ID in ${outputDir} to have an uploaded source map`); diff --git a/packages/bundler-plugins/src/core/build-plugin-manager.ts b/packages/bundler-plugins/src/core/build-plugin-manager.ts index f4ccb991df5b..a529629e4413 100644 --- a/packages/bundler-plugins/src/core/build-plugin-manager.ts +++ b/packages/bundler-plugins/src/core/build-plugin-manager.ts @@ -4,6 +4,7 @@ import * as dotenv from 'dotenv'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import { escape as escapeGlob } from 'glob'; import { SentryCliAdapter } from './cli'; import type { NormalizedOptions } from './options-mapping'; import { normalizeUserOptions, validateOptions } from './options-mapping'; @@ -708,7 +709,7 @@ export function createSentryBuildPluginManager( logger.debug( 'No `sourcemaps.assets` option provided, falling back to uploading detected build artifacts.', ); - globAssets = buildArtifactPaths; + globAssets = buildArtifactPaths.map(asset => escapeGlob(asset.split(path.sep).join('/'))); } const globResult = await startSpan({ name: 'glob', scope: sentryScope }, async () => diff --git a/packages/bundler-plugins/test/rollup/literal-artifact-paths.test.ts b/packages/bundler-plugins/test/rollup/literal-artifact-paths.test.ts new file mode 100644 index 000000000000..b1434b349133 --- /dev/null +++ b/packages/bundler-plugins/test/rollup/literal-artifact-paths.test.ts @@ -0,0 +1,70 @@ +import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, sep } from 'node:path'; +import { rollup } from 'rollup'; +import { afterEach, beforeEach, expect, test, vi } from 'vitest'; +import { sentryRollupPlugin } from '../../src/rollup'; + +const upload = vi.hoisted(() => vi.fn()); +const fixtures: string[] = []; +vi.mock('sentry', () => ({ + createSentrySDK: () => ({ + sourcemap: { upload }, + release: {}, + }), +})); + +beforeEach(() => { + upload.mockReset(); +}); + +afterEach(() => { + for (const fixture of fixtures.splice(0)) { + rmSync(fixture, { recursive: true, force: true }); + } +}); + +test.each([ + { name: 'ordinary filenames', file: 'sdk.mjs', explicitAssets: false, nativePath: false }, + { name: 'glob characters in detected paths', file: 'sdk+[...].mjs', explicitAssets: false, nativePath: false }, + { name: 'explicit asset globs', file: 'sdk+[...].mjs', explicitAssets: true, nativePath: false }, + { name: 'native path separators', file: 'sdk+[...].mjs', explicitAssets: false, nativePath: true }, + { name: 'ignored assets', file: 'sdk+[...].mjs', explicitAssets: false, nativePath: false, ignored: true }, +])('handles $name when uploading Rollup artifacts', async ({ file, explicitAssets, nativePath, ignored }) => { + const fixture = mkdtempSync(join(tmpdir(), 'sentry-rollup-literal-paths-')); + fixtures.push(fixture); + const input = join(fixture, 'input.js'); + const output = join(fixture, 'dist', '_libs', file); + writeFileSync(input, 'export function example() { throw new Error("original fixture"); }\n'); + const uploadedFiles: string[] = []; + upload.mockImplementation(async (options: { directory: string }) => { + uploadedFiles.push(...readdirSync(options.directory)); + }); + + const bundle = await rollup({ + input, + plugins: sentryRollupPlugin({ + authToken: 'local-fixture-only', + org: 'local-fixture', + project: 'local-fixture', + telemetry: false, + silent: true, + release: { create: false, finalize: false }, + sourcemaps: { + assets: explicitAssets ? join(fixture, 'dist', '**', '*.mjs').split(sep).join('/') : undefined, + ignore: ignored ? join(fixture, 'dist', '**', 'sdk+*.mjs').split(sep).join('/') : undefined, + }, + }), + }); + try { + await bundle.write({ file: nativePath ? output : output.replace(/\\/g, '/'), format: 'es', sourcemap: true }); + } finally { + await bundle.close(); + } + + expect(existsSync(output)).toBe(true); + expect(existsSync(`${output}.map`)).toBe(true); + expect(readFileSync(output, 'utf8')).toContain('sentry-dbid-'); + expect(uploadedFiles.filter(name => name.endsWith('.js'))).toHaveLength(ignored ? 0 : 1); + expect(uploadedFiles.filter(name => name.endsWith('.js.map'))).toHaveLength(ignored ? 0 : 1); +}); From 8d6b875ea6ee83ec4d22f3063eb07f5359a3acef Mon Sep 17 00:00:00 2001 From: "javascript-sdk-gitflow[bot]" <255134079+javascript-sdk-gitflow[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 12:46:53 +0100 Subject: [PATCH 105/169] chore: Add external contributor to CHANGELOG.md (#26017) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #25356 Co-authored-by: msonnb <26038247+msonnb@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efb65251493..f23f10319b80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @lazerg, @chiliec, and @61465. Thank you for your contributions! +Work in this release was contributed by @lazerg, @chiliec, @61465, and @jpdefo. Thank you for your contributions! ### Important Changes From ec88aef26663de83ece9d17588d7d0e297429abb Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Tue, 3 Nov 2026 13:03:42 +0100 Subject: [PATCH 106/169] test(e2e): Point together-ai 1.x at OpenRouter's base URL in gen-ai-libraries (#25947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Why:** The `gen-ai-libraries (latest)` variant installs `together-ai@latest`, which is now 1.0.0. That release moved `/v1` from the SDK's default base URL into the request paths, so with our `https://openrouter.ai/api/v1` base URL it requests `/api/v1/v1/chat/completions` and both Together cases fail with a 500 (e.g. on #25940). Against a local server, 0.54.0 hits `/api/v1/chat/completions` with the `/v1` base and 1.0.0 hits `/api/v1/v1/chat/completions`; with the base URL minus `/v1`, 1.0.0 hits the right path. The default variant keeps 0.54.0, so the client picks the base URL from `together-ai/version`, which both versions export. Verified against a local server for both versions, and the import and expression type-check against both. **How to review:** One line in `src/libraries.ts`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- .../test-applications/gen-ai-libraries/src/libraries.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/libraries.ts b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/libraries.ts index dd002c976962..f8497fce7a33 100644 --- a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/libraries.ts +++ b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/src/libraries.ts @@ -21,6 +21,7 @@ import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { Mistral } from '@mistralai/mistralai'; import OpenAI from 'openai'; import Together from 'together-ai'; +import { VERSION as TOGETHER_VERSION } from 'together-ai/version'; import { generateText, tool } from 'ai'; import { z } from 'zod'; @@ -171,7 +172,9 @@ export const libraries: Library[] = [ 'together', 'together_ai', 'auto.ai.together_ai', - apiKey => new Together({ apiKey, baseURL: OPENROUTER_V1 }) as any, + // together-ai 1.0 moved `/v1` from its base URL into the request paths. + apiKey => + new Together({ apiKey, baseURL: TOGETHER_VERSION.startsWith('0.') ? OPENROUTER_V1 : OPENROUTER_BASE }) as any, ), { From 9ace99a55936b8eed3ad6480f95be00310311978 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 3 Nov 2026 13:24:51 +0100 Subject: [PATCH 107/169] ref(server-utils): Clarify PostgreSQL instrumentation filenames (#25939) **Why:** The current filenames blur the distinction between the `pg` driver, postgres.js automatic instrumentation, and its portable wrapper. **How to review:** File moves and import updates only. Happy to take suggestions for different naming, but the existing naming is a bit confusing imo. Co-authored-by: GPT-6 --- packages/server-utils/src/exports.ts | 4 +- packages/server-utils/src/index.ts | 4 +- .../server-utils/src/integrations/index.ts | 4 +- .../server-utils/src/integrations/neon.ts | 2 +- .../src/integrations/{postgres.ts => pg.ts} | 0 .../src/integrations/postgres-js.ts | 349 ---------- .../src/integrations/postgresjs-wrapper.ts | 424 ++++++++++++ .../src/integrations/postgresjs.ts | 635 ++++++++---------- .../pg-ignore-connect.test.ts} | 4 +- .../postgres.test.ts => pg/pg.test.ts} | 2 +- ...sjs.test.ts => postgresjs-wrapper.test.ts} | 2 +- 11 files changed, 715 insertions(+), 715 deletions(-) rename packages/server-utils/src/integrations/{postgres.ts => pg.ts} (100%) delete mode 100644 packages/server-utils/src/integrations/postgres-js.ts create mode 100644 packages/server-utils/src/integrations/postgresjs-wrapper.ts rename packages/server-utils/test/integrations/{postgres/postgres-ignore-connect.test.ts => pg/pg-ignore-connect.test.ts} (98%) rename packages/server-utils/test/integrations/{postgres/postgres.test.ts => pg/pg.test.ts} (99%) rename packages/server-utils/test/integrations/{postgresjs.test.ts => postgresjs-wrapper.test.ts} (99%) diff --git a/packages/server-utils/src/exports.ts b/packages/server-utils/src/exports.ts index 8cd8bf657c95..02c2b1393338 100644 --- a/packages/server-utils/src/exports.ts +++ b/packages/server-utils/src/exports.ts @@ -5,8 +5,8 @@ export { openTelemetryIntegration, getOtlpTracesEndpoint } from './opentelemetry export * from './ai'; export { getSqlQuerySummary, sanitizeSqlQuery, getSqlQueryAttributes } from './utils/sql'; export type { SqlDialect } from './utils/sql'; -export { instrumentPostgresJsSql } from './integrations/postgresjs'; -export type { PostgresConnectionContext } from './integrations/postgresjs'; +export { instrumentPostgresJsSql } from './integrations/postgresjs-wrapper'; +export type { PostgresConnectionContext } from './integrations/postgresjs-wrapper'; // Shared, runtime-agnostic Hono instrumentation re-used by the `@sentry/hono` SDK across all runtimes. // Sourced directly from their modules (not the `./integrations/hono` barrel, which also exports the diff --git a/packages/server-utils/src/index.ts b/packages/server-utils/src/index.ts index 005e8a4a8b79..cb666c2fc181 100644 --- a/packages/server-utils/src/index.ts +++ b/packages/server-utils/src/index.ts @@ -70,8 +70,8 @@ export { typesafeIntegration } from './integrations/typesafe'; export { mysqlIntegration } from './integrations/mysql'; export { mysql2Integration } from './integrations/mysql2'; export { openAIIntegration } from './integrations/openai'; -export { postgresIntegration } from './integrations/postgres'; -export { postgresJsIntegration } from './integrations/postgres-js'; +export { postgresIntegration } from './integrations/pg'; +export { postgresJsIntegration } from './integrations/postgresjs'; export { neonIntegration } from './integrations/neon'; export { tediousIntegration } from './integrations/tedious'; export { vercelAIIntegration } from './integrations/vercel-ai'; diff --git a/packages/server-utils/src/integrations/index.ts b/packages/server-utils/src/integrations/index.ts index 9643f95250ff..e70488a63351 100644 --- a/packages/server-utils/src/integrations/index.ts +++ b/packages/server-utils/src/integrations/index.ts @@ -6,7 +6,7 @@ import { graphqlIntegration } from './graphql'; import { redisIntegration } from './redis'; import { mysqlIntegration } from './mysql'; import { mysql2Integration } from './mysql2'; -import { postgresIntegration } from './postgres'; +import { postgresIntegration } from './pg'; import { prismaIntegration } from './prisma'; import { tediousIntegration } from './tedious'; import { genericPoolIntegration } from './generic-pool'; @@ -26,7 +26,7 @@ import { mistralAIIntegration } from './mistral'; import { groqIntegration } from './groq'; import { togetherAIIntegration } from './together-ai'; import { typesafeIntegration } from './typesafe'; -import { postgresJsIntegration } from './postgres-js'; +import { postgresJsIntegration } from './postgresjs'; import { neonIntegration } from './neon'; import { firebaseIntegration } from './firebase'; import { expressIntegration } from './express'; diff --git a/packages/server-utils/src/integrations/neon.ts b/packages/server-utils/src/integrations/neon.ts index 3a33aef801d7..1dd3eeceb468 100644 --- a/packages/server-utils/src/integrations/neon.ts +++ b/packages/server-utils/src/integrations/neon.ts @@ -17,7 +17,7 @@ import { CHANNELS } from '../orchestrion/channels'; import { neonModuleNames } from '../orchestrion/config/neon'; import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation'; import { bindTracingChannelToSpan } from '../tracing-channel'; -import { bindPgQueryChannel, getConnectionAttributes } from './postgres'; +import { bindPgQueryChannel, getConnectionAttributes } from './pg'; const INTEGRATION_NAME = 'Neon' as const; const ORIGIN = 'auto.db.neon'; diff --git a/packages/server-utils/src/integrations/postgres.ts b/packages/server-utils/src/integrations/pg.ts similarity index 100% rename from packages/server-utils/src/integrations/postgres.ts rename to packages/server-utils/src/integrations/pg.ts diff --git a/packages/server-utils/src/integrations/postgres-js.ts b/packages/server-utils/src/integrations/postgres-js.ts deleted file mode 100644 index ab595d277c47..000000000000 --- a/packages/server-utils/src/integrations/postgres-js.ts +++ /dev/null @@ -1,349 +0,0 @@ -import * as diagnosticsChannel from '../utils/diagnosticsChannel'; -import { - DB_OPERATION_NAME, - DB_QUERY_SUMMARY, - DB_QUERY_TEXT, - DB_SYSTEM_NAME, - ERROR_TYPE, - SENTRY_KIND, - SENTRY_OP, - SENTRY_ORIGIN, - DB_RESPONSE_STATUS_CODE, -} from '@sentry/conventions/attributes'; -import { DB } from '@sentry/conventions/op'; -import type { IntegrationFn, Span } from '@sentry/core'; -import { - debug, - defineIntegration, - getClient, - hasSpanStreamingEnabled, - SPAN_STATUS_ERROR, - startInactiveSpan, -} from '@sentry/core'; -import { DEBUG_BUILD } from '../debug-build'; -import { getSqlQueryAttributes } from '../utils/sql'; -import { - _buildConnectionContext, - _getConnectionAttributes, - _reconstructQuery, - type PostgresConnectionContext, -} from './postgresjs'; -import type { OrchestrionChannelContext } from '../orchestrion/types'; -import { CHANNELS } from '../orchestrion/channels'; -import { bindTracingChannelToSpan } from '../tracing-channel'; -import { postgresJsModuleNames } from '../orchestrion/config/postgres'; -import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation'; - -// Same name as the OTel `PostgresJs` integration by design, so the OTel -// integration of the same name is deduplicated out of the default set. -const INTEGRATION_NAME = 'PostgresJs' as const; - -const ORIGIN = 'auto.db.postgresjs'; - -const DB_SYSTEM_NAME_POSTGRES = 'postgres'; - -const NOOP = (): void => {}; - -// Same `Symbol.for()` marker the core `instrumentPostgresJsSql` wrapper sets on -// queries it manually instruments, so we skip them there and never double-span. -const QUERY_FROM_INSTRUMENTED_SQL = Symbol.for('sentry.query.from.instrumented.sql'); -// The query span, stashed on the `Query` so the `execute`/`connect` channels can -// attach connection attributes to it. -const QUERY_SPAN = Symbol('sentryPostgresJsSpan'); -// Set once connection attributes are on the span, so the fallback and the -// `execute`/`connect` channels don't both write them. -const CONNECTION_ATTRS_SET = Symbol('sentryPostgresJsConnectionAttrsSet'); -// Set on the channel context once the resolve/reject wrappers have ended the -// span, so `deferSpanEnd` knows the wrappers own the lifecycle. -const SPAN_ENDED = Symbol('sentryPostgresJsSpanEnded'); - -export interface PostgresJsIntegrationOptions { - /** - * Only create spans when there's already an active parent span. Defaults to - * `true`, matching the OTel `postgresJsIntegration`. - */ - requireParentSpan?: boolean; - /** - * Hook to modify the query span before the query runs. Receives the span, the - * sanitized SQL, and (when resolvable) the connection context. - */ - requestHook?: (span: Span, sanitizedSqlQuery: string, postgresConnectionContext?: PostgresConnectionContext) => void; -} - -/** The `Query` instance postgres.js passes as `self` to `Query.prototype.handle`. */ -interface PostgresQuery { - strings?: string[]; - executed?: boolean; - resolve?: (...args: unknown[]) => unknown; - reject?: (...args: unknown[]) => unknown; -} - -interface PostgresJsQueryContext extends OrchestrionChannelContext { - self?: PostgresQuery; - [SPAN_ENDED]?: boolean; -} - -// A connection object -> its resolved context, populated on the `connection` -// channel and read on the `execute`/`connect` channels (keyed by the same object). -const connectionContexts = new WeakMap(); -// Distinct endpoints seen so far (value-compared, so N connections to one DB -// count once). When exactly one endpoint exists — the common case, and the only -// one the tests exercise — every query resolves to it at handle-start. -const endpointRegistry: PostgresConnectionContext[] = []; - -function registerEndpoint(context: PostgresConnectionContext): void { - const alreadyKnown = endpointRegistry.some( - e => - e.ATTR_SERVER_ADDRESS === context.ATTR_SERVER_ADDRESS && - e.ATTR_SERVER_PORT === context.ATTR_SERVER_PORT && - e.ATTR_DB_NAMESPACE === context.ATTR_DB_NAMESPACE, - ); - if (!alreadyKnown) { - endpointRegistry.push(context); - } -} - -/** The single known endpoint, or `undefined` when zero or multiple are known. */ -function resolveSingleEndpoint(): PostgresConnectionContext | undefined { - return endpointRegistry.length === 1 ? endpointRegistry[0] : undefined; -} - -/** - * Record a connection from the `connection` channel `end` (`result` is the - * connection object, `arguments[0]` the parsed options), keying its resolved - * context by the connection object and tracking its endpoint. - */ -function recordConnectionFromChannel(message: PostgresJsQueryContext): void { - const connection = message.result; - const options = message.arguments?.[0] as { host?: string[]; port?: number[]; database?: string } | undefined; - if (!connection || typeof connection !== 'object' || !options) { - return; - } - const context = _buildConnectionContext(options); - connectionContexts.set(connection, context); - registerEndpoint(context); -} - -function setConnectionAttributes(span: Span, query: PostgresQuery, context: PostgresConnectionContext): void { - const queryRecord = query as Record; - if (queryRecord[CONNECTION_ATTRS_SET]) { - return; - } - queryRecord[CONNECTION_ATTRS_SET] = true; - if (context) { - span.setAttributes(_getConnectionAttributes(context)); - } -} - -/** - * Backfill connection attributes onto a query's span from a channel whose `self` - * is the connection object and `arguments[0]` the query. Shared by the `execute` - * and `connect` channels; both carry that shape and both resolve the context via - * the `connectionContexts` WeakMap. Idempotent (guarded inside - * `setConnectionAttributes`). - */ -function attachConnectionAttributesFromChannel(message: PostgresJsQueryContext): void { - const connection = message.self; - const query = message.arguments?.[0] as PostgresQuery | undefined; - if (!connection || !query) { - return; - } - const span = (query as Record)[QUERY_SPAN] as Span | undefined; - const context = connectionContexts.get(connection); - if (span && context) { - setConnectionAttributes(span, query, context); - } -} - -/** - * Wrap `query.resolve`/`query.reject` so the span ends when the query settles. - * - * `Query extends Promise` and `async handle()` only dispatches — its promise - * resolves immediately, long before the query completes. postgres.js signals - * completion by calling `this.resolve`/`this.reject`, so we own the span end - * there. Wrapping happens at handle-start because `reject` can fire - * synchronously during dispatch and `cursor()` reassigns both before executing. - */ -function wrapQuerySettlement(data: PostgresJsQueryContext, span: Span, operationName: string | undefined): void { - const query = data.self; - if (!query) { - return; - } - - // Claim ownership of ending the span up front, so `deferSpanEnd` defers to the - // wrapper even if `span.end()` below throws. - const markEnded = (): void => { - data[SPAN_ENDED] = true; - }; - - const originalResolve = query.resolve; - if (typeof originalResolve === 'function') { - query.resolve = function (this: unknown, ...resolveArgs: unknown[]): unknown { - markEnded(); - try { - const command = (resolveArgs[0] as { command?: string } | undefined)?.command; - // Re-set the operation name with the server-reported command, which is more reliable than the query text. - span.setAttribute(DB_OPERATION_NAME, command || operationName); - span.end(); - } catch (e) { - DEBUG_BUILD && debug.error('[instrumentation:postgresjs] error ending span in resolve:', e); - } - return originalResolve.apply(this, resolveArgs); - }; - } - - const originalReject = query.reject; - if (typeof originalReject === 'function') { - query.reject = function (this: unknown, ...rejectArgs: unknown[]): unknown { - markEnded(); - try { - const err = rejectArgs[0] as { message?: string; code?: string; name?: string } | undefined; - span.setStatus({ code: SPAN_STATUS_ERROR, message: err?.message || 'unknown_error' }); - span.setAttribute(DB_RESPONSE_STATUS_CODE, err?.code || 'unknown'); - span.setAttribute(ERROR_TYPE, err?.name || 'unknown'); - span.end(); - } catch (e) { - DEBUG_BUILD && debug.error('[instrumentation:postgresjs] error ending span in reject:', e); - } - return originalReject.apply(this, rejectArgs); - }; - } -} - -const _postgresJsIntegration = ((options: PostgresJsIntegrationOptions = {}) => { - return { - name: INTEGRATION_NAME, - setup(client) { - invokeOrchestrionInstrumentation(client, postgresJsModuleNames, instrumentPostgresJs, [options]); - }, - }; -}) satisfies IntegrationFn; - -function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void { - const { requireParentSpan, requestHook } = options; - - // Connection + execute are pure observers (no span, no async binding). - diagnosticsChannel.tracingChannel(CHANNELS.POSTGRESJS_CONNECTION).subscribe({ - start: NOOP, - asyncStart: NOOP, - asyncEnd: NOOP, - error: NOOP, - end: recordConnectionFromChannel, - }); - - // Per-connection attributes for queries reusing an already-open connection - // (`c.execute(q)`, `self === c`). `execute` is also called bare - // (`self === undefined`) for the first query on each connection, `fetchState` - // and `retry`; those miss here (the `connect` channel below covers the first - // user query, and the single-endpoint fallback covers the common case). - diagnosticsChannel.tracingChannel(CHANNELS.POSTGRESJS_EXECUTE).subscribe({ - end: NOOP, - asyncStart: NOOP, - asyncEnd: NOOP, - error: NOOP, - start: attachConnectionAttributesFromChannel, - }); - - // The connection's `connect(query)` method (`self === c`, `arguments[0]` the - // query) fires when a fresh connection is opened for a query. That first query - // is later dispatched via a bare `execute` (no `self`), so this is where it - // gets its connection attributes in multi-endpoint apps. - diagnosticsChannel.tracingChannel(CHANNELS.POSTGRESJS_CONNECT).subscribe({ - end: NOOP, - asyncStart: NOOP, - asyncEnd: NOOP, - error: NOOP, - start: attachConnectionAttributesFromChannel, - }); - - bindTracingChannelToSpan( - diagnosticsChannel.tracingChannel(CHANNELS.POSTGRESJS_HANDLE), - data => { - const query = data.self; - if (!query) { - return undefined; - } - - // Opt out of re-entrant `handle()` calls (then/catch/finally re-invoke - // it, guarded by `executed`) and queries already wrapped by the portable - // `instrumentPostgresJsSql`. - if (query.executed === true || (query as Record)[QUERY_FROM_INSTRUMENTED_SQL]) { - return undefined; - } - - const queryAttributes = getSqlQueryAttributes(_reconstructQuery(query.strings)); - const sanitizedSqlQuery = queryAttributes[DB_QUERY_TEXT] ?? ''; - - const client = getClient(); - - // Single-endpoint fallback: resolve context now so the span name and `requestHook` have - // it, and the first-query-per-connection (bare `execute`) path still gets attrs. - const context = resolveSingleEndpoint(); - - const name = - client && hasSpanStreamingEnabled(client) - ? queryAttributes[DB_QUERY_SUMMARY] || context?.ATTR_DB_NAMESPACE || DB_SYSTEM_NAME_POSTGRES - : sanitizedSqlQuery || 'postgresjs.query'; - - // `sentry.kind: client` matches the mysql/pg channel subscribers. - const span = startInactiveSpan({ - name, - onlyIfParent: requireParentSpan !== false, - attributes: { - [SENTRY_OP]: DB, - [SENTRY_KIND]: 'client', - [SENTRY_ORIGIN]: ORIGIN, - [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_POSTGRES, - ...queryAttributes, - }, - }); - - // Stash for the `execute`/`connect` channels to attach per-connection attributes. - (query as Record)[QUERY_SPAN] = span; - - if (context) { - setConnectionAttributes(span, query, context); - } - - if (requestHook) { - try { - requestHook(span, sanitizedSqlQuery, context); - } catch (e) { - span.setAttribute('sentry.hook.error', 'requestHook failed'); - DEBUG_BUILD && debug.error('[instrumentation:postgresjs] error in requestHook:', e); - } - } - - wrapQuerySettlement(data, span, queryAttributes[DB_OPERATION_NAME]); - - return span; - }, - { - deferSpanEnd({ data }) { - // `handle` is async: its promise settles on dispatch (asyncEnd), long - // before the query does. The resolve/reject wrappers own the ending. - if (data[SPAN_ENDED]) { - return true; // wrappers already ended it - } - if ('error' in data) { - return false; // `handle()` itself threw; the error subscriber annotated the span, let the helper end it - } - // NOTE: for a cursor consumed as an async iterator, only the first batch - // reaches `handle` (the `executed` guard blocks the rest), so the span - // ends on the first batch — a pre-existing flaw kept for parity. - return true; // query in flight; the wrappers will end the span when it settles - }, - }, - ); -} - -/** - * Diagnostics-channel-based postgres.js (`postgres` v3.x) integration. - * - * Subscribes to the `orchestrion:postgres:handle` / `:connection` / `:execute` / - * `:connect` diagnostics channels injected into postgres.js' `Query.prototype.handle` - * and `Connection`/`execute`/`connect` (in `src/*` and `cjs/src/*`) and creates db - * spans matching the OTel `postgresJsIntegration`. Requires the Sentry runtime - * hook or bundler plugin. - */ -export const postgresJsIntegration = defineIntegration(_postgresJsIntegration); diff --git a/packages/server-utils/src/integrations/postgresjs-wrapper.ts b/packages/server-utils/src/integrations/postgresjs-wrapper.ts new file mode 100644 index 000000000000..3b42f0c80f50 --- /dev/null +++ b/packages/server-utils/src/integrations/postgresjs-wrapper.ts @@ -0,0 +1,424 @@ +// Portable instrumentation for https://github.com/porsager/postgres +// This can be used in any environment (Node.js, Cloudflare Workers, etc.) +// without depending on OpenTelemetry module hooking. +/* eslint-disable max-lines */ + +import { + debug, + getActiveSpan, + getClient, + hasSpanStreamingEnabled, + isObjectLike, + type Span, + type SpanAttributes, + SPAN_STATUS_ERROR, + startSpanManual, +} from '@sentry/core'; +import { DEBUG_BUILD } from '../debug-build'; +import { getSqlQueryAttributes } from '../utils/sql'; +import { + DB_NAMESPACE, + DB_OPERATION_NAME, + DB_QUERY_SUMMARY, + DB_QUERY_TEXT, + DB_SYSTEM_NAME, + SENTRY_OP, + SENTRY_ORIGIN, + SERVER_ADDRESS, + SERVER_PORT, + DB_RESPONSE_STATUS_CODE, + ERROR_TYPE, +} from '@sentry/conventions/attributes'; +import { DB } from '@sentry/conventions/op'; + +export type PostgresConnectionContext = { + ATTR_DB_NAMESPACE?: string; + ATTR_SERVER_ADDRESS?: string; + ATTR_SERVER_PORT?: string; +}; + +interface PostgresJsSqlInstrumentationOptions { + /** + * Whether to require a parent span for the instrumentation. + * If set to true, the instrumentation will only create spans if there is a parent span + * available in the current scope. + * @default true + */ + requireParentSpan?: boolean; + /** + * Hook to modify the span before it is started. + * This can be used to set additional attributes or modify the span in any way. + */ + requestHook?: (span: Span, sanitizedSqlQuery: string, postgresConnectionContext?: PostgresConnectionContext) => void; +} + +const CONNECTION_CONTEXT_SYMBOL = Symbol('sentryPostgresConnectionContext'); + +// Use the same Symbol.for() markers as the Node.js OTel instrumentation +// so that both approaches recognize each other and prevent double-wrapping. +const INSTRUMENTED_MARKER = Symbol.for('sentry.instrumented.postgresjs'); +// Marker to track if a query was created from an instrumented sql instance. +// This prevents double-spanning when both the wrapper and the Node.js Query.prototype +// fallback patch are active simultaneously. +const QUERY_FROM_INSTRUMENTED_SQL = Symbol.for('sentry.query.from.instrumented.sql'); + +/** + * Instruments a postgres.js `sql` instance with Sentry tracing. + * + * This is a portable instrumentation function that works in any environment + * (Node.js, Cloudflare Workers, etc.) without depending on OpenTelemetry. + * + * @example + * ```javascript + * import postgres from 'postgres'; + * import * as Sentry from '@sentry/cloudflare'; // or '@sentry/deno' + * + * const sql = Sentry.instrumentPostgresJsSql( + * postgres({ host: 'localhost', database: 'mydb' }) + * ); + * + * // All queries now create Sentry spans + * await sql`SELECT * FROM users WHERE id = ${userId}`; + * ``` + */ +export function instrumentPostgresJsSql(sql: T, options?: PostgresJsSqlInstrumentationOptions): T { + if (!sql || typeof sql !== 'function') { + DEBUG_BUILD && debug.warn('instrumentPostgresJsSql: provided value is not a valid postgres.js sql instance'); + return sql; + } + + return _instrumentSqlInstance(sql, { requireParentSpan: true, ...options }) as T; +} + +/** + * Instruments a sql instance by wrapping its query execution methods. + */ +function _instrumentSqlInstance( + sql: unknown, + options: PostgresJsSqlInstrumentationOptions, + parentConnectionContext?: PostgresConnectionContext, +): unknown { + // Check if already instrumented to prevent double-wrapping + // Using Symbol.for() ensures the marker survives proxying + if ((sql as Record)[INSTRUMENTED_MARKER]) { + return sql; + } + + // Wrap the sql function to intercept query creation + const proxiedSql: unknown = new Proxy(sql as (...args: unknown[]) => unknown, { + apply(target, thisArg, argumentsList: unknown[]) { + const query = Reflect.apply(target, thisArg, argumentsList); + + if (isObjectLike(query) && 'handle' in query) { + _wrapSingleQueryHandle(query as { handle: unknown; strings?: string[] }, proxiedSql, options); + } + + return query; + }, + get(target, prop) { + const original = (target as unknown as Record)[prop]; + + if (typeof prop !== 'string' || typeof original !== 'function') { + return original; + } + + // Wrap methods that return PendingQuery objects (unsafe, file) + if (prop === 'unsafe' || prop === 'file') { + return _wrapQueryMethod(original as (...args: unknown[]) => unknown, target, proxiedSql, options); + } + + // Wrap begin and reserve (not savepoint to avoid duplicate spans) + if (prop === 'begin' || prop === 'reserve') { + return _wrapCallbackMethod(original as (...args: unknown[]) => unknown, target, proxiedSql, options); + } + + return original; + }, + }); + + // Use provided parent context if available, otherwise extract from sql.options + if (parentConnectionContext) { + (proxiedSql as Record)[CONNECTION_CONTEXT_SYMBOL] = parentConnectionContext; + } else { + _attachConnectionContext(sql, proxiedSql as Record); + } + + // Mark both the original and proxy as instrumented to prevent double-wrapping + (sql as Record)[INSTRUMENTED_MARKER] = true; + (proxiedSql as Record)[INSTRUMENTED_MARKER] = true; + + return proxiedSql; +} + +/** + * Wraps query-returning methods (unsafe, file) to ensure their queries are instrumented. + */ +function _wrapQueryMethod( + original: (...args: unknown[]) => unknown, + target: unknown, + proxiedSql: unknown, + options: PostgresJsSqlInstrumentationOptions, +): (...args: unknown[]) => unknown { + return function (this: unknown, ...args: unknown[]): unknown { + const query = Reflect.apply(original, target, args); + + if (isObjectLike(query) && 'handle' in query) { + _wrapSingleQueryHandle(query as { handle: unknown; strings?: string[] }, proxiedSql, options); + } + + return query; + }; +} + +/** + * Wraps callback-based methods (begin, reserve) to recursively instrument Sql instances. + * Note: These methods can also be used as tagged templates, which we pass through unchanged. + * + * Savepoint is not wrapped to avoid complex nested transaction instrumentation issues. + * Queries within savepoint callbacks are still instrumented through the parent transaction's Sql instance. + */ +function _wrapCallbackMethod( + original: (...args: unknown[]) => unknown, + target: unknown, + parentSqlInstance: unknown, + options: PostgresJsSqlInstrumentationOptions, +): (...args: unknown[]) => unknown { + return function (this: unknown, ...args: unknown[]): unknown { + // Extract parent context to propagate to child instances + const parentContext = (parentSqlInstance as Record)[CONNECTION_CONTEXT_SYMBOL] as + | PostgresConnectionContext + | undefined; + + // Check if this is a callback-based call by verifying the last argument is a function + const isCallbackBased = typeof args[args.length - 1] === 'function'; + + if (!isCallbackBased) { + // Not a callback-based call - could be tagged template or promise-based + const result = Reflect.apply(original, target, args); + // If result is a Promise (e.g., reserve() without callback), instrument the resolved Sql instance + if (result && typeof (result as Promise).then === 'function') { + return (result as Promise).then((sqlInstance: unknown) => { + return _instrumentSqlInstance(sqlInstance, options, parentContext); + }); + } + return result; + } + + // Callback-based call: wrap the callback to instrument the Sql instance + const callback = (args.length === 1 ? args[0] : args[1]) as (sql: unknown) => unknown; + const wrappedCallback = function (sqlInstance: unknown): unknown { + const instrumentedSql = _instrumentSqlInstance(sqlInstance, options, parentContext); + return callback(instrumentedSql); + }; + + const newArgs = args.length === 1 ? [wrappedCallback] : [args[0], wrappedCallback]; + return Reflect.apply(original, target, newArgs); + }; +} + +/** + * Wraps a single query's handle method to create spans. + */ +function _wrapSingleQueryHandle( + query: { handle: unknown; strings?: string[]; __sentryWrapped?: boolean }, + sqlInstance: unknown, + options: PostgresJsSqlInstrumentationOptions, +): void { + // Prevent double wrapping - check if the handle itself is already wrapped + if ((query.handle as { __sentryWrapped?: boolean })?.__sentryWrapped) { + return; + } + + // Mark this query as coming from an instrumented sql instance. + // This prevents the Node.js Query.prototype fallback patch from double-spanning. + (query as Record)[QUERY_FROM_INSTRUMENTED_SQL] = true; + + const originalHandle = query.handle as (...args: unknown[]) => Promise; + + // IMPORTANT: We must replace the handle function directly, not use a Proxy, + // because Query.then() internally calls this.handle(), which would bypass a Proxy wrapper. + const wrappedHandle = async function (this: { executed?: boolean }, ...args: unknown[]): Promise { + // postgres.js calls handle() from then/catch/finally — only the first call executes SQL, + // subsequent calls are no-ops (guarded by this.executed). Skip span creation for no-ops. + if (this.executed || !_shouldCreateSpans(options)) { + return originalHandle.apply(this, args); + } + + const queryAttributes = getSqlQueryAttributes(_reconstructQuery(query.strings)); + const sanitizedSqlQuery = queryAttributes[DB_QUERY_TEXT] ?? ''; + + const client = getClient(); + + const connectionContext = sqlInstance + ? ((sqlInstance as Record)[CONNECTION_CONTEXT_SYMBOL] as PostgresConnectionContext | undefined) + : undefined; + + const name = + client && hasSpanStreamingEnabled(client) + ? queryAttributes[DB_QUERY_SUMMARY] || connectionContext?.ATTR_DB_NAMESPACE || 'postgres' + : sanitizedSqlQuery || 'postgresjs.query'; + + return startSpanManual( + { + name, + attributes: { + [SENTRY_OP]: DB, + [SENTRY_ORIGIN]: 'auto.db.postgresjs', + [DB_SYSTEM_NAME]: 'postgres', + ...queryAttributes, + ...(connectionContext && _getConnectionAttributes(connectionContext)), + }, + }, + (span: Span) => { + if (options.requestHook) { + try { + options.requestHook(span, sanitizedSqlQuery, connectionContext); + } catch (e) { + span.setAttribute('sentry.hook.error', 'requestHook failed'); + DEBUG_BUILD && debug.error('Error in requestHook for PostgresJs instrumentation:', e); + } + } + + const queryWithCallbacks = this as { + resolve: unknown; + reject: unknown; + }; + + queryWithCallbacks.resolve = new Proxy(queryWithCallbacks.resolve as (...args: unknown[]) => unknown, { + apply: (resolveTarget, resolveThisArg, resolveArgs: [{ command?: string }]) => { + try { + // Re-set the operation name with the server-reported command, which is more reliable than the query text. + span.setAttribute(DB_OPERATION_NAME, resolveArgs?.[0]?.command || queryAttributes[DB_OPERATION_NAME]); + span.end(); + } catch (e) { + DEBUG_BUILD && debug.error('Error ending span in resolve callback:', e); + } + + return Reflect.apply(resolveTarget, resolveThisArg, resolveArgs); + }, + }); + + queryWithCallbacks.reject = new Proxy(queryWithCallbacks.reject as (...args: unknown[]) => unknown, { + apply: (rejectTarget, rejectThisArg, rejectArgs: { message?: string; code?: string; name?: string }[]) => { + try { + span.setStatus({ + code: SPAN_STATUS_ERROR, + message: rejectArgs?.[0]?.message || 'unknown_error', + }); + + span.setAttribute(DB_RESPONSE_STATUS_CODE, rejectArgs?.[0]?.code || 'unknown'); + span.setAttribute(ERROR_TYPE, rejectArgs?.[0]?.name || 'unknown'); + + span.end(); + } catch (e) { + DEBUG_BUILD && debug.error('Error ending span in reject callback:', e); + } + return Reflect.apply(rejectTarget, rejectThisArg, rejectArgs); + }, + }); + + // Handle synchronous errors that might occur before promise is created + try { + return originalHandle.apply(this, args); + } catch (e) { + span.setStatus({ + code: SPAN_STATUS_ERROR, + message: e instanceof Error ? e.message : 'unknown_error', + }); + span.end(); + throw e; + } + }, + ); + }; + + (wrappedHandle as { __sentryWrapped?: boolean }).__sentryWrapped = true; + query.handle = wrappedHandle; +} + +/** + * Determines whether a span should be created based on the current context. + * If `requireParentSpan` is set to true in the options, a span will + * only be created if there is a parent span available. + */ +function _shouldCreateSpans(options: PostgresJsSqlInstrumentationOptions): boolean { + const hasParentSpan = getActiveSpan() !== undefined; + return hasParentSpan || !options.requireParentSpan; +} + +/** + * Reconstructs the full SQL query from template strings with PostgreSQL placeholders. + * + * For sql`SELECT * FROM users WHERE id = ${123} AND name = ${'foo'}`: + * strings = ["SELECT * FROM users WHERE id = ", " AND name = ", ""] + * returns: "SELECT * FROM users WHERE id = $1 AND name = $2" + * + * @internal Exported for testing only + */ +export function _reconstructQuery(strings: string[] | undefined): string | undefined { + if (!strings?.length) { + return undefined; + } + if (strings.length === 1) { + return strings[0] || undefined; + } + // Join template parts with PostgreSQL placeholders ($1, $2, etc.) + return strings.reduce((acc, str, i) => (i === 0 ? str : `${acc}$${i}${str}`), ''); +} + +/** + * Returns connection context attributes. + * + * @internal Exported for the diagnostics-channel integration. + */ +export function _getConnectionAttributes(connectionContext: PostgresConnectionContext): SpanAttributes { + const attributes: SpanAttributes = {}; + + const portNumber = connectionContext.ATTR_SERVER_PORT ? parseInt(connectionContext.ATTR_SERVER_PORT, 10) : undefined; + const dbNamespace = connectionContext.ATTR_DB_NAMESPACE; + const serverAddress = connectionContext.ATTR_SERVER_ADDRESS; + + if (dbNamespace) { + attributes[DB_NAMESPACE] = dbNamespace; + } + if (serverAddress) { + attributes[SERVER_ADDRESS] = serverAddress; + } + if (portNumber !== undefined && !isNaN(portNumber)) { + attributes[SERVER_PORT] = portNumber; + } + + return attributes; +} + +/** + * Builds a {@link PostgresConnectionContext} from postgres.js' parsed options + * (which store `host`/`port` as arrays). Defaults to 'localhost'/5432. + * + * @internal Exported for the diagnostics-channel integration. + */ +export function _buildConnectionContext(options: { + host?: string[]; + port?: number[]; + database?: string; +}): PostgresConnectionContext { + const host = options.host?.[0] || 'localhost'; + const port = options.port?.[0] || 5432; + return { + ATTR_DB_NAMESPACE: typeof options.database === 'string' && options.database !== '' ? options.database : undefined, + ATTR_SERVER_ADDRESS: host, + ATTR_SERVER_PORT: String(port), + }; +} + +/** + * Extracts and stores connection context from sql.options. + */ +function _attachConnectionContext(sql: unknown, proxiedSql: Record): void { + const sqlInstance = sql as { options?: { host?: string[]; port?: number[]; database?: string } }; + if (!sqlInstance.options || typeof sqlInstance.options !== 'object') { + return; + } + + proxiedSql[CONNECTION_CONTEXT_SYMBOL] = _buildConnectionContext(sqlInstance.options); +} diff --git a/packages/server-utils/src/integrations/postgresjs.ts b/packages/server-utils/src/integrations/postgresjs.ts index 3b42f0c80f50..2cc11f64bf75 100644 --- a/packages/server-utils/src/integrations/postgresjs.ts +++ b/packages/server-utils/src/integrations/postgresjs.ts @@ -1,424 +1,349 @@ -// Portable instrumentation for https://github.com/porsager/postgres -// This can be used in any environment (Node.js, Cloudflare Workers, etc.) -// without depending on OpenTelemetry module hooking. -/* eslint-disable max-lines */ - -import { - debug, - getActiveSpan, - getClient, - hasSpanStreamingEnabled, - isObjectLike, - type Span, - type SpanAttributes, - SPAN_STATUS_ERROR, - startSpanManual, -} from '@sentry/core'; -import { DEBUG_BUILD } from '../debug-build'; -import { getSqlQueryAttributes } from '../utils/sql'; +import * as diagnosticsChannel from '../utils/diagnosticsChannel'; import { - DB_NAMESPACE, DB_OPERATION_NAME, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME, + ERROR_TYPE, + SENTRY_KIND, SENTRY_OP, SENTRY_ORIGIN, - SERVER_ADDRESS, - SERVER_PORT, DB_RESPONSE_STATUS_CODE, - ERROR_TYPE, } from '@sentry/conventions/attributes'; import { DB } from '@sentry/conventions/op'; +import type { IntegrationFn, Span } from '@sentry/core'; +import { + debug, + defineIntegration, + getClient, + hasSpanStreamingEnabled, + SPAN_STATUS_ERROR, + startInactiveSpan, +} from '@sentry/core'; +import { DEBUG_BUILD } from '../debug-build'; +import { getSqlQueryAttributes } from '../utils/sql'; +import { + _buildConnectionContext, + _getConnectionAttributes, + _reconstructQuery, + type PostgresConnectionContext, +} from './postgresjs-wrapper'; +import type { OrchestrionChannelContext } from '../orchestrion/types'; +import { CHANNELS } from '../orchestrion/channels'; +import { bindTracingChannelToSpan } from '../tracing-channel'; +import { postgresJsModuleNames } from '../orchestrion/config/postgres'; +import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation'; -export type PostgresConnectionContext = { - ATTR_DB_NAMESPACE?: string; - ATTR_SERVER_ADDRESS?: string; - ATTR_SERVER_PORT?: string; -}; +// Same name as the OTel `PostgresJs` integration by design, so the OTel +// integration of the same name is deduplicated out of the default set. +const INTEGRATION_NAME = 'PostgresJs' as const; -interface PostgresJsSqlInstrumentationOptions { +const ORIGIN = 'auto.db.postgresjs'; + +const DB_SYSTEM_NAME_POSTGRES = 'postgres'; + +const NOOP = (): void => {}; + +// Same `Symbol.for()` marker the core `instrumentPostgresJsSql` wrapper sets on +// queries it manually instruments, so we skip them there and never double-span. +const QUERY_FROM_INSTRUMENTED_SQL = Symbol.for('sentry.query.from.instrumented.sql'); +// The query span, stashed on the `Query` so the `execute`/`connect` channels can +// attach connection attributes to it. +const QUERY_SPAN = Symbol('sentryPostgresJsSpan'); +// Set once connection attributes are on the span, so the fallback and the +// `execute`/`connect` channels don't both write them. +const CONNECTION_ATTRS_SET = Symbol('sentryPostgresJsConnectionAttrsSet'); +// Set on the channel context once the resolve/reject wrappers have ended the +// span, so `deferSpanEnd` knows the wrappers own the lifecycle. +const SPAN_ENDED = Symbol('sentryPostgresJsSpanEnded'); + +export interface PostgresJsIntegrationOptions { /** - * Whether to require a parent span for the instrumentation. - * If set to true, the instrumentation will only create spans if there is a parent span - * available in the current scope. - * @default true + * Only create spans when there's already an active parent span. Defaults to + * `true`, matching the OTel `postgresJsIntegration`. */ requireParentSpan?: boolean; /** - * Hook to modify the span before it is started. - * This can be used to set additional attributes or modify the span in any way. + * Hook to modify the query span before the query runs. Receives the span, the + * sanitized SQL, and (when resolvable) the connection context. */ requestHook?: (span: Span, sanitizedSqlQuery: string, postgresConnectionContext?: PostgresConnectionContext) => void; } -const CONNECTION_CONTEXT_SYMBOL = Symbol('sentryPostgresConnectionContext'); +/** The `Query` instance postgres.js passes as `self` to `Query.prototype.handle`. */ +interface PostgresQuery { + strings?: string[]; + executed?: boolean; + resolve?: (...args: unknown[]) => unknown; + reject?: (...args: unknown[]) => unknown; +} -// Use the same Symbol.for() markers as the Node.js OTel instrumentation -// so that both approaches recognize each other and prevent double-wrapping. -const INSTRUMENTED_MARKER = Symbol.for('sentry.instrumented.postgresjs'); -// Marker to track if a query was created from an instrumented sql instance. -// This prevents double-spanning when both the wrapper and the Node.js Query.prototype -// fallback patch are active simultaneously. -const QUERY_FROM_INSTRUMENTED_SQL = Symbol.for('sentry.query.from.instrumented.sql'); +interface PostgresJsQueryContext extends OrchestrionChannelContext { + self?: PostgresQuery; + [SPAN_ENDED]?: boolean; +} -/** - * Instruments a postgres.js `sql` instance with Sentry tracing. - * - * This is a portable instrumentation function that works in any environment - * (Node.js, Cloudflare Workers, etc.) without depending on OpenTelemetry. - * - * @example - * ```javascript - * import postgres from 'postgres'; - * import * as Sentry from '@sentry/cloudflare'; // or '@sentry/deno' - * - * const sql = Sentry.instrumentPostgresJsSql( - * postgres({ host: 'localhost', database: 'mydb' }) - * ); - * - * // All queries now create Sentry spans - * await sql`SELECT * FROM users WHERE id = ${userId}`; - * ``` - */ -export function instrumentPostgresJsSql(sql: T, options?: PostgresJsSqlInstrumentationOptions): T { - if (!sql || typeof sql !== 'function') { - DEBUG_BUILD && debug.warn('instrumentPostgresJsSql: provided value is not a valid postgres.js sql instance'); - return sql; +// A connection object -> its resolved context, populated on the `connection` +// channel and read on the `execute`/`connect` channels (keyed by the same object). +const connectionContexts = new WeakMap(); +// Distinct endpoints seen so far (value-compared, so N connections to one DB +// count once). When exactly one endpoint exists — the common case, and the only +// one the tests exercise — every query resolves to it at handle-start. +const endpointRegistry: PostgresConnectionContext[] = []; + +function registerEndpoint(context: PostgresConnectionContext): void { + const alreadyKnown = endpointRegistry.some( + e => + e.ATTR_SERVER_ADDRESS === context.ATTR_SERVER_ADDRESS && + e.ATTR_SERVER_PORT === context.ATTR_SERVER_PORT && + e.ATTR_DB_NAMESPACE === context.ATTR_DB_NAMESPACE, + ); + if (!alreadyKnown) { + endpointRegistry.push(context); } +} - return _instrumentSqlInstance(sql, { requireParentSpan: true, ...options }) as T; +/** The single known endpoint, or `undefined` when zero or multiple are known. */ +function resolveSingleEndpoint(): PostgresConnectionContext | undefined { + return endpointRegistry.length === 1 ? endpointRegistry[0] : undefined; } /** - * Instruments a sql instance by wrapping its query execution methods. + * Record a connection from the `connection` channel `end` (`result` is the + * connection object, `arguments[0]` the parsed options), keying its resolved + * context by the connection object and tracking its endpoint. */ -function _instrumentSqlInstance( - sql: unknown, - options: PostgresJsSqlInstrumentationOptions, - parentConnectionContext?: PostgresConnectionContext, -): unknown { - // Check if already instrumented to prevent double-wrapping - // Using Symbol.for() ensures the marker survives proxying - if ((sql as Record)[INSTRUMENTED_MARKER]) { - return sql; +function recordConnectionFromChannel(message: PostgresJsQueryContext): void { + const connection = message.result; + const options = message.arguments?.[0] as { host?: string[]; port?: number[]; database?: string } | undefined; + if (!connection || typeof connection !== 'object' || !options) { + return; } + const context = _buildConnectionContext(options); + connectionContexts.set(connection, context); + registerEndpoint(context); +} - // Wrap the sql function to intercept query creation - const proxiedSql: unknown = new Proxy(sql as (...args: unknown[]) => unknown, { - apply(target, thisArg, argumentsList: unknown[]) { - const query = Reflect.apply(target, thisArg, argumentsList); - - if (isObjectLike(query) && 'handle' in query) { - _wrapSingleQueryHandle(query as { handle: unknown; strings?: string[] }, proxiedSql, options); - } - - return query; - }, - get(target, prop) { - const original = (target as unknown as Record)[prop]; - - if (typeof prop !== 'string' || typeof original !== 'function') { - return original; - } - - // Wrap methods that return PendingQuery objects (unsafe, file) - if (prop === 'unsafe' || prop === 'file') { - return _wrapQueryMethod(original as (...args: unknown[]) => unknown, target, proxiedSql, options); - } - - // Wrap begin and reserve (not savepoint to avoid duplicate spans) - if (prop === 'begin' || prop === 'reserve') { - return _wrapCallbackMethod(original as (...args: unknown[]) => unknown, target, proxiedSql, options); - } - - return original; - }, - }); - - // Use provided parent context if available, otherwise extract from sql.options - if (parentConnectionContext) { - (proxiedSql as Record)[CONNECTION_CONTEXT_SYMBOL] = parentConnectionContext; - } else { - _attachConnectionContext(sql, proxiedSql as Record); +function setConnectionAttributes(span: Span, query: PostgresQuery, context: PostgresConnectionContext): void { + const queryRecord = query as Record; + if (queryRecord[CONNECTION_ATTRS_SET]) { + return; + } + queryRecord[CONNECTION_ATTRS_SET] = true; + if (context) { + span.setAttributes(_getConnectionAttributes(context)); } - - // Mark both the original and proxy as instrumented to prevent double-wrapping - (sql as Record)[INSTRUMENTED_MARKER] = true; - (proxiedSql as Record)[INSTRUMENTED_MARKER] = true; - - return proxiedSql; } /** - * Wraps query-returning methods (unsafe, file) to ensure their queries are instrumented. + * Backfill connection attributes onto a query's span from a channel whose `self` + * is the connection object and `arguments[0]` the query. Shared by the `execute` + * and `connect` channels; both carry that shape and both resolve the context via + * the `connectionContexts` WeakMap. Idempotent (guarded inside + * `setConnectionAttributes`). */ -function _wrapQueryMethod( - original: (...args: unknown[]) => unknown, - target: unknown, - proxiedSql: unknown, - options: PostgresJsSqlInstrumentationOptions, -): (...args: unknown[]) => unknown { - return function (this: unknown, ...args: unknown[]): unknown { - const query = Reflect.apply(original, target, args); - - if (isObjectLike(query) && 'handle' in query) { - _wrapSingleQueryHandle(query as { handle: unknown; strings?: string[] }, proxiedSql, options); - } - - return query; - }; +function attachConnectionAttributesFromChannel(message: PostgresJsQueryContext): void { + const connection = message.self; + const query = message.arguments?.[0] as PostgresQuery | undefined; + if (!connection || !query) { + return; + } + const span = (query as Record)[QUERY_SPAN] as Span | undefined; + const context = connectionContexts.get(connection); + if (span && context) { + setConnectionAttributes(span, query, context); + } } /** - * Wraps callback-based methods (begin, reserve) to recursively instrument Sql instances. - * Note: These methods can also be used as tagged templates, which we pass through unchanged. + * Wrap `query.resolve`/`query.reject` so the span ends when the query settles. * - * Savepoint is not wrapped to avoid complex nested transaction instrumentation issues. - * Queries within savepoint callbacks are still instrumented through the parent transaction's Sql instance. + * `Query extends Promise` and `async handle()` only dispatches — its promise + * resolves immediately, long before the query completes. postgres.js signals + * completion by calling `this.resolve`/`this.reject`, so we own the span end + * there. Wrapping happens at handle-start because `reject` can fire + * synchronously during dispatch and `cursor()` reassigns both before executing. */ -function _wrapCallbackMethod( - original: (...args: unknown[]) => unknown, - target: unknown, - parentSqlInstance: unknown, - options: PostgresJsSqlInstrumentationOptions, -): (...args: unknown[]) => unknown { - return function (this: unknown, ...args: unknown[]): unknown { - // Extract parent context to propagate to child instances - const parentContext = (parentSqlInstance as Record)[CONNECTION_CONTEXT_SYMBOL] as - | PostgresConnectionContext - | undefined; - - // Check if this is a callback-based call by verifying the last argument is a function - const isCallbackBased = typeof args[args.length - 1] === 'function'; - - if (!isCallbackBased) { - // Not a callback-based call - could be tagged template or promise-based - const result = Reflect.apply(original, target, args); - // If result is a Promise (e.g., reserve() without callback), instrument the resolved Sql instance - if (result && typeof (result as Promise).then === 'function') { - return (result as Promise).then((sqlInstance: unknown) => { - return _instrumentSqlInstance(sqlInstance, options, parentContext); - }); +function wrapQuerySettlement(data: PostgresJsQueryContext, span: Span, operationName: string | undefined): void { + const query = data.self; + if (!query) { + return; + } + + // Claim ownership of ending the span up front, so `deferSpanEnd` defers to the + // wrapper even if `span.end()` below throws. + const markEnded = (): void => { + data[SPAN_ENDED] = true; + }; + + const originalResolve = query.resolve; + if (typeof originalResolve === 'function') { + query.resolve = function (this: unknown, ...resolveArgs: unknown[]): unknown { + markEnded(); + try { + const command = (resolveArgs[0] as { command?: string } | undefined)?.command; + // Re-set the operation name with the server-reported command, which is more reliable than the query text. + span.setAttribute(DB_OPERATION_NAME, command || operationName); + span.end(); + } catch (e) { + DEBUG_BUILD && debug.error('[instrumentation:postgresjs] error ending span in resolve:', e); } - return result; - } - - // Callback-based call: wrap the callback to instrument the Sql instance - const callback = (args.length === 1 ? args[0] : args[1]) as (sql: unknown) => unknown; - const wrappedCallback = function (sqlInstance: unknown): unknown { - const instrumentedSql = _instrumentSqlInstance(sqlInstance, options, parentContext); - return callback(instrumentedSql); + return originalResolve.apply(this, resolveArgs); }; + } - const newArgs = args.length === 1 ? [wrappedCallback] : [args[0], wrappedCallback]; - return Reflect.apply(original, target, newArgs); - }; + const originalReject = query.reject; + if (typeof originalReject === 'function') { + query.reject = function (this: unknown, ...rejectArgs: unknown[]): unknown { + markEnded(); + try { + const err = rejectArgs[0] as { message?: string; code?: string; name?: string } | undefined; + span.setStatus({ code: SPAN_STATUS_ERROR, message: err?.message || 'unknown_error' }); + span.setAttribute(DB_RESPONSE_STATUS_CODE, err?.code || 'unknown'); + span.setAttribute(ERROR_TYPE, err?.name || 'unknown'); + span.end(); + } catch (e) { + DEBUG_BUILD && debug.error('[instrumentation:postgresjs] error ending span in reject:', e); + } + return originalReject.apply(this, rejectArgs); + }; + } } -/** - * Wraps a single query's handle method to create spans. - */ -function _wrapSingleQueryHandle( - query: { handle: unknown; strings?: string[]; __sentryWrapped?: boolean }, - sqlInstance: unknown, - options: PostgresJsSqlInstrumentationOptions, -): void { - // Prevent double wrapping - check if the handle itself is already wrapped - if ((query.handle as { __sentryWrapped?: boolean })?.__sentryWrapped) { - return; - } +const _postgresJsIntegration = ((options: PostgresJsIntegrationOptions = {}) => { + return { + name: INTEGRATION_NAME, + setup(client) { + invokeOrchestrionInstrumentation(client, postgresJsModuleNames, instrumentPostgresJs, [options]); + }, + }; +}) satisfies IntegrationFn; + +function instrumentPostgresJs(options: PostgresJsIntegrationOptions): void { + const { requireParentSpan, requestHook } = options; + + // Connection + execute are pure observers (no span, no async binding). + diagnosticsChannel.tracingChannel(CHANNELS.POSTGRESJS_CONNECTION).subscribe({ + start: NOOP, + asyncStart: NOOP, + asyncEnd: NOOP, + error: NOOP, + end: recordConnectionFromChannel, + }); + + // Per-connection attributes for queries reusing an already-open connection + // (`c.execute(q)`, `self === c`). `execute` is also called bare + // (`self === undefined`) for the first query on each connection, `fetchState` + // and `retry`; those miss here (the `connect` channel below covers the first + // user query, and the single-endpoint fallback covers the common case). + diagnosticsChannel.tracingChannel(CHANNELS.POSTGRESJS_EXECUTE).subscribe({ + end: NOOP, + asyncStart: NOOP, + asyncEnd: NOOP, + error: NOOP, + start: attachConnectionAttributesFromChannel, + }); - // Mark this query as coming from an instrumented sql instance. - // This prevents the Node.js Query.prototype fallback patch from double-spanning. - (query as Record)[QUERY_FROM_INSTRUMENTED_SQL] = true; + // The connection's `connect(query)` method (`self === c`, `arguments[0]` the + // query) fires when a fresh connection is opened for a query. That first query + // is later dispatched via a bare `execute` (no `self`), so this is where it + // gets its connection attributes in multi-endpoint apps. + diagnosticsChannel.tracingChannel(CHANNELS.POSTGRESJS_CONNECT).subscribe({ + end: NOOP, + asyncStart: NOOP, + asyncEnd: NOOP, + error: NOOP, + start: attachConnectionAttributesFromChannel, + }); - const originalHandle = query.handle as (...args: unknown[]) => Promise; + bindTracingChannelToSpan( + diagnosticsChannel.tracingChannel(CHANNELS.POSTGRESJS_HANDLE), + data => { + const query = data.self; + if (!query) { + return undefined; + } - // IMPORTANT: We must replace the handle function directly, not use a Proxy, - // because Query.then() internally calls this.handle(), which would bypass a Proxy wrapper. - const wrappedHandle = async function (this: { executed?: boolean }, ...args: unknown[]): Promise { - // postgres.js calls handle() from then/catch/finally — only the first call executes SQL, - // subsequent calls are no-ops (guarded by this.executed). Skip span creation for no-ops. - if (this.executed || !_shouldCreateSpans(options)) { - return originalHandle.apply(this, args); - } + // Opt out of re-entrant `handle()` calls (then/catch/finally re-invoke + // it, guarded by `executed`) and queries already wrapped by the portable + // `instrumentPostgresJsSql`. + if (query.executed === true || (query as Record)[QUERY_FROM_INSTRUMENTED_SQL]) { + return undefined; + } - const queryAttributes = getSqlQueryAttributes(_reconstructQuery(query.strings)); - const sanitizedSqlQuery = queryAttributes[DB_QUERY_TEXT] ?? ''; + const queryAttributes = getSqlQueryAttributes(_reconstructQuery(query.strings)); + const sanitizedSqlQuery = queryAttributes[DB_QUERY_TEXT] ?? ''; - const client = getClient(); + const client = getClient(); - const connectionContext = sqlInstance - ? ((sqlInstance as Record)[CONNECTION_CONTEXT_SYMBOL] as PostgresConnectionContext | undefined) - : undefined; + // Single-endpoint fallback: resolve context now so the span name and `requestHook` have + // it, and the first-query-per-connection (bare `execute`) path still gets attrs. + const context = resolveSingleEndpoint(); - const name = - client && hasSpanStreamingEnabled(client) - ? queryAttributes[DB_QUERY_SUMMARY] || connectionContext?.ATTR_DB_NAMESPACE || 'postgres' - : sanitizedSqlQuery || 'postgresjs.query'; + const name = + client && hasSpanStreamingEnabled(client) + ? queryAttributes[DB_QUERY_SUMMARY] || context?.ATTR_DB_NAMESPACE || DB_SYSTEM_NAME_POSTGRES + : sanitizedSqlQuery || 'postgresjs.query'; - return startSpanManual( - { + // `sentry.kind: client` matches the mysql/pg channel subscribers. + const span = startInactiveSpan({ name, + onlyIfParent: requireParentSpan !== false, attributes: { [SENTRY_OP]: DB, - [SENTRY_ORIGIN]: 'auto.db.postgresjs', - [DB_SYSTEM_NAME]: 'postgres', + [SENTRY_KIND]: 'client', + [SENTRY_ORIGIN]: ORIGIN, + [DB_SYSTEM_NAME]: DB_SYSTEM_NAME_POSTGRES, ...queryAttributes, - ...(connectionContext && _getConnectionAttributes(connectionContext)), }, - }, - (span: Span) => { - if (options.requestHook) { - try { - options.requestHook(span, sanitizedSqlQuery, connectionContext); - } catch (e) { - span.setAttribute('sentry.hook.error', 'requestHook failed'); - DEBUG_BUILD && debug.error('Error in requestHook for PostgresJs instrumentation:', e); - } - } + }); - const queryWithCallbacks = this as { - resolve: unknown; - reject: unknown; - }; - - queryWithCallbacks.resolve = new Proxy(queryWithCallbacks.resolve as (...args: unknown[]) => unknown, { - apply: (resolveTarget, resolveThisArg, resolveArgs: [{ command?: string }]) => { - try { - // Re-set the operation name with the server-reported command, which is more reliable than the query text. - span.setAttribute(DB_OPERATION_NAME, resolveArgs?.[0]?.command || queryAttributes[DB_OPERATION_NAME]); - span.end(); - } catch (e) { - DEBUG_BUILD && debug.error('Error ending span in resolve callback:', e); - } - - return Reflect.apply(resolveTarget, resolveThisArg, resolveArgs); - }, - }); - - queryWithCallbacks.reject = new Proxy(queryWithCallbacks.reject as (...args: unknown[]) => unknown, { - apply: (rejectTarget, rejectThisArg, rejectArgs: { message?: string; code?: string; name?: string }[]) => { - try { - span.setStatus({ - code: SPAN_STATUS_ERROR, - message: rejectArgs?.[0]?.message || 'unknown_error', - }); - - span.setAttribute(DB_RESPONSE_STATUS_CODE, rejectArgs?.[0]?.code || 'unknown'); - span.setAttribute(ERROR_TYPE, rejectArgs?.[0]?.name || 'unknown'); - - span.end(); - } catch (e) { - DEBUG_BUILD && debug.error('Error ending span in reject callback:', e); - } - return Reflect.apply(rejectTarget, rejectThisArg, rejectArgs); - }, - }); - - // Handle synchronous errors that might occur before promise is created + // Stash for the `execute`/`connect` channels to attach per-connection attributes. + (query as Record)[QUERY_SPAN] = span; + + if (context) { + setConnectionAttributes(span, query, context); + } + + if (requestHook) { try { - return originalHandle.apply(this, args); + requestHook(span, sanitizedSqlQuery, context); } catch (e) { - span.setStatus({ - code: SPAN_STATUS_ERROR, - message: e instanceof Error ? e.message : 'unknown_error', - }); - span.end(); - throw e; + span.setAttribute('sentry.hook.error', 'requestHook failed'); + DEBUG_BUILD && debug.error('[instrumentation:postgresjs] error in requestHook:', e); } - }, - ); - }; - - (wrappedHandle as { __sentryWrapped?: boolean }).__sentryWrapped = true; - query.handle = wrappedHandle; -} - -/** - * Determines whether a span should be created based on the current context. - * If `requireParentSpan` is set to true in the options, a span will - * only be created if there is a parent span available. - */ -function _shouldCreateSpans(options: PostgresJsSqlInstrumentationOptions): boolean { - const hasParentSpan = getActiveSpan() !== undefined; - return hasParentSpan || !options.requireParentSpan; -} - -/** - * Reconstructs the full SQL query from template strings with PostgreSQL placeholders. - * - * For sql`SELECT * FROM users WHERE id = ${123} AND name = ${'foo'}`: - * strings = ["SELECT * FROM users WHERE id = ", " AND name = ", ""] - * returns: "SELECT * FROM users WHERE id = $1 AND name = $2" - * - * @internal Exported for testing only - */ -export function _reconstructQuery(strings: string[] | undefined): string | undefined { - if (!strings?.length) { - return undefined; - } - if (strings.length === 1) { - return strings[0] || undefined; - } - // Join template parts with PostgreSQL placeholders ($1, $2, etc.) - return strings.reduce((acc, str, i) => (i === 0 ? str : `${acc}$${i}${str}`), ''); -} - -/** - * Returns connection context attributes. - * - * @internal Exported for the diagnostics-channel integration. - */ -export function _getConnectionAttributes(connectionContext: PostgresConnectionContext): SpanAttributes { - const attributes: SpanAttributes = {}; - - const portNumber = connectionContext.ATTR_SERVER_PORT ? parseInt(connectionContext.ATTR_SERVER_PORT, 10) : undefined; - const dbNamespace = connectionContext.ATTR_DB_NAMESPACE; - const serverAddress = connectionContext.ATTR_SERVER_ADDRESS; + } - if (dbNamespace) { - attributes[DB_NAMESPACE] = dbNamespace; - } - if (serverAddress) { - attributes[SERVER_ADDRESS] = serverAddress; - } - if (portNumber !== undefined && !isNaN(portNumber)) { - attributes[SERVER_PORT] = portNumber; - } + wrapQuerySettlement(data, span, queryAttributes[DB_OPERATION_NAME]); - return attributes; + return span; + }, + { + deferSpanEnd({ data }) { + // `handle` is async: its promise settles on dispatch (asyncEnd), long + // before the query does. The resolve/reject wrappers own the ending. + if (data[SPAN_ENDED]) { + return true; // wrappers already ended it + } + if ('error' in data) { + return false; // `handle()` itself threw; the error subscriber annotated the span, let the helper end it + } + // NOTE: for a cursor consumed as an async iterator, only the first batch + // reaches `handle` (the `executed` guard blocks the rest), so the span + // ends on the first batch — a pre-existing flaw kept for parity. + return true; // query in flight; the wrappers will end the span when it settles + }, + }, + ); } /** - * Builds a {@link PostgresConnectionContext} from postgres.js' parsed options - * (which store `host`/`port` as arrays). Defaults to 'localhost'/5432. + * Diagnostics-channel-based postgres.js (`postgres` v3.x) integration. * - * @internal Exported for the diagnostics-channel integration. + * Subscribes to the `orchestrion:postgres:handle` / `:connection` / `:execute` / + * `:connect` diagnostics channels injected into postgres.js' `Query.prototype.handle` + * and `Connection`/`execute`/`connect` (in `src/*` and `cjs/src/*`) and creates db + * spans matching the OTel `postgresJsIntegration`. Requires the Sentry runtime + * hook or bundler plugin. */ -export function _buildConnectionContext(options: { - host?: string[]; - port?: number[]; - database?: string; -}): PostgresConnectionContext { - const host = options.host?.[0] || 'localhost'; - const port = options.port?.[0] || 5432; - return { - ATTR_DB_NAMESPACE: typeof options.database === 'string' && options.database !== '' ? options.database : undefined, - ATTR_SERVER_ADDRESS: host, - ATTR_SERVER_PORT: String(port), - }; -} - -/** - * Extracts and stores connection context from sql.options. - */ -function _attachConnectionContext(sql: unknown, proxiedSql: Record): void { - const sqlInstance = sql as { options?: { host?: string[]; port?: number[]; database?: string } }; - if (!sqlInstance.options || typeof sqlInstance.options !== 'object') { - return; - } - - proxiedSql[CONNECTION_CONTEXT_SYMBOL] = _buildConnectionContext(sqlInstance.options); -} +export const postgresJsIntegration = defineIntegration(_postgresJsIntegration); diff --git a/packages/server-utils/test/integrations/postgres/postgres-ignore-connect.test.ts b/packages/server-utils/test/integrations/pg/pg-ignore-connect.test.ts similarity index 98% rename from packages/server-utils/test/integrations/postgres/postgres-ignore-connect.test.ts rename to packages/server-utils/test/integrations/pg/pg-ignore-connect.test.ts index 5254c7890e08..acc6785f97f4 100644 --- a/packages/server-utils/test/integrations/postgres/postgres-ignore-connect.test.ts +++ b/packages/server-utils/test/integrations/pg/pg-ignore-connect.test.ts @@ -9,7 +9,7 @@ import { setAsyncContextStrategy, } from '@sentry/core'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { postgresIntegration } from '../../../src/integrations/postgres'; +import { postgresIntegration } from '../../../src/integrations/pg'; import { CHANNELS } from '../../../src/orchestrion/channels'; interface TestStore { @@ -64,7 +64,7 @@ function installTestAsyncContextStrategy(): void { // ABSENCE of connect subscribers only holds when no other (default-options) // integration in the same module context has subscribed. vitest isolates // module state per file, so this file keeps that assertion clean (the default -// options integration is exercised in `postgres.test.ts`). +// options integration is exercised in `pg.test.ts`). describe('postgresIntegration({ ignoreConnectSpans: true })', () => { beforeAll(() => { installTestAsyncContextStrategy(); diff --git a/packages/server-utils/test/integrations/postgres/postgres.test.ts b/packages/server-utils/test/integrations/pg/pg.test.ts similarity index 99% rename from packages/server-utils/test/integrations/postgres/postgres.test.ts rename to packages/server-utils/test/integrations/pg/pg.test.ts index 9af1c1a5c851..c572441e6896 100644 --- a/packages/server-utils/test/integrations/postgres/postgres.test.ts +++ b/packages/server-utils/test/integrations/pg/pg.test.ts @@ -11,7 +11,7 @@ import { setAsyncContextStrategy, } from '@sentry/core'; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, type MockInstance, vi } from 'vitest'; -import { postgresIntegration } from '../../../src/integrations/postgres'; +import { postgresIntegration } from '../../../src/integrations/pg'; import { CHANNELS } from '../../../src/orchestrion/channels'; interface TestStore { diff --git a/packages/server-utils/test/integrations/postgresjs.test.ts b/packages/server-utils/test/integrations/postgresjs-wrapper.test.ts similarity index 99% rename from packages/server-utils/test/integrations/postgresjs.test.ts rename to packages/server-utils/test/integrations/postgresjs-wrapper.test.ts index 3d2283f2cc28..d1874a458589 100644 --- a/packages/server-utils/test/integrations/postgresjs.test.ts +++ b/packages/server-utils/test/integrations/postgresjs-wrapper.test.ts @@ -1,6 +1,6 @@ import * as SentryCore from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { _reconstructQuery, instrumentPostgresJsSql } from '../../src/integrations/postgresjs'; +import { _reconstructQuery, instrumentPostgresJsSql } from '../../src/integrations/postgresjs-wrapper'; import { sanitizeSqlQuery } from '../../src/utils/sql'; describe('PostgresJs portable instrumentation', () => { From afa37635f5dad1b203d0077ac2e52160763dffe2 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 3 Nov 2026 13:38:13 +0100 Subject: [PATCH 108/169] perf(ci): Speed up Next.js E2E tests by sharding into dev/prod (#25960) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reduce CI bottlenecks by running Next.js production/development suites in parallel, shortening fixture flush waits, and skipping redundant production builds in app-dir development jobs. Some individual job timings before → after (measured on the weekend when there was likely lower load so absolute numbers are probably more of a lower bound but the relative improvements should still be there): - Next 16 webpack: **4:31 → 3:09**; latest: **5:00 → 2:41** - Next 16 Turbopack: **4:04 → 1:55**; latest: **3:58 → 2:00** - App-dir Next 14: **3:30 → 2:09** - App-dir Next 15: **3:51 → 2:04** - Next 15: **2:29 → 1:40** - Next 16 static: **2:53 → 1:38** Overall required CI completion dropped from **7:20 to 5:34 (24%)** in my latest test. --------- Co-authored-by: OpenAI Codex --- dev-packages/e2e-tests/README.md | 4 ++ dev-packages/e2e-tests/lib/getTestMatrix.mjs | 2 +- dev-packages/e2e-tests/run.mjs | 3 +- .../test-applications/nextjs-15/package.json | 12 ++++++ .../nextjs-16-static/package.json | 14 ++++++- .../tests/build-output.test.ts | 3 ++ .../nextjs-16/instrumentation-client.ts | 1 + .../test-applications/nextjs-16/package.json | 38 ++++++++++++++++--- .../nextjs-16/sentry.server.config.ts | 1 + .../nextjs-16/tests/build-output.test.ts | 2 + .../nextjs-app-dir/package.json | 23 +++++++++-- 11 files changed, 91 insertions(+), 12 deletions(-) diff --git a/dev-packages/e2e-tests/README.md b/dev-packages/e2e-tests/README.md index 426b274da2d0..b5cdf556befd 100644 --- a/dev-packages/e2e-tests/README.md +++ b/dev-packages/e2e-tests/README.md @@ -292,6 +292,10 @@ For example, if something is changed in the browser package, only E2E test apps You can add additional information about the test (e.g. canary versions, optional in CI) by adding `sentryTest` in the `package.json` of a test application. +`sentryTest.baseVariant` overrides the default matrix entry using the same fields as an entry in `variants`. For example, +set its `assert-command` to run production tests and add a development variant to run both environments in parallel. +The local `test:assert` script can still run both; select the base variant locally with `--variant