From ad41a1a6c65d7f6d8d561eb4d0fdb1bec0ee62ef Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Tue, 18 Aug 2026 10:41:43 +0200 Subject: [PATCH 1/2] feat(migration): add interactive v10-to-v11 JavaScript migration guide Adds an interactive guide for the Sentry JavaScript SDK v10-to-v11 upgrade, rendered into every JavaScript platform and guide page via ``. - 88 breaking changes as standalone MDX items in `includes/migration/javascript-v11/`, each tagged with the phase, severity, frameworks, platform category and features it applies to - A server component that drops items the current guide cannot use, and a client shell owning facet filtering, per-guide checklist state and the copy-for-agent prompt - Setup detection that reads facets and the SDK package off a pasted `package.json` or `Sentry.init()` block, and links readers to the right guide when they are on the wrong one - A frontmatter validator run by `pnpm test`, with `pnpm migration-items` for the breakdown by phase, severity and category - Interactive chrome is excluded from the `.md` exports, so agents fetching a page get the guide without controls they cannot operate Adds the missing `categories` frontmatter to the Effect guide, without which the migration page showed browser-only items on a server-only SDK. Co-Authored-By: Claude Opus 5 (1M context) --- .../common/migration/v10-to-v11.mdx | 45 ++ .../javascript/guides/effect/index.mdx | 4 + .../javascript-v11/ai-exports-moved.mdx | 17 + .../ai-internal-exports-removed.mdx | 17 + .../javascript-v11/ai-non-inference-spans.mdx | 15 + .../javascript-v11/astro-min-version.mdx | 15 + .../javascript-v11/astro-runtime-options.mdx | 44 ++ .../attach-stacktrace-default.mdx | 15 + .../javascript-v11/aws-lambda-layer.mdx | 15 + .../before-send-span-format.mdx | 49 ++ .../before-send-transaction-removed.mdx | 41 + .../browser-http-headers-data-collection.mdx | 15 + .../browser-session-lifecycle-page.mdx | 21 + .../browser-session-unhandled.mdx | 15 + .../javascript-v11/browser-support.mdx | 15 + .../javascript-v11/browser-user-timing.mdx | 32 + .../browser-web-vital-options.mdx | 15 + .../channel-based-instrumentation.mdx | 15 + .../child-process-worker-split.mdx | 15 + .../cloudflare-instrument-d1.mdx | 28 + ...loudflare-instrument-prototype-methods.mdx | 26 + .../cloudflare-nodejs-compat-subpath.mdx | 18 + .../cloudflare-nodejs-compat.mdx | 20 + .../cloudflare-rpc-trace-propagation.mdx | 15 + .../cloudflare-type-changes.mdx | 33 + .../cloudflare-vite-auto-instrumentation.mdx | 22 + .../cloudflare-wrap-request-handler.mdx | 18 + .../javascript-v11/console-breadcrumbs.mdx | 15 + .../javascript-v11/core-removed-apis.mdx | 23 + .../javascript-v11/custom-otel-setup.mdx | 50 ++ .../javascript-v11/data-collection.mdx | 49 ++ .../deno-integrations-rename.mdx | 24 + .../migration/javascript-v11/deno-version.mdx | 15 + .../ember-app-instance-required.mdx | 15 + .../javascript-v11/ember-v2-addon.mdx | 55 ++ .../javascript-v11/enable-logs-removed.mdx | 23 + .../javascript-v11/enable-metrics-removed.mdx | 28 + .../enable-opentelemetry-setup.mdx | 27 + .../javascript-v11/event-filters-rename.mdx | 29 + .../javascript-v11/fastify-min-version.mdx | 15 + .../hono-integration-removed.mdx | 23 + .../hydrogen-legacy-dropped.mdx | 15 + .../ignore-transactions-removed.mdx | 29 + .../javascript-v11/inp-web-vital-span.mdx | 15 + .../instrument-state-graph-rename.mdx | 21 + .../interaction-spans-integration.mdx | 27 + .../javascript-v11/metrics-cdn-bundle.mdx | 15 + .../javascript-v11/nextjs-min-version.mdx | 15 + .../nextjs-react-component-annotation.mdx | 24 + .../javascript-v11/nextjs-removed-options.mdx | 29 + .../nextjs-template-tracing.mdx | 15 + .../javascript-v11/node-core-merged.mdx | 23 + .../node-loader-entry-points.mdx | 29 + .../javascript-v11/node-removed-apis.mdx | 24 + .../migration/javascript-v11/node-version.mdx | 24 + .../nuxt-source-maps-options.mdx | 31 + .../javascript-v11/opentelemetry-interop.mdx | 19 + .../opentelemetry-removed-apis.mdx | 15 + .../otlp-integration-changes.mdx | 27 + .../profiling-legacy-options.mdx | 26 + .../profiling-node-prune-script.mdx | 15 + .../react-error-boundary-type.mdx | 15 + .../javascript-v11/react-min-version.mdx | 15 + .../react-router-min-version.mdx | 15 + .../react-router-server-wrappers.mdx | 19 + .../react-router-source-maps-options.mdx | 15 + .../javascript-v11/remix-action-form-data.mdx | 28 + .../javascript-v11/remix-min-version.mdx | 15 + .../request-data-integration.mdx | 15 + .../javascript-v11/require-to-import.mdx | 25 + .../javascript-v11/sentry-cli-v4.mdx | 15 + .../javascript-v11/sentry-types-removed.mdx | 23 + .../server-utils-removed-exports.mdx | 15 + .../solidstart-source-maps-options.mdx | 33 + .../javascript-v11/span-attribute-changes.mdx | 50 ++ .../javascript-v11/span-op-changes.mdx | 83 ++ .../javascript-v11/span-streaming-default.mdx | 19 + .../javascript-v11/span-streaming-opt-out.mdx | 27 + .../javascript-v11/span-to-json-format.mdx | 25 + .../sveltekit-source-maps-options.mdx | 33 + .../javascript-v11/sveltekit-vite-subpath.mdx | 23 + .../javascript-v11/tanstackstart-removed.mdx | 15 + ...ird-party-error-filter-internal-frames.mdx | 15 + .../trace-propagation-targets-case.mdx | 15 + .../track-fetch-stream-performance.mdx | 27 + .../migration/javascript-v11/type-changes.mdx | 21 + .../javascript-v11/typescript-version.mdx | 15 + .../unstable-bundler-plugin-options.mdx | 36 + .../javascript-v11/vercel-ai-v3-dropped.mdx | 15 + .../with-streamed-span-noop.mdx | 23 + package.json | 1 + scripts/generate-md-exports.mjs | 140 ++-- scripts/validate-migration-items.mjs | 189 +++++ src/components/migrationGuide/client.tsx | 748 ++++++++++++++++++ src/components/migrationGuide/constants.ts | 171 ++++ src/components/migrationGuide/detect.ts | 355 +++++++++ src/components/migrationGuide/index.tsx | 130 +++ .../migrationGuide/migrationGuide.spec.ts | 384 +++++++++ .../migrationGuide/styles.module.scss | 425 ++++++++++ src/mdxComponents.ts | 2 + src/metrics.ts | 4 +- 101 files changed, 4596 insertions(+), 72 deletions(-) create mode 100644 docs/platforms/javascript/common/migration/v10-to-v11.mdx create mode 100644 includes/migration/javascript-v11/ai-exports-moved.mdx create mode 100644 includes/migration/javascript-v11/ai-internal-exports-removed.mdx create mode 100644 includes/migration/javascript-v11/ai-non-inference-spans.mdx create mode 100644 includes/migration/javascript-v11/astro-min-version.mdx create mode 100644 includes/migration/javascript-v11/astro-runtime-options.mdx create mode 100644 includes/migration/javascript-v11/attach-stacktrace-default.mdx create mode 100644 includes/migration/javascript-v11/aws-lambda-layer.mdx create mode 100644 includes/migration/javascript-v11/before-send-span-format.mdx create mode 100644 includes/migration/javascript-v11/before-send-transaction-removed.mdx create mode 100644 includes/migration/javascript-v11/browser-http-headers-data-collection.mdx create mode 100644 includes/migration/javascript-v11/browser-session-lifecycle-page.mdx create mode 100644 includes/migration/javascript-v11/browser-session-unhandled.mdx create mode 100644 includes/migration/javascript-v11/browser-support.mdx create mode 100644 includes/migration/javascript-v11/browser-user-timing.mdx create mode 100644 includes/migration/javascript-v11/browser-web-vital-options.mdx create mode 100644 includes/migration/javascript-v11/channel-based-instrumentation.mdx create mode 100644 includes/migration/javascript-v11/child-process-worker-split.mdx create mode 100644 includes/migration/javascript-v11/cloudflare-instrument-d1.mdx create mode 100644 includes/migration/javascript-v11/cloudflare-instrument-prototype-methods.mdx create mode 100644 includes/migration/javascript-v11/cloudflare-nodejs-compat-subpath.mdx create mode 100644 includes/migration/javascript-v11/cloudflare-nodejs-compat.mdx create mode 100644 includes/migration/javascript-v11/cloudflare-rpc-trace-propagation.mdx create mode 100644 includes/migration/javascript-v11/cloudflare-type-changes.mdx create mode 100644 includes/migration/javascript-v11/cloudflare-vite-auto-instrumentation.mdx create mode 100644 includes/migration/javascript-v11/cloudflare-wrap-request-handler.mdx create mode 100644 includes/migration/javascript-v11/console-breadcrumbs.mdx create mode 100644 includes/migration/javascript-v11/core-removed-apis.mdx create mode 100644 includes/migration/javascript-v11/custom-otel-setup.mdx create mode 100644 includes/migration/javascript-v11/data-collection.mdx create mode 100644 includes/migration/javascript-v11/deno-integrations-rename.mdx create mode 100644 includes/migration/javascript-v11/deno-version.mdx create mode 100644 includes/migration/javascript-v11/ember-app-instance-required.mdx create mode 100644 includes/migration/javascript-v11/ember-v2-addon.mdx create mode 100644 includes/migration/javascript-v11/enable-logs-removed.mdx create mode 100644 includes/migration/javascript-v11/enable-metrics-removed.mdx create mode 100644 includes/migration/javascript-v11/enable-opentelemetry-setup.mdx create mode 100644 includes/migration/javascript-v11/event-filters-rename.mdx create mode 100644 includes/migration/javascript-v11/fastify-min-version.mdx create mode 100644 includes/migration/javascript-v11/hono-integration-removed.mdx create mode 100644 includes/migration/javascript-v11/hydrogen-legacy-dropped.mdx create mode 100644 includes/migration/javascript-v11/ignore-transactions-removed.mdx create mode 100644 includes/migration/javascript-v11/inp-web-vital-span.mdx create mode 100644 includes/migration/javascript-v11/instrument-state-graph-rename.mdx create mode 100644 includes/migration/javascript-v11/interaction-spans-integration.mdx create mode 100644 includes/migration/javascript-v11/metrics-cdn-bundle.mdx create mode 100644 includes/migration/javascript-v11/nextjs-min-version.mdx create mode 100644 includes/migration/javascript-v11/nextjs-react-component-annotation.mdx create mode 100644 includes/migration/javascript-v11/nextjs-removed-options.mdx create mode 100644 includes/migration/javascript-v11/nextjs-template-tracing.mdx create mode 100644 includes/migration/javascript-v11/node-core-merged.mdx create mode 100644 includes/migration/javascript-v11/node-loader-entry-points.mdx create mode 100644 includes/migration/javascript-v11/node-removed-apis.mdx create mode 100644 includes/migration/javascript-v11/node-version.mdx create mode 100644 includes/migration/javascript-v11/nuxt-source-maps-options.mdx create mode 100644 includes/migration/javascript-v11/opentelemetry-interop.mdx create mode 100644 includes/migration/javascript-v11/opentelemetry-removed-apis.mdx create mode 100644 includes/migration/javascript-v11/otlp-integration-changes.mdx create mode 100644 includes/migration/javascript-v11/profiling-legacy-options.mdx create mode 100644 includes/migration/javascript-v11/profiling-node-prune-script.mdx create mode 100644 includes/migration/javascript-v11/react-error-boundary-type.mdx create mode 100644 includes/migration/javascript-v11/react-min-version.mdx create mode 100644 includes/migration/javascript-v11/react-router-min-version.mdx create mode 100644 includes/migration/javascript-v11/react-router-server-wrappers.mdx create mode 100644 includes/migration/javascript-v11/react-router-source-maps-options.mdx create mode 100644 includes/migration/javascript-v11/remix-action-form-data.mdx create mode 100644 includes/migration/javascript-v11/remix-min-version.mdx create mode 100644 includes/migration/javascript-v11/request-data-integration.mdx create mode 100644 includes/migration/javascript-v11/require-to-import.mdx create mode 100644 includes/migration/javascript-v11/sentry-cli-v4.mdx create mode 100644 includes/migration/javascript-v11/sentry-types-removed.mdx create mode 100644 includes/migration/javascript-v11/server-utils-removed-exports.mdx create mode 100644 includes/migration/javascript-v11/solidstart-source-maps-options.mdx create mode 100644 includes/migration/javascript-v11/span-attribute-changes.mdx create mode 100644 includes/migration/javascript-v11/span-op-changes.mdx create mode 100644 includes/migration/javascript-v11/span-streaming-default.mdx create mode 100644 includes/migration/javascript-v11/span-streaming-opt-out.mdx create mode 100644 includes/migration/javascript-v11/span-to-json-format.mdx create mode 100644 includes/migration/javascript-v11/sveltekit-source-maps-options.mdx create mode 100644 includes/migration/javascript-v11/sveltekit-vite-subpath.mdx create mode 100644 includes/migration/javascript-v11/tanstackstart-removed.mdx create mode 100644 includes/migration/javascript-v11/third-party-error-filter-internal-frames.mdx create mode 100644 includes/migration/javascript-v11/trace-propagation-targets-case.mdx create mode 100644 includes/migration/javascript-v11/track-fetch-stream-performance.mdx create mode 100644 includes/migration/javascript-v11/type-changes.mdx create mode 100644 includes/migration/javascript-v11/typescript-version.mdx create mode 100644 includes/migration/javascript-v11/unstable-bundler-plugin-options.mdx create mode 100644 includes/migration/javascript-v11/vercel-ai-v3-dropped.mdx create mode 100644 includes/migration/javascript-v11/with-streamed-span-noop.mdx create mode 100644 scripts/validate-migration-items.mjs create mode 100644 src/components/migrationGuide/client.tsx create mode 100644 src/components/migrationGuide/constants.ts create mode 100644 src/components/migrationGuide/detect.ts create mode 100644 src/components/migrationGuide/index.tsx create mode 100644 src/components/migrationGuide/migrationGuide.spec.ts create mode 100644 src/components/migrationGuide/styles.module.scss diff --git a/docs/platforms/javascript/common/migration/v10-to-v11.mdx b/docs/platforms/javascript/common/migration/v10-to-v11.mdx new file mode 100644 index 0000000000000..7958c277f0cef --- /dev/null +++ b/docs/platforms/javascript/common/migration/v10-to-v11.mdx @@ -0,0 +1,45 @@ +--- +title: Migrate from 10.x to 11.x +sidebar_order: 8870 +description: "Learn about migrating from Sentry JavaScript SDK 10.x to 11.x, with an interactive checklist of the changes that apply to your setup." +notSupported: + - javascript.capacitor + - javascript.cordova + - javascript.electron +--- + + + + +This page shows the changes that apply to browser JavaScript. To see the steps for your setup instead, switch to the guide for it with the platform dropdown in the top-left corner, or go straight to [Next.js](/platforms/javascript/guides/nextjs/migration/v10-to-v11/), [React](/platforms/javascript/guides/react/migration/v10-to-v11/), [Node.js](/platforms/javascript/guides/node/migration/v10-to-v11/), or [any other JavaScript platform](/platforms/javascript/). + + + + +Version 11 of the Sentry JavaScript SDK focuses on better OpenTelemetry interoperability, more flexible instrumentation, and better out-of-the-box defaults. It raises the minimum versions of Node.js, TypeScript, and several frameworks. For many of these breaking changes, you can migrate automatically: + +```bash +npx sentry@latest migrate +``` + +We recommend upgrading to the most recent 10.x release first, since most of what v11 removes is already deprecated there. + +Version 11 is compatible with Sentry self-hosted 24.4.2 or higher, unchanged from v10. Lower versions may continue to work, but may not support all features. + + + +TypeScript and other tooling won't catch all of these changes. Read through the steps below before upgrading, rather than upgrading and fixing what breaks. + + + +## Your Migration Steps + + + +## No Version Support Timeline + +Version support timelines are stressful for everybody using the SDK, so we won't be defining one. Instead, we will be applying bug fixes and features to older versions as long as there is demand. + +Additionally, we hold ourselves accountable to any security issues, meaning that if any vulnerabilities are found, we will in almost all cases backport them. + +It's decided on a case-by-case basis what gets backported. If you need a fix or feature in a previous version of the SDK, please reach out via a [GitHub issue](https://github.com/getsentry/sentry-javascript/issues). diff --git a/docs/platforms/javascript/guides/effect/index.mdx b/docs/platforms/javascript/guides/effect/index.mdx index e77b39975489f..5085426aadae4 100644 --- a/docs/platforms/javascript/guides/effect/index.mdx +++ b/docs/platforms/javascript/guides/effect/index.mdx @@ -2,6 +2,10 @@ title: Effect sdk: sentry.javascript.effect description: Learn how to set up and configure Sentry in your Effect application, capture your first errors, and view them in Sentry. +categories: + - javascript + - server + - server-node --- diff --git a/includes/migration/javascript-v11/ai-exports-moved.mdx b/includes/migration/javascript-v11/ai-exports-moved.mdx new file mode 100644 index 0000000000000..e23b9858bca6b --- /dev/null +++ b/includes/migration/javascript-v11/ai-exports-moved.mdx @@ -0,0 +1,17 @@ +--- +id: ai-exports-moved +title: "AI instrumentation moved out of `@sentry/core`" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [ai-agents] +platformCategory: server +order: 270 +--- + +AI instrumentation moved from `@sentry/core` to `@sentry/server-utils`, and is no longer available in the browser SDK. The `enableTruncation` and `streamGenAiSpans` flags were removed, along with the internal `sentry.sdk_meta.gen_ai.input.messages.original_length` attribute. + +Importing a helper from `@sentry/node` or another platform SDK still works. If you imported one directly from `@sentry/core`, import it from `@sentry/server-utils` instead: `instrumentOpenAiClient`, `instrumentAnthropicAiClient`, `instrumentGoogleGenAIClient`, `instrumentWorkersAiClient`, `createLangChainCallbackHandler`, `instrumentLangChainEmbeddings`, `instrumentStateGraph`, `instrumentStateGraphCompile`, `instrumentCreateReactAgent`, `addVercelAiProcessors`. + +Remove `enableTruncation` and `streamGenAiSpans` from your config, and remove AI integrations from browser setups. diff --git a/includes/migration/javascript-v11/ai-internal-exports-removed.mdx b/includes/migration/javascript-v11/ai-internal-exports-removed.mdx new file mode 100644 index 0000000000000..94ee509f04928 --- /dev/null +++ b/includes/migration/javascript-v11/ai-internal-exports-removed.mdx @@ -0,0 +1,17 @@ +--- +id: ai-internal-exports-removed +title: "Low-level AI exports are no longer public API" +phase: code-changes +category: removed-api +severity: informational +frameworks: all +features: [ai-agents] +platformCategory: server +order: 280 +--- + +These provider-instrumentation internals are no longer exported from `@sentry/core`: + +- **Helpers:** `extractOpenAiRequestAttributes`, `addOpenAiRequestAttributes`, `addOpenAiResponseAttributes`, `extractOpenAiRequestParameters`, `instrumentOpenAiStream`, `extractAnthropicRequestAttributes`, `addAnthropicRequestAttributes`, `addAnthropicResponseAttributes`, `instrumentAsyncIterableStream`, `instrumentMessageStream`, `extractGoogleGenAIRequestAttributes`, `addGoogleGenAIRequestAttributes`, `addGoogleGenAIResponseAttributes`, `instrumentGoogleGenAIStream`, `getProviderMetadataAttributes`, `getTruncatedJsonString`, `shouldEnableTruncation`, `resolveAIRecordingOptions`, `wrapToolsWithSpans`, `extractLLMFromParams`, `extractAgentNameFromParams`, `instrumentCompiledGraphInvoke` +- **Integration-name constants:** `OPENAI_INTEGRATION_NAME`, `ANTHROPIC_AI_INTEGRATION_NAME`, `GOOGLE_GENAI_INTEGRATION_NAME`, `LANGCHAIN_INTEGRATION_NAME`, `LANGGRAPH_INTEGRATION_NAME` +- **Types:** `OpenAiClient`, `OpenAiOptions`, `InstrumentedMethod`, `AnthropicAiClient`, `AnthropicAiOptions`, `AnthropicAiResponse`, `AnthropicAiInstrumentedMethod`, `GoogleGenAIClient`, `GoogleGenAIChat`, `GoogleGenAIOptions`, `GoogleGenAIResponse`, `GoogleGenAIInstrumentedMethod`, `GoogleGenAIIstrumentedMethod`, `WorkersAiClient`, `WorkersAiOptions`, `LangChainOptions`, `LangChainIntegration`, `LangGraphOptions`, `LangGraphIntegration`, `CompiledGraph` diff --git a/includes/migration/javascript-v11/ai-non-inference-spans.mdx b/includes/migration/javascript-v11/ai-non-inference-spans.mdx new file mode 100644 index 0000000000000..faa42da30dc5c --- /dev/null +++ b/includes/migration/javascript-v11/ai-non-inference-spans.mdx @@ -0,0 +1,15 @@ +--- +id: ai-non-inference-spans +title: "AI integrations no longer trace non-inference operations" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [ai-agents] +platformCategory: server +order: 90 +--- + +AI integrations only trace model invocations, tool calls, and agent invocations now. Anthropic `messages.countTokens`, `models.retrieve` and `models.get`, and LangGraph `gen_ai.create_agent` on graph compilation, no longer produce spans. + +Update dashboards and alerts that reference these spans. diff --git a/includes/migration/javascript-v11/astro-min-version.mdx b/includes/migration/javascript-v11/astro-min-version.mdx new file mode 100644 index 0000000000000..769468c2da2fd --- /dev/null +++ b/includes/migration/javascript-v11/astro-min-version.mdx @@ -0,0 +1,15 @@ +--- +id: astro-min-version +title: "Astro 3 support was dropped" +phase: prerequisites +category: version-support +severity: action-required +frameworks: [astro] +features: [] +platformCategory: all +order: 70 +--- + +`@sentry/astro` now requires Astro 4. + +Upgrade Astro before upgrading the SDK. diff --git a/includes/migration/javascript-v11/astro-runtime-options.mdx b/includes/migration/javascript-v11/astro-runtime-options.mdx new file mode 100644 index 0000000000000..82180c944a18c --- /dev/null +++ b/includes/migration/javascript-v11/astro-runtime-options.mdx @@ -0,0 +1,44 @@ +--- +id: astro-runtime-options +title: "Runtime SDK options moved out of `sentryAstro()`" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [astro] +features: [] +platformCategory: all +order: 400 +--- + +Runtime SDK options (`dsn`, `environment`, `release` as a string, `sampleRate`, `tracesSampleRate`, `replaysSessionSampleRate`, `replaysOnErrorSampleRate`) can no longer be passed to `sentryAstro()`. `release` and `debug` are build-time options now, and the generated client snippet always includes the `Replay` integration. + +Keep build-time options in `astro.config.mjs`: + +```ts +export default defineConfig({ + integrations: [ + sentry({ + org: 'my-org', + project: 'my-project', + authToken: process.env.SENTRY_AUTH_TOKEN, + release: { name: '1.0.0' }, + debug: true, + }), + ], +}); +``` + +Move runtime options to `sentry.client.config.ts` or `sentry.server.config.ts`: + +```ts +import * as Sentry from '@sentry/astro'; + +Sentry.init({ + dsn: 'https://example@sentry.io/123', + release: '1.0.0', + environment: 'production', + tracesSampleRate: 0.5, +}); +``` + +To customize or remove Replay, create a `sentry.client.config.ts`. diff --git a/includes/migration/javascript-v11/attach-stacktrace-default.mdx b/includes/migration/javascript-v11/attach-stacktrace-default.mdx new file mode 100644 index 0000000000000..1673241b24b84 --- /dev/null +++ b/includes/migration/javascript-v11/attach-stacktrace-default.mdx @@ -0,0 +1,15 @@ +--- +id: attach-stacktrace-default +title: "`attachStacktrace` now defaults to `true`" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [] +platformCategory: all +order: 40 +--- + +`attachStacktrace` now defaults to `true`, so `Sentry.captureMessage` events and non-`Error` values passed to `Sentry.captureException` get a synthetic stack trace. Expect new issue groups, and expect these events to mark sessions as errored (crash-free rate is unaffected). + +Set `attachStacktrace: false` to restore the previous behavior. For purely informational output, use Sentry Logs instead. diff --git a/includes/migration/javascript-v11/aws-lambda-layer.mdx b/includes/migration/javascript-v11/aws-lambda-layer.mdx new file mode 100644 index 0000000000000..ccab301a0fdc0 --- /dev/null +++ b/includes/migration/javascript-v11/aws-lambda-layer.mdx @@ -0,0 +1,15 @@ +--- +id: aws-lambda-layer +title: "A new AWS Lambda Layer is published for v11" +phase: packages +category: package +severity: action-required +frameworks: [aws-lambda] +features: [] +platformCategory: serverless +order: 50 +--- + +The v11 AWS Lambda Layer is published as `SentryNodeServerlessSDKv11`. Version 10 updates continue as `SentryNodeServerlessSDKv10`. + +Point your functions at the new layer ARN, listed in the [Lambda layer docs](https://docs.sentry.io/platforms/javascript/guides/aws-lambda/install/cjs-layer/). The layer supports the `nodejs20.x`, `nodejs22.x`, and `nodejs24.x` runtimes, so functions on `nodejs18.x` need a newer runtime first. diff --git a/includes/migration/javascript-v11/before-send-span-format.mdx b/includes/migration/javascript-v11/before-send-span-format.mdx new file mode 100644 index 0000000000000..0e5cc6563c990 --- /dev/null +++ b/includes/migration/javascript-v11/before-send-span-format.mdx @@ -0,0 +1,49 @@ +--- +id: before-send-span-format +title: "`beforeSendSpan` receives the streamed span format" +phase: code-changes +category: behavior +severity: action-required +frameworks: all +features: [tracing] +platformCategory: all +order: 50 +--- + +`beforeSendSpan` now receives a `StreamedSpanJSON` object as each span finishes, rather than every span of a transaction just before it is sent. The fields were renamed: + +| Before (`SpanJSON`) | After (`StreamedSpanJSON`) | +| ------------------- | ------------------------------ | +| `description` | `name` | +| `data` | `attributes` | +| `op` | `attributes['sentry.op']` | +| `timestamp` | `end_timestamp` | +| `status` (`string`) | `status` (`'ok'` or `'error'`) | + +Update the callback: + +```js +// Before +Sentry.init({ + beforeSendSpan: span => { + if (span.op === 'db.query') { + span.description = scrub(span.description); + span.data['db.statement'] = scrub(span.data['db.statement']); + } + return span; + }, +}); + +// After +Sentry.init({ + beforeSendSpan: span => { + if (span.attributes?.['sentry.op'] === 'db.query') { + span.name = scrub(span.name); + span.attributes['db.statement'] = scrub(span.attributes['db.statement']); + } + return span; + }, +}); +``` + +Use `ignoreSpans` to drop spans; returning `null` is a no-op. A callback that does not match your `traceLifecycle` is never invoked: wrap it in `Sentry.withStaticSpan()` for `'static'`, leave it unwrapped for `'stream'`. diff --git a/includes/migration/javascript-v11/before-send-transaction-removed.mdx b/includes/migration/javascript-v11/before-send-transaction-removed.mdx new file mode 100644 index 0000000000000..c1fa2dad801fb --- /dev/null +++ b/includes/migration/javascript-v11/before-send-transaction-removed.mdx @@ -0,0 +1,41 @@ +--- +id: before-send-transaction-removed +title: "`beforeSendTransaction` no longer runs" +phase: code-changes +category: behavior +severity: action-required +frameworks: all +features: [tracing] +platformCategory: all +order: 60 +--- + +`beforeSendTransaction` no-ops, because span streaming produces no transaction events. + +Move scrubbing to `beforeSendSpan` and guard on `is_segment`, and move dropping to `ignoreSpans`: + +```js +// Before +Sentry.init({ + beforeSendTransaction: event => { + if (event.transaction === 'GET /health') { + return null; + } + event.transaction = scrubIds(event.transaction); + return event; + }, +}); + +// After +Sentry.init({ + ignoreSpans: ['GET /health'], + beforeSendSpan: span => { + if (span.is_segment) { + span.name = scrubIds(span.name); + } + return span; + }, +}); +``` + +Scope `tags` and `extra` are not carried over to streamed spans. Use `Sentry.setAttribute()` or `Sentry.setAttributes()`. diff --git a/includes/migration/javascript-v11/browser-http-headers-data-collection.mdx b/includes/migration/javascript-v11/browser-http-headers-data-collection.mdx new file mode 100644 index 0000000000000..df8298d43cc48 --- /dev/null +++ b/includes/migration/javascript-v11/browser-http-headers-data-collection.mdx @@ -0,0 +1,15 @@ +--- +id: browser-http-headers-data-collection +title: "Browser request headers follow `dataCollection`" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [] +platformCategory: browser +order: 140 +--- + +`httpContextIntegration` attached `Referer` and `User-Agent` regardless of your configuration. Both now filter through `dataCollection.httpHeaders.request`, matching the server SDKs. Headers you set yourself are left alone. + +Nothing to do, unless you set `httpHeaders: { request: false }` and relied on those two headers still arriving from the browser. diff --git a/includes/migration/javascript-v11/browser-session-lifecycle-page.mdx b/includes/migration/javascript-v11/browser-session-lifecycle-page.mdx new file mode 100644 index 0000000000000..ee865226c0977 --- /dev/null +++ b/includes/migration/javascript-v11/browser-session-lifecycle-page.mdx @@ -0,0 +1,21 @@ +--- +id: browser-session-lifecycle-page +title: "`page` is the default browser session lifecycle mode" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [] +platformCategory: browser +order: 30 +--- + +`browserSessionIntegration` now defaults to `lifecycle: 'page'`, so a session is created on page load and not renewed on navigation. Expect session counts to drop for single-page apps. + +To keep the previous behavior: + +```js +Sentry.init({ + integrations: [Sentry.browserSessionIntegration({ lifecycle: 'route' })], +}); +``` diff --git a/includes/migration/javascript-v11/browser-session-unhandled.mdx b/includes/migration/javascript-v11/browser-session-unhandled.mdx new file mode 100644 index 0000000000000..92e3983251eeb --- /dev/null +++ b/includes/migration/javascript-v11/browser-session-unhandled.mdx @@ -0,0 +1,15 @@ +--- +id: browser-session-unhandled +title: "Browser sessions use `unhandled` instead of `crashed`" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [] +platformCategory: browser +order: 20 +--- + +Browser sessions affected by an uncaught error are recorded as `unhandled` instead of `crashed`, so crash-free session rates will shift. + +Review Release Health alerts built on crash-free rate before rolling out. diff --git a/includes/migration/javascript-v11/browser-support.mdx b/includes/migration/javascript-v11/browser-support.mdx new file mode 100644 index 0000000000000..4265689e37e36 --- /dev/null +++ b/includes/migration/javascript-v11/browser-support.mdx @@ -0,0 +1,15 @@ +--- +id: browser-support +title: "Safari 14 support was dropped" +phase: prerequisites +category: version-support +severity: behavior-change +frameworks: all +features: [] +platformCategory: browser +order: 30 +--- + +The SDK now supports Safari 15 and higher. Safari 14 was dropped. + +Some SDK features rely on newer browser APIs and don't work on Safari 15. diff --git a/includes/migration/javascript-v11/browser-user-timing.mdx b/includes/migration/javascript-v11/browser-user-timing.mdx new file mode 100644 index 0000000000000..bac955d069a10 --- /dev/null +++ b/includes/migration/javascript-v11/browser-user-timing.mdx @@ -0,0 +1,32 @@ +--- +id: browser-user-timing +title: "`performance.mark()` spans moved to `userTimingIntegration`" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [tracing] +platformCategory: browser +order: 210 +--- + +`browserTracingIntegration` no longer captures `performance.mark()` and `performance.measure()` spans. The `ignorePerformanceApiSpans` option moved to `userTimingIntegration` as `ignore`. + +Add `userTimingIntegration()` to keep capturing them: + +```js +// Before +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ ignorePerformanceApiSpans: ['third-party-mark'] }), + ], +}); + +// After +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration(), + Sentry.userTimingIntegration({ ignore: ['third-party-mark'] }), + ], +}); +``` diff --git a/includes/migration/javascript-v11/browser-web-vital-options.mdx b/includes/migration/javascript-v11/browser-web-vital-options.mdx new file mode 100644 index 0000000000000..54d76bd0c1a23 --- /dev/null +++ b/includes/migration/javascript-v11/browser-web-vital-options.mdx @@ -0,0 +1,15 @@ +--- +id: browser-web-vital-options +title: "Standalone CLS and LCP span options were removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [tracing] +platformCategory: browser +order: 220 +--- + +The experimental `_experiments.enableStandaloneClsSpans` and `_experiments.enableStandaloneLcpSpans` options were removed from `browserTracingIntegration` and `webVitalsIntegration`. With span streaming on by default, CLS and LCP are sent as dedicated spans. They are only recorded as measurements on the pageload span if you opt out with `traceLifecycle: 'static'`. + +Remove both options, and update dashboards or alerts that read CLS or LCP as pageload span measurements. diff --git a/includes/migration/javascript-v11/channel-based-instrumentation.mdx b/includes/migration/javascript-v11/channel-based-instrumentation.mdx new file mode 100644 index 0000000000000..b0ff19507c78e --- /dev/null +++ b/includes/migration/javascript-v11/channel-based-instrumentation.mdx @@ -0,0 +1,15 @@ +--- +id: channel-based-instrumentation +title: "Channel-based instrumentation is the default" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [] +platformCategory: server +order: 10 +--- + +Channel-based instrumentation, which uses `orchestrion` instead of `import-in-the-middle`, is now the default. It was opt-in in v10. + +After upgrading, confirm spans still appear for the libraries you care about, especially if you had a custom instrumentation setup or relied on ESM loader hooks. diff --git a/includes/migration/javascript-v11/child-process-worker-split.mdx b/includes/migration/javascript-v11/child-process-worker-split.mdx new file mode 100644 index 0000000000000..466bcb5da318b --- /dev/null +++ b/includes/migration/javascript-v11/child-process-worker-split.mdx @@ -0,0 +1,15 @@ +--- +id: child-process-worker-split +title: "`childProcessIntegration` was split in two" +phase: code-changes +category: rename +severity: action-required +frameworks: all +features: [] +platformCategory: server +order: 320 +--- + +`childProcessIntegration` was split into `childProcessIntegration` for `child_process` and `workerIntegration` for `worker_threads`. + +If you customized worker thread behavior, move that configuration to `workerIntegration`. diff --git a/includes/migration/javascript-v11/cloudflare-instrument-d1.mdx b/includes/migration/javascript-v11/cloudflare-instrument-d1.mdx new file mode 100644 index 0000000000000..46329f790dacc --- /dev/null +++ b/includes/migration/javascript-v11/cloudflare-instrument-d1.mdx @@ -0,0 +1,28 @@ +--- +id: cloudflare-instrument-d1 +title: "`instrumentD1WithSentry` was removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [cloudflare] +features: [tracing] +platformCategory: serverless +order: 430 +--- + +The deprecated `instrumentD1WithSentry` export was removed. `withSentry()` instruments all D1 bindings on `env`. + +Use the binding directly: + +```diff + export default withSentry( + (env) => ({ dsn: env.SENTRY_DSN }), + { + async fetch(request, env, ctx) { +- const db = Sentry.instrumentD1WithSentry(env.DB); +- const result = await db.prepare('SELECT * FROM users').all(); ++ const result = await env.DB.prepare('SELECT * FROM users').all(); + }, + }, + ); +``` diff --git a/includes/migration/javascript-v11/cloudflare-instrument-prototype-methods.mdx b/includes/migration/javascript-v11/cloudflare-instrument-prototype-methods.mdx new file mode 100644 index 0000000000000..9f5dae8e98cd1 --- /dev/null +++ b/includes/migration/javascript-v11/cloudflare-instrument-prototype-methods.mdx @@ -0,0 +1,26 @@ +--- +id: cloudflare-instrument-prototype-methods +title: "`instrumentPrototypeMethods` was removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [cloudflare] +features: [tracing] +platformCategory: serverless +order: 440 +--- + +The `instrumentPrototypeMethods` option of `instrumentDurableObjectWithSentry` was removed. + +Use `enableRpcTracePropagation` instead: + +```diff + export const MyDO = Sentry.instrumentDurableObjectWithSentry( + (env) => ({ + dsn: env.SENTRY_DSN, +- instrumentPrototypeMethods: true, ++ enableRpcTracePropagation: true, + }), + MyDOBase, + ); +``` diff --git a/includes/migration/javascript-v11/cloudflare-nodejs-compat-subpath.mdx b/includes/migration/javascript-v11/cloudflare-nodejs-compat-subpath.mdx new file mode 100644 index 0000000000000..b84c554f58423 --- /dev/null +++ b/includes/migration/javascript-v11/cloudflare-nodejs-compat-subpath.mdx @@ -0,0 +1,18 @@ +--- +id: cloudflare-nodejs-compat-subpath +title: "The `@sentry/cloudflare/nodejs_compat` subpath was removed" +phase: packages +category: removed-api +severity: action-required +frameworks: [cloudflare] +features: [] +platformCategory: serverless +order: 60 +--- + +The `@sentry/cloudflare/nodejs_compat` subpath was removed. The main entry point now includes everything. + +```diff +- import * as Sentry from '@sentry/cloudflare/nodejs_compat'; ++ import * as Sentry from '@sentry/cloudflare'; +``` diff --git a/includes/migration/javascript-v11/cloudflare-nodejs-compat.mdx b/includes/migration/javascript-v11/cloudflare-nodejs-compat.mdx new file mode 100644 index 0000000000000..a7a1cf8def477 --- /dev/null +++ b/includes/migration/javascript-v11/cloudflare-nodejs-compat.mdx @@ -0,0 +1,20 @@ +--- +id: cloudflare-nodejs-compat +title: "The `nodejs_compat` compatibility flag is now required" +phase: prerequisites +category: version-support +severity: action-required +frameworks: [cloudflare] +features: [] +platformCategory: serverless +order: 110 +--- + +`@sentry/cloudflare` now requires the `nodejs_compat` compatibility flag instead of `nodejs_als`. + +Update your `wrangler.toml` or `wrangler.jsonc`: + +```diff +- compatibility_flags = ["nodejs_als"] ++ compatibility_flags = ["nodejs_compat"] +``` diff --git a/includes/migration/javascript-v11/cloudflare-rpc-trace-propagation.mdx b/includes/migration/javascript-v11/cloudflare-rpc-trace-propagation.mdx new file mode 100644 index 0000000000000..df8c88cfb6b8c --- /dev/null +++ b/includes/migration/javascript-v11/cloudflare-rpc-trace-propagation.mdx @@ -0,0 +1,15 @@ +--- +id: cloudflare-rpc-trace-propagation +title: "`enableRpcTracePropagation` now defaults to `true`" +phase: verify +category: behavior +severity: behavior-change +frameworks: [cloudflare] +features: [tracing] +platformCategory: serverless +order: 110 +--- + +Trace context is now propagated across RPC calls (service bindings, Durable Objects, and WorkerEntrypoints) by default. + +Set `enableRpcTracePropagation: false` to opt out. diff --git a/includes/migration/javascript-v11/cloudflare-type-changes.mdx b/includes/migration/javascript-v11/cloudflare-type-changes.mdx new file mode 100644 index 0000000000000..0fb864ef5531f --- /dev/null +++ b/includes/migration/javascript-v11/cloudflare-type-changes.mdx @@ -0,0 +1,33 @@ +--- +id: cloudflare-type-changes +title: "Cloudflare `env` types and generics were reworked" +phase: cleanup +category: type +severity: informational +frameworks: [cloudflare] +features: [] +platformCategory: serverless +order: 20 +--- + +The `env` types and the generics on `withSentry` and `instrumentDurableObjectWithSentry` were reworked. Types are now inferred. + +Drop explicit generic type parameters: + +```diff +- export default withSentry( ++ export default withSentry( + (env) => ({ dsn: env.SENTRY_DSN }), + { + async fetch(request, env, ctx) {}, + } satisfies ExportedHandler, + ); +``` + +```diff +- export const MyDO = Sentry.instrumentDurableObjectWithSentry( ++ export const MyDO = Sentry.instrumentDurableObjectWithSentry( + (env) => ({ dsn: env.SENTRY_DSN }), + MyDOBase, + ); +``` diff --git a/includes/migration/javascript-v11/cloudflare-vite-auto-instrumentation.mdx b/includes/migration/javascript-v11/cloudflare-vite-auto-instrumentation.mdx new file mode 100644 index 0000000000000..b650bc751a13b --- /dev/null +++ b/includes/migration/javascript-v11/cloudflare-vite-auto-instrumentation.mdx @@ -0,0 +1,22 @@ +--- +id: cloudflare-vite-auto-instrumentation +title: "The Cloudflare Vite plugin auto-instruments your Worker" +phase: verify +category: behavior +severity: behavior-change +frameworks: [cloudflare] +features: [] +platformCategory: serverless +order: 120 +--- + +`sentryCloudflareVitePlugin()` now wraps your Worker entry, and any Durable Object, Workflow or WorkerEntrypoint class in your wrangler config, at build time. Entries you wrapped yourself are left untouched. + +Remove the experimental opt-in if you set it: + +```diff +- sentryCloudflareVitePlugin({ _experimental: { autoInstrumentation: true } }); ++ sentryCloudflareVitePlugin(); +``` + +To opt out entirely, use `sentryCloudflareVitePlugin({ autoInstrumentation: false })`. diff --git a/includes/migration/javascript-v11/cloudflare-wrap-request-handler.mdx b/includes/migration/javascript-v11/cloudflare-wrap-request-handler.mdx new file mode 100644 index 0000000000000..1ebefaed663a1 --- /dev/null +++ b/includes/migration/javascript-v11/cloudflare-wrap-request-handler.mdx @@ -0,0 +1,18 @@ +--- +id: cloudflare-wrap-request-handler +title: "`wrapRequestHandler` moved to `@sentry/cloudflare/request`" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [cloudflare] +features: [] +platformCategory: serverless +order: 420 +--- + +`wrapRequestHandler` moved out of the main `@sentry/cloudflare` entry point. + +```diff +- import { wrapRequestHandler } from '@sentry/cloudflare'; ++ import { wrapRequestHandler } from '@sentry/cloudflare/request'; +``` diff --git a/includes/migration/javascript-v11/console-breadcrumbs.mdx b/includes/migration/javascript-v11/console-breadcrumbs.mdx new file mode 100644 index 0000000000000..4e2a7af216962 --- /dev/null +++ b/includes/migration/javascript-v11/console-breadcrumbs.mdx @@ -0,0 +1,15 @@ +--- +id: console-breadcrumbs +title: "Console breadcrumbs are handled by `consoleIntegration`" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [] +platformCategory: browser +order: 240 +--- + +The `console` option of `breadcrumbsIntegration` was removed from `@sentry/browser` and `@sentry/deno`. + +Use `consoleIntegration` from `@sentry/core` to capture console breadcrumbs. diff --git a/includes/migration/javascript-v11/core-removed-apis.mdx b/includes/migration/javascript-v11/core-removed-apis.mdx new file mode 100644 index 0000000000000..bc086033cbf62 --- /dev/null +++ b/includes/migration/javascript-v11/core-removed-apis.mdx @@ -0,0 +1,23 @@ +--- +id: core-removed-apis +title: "`@sentry/core` API removals" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [] +platformCategory: all +order: 170 +--- + +These exports and options were removed from `@sentry/core` and every SDK that re-exported them: + +- `addAutoIpAddressToUser` +- `Scope.clear()`. Use `withScope` or `withIsolationScope` for a fresh scope. +- The positional `spanOrigin` argument of `instrumentFetchRequest`. Pass `{ spanOrigin }` as the last argument. +- `createSpanEnvelope`, and the `SpanEnvelope` and `SpanItem` types. +- `disableInstrumentationWarnings` and the `MissingInstrumentationContext` type. +- `isStreamedBeforeSendSpanCallback()` +- The `semanticAttributes` re-export. Import span attribute constants from `@sentry/core` directly. + +TypeScript catches all of these except `disableInstrumentationWarnings` in plain-JavaScript configs. diff --git a/includes/migration/javascript-v11/custom-otel-setup.mdx b/includes/migration/javascript-v11/custom-otel-setup.mdx new file mode 100644 index 0000000000000..489e1cf645a2f --- /dev/null +++ b/includes/migration/javascript-v11/custom-otel-setup.mdx @@ -0,0 +1,50 @@ +--- +id: custom-otel-setup +title: "Custom OpenTelemetry setups must export over OTLP" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [custom-otel] +platformCategory: server +order: 140 +--- + +`SentryContextManager`, `SentrySampler`, and `SentrySpanProcessor` were removed, so you can no longer route spans from your own OpenTelemetry provider into Sentry as Sentry spans. + +Export them over OTLP instead. `Sentry.getOtlpTracesEndpoint()` turns your DSN into the endpoint URL and auth headers: + +```js +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; +import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import * as Sentry from '@sentry/node'; + +const provider = new NodeTracerProvider({ + spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter(Sentry.getOtlpTracesEndpoint('__DSN__')))], +}); + +provider.register(); + +Sentry.init({ + dsn: '__DSN__', + // no tracesSampleRate: OpenTelemetry owns spans, Sentry owns errors and logs + integrations: [Sentry.otlpIntegration()], +}); +``` + +Leave `tracesSampleRate` unset to avoid duplicate spans, since Sentry instruments many of the same libraries. Sentry still isolates requests with tracing off. + +If you do want Sentry spans alongside your own, keep `tracesSampleRate` set and drop the overlapping integrations. Turn off HTTP and fetch spans rather than the integrations, which also provide request isolation and session tracking: + +```js +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, + integrations: integrations => [ + ...integrations.filter(integration => integration.name !== 'Postgres'), + Sentry.httpIntegration({ spans: false }), + Sentry.nativeNodeFetchIntegration({ spans: false }), + ], +}); +``` diff --git a/includes/migration/javascript-v11/data-collection.mdx b/includes/migration/javascript-v11/data-collection.mdx new file mode 100644 index 0000000000000..c1c2ad5291a79 --- /dev/null +++ b/includes/migration/javascript-v11/data-collection.mdx @@ -0,0 +1,49 @@ +--- +id: data-collection +title: "`sendDefaultPii` is replaced by `dataCollection`" +phase: code-changes +category: behavior +severity: action-required +frameworks: all +features: [] +platformCategory: all +order: 10 +--- + +`sendDefaultPii` was replaced by `dataCollection`, which controls each category individually. Leaving `dataCollection` unset collects most categories by default, where the unset `sendDefaultPii` was restrictive. + +| Category | v10 default (`sendDefaultPii` off) | v11 default | +| --------------------- | ---------------------------------- | -------------------- | +| `userInfo` | `false` | `true` | +| `cookies` | not collected | `true` | +| `httpHeaders` | request + response, PII scrubbed | request + response | +| `httpBodies` | not collected (size only) | all request/response | +| `urlQueryParams` | `true` | `true` | +| `genAI` | inputs + outputs not collected | inputs + outputs | +| `databaseQueryData` | `false` | `true` | +| `stackFrameVariables` | `true` | `true` | +| `frameContextLines` | `7` | `5` | + +If you set `sendDefaultPii: true`, remove it. The v11 default matches that behavior. + +If you relied on the v10 default, set the baseline explicitly: + +```js +Sentry.init({ + dataCollection: { + userInfo: false, + cookies: false, + httpHeaders: { + request: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] }, + response: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] }, + }, + httpBodies: [], + urlQueryParams: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] }, + genAI: { inputs: false, outputs: false }, + databaseQueryData: false, + graphQL: { document: false, variables: false }, + }, +}); +``` + +Review your data-scrubbing config, especially for request and response bodies. `cookies`, `urlQueryParams`, and the `httpHeaders` fields also accept `{ allow: string[] }` or `{ deny: string[] }`. See the [`dataCollection` docs](https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection). diff --git a/includes/migration/javascript-v11/deno-integrations-rename.mdx b/includes/migration/javascript-v11/deno-integrations-rename.mdx new file mode 100644 index 0000000000000..d6360a18cc012 --- /dev/null +++ b/includes/migration/javascript-v11/deno-integrations-rename.mdx @@ -0,0 +1,24 @@ +--- +id: deno-integrations-rename +title: "Deno default integrations were renamed" +phase: code-changes +category: rename +severity: behavior-change +frameworks: [deno] +features: [] +platformCategory: server +order: 310 +--- + +Default integrations in `@sentry/deno` were renamed to match the other SDKs. The old `deno*Integration` exports remain as deprecated aliases. + +| Before | After | +| -------------- | ---------- | +| `DenoAmqplib` | `Amqplib` | +| `DenoKoa` | `Koa` | +| `DenoMongodb` | `Mongodb` | +| `DenoMongoose` | `Mongoose` | +| `DenoMysql` | `Mysql` | +| `DenoPostgres` | `Postgres` | + +Update any reference to the old names, such as disabling an integration by name. diff --git a/includes/migration/javascript-v11/deno-version.mdx b/includes/migration/javascript-v11/deno-version.mdx new file mode 100644 index 0000000000000..b0a5866774d15 --- /dev/null +++ b/includes/migration/javascript-v11/deno-version.mdx @@ -0,0 +1,15 @@ +--- +id: deno-version +title: "Deno 2.8.3 is the new minimum" +phase: prerequisites +category: version-support +severity: action-required +frameworks: [deno] +features: [] +platformCategory: server +order: 20 +--- + +The minimum supported Deno version is now 2.8.3. + +Upgrade Deno before upgrading the SDK. diff --git a/includes/migration/javascript-v11/ember-app-instance-required.mdx b/includes/migration/javascript-v11/ember-app-instance-required.mdx new file mode 100644 index 0000000000000..66eab612e7984 --- /dev/null +++ b/includes/migration/javascript-v11/ember-app-instance-required.mdx @@ -0,0 +1,15 @@ +--- +id: ember-app-instance-required +title: "`appInstance` is required in the Ember browser tracing options" +phase: code-changes +category: behavior +severity: action-required +frameworks: [ember] +features: [tracing] +platformCategory: browser +order: 460 +--- + +`appInstance` is now required in the Ember browser tracing integration options. + +Pass the application instance explicitly. If you set up performance through `instrumentAppInstancePerformance()`, it is already threaded through for you. diff --git a/includes/migration/javascript-v11/ember-v2-addon.mdx b/includes/migration/javascript-v11/ember-v2-addon.mdx new file mode 100644 index 0000000000000..189672d97172f --- /dev/null +++ b/includes/migration/javascript-v11/ember-v2-addon.mdx @@ -0,0 +1,55 @@ +--- +id: ember-v2-addon +title: "`@sentry/ember` is now a v2 addon with manual setup" +phase: code-changes +category: behavior +severity: action-required +frameworks: [ember] +features: [] +platformCategory: browser +order: 450 +--- + +`@sentry/ember` is now a [v2 (Embroider) addon](https://rfcs.emberjs.com/id/0507-embroider-v2-package-format/), so it no longer configures itself from `config/environment.js` or registers an initializer. + +Remove the `'@sentry/ember'` block from `config/environment.js` and call `init()` in `app/app.ts`: + +```ts +import Application from '@ember/application'; +import Resolver from 'ember-resolver'; +import loadInitializers from 'ember-load-initializers'; +import config from 'my-app/config/environment'; +import * as Sentry from '@sentry/ember'; + +Sentry.init({ + dsn: config.sentryDsn, + tracesSampleRate: 1.0, +}); + +export default class App extends Application { + modulePrefix = config.modulePrefix; + podModulePrefix = config.podModulePrefix; + Resolver = Resolver; +} + +loadInitializers(App, config.modulePrefix); +``` + +Opt into performance instrumentation with an instance-initializer, where the former `disable*` flags now live: + +```ts +// app/instance-initializers/sentry-performance.ts +import type ApplicationInstance from '@ember/application/instance'; +import { instrumentAppInstancePerformance } from '@sentry/ember'; + +export function initialize(appInstance: ApplicationInstance): void { + instrumentAppInstancePerformance(appInstance, { + // disableRunloopPerformance: false, + // disableInstrumentComponents: false, + }); +} + +export default { initialize }; +``` + +Omit that initializer to disable performance instrumentation; `disablePerformance` no longer exists. `instrumentRoutePerformance` is unchanged, and FastBoot is detected automatically. Full walkthrough: [`packages/ember/UPGRADE.md`](https://github.com/getsentry/sentry-javascript/blob/master/packages/ember/UPGRADE.md). diff --git a/includes/migration/javascript-v11/enable-logs-removed.mdx b/includes/migration/javascript-v11/enable-logs-removed.mdx new file mode 100644 index 0000000000000..d5d12e3bd7e5f --- /dev/null +++ b/includes/migration/javascript-v11/enable-logs-removed.mdx @@ -0,0 +1,23 @@ +--- +id: enable-logs-removed +title: "The `enableLogs` option was removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [logs] +platformCategory: all +order: 90 +--- + +`enableLogs` and `_experiments.enableLogs` were removed. Logs are captured whenever you call `Sentry.logger.*` or add a logging integration such as `consoleLoggingIntegration()`. + +Remove the option: + +```js +// Before +Sentry.init({ enableLogs: true }); + +// After +Sentry.init({}); +``` diff --git a/includes/migration/javascript-v11/enable-metrics-removed.mdx b/includes/migration/javascript-v11/enable-metrics-removed.mdx new file mode 100644 index 0000000000000..f7b5cceeef84b --- /dev/null +++ b/includes/migration/javascript-v11/enable-metrics-removed.mdx @@ -0,0 +1,28 @@ +--- +id: enable-metrics-removed +title: "The `enableMetrics` option was removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [metrics] +platformCategory: all +order: 100 +--- + +`enableMetrics` and `_experiments.enableMetrics` were removed. Metrics are captured whenever you use `Sentry.metrics.*`. The `_experiments.beforeSendMetric` callback moved to the top level. + +```js +// Before +Sentry.init({ + _experiments: { + enableMetrics: true, + beforeSendMetric: metric => metric, + }, +}); + +// After +Sentry.init({ + beforeSendMetric: metric => metric, +}); +``` diff --git a/includes/migration/javascript-v11/enable-opentelemetry-setup.mdx b/includes/migration/javascript-v11/enable-opentelemetry-setup.mdx new file mode 100644 index 0000000000000..ef83f5248eef0 --- /dev/null +++ b/includes/migration/javascript-v11/enable-opentelemetry-setup.mdx @@ -0,0 +1,27 @@ +--- +id: enable-opentelemetry-setup +title: "`skipOpenTelemetrySetup` is replaced by `enableOpenTelemetrySetup`" +phase: code-changes +category: rename +severity: action-required +frameworks: all +features: [custom-otel] +platformCategory: server +order: 130 +--- + +`skipOpenTelemetrySetup` was replaced by `enableOpenTelemetrySetup`, with inverted meaning. It defaults to `false` for most server SDKs, and `true` for `@sentry/nextjs` and `@sentry/sveltekit`. + +```js +// Before +Sentry.init({ skipOpenTelemetrySetup: true }); + +// After +Sentry.init({ enableOpenTelemetrySetup: false }); +``` + +Pick the setup you want: + +1. **Sentry only** (default): nothing to set. +2. **OpenTelemetry-compatible, everything to Sentry**: set `enableOpenTelemetrySetup: true`. Spans created through `@opentelemetry/api` become Sentry spans. There is no exporter and no OTLP output. +3. **Your own OpenTelemetry, Sentry linked to it**: leave it unset or `false`, turn Sentry tracing off, and add `Sentry.otlpIntegration()`. On `@sentry/nextjs` and `@sentry/sveltekit` you must set it to `false` explicitly. diff --git a/includes/migration/javascript-v11/event-filters-rename.mdx b/includes/migration/javascript-v11/event-filters-rename.mdx new file mode 100644 index 0000000000000..dee2f57456a50 --- /dev/null +++ b/includes/migration/javascript-v11/event-filters-rename.mdx @@ -0,0 +1,29 @@ +--- +id: event-filters-rename +title: "`InboundFilters` was renamed to `EventFilters`" +phase: code-changes +category: rename +severity: action-required +frameworks: all +features: [] +platformCategory: all +order: 290 +--- + +`inboundFiltersIntegration` was renamed to `eventFiltersIntegration`, and the integration reports itself as `EventFilters`. + +Update the import and any reference by name: + +```js +// Before +import { inboundFiltersIntegration } from '@sentry/browser'; +Sentry.init({ + integrations: integrations => integrations.filter(integration => integration.name !== 'InboundFilters'), +}); + +// After +import { eventFiltersIntegration } from '@sentry/browser'; +Sentry.init({ + integrations: integrations => integrations.filter(integration => integration.name !== 'EventFilters'), +}); +``` diff --git a/includes/migration/javascript-v11/fastify-min-version.mdx b/includes/migration/javascript-v11/fastify-min-version.mdx new file mode 100644 index 0000000000000..f872d8671b89d --- /dev/null +++ b/includes/migration/javascript-v11/fastify-min-version.mdx @@ -0,0 +1,15 @@ +--- +id: fastify-min-version +title: "Fastify 3.21 is the new minimum" +phase: prerequisites +category: version-support +severity: action-required +frameworks: [fastify] +features: [] +platformCategory: server +order: 100 +--- + +`@sentry/node` now requires Fastify 3.21. Fastify 3.0 to 3.20 were dropped. + +Upgrade Fastify before upgrading the SDK. diff --git a/includes/migration/javascript-v11/hono-integration-removed.mdx b/includes/migration/javascript-v11/hono-integration-removed.mdx new file mode 100644 index 0000000000000..0e2c834c6db92 --- /dev/null +++ b/includes/migration/javascript-v11/hono-integration-removed.mdx @@ -0,0 +1,23 @@ +--- +id: hono-integration-removed +title: "`honoIntegration` was removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [hono, cloudflare] +features: [] +platformCategory: server +order: 200 +--- + +The deprecated `honoIntegration` was removed from `@sentry/node` and `@sentry/cloudflare`. + +Use the [`@sentry/hono`](https://www.npmjs.com/package/@sentry/hono) middleware: + +```diff +- import * as Sentry from '@sentry/cloudflare'; ++ import { sentry } from '@sentry/hono/cloudflare'; + + const app = new Hono(); ++ app.use(sentry()); +``` diff --git a/includes/migration/javascript-v11/hydrogen-legacy-dropped.mdx b/includes/migration/javascript-v11/hydrogen-legacy-dropped.mdx new file mode 100644 index 0000000000000..abb906e429e81 --- /dev/null +++ b/includes/migration/javascript-v11/hydrogen-legacy-dropped.mdx @@ -0,0 +1,15 @@ +--- +id: hydrogen-legacy-dropped +title: "Legacy Hydrogen on Oxygen is no longer supported" +phase: prerequisites +category: version-support +severity: action-required +frameworks: [cloudflare] +features: [] +platformCategory: serverless +order: 140 +--- + +Legacy Hydrogen versions are no longer supported on Oxygen, and local development needs `@shopify/mini-oxygen` 4 or higher, which ships the miniflare version that supports all compatibility flags. + +Upgrade Hydrogen and `@shopify/mini-oxygen` before upgrading the SDK. See the [Hydrogen guide](https://docs.sentry.io/platforms/javascript/guides/cloudflare/frameworks/hydrogen-remix/). diff --git a/includes/migration/javascript-v11/ignore-transactions-removed.mdx b/includes/migration/javascript-v11/ignore-transactions-removed.mdx new file mode 100644 index 0000000000000..1d17505771ee5 --- /dev/null +++ b/includes/migration/javascript-v11/ignore-transactions-removed.mdx @@ -0,0 +1,29 @@ +--- +id: ignore-transactions-removed +title: "`ignoreTransactions` is replaced by `ignoreSpans`" +phase: code-changes +category: behavior +severity: action-required +frameworks: all +features: [tracing] +platformCategory: all +order: 70 +--- + +`ignoreTransactions` no-ops. `ignoreSpans` now takes effect when a span starts, and dropping a segment span drops its children with it. + +```js +// Before +Sentry.init({ ignoreTransactions: ['GET /health'] }); + +// After +Sentry.init({ ignoreSpans: ['GET /health'] }); +``` + +`ignoreSpans` matches on span `name` and applies to every span, so narrow the filter if child spans share a name: + +```js +Sentry.init({ + ignoreSpans: [{ name: 'GET /health', attributes: { 'sentry.op': 'http.server' } }], +}); +``` diff --git a/includes/migration/javascript-v11/inp-web-vital-span.mdx b/includes/migration/javascript-v11/inp-web-vital-span.mdx new file mode 100644 index 0000000000000..52f872749da9d --- /dev/null +++ b/includes/migration/javascript-v11/inp-web-vital-span.mdx @@ -0,0 +1,15 @@ +--- +id: inp-web-vital-span +title: "INP is always sent as a web vital span" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [tracing] +platformCategory: browser +order: 80 +--- + +INP is always sent as a web vital span now, carrying its value in the `browser.web_vital.inp.value` attribute instead of as a span measurement. + +Update dashboards and alerts that read INP as a span measurement. diff --git a/includes/migration/javascript-v11/instrument-state-graph-rename.mdx b/includes/migration/javascript-v11/instrument-state-graph-rename.mdx new file mode 100644 index 0000000000000..c56b2c5073144 --- /dev/null +++ b/includes/migration/javascript-v11/instrument-state-graph-rename.mdx @@ -0,0 +1,21 @@ +--- +id: instrument-state-graph-rename +title: "`instrumentLangGraph` was renamed to `instrumentStateGraph`" +phase: code-changes +category: rename +severity: action-required +frameworks: all +features: [ai-agents] +platformCategory: server +order: 300 +--- + +`instrumentLangGraph` was renamed to `instrumentStateGraph`. + +```js +// Before +import { instrumentLangGraph } from '@sentry/node'; + +// After +import { instrumentStateGraph } from '@sentry/node'; +``` diff --git a/includes/migration/javascript-v11/interaction-spans-integration.mdx b/includes/migration/javascript-v11/interaction-spans-integration.mdx new file mode 100644 index 0000000000000..c69f061a63f5e --- /dev/null +++ b/includes/migration/javascript-v11/interaction-spans-integration.mdx @@ -0,0 +1,27 @@ +--- +id: interaction-spans-integration +title: "Interaction spans moved to `interactionsIntegration`" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [tracing] +platformCategory: browser +order: 215 +--- + +The experimental `_experiments.enableInteractions` option of `browserTracingIntegration` was removed. Interaction spans now come from a dedicated opt-in integration. + +```js +// Before +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ _experiments: { enableInteractions: true } }), + ], +}); + +// After +Sentry.init({ + integrations: [Sentry.browserTracingIntegration(), Sentry.interactionsIntegration()], +}); +``` diff --git a/includes/migration/javascript-v11/metrics-cdn-bundle.mdx b/includes/migration/javascript-v11/metrics-cdn-bundle.mdx new file mode 100644 index 0000000000000..3ec173191e5ab --- /dev/null +++ b/includes/migration/javascript-v11/metrics-cdn-bundle.mdx @@ -0,0 +1,15 @@ +--- +id: metrics-cdn-bundle +title: "Metrics moved out of the base CDN bundle" +phase: packages +category: package +severity: action-required +frameworks: all +features: [metrics] +platformCategory: browser +order: 40 +--- + +Metrics ship only in the `*.logs.metrics` CDN bundles. On other bundles, `Sentry.metrics.*` is a no-op shim. + +If you use metrics via the CDN, switch your script tag to a `*.logs.metrics` bundle. diff --git a/includes/migration/javascript-v11/nextjs-min-version.mdx b/includes/migration/javascript-v11/nextjs-min-version.mdx new file mode 100644 index 0000000000000..73f1c87f2c40d --- /dev/null +++ b/includes/migration/javascript-v11/nextjs-min-version.mdx @@ -0,0 +1,15 @@ +--- +id: nextjs-min-version +title: "Next.js 13 support was dropped" +phase: prerequisites +category: version-support +severity: action-required +frameworks: [nextjs] +features: [] +platformCategory: all +order: 50 +--- + +`@sentry/nextjs` now requires Next.js 14. + +Upgrade Next.js before upgrading the SDK. diff --git a/includes/migration/javascript-v11/nextjs-react-component-annotation.mdx b/includes/migration/javascript-v11/nextjs-react-component-annotation.mdx new file mode 100644 index 0000000000000..7ae204ef4cf70 --- /dev/null +++ b/includes/migration/javascript-v11/nextjs-react-component-annotation.mdx @@ -0,0 +1,24 @@ +--- +id: nextjs-react-component-annotation +title: "`reactComponentAnnotation` is a single top-level option again" +phase: code-changes +category: behavior +severity: action-required +frameworks: [nextjs] +features: [] +platformCategory: all +order: 340 +--- + +React component annotation is configured through a single top-level `reactComponentAnnotation` option that covers webpack and Turbopack: + +```js +export default withSentryConfig(nextConfig, { + reactComponentAnnotation: { + enabled: true, + ignoredComponents: ['MyComponent'], + }, +}); +``` + +If you moved to `webpack.reactComponentAnnotation` for v10, move back to the top level. The bundler-specific options still work but are deprecated, and win over the top-level one for their bundler. On Turbopack, annotation requires Next.js 16 or higher, and the SDK now warns at build time on older versions. diff --git a/includes/migration/javascript-v11/nextjs-removed-options.mdx b/includes/migration/javascript-v11/nextjs-removed-options.mdx new file mode 100644 index 0000000000000..60cdf86157eed --- /dev/null +++ b/includes/migration/javascript-v11/nextjs-removed-options.mdx @@ -0,0 +1,29 @@ +--- +id: nextjs-removed-options +title: "Long-deprecated `withSentryConfig` options were removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [nextjs] +features: [] +platformCategory: all +order: 330 +--- + +These deprecated top-level options in `withSentryConfig` were removed: + +| Removed option | Replacement | +| --------------------------------------- | -------------------------------------------------- | +| `autoInstrumentServerFunctions` | `webpack.autoInstrumentServerFunctions` | +| `autoInstrumentMiddleware` | `webpack.autoInstrumentMiddleware` | +| `autoInstrumentAppDirectory` | `webpack.autoInstrumentAppDirectory` | +| `automaticVercelMonitors` | `webpack.automaticVercelMonitors` | +| `excludeServerRoutes` | `webpack.excludeServerRoutes` | +| `reactComponentAnnotation` | `webpack.reactComponentAnnotation` | +| `unstable_sentryWebpackPluginOptions` | removed, see the `unstable_` options item | +| `disableSentryWebpackConfig` | `webpack.disableSentryConfig` | +| `disableLogger` | `webpack.treeshake.removeDebugLogging` | +| `disableManifestInjection` | `routeManifestInjection: false` | +| `_experimental.turbopackApplicationKey` | `applicationKey` | + +Move each option you use to its replacement. `next.config.js` is often plain JavaScript, so check it by hand. diff --git a/includes/migration/javascript-v11/nextjs-template-tracing.mdx b/includes/migration/javascript-v11/nextjs-template-tracing.mdx new file mode 100644 index 0000000000000..5a0ca7144022f --- /dev/null +++ b/includes/migration/javascript-v11/nextjs-template-tracing.mdx @@ -0,0 +1,15 @@ +--- +id: nextjs-template-tracing +title: "Tracing was removed from generated Next.js templates" +phase: verify +category: behavior +severity: behavior-change +frameworks: [nextjs] +features: [tracing] +platformCategory: all +order: 100 +--- + +Tracing was removed from the generated Pages Router API handler, Edge API handler, and Middleware wrapper templates. + +Route handlers and middleware are still instrumented automatically. After upgrading, confirm you still see the spans you expect. diff --git a/includes/migration/javascript-v11/node-core-merged.mdx b/includes/migration/javascript-v11/node-core-merged.mdx new file mode 100644 index 0000000000000..0a8e637ab66fd --- /dev/null +++ b/includes/migration/javascript-v11/node-core-merged.mdx @@ -0,0 +1,23 @@ +--- +id: node-core-merged +title: "`@sentry/node-core` was merged back into `@sentry/node`" +phase: packages +category: package +severity: action-required +frameworks: all +features: [] +platformCategory: server +order: 20 +--- + +`@sentry/node-core` was removed. + +Remove it from your dependencies and import from `@sentry/node`: + +```js +// Before +import { init } from '@sentry/node-core'; + +// After +import { init } from '@sentry/node'; +``` diff --git a/includes/migration/javascript-v11/node-loader-entry-points.mdx b/includes/migration/javascript-v11/node-loader-entry-points.mdx new file mode 100644 index 0000000000000..0ffb2beb36567 --- /dev/null +++ b/includes/migration/javascript-v11/node-loader-entry-points.mdx @@ -0,0 +1,29 @@ +--- +id: node-loader-entry-points +title: "The `/loader`, `/init`, and `/preload` entry points were removed" +phase: packages +category: removed-api +severity: action-required +frameworks: all +features: [] +platformCategory: server +order: 80 +--- + +The loader and preload entry points were removed: + +- `@sentry/node/init` and `@sentry/node/preload` +- `@sentry/node/loader`, `@sentry/astro/loader`, `@sentry/aws-serverless/loader`, `@sentry/google-cloud-serverless/loader`, `@sentry/nextjs/loader`, `@sentry/remix/loader`, `@sentry/tanstackstart-react/loader` +- `preloadOpenTelemetry()` + +Replace `/loader` imports with `/import`: + +```bash +# Before +node --import @sentry/node/loader app.js + +# After +node --import @sentry/node/import app.js +``` + +If you used `/init` or `/preload`, create your own instrument file that calls `Sentry.init()` and preload it with `node --import ./instrument.mjs app.js`. Remove `preloadOpenTelemetry()` calls. diff --git a/includes/migration/javascript-v11/node-removed-apis.mdx b/includes/migration/javascript-v11/node-removed-apis.mdx new file mode 100644 index 0000000000000..d9345aca27da3 --- /dev/null +++ b/includes/migration/javascript-v11/node-removed-apis.mdx @@ -0,0 +1,24 @@ +--- +id: node-removed-apis +title: "`@sentry/node` and server SDK API removals" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [] +platformCategory: server +order: 180 +--- + +These were removed from `@sentry/node` and the server-side SDKs that re-exported them: + +- `SentryContextManager` +- The `OpenTelemetryServerRuntimeOptions` type. `enableOpenTelemetrySetup` is part of `NodeOptions` and the other SDK option types. +- The `connect` instrumentation. +- The `prismaInstrumentation` option. Prisma works out of the box. +- The `registerEsmLoaderHooks` option. +- `SentryHttpInstrumentation` and `SentryNodeFetchInstrumentation`. Use `instrumentHttpOutgoingRequests()` and `nativeNodeFetchIntegration`. +- `generateInstrumentOnce` +- (Fastify) `setShouldHandleError` +- (AWS Lambda) `disableAwsContextPropagation`, `startTrace`, and `tryPatchHandler`. To disable tracing, set `tracesSampleRate` to `0`. +- (Express) The `patchExpressModule(options)` signature. Use `patchExpressModule(moduleExports, getOptions)`. diff --git a/includes/migration/javascript-v11/node-version.mdx b/includes/migration/javascript-v11/node-version.mdx new file mode 100644 index 0000000000000..084e27f9023a1 --- /dev/null +++ b/includes/migration/javascript-v11/node-version.mdx @@ -0,0 +1,24 @@ +--- +id: node-version +title: "Node.js 20.19.0 is the new minimum" +phase: prerequisites +category: version-support +severity: action-required +frameworks: all +features: [] +platformCategory: all +order: 10 +--- + +The minimum supported Node.js version is now 20.19.0, and the supported range has gaps: + +| Node.js version | Supported | +| --------------- | --------- | +| < 20.19.0 | No | +| 20.19.0 to 21.x | Yes | +| 22.0 to 22.11 | No | +| 22.12 to 22.x | Yes | +| 23.0 to 23.1 | No | +| >= 23.2.0 | Yes | + +Move to a supported version everywhere the SDK is installed or your app is built: Dockerfiles, CI images, `engines`, `.nvmrc`, and serverless runtime settings. This applies to browser projects too, if their build steps run on Node.js. diff --git a/includes/migration/javascript-v11/nuxt-source-maps-options.mdx b/includes/migration/javascript-v11/nuxt-source-maps-options.mdx new file mode 100644 index 0000000000000..0a9907b4d6c63 --- /dev/null +++ b/includes/migration/javascript-v11/nuxt-source-maps-options.mdx @@ -0,0 +1,31 @@ +--- +id: nuxt-source-maps-options +title: "`sourceMapsUploadOptions` was removed from the Nuxt module" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [nuxt] +features: [] +platformCategory: all +order: 360 +--- + +The deprecated `sourceMapsUploadOptions` module option was removed. `url` was renamed to `sentryUrl`, and `enabled: false` became `sourcemaps: { disable: true }`. + +Move its fields to the root of the `sentry` module options: + +```ts +// nuxt.config.ts +export default defineNuxtConfig({ + modules: ['@sentry/nuxt/module'], + sentry: { + org: 'my-org', + project: 'my-project', + authToken: process.env.SENTRY_AUTH_TOKEN, + sentryUrl: 'https://my-sentry.example.com', + sourcemaps: { + assets: ['./dist/**/*'], + }, + }, +}); +``` diff --git a/includes/migration/javascript-v11/opentelemetry-interop.mdx b/includes/migration/javascript-v11/opentelemetry-interop.mdx new file mode 100644 index 0000000000000..44f7b763cfe15 --- /dev/null +++ b/includes/migration/javascript-v11/opentelemetry-interop.mdx @@ -0,0 +1,19 @@ +--- +id: opentelemetry-interop +title: "Sentry no longer sets up OpenTelemetry for you" +phase: code-changes +category: behavior +severity: behavior-change +frameworks: all +features: [tracing] +platformCategory: server +order: 120 +--- + +Most server SDKs no longer set up an OpenTelemetry tracer provider and produce native Sentry spans instead. Only `@sentry/nextjs` and `@sentry/sveltekit` still register a lightweight OpenTelemetry-compatible provider. `@opentelemetry/api` is the only remaining OpenTelemetry dependency. + +Nothing to do if you only use the Sentry SDK. If you run your own OpenTelemetry setup, read these: + +- [`skipOpenTelemetrySetup` is replaced by `enableOpenTelemetrySetup`](#enable-opentelemetry-setup) +- [Custom OpenTelemetry setups must export over OTLP](#custom-otel-setup) +- [`otlpIntegration` moved and lost its exporter options](#otlp-integration-changes) diff --git a/includes/migration/javascript-v11/opentelemetry-removed-apis.mdx b/includes/migration/javascript-v11/opentelemetry-removed-apis.mdx new file mode 100644 index 0000000000000..7c16e6a3ca6c0 --- /dev/null +++ b/includes/migration/javascript-v11/opentelemetry-removed-apis.mdx @@ -0,0 +1,15 @@ +--- +id: opentelemetry-removed-apis +title: "`@sentry/opentelemetry` removals" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [custom-otel] +platformCategory: server +order: 160 +--- + +`getTraceContextForScope` and `getSentryResource` were removed, the `@opentelemetry/core` peer dependency is gone, and OpenTelemetry resources are no longer collected. `contexts.otel.resource` was dropped from events, and `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` are no longer read. + +Remove any use of the two functions. If you set those environment variables, set the equivalent values on the SDK directly, and update dashboards or alerts that read `contexts.otel.resource`. diff --git a/includes/migration/javascript-v11/otlp-integration-changes.mdx b/includes/migration/javascript-v11/otlp-integration-changes.mdx new file mode 100644 index 0000000000000..d2a8ecb772aaa --- /dev/null +++ b/includes/migration/javascript-v11/otlp-integration-changes.mdx @@ -0,0 +1,27 @@ +--- +id: otlp-integration-changes +title: "`otlpIntegration` moved and lost its exporter options" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [custom-otel] +platformCategory: server +order: 150 +--- + +`Sentry.otlpIntegration()` is exported from every server-side SDK now, and no longer sets up an exporter, span processor, or tracer provider. The `@sentry/node-core/light/otlp` entry point, the `setupOtlpTracesExporter` and `collectorUrl` options, and the `OtlpIntegration` name were removed. + +Import it from your platform SDK and configure your own exporter, pointing it at `Sentry.getOtlpTracesEndpoint(dsn)` or at your collector: + +```diff +- import { otlpIntegration } from '@sentry/node-core/light/otlp'; ++ import { otlpIntegration } from '@sentry/node'; +``` + +If you reference the integration by name, update it: + +```diff +- integrations.filter(integration => integration.name !== 'OtlpIntegration') ++ integrations.filter(integration => integration.name !== 'Otlp') +``` diff --git a/includes/migration/javascript-v11/profiling-legacy-options.mdx b/includes/migration/javascript-v11/profiling-legacy-options.mdx new file mode 100644 index 0000000000000..e71b5040dc1b4 --- /dev/null +++ b/includes/migration/javascript-v11/profiling-legacy-options.mdx @@ -0,0 +1,26 @@ +--- +id: profiling-legacy-options +title: "`profilesSampleRate` and `profilesSampler` were removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [profiling] +platformCategory: all +order: 250 +--- + +The `profilesSampleRate` and `profilesSampler` options were removed from browser and Node.js profiling. + +Use session-based profiling with `profileSessionSampleRate` and a `profileLifecycle` of `'trace'` (starts and stops with active traces) or `'manual'`: + +```js +// Before +Sentry.init({ profilesSampleRate: 1.0 }); + +// After +Sentry.init({ + profileSessionSampleRate: 1.0, + profileLifecycle: 'trace', +}); +``` diff --git a/includes/migration/javascript-v11/profiling-node-prune-script.mdx b/includes/migration/javascript-v11/profiling-node-prune-script.mdx new file mode 100644 index 0000000000000..bb5e5ece29c48 --- /dev/null +++ b/includes/migration/javascript-v11/profiling-node-prune-script.mdx @@ -0,0 +1,15 @@ +--- +id: profiling-node-prune-script +title: "The `prune-profiler-binaries` script was removed" +phase: code-changes +category: removed-api +severity: informational +frameworks: all +features: [profiling] +platformCategory: server +order: 260 +--- + +The `prune-profiler-binaries` script was removed from `@sentry/profiling-node`. + +Nothing to do, unless you call it in a build or packaging step. Remove that call. diff --git a/includes/migration/javascript-v11/react-error-boundary-type.mdx b/includes/migration/javascript-v11/react-error-boundary-type.mdx new file mode 100644 index 0000000000000..a8ff73868fe67 --- /dev/null +++ b/includes/migration/javascript-v11/react-error-boundary-type.mdx @@ -0,0 +1,15 @@ +--- +id: react-error-boundary-type +title: "`withErrorBoundary` returns a memoized component type" +phase: cleanup +category: type +severity: informational +frameworks: [react, nextjs, remix, react-router, tanstackstart-react, gatsby] +features: [] +platformCategory: browser +order: 40 +--- + +`withErrorBoundary` wraps its component in `memo`, so its return type is `React.MemoExoticComponent` instead of a plain function component. + +Nothing to do, unless you annotated the result yourself. Update that annotation. diff --git a/includes/migration/javascript-v11/react-min-version.mdx b/includes/migration/javascript-v11/react-min-version.mdx new file mode 100644 index 0000000000000..4326d247f4499 --- /dev/null +++ b/includes/migration/javascript-v11/react-min-version.mdx @@ -0,0 +1,15 @@ +--- +id: react-min-version +title: "React 16 support was dropped" +phase: prerequisites +category: version-support +severity: action-required +frameworks: [react, nextjs, remix, react-router, tanstackstart-react, gatsby] +features: [] +platformCategory: browser +order: 60 +--- + +`@sentry/react` now requires React 17. + +Upgrade React before upgrading the SDK. diff --git a/includes/migration/javascript-v11/react-router-min-version.mdx b/includes/migration/javascript-v11/react-router-min-version.mdx new file mode 100644 index 0000000000000..186b9134fd32a --- /dev/null +++ b/includes/migration/javascript-v11/react-router-min-version.mdx @@ -0,0 +1,15 @@ +--- +id: react-router-min-version +title: "React Router 7.15 is the new minimum" +phase: prerequisites +category: version-support +severity: action-required +frameworks: [react-router] +features: [] +platformCategory: all +order: 80 +--- + +`@sentry/react-router` now requires React Router 7.15 in framework mode. + +Upgrade React Router before upgrading the SDK. diff --git a/includes/migration/javascript-v11/react-router-server-wrappers.mdx b/includes/migration/javascript-v11/react-router-server-wrappers.mdx new file mode 100644 index 0000000000000..36ed856c46e6f --- /dev/null +++ b/includes/migration/javascript-v11/react-router-server-wrappers.mdx @@ -0,0 +1,19 @@ +--- +id: react-router-server-wrappers +title: "`wrapServerLoader` and `wrapServerAction` were removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [react-router] +features: [tracing] +platformCategory: server +order: 410 +--- + +The deprecated `wrapServerLoader`, `wrapServerAction`, and `sentryHandleRequest` exports were removed. + +Loaders and actions are instrumented automatically. Export the instrumentation from your `entry.server.tsx`, and replace `sentryHandleRequest` with `wrapSentryHandleRequest`: + +```ts +export const instrumentations = [Sentry.createSentryServerInstrumentation()]; +``` diff --git a/includes/migration/javascript-v11/react-router-source-maps-options.mdx b/includes/migration/javascript-v11/react-router-source-maps-options.mdx new file mode 100644 index 0000000000000..6f7c6be6d8c05 --- /dev/null +++ b/includes/migration/javascript-v11/react-router-source-maps-options.mdx @@ -0,0 +1,15 @@ +--- +id: react-router-source-maps-options +title: "`sourceMapsUploadOptions` was removed from `sentryReactRouter()`" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [react-router] +features: [] +platformCategory: all +order: 380 +--- + +The deprecated `sourceMapsUploadOptions` option was removed from `sentryReactRouter()`. `enabled: false` became `sourcemaps: { disable: true }`. + +Move its fields to the root of the `sentryConfig` passed to `sentryReactRouter()`. diff --git a/includes/migration/javascript-v11/remix-action-form-data.mdx b/includes/migration/javascript-v11/remix-action-form-data.mdx new file mode 100644 index 0000000000000..f479bdd990411 --- /dev/null +++ b/includes/migration/javascript-v11/remix-action-form-data.mdx @@ -0,0 +1,28 @@ +--- +id: remix-action-form-data +title: "Remix action form data is reported under a new attribute name" +phase: code-changes +category: behavior +severity: action-required +frameworks: [remix] +features: [tracing] +platformCategory: all +order: 30 +--- + +`captureActionFormDataKeys` is an integration-level override now, so it no longer requires `dataCollection.httpBodies` to include `'incomingRequest'`. Captured fields are reported as `remix.action_form_data.` span attributes on every runtime, where Node.js previously used `formData.`. + +```js +// v10: both were required +Sentry.init({ + captureActionFormDataKeys: { username: true }, + dataCollection: { httpBodies: ['incomingRequest'] }, +}); + +// v11 +Sentry.init({ + captureActionFormDataKeys: { username: true }, +}); +``` + +Update dashboards, alerts, and saved searches that query `formData.*`. If you do not set `captureActionFormDataKeys`, all form fields are captured when `dataCollection.httpBodies` includes `'incomingRequest'`, with sensitive-looking names replaced by `[Filtered]`. diff --git a/includes/migration/javascript-v11/remix-min-version.mdx b/includes/migration/javascript-v11/remix-min-version.mdx new file mode 100644 index 0000000000000..d1c53050993b7 --- /dev/null +++ b/includes/migration/javascript-v11/remix-min-version.mdx @@ -0,0 +1,15 @@ +--- +id: remix-min-version +title: "`@remix-run/node` v1 support was dropped" +phase: prerequisites +category: version-support +severity: action-required +frameworks: [remix] +features: [] +platformCategory: all +order: 90 +--- + +`@sentry/remix` now requires `@remix-run/node` v2. + +Upgrade `@remix-run/node` before upgrading the SDK. diff --git a/includes/migration/javascript-v11/request-data-integration.mdx b/includes/migration/javascript-v11/request-data-integration.mdx new file mode 100644 index 0000000000000..7dd1126d23cc2 --- /dev/null +++ b/includes/migration/javascript-v11/request-data-integration.mdx @@ -0,0 +1,15 @@ +--- +id: request-data-integration +title: "`requestDataIntegration` overrides now interact with `dataCollection`" +phase: code-changes +category: behavior +severity: behavior-change +frameworks: all +features: [] +platformCategory: server +order: 20 +--- + +The `include` options of `requestDataIntegration` stay an integration-level override on top of `dataCollection`, and user IP inference is now controlled by `dataCollection.userInfo`. + +Nothing to do, unless you set `sendDefaultPii: false` and relied on `requestDataIntegration({ include: { ip: true } })`. That override still wins, so verify the combination matches your intent under the new defaults. diff --git a/includes/migration/javascript-v11/require-to-import.mdx b/includes/migration/javascript-v11/require-to-import.mdx new file mode 100644 index 0000000000000..262a9027b550f --- /dev/null +++ b/includes/migration/javascript-v11/require-to-import.mdx @@ -0,0 +1,25 @@ +--- +id: require-to-import +title: "Initializing via `--require` is no longer supported" +phase: code-changes +category: behavior +severity: action-required +frameworks: all +features: [] +platformCategory: server +order: 110 +--- + +The SDK now skips initialization on the Node.js internal module loader thread, and warns when it detects that it was loaded through `--require`. + +Use [`--import`](https://nodejs.org/api/cli.html#--importmodule) instead: + +```bash +# Before +node --require ./instrument.js app.js + +# After +node --import ./instrument.js app.js +``` + +`--import` works for CommonJS apps too. The instrument file's extension decides how it loads. diff --git a/includes/migration/javascript-v11/sentry-cli-v4.mdx b/includes/migration/javascript-v11/sentry-cli-v4.mdx new file mode 100644 index 0000000000000..e15fe3fd41dff --- /dev/null +++ b/includes/migration/javascript-v11/sentry-cli-v4.mdx @@ -0,0 +1,15 @@ +--- +id: sentry-cli-v4 +title: "The SDK and bundler plugins now use Sentry CLI v4" +phase: prerequisites +category: version-support +severity: behavior-change +frameworks: all +features: [] +platformCategory: all +order: 120 +--- + +The SDK and the Sentry bundler plugins now depend on Sentry CLI v4. + +Nothing to do, unless you pin `@sentry/cli` yourself or call it in scripts or CI. Upgrade those to v4. diff --git a/includes/migration/javascript-v11/sentry-types-removed.mdx b/includes/migration/javascript-v11/sentry-types-removed.mdx new file mode 100644 index 0000000000000..9e11416f8b8c6 --- /dev/null +++ b/includes/migration/javascript-v11/sentry-types-removed.mdx @@ -0,0 +1,23 @@ +--- +id: sentry-types-removed +title: "`@sentry/types` is no longer published" +phase: packages +category: package +severity: action-required +frameworks: all +features: [] +platformCategory: all +order: 10 +--- + +The `@sentry/types` package is no longer published. + +Remove it from your dependencies and import types from `@sentry/core`: + +```js +// Before +import type { Event } from '@sentry/types'; + +// After +import type { Event } from '@sentry/core'; +``` diff --git a/includes/migration/javascript-v11/server-utils-removed-exports.mdx b/includes/migration/javascript-v11/server-utils-removed-exports.mdx new file mode 100644 index 0000000000000..1fbab5be9b125 --- /dev/null +++ b/includes/migration/javascript-v11/server-utils-removed-exports.mdx @@ -0,0 +1,15 @@ +--- +id: server-utils-removed-exports +title: "`@sentry/server-utils` internal exports were removed" +phase: code-changes +category: removed-api +severity: informational +frameworks: all +features: [] +platformCategory: server +order: 190 +--- + +These internal exports were removed from `@sentry/server-utils`: `instrumentPrisma`, `defaultDbStatementSerializer`, and the types `PrismaInstrumentationConfig`, `PrismaOptions`, `RedisDiagnosticChannelsOptions`, `SentryTracingChannel`, `TracingChannelLifeCycleOptions`, `TracingChannelBindingHandle`. + +Nothing to do, unless you imported from `@sentry/server-utils` directly, which was never a supported path. Prisma is instrumented via `prismaIntegration`. diff --git a/includes/migration/javascript-v11/solidstart-source-maps-options.mdx b/includes/migration/javascript-v11/solidstart-source-maps-options.mdx new file mode 100644 index 0000000000000..39d6188d82e5c --- /dev/null +++ b/includes/migration/javascript-v11/solidstart-source-maps-options.mdx @@ -0,0 +1,33 @@ +--- +id: solidstart-source-maps-options +title: "`sourceMapsUploadOptions` was removed from the SolidStart build options" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [solidstart] +features: [] +platformCategory: all +order: 390 +--- + +The `sourceMapsUploadOptions` build option was removed from SolidStart 1 setups using `withSentry()` or `sentrySolidStartVite()`. `enabled: false` became `sourcemaps: { disable: true }`. + +Move its fields to the top level: + +```ts +// app.config.ts +export default defineConfig( + withSentry( + {}, + { + telemetry: false, + sourcemaps: { + disable: false, + filesToDeleteAfterUpload: ['./dist/**/*.map'], + }, + }, + ), +); +``` + +The build options also accept `applicationKey`, `sentryUrl`, `headers`, `silent`, `errorHandler`, `release`, and `moduleMetadata` now. diff --git a/includes/migration/javascript-v11/span-attribute-changes.mdx b/includes/migration/javascript-v11/span-attribute-changes.mdx new file mode 100644 index 0000000000000..28425d340a396 --- /dev/null +++ b/includes/migration/javascript-v11/span-attribute-changes.mdx @@ -0,0 +1,50 @@ +--- +id: span-attribute-changes +title: "Span attributes were renamed to match semantic conventions" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [tracing] +platformCategory: all +order: 60 +--- + +Span attributes follow the shared `@sentry/conventions` package now, and many were renamed. + +**HTTP and network** + +- `http.query` and `http.fragment` to `url.query` and `url.fragment` +- `net.peer.name` and `net.peer.port` on database and messaging client spans to `server.address` and `server.port` +- `net.transport` to `network.transport` + +**Database** + +- `db.system` to `db.system.name` +- `db.name` to `db.namespace` +- `db.operation` to `db.operation.name` +- `db.statement` to `db.query.text` +- `db.mongodb.collection` to `db.collection.name` +- Legacy `messaging.*` attributes on the AMQP instrumentation now use their semantic-convention equivalents + +**Gen AI** + +- `gen_ai.system` to `gen_ai.provider.name` +- `gen_ai.request.available_tools` to `gen_ai.tool.definitions` +- `gen_ai.tool.input` to `gen_ai.tool.call.arguments` +- `gen_ai.tool.output` to `gen_ai.tool.call.result` +- `gen_ai.usage.cache_creation_input_tokens` to `gen_ai.usage.cache_creation.input_tokens` +- `gen_ai.usage.cache_read_input_tokens` to `gen_ai.usage.cache_read.input_tokens` +- (Vercel AI) `gen_ai.usage.input_tokens.cached` to `gen_ai.usage.cache_read.input_tokens` +- (Vercel AI) `gen_ai.usage.input_tokens.cache_write` to `gen_ai.usage.cache_creation.input_tokens` +- (Vercel AI) `gen_ai.usage.output_tokens.reasoning` to `gen_ai.usage.reasoning.output_tokens` +- (Vercel AI) `ai.pipeline.name` and `ai.streaming` to `gen_ai.pipeline.name` and `gen_ai.response.streaming` +- `gen_ai.tool.type` is no longer set +- (Anthropic) `gen_ai.prompt` is no longer set. The legacy Completions API's `prompt` is reported as a user message on `gen_ai.input.messages`. + +**Browser and filesystem** + +- `code.filepath` and `code.function` on `ui.long_animation_frame` spans to `code.file.path` and `code.function.name` +- `fs_error` on `file` spans to `error.type`, carrying the syscall error code (such as `ENOENT`) instead of the full message + +Update any custom instrumentation, `beforeSendSpan` callback, dashboard or alert that references these. TypeScript does not catch them. diff --git a/includes/migration/javascript-v11/span-op-changes.mdx b/includes/migration/javascript-v11/span-op-changes.mdx new file mode 100644 index 0000000000000..ef2b6ee3eebd5 --- /dev/null +++ b/includes/migration/javascript-v11/span-op-changes.mdx @@ -0,0 +1,83 @@ +--- +id: span-op-changes +title: "Span operations were consolidated" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [tracing] +platformCategory: all +order: 70 +--- + +Span ops were aligned to a smaller, framework-neutral set. The detail that used to live in the op is preserved in attributes such as `code.function.name`, `sentry.origin`, `db.system.name`, `db.operation.name`, and `faas.trigger`. + +**Backend HTTP, handlers, middleware, and routers** + +| Area | Before | After | +| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| Request handlers (Express, Koa, Connect, Fastify, Elysia, NestJS) | `request_handler.`, `handler.nestjs` | `handler` | +| Hono `app.request()` in-process dispatch | `hono.request` | `http.server` | +| Web-server middleware | `middleware.express`, `middleware.koa`, `middleware.hono`, `middleware.elysia`, `middleware.nestjs`, `middleware.nuxt`, `middleware.nitro`, `middleware.tanstackstart`, `hook.fastify`, `http.server.middleware` (Next.js) | `middleware` | +| Backend router layers | `router.express`, `router.koa`, `router.hapi` | `router` | +| Hapi server extensions | `server.ext.hapi` | `middleware` | +| NestJS setup and lifecycle handlers | `app_creation.nestjs`, `request_context.nestjs`, `event.nestjs` | `function` | + +**Framework functions** + +| Area | Before | After | +| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| Loaders, actions, and server functions (Next.js, Remix, React Router, SvelteKit, SolidStart, TanStack Start) | `function.nextjs`, `function.sveltekit.load`, `function.react_router.loader`, `function.remix.document_request`, `loader.remix`, `action.remix`, `function.server_action`, `function.tanstackstart` | `function` | + +**Frontend and UI** + +| Area | Before | After | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- | +| Frontend routing | `ui.angular.routing`, `ui.sveltekit.routing`, `ui.ember.transition` | `router` | +| React, Vue, and Svelte component lifecycles | `ui.react.mount`/`render`/`update`, `ui.svelte.init`/`update`, Vue `render`/`update`/`mount`/`create`/`activate`/`unmount`/`destroy` | `ui.mount`, `ui.render`, `ui.update`, `ui.unmount` | +| Angular tracing decorators | `ui.angular.init`, `ui.angular.` | `ui.mount`, `function` | +| Ember route hooks, runloop, and components | `ui.ember.route.`, `ui.ember.runloop.`, `ui.ember.component.render`/`definition`/`init` | `function`, `ui.task`, `ui.render`/`function`/`ui.mount` | +| Browser paint entries | `paint` | `browser.paint` | + +**Databases, cache, and messaging** + +| Area | Before | After | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| Redis commands and connect | `db.redis`, `db.redis.connect` | `db.query`, `db` | +| Nuxt and Nitro storage (unstorage) | `cache.has_item`, `cache.get_item`, `cache.get_items`, `cache.get_keys`, `cache.set_item`, `cache.set_items`, `cache.remove_item`, `cache.clear` | `cache.get`, `cache.put`, `cache.remove` | +| Kafka, AMQP, and OTel-inferred messaging | `message`, `message.produce`, `message.consume` | `queue.publish`, `queue.receive`, `queue.process` | + +**RPC and Gen AI** + +| Area | Before | After | +| ---------------------------------------------------------- | ------------------------------------------- | ---------------------------------------- | +| tRPC | `rpc.server` | `rpc` | +| GCP gRPC calls | `grpc.` | `grpc` | +| AWS Bedrock inference | `rpc` | `gen_ai.chat`, `gen_ai.generate_content` | +| Gen AI fallbacks and model metadata (Vercel AI, LangGraph) | `gen_ai.unknown`, `ai.run`, `gen_ai.models` | `function` | + +**FaaS, serverless, and HTTP clients** + +| Area | Before | After | +| ---------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------ | +| AWS Lambda functions | `function.aws.lambda` | `function.aws` | +| GCP functions | `function.gcp.http`, `function.gcp.event`, `function.gcp.cloud_event` | `function.gcp` | +| Firebase functions | `http.request` | `function.gcp` | +| Cloudflare cron, email, and workflow steps | `faas.cron`, `faas.email`, `function.step.do` | `function` | +| OTel-inferred FaaS spans (from `faas.trigger`) | arbitrary trigger strings used verbatim | `http.server`, `queue.process`, `function` | +| GCP HTTP client | `http.client.` | `http.client` | +| Prefetch HTTP requests | `http.client.prefetch`, `http.server.prefetch` | `http.client`, `http.server` | + +**Casing normalized to snake_case** + +| Before | After | +| ------------------------------- | ---------------------------------- | +| `ui.long-task` | `ui.long_task` | +| `ui.long-animation-frame` | `ui.long_animation_frame` | +| `browser.unloadEvent` | `browser.unload_event` | +| `browser.domContentLoadedEvent` | `browser.dom_content_loaded_event` | +| `browser.loadEvent` | `browser.load_event` | +| `browser.TLS/SSL` | `browser.tls_ssl` | +| `browser.DNS` | `browser.dns` | + +Update anything that filters, groups, or alerts on span ops: dashboards, dynamic sampling rules, `ignoreSpans`, and `beforeSendSpan`. TypeScript does not catch these. diff --git a/includes/migration/javascript-v11/span-streaming-default.mdx b/includes/migration/javascript-v11/span-streaming-default.mdx new file mode 100644 index 0000000000000..a591f4705f9dd --- /dev/null +++ b/includes/migration/javascript-v11/span-streaming-default.mdx @@ -0,0 +1,19 @@ +--- +id: span-streaming-default +title: "Span streaming is now the default" +phase: code-changes +category: behavior +severity: behavior-change +frameworks: all +features: [tracing] +platformCategory: all +order: 40 +--- + +Spans are sent in small batches as they finish, instead of being buffered until the root span completes. The 1000-span-per-transaction limit is gone. + +No transaction events are produced any more, so read these if you use them: + +- [`beforeSendSpan` receives the streamed span format](#before-send-span-format) +- [`beforeSendTransaction` no longer runs](#before-send-transaction-removed) +- [`ignoreTransactions` is replaced by `ignoreSpans`](#ignore-transactions-removed) diff --git a/includes/migration/javascript-v11/span-streaming-opt-out.mdx b/includes/migration/javascript-v11/span-streaming-opt-out.mdx new file mode 100644 index 0000000000000..da4e8a8f88604 --- /dev/null +++ b/includes/migration/javascript-v11/span-streaming-opt-out.mdx @@ -0,0 +1,27 @@ +--- +id: span-streaming-opt-out +title: "Opting out of span streaming" +phase: code-changes +category: behavior +severity: informational +frameworks: all +features: [tracing] +platformCategory: all +order: 80 +--- + +`traceLifecycle: 'static'` keeps the previous transaction-based model if you cannot migrate `beforeSendSpan`, `beforeSendTransaction`, or `ignoreTransactions` yet. + +```js +Sentry.init({ + traceLifecycle: 'static', + + // `beforeSendSpan` MUST be wrapped with Sentry.withStaticSpan: + beforeSendSpan: Sentry.withStaticSpan(span => { + span.description = scrub(span.description); + return span; + }), +}); +``` + +In Node.js, Bun, Vercel Edge, and Cloudflare you can set `SENTRY_TRACE_LIFECYCLE=static` instead. The static lifecycle is planned for removal in a future major version. diff --git a/includes/migration/javascript-v11/span-to-json-format.mdx b/includes/migration/javascript-v11/span-to-json-format.mdx new file mode 100644 index 0000000000000..5a5d585761aef --- /dev/null +++ b/includes/migration/javascript-v11/span-to-json-format.mdx @@ -0,0 +1,25 @@ +--- +id: span-to-json-format +title: "`spanToJSON` returns the streamed span format" +phase: code-changes +category: behavior +severity: action-required +frameworks: all +features: [tracing] +platformCategory: all +order: 75 +--- + +`Sentry.spanToJSON` returns a `StreamedSpanJSON` object instead of `SpanJSON`, the same shape [`beforeSendSpan` receives](#before-send-span-format). `spanToStreamedSpanJSON` was removed. + +Replace calls to the removed helper: + +```js +// Before +const spanJson = Sentry.spanToStreamedSpanJSON(span); + +// After +const spanJson = Sentry.spanToJSON(span); +``` + +If you [opt out of span streaming](#span-streaming-opt-out), use `spanToStaticSpanJSON` to get the old `SpanJSON` shape. diff --git a/includes/migration/javascript-v11/sveltekit-source-maps-options.mdx b/includes/migration/javascript-v11/sveltekit-source-maps-options.mdx new file mode 100644 index 0000000000000..36194c5300ffa --- /dev/null +++ b/includes/migration/javascript-v11/sveltekit-source-maps-options.mdx @@ -0,0 +1,33 @@ +--- +id: sveltekit-source-maps-options +title: "`sourceMapsUploadOptions` was removed from `sentrySvelteKit()`" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [sveltekit] +features: [] +platformCategory: all +order: 370 +--- + +The deprecated `sourceMapsUploadOptions` option was removed from `sentrySvelteKit()`. `url` was renamed to `sentryUrl`. + +Move its fields to the root of the `sentrySvelteKit()` options: + +```ts +// vite.config.ts +export default defineConfig({ + plugins: [ + sentrySvelteKit({ + org: 'my-org', + project: 'my-project', + authToken: process.env.SENTRY_AUTH_TOKEN, + sentryUrl: 'https://my-sentry.example.com', + sourcemaps: { + assets: ['./build/**/*'], + }, + }), + sveltekit(), + ], +}); +``` diff --git a/includes/migration/javascript-v11/sveltekit-vite-subpath.mdx b/includes/migration/javascript-v11/sveltekit-vite-subpath.mdx new file mode 100644 index 0000000000000..9a6b1563ba62f --- /dev/null +++ b/includes/migration/javascript-v11/sveltekit-vite-subpath.mdx @@ -0,0 +1,23 @@ +--- +id: sveltekit-vite-subpath +title: "`sentrySvelteKit` moved to the `@sentry/sveltekit/vite` subpath" +phase: packages +category: removed-api +severity: action-required +frameworks: [sveltekit] +features: [] +platformCategory: all +order: 70 +--- + +The `sentrySvelteKit` Vite plugin is no longer re-exported from the main `@sentry/sveltekit` entry point. + +Import it from `@sentry/sveltekit/vite` in your `vite.config.ts`: + +```ts +// Before +import { sentrySvelteKit } from '@sentry/sveltekit'; + +// After +import { sentrySvelteKit } from '@sentry/sveltekit/vite'; +``` diff --git a/includes/migration/javascript-v11/tanstackstart-removed.mdx b/includes/migration/javascript-v11/tanstackstart-removed.mdx new file mode 100644 index 0000000000000..93e00e5e5252d --- /dev/null +++ b/includes/migration/javascript-v11/tanstackstart-removed.mdx @@ -0,0 +1,15 @@ +--- +id: tanstackstart-removed +title: "`@sentry/tanstackstart` was removed" +phase: packages +category: package +severity: action-required +frameworks: [tanstackstart-react] +features: [] +platformCategory: all +order: 30 +--- + +The `@sentry/tanstackstart` package was removed. + +Use `@sentry/tanstackstart-react` instead. diff --git a/includes/migration/javascript-v11/third-party-error-filter-internal-frames.mdx b/includes/migration/javascript-v11/third-party-error-filter-internal-frames.mdx new file mode 100644 index 0000000000000..72b0993350ce7 --- /dev/null +++ b/includes/migration/javascript-v11/third-party-error-filter-internal-frames.mdx @@ -0,0 +1,15 @@ +--- +id: third-party-error-filter-internal-frames +title: "`thirdPartyErrorFilterIntegration` ignores Sentry frames by default" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [] +platformCategory: browser +order: 130 +--- + +`ignoreSentryInternalFrames` is the default behavior of `thirdPartyErrorFilterIntegration` now, so errors are no longer attributed to third-party code because of a Sentry frame in the stack. + +If you filter on third-party errors, expect fewer of them after upgrading, and check any alerts built on that. diff --git a/includes/migration/javascript-v11/trace-propagation-targets-case.mdx b/includes/migration/javascript-v11/trace-propagation-targets-case.mdx new file mode 100644 index 0000000000000..0f194d00a91aa --- /dev/null +++ b/includes/migration/javascript-v11/trace-propagation-targets-case.mdx @@ -0,0 +1,15 @@ +--- +id: trace-propagation-targets-case +title: "`tracePropagationTargets` matching is now case-insensitive" +phase: verify +category: behavior +severity: behavior-change +frameworks: all +features: [tracing] +platformCategory: all +order: 50 +--- + +String and regular-expression matching for `tracePropagationTargets` is now case-insensitive. + +Nothing to do, unless you relied on case sensitivity to exclude a target. It will now match and receive trace headers. diff --git a/includes/migration/javascript-v11/track-fetch-stream-performance.mdx b/includes/migration/javascript-v11/track-fetch-stream-performance.mdx new file mode 100644 index 0000000000000..191643fb609c1 --- /dev/null +++ b/includes/migration/javascript-v11/track-fetch-stream-performance.mdx @@ -0,0 +1,27 @@ +--- +id: track-fetch-stream-performance +title: "`trackFetchStreamPerformance` moved to its own integration" +phase: code-changes +category: removed-api +severity: action-required +frameworks: all +features: [tracing] +platformCategory: browser +order: 230 +--- + +The deprecated `trackFetchStreamPerformance` option of `browserTracingIntegration` was removed. + +Add `fetchStreamPerformanceIntegration()` instead: + +```js +// Before +Sentry.init({ + integrations: [Sentry.browserTracingIntegration({ trackFetchStreamPerformance: true })], +}); + +// After +Sentry.init({ + integrations: [Sentry.browserTracingIntegration(), Sentry.fetchStreamPerformanceIntegration()], +}); +``` diff --git a/includes/migration/javascript-v11/type-changes.mdx b/includes/migration/javascript-v11/type-changes.mdx new file mode 100644 index 0000000000000..e0529982aa6c4 --- /dev/null +++ b/includes/migration/javascript-v11/type-changes.mdx @@ -0,0 +1,21 @@ +--- +id: type-changes +title: "Type changes" +phase: cleanup +category: type +severity: informational +frameworks: all +features: [] +platformCategory: all +order: 10 +--- + +Several public types changed: + +- `StackFrame`, `SamplingContext`, `SentryError`, and `User` use `unknown` instead of `any`. +- Attribute typing and serialization were unified. +- `attributes` on `ScopeData` is required. +- `endTimestamp` was removed from `SentrySpanArguments`. +- `BrowserOptions` supports the `TransportOptions` generic. + +Narrow types where you relied on `any`, add `attributes: {}` where you construct `ScopeData` manually, and replace `endTimestamp` with `span.end(timestamp)`. diff --git a/includes/migration/javascript-v11/typescript-version.mdx b/includes/migration/javascript-v11/typescript-version.mdx new file mode 100644 index 0000000000000..afade7cc45d84 --- /dev/null +++ b/includes/migration/javascript-v11/typescript-version.mdx @@ -0,0 +1,15 @@ +--- +id: typescript-version +title: "TypeScript 5.0.4 is the new minimum" +phase: prerequisites +category: version-support +severity: action-required +frameworks: all +features: [] +platformCategory: all +order: 40 +--- + +The minimum supported TypeScript version is now 5.0.4, and the SDK no longer emits down-leveled types. + +Upgrade TypeScript to 5.0.4 or higher. diff --git a/includes/migration/javascript-v11/unstable-bundler-plugin-options.mdx b/includes/migration/javascript-v11/unstable-bundler-plugin-options.mdx new file mode 100644 index 0000000000000..be5e49ab0eff4 --- /dev/null +++ b/includes/migration/javascript-v11/unstable-bundler-plugin-options.mdx @@ -0,0 +1,36 @@ +--- +id: unstable-bundler-plugin-options +title: "The `unstable_sentry*PluginOptions` escape hatch was removed" +phase: code-changes +category: removed-api +severity: action-required +frameworks: [astro, nextjs, nuxt, react-router, solidstart, sveltekit] +features: [] +platformCategory: all +order: 350 +--- + +The `unstable_sentry*PluginOptions` escape hatch was removed from every SDK: + +| SDK | Removed option | +| ---------------------- | ----------------------------------------------------------------------------------- | +| `@sentry/astro` | `unstable_sentryVitePluginOptions` (top-level and inside `sourceMapsUploadOptions`) | +| `@sentry/nextjs` | `unstable_sentryWebpackPluginOptions` (top-level and inside `webpack`) | +| `@sentry/nuxt` | `unstable_sentryBundlerPluginOptions` | +| `@sentry/react-router` | `unstable_sentryVitePluginOptions` | +| `@sentry/solidstart` | `unstable_sentryVitePluginOptions` | +| `@sentry/sveltekit` | `unstable_sentryVitePluginOptions` | + +Set the option directly on the Sentry build options: + +```js +// Before +unstable_sentryWebpackPluginOptions: { + applicationKey: 'my-app', +}, + +// After +applicationKey: 'my-app', +``` + +`moduleMetadata` and `sourcemaps.resolveSourceMap` are first-class build options now. `release.uploadLegacySourcemaps`, `_experiments`, and the whole-plugin `disable` flag have no equivalent; use `sourcemaps.disable` instead. Passing a removed option logs a build-time warning. diff --git a/includes/migration/javascript-v11/vercel-ai-v3-dropped.mdx b/includes/migration/javascript-v11/vercel-ai-v3-dropped.mdx new file mode 100644 index 0000000000000..d84d2dd3f6fa5 --- /dev/null +++ b/includes/migration/javascript-v11/vercel-ai-v3-dropped.mdx @@ -0,0 +1,15 @@ +--- +id: vercel-ai-v3-dropped +title: "Vercel AI SDK 3 support was dropped" +phase: prerequisites +category: version-support +severity: action-required +frameworks: all +features: [ai-agents] +platformCategory: server +order: 130 +--- + +The Vercel AI integration covers `ai` 4, 5, and 6 through the orchestrion transform, and `ai` 7 through its native `ai:telemetry` channel. Version 3 is no longer instrumented. + +Upgrade `ai` to 4 or higher, or expect no AI spans. diff --git a/includes/migration/javascript-v11/with-streamed-span-noop.mdx b/includes/migration/javascript-v11/with-streamed-span-noop.mdx new file mode 100644 index 0000000000000..c90c336a50ed1 --- /dev/null +++ b/includes/migration/javascript-v11/with-streamed-span-noop.mdx @@ -0,0 +1,23 @@ +--- +id: with-streamed-span-noop +title: "`withStreamedSpan()` is now a no-op" +phase: cleanup +category: removed-api +severity: informational +frameworks: all +features: [tracing] +platformCategory: all +order: 30 +--- + +`Sentry.withStreamedSpan()` is a no-op and deprecated. It will be removed in v12. + +Remove the wrapper: + +```js +// Before +beforeSendSpan: Sentry.withStreamedSpan(span => span); + +// After +beforeSendSpan: span => span; +``` diff --git a/package.json b/package.json index 67cf61010e40c..40e4ac98fbc09 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "lint:fix": "pnpm run lint:prettier:fix && pnpm run lint:eslint:fix", "test": "vitest", "test:ci": "vitest run", + "migration-items": "node scripts/validate-migration-items.mjs", "enforce-redirects": "node ./scripts/no-vercel-json-redirects.mjs" }, "dependencies": { diff --git a/scripts/generate-md-exports.mjs b/scripts/generate-md-exports.mjs index e403ef90c082e..e544be35ea2ed 100644 --- a/scripts/generate-md-exports.mjs +++ b/scripts/generate-md-exports.mjs @@ -1,18 +1,18 @@ #!/usr/bin/env node /* eslint-disable no-console */ -import {ListObjectsV2Command, PutObjectCommand, S3Client} from '@aws-sdk/client-s3'; +import { ListObjectsV2Command, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; import imgLinks from '@pondorasti/remark-img-links'; -import {selectAll} from 'hast-util-select'; -import {createHash} from 'node:crypto'; -import {createReadStream, createWriteStream, existsSync} from 'node:fs'; -import {mkdir, opendir, readdir, readFile, rm, writeFile} from 'node:fs/promises'; -import {cpus} from 'node:os'; +import { selectAll } from 'hast-util-select'; +import { createHash } from 'node:crypto'; +import { createReadStream, createWriteStream, existsSync } from 'node:fs'; +import { mkdir, opendir, readdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { cpus } from 'node:os'; import * as path from 'node:path'; -import {compose, Readable} from 'node:stream'; -import {text} from 'node:stream/consumers'; -import {pipeline} from 'node:stream/promises'; -import {fileURLToPath} from 'node:url'; -import {isMainThread, parentPort, Worker, workerData} from 'node:worker_threads'; +import { compose, Readable } from 'node:stream'; +import { text } from 'node:stream/consumers'; +import { pipeline } from 'node:stream/promises'; +import { fileURLToPath } from 'node:url'; +import { isMainThread, parentPort, Worker, workerData } from 'node:worker_threads'; import { constants as zlibConstants, createBrotliCompress, @@ -24,11 +24,11 @@ import rehypeRemark from 'rehype-remark'; import remarkGfm from 'remark-gfm'; import RemarkLinkRewrite from 'remark-link-rewrite'; import remarkStringify from 'remark-stringify'; -import {unified} from 'unified'; -import {remove} from 'unist-util-remove'; +import { unified } from 'unified'; +import { remove } from 'unist-util-remove'; -import {rehypeExpandCodeTabs} from './rehype-expand-code-tabs.mjs'; -import {replaceCurrentUrlTokens} from './markdown-keywords.mjs'; +import { rehypeExpandCodeTabs } from './rehype-expand-code-tabs.mjs'; +import { replaceCurrentUrlTokens } from './markdown-keywords.mjs'; // Default values for code keyword placeholders (e.g. ___PUBLIC_DSN___) that are // normally replaced client-side by codeKeywords.tsx. These must stay in sync with @@ -279,8 +279,8 @@ const pageOverrides = [ // Platform index pages (e.g., platforms/javascript.md) match: ctx => ctx.pathParts[0] === 'platforms' && ctx.pathParts.length === 2, sections: [ - {heading: 'Frameworks', items: ctx => nodeLinks(ctx.node, 'guides')}, - {heading: 'Topics', items: ctx => childLinks(ctx, p => !p.includes('/guides/'))}, + { heading: 'Frameworks', items: ctx => nodeLinks(ctx.node, 'guides') }, + { heading: 'Topics', items: ctx => childLinks(ctx, p => !p.includes('/guides/')) }, ], }, { @@ -294,7 +294,7 @@ const pageOverrides = [ heading: ctx => `Other ${platformTitle(ctx)} Frameworks`, items: ctx => siblingGuideLinks(ctx), }, - {heading: 'Topics', items: ctx => childLinks(ctx)}, + { heading: 'Topics', items: ctx => childLinks(ctx) }, ], }, ]; @@ -319,7 +319,7 @@ function buildChildSection(ctx) { // Default: list children sorted by sidebar_order return renderSections(ctx, [ - {heading: 'Pages in this section', items: () => childLinks(ctx)}, + { heading: 'Pages in this section', items: () => childLinks(ctx) }, ]); } @@ -397,7 +397,7 @@ function buildFrontmatterMap(docTree) { * Formats a YAML frontmatter block for a markdown file. * Only includes fields that have non-empty values. */ -function formatYamlFrontmatter({title, description, url}) { +function formatYamlFrontmatter({ title, description, url }) { let yaml = '---\n'; if (title) { yaml += `title: ${JSON.stringify(title.replace(/\r?\n/g, ' '))}\n`; @@ -430,8 +430,8 @@ function buildMdxComponents(docTree, createElement) { platforms.map(p => createElement( 'li', - {key: p.slug}, - createElement('a', {href: `/platforms/${p.slug}`}, getTitle(p)) + { key: p.slug }, + createElement('a', { href: `/platforms/${p.slug}` }, getTitle(p)) ) ) ); @@ -457,17 +457,17 @@ function buildMdxComponents(docTree, createElement) { continue; } groups.push( - createElement('h3', {key: `h-${platform.slug}`}, getTitle(platform)), + createElement('h3', { key: `h-${platform.slug}` }, getTitle(platform)), createElement( 'ul', - {key: `ul-${platform.slug}`}, + { key: `ul-${platform.slug}` }, guides.map(g => createElement( 'li', - {key: g.slug}, + { key: g.slug }, createElement( 'a', - {href: `/platforms/${platform.slug}/guides/${g.slug}`}, + { href: `/platforms/${platform.slug}/guides/${g.slug}` }, getTitle(g) ) ) @@ -478,7 +478,7 @@ function buildMdxComponents(docTree, createElement) { return createElement('div', null, ...groups); } - function DocSectionList({exclude = []}) { + function DocSectionList({ exclude = [] }) { if (!docTree) { return null; } @@ -491,8 +491,8 @@ function buildMdxComponents(docTree, createElement) { sections.map(child => createElement( 'li', - {key: child.slug}, - createElement('a', {href: `/${child.slug}`}, getTitle(child)) + { key: child.slug }, + createElement('a', { href: `/${child.slug}` }, getTitle(child)) ) ) ); @@ -500,7 +500,7 @@ function buildMdxComponents(docTree, createElement) { // Renders every top-level section with its visible children as a nested list. // Used for the root index.md sitemap. - function SectionTree({exclude = []}) { + function SectionTree({ exclude = [] }) { if (!docTree) { return null; } @@ -509,18 +509,18 @@ function buildMdxComponents(docTree, createElement) { ); const elements = []; for (const section of sections) { - elements.push(createElement('h2', {key: `h-${section.slug}`}, getTitle(section))); + elements.push(createElement('h2', { key: `h-${section.slug}` }, getTitle(section))); const children = getVisibleChildren(section); if (children.length > 0) { elements.push( createElement( 'ul', - {key: `ul-${section.slug}`}, + { key: `ul-${section.slug}` }, children.map(child => createElement( 'li', - {key: child.slug}, - createElement('a', {href: `/${child.path}`}, getTitle(child)) + { key: child.slug }, + createElement('a', { href: `/${child.path}` }, getTitle(child)) ) ) ) @@ -530,7 +530,7 @@ function buildMdxComponents(docTree, createElement) { return createElement('div', null, ...elements); } - return {PlatformList, FrameworkGroups, DocSectionList, SectionTree}; + return { PlatformList, FrameworkGroups, DocSectionList, SectionTree }; } async function renderMdxOverrides(root, docTree) { @@ -545,13 +545,13 @@ async function renderMdxOverrides(root, docTree) { } const tempDir = path.join(root, '.next', 'cache', 'md-override-html'); - await rm(tempDir, {recursive: true, force: true}); - await mkdir(tempDir, {recursive: true}); + await rm(tempDir, { recursive: true, force: true }); + await mkdir(tempDir, { recursive: true }); - const {evaluate} = await import('@mdx-js/mdx'); + const { evaluate } = await import('@mdx-js/mdx'); const jsxRuntime = await import('react/jsx-runtime'); const React = await import('react'); - const {renderToStaticMarkup} = await import('react-dom/server'); + const { renderToStaticMarkup } = await import('react-dom/server'); const grayMatter = (await import('gray-matter')).default; const components = buildMdxComponents(docTree, React.createElement); @@ -564,16 +564,16 @@ async function renderMdxOverrides(root, docTree) { continue; } - const mdxSource = await readFile(path.join(overrideDir, file), {encoding: 'utf8'}); - const {data: frontmatter, content} = grayMatter(mdxSource); + const mdxSource = await readFile(path.join(overrideDir, file), { encoding: 'utf8' }); + const { data: frontmatter, content } = grayMatter(mdxSource); - const {default: MDXContent} = await evaluate(content, { + const { default: MDXContent } = await evaluate(content, { jsx: jsxRuntime.jsx, jsxs: jsxRuntime.jsxs, Fragment: jsxRuntime.Fragment, }); - const bodyHtml = renderToStaticMarkup(React.createElement(MDXContent, {components})); + const bodyHtml = renderToStaticMarkup(React.createElement(MDXContent, { components })); const relativePath = file.replace(/\.mdx$/, '.md'); const urlPath = file.replace(/\.mdx$/, '').replace(/^index$/, ''); @@ -589,10 +589,10 @@ async function renderMdxOverrides(root, docTree) { ].join('\n'); const htmlPath = path.join(tempDir, file.replace(/\.mdx$/, '.html')); - await mkdir(path.dirname(htmlPath), {recursive: true}); - await writeFile(htmlPath, html, {encoding: 'utf8'}); + await mkdir(path.dirname(htmlPath), { recursive: true }); + await writeFile(htmlPath, html, { encoding: 'utf8' }); - overrides.set(relativePath, {htmlPath, frontmatter}); + overrides.set(relativePath, { htmlPath, frontmatter }); console.log(`📝 Rendered MDX override: ${file} → ${relativePath}`); } @@ -602,7 +602,7 @@ async function renderMdxOverrides(root, docTree) { // Global set to track which cache files are used across all workers let globalUsedCacheFiles = null; -function taskFinishHandler({id, success, failedTasks, usedCacheFiles}) { +function taskFinishHandler({ id, success, failedTasks, usedCacheFiles }) { // Collect cache files used by this worker into the global set if (usedCacheFiles && globalUsedCacheFiles) { console.log(`🔍 Worker[${id}]: returned ${usedCacheFiles.size} cache files.`); @@ -644,7 +644,7 @@ async function createWork() { const doctreePath = path.join(root, 'public', doctreeFilename); let docTree = null; try { - docTree = JSON.parse(await readFile(doctreePath, {encoding: 'utf8'})); + docTree = JSON.parse(await readFile(doctreePath, { encoding: 'utf8' })); console.log(`🌳 Loaded doc tree from ${doctreePath}`); } catch (err) { console.warn(`⚠️ Could not load doctree (${doctreePath}): ${err.message}`); @@ -658,8 +658,8 @@ async function createWork() { const mdxOverrides = await renderMdxOverrides(root, docTree); // Clear output directory - await rm(OUTPUT_DIR, {recursive: true, force: true}); - await mkdir(OUTPUT_DIR, {recursive: true}); + await rm(OUTPUT_DIR, { recursive: true, force: true }); + await mkdir(OUTPUT_DIR, { recursive: true }); const CACHE_DIR = path.join(root, '.next', 'cache', 'md-exports'); console.log(`💰 Cache directory: ${CACHE_DIR}`); @@ -667,7 +667,7 @@ async function createWork() { let initialCacheFiles = []; if (noCache) { console.log(`ℹ️ No cache directory found, this will take a while...`); - await mkdir(CACHE_DIR, {recursive: true}); + await mkdir(CACHE_DIR, { recursive: true }); } else { initialCacheFiles = await readdir(CACHE_DIR); console.log( @@ -696,7 +696,7 @@ async function createWork() { }) ); continuationToken = response.NextContinuationToken; - for (const {Key, ETag} of response.Contents || []) { + for (const { Key, ETag } of response.Contents || []) { existingFilesOnR2.set(Key, ETag.slice(1, -1)); // Remove quotes from ETag } } while (continuationToken); @@ -709,7 +709,7 @@ async function createWork() { let workerIdx = 0; // Need a high buffer size here otherwise Node skips some subdirectories! // See https://github.com/nodejs/node/issues/48820 - const dir = await opendir(INPUT_DIR, {recursive: true, bufferSize: 1024}); + const dir = await opendir(INPUT_DIR, { recursive: true, bufferSize: 1024 }); for await (const dirent of dir) { if (dirent.name.endsWith('.html') && dirent.isFile()) { @@ -718,7 +718,7 @@ async function createWork() { OUTPUT_DIR, path.relative(INPUT_DIR, dirent.parentPath || dirent.path) ); - await mkdir(targetDir, {recursive: true}); + await mkdir(targetDir, { recursive: true }); const targetPath = path.join(targetDir, dirent.name.slice(0, -5) + '.md'); const relativePath = normalizeRelativePath(path.relative(OUTPUT_DIR, targetPath)); // Use MDX override HTML if available, otherwise use Next.js build HTML @@ -860,7 +860,7 @@ async function createWork() { const parentFile = path.join(OUTPUT_DIR, parentPath); let existingContent; try { - existingContent = await readFile(parentFile, {encoding: 'utf8'}); + existingContent = await readFile(parentFile, { encoding: 'utf8' }); } catch (err) { if (err.code === 'ENOENT') { continue; // Parent file doesn't exist, skip @@ -884,7 +884,7 @@ async function createWork() { if (childSection) { const updatedContent = existingContent + childSection; - await writeFile(parentFile, updatedContent, {encoding: 'utf8'}); + await writeFile(parentFile, updatedContent, { encoding: 'utf8' }); updatedCount++; if (hasR2) { r2Uploads.set(parentPath, updatedContent); @@ -930,7 +930,7 @@ async function createWork() { const limit = pLimit(50); await Promise.all( filesToDelete.map(file => - limit(() => rm(path.join(CACHE_DIR, file), {force: true})) + limit(() => rm(path.join(CACHE_DIR, file), { force: true })) ) ); console.log(`🧹 Cleaned up ${filesToDelete.length} unused cache files`); @@ -1017,8 +1017,8 @@ function extractContentForCacheKey(html) { return title + '\0' + canonical + '\0' + normalizedMain; } -async function genMDFromHTML(source, {cacheDir, noCache, usedCacheFiles}) { - const rawHTML = await readFile(source, {encoding: 'utf8'}); +async function genMDFromHTML(source, { cacheDir, noCache, usedCacheFiles }) { + const rawHTML = await readFile(source, { encoding: 'utf8' }); // Strip build-specific HTML elements for faster parsing. // See stripUnstableElements() for details on what's removed and why. const strippedHTML = stripUnstableElements(rawHTML); @@ -1038,7 +1038,7 @@ async function genMDFromHTML(source, {cacheDir, noCache, usedCacheFiles}) { usedCacheFiles.add(cacheKey); } - return {cacheHit: true, data}; + return { cacheHit: true, data }; } catch (err) { if (err.code !== 'ENOENT') { console.warn(`Error using cache file ${cacheFile}:`, err); @@ -1071,7 +1071,7 @@ async function genMDFromHTML(source, {cacheDir, noCache, usedCacheFiles}) { // markdown on component-rendered pages like the API docs. Comments dispatch by node // type, so this must live in nodeHandlers rather than handlers. nodeHandlers: { - comment() {}, + comment() { }, }, handlers: { // HACK: Extract the canonical URL during parsing @@ -1081,7 +1081,7 @@ async function genMDFromHTML(source, {cacheDir, noCache, usedCacheFiles}) { } }, // Remove buttons as they usually get confusing in markdown, especially since we use them as tab headers - button() {}, + button() { }, // Convert the title to the top level heading // This is needed because the HTML title tag is not part of the main content // and we want to have a top level heading in the markdown @@ -1112,9 +1112,9 @@ async function genMDFromHTML(source, {cacheDir, noCache, usedCacheFiles}) { return mdUrl; }, }) - .use(imgLinks, {absolutePath: DOCS_ORIGIN}) + .use(imgLinks, { absolutePath: DOCS_ORIGIN }) // We end up with empty inline code blocks, probably from some tab logic in the HTML, remove them - .use(() => tree => remove(tree, {type: 'inlineCode', value: ''})) + .use(() => tree => remove(tree, { type: 'inlineCode', value: '' })) .use(remarkGfm) .use(remarkStringify) .process(strippedHTML) @@ -1137,10 +1137,10 @@ async function genMDFromHTML(source, {cacheDir, noCache, usedCacheFiles}) { usedCacheFiles.add(cacheKey); } - return {cacheHit: false, data}; + return { cacheHit: false, data }; } -async function processTaskList({id, tasks, cacheDir, noCache, usedCacheFiles}) { +async function processTaskList({ id, tasks, cacheDir, noCache, usedCacheFiles }) { // Workers don't receive usedCacheFiles in workerData, so create a new Set if (!usedCacheFiles) { usedCacheFiles = new Set(); @@ -1152,9 +1152,9 @@ async function processTaskList({id, tasks, cacheDir, noCache, usedCacheFiles}) { let cacheMisses = []; let r2CacheMisses = []; console.log(`🤖 Worker[${id}]: Starting to process ${tasks.length} files...`); - for (const {sourcePath, targetPath, relativePath, r2Hash, frontmatter} of tasks) { + for (const { sourcePath, targetPath, relativePath, r2Hash, frontmatter } of tasks) { try { - const {data, cacheHit} = await genMDFromHTML(sourcePath, { + const { data, cacheHit } = await genMDFromHTML(sourcePath, { cacheDir, noCache, usedCacheFiles, @@ -1174,7 +1174,7 @@ async function processTaskList({id, tasks, cacheDir, noCache, usedCacheFiles}) { const output = frontmatter ? formatYamlFrontmatter(frontmatter) + resolved : resolved; - await writeFile(targetPath, output, {encoding: 'utf8'}); + await writeFile(targetPath, output, { encoding: 'utf8' }); if (r2Hash !== null && s3Client) { const fileHash = md5(output); @@ -1185,7 +1185,7 @@ async function processTaskList({id, tasks, cacheDir, noCache, usedCacheFiles}) { } } } catch (error) { - failedTasks.push({sourcePath, targetPath, error}); + failedTasks.push({ sourcePath, targetPath, error }); } } const success = tasks.length - failedTasks.length; diff --git a/scripts/validate-migration-items.mjs b/scripts/validate-migration-items.mjs new file mode 100644 index 0000000000000..c7e6b7104630f --- /dev/null +++ b/scripts/validate-migration-items.mjs @@ -0,0 +1,189 @@ +/** + * Validates the v11 migration item collection in `includes/migration/javascript-v11/`. + * + * Each item is a standalone MDX file with tagged frontmatter, rendered by the + * interactive migration guide at + * `docs/platforms/javascript/common/migration/v10-to-v11.mdx`. Because the page + * filters, counts and serializes items based on that frontmatter, a mistyped + * facet silently drops an item from a user's guide rather than failing loudly. + * This script is the guard against that. + * + * `loadItems` is called from `migrationGuide.spec.ts`, so `pnpm test` fails on + * invalid frontmatter. Run `pnpm migration-items` for the breakdown by phase, + * severity and category as well. + */ +import {readdirSync, readFileSync} from 'fs'; +import path from 'path'; + +import matter from 'gray-matter'; + +const ITEMS_DIR = 'includes/migration/javascript-v11'; +const GUIDES_DIR = 'docs/platforms/javascript/guides'; + +/** The change itself, stated once. Anything longer is an explanation. */ +const MAX_FIRST_PARAGRAPH = 400; + +export const PHASES = ['prerequisites', 'packages', 'code-changes', 'verify', 'cleanup']; +export const CATEGORIES = [ + 'version-support', + 'behavior', + 'removed-api', + 'package', + 'rename', + 'type', +]; +export const SEVERITIES = ['action-required', 'behavior-change', 'informational']; +export const FEATURES = [ + 'tracing', + 'profiling', + 'logs', + 'metrics', + 'ai-agents', + 'custom-otel', +]; +export const PLATFORM_CATEGORIES = ['browser', 'server', 'serverless', 'all']; + +/** Reads and validates every item. Returns `{items, errors}`. */ +export function loadItems(root = process.cwd()) { + const dir = path.join(root, ITEMS_DIR); + const guides = new Set(readdirSync(path.join(root, GUIDES_DIR))); + const files = readdirSync(dir).filter(f => f.endsWith('.mdx')); + + const errors = []; + const items = []; + const ids = new Set(); + const orders = new Map(); + + for (const file of files) { + const {data, content} = matter(readFileSync(path.join(dir, file), 'utf8')); + const fail = message => errors.push(`${file}: ${message}`); + + const expectedId = file.replace(/\.mdx$/, ''); + if (data.id !== expectedId) { + fail(`id "${data.id}" does not match filename`); + } + if (ids.has(data.id)) { + fail(`duplicate id "${data.id}"`); + } + ids.add(data.id); + + if (!data.title) { + fail('missing title'); + } + if (!PHASES.includes(data.phase)) { + fail(`unknown phase "${data.phase}"`); + } + if (!CATEGORIES.includes(data.category)) { + fail(`unknown category "${data.category}"`); + } + if (!SEVERITIES.includes(data.severity)) { + fail(`unknown severity "${data.severity}"`); + } + if (!PLATFORM_CATEGORIES.includes(data.platformCategory)) { + fail(`unknown platformCategory "${data.platformCategory}"`); + } + if (typeof data.order !== 'number') { + fail('missing or non-numeric order'); + } + + if (data.frameworks !== 'all') { + if (!Array.isArray(data.frameworks)) { + fail('frameworks must be "all" or an array of guide slugs'); + } else { + data.frameworks + .filter(f => !guides.has(f)) + .forEach(f => fail(`unknown framework "${f}"`)); + } + } + (data.features ?? []) + .filter(f => !FEATURES.includes(f)) + .forEach(f => fail(`unknown feature "${f}"`)); + + // Items are meant to be scannable: what changed, then what to do about it. + // These checks keep the collection from drifting back into prose. + const body = content.trim(); + if (!body) { + fail('empty body'); + } + for (const heading of ['**What changed**', '**What you need to do**', '**Why**']) { + if (body.includes(heading)) { + fail( + `remove the "${heading.replaceAll('*', '')}" heading, lead with the change instead` + ); + } + } + if (/[\u2014\u2013]/.test(body)) { + fail('uses an em or en dash, rewrite the sentence'); + } + // Items are re-ordered by severity and hidden by the reader's filters, so + // "the next three items" can point at nothing. Link the item by anchor. + for (const positional of [ + /\b(next|previous|following|preceding)\s+(\w+\s+)?items?\b/i, + /\bitems?\s+(that\s+)?(follows?|precedes?)\b/i, + /\bitems?\s+(above|below)\b/i, + ]) { + if (positional.test(body)) { + fail('refers to an item by position, link to its anchor instead'); + } + } + const firstParagraph = body.split('\n\n')[0]; + if (firstParagraph.length > MAX_FIRST_PARAGRAPH) { + fail( + `opening paragraph is ${firstParagraph.length} characters, keep it under ${MAX_FIRST_PARAGRAPH}` + ); + } + + const orderKey = `${data.phase}/${data.order}`; + if (orders.has(orderKey)) { + fail(`duplicate order ${orderKey}, also used by ${orders.get(orderKey)}`); + } + orders.set(orderKey, file); + + items.push({file, ...data}); + } + + return {items, errors}; +} + +function main() { + const {items, errors} = loadItems(); + const count = (list, predicate) => list.filter(predicate).length; + const pad = (value, width) => String(value).padEnd(width); + + console.log(`${items.length} migration items\n`); + + console.log('BY PHASE'); + PHASES.forEach(p => console.log(` ${pad(p, 16)}${count(items, i => i.phase === p)}`)); + + console.log('\nBY SEVERITY'); + SEVERITIES.forEach(s => + console.log(` ${pad(s, 16)}${count(items, i => i.severity === s)}`) + ); + + console.log('\nBY CATEGORY'); + CATEGORIES.forEach(c => + console.log(` ${pad(c, 16)}${count(items, i => i.category === c)}`) + ); + + const unusedFeatures = FEATURES.filter( + f => !count(items, i => (i.features ?? []).includes(f)) + ); + if (unusedFeatures.length) { + // A facet no item carries renders a checkbox that filters nothing, which + // reads as broken to anyone who ticks it. + console.log('\nWARNING: facets with no items (remove them from the filter panel):'); + unusedFeatures.forEach(f => console.log(` ${f}`)); + } + + if (errors.length) { + console.log(`\n${errors.length} error(s):`); + errors.forEach(e => console.log(` ${e}`)); + process.exit(1); + } + + console.log('\nAll items valid.'); +} + +if (import.meta.url === `file://${process.argv[1]}`) { + main(); +} diff --git a/src/components/migrationGuide/client.tsx b/src/components/migrationGuide/client.tsx new file mode 100644 index 0000000000000..667df590b75f7 --- /dev/null +++ b/src/components/migrationGuide/client.tsx @@ -0,0 +1,748 @@ +'use client'; + +import {QuestionMarkCircledIcon} from '@radix-ui/react-icons'; +import * as Tooltip from '@radix-ui/react-tooltip'; +import {Button, Checkbox, Theme} from '@radix-ui/themes'; +import * as Sentry from '@sentry/nextjs'; +import classNames from 'classnames'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import {usePlausibleEvent} from 'sentry-docs/hooks/usePlausibleEvent'; +import {DocMetrics} from 'sentry-docs/metrics'; + +import { + isItemVisible, + migrationGuideHref, + migrationGuideLabel, + MigrationItem, + PHASES, + SEVERITIES, +} from './constants'; +import {detect, Detection} from './detect'; +import styles from './styles.module.scss'; + +const FILTER_PARAM = 'using'; + +/** The facets offered on this page, which vary by guide. */ +export type Facet = {description: string; id: string; label: string}; + +/** + * Error monitoring has no checkbox to tick: every setup has it, and the changes + * that come with it apply to everyone. It is rendered as a locked option rather + * than left out, so the panel reads as the full set of what the SDK does, the + * same way the onboarding options do. + */ +const ERRORS_OPTION = { + label: 'Errors', + description: 'Error monitoring is part of every setup, so those changes always apply.', +}; + +/** An item body, rendered on the server and matched to its item by id. */ +export type ItemBody = {body: React.ReactNode; id: string}; + +type Props = { + /** + * Rendered item bodies, keyed by item id rather than by position: matching + * them to `items` by array index would misplace every body after the first + * divergence instead of failing. + */ + bodies: ItemBody[]; + /** + * Facets carried by at least one item on this page. A browser-only guide has + * no OpenTelemetry or AI items, so those checkboxes are simply absent there. + */ + facets: readonly Facet[]; + /** Guide slug, used to scope checklist storage and label the agent prompt. */ + framework: string; + /** Human-readable platform or guide name, for the headline. */ + frameworkLabel: string; + items: MigrationItem[]; + /** Item count before framework filtering, used for the headline. */ + totalItems: number; +}; + +export function MigrationGuideClient({ + items, + bodies: renderedBodies, + facets, + framework, + frameworkLabel, + totalItems, +}: Props) { + const allFacets = useMemo(() => facets.map(f => f.id), [facets]); + const bodies = useMemo( + () => new Map(renderedBodies.map(({id, body}) => [id, body])), + [renderedBodies] + ); + + // Everything is selected until the reader narrows it, so the page reads as a + // complete guide before anyone touches a checkbox — and still does with + // JavaScript disabled, since the server renders every item. + const [selected, setSelected] = useState>(() => new Set(allFacets)); + const [urlRead, setUrlRead] = useState(false); + + // Filter state is mirrored into the URL so a narrowed guide can be shared. + // + // It is read and written directly rather than through `useSearchParams`, + // because that hook client-renders the tree up to the nearest Suspense + // boundary, and this docs route is `force-static`. The guide is the page + // body, so it would drop out of the prerendered HTML: `.md` exports are + // generated from that HTML, so every item would disappear from `.md`, + // along with what crawlers and readers without JavaScript get. Note this + // only shows up in a production build; in development the hook does not + // suspend and everything looks fine. + // + // `history.replaceState` is what Next.js documents for updating the query + // string without a navigation. + useEffect(() => { + const raw = new URLSearchParams(window.location.search).get(FILTER_PARAM); + if (raw !== null) { + // Ignore facets from a shared URL that this guide does not offer. + setSelected(new Set(raw.split(',').filter(f => allFacets.includes(f)))); + } + setUrlRead(true); + }, [allFacets]); + + useEffect(() => { + if (!urlRead) { + return; + } + const params = new URLSearchParams(window.location.search); + if (selected.size === allFacets.length) { + params.delete(FILTER_PARAM); + } else { + params.set(FILTER_PARAM, [...selected].join(',')); + } + const query = params.toString(); + // The hash is carried over: items render as `#` anchors, and + // rewriting the URL without it would break a shared deep link on the first + // filter change — or on hydration, before the reader touches anything. + window.history.replaceState( + null, + '', + `${window.location.pathname}${query ? `?${query}` : ''}${window.location.hash}` + ); + }, [selected, urlRead, allFacets]); + + const toggleFacet = useCallback((id: string) => { + // Functional update, so two toggles in the same tick cannot clobber each + // other. + setSelected(prev => toggled(prev, id)); + }, []); + + // Checklist progress is scoped per framework: someone migrating a monorepo + // has separate checklists for their Next.js app and their Node service. + const storageKey = `sentry-v11-migration:${framework}`; + const [checked, setChecked] = useState>(new Set()); + const [hydrated, setHydrated] = useState(false); + + useEffect(() => { + try { + const stored = window.localStorage.getItem(storageKey); + setChecked(new Set(stored ? (JSON.parse(stored) as string[]) : [])); + } catch { + // Private mode, disabled storage — the guide still works, it just forgets. + } + setHydrated(true); + }, [storageKey]); + + // Persisted from an effect rather than from inside the state updater, which + // has to stay pure: React invokes updaters twice in development, and an + // updater that writes to storage would write twice per click. + useEffect(() => { + if (!hydrated) { + // Never write before the stored value has been read, or the first render + // would overwrite the reader's saved progress with an empty set. + return; + } + try { + window.localStorage.setItem(storageKey, JSON.stringify([...checked])); + } catch { + // See above. + } + }, [checked, hydrated, storageKey]); + + const toggleChecked = useCallback((id: string) => { + setChecked(prev => toggled(prev, id)); + }, []); + + const [showHidden, setShowHidden] = useState(false); + + const visible = useMemo( + () => items.filter(item => isItemVisible(item, selected)), + [items, selected] + ); + const visibleIds = useMemo(() => new Set(visible.map(i => i.id)), [visible]); + const hiddenCount = items.length - visible.length; + + const actionRequired = visible.filter(i => i.severity === 'action-required'); + // Every item is checkable: the action-required ones as work, the rest as an + // acknowledgement that you read them and decided they need nothing. So + // progress counts all of them, not just the work. + const doneCount = visible.filter(i => checked.has(i.id)).length; + const progress = visible.length ? Math.round((doneCount / visible.length) * 100) : 100; + + const shown = showHidden ? items : visible; + + return ( +
+ + + + + + + {hiddenCount > 0 && ( + + )} + + {PHASES.map(phase => { + const phaseItems = shown.filter(item => item.phase === phase.id); + if (phaseItems.length === 0) { + return null; + } + const phaseLeft = phaseItems.filter( + i => visibleIds.has(i.id) && !checked.has(i.id) + ).length; + + return ( +
+
+

{phase.title}

+ + {phaseLeft > 0 ? `${phaseLeft} left` : 'all done'} + +
+

{phase.description}

+ + {phaseItems.map(item => ( + toggleChecked(item.id)} + dimmed={!visibleIds.has(item.id)} + /> + ))} +
+ ); + })} +
+ ); +} + +/** Adds or removes `id`, without mutating `set`. */ +function toggled(set: Set, id: string): Set { + const next = new Set(set); + if (!next.delete(id)) { + next.add(id); + } + return next; +} + +function FilterPanel({ + facets, + selected, + onToggle, + onSet, + framework, + frameworkLabel, +}: { + facets: readonly Facet[]; + framework: string; + frameworkLabel: string; + onSet: (next: Set) => void; + onToggle: (id: string) => void; + selected: Set; +}) { + return ( +
+
+

What does your setup use?

+
+ + +
+
+ +
+ + {facets.map(option => ( + onToggle(option.id)} + /> + ))} +
+ + +
+ ); +} + +/** + * One filter checkbox, built from the same Radix pair as the onboarding option + * buttons so the two pickers read as one control. Without `onToggle` the option + * is locked on, for things every setup has. + */ +function FacetOption({ + label, + description, + checked, + onToggle, +}: { + description: string; + label: string; + checked?: boolean; + onToggle?: () => void; +}) { + const locked = !onToggle; + + return ( + + ); +} + +const EMPTY_DETECTION: Detection = { + facets: new Set(), + signals: [], + framework: undefined, + gaps: {missingInit: true, missingManifest: true}, +}; + +/** + * Reads the reader's setup off a pasted `package.json` or `Sentry.init()` block + * and sets the filters from it, because a hand-ticked selection is only as + * accurate as their memory of their own config. + * + * Everything below is optional: the checkboxes above work untouched. + */ +function PasteDetect({ + onDetect, + framework, + frameworkLabel, + facets, +}: { + facets: readonly Facet[]; + framework: string; + frameworkLabel: string; + onDetect: (next: Set) => void; +}) { + const [open, setOpen] = useState(false); + const [text, setText] = useState(''); + const [detection, setDetection] = useState(EMPTY_DETECTION); + // The last selection this component applied. A detected result narrows the + // filters, which means overwriting what the reader ticked — fair on a fresh + // paste, but not on every keystroke afterwards: re-applying an unchanged + // result would silently undo a correction they made by hand. + const lastApplied = useRef(undefined); + + const handleChange = useCallback( + (value: string) => { + setText(value); + + // Guard against someone pasting a lockfile: the rules are cheap, but + // there is no reason to scan megabytes of it. + const result = detect(value.slice(0, 200_000)); + + // A pasted config can mention things this guide has no items for — a + // React app's dependencies may include an AI SDK used only on its server. + // Keep the detection honest by intersecting it with what this page can + // actually show. + const applicable = new Set( + [...result.facets].filter(facet => facets.some(f => f.id === facet)) + ); + setDetection({ + ...result, + facets: applicable, + signals: result.signals.filter(s => applicable.has(s.facet)), + }); + + if (applicable.size === 0) { + // Nothing to apply, and an emptied box should not count as "already + // applied" — re-pasting the same config has to work. + lastApplied.current = undefined; + return; + } + const key = [...applicable].sort().join(','); + if (key !== lastApplied.current) { + lastApplied.current = key; + onDetect(new Set(applicable)); + } + }, + [facets, onDetect] + ); + + const {facets: detectedFacets, signals, gaps, framework: detectedFramework} = detection; + // Only a framework this reader is *not* reading about is worth mentioning. + // One package serves several guides — `@sentry/node` backs Express, Fastify, + // Koa and more — so being outside that set is the test, not an exact match. + const wrongGuide = + detectedFramework && !detectedFramework.guides.includes(framework) + ? detectedFramework + : undefined; + + return ( +
+ + + {open && ( +
+