From 4abf53041ec03ef03c7c228e47d77c81d97671cd Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 23 Jul 2026 16:07:41 +0200 Subject: [PATCH 1/4] feat!: Require Node `>=20.19.0` as minimum supported version Bumps the minimum supported Node.js version to 20.19.0 for v11 and removes now-dead runtime branches that guarded pre-20 Node versions. Ref https://github.com/getsentry/sentry-javascript/issues/22056 Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/ISSUE_TEMPLATE/flaky.yml | 2 +- .github/workflows/build.yml | 1 - .../browser-integration-tests/package.json | 2 +- .../bun-integration-tests/package.json | 2 +- .../clear-cache-gh-action/package.json | 2 +- .../cloudflare-integration-tests/package.json | 2 +- .../deno-integration-tests/package.json | 2 +- .../ember-classic/package.json | 2 +- .../ember-embroider/package.json | 2 +- .../package.json | 2 +- .../node-integration-tests/package.json | 2 +- .../size-limit-gh-action/package.json | 2 +- dev-packages/test-utils/package.json | 2 +- docs/supporting-a-new-node-version.md | 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/bundler-plugins/package.json | 2 +- packages/cloudflare/package.json | 2 +- packages/core/package.json | 2 +- packages/effect/package.json | 2 +- packages/elysia/package.json | 2 +- packages/ember/package.json | 2 +- packages/eslint-config-sdk/package.json | 2 +- packages/eslint-plugin-sdk/package.json | 2 +- packages/feedback/package.json | 2 +- packages/gatsby/package.json | 2 +- packages/google-cloud-serverless/package.json | 2 +- packages/hono/package.json | 2 +- packages/integration-shims/package.json | 2 +- packages/nestjs/package.json | 2 +- packages/nextjs/package.json | 2 +- packages/nitro/package.json | 2 +- packages/node-native/package.json | 2 +- packages/node/package.json | 2 +- packages/node/src/integrations/anr/index.ts | 5 ---- .../SentryNodeFetchInstrumentation.ts | 17 ++---------- .../node-fetch/undici-instrumentation.ts | 16 +++--------- packages/node/src/integrations/pino.ts | 2 +- packages/node/src/integrations/spotlight.ts | 2 -- .../src/integrations/tracing/redis/index.ts | 6 ++--- packages/node/src/utils/detection.ts | 26 +------------------ packages/nuxt/package.json | 2 +- packages/opentelemetry/package.json | 2 +- packages/profiling-node/package.json | 2 +- packages/react-router/package.json | 2 +- packages/react/package.json | 2 +- packages/remix/package.json | 2 +- packages/replay-canvas/package.json | 2 +- packages/replay-internal/package.json | 2 +- packages/replay-worker/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/types/package.json | 2 +- packages/vercel-edge/package.json | 2 +- packages/vue/package.json | 2 +- packages/wasm/package.json | 2 +- scripts/ci-unit-tests.ts | 8 ------ 65 files changed, 65 insertions(+), 130 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/flaky.yml b/.github/ISSUE_TEMPLATE/flaky.yml index 1b9290cc3bbc..22b035ec1145 100644 --- a/.github/ISSUE_TEMPLATE/flaky.yml +++ b/.github/ISSUE_TEMPLATE/flaky.yml @@ -18,7 +18,7 @@ body: id: job-name attributes: label: Name of Job - placeholder: 'CI: Build & Test / Nextjs (Node 18) Tests' + placeholder: 'CI: Build & Test / Nextjs (Node 20) Tests' description: name of job as reported in the status report validations: required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17bf2143e1e7..70d6b1c997c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -800,7 +800,6 @@ jobs: # Only check typescript for latest version (to streamline CI) - node: 24 typescript: '5.0' - # No need to test orchestrion for v18 - node: 20.19 use_orchestrion: 'true' - node: 22 diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 3b0952904a10..8e936fa20ef2 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -4,7 +4,7 @@ "main": "index.js", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "private": true, "scripts": { diff --git a/dev-packages/bun-integration-tests/package.json b/dev-packages/bun-integration-tests/package.json index 5601caddb316..3c24abf0327f 100644 --- a/dev-packages/bun-integration-tests/package.json +++ b/dev-packages/bun-integration-tests/package.json @@ -3,7 +3,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "private": true, "scripts": { diff --git a/dev-packages/clear-cache-gh-action/package.json b/dev-packages/clear-cache-gh-action/package.json index a117f7532f0a..984f84d8cbe2 100644 --- a/dev-packages/clear-cache-gh-action/package.json +++ b/dev-packages/clear-cache-gh-action/package.json @@ -4,7 +4,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "private": true, "main": "index.mjs", diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json index 938e6c5beee7..b623ada6414c 100644 --- a/dev-packages/cloudflare-integration-tests/package.json +++ b/dev-packages/cloudflare-integration-tests/package.json @@ -3,7 +3,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "private": true, "scripts": { diff --git a/dev-packages/deno-integration-tests/package.json b/dev-packages/deno-integration-tests/package.json index 78de7fb40aa3..d2d7ca5e434d 100644 --- a/dev-packages/deno-integration-tests/package.json +++ b/dev-packages/deno-integration-tests/package.json @@ -3,7 +3,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/package.json b/dev-packages/e2e-tests/test-applications/ember-classic/package.json index 9f96912ce555..74abad21cb08 100644 --- a/dev-packages/e2e-tests/test-applications/ember-classic/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-classic/package.json @@ -72,7 +72,7 @@ "webpack": "~5.104.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "resolutions": { "@babel/traverse": "~7.25.9", diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json index f1dff2628aac..81ec1e52fa39 100644 --- a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json @@ -61,7 +61,7 @@ "typescript": "^5.4.5" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "ember": { "edition": "octane" diff --git a/dev-packages/external-contributor-gh-action/package.json b/dev-packages/external-contributor-gh-action/package.json index 5ae0ff242e67..b8018de64db0 100644 --- a/dev-packages/external-contributor-gh-action/package.json +++ b/dev-packages/external-contributor-gh-action/package.json @@ -4,7 +4,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "private": true, "main": "index.mjs", diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index edd41b92ed07..32fa5c18f0aa 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -3,7 +3,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "private": true, "main": "build/cjs/index.js", diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json index 1f4c02bb2da3..5b40b839d3bb 100644 --- a/dev-packages/size-limit-gh-action/package.json +++ b/dev-packages/size-limit-gh-action/package.json @@ -4,7 +4,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "private": true, "main": "index.mjs", diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json index 4ec1db540b0f..3f4dc95f32d8 100644 --- a/dev-packages/test-utils/package.json +++ b/dev-packages/test-utils/package.json @@ -27,7 +27,7 @@ }, "sideEffects": false, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "scripts": { "lint:fix": "OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS=true oxlint . --fix --type-aware", diff --git a/docs/supporting-a-new-node-version.md b/docs/supporting-a-new-node-version.md index 957ec9b078db..bd4068d03eb5 100644 --- a/docs/supporting-a-new-node-version.md +++ b/docs/supporting-a-new-node-version.md @@ -86,7 +86,7 @@ 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: [18, 20, 22, 24]` entry. +1. Add the version to the CI test matrix in `.github/workflows/build.yml` to every `node: [20.19, 22, 24, 26]` entry. 2. Bump the native module dependencies to the versions released in Steps 1 and 2: - [ ] `@sentry-internal/node-cpu-profiler` in `packages/profiling-node` diff --git a/packages/angular/package.json b/packages/angular/package.json index 5a98e02303e3..2a32811c8d12 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "type": "module", "module": "build/fesm2015/sentry-angular.mjs", diff --git a/packages/astro/package.json b/packages/astro/package.json index c8977282394f..55962a6fb617 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -14,7 +14,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18.19.1" + "node": ">=20.19.0" }, "type": "module", "files": [ diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 31281278cf06..3764e29cd7cd 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build/npm", diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json index ca6b84d78495..e0bc52d3f007 100644 --- a/packages/browser-utils/package.json +++ b/packages/browser-utils/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/browser/package.json b/packages/browser/package.json index 7638670e1ce6..7d160df2a744 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build/npm" diff --git a/packages/bun/package.json b/packages/bun/package.json index cc98b8913ac7..b1f74c661290 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/bundler-plugins/package.json b/packages/bundler-plugins/package.json index 22a3a36298f9..3578a2098eee 100644 --- a/packages/bundler-plugins/package.json +++ b/packages/bundler-plugins/package.json @@ -143,7 +143,7 @@ "extends": "../../package.json" }, "engines": { - "node": ">= 18" + "node": ">=20.19.0" }, "sideEffects": [ "./sentry-release-injection-file.js", diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index d4dc35e6fbdd..0afd30c63e26 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/core/package.json b/packages/core/package.json index 8ec000a64080..e9f0e8318222 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build", diff --git a/packages/effect/package.json b/packages/effect/package.json index e19b2cef7ba2..3d8f33af39c7 100644 --- a/packages/effect/package.json +++ b/packages/effect/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/elysia/package.json b/packages/elysia/package.json index 8919adf3cf2b..23f394cc5499 100644 --- a/packages/elysia/package.json +++ b/packages/elysia/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/ember/package.json b/packages/ember/package.json index 3254d31a92fc..ab37d32837a8 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -82,7 +82,7 @@ "webpack": "~5.104.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "ember": { "edition": "octane" diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json index 41ce9bff9d36..d583bfbaa034 100644 --- a/packages/eslint-config-sdk/package.json +++ b/packages/eslint-config-sdk/package.json @@ -12,7 +12,7 @@ "sentry" ], "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/src" diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json index c22affcc4f20..c8f01320c928 100644 --- a/packages/eslint-plugin-sdk/package.json +++ b/packages/eslint-plugin-sdk/package.json @@ -12,7 +12,7 @@ "sentry" ], "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/src" diff --git a/packages/feedback/package.json b/packages/feedback/package.json index fc85967dbf5d..9c48c4347a02 100644 --- a/packages/feedback/package.json +++ b/packages/feedback/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build/npm" diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index b3f06e9385ff..20e35b706b5d 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -11,7 +11,7 @@ "gatsby-plugin" ], "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build", diff --git a/packages/google-cloud-serverless/package.json b/packages/google-cloud-serverless/package.json index 7b05bf42a8b9..706bc9cf5136 100644 --- a/packages/google-cloud-serverless/package.json +++ b/packages/google-cloud-serverless/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/hono/package.json b/packages/hono/package.json index f306fb07095f..3a515bbd6073 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/integration-shims/package.json b/packages/integration-shims/package.json index 55b60925aff3..f809cfcf7f72 100644 --- a/packages/integration-shims/package.json +++ b/packages/integration-shims/package.json @@ -50,7 +50,7 @@ "@sentry/core": "10.67.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 40f6330e1908..92f5e1e58236 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index a4a814869a22..b027c28c60d0 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", diff --git a/packages/nitro/package.json b/packages/nitro/package.json index a90e973000e4..5660288e8d6e 100644 --- a/packages/nitro/package.json +++ b/packages/nitro/package.json @@ -14,7 +14,7 @@ "error-tracking" ], "engines": { - "node": ">=18.19.1" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/node-native/package.json b/packages/node-native/package.json index 80c3c09ce433..0a2478827c43 100644 --- a/packages/node-native/package.json +++ b/packages/node-native/package.json @@ -31,7 +31,7 @@ } }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "publishConfig": { "access": "public" diff --git a/packages/node/package.json b/packages/node/package.json index 1b6747382cc7..69d4c1b18ea4 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/node/src/integrations/anr/index.ts b/packages/node/src/integrations/anr/index.ts index 23255dd3357e..1c5b00fe9d4e 100644 --- a/packages/node/src/integrations/anr/index.ts +++ b/packages/node/src/integrations/anr/index.ts @@ -11,7 +11,6 @@ import { getIsolationScope, GLOBAL_OBJ, } from '@sentry/core'; -import { NODE_VERSION } from '../../nodeVersion'; import type { NodeClient } from '../../sdk/client'; import { isDebuggerEnabled } from '../../utils/debug'; import type { AnrIntegrationOptions, WorkerStartData } from './common'; @@ -65,10 +64,6 @@ type AnrInternal = { startWorker: () => void; stopWorker: () => void }; // eslint-disable-next-line typescript/no-deprecated const _anrIntegration = ((options: Partial = {}) => { - if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) { - throw new Error('ANR detection requires Node 16.17.0 or later'); - } - let worker: Promise<() => void> | undefined; let client: NodeClient | undefined; diff --git a/packages/node/src/integrations/node-fetch/SentryNodeFetchInstrumentation.ts b/packages/node/src/integrations/node-fetch/SentryNodeFetchInstrumentation.ts index 6a8439a60b57..ffdd56bb2f8c 100644 --- a/packages/node/src/integrations/node-fetch/SentryNodeFetchInstrumentation.ts +++ b/packages/node/src/integrations/node-fetch/SentryNodeFetchInstrumentation.ts @@ -2,7 +2,6 @@ import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase } from '@opentelemetry/instrumentation'; import { LRUMap, SDK_VERSION, isTracingSuppressed } from '@sentry/core'; import * as diagch from 'diagnostics_channel'; -import { NODE_MAJOR, NODE_MINOR } from '../../nodeVersion'; import { addFetchRequestBreadcrumb, addTracePropagationHeadersToFetchRequest, @@ -160,23 +159,11 @@ export class SentryNodeFetchInstrumentation extends InstrumentationBase void, ): void { - // `diagnostics_channel` had a ref counting bug until v18.19.0. - // https://github.com/nodejs/node/pull/47520 - const useNewSubscribe = NODE_MAJOR > 18 || (NODE_MAJOR === 18 && NODE_MINOR >= 19); - - let unsubscribe: () => void; - if (useNewSubscribe) { - diagch.subscribe?.(diagnosticChannel, onMessage); - unsubscribe = () => diagch.unsubscribe?.(diagnosticChannel, onMessage); - } else { - const channel = diagch.channel(diagnosticChannel); - channel.subscribe(onMessage); - unsubscribe = () => channel.unsubscribe(onMessage); - } + diagch.subscribe?.(diagnosticChannel, onMessage); this._channelSubs.push({ name: diagnosticChannel, - unsubscribe, + unsubscribe: () => diagch.unsubscribe?.(diagnosticChannel, onMessage), }); } diff --git a/packages/node/src/integrations/node-fetch/undici-instrumentation.ts b/packages/node/src/integrations/node-fetch/undici-instrumentation.ts index 89a754b36274..d100f8c3d3b2 100644 --- a/packages/node/src/integrations/node-fetch/undici-instrumentation.ts +++ b/packages/node/src/integrations/node-fetch/undici-instrumentation.ts @@ -12,6 +12,8 @@ * - Dropped the `@opentelemetry/instrumentation` base (undici reports via `diagnostics_channel`, * so no module patching was needed) — exposed as a plain `instrumentUndici()` function that the * integration wires up directly + * - Dropped the pre-18.19.0 `diagnostics_channel` ref-counting workaround, since the SDK + * requires Node >=20.19.0 */ /* eslint-disable max-lines */ @@ -116,19 +118,7 @@ function subscribeToChannel( diagnosticChannel: string, onMessage: (message: unknown, name: string | symbol) => void, ): void { - // `diagnostics_channel` had a ref counting bug until v18.19.0. - // https://github.com/nodejs/node/pull/47520 - const [major = 0, minor = 0] = process.version - .replace('v', '') - .split('.') - .map(n => Number(n)); - const useNewSubscribe = major > 18 || (major === 18 && minor >= 19); - - if (useNewSubscribe) { - _channelSubs.push(diagch.subscribe?.(diagnosticChannel, onMessage)); - } else { - _channelSubs.push(diagch.channel(diagnosticChannel).subscribe(onMessage)); - } + _channelSubs.push(diagch.subscribe?.(diagnosticChannel, onMessage)); } function parseRequestHeaders(request: UndiciRequest): Map { diff --git a/packages/node/src/integrations/pino.ts b/packages/node/src/integrations/pino.ts index b6b1c777ef30..89105bdaa0b6 100644 --- a/packages/node/src/integrations/pino.ts +++ b/packages/node/src/integrations/pino.ts @@ -232,7 +232,7 @@ interface PinoIntegrationFunction { * * If you disable automatic instrumentation with `autoInstrument: false`, you can mark specific loggers to be tracked with `pinoIntegration.trackLogger(logger)`. * - * Requires Pino >=v8.0.0 and Node >=20.6.0 or >=18.19.0 + * Requires Pino >=v8.0.0 */ export const pinoIntegration = Object.assign(_pinoIntegration, { trackLogger(logger: unknown): void { diff --git a/packages/node/src/integrations/spotlight.ts b/packages/node/src/integrations/spotlight.ts index 5e85bdb092e1..217ef197b135 100644 --- a/packages/node/src/integrations/spotlight.ts +++ b/packages/node/src/integrations/spotlight.ts @@ -36,8 +36,6 @@ const _spotlightIntegration = ((options: Partial = { * Use this integration to send errors and transactions to Spotlight. * * Learn more about spotlight at https://spotlightjs.com - * - * Important: This integration only works with Node 18 or newer. */ export const spotlightIntegration = defineIntegration(_spotlightIntegration); diff --git a/packages/node/src/integrations/tracing/redis/index.ts b/packages/node/src/integrations/tracing/redis/index.ts index 9d01332cf679..77bde6ca57ea 100644 --- a/packages/node/src/integrations/tracing/redis/index.ts +++ b/packages/node/src/integrations/tracing/redis/index.ts @@ -1,6 +1,5 @@ import type { IntegrationFn } from '@sentry/core'; import { defineIntegration, extendIntegration } from '@sentry/core'; -import * as dc from 'node:diagnostics_channel'; import { redisIntegration as redisChannelIntegration } from '@sentry/server-utils'; import { generateInstrumentOnce } from '../../../otel/instrument'; import { isDiagnosticsChannelInjectionEnabled } from '../../../sdk/diagnosticsChannelInjection'; @@ -35,9 +34,8 @@ export const instrumentRedis = Object.assign( (): void => { // When diagnostics-channel injection is opted in, orchestrion fully owns the older // ioredis (`<5.11.0`) and redis/node-redis (`<5.12.0`) ranges — commands, connect, and - // batches — so skip both OTel monkey-patches to avoid double instrumentation. On Node - // without `tracingChannel` (<18.19) orchestrion can't run, so keep the OTel patches there. - if (!isDiagnosticsChannelInjectionEnabled() || !dc.tracingChannel) { + // batches — so skip both OTel monkey-patches to avoid double instrumentation. + if (!isDiagnosticsChannelInjectionEnabled()) { instrumentIORedis(); instrumentRedisModule(); } diff --git a/packages/node/src/utils/detection.ts b/packages/node/src/utils/detection.ts index 43799c611782..98fcf3975609 100644 --- a/packages/node/src/utils/detection.ts +++ b/packages/node/src/utils/detection.ts @@ -1,6 +1,3 @@ -import { consoleSandbox } from '@sentry/core'; -import { NODE_MAJOR, NODE_MINOR } from '../nodeVersion'; - function isCjs(): boolean { /*! rollup-include-cjs-only */ return true; @@ -11,30 +8,9 @@ function isCjs(): boolean { /*! rollup-include-esm-only-end */ } -let hasWarnedAboutNodeVersion: boolean | undefined; - /** * Check if the current Node.js version supports module.register */ export function supportsEsmLoaderHooks(): boolean { - if (isCjs()) { - return false; - } - - if (NODE_MAJOR >= 21 || (NODE_MAJOR === 20 && NODE_MINOR >= 6) || (NODE_MAJOR === 18 && NODE_MINOR >= 19)) { - return true; - } - - if (!hasWarnedAboutNodeVersion) { - hasWarnedAboutNodeVersion = true; - - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - `[Sentry] You are using Node.js v${process.versions.node} in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.`, - ); - }); - } - - return false; + return !isCjs(); } diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index b8f31311f7b7..8f504a3ae072 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18.19.1" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index fe8bab714e74..2642c0cc7e03 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index fdeb64d30945..09b60aea12a3 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -26,7 +26,7 @@ "sentry-prune-profiler-binaries": "scripts/prune-profiler-binaries.js" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "publishConfig": { "access": "public" diff --git a/packages/react-router/package.json b/packages/react-router/package.json index d2abc3eab12a..5292a8965cdf 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/react/package.json b/packages/react/package.json index c0a9fd9c4113..10b59e292bdd 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/remix/package.json b/packages/remix/package.json index 3aae87ea0539..b19abbb1343d 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -10,7 +10,7 @@ "sentry-upload-sourcemaps": "scripts/sentry-upload-sourcemaps.js" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build", diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 61915f6b2645..676c7e7a373c 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -64,7 +64,7 @@ "@sentry/core": "10.67.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index 6459bc993bd5..0b03e8d87c2e 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -83,7 +83,7 @@ "@sentry/core": "10.67.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/replay-worker/package.json b/packages/replay-worker/package.json index 67b10318c6a7..306e9c5df60f 100644 --- a/packages/replay-worker/package.json +++ b/packages/replay-worker/package.json @@ -57,7 +57,7 @@ "fflate": "0.8.2" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/server-utils/package.json b/packages/server-utils/package.json index 4b7c302aa376..497264221eff 100644 --- a/packages/server-utils/package.json +++ b/packages/server-utils/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/solid/package.json b/packages/solid/package.json index 484401f88e93..b29ee64e49ae 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build", diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index ac54823eb596..d3a6995a64f3 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18.19.1" + "node": ">=20.19.0" }, "files": [ "/build", diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 77a8368f0138..4d55822856d5 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index b61b9df48341..295874a76ea8 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index a47644115ac4..a75fdbd87d11 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", diff --git a/packages/types/package.json b/packages/types/package.json index a6f5d8bc8f34..2f7ec5ee3025 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index a6e04692401c..1ed4f5d6e3b7 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build" diff --git a/packages/vue/package.json b/packages/vue/package.json index 58dacc73417a..0ffe1ae27049 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build", diff --git a/packages/wasm/package.json b/packages/wasm/package.json index a2a33011f48b..88b30dc6abaf 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "files": [ "/build/npm" diff --git a/scripts/ci-unit-tests.ts b/scripts/ci-unit-tests.ts index 8d4032f62b5a..7bb6f6a4d8d2 100644 --- a/scripts/ci-unit-tests.ts +++ b/scripts/ci-unit-tests.ts @@ -4,7 +4,6 @@ import * as path from 'path'; const UNIT_TEST_ENV = process.env.UNIT_TEST_ENV as 'node' | 'browser' | undefined; const RUN_AFFECTED = process.argv.includes('--affected'); -const NODE_VERSION = process.env.NODE_VERSION as '18' | '20' | '22' | '24'; // These packages are tested separately in CI, so no need to run them here const DEFAULT_SKIP_PACKAGES = ['@sentry/bun', '@sentry/deno']; @@ -27,9 +26,6 @@ const BROWSER_TEST_PACKAGES = [ '@sentry/wasm', ]; -// Packages that cannot run in Node 18 -const SKIP_NODE_18_PACKAGES = ['@sentry/react-router']; - function getAllPackages(): string[] { const { workspaces }: { workspaces: string[] } = JSON.parse( fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'), @@ -60,10 +56,6 @@ function runTests(): void { }); } else if (UNIT_TEST_ENV === 'node') { BROWSER_TEST_PACKAGES.forEach(pkg => ignores.add(pkg)); - - if (NODE_VERSION === '18') { - SKIP_NODE_18_PACKAGES.forEach(pkg => ignores.add(pkg)); - } } if (RUN_AFFECTED) { From 77caf0d2371fe2acfdd2675ba2b96f1b1d1fb071 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 24 Jul 2026 09:38:46 +0200 Subject: [PATCH 2/4] Remove redundant vendoring NOTICE comment --- .../node/src/integrations/node-fetch/undici-instrumentation.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/node/src/integrations/node-fetch/undici-instrumentation.ts b/packages/node/src/integrations/node-fetch/undici-instrumentation.ts index d100f8c3d3b2..c20a0ef4ae13 100644 --- a/packages/node/src/integrations/node-fetch/undici-instrumentation.ts +++ b/packages/node/src/integrations/node-fetch/undici-instrumentation.ts @@ -12,8 +12,6 @@ * - Dropped the `@opentelemetry/instrumentation` base (undici reports via `diagnostics_channel`, * so no module patching was needed) — exposed as a plain `instrumentUndici()` function that the * integration wires up directly - * - Dropped the pre-18.19.0 `diagnostics_channel` ref-counting workaround, since the SDK - * requires Node >=20.19.0 */ /* eslint-disable max-lines */ From 92177711241215e0c67392e58ea18fc0edb85343 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 24 Jul 2026 10:23:18 +0200 Subject: [PATCH 3/4] Narrow engines.node to exclude Node versions without HTTP diagnostics channel support --- dev-packages/browser-integration-tests/package.json | 2 +- dev-packages/bun-integration-tests/package.json | 2 +- dev-packages/clear-cache-gh-action/package.json | 2 +- dev-packages/cloudflare-integration-tests/package.json | 2 +- dev-packages/deno-integration-tests/package.json | 2 +- .../e2e-tests/test-applications/ember-classic/package.json | 2 +- .../e2e-tests/test-applications/ember-embroider/package.json | 2 +- dev-packages/external-contributor-gh-action/package.json | 2 +- dev-packages/node-integration-tests/package.json | 2 +- dev-packages/size-limit-gh-action/package.json | 2 +- dev-packages/test-utils/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/bundler-plugins/package.json | 2 +- packages/cloudflare/package.json | 2 +- packages/core/package.json | 2 +- packages/effect/package.json | 2 +- packages/elysia/package.json | 2 +- packages/ember/package.json | 2 +- packages/eslint-config-sdk/package.json | 2 +- packages/eslint-plugin-sdk/package.json | 2 +- packages/feedback/package.json | 2 +- packages/gatsby/package.json | 2 +- packages/google-cloud-serverless/package.json | 2 +- packages/hono/package.json | 2 +- packages/integration-shims/package.json | 2 +- packages/nestjs/package.json | 2 +- packages/nextjs/package.json | 2 +- packages/nitro/package.json | 2 +- packages/node-native/package.json | 2 +- packages/node/package.json | 2 +- packages/nuxt/package.json | 2 +- packages/opentelemetry/package.json | 2 +- packages/profiling-node/package.json | 2 +- packages/react-router/package.json | 2 +- packages/react/package.json | 2 +- packages/remix/package.json | 2 +- packages/replay-canvas/package.json | 2 +- packages/replay-internal/package.json | 2 +- packages/replay-worker/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/types/package.json | 2 +- packages/vercel-edge/package.json | 2 +- packages/vue/package.json | 2 +- packages/wasm/package.json | 2 +- 54 files changed, 54 insertions(+), 54 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index 8e936fa20ef2..0a7a6c1b7d1f 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -4,7 +4,7 @@ "main": "index.js", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "private": true, "scripts": { diff --git a/dev-packages/bun-integration-tests/package.json b/dev-packages/bun-integration-tests/package.json index 3c24abf0327f..5a3f018a7faf 100644 --- a/dev-packages/bun-integration-tests/package.json +++ b/dev-packages/bun-integration-tests/package.json @@ -3,7 +3,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "private": true, "scripts": { diff --git a/dev-packages/clear-cache-gh-action/package.json b/dev-packages/clear-cache-gh-action/package.json index 984f84d8cbe2..4f71c378d3b5 100644 --- a/dev-packages/clear-cache-gh-action/package.json +++ b/dev-packages/clear-cache-gh-action/package.json @@ -4,7 +4,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "private": true, "main": "index.mjs", diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json index b623ada6414c..961e60b7307a 100644 --- a/dev-packages/cloudflare-integration-tests/package.json +++ b/dev-packages/cloudflare-integration-tests/package.json @@ -3,7 +3,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "private": true, "scripts": { diff --git a/dev-packages/deno-integration-tests/package.json b/dev-packages/deno-integration-tests/package.json index d2d7ca5e434d..f9093d7e34dc 100644 --- a/dev-packages/deno-integration-tests/package.json +++ b/dev-packages/deno-integration-tests/package.json @@ -3,7 +3,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/package.json b/dev-packages/e2e-tests/test-applications/ember-classic/package.json index 74abad21cb08..1fcfbea1dc2e 100644 --- a/dev-packages/e2e-tests/test-applications/ember-classic/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-classic/package.json @@ -72,7 +72,7 @@ "webpack": "~5.104.1" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "resolutions": { "@babel/traverse": "~7.25.9", diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json index 81ec1e52fa39..d733eb08df19 100644 --- a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json @@ -61,7 +61,7 @@ "typescript": "^5.4.5" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "ember": { "edition": "octane" diff --git a/dev-packages/external-contributor-gh-action/package.json b/dev-packages/external-contributor-gh-action/package.json index b8018de64db0..7d66114b93f6 100644 --- a/dev-packages/external-contributor-gh-action/package.json +++ b/dev-packages/external-contributor-gh-action/package.json @@ -4,7 +4,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "private": true, "main": "index.mjs", diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 32fa5c18f0aa..595611a1e888 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -3,7 +3,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "private": true, "main": "build/cjs/index.js", diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json index 5b40b839d3bb..5ce36a062fe9 100644 --- a/dev-packages/size-limit-gh-action/package.json +++ b/dev-packages/size-limit-gh-action/package.json @@ -4,7 +4,7 @@ "version": "10.67.0", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "private": true, "main": "index.mjs", diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json index 3f4dc95f32d8..4e288b3f7864 100644 --- a/dev-packages/test-utils/package.json +++ b/dev-packages/test-utils/package.json @@ -27,7 +27,7 @@ }, "sideEffects": false, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "scripts": { "lint:fix": "OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS=true oxlint . --fix --type-aware", diff --git a/packages/angular/package.json b/packages/angular/package.json index 2a32811c8d12..c65597e0241b 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "type": "module", "module": "build/fesm2015/sentry-angular.mjs", diff --git a/packages/astro/package.json b/packages/astro/package.json index 55962a6fb617..824d265f1fbe 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -14,7 +14,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "type": "module", "files": [ diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 3764e29cd7cd..c516d711d6f5 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build/npm", diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json index e0bc52d3f007..ea5a26223bc1 100644 --- a/packages/browser-utils/package.json +++ b/packages/browser-utils/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/browser/package.json b/packages/browser/package.json index 7d160df2a744..cb7f6aec3afa 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build/npm" diff --git a/packages/bun/package.json b/packages/bun/package.json index b1f74c661290..da4d858fa105 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/bundler-plugins/package.json b/packages/bundler-plugins/package.json index 3578a2098eee..bd0b3c7bc725 100644 --- a/packages/bundler-plugins/package.json +++ b/packages/bundler-plugins/package.json @@ -143,7 +143,7 @@ "extends": "../../package.json" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "sideEffects": [ "./sentry-release-injection-file.js", diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 0afd30c63e26..b8dba6e25e52 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/core/package.json b/packages/core/package.json index e9f0e8318222..af00b77c1de4 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build", diff --git a/packages/effect/package.json b/packages/effect/package.json index 3d8f33af39c7..ced278816997 100644 --- a/packages/effect/package.json +++ b/packages/effect/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/elysia/package.json b/packages/elysia/package.json index 23f394cc5499..ccb8f869dfdd 100644 --- a/packages/elysia/package.json +++ b/packages/elysia/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/ember/package.json b/packages/ember/package.json index ab37d32837a8..441b6aa2ef53 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -82,7 +82,7 @@ "webpack": "~5.104.1" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "ember": { "edition": "octane" diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json index d583bfbaa034..91ae0b1fdfb0 100644 --- a/packages/eslint-config-sdk/package.json +++ b/packages/eslint-config-sdk/package.json @@ -12,7 +12,7 @@ "sentry" ], "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/src" diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json index c8f01320c928..a295fc81fdd2 100644 --- a/packages/eslint-plugin-sdk/package.json +++ b/packages/eslint-plugin-sdk/package.json @@ -12,7 +12,7 @@ "sentry" ], "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/src" diff --git a/packages/feedback/package.json b/packages/feedback/package.json index 9c48c4347a02..47d203e41721 100644 --- a/packages/feedback/package.json +++ b/packages/feedback/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build/npm" diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 20e35b706b5d..2e502b404072 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -11,7 +11,7 @@ "gatsby-plugin" ], "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build", diff --git a/packages/google-cloud-serverless/package.json b/packages/google-cloud-serverless/package.json index 706bc9cf5136..31e6a3d823b2 100644 --- a/packages/google-cloud-serverless/package.json +++ b/packages/google-cloud-serverless/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/hono/package.json b/packages/hono/package.json index 3a515bbd6073..e40f9ea13d4d 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/integration-shims/package.json b/packages/integration-shims/package.json index f809cfcf7f72..35d0d6f9c89f 100644 --- a/packages/integration-shims/package.json +++ b/packages/integration-shims/package.json @@ -50,7 +50,7 @@ "@sentry/core": "10.67.0" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 92f5e1e58236..1e781fc79eb2 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index b027c28c60d0..09cf846ece3f 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", diff --git a/packages/nitro/package.json b/packages/nitro/package.json index 5660288e8d6e..ec0e35fcb399 100644 --- a/packages/nitro/package.json +++ b/packages/nitro/package.json @@ -14,7 +14,7 @@ "error-tracking" ], "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/node-native/package.json b/packages/node-native/package.json index 0a2478827c43..fbee84046e43 100644 --- a/packages/node-native/package.json +++ b/packages/node-native/package.json @@ -31,7 +31,7 @@ } }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "publishConfig": { "access": "public" diff --git a/packages/node/package.json b/packages/node/package.json index 69d4c1b18ea4..89ad77dbc181 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 8f504a3ae072..e758e5799bb0 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 2642c0cc7e03..5d7810c30611 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index 09b60aea12a3..7ece3c425827 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -26,7 +26,7 @@ "sentry-prune-profiler-binaries": "scripts/prune-profiler-binaries.js" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "publishConfig": { "access": "public" diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 5292a8965cdf..0108b4f650da 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/react/package.json b/packages/react/package.json index 10b59e292bdd..5f9295db8479 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/remix/package.json b/packages/remix/package.json index b19abbb1343d..92cabfe9a8dc 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -10,7 +10,7 @@ "sentry-upload-sourcemaps": "scripts/sentry-upload-sourcemaps.js" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build", diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 676c7e7a373c..6b57dbe06edd 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -64,7 +64,7 @@ "@sentry/core": "10.67.0" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index 0b03e8d87c2e..b46272836031 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -83,7 +83,7 @@ "@sentry/core": "10.67.0" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/replay-worker/package.json b/packages/replay-worker/package.json index 306e9c5df60f..fbfcb04a11c9 100644 --- a/packages/replay-worker/package.json +++ b/packages/replay-worker/package.json @@ -57,7 +57,7 @@ "fflate": "0.8.2" }, "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/server-utils/package.json b/packages/server-utils/package.json index 497264221eff..3d0f564dc2a5 100644 --- a/packages/server-utils/package.json +++ b/packages/server-utils/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/solid/package.json b/packages/solid/package.json index b29ee64e49ae..64add39d2488 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build", diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index d3a6995a64f3..d2a4196fc90c 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build", diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 4d55822856d5..4151002b92ac 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 295874a76ea8..dfb79075f0fb 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index a75fdbd87d11..e48b14426041 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", diff --git a/packages/types/package.json b/packages/types/package.json index 2f7ec5ee3025..6c5e85858c31 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index 1ed4f5d6e3b7..eafcdfdd4704 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build" diff --git a/packages/vue/package.json b/packages/vue/package.json index 0ffe1ae27049..fa22b3bcd6b0 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build", diff --git a/packages/wasm/package.json b/packages/wasm/package.json index 88b30dc6abaf..367890d8b073 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -7,7 +7,7 @@ "author": "Sentry", "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, "files": [ "/build/npm" From 9ba7e02291c2ec62f6f1b0aef590e2aa061a7341 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 24 Jul 2026 10:23:23 +0200 Subject: [PATCH 4/4] Remove sync LocalVariables integration --- .../src/integrations/local-variables/index.ts | 4 +- .../local-variables/local-variables-sync.ts | 433 ------------------ .../test/integrations/localvariables.test.ts | 77 +--- 3 files changed, 2 insertions(+), 512 deletions(-) delete mode 100644 packages/node/src/integrations/local-variables/local-variables-sync.ts diff --git a/packages/node/src/integrations/local-variables/index.ts b/packages/node/src/integrations/local-variables/index.ts index 13a580383fcf..f441dd726286 100644 --- a/packages/node/src/integrations/local-variables/index.ts +++ b/packages/node/src/integrations/local-variables/index.ts @@ -1,9 +1,7 @@ import type { Integration } from '@sentry/core'; -import { NODE_VERSION } from '../../nodeVersion'; import type { LocalVariablesIntegrationOptions } from './common'; import { localVariablesAsyncIntegration } from './local-variables-async'; -import { localVariablesSyncIntegration } from './local-variables-sync'; export const localVariablesIntegration = (options: LocalVariablesIntegrationOptions = {}): Integration => { - return NODE_VERSION.major < 19 ? localVariablesSyncIntegration(options) : localVariablesAsyncIntegration(options); + return localVariablesAsyncIntegration(options); }; diff --git a/packages/node/src/integrations/local-variables/local-variables-sync.ts b/packages/node/src/integrations/local-variables/local-variables-sync.ts deleted file mode 100644 index 043132fcb275..000000000000 --- a/packages/node/src/integrations/local-variables/local-variables-sync.ts +++ /dev/null @@ -1,433 +0,0 @@ -import type { Debugger, InspectorNotification, Runtime, Session } from 'node:inspector'; -import type { CollectBehavior, Event, Exception, IntegrationFn, StackFrame, StackParser } from '@sentry/core'; -import { debug, defineIntegration, getClient, LRUMap } from '@sentry/core'; -import { NODE_MAJOR } from '../../nodeVersion'; -import type { NodeClient } from '../../sdk/client'; -import { isDebuggerEnabled } from '../../utils/debug'; -import type { - FrameVariables, - LocalVariablesIntegrationOptions, - PausedExceptionEvent, - RateLimitIncrement, - Variables, -} from './common'; -import { createRateLimiter, filterFrameVariables, functionNamesMatch } from './common'; - -/** Creates a unique hash from stack frames */ -export function hashFrames(frames: StackFrame[] | undefined): string | undefined { - if (frames === undefined) { - return; - } - - // Only hash the 10 most recent frames (ie. the last 10) - return frames.slice(-10).reduce((acc, frame) => `${acc},${frame.function},${frame.lineno},${frame.colno}`, ''); -} - -/** - * We use the stack parser to create a unique hash from the exception stack trace - * This is used to lookup vars when the exception passes through the event processor - */ -export function hashFromStack(stackParser: StackParser, stack: string | undefined): string | undefined { - if (stack === undefined) { - return undefined; - } - - return hashFrames(stackParser(stack, 1)); -} - -type OnPauseEvent = InspectorNotification; -export interface DebugSession { - /** Configures and connects to the debug session */ - configureAndConnect(onPause: (message: OnPauseEvent, complete: () => void) => void, captureAll: boolean): void; - /** Updates which kind of exceptions to capture */ - setPauseOnExceptions(captureAll: boolean): void; - /** Gets local variables for an objectId */ - getLocalVariables(objectId: string, callback: (vars: Variables) => void): void; -} - -type Next = (result: T) => void; -type Add = (fn: Next) => void; -type CallbackWrapper = { add: Add; next: Next }; - -/** Creates a container for callbacks to be called sequentially */ -export function createCallbackList(complete: Next): CallbackWrapper { - // A collection of callbacks to be executed last to first - let callbacks: Next[] = []; - - let completedCalled = false; - function checkedComplete(result: T): void { - callbacks = []; - if (completedCalled) { - return; - } - completedCalled = true; - complete(result); - } - - // complete should be called last - callbacks.push(checkedComplete); - - function add(fn: Next): void { - callbacks.push(fn); - } - - function next(result: T): void { - const popped = callbacks.pop() || checkedComplete; - - try { - popped(result); - } catch { - // If there is an error, we still want to call the complete callback - checkedComplete(result); - } - } - - return { add, next }; -} - -/** - * Promise API is available as `Experimental` and in Node 19 only. - * - * Callback-based API is `Stable` since v14 and `Experimental` since v8. - * Because of that, we are creating our own `AsyncSession` class. - * - * https://nodejs.org/docs/latest-v19.x/api/inspector.html#promises-api - * https://nodejs.org/docs/latest-v14.x/api/inspector.html - */ -class AsyncSession implements DebugSession { - /** Throws if inspector API is not available */ - private constructor(private readonly _session: Session) { - // - } - - public static async create(orDefault?: DebugSession | undefined): Promise { - if (orDefault) { - return orDefault; - } - - const inspector = await import('node:inspector'); - return new AsyncSession(new inspector.Session()); - } - - /** @inheritdoc */ - public configureAndConnect(onPause: (event: OnPauseEvent, complete: () => void) => void, captureAll: boolean): void { - this._session.connect(); - - this._session.on('Debugger.paused', event => { - onPause(event, () => { - // After the pause work is complete, resume execution or the exception context memory is leaked - this._session.post('Debugger.resume'); - }); - }); - - this._session.post('Debugger.enable'); - this._session.post('Debugger.setPauseOnExceptions', { state: captureAll ? 'all' : 'uncaught' }); - } - - public setPauseOnExceptions(captureAll: boolean): void { - this._session.post('Debugger.setPauseOnExceptions', { state: captureAll ? 'all' : 'uncaught' }); - } - - /** @inheritdoc */ - public getLocalVariables(objectId: string, complete: (vars: Variables) => void): void { - this._getProperties(objectId, props => { - const { add, next } = createCallbackList(complete); - - for (const prop of props) { - if (prop.value?.objectId && prop.value.className === 'Array') { - const id = prop.value.objectId; - add(vars => this._unrollArray(id, prop.name, vars, next)); - } else if (prop.value?.objectId && prop.value.className === 'Object') { - const id = prop.value.objectId; - add(vars => this._unrollObject(id, prop.name, vars, next)); - } else if (prop.value) { - add(vars => this._unrollOther(prop, vars, next)); - } - } - - next({}); - }); - } - - /** - * Gets all the PropertyDescriptors of an object - */ - private _getProperties(objectId: string, next: (result: Runtime.PropertyDescriptor[]) => void): void { - this._session.post( - 'Runtime.getProperties', - { - objectId, - ownProperties: true, - }, - (err, params) => { - if (err) { - next([]); - } else { - next(params.result); - } - }, - ); - } - - /** - * Unrolls an array property - */ - private _unrollArray(objectId: string, name: string, vars: Variables, next: (vars: Variables) => void): void { - this._getProperties(objectId, props => { - vars[name] = props - .filter(v => v.name !== 'length' && !isNaN(parseInt(v.name, 10))) - .sort((a, b) => parseInt(a.name, 10) - parseInt(b.name, 10)) - .map(v => v.value?.value); - - next(vars); - }); - } - - /** - * Unrolls an object property - */ - private _unrollObject(objectId: string, name: string, vars: Variables, next: (obj: Variables) => void): void { - this._getProperties(objectId, props => { - vars[name] = props - .map<[string, unknown]>(v => [v.name, v.value?.value]) - .reduce((obj, [key, val]) => { - obj[key] = val; - return obj; - }, {} as Variables); - - next(vars); - }); - } - - /** - * Unrolls other properties - */ - private _unrollOther(prop: Runtime.PropertyDescriptor, vars: Variables, next: (vars: Variables) => void): void { - if (prop.value) { - if ('value' in prop.value) { - if (prop.value.value === undefined || prop.value.value === null) { - vars[prop.name] = `<${prop.value.value}>`; - } else { - vars[prop.name] = prop.value.value; - } - } else if ('description' in prop.value && prop.value.type !== 'function') { - vars[prop.name] = `<${prop.value.description}>`; - } else if (prop.value.type === 'undefined') { - vars[prop.name] = ''; - } - } - - next(vars); - } -} - -const INTEGRATION_NAME = 'LocalVariables' as const; - -/** - * Adds local variables to exception frames - */ -const _localVariablesSyncIntegration = (( - options: LocalVariablesIntegrationOptions = {}, - sessionOverride?: DebugSession, -) => { - const cachedFrames: LRUMap = new LRUMap(20); - let rateLimiter: RateLimitIncrement | undefined; - let shouldProcessEvent = false; - - function addLocalVariablesToException(exception: Exception, behavior: CollectBehavior): void { - const hash = hashFrames(exception.stacktrace?.frames); - - if (hash === undefined) { - return; - } - - // Check if we have local variables for an exception that matches the hash - // remove is identical to get but also removes the entry from the cache - const cachedFrame = cachedFrames.remove(hash); - - // When disabled, nothing is collected so we don't attach empty `vars` to frames - if (cachedFrame === undefined || behavior === false) { - return; - } - - // Filter out frames where the function name is `new Promise` since these are in the error.stack frames - // but do not appear in the debugger call frames - const frames = (exception.stacktrace?.frames || []).filter(frame => frame.function !== 'new Promise'); - - for (let i = 0; i < frames.length; i++) { - // Sentry frames are in reverse order - const frameIndex = frames.length - i - 1; - - const cachedFrameVariable = cachedFrame[i]; - const frameVariable = frames[frameIndex]; - - // Drop out if we run out of frames to match up - if (!frameVariable || !cachedFrameVariable) { - break; - } - - if ( - // We need to have vars to add - cachedFrameVariable.vars === undefined || - // Only skip out-of-app frames if includeOutOfAppFrames is not true - (frameVariable.in_app === false && options.includeOutOfAppFrames !== true) || - // The function names need to match - !functionNamesMatch(frameVariable.function, cachedFrameVariable.function) - ) { - continue; - } - - frameVariable.vars = filterFrameVariables(cachedFrameVariable.vars, behavior); - } - } - - function addLocalVariablesToEvent(event: Event): Event { - for (const exception of event.exception?.values || []) { - addLocalVariablesToException(exception, getClient()?.getDataCollectionOptions().stackFrameVariables ?? true); - } - - return event; - } - - let setupPromise: Promise | undefined; - - async function setup(): Promise { - const client = getClient(); - const clientOptions = client?.getOptions(); - - if (!clientOptions?.includeLocalVariables) { - return; - } - - // Only setup this integration if the Node version is >= v18 - // https://github.com/getsentry/sentry-javascript/issues/7697 - const unsupportedNodeVersion = NODE_MAJOR < 18; - - if (unsupportedNodeVersion) { - debug.log('The `LocalVariables` integration is only supported on Node >= v18.'); - return; - } - - if (await isDebuggerEnabled()) { - debug.warn('Local variables capture has been disabled because the debugger was already enabled'); - return; - } - - try { - const session = await AsyncSession.create(sessionOverride); - - const handlePaused = ( - stackParser: StackParser, - { params: { reason, data, callFrames } }: InspectorNotification, - complete: () => void, - ): void => { - if (reason !== 'exception' && reason !== 'promiseRejection') { - complete(); - return; - } - - rateLimiter?.(); - - // data.description contains the original error.stack - const exceptionHash = hashFromStack(stackParser, data.description); - - if (exceptionHash == undefined) { - complete(); - return; - } - - const { add, next } = createCallbackList(frames => { - cachedFrames.set(exceptionHash, frames); - complete(); - }); - - // Because we're queuing up and making all these calls synchronously, we can potentially overflow the stack - // For this reason we only attempt to get local variables for the first 5 frames - for (let i = 0; i < Math.min(callFrames.length, 5); i++) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const { scopeChain, functionName, this: obj } = callFrames[i]!; - - const localScope = scopeChain.find(scope => scope.type === 'local'); - - // obj.className is undefined in ESM modules - const fn = obj.className === 'global' || !obj.className ? functionName : `${obj.className}.${functionName}`; - - if (localScope?.object.objectId === undefined) { - add(frames => { - frames[i] = { function: fn }; - next(frames); - }); - } else { - const id = localScope.object.objectId; - add(frames => - session.getLocalVariables(id, vars => { - frames[i] = { function: fn, vars }; - next(frames); - }), - ); - } - } - - next([]); - }; - - const captureAll = options.captureAllExceptions !== false; - - session.configureAndConnect( - (ev, complete) => - handlePaused(clientOptions.stackParser, ev as InspectorNotification, complete), - captureAll, - ); - - if (captureAll) { - const max = options.maxExceptionsPerSecond || 50; - - rateLimiter = createRateLimiter( - max, - () => { - debug.log('Local variables rate-limit lifted.'); - session.setPauseOnExceptions(true); - }, - seconds => { - debug.log( - `Local variables rate-limit exceeded. Disabling capturing of caught exceptions for ${seconds} seconds.`, - ); - session.setPauseOnExceptions(false); - }, - ); - } - - shouldProcessEvent = true; - } catch (error) { - debug.log('The `LocalVariables` integration failed to start.', error); - } - } - - return { - name: INTEGRATION_NAME, - setupOnce() { - setupPromise = setup(); - }, - async processEvent(event: Event): Promise { - await setupPromise; - - if (shouldProcessEvent) { - return addLocalVariablesToEvent(event); - } - - return event; - }, - // These are entirely for testing - _getCachedFramesCount(): number { - return cachedFrames.size; - }, - _getFirstCachedFrame(): FrameVariables[] | undefined { - return cachedFrames.values()[0]; - }, - }; -}) satisfies IntegrationFn; - -/** - * Adds local variables to exception frames. - */ -export const localVariablesSyncIntegration = defineIntegration(_localVariablesSyncIntegration); diff --git a/packages/node/test/integrations/localvariables.test.ts b/packages/node/test/integrations/localvariables.test.ts index 82e43ef34135..0b42e2f702e9 100644 --- a/packages/node/test/integrations/localvariables.test.ts +++ b/packages/node/test/integrations/localvariables.test.ts @@ -1,11 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createRateLimiter, filterFrameVariables } from '../../src/integrations/local-variables/common'; -import { createCallbackList } from '../../src/integrations/local-variables/local-variables-sync'; -import { NODE_MAJOR } from '../../src/nodeVersion'; -const describeIf = (condition: boolean) => (condition ? describe : describe.skip); - -describeIf(NODE_MAJOR >= 18)('LocalVariables', () => { +describe('LocalVariables', () => { beforeEach(() => { vi.useFakeTimers(); }); @@ -42,77 +38,6 @@ describeIf(NODE_MAJOR >= 18)('LocalVariables', () => { }); }); - describe('createCallbackList', () => { - it('Should call callbacks in reverse order', () => - new Promise(done => { - const log: number[] = []; - - const { add, next } = createCallbackList(n => { - expect(log).toEqual([5, 4, 3, 2, 1]); - expect(n).toBe(15); - done(); - }); - - add(n => { - log.push(1); - next(n + 1); - }); - - add(n => { - log.push(2); - next(n + 1); - }); - - add(n => { - log.push(3); - next(n + 1); - }); - - add(n => { - log.push(4); - next(n + 1); - }); - - add(n => { - log.push(5); - next(n + 11); - }); - - next(0); - })); - - it('only calls complete once even if multiple next', () => - new Promise(done => { - const { add, next } = createCallbackList(n => { - expect(n).toBe(1); - done(); - }); - - add(n => { - next(n + 1); - // We dont actually do this in our code... - next(n + 1); - }); - - next(0); - })); - - it('calls completed if added closure throws', () => - new Promise(done => { - const { add, next } = createCallbackList(n => { - expect(n).toBe(10); - done(); - }); - - add(n => { - throw new Error('test'); - next(n + 1); - }); - - next(10); - })); - }); - describe('rateLimiter', () => { it('calls disable if exceeded', () => new Promise(done => {