From e3bb39674e62ff7f6f355e53988987b09b12a672 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 15 Oct 2026 14:56:16 +0200 Subject: [PATCH 001/118] test(browser): Use blocking scripts in loader integration tests (#25395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Loader tests were flaky (e.g. `sentryOnLoad callback is called before Sentry.onLoad()` timing out waiting for the error event). HtmlWebpackPlugin defaults to `defer` for the test's `init`/`subject` scripts. With a non-lazy loader, the CDN bundle can load and replace the `window.Sentry` stub before a deferred script runs, while the loader only calls `init()` in a later `setTimeout`. SDK calls landing in that gap hit the real SDK with no client and are dropped. For loader builds the scripts are now blocking, so they run during parsing right after `loader.js` and always go through the stub's queue โ€” which also mirrors how the loader snippet is used in practice. Non-loader builds keep `defer`. ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- dev-packages/browser-integration-tests/utils/generatePage.ts | 5 ++++- .../browser-integration-tests/utils/generatePlugin.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dev-packages/browser-integration-tests/utils/generatePage.ts b/dev-packages/browser-integration-tests/utils/generatePage.ts index a74cd7dfe7bb..463bd9e26716 100644 --- a/dev-packages/browser-integration-tests/utils/generatePage.ts +++ b/dev-packages/browser-integration-tests/utils/generatePage.ts @@ -2,7 +2,7 @@ import { mkdirSync } from 'fs'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import webpack from 'webpack'; import webpackConfig from '../webpack.config'; -import SentryScenarioGenerationPlugin from './generatePlugin'; +import SentryScenarioGenerationPlugin, { useLoader } from './generatePlugin'; export async function generatePage( initPath: string, @@ -30,6 +30,9 @@ export async function generatePage( filename: outPageName, template: templatePath, inject: 'body', + // Deferred scripts may run after the loader has swapped in the CDN bundle but before it calls + // `init()`, so their SDK calls would be dropped. Blocking scripts always hit the loader stub. + scriptLoading: useLoader ? 'blocking' : 'defer', }), ], }), diff --git a/dev-packages/browser-integration-tests/utils/generatePlugin.ts b/dev-packages/browser-integration-tests/utils/generatePlugin.ts index b7a8b803a243..3dfdbb25288c 100644 --- a/dev-packages/browser-integration-tests/utils/generatePlugin.ts +++ b/dev-packages/browser-integration-tests/utils/generatePlugin.ts @@ -19,7 +19,7 @@ const useCompiledModule = bundleKey === 'esm' || bundleKey === 'cjs'; // Bundles need to be injected into HTML before Sentry initialization. const useBundleOrLoader = bundleKey && !useCompiledModule; -const useLoader = bundleKey.startsWith('loader'); +export const useLoader = bundleKey.startsWith('loader'); // These are imports that, when using CDN bundles, are not included in the main CDN bundle. // In this case, if we encounter this import, we want to add this CDN bundle file instead From 26c5cf239652d5dae313003d0756584d9416e56c Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 15 Oct 2026 14:56:32 +0200 Subject: [PATCH 002/118] test(replay): Fix flaky bufferStalledRequests segmentId assertion (#25394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the reload, the resumed session (now in `session` mode) flushes segment 1 after `flushMinDelay` (200ms), which bumps `segmentId` to 2. Snapshotting replay state could land on either side of that flush, so `segmentId` was sometimes 2. Instead of snapshotting `segmentId`, the test now asserts that the first replay request sent after reload carries `segment_id: 1` for the same replay, which verifies the same resumption behavior deterministically. ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- .../suites/replay/bufferStalledRequests/test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/bufferStalledRequests/test.ts b/dev-packages/browser-integration-tests/suites/replay/bufferStalledRequests/test.ts index ea106dfcd7ff..c38d31da2e12 100644 --- a/dev-packages/browser-integration-tests/suites/replay/bufferStalledRequests/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/bufferStalledRequests/test.ts @@ -2,9 +2,11 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; import { envelopeRequestParser, waitForErrorRequest } from '../../../utils/helpers'; import { + getReplayEvent, getReplaySnapshot, isReplayEvent, shouldSkipReplayTest, + waitForReplayRequest, waitForReplayRunning, } from '../../../utils/replayHelpers'; @@ -155,16 +157,22 @@ sentryTest('buffer mode remains after interrupting replay flush', async ({ getLo expect(firstSession.session?.dirty).toBe(true); expect(firstSession.recordingMode).toBe('buffer'); // But still in buffer mode + // The resumed session flushes shortly after reload, which bumps `segmentId` again, so a snapshot + // can't reliably observe `segmentId: 1`. Assert on the first segment sent after reload instead. + const resumedReplayRequestPromise = waitForReplayRequest(page); await page.reload(); await waitForReplayRunning(page); const secondSession = await getReplaySnapshot(page); expect(secondSession.session?.sampled).toBe('buffer'); expect(secondSession.session?.dirty).toBe(true); expect(secondSession.session?.id).toBe(firstSessionId); - expect(secondSession.session?.segmentId).toBe(1); // Because a flush attempt was made and not allowed to complete, segmentId increased from 0, // so we resume in session mode expect(secondSession.recordingMode).toBe('session'); + + const resumedReplayEvent = getReplayEvent(await resumedReplayRequestPromise); + expect(resumedReplayEvent.replay_id).toBe(firstSessionId); + expect(resumedReplayEvent.segment_id).toBe(1); }); sentryTest( From d3b223105122907815c74d9adc18a07e3e4bf831 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 15 Oct 2026 16:17:26 +0200 Subject: [PATCH 003/118] test(e2e): Fix peer dependency issues in hydrogen-react-router-7 (#25400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes every peer dependency issue in `hydrogen-react-router-7`. The app now installs cleanly with `strict-peer-dependencies`. - **React Router versions:** `react-router` was on 7.18.2, while `@react-router/dev`, `@react-router/fs-routes` and `react-router-dom` were still on 7.13.0. The app therefore resolved `@react-router/node` 7.13.0, below the `>=7.15.0` that `@sentry/react-router` requires as a peer since #22481, so it was testing a version the SDK doesn't support. All React Router packages are now on 7.16.0. - **Hydrogen:** `@shopify/hydrogen@2025.5.0` pinned `react-router@7.6.0`, and `@shopify/remix-oxygen` pinned `~7.16.0`. Hydrogen 2026.4.7 (the latest 2026.4 release) requires `~7.16.0`, which is why all React Router packages are on 7.16.0. The newer 2026.10 line changes its peers completely, so I didn't move to it. `react` and `react-dom` move to `^18.3.1`, as Hydrogen requires. - **`@shopify/remix-oxygen`:** it's deprecated, so the app now imports `createRequestHandler` and `getStorefrontHeaders` from `@shopify/hydrogen/oxygen`, and the React Router types and session helpers from `react-router`. It was also the cause of a type error with the newer Hydrogen. - **Unused dependencies:** `@tailwindcss/vite` (an alpha requiring Vite 5) and the ESLint packages weren't used at all and had outdated peers, so they're removed along with the unused `lint` script. ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- .../app/entry.server.tsx | 3 +-- .../app/lib/session.ts | 2 +- .../hydrogen-react-router-7/app/root.tsx | 2 +- .../app/routes/loader-error.tsx | 2 +- .../app/routes/navigate.tsx | 2 +- .../hydrogen-react-router-7/env.d.ts | 1 - .../hydrogen-react-router-7/package.json | 20 +++++++------------ .../hydrogen-react-router-7/server.ts | 3 ++- 8 files changed, 14 insertions(+), 21 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/entry.server.tsx index acf5b4782b20..792a4164bbe5 100644 --- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/entry.server.tsx +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/entry.server.tsx @@ -1,7 +1,6 @@ import '../instrument.server'; -import { HandleErrorFunction, ServerRouter } from 'react-router'; +import { type EntryContext, HandleErrorFunction, ServerRouter } from 'react-router'; import { createContentSecurityPolicy } from '@shopify/hydrogen'; -import type { EntryContext } from '@shopify/remix-oxygen'; import { renderToReadableStream } from 'react-dom/server'; import * as Sentry from '@sentry/react-router/cloudflare'; diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/session.ts b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/session.ts index 80d6e7b86b52..941a6ff4c8b4 100644 --- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/session.ts +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/session.ts @@ -1,5 +1,5 @@ import type { HydrogenSession } from '@shopify/hydrogen'; -import { type Session, type SessionStorage, createCookieSessionStorage } from '@shopify/remix-oxygen'; +import { type Session, type SessionStorage, createCookieSessionStorage } from 'react-router'; /** * This is a custom session implementation for your Hydrogen shop. diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/root.tsx b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/root.tsx index 4e3a2f362bdf..8dcc1aee3a25 100644 --- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/root.tsx +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/root.tsx @@ -1,5 +1,5 @@ -import { type LoaderFunctionArgs } from '@shopify/remix-oxygen'; import { + type LoaderFunctionArgs, Outlet, isRouteErrorResponse, type ShouldRevalidateFunction, diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/loader-error.tsx b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/loader-error.tsx index 1548c38084ad..006094a17f74 100644 --- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/loader-error.tsx +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/loader-error.tsx @@ -1,5 +1,5 @@ import { useLoaderData } from 'react-router'; -import type { LoaderFunction } from '@shopify/remix-oxygen'; +import type { LoaderFunction } from 'react-router'; export default function LoaderError() { useLoaderData(); diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/navigate.tsx b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/navigate.tsx index 06ca3d7f2ae0..d5d652d27370 100644 --- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/navigate.tsx +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/navigate.tsx @@ -1,5 +1,5 @@ import { useLoaderData } from 'react-router'; -import type { LoaderFunction } from '@shopify/remix-oxygen'; +import type { LoaderFunction } from 'react-router'; export const loader: LoaderFunction = async ({ params: { id } }) => { if (id === '-1') { diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/env.d.ts b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/env.d.ts index ce37d9f3c464..3f7bdd4c9e84 100644 --- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/env.d.ts +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/env.d.ts @@ -1,5 +1,4 @@ /// -/// /// // Enhance TypeScript's built-in typings. diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json index 7687007647b3..16155eda734c 100644 --- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json @@ -6,7 +6,6 @@ "build": "pnpm typecheck && shopify hydrogen build --codegen", "dev": "shopify hydrogen dev --codegen", "preview": "shopify hydrogen preview", - "lint": "eslint --no-error-on-unmatched-pattern --ext .js,.ts,.jsx,.tsx .", "typecheck": "tsc", "codegen": "shopify hydrogen codegen", "clean": "npx rimraf node_modules dist pnpm-lock.yaml", @@ -17,35 +16,30 @@ "@sentry/cloudflare": "file:../../packed/sentry-cloudflare-packed.tgz", "@sentry/react-router": "file:../../packed/sentry-react-router-packed.tgz", "@sentry/vite-plugin": "^5.4.1", - "@shopify/hydrogen": "2025.5.0", - "@shopify/remix-oxygen": "^3.0.0", + "@shopify/hydrogen": "2026.4.7", "graphql": "^16.10.0", "graphql-tag": "^2.12.6", "isbot": "^5.1.22", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router": "7.18.2", - "react-router-dom": "7.13.0" + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router": "7.16.0", + "react-router-dom": "7.16.0" }, "devDependencies": { "@graphql-codegen/cli": "5.0.2", "@playwright/test": "~1.63.0", - "@react-router/dev": "7.13.0", - "@react-router/fs-routes": "7.13.0", + "@react-router/dev": "7.16.0", + "@react-router/fs-routes": "7.16.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@shopify/cli": "3.80.4", "@shopify/hydrogen-codegen": "^0.3.3", "@shopify/mini-oxygen": "3.2.1", "@shopify/oxygen-workers-types": "^4.1.6", - "@tailwindcss/vite": "4.0.0-alpha.17", "@total-typescript/ts-reset": "^0.4.2", - "@types/eslint": "^8.4.10", "@types/react": "^18.2.22", "@types/node": "^18.19.1", "@types/react-dom": "^18.2.7", "esbuild": "0.25.0", - "eslint": "^9.18.0", - "eslint-plugin-hydrogen": "0.12.2", "typescript": "^5.2.2", "vite": "^6.2.4", "vite-tsconfig-paths": "^4.3.1" diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/server.ts b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/server.ts index 74ef453d062b..8b937c4a58b5 100644 --- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/server.ts +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/server.ts @@ -6,7 +6,8 @@ import { createStorefrontClient, storefrontRedirect, } from '@shopify/hydrogen'; -import { type AppLoadContext, createRequestHandler, getStorefrontHeaders } from '@shopify/remix-oxygen'; +import { createRequestHandler, getStorefrontHeaders } from '@shopify/hydrogen/oxygen'; +import type { AppLoadContext } from 'react-router'; import { CART_QUERY_FRAGMENT } from '~/lib/fragments'; import { AppSession } from '~/lib/session'; import { httpServerIntegration, wrapRequestHandler } from '@sentry/cloudflare/request'; From a8b06c1b497ecfaa3f6dd4e5693ec759c21b0482 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 15 Oct 2026 16:17:43 +0200 Subject: [PATCH 004/118] test(e2e): Drop unused @sentry/bundler-plugins dependency from bundler apps (#25399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `node-esbuild`, `node-rolldown`, `node-rollup`, `node-vite` and `node-webpack` declared `@sentry/bundler-plugins` as a direct dependency, but none of them imports it. They use the plugins re-exported from `@sentry/node/`, and `@sentry/node` depends on `@sentry/bundler-plugins` itself. That's also how users install them. Dropping the direct dependency means the apps get `@sentry/bundler-plugins` the same way users do. All five pass locally with the change. ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- .../e2e-tests/test-applications/node-esbuild/package.json | 3 +-- .../e2e-tests/test-applications/node-rolldown/package.json | 3 +-- .../e2e-tests/test-applications/node-rollup/package.json | 3 +-- .../e2e-tests/test-applications/node-vite/package.json | 3 +-- .../e2e-tests/test-applications/node-webpack/package.json | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-esbuild/package.json b/dev-packages/e2e-tests/test-applications/node-esbuild/package.json index 9067ae46f6a6..f2c6a8b74e8f 100644 --- a/dev-packages/e2e-tests/test-applications/node-esbuild/package.json +++ b/dev-packages/e2e-tests/test-applications/node-esbuild/package.json @@ -11,8 +11,7 @@ }, "dependencies": { "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz", - "@sentry/bundler-plugins": "file:../../packed/sentry-bundler-plugins-packed.tgz" + "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz" }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", diff --git a/dev-packages/e2e-tests/test-applications/node-rolldown/package.json b/dev-packages/e2e-tests/test-applications/node-rolldown/package.json index c0edf0628f9e..14c868b2da02 100644 --- a/dev-packages/e2e-tests/test-applications/node-rolldown/package.json +++ b/dev-packages/e2e-tests/test-applications/node-rolldown/package.json @@ -11,8 +11,7 @@ }, "dependencies": { "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz", - "@sentry/bundler-plugins": "file:../../packed/sentry-bundler-plugins-packed.tgz" + "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz" }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", diff --git a/dev-packages/e2e-tests/test-applications/node-rollup/package.json b/dev-packages/e2e-tests/test-applications/node-rollup/package.json index 40cc0d2cdada..6476b61b7672 100644 --- a/dev-packages/e2e-tests/test-applications/node-rollup/package.json +++ b/dev-packages/e2e-tests/test-applications/node-rollup/package.json @@ -11,8 +11,7 @@ }, "dependencies": { "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz", - "@sentry/bundler-plugins": "file:../../packed/sentry-bundler-plugins-packed.tgz" + "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz" }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", diff --git a/dev-packages/e2e-tests/test-applications/node-vite/package.json b/dev-packages/e2e-tests/test-applications/node-vite/package.json index 22988b563137..a6617c704f85 100644 --- a/dev-packages/e2e-tests/test-applications/node-vite/package.json +++ b/dev-packages/e2e-tests/test-applications/node-vite/package.json @@ -11,8 +11,7 @@ }, "dependencies": { "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz", - "@sentry/bundler-plugins": "file:../../packed/sentry-bundler-plugins-packed.tgz" + "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz" }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", diff --git a/dev-packages/e2e-tests/test-applications/node-webpack/package.json b/dev-packages/e2e-tests/test-applications/node-webpack/package.json index 37da560ad577..3c19399d2568 100644 --- a/dev-packages/e2e-tests/test-applications/node-webpack/package.json +++ b/dev-packages/e2e-tests/test-applications/node-webpack/package.json @@ -11,8 +11,7 @@ }, "dependencies": { "@sentry/node": "file:../../packed/sentry-node-packed.tgz", - "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz", - "@sentry/bundler-plugins": "file:../../packed/sentry-bundler-plugins-packed.tgz" + "@sentry/server-utils": "file:../../packed/sentry-server-utils-packed.tgz" }, "devDependencies": { "@sentry-internal/test-utils": "link:../../../test-utils", From 1b494da593673d886273e4847065c5fc223001db Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 15 Oct 2026 16:18:36 +0200 Subject: [PATCH 005/118] ci(e2e): Run all E2E tests when CI config changes (#25391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The E2E matrix only includes apps whose `@sentry/*` dependencies Nx marks as affected, or that changed themselves. Nx doesn't track `.github/**`, so a PR that only changes workflows or actions (for example how E2E apps are installed or run) gets an empty E2E matrix and never exercises its own change. The other test jobs already handle this through `changed_ci` (any change under `.github/`). The required and optional matrix steps now drop `--base` in that case. `getTestMatrix.mjs` already returns every app without `--base`, as on pushes to `develop`. Optional apps keep their existing conditions: they still don't run on fork PRs, Dependabot PRs or release branches. The tradeoff is that unrelated `.github` changes, such as `CODEOWNERS`, also run the full matrix. That matches how the rest of CI treats them. ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- .github/workflows/build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99ad92d4515d..702a6ddbc2cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,14 +143,16 @@ jobs: id: matrix run: yarn --silent ci:build-matrix --base=${{ (github.event_name == 'pull_request' && - github.event.pull_request.base.sha) || '' }} >> $GITHUB_OUTPUT + needs.job_get_metadata.outputs.changed_ci != 'true' && github.event.pull_request.base.sha) || '' }} >> + $GITHUB_OUTPUT working-directory: dev-packages/e2e-tests - name: Determine which optional E2E test applications should be run id: matrix-optional run: yarn --silent ci:build-matrix-optional --base=${{ (github.event_name == 'pull_request' && - github.event.pull_request.base.sha) || '' }} >> $GITHUB_OUTPUT + needs.job_get_metadata.outputs.changed_ci != 'true' && github.event.pull_request.base.sha) || '' }} >> + $GITHUB_OUTPUT working-directory: dev-packages/e2e-tests outputs: From 5ba912322f606972f0848b4e49aa63be7eae34c2 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 15 Oct 2026 16:18:37 +0200 Subject: [PATCH 006/118] ci: Read the Playwright version from package.json in install-playwright (#25380) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `install-playwright` action resolved the version from the installed `@playwright/test` in `node_modules`. In E2E jobs that is only available after the app's build, so the jobs installed the root's version in the background and then ran the action a second time, in case the app used a different one. The action now requires a `cwd` and reads `@playwright/test` from `/package.json`. That works before any dependencies are installed, and each caller always gets the version it actually uses. E2E jobs point it at the checked-out app and install once. Directories without `@playwright/test` install nothing. The version is the declared range with its `~`/`^` prefix stripped. A follow-up PR pins exact versions so this is always the version that gets installed. ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 --- .github/actions/install-playwright/action.yml | 35 ++++++++----------- .github/workflows/build.yml | 24 ++++--------- .github/workflows/flaky-test-detector.yml | 1 + 3 files changed, 21 insertions(+), 39 deletions(-) diff --git a/.github/actions/install-playwright/action.yml b/.github/actions/install-playwright/action.yml index 6c8ea05d3c91..30815292dea2 100644 --- a/.github/actions/install-playwright/action.yml +++ b/.github/actions/install-playwright/action.yml @@ -5,33 +5,25 @@ inputs: description: 'What browsers to install.' default: 'chromium webkit firefox' cwd: - description: 'The working directory to run Playwright in.' - default: '.' - installed-version: description: - 'A Playwright version that an earlier run of this action already installed. Skips the install if it matches.' - default: '' - -outputs: - version: - description: 'The resolved Playwright version, empty if Playwright is not installed or the install was skipped.' - value: ${{ steps.playwright-version.outputs.version }} + 'The directory whose package.json declares the `@playwright/test` version to install. Nothing is installed if it + does not depend on `@playwright/test`.' + required: true runs: using: 'composite' steps: - # Resolve the app's @playwright/test version. Some apps (e.g. the node bundler apps) assert via a - # plain node script and don't depend on Playwright โ€” emit an empty version instead of failing, so - # the steps below skip the (wasted) browser install rather than erroring on the missing package. + # Read from package.json rather than node_modules, so this can run before dependencies are installed. - name: Get Playwright version id: playwright-version run: | - version=$(node -e "let v = ''; try { v = require('@playwright/test/package.json').version; } catch {} process.stdout.write(String(v));") - if [ "$version" = "$INSTALLED_VERSION" ]; then version=''; fi + version=$(node -e " + const { dependencies, devDependencies } = require('./package.json'); + const range = devDependencies?.['@playwright/test'] ?? dependencies?.['@playwright/test'] ?? ''; + process.stdout.write(range.replace(/^[~^=]/, '')); + ") echo "version=$version" >> "$GITHUB_OUTPUT" shell: bash - env: - INSTALLED_VERSION: ${{ inputs.installed-version }} working-directory: ${{ inputs.cwd }} - name: Restore cached playwright binaries @@ -46,18 +38,19 @@ runs: # We always install all browsers, if uncached - name: Install Playwright dependencies (uncached) - run: npx playwright install chromium webkit firefox --with-deps + env: + PLAYWRIGHT_VERSION: ${{ steps.playwright-version.outputs.version }} + run: npx -y "playwright@$PLAYWRIGHT_VERSION" install chromium webkit firefox --with-deps if: steps.playwright-version.outputs.version != '' && steps.playwright-cache.outputs.cache-hit != 'true' shell: bash - working-directory: ${{ inputs.cwd }} - name: Install Playwright system dependencies only (cached) env: PLAYWRIGHT_BROWSERS: ${{ inputs.browsers || 'chromium webkit firefox' }} - run: npx playwright install-deps "$PLAYWRIGHT_BROWSERS" + PLAYWRIGHT_VERSION: ${{ steps.playwright-version.outputs.version }} + run: npx -y "playwright@$PLAYWRIGHT_VERSION" install-deps "$PLAYWRIGHT_BROWSERS" if: steps.playwright-version.outputs.version != '' && steps.playwright-cache.outputs.cache-hit == 'true' shell: bash - working-directory: ${{ inputs.cwd }} # Only store cache on develop branch - name: Store cached playwright binaries diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 702a6ddbc2cb..cb05f34f6df2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -684,6 +684,7 @@ jobs: uses: ./.github/actions/install-playwright with: browsers: ${{ matrix.project }} + cwd: dev-packages/browser-integration-tests - name: Run Playwright tests env: @@ -746,6 +747,7 @@ jobs: uses: ./.github/actions/install-playwright with: browsers: chromium + cwd: dev-packages/browser-integration-tests - name: Run Playwright Loader tests env: @@ -1101,8 +1103,7 @@ jobs: name: build-layer-output path: ${{ env.BUILD_LAYER_PATH }} - # Installs the root workspace's Playwright version while the app builds. Most apps use the same version. - # The app's own version is only known after its build, so it gets installed then if it differs. + # Installs Playwright while the app builds. # Started after the artifact downloads: starting next to them made `download-artifact` fail to read the event file. - name: Install Playwright id: install-playwright @@ -1110,6 +1111,7 @@ jobs: uses: ./.github/actions/install-playwright with: browsers: chromium + cwd: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - name: Prepare e2e tests run: yarn test:prepare @@ -1138,13 +1140,6 @@ jobs: - wait: [install-playwright] - - name: Install Playwright for the app's version - uses: ./.github/actions/install-playwright - with: - browsers: chromium - cwd: ${{ runner.temp }}/test-application - installed-version: ${{ steps.install-playwright.outputs.version }} - - name: Run E2E test working-directory: ${{ runner.temp }}/test-application timeout-minutes: 10 @@ -1248,8 +1243,7 @@ jobs: name: build-tarball-output path: ${{ env.TARBALL_ARTIFACT_DOWNLOAD_PATH }} - # Installs the root workspace's Playwright version while the app builds. Most apps use the same version. - # The app's own version is only known after its build, so it gets installed then if it differs. + # Installs Playwright while the app builds. # Started after the artifact downloads: starting next to them made `download-artifact` fail to read the event file. - name: Install Playwright id: install-playwright @@ -1257,6 +1251,7 @@ jobs: uses: ./.github/actions/install-playwright with: browsers: chromium + cwd: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - name: Prepare E2E tests run: yarn test:prepare @@ -1283,13 +1278,6 @@ jobs: - wait: [install-playwright] - - name: Install Playwright for the app's version - uses: ./.github/actions/install-playwright - with: - browsers: chromium - cwd: ${{ runner.temp }}/test-application - installed-version: ${{ steps.install-playwright.outputs.version }} - - name: Run E2E test working-directory: ${{ runner.temp }}/test-application timeout-minutes: 10 diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml index ae4fbc9ad692..b4c7e6e40c5f 100644 --- a/.github/workflows/flaky-test-detector.yml +++ b/.github/workflows/flaky-test-detector.yml @@ -42,6 +42,7 @@ jobs: uses: ./.github/actions/install-playwright with: browsers: 'chromium' + cwd: dev-packages/browser-integration-tests - name: Determine changed tests uses: dorny/paths-filter@v4.0.3 From 306cebbf7e28ec670209b6dbb47df17be4bdf00f Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Thu, 15 Oct 2026 16:18:38 +0200 Subject: [PATCH 007/118] test(e2e): Align Playwright versions with the root version (#25381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ten E2E apps still declared `@playwright/test@~1.56.0`, while the root and all other apps use `~1.63.0`: - `bun-express` - `cloudflare-workers-send-to-sentry` - `node-eve` - `node-fastify-6` - `node-flue` - `node-mastra` - `node-prisma-8` - `remix-v3` - `tanstackstart-react-static` - `vue-3-static` No app is ahead of the root, and nothing in these apps references 1.56 specifically. Aligning them gets every job onto one cached set of browsers, and prepares for pinning exact versions and checking for drift in follow-up PRs. Stacked on #25380. ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- .../e2e-tests/test-applications/bun-express/package.json | 2 +- .../cloudflare-workers-send-to-sentry/package.json | 2 +- dev-packages/e2e-tests/test-applications/node-eve/package.json | 2 +- .../e2e-tests/test-applications/node-fastify-6/package.json | 2 +- dev-packages/e2e-tests/test-applications/node-flue/package.json | 2 +- .../e2e-tests/test-applications/node-mastra/package.json | 2 +- .../e2e-tests/test-applications/node-prisma-8/package.json | 2 +- dev-packages/e2e-tests/test-applications/remix-v3/package.json | 2 +- .../test-applications/tanstackstart-react-static/package.json | 2 +- .../e2e-tests/test-applications/vue-3-static/package.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/bun-express/package.json b/dev-packages/e2e-tests/test-applications/bun-express/package.json index cd2634fe22f4..8922fb8d0edc 100644 --- a/dev-packages/e2e-tests/test-applications/bun-express/package.json +++ b/dev-packages/e2e-tests/test-applications/bun-express/package.json @@ -15,7 +15,7 @@ "express": "^5.1.0" }, "devDependencies": { - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@types/express": "^5.0.0", "bun-types": "^1.2.9" diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/package.json index 1c2069e8c9d4..0d912e57fc80 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/package.json +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/package.json @@ -17,7 +17,7 @@ "devDependencies": { "@cloudflare/vite-plugin": "^1.47.0", "@cloudflare/workers-types": "^5.20260727.1", - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@types/node": "^26.1.2", "sentry": "~0.44.1", diff --git a/dev-packages/e2e-tests/test-applications/node-eve/package.json b/dev-packages/e2e-tests/test-applications/node-eve/package.json index 0165ca0f8bf2..b9ff4cfa3c36 100644 --- a/dev-packages/e2e-tests/test-applications/node-eve/package.json +++ b/dev-packages/e2e-tests/test-applications/node-eve/package.json @@ -30,7 +30,7 @@ "zod": "4.5.4" }, "devDependencies": { - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/conventions": "0.27.0", "@sentry/core": "file:../../packed/sentry-core-packed.tgz", diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-6/package.json b/dev-packages/e2e-tests/test-applications/node-fastify-6/package.json index cf104f24b932..36b5a84a77fe 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify-6/package.json +++ b/dev-packages/e2e-tests/test-applications/node-fastify-6/package.json @@ -18,7 +18,7 @@ "ts-node": "10.9.2" }, "devDependencies": { - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/node-flue/package.json b/dev-packages/e2e-tests/test-applications/node-flue/package.json index f3876f611d92..c375103f6fa9 100644 --- a/dev-packages/e2e-tests/test-applications/node-flue/package.json +++ b/dev-packages/e2e-tests/test-applications/node-flue/package.json @@ -24,7 +24,7 @@ "devDependencies": { "@flue/cli": "2.0.5", "@flue/vite": "2.0.5", - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "file:../../packed/sentry-core-packed.tgz", "@types/node": "24.x", diff --git a/dev-packages/e2e-tests/test-applications/node-mastra/package.json b/dev-packages/e2e-tests/test-applications/node-mastra/package.json index 686eae393ed3..a0a11a996998 100644 --- a/dev-packages/e2e-tests/test-applications/node-mastra/package.json +++ b/dev-packages/e2e-tests/test-applications/node-mastra/package.json @@ -26,7 +26,7 @@ "zod": "4.5.4" }, "devDependencies": { - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/conventions": "0.27.0", "@types/node": "24.x", diff --git a/dev-packages/e2e-tests/test-applications/node-prisma-8/package.json b/dev-packages/e2e-tests/test-applications/node-prisma-8/package.json index 6b42d3ee2e7c..48d9252eb770 100644 --- a/dev-packages/e2e-tests/test-applications/node-prisma-8/package.json +++ b/dev-packages/e2e-tests/test-applications/node-prisma-8/package.json @@ -20,7 +20,7 @@ "typescript": "~5.9.0" }, "devDependencies": { - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "file:../../packed/sentry-core-packed.tgz" }, diff --git a/dev-packages/e2e-tests/test-applications/remix-v3/package.json b/dev-packages/e2e-tests/test-applications/remix-v3/package.json index 6e27d54be58b..fe19e83132f1 100644 --- a/dev-packages/e2e-tests/test-applications/remix-v3/package.json +++ b/dev-packages/e2e-tests/test-applications/remix-v3/package.json @@ -15,7 +15,7 @@ "remix": "3.0.0" }, "devDependencies": { - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "file:../../packed/sentry-core-packed.tgz", "@types/node": "^24.6.0", diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json b/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json index ff237684be5d..46d69f6fcf7b 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json @@ -43,7 +43,7 @@ "typescript": "^5.9.0", "vite": "7.3.2", "vite-tsconfig-paths": "^5.1.4", - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/vue-3-static/package.json b/dev-packages/e2e-tests/test-applications/vue-3-static/package.json index ec52fbcbeeaf..4c3219a12cc2 100644 --- a/dev-packages/e2e-tests/test-applications/vue-3-static/package.json +++ b/dev-packages/e2e-tests/test-applications/vue-3-static/package.json @@ -27,7 +27,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@playwright/test": "~1.56.0", + "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils", "@tsconfig/node20": "^20.1.2", "@types/node": "^18.19.1", From 1c5303c9b24d333103e084ca4b76fb9acdfb25bc Mon Sep 17 00:00:00 2001 From: Sigrid <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 15 Oct 2026 16:24:01 +0200 Subject: [PATCH 008/118] ci(e2e): Run E2E tests with pnpm 12 (#25334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves the E2E tests from pnpm 9 to pnpm 12. Volta still manages node. - **Every app has a `pnpm-workspace.yaml`.** pnpm 11+ reads its settings only from this file. - `allowBuilds`: pnpm 11+ fails the install on build scripts that are not listed. `false` is only for scripts that just print a message. - `minimumReleaseAge: 0`: by default, pnpm 11+ installs only releases that are at least 24 hours old, and falls back to older versions. The apps test against the newest releases, so the E2E runner fails if an app does not set it. - **Updated to `@electron/rebuild` ^4.2.1.** Older versions depend on `@electron/node-gyp` from git, and pnpm 11+ blocks git dependencies of dependencies. - **Ignored overrides fixed.** The bundler-plugin fixtures had overrides in `package.json#pnpm`, and some apps used `package.json#resolutions`. pnpm 11+ ignores both without an error. The E2E runner now fails if an app still uses them. - **Test app fixes for pnpm 12.** - The Next.js tests and build assertions read the installed version from `next/package.json`. `pnpm add next@15` now saves `^15.x.y`, and the old version checks did not work with the `^`. - The tanstackstart apps use `"nitro": "^3.0.0"`, because pnpm 12 resolved `latest || *` to the placeholder `nitro@0.0.0`. This installs the same `nitro@3.0.0` as pnpm 9. A follow-up moves them to the nitro beta, which needs Vite 8. - 7 apps no longer run `npx playwright install` in `test:build`. CI already installs the app's Playwright version, and the second install ran at the same time and failed. **Why pnpm is a root dev dependency** pnpm switches to the version in a `packageManager` field, but a pnpm must already be installed to do that. Yarn installs that first pnpm as a dev dependency, because Volta [can't run pnpm 12](https://github.com/volta-cli/volta/issues/2114) and is [unmaintained](https://github.com/volta-cli/volta/issues/2080). The `pnpm` package is a native binary, so it does not depend on the node version that an app pins. `yarn.lock` pins the version, so local runs and CI use the same pnpm. Contributors need no extra setup, and CI caches pnpm with `node_modules`. Rejected alternatives: - `pnpm/action-setup` alone: it only installs pnpm in CI. Contributors would have to install pnpm themselves, and their version could differ from CI. - Corepack: it is deprecated, and Node 25 no longer includes it. - mise: no getsentry JS repo uses it, and contributors would need one more tool (alongside Volta). โš ๏ธ @JPeer264 When the repo moves from Yarn to pnpm, remove the dev dependency and set `packageManager` in the root `package.json`. A test app can still pin another pnpm version (10.30 or newer, which reads `allowBuilds`) with its own `packageManager` field. - https://github.com/getsentry/sentry-javascript/pull/25354 --- .agents/skills/e2e/SKILL.md | 2 +- .cursor/environment.json | 6 +- .github/actions/install-pnpm/action.yml | 29 ++---- .github/workflows/build.yml | 15 +--- .github/workflows/canary.yml | 6 +- .github/workflows/lighthouse.yml | 4 +- AGENTS.md | 3 +- CONTRIBUTING.md | 9 +- .../fixtures/esbuild/package.json | 7 -- .../fixtures/esbuild/pnpm-workspace.yaml | 7 ++ .../fixtures/rolldown/package.json | 7 -- .../fixtures/rolldown/pnpm-workspace.yaml | 5 ++ .../fixtures/rollup3/package.json | 7 -- .../fixtures/rollup3/pnpm-workspace.yaml | 5 ++ .../fixtures/rollup4/package.json | 7 -- .../fixtures/rollup4/pnpm-workspace.yaml | 5 ++ .../fixtures/vite4/package.json | 7 -- .../fixtures/vite4/pnpm-workspace.yaml | 7 ++ .../fixtures/vite6/package.json | 7 -- .../fixtures/vite6/pnpm-workspace.yaml | 7 ++ .../fixtures/vite7/package.json | 7 -- .../fixtures/vite7/pnpm-workspace.yaml | 7 ++ .../fixtures/vite8/package.json | 7 -- .../fixtures/vite8/pnpm-workspace.yaml | 5 ++ .../fixtures/webpack5/package.json | 7 -- .../fixtures/webpack5/pnpm-workspace.yaml | 5 ++ dev-packages/e2e-tests/README.md | 12 ++- dev-packages/e2e-tests/lib/pnpmOverrides.ts | 84 ++++++----------- dev-packages/e2e-tests/run.ts | 49 ++++++---- .../angular-17/pnpm-workspace.yaml | 4 + .../angular-18/pnpm-workspace.yaml | 6 ++ .../angular-19/pnpm-workspace.yaml | 6 ++ .../angular-20/pnpm-workspace.yaml | 6 ++ .../angular-21/pnpm-workspace.yaml | 6 ++ .../angular-22/pnpm-workspace.yaml | 6 ++ .../astro-4/pnpm-workspace.yaml | 4 + .../astro-5-cf-workers/pnpm-workspace.yaml | 5 ++ .../test-applications/astro-5/package.json | 5 -- .../astro-5/pnpm-workspace.yaml | 6 ++ .../astro-6-cf-workers/pnpm-workspace.yaml | 5 ++ .../astro-6/pnpm-workspace.yaml | 4 + .../astro-7-sourcemaps/pnpm-workspace.yaml | 1 + .../astro-7-static/pnpm-workspace.yaml | 1 + .../astro-7/pnpm-workspace.yaml | 1 + .../aws-serverless-layer/package.json | 7 -- .../aws-serverless-layer/pnpm-workspace.yaml | 4 + .../aws-serverless/pnpm-workspace.yaml | 1 + .../aws-serverless/src/stack.ts | 15 ++-- .../browser-bfcache/pnpm-workspace.yaml | 3 + .../browser-mfe-vite/pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 3 + .../bun-bytecode/pnpm-workspace.yaml | 1 + .../bun-express/pnpm-workspace.yaml | 1 + .../bun-mysql/pnpm-workspace.yaml | 1 + .../cloudflare-agent/pnpm-workspace.yaml | 5 ++ .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 5 ++ .../pnpm-workspace.yaml | 7 ++ .../cloudflare-local-workers/package.json | 5 -- .../pnpm-workspace.yaml | 6 ++ .../cloudflare-mastra/pnpm-workspace.yaml | 4 + .../cloudflare-mcp-agent/pnpm-workspace.yaml | 5 ++ .../cloudflare-mcp/pnpm-workspace.yaml | 5 ++ .../pnpm-workspace.yaml | 4 + .../cloudflare-pi-durable/pnpm-workspace.yaml | 7 ++ .../cloudflare-think/pnpm-workspace.yaml | 7 ++ .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 4 + .../cloudflare-workers-static/package.json | 5 -- .../pnpm-workspace.yaml | 6 ++ .../pnpm-workspace.yaml | 5 ++ .../cloudflare-workers/package.json | 5 -- .../cloudflare-workers/pnpm-workspace.yaml | 6 ++ .../cloudflare-workersentrypoint/package.json | 5 -- .../pnpm-workspace.yaml | 6 ++ .../create-react-app/pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 4 + .../create-remix-app-v2-non-vite/package.json | 5 +- .../pnpm-workspace.yaml | 6 ++ .../create-remix-app-v2-static/package.json | 3 - .../pnpm-workspace.yaml | 6 ++ .../create-remix-app-v2/package.json | 3 - .../create-remix-app-v2/pnpm-workspace.yaml | 6 ++ .../debug-id-sourcemaps/pnpm-workspace.yaml | 1 + .../pnpm-workspace.yaml | 1 + .../default-browser/pnpm-workspace.yaml | 1 + .../deno/pnpm-workspace.yaml | 1 + .../effect-3-browser/pnpm-workspace.yaml | 1 + .../effect-3-node/pnpm-workspace.yaml | 4 + .../effect-4-browser/pnpm-workspace.yaml | 1 + .../effect-4-node/pnpm-workspace.yaml | 1 + .../elysia-bun-static/pnpm-workspace.yaml | 1 + .../elysia-bun/pnpm-workspace.yaml | 1 + .../elysia-node/pnpm-workspace.yaml | 1 + .../ember-classic/package.json | 9 -- .../ember-classic/pnpm-workspace.yaml | 7 ++ .../ember-embroider/package.json | 5 -- .../ember-embroider/pnpm-workspace.yaml | 5 ++ .../ember-strict-resolver/package.json | 8 -- .../ember-strict-resolver/pnpm-workspace.yaml | 8 ++ .../test-applications/ember-vite/package.json | 8 -- .../ember-vite/pnpm-workspace.yaml | 8 ++ .../gen-ai-libraries/pnpm-workspace.yaml | 4 + .../generic-ts5.0/pnpm-workspace.yaml | 1 + .../hono-4-legacy/pnpm-workspace.yaml | 4 + .../hono-4/pnpm-workspace.yaml | 4 + .../hydrogen-react-router-7/package.json | 2 +- .../pnpm-workspace.yaml | 4 + .../lighthouse-react/pnpm-workspace.yaml | 4 + .../nestjs-11-static/package.json | 5 -- .../nestjs-11-static/pnpm-workspace.yaml | 3 + .../test-applications/nestjs-11/package.json | 5 -- .../nestjs-11/pnpm-workspace.yaml | 3 + .../test-applications/nestjs-12/package.json | 5 -- .../nestjs-12/pnpm-workspace.yaml | 3 + .../nestjs-8/pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 4 + .../nestjs-basic/pnpm-workspace.yaml | 3 + .../nestjs-bullmq/pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 3 + .../nestjs-fastify/pnpm-workspace.yaml | 3 + .../nestjs-graphql/pnpm-workspace.yaml | 4 + .../nestjs-microservices/package.json | 5 -- .../nestjs-microservices/pnpm-workspace.yaml | 3 + .../nestjs-websockets/pnpm-workspace.yaml | 1 + .../pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 3 + .../nextjs-14/pnpm-workspace.yaml | 3 + .../nextjs-15-basepath/pnpm-workspace.yaml | 1 + .../nextjs-15-intl/pnpm-workspace.yaml | 4 + .../nextjs-15-t3/pnpm-workspace.yaml | 3 + .../nextjs-15/pnpm-workspace.yaml | 1 + .../tests/client-trace-propagation.test.ts | 4 +- .../nextjs-15/tests/nested-rsc-error.test.ts | 4 +- .../tests/streaming-rsc-error.test.ts | 4 +- .../pnpm-workspace.yaml | 3 + .../nextjs-16-standalone/pnpm-workspace.yaml | 3 + .../nextjs-16-static/pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 3 + .../nextjs-16-streaming/pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 3 + .../nextjs-16-tunnel/pnpm-workspace.yaml | 5 ++ .../pnpm-workspace.yaml | 1 + .../nextjs-16/pnpm-workspace.yaml | 5 ++ .../nextjs-app-dir/assert-build.ts | 4 +- .../nextjs-app-dir/pnpm-workspace.yaml | 1 + .../tests/client-errors.test.ts | 4 +- .../nextjs-app-dir/tests/transactions.test.ts | 12 +-- .../nextjs-orpc/pnpm-workspace.yaml | 3 + .../nextjs-otlp/pnpm-workspace.yaml | 3 + .../nextjs-pages-dir/assert-build.ts | 4 +- .../nextjs-pages-dir/pnpm-workspace.yaml | 1 + .../tests/transactions.test.ts | 4 +- .../nextjs-sourcemaps/pnpm-workspace.yaml | 1 + .../test-applications/nitro-3/package.json | 5 -- .../nitro-3/pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 1 + .../node-esbuild/pnpm-workspace.yaml | 3 + .../node-eve/pnpm-workspace.yaml | 1 + .../node-exports-test-app/pnpm-workspace.yaml | 1 + .../pnpm-workspace.yaml | 1 + .../pnpm-workspace.yaml | 1 + .../node-express-mcp-v1/pnpm-workspace.yaml | 1 + .../node-express-mcp-v2/pnpm-workspace.yaml | 1 + .../node-express-otlp/pnpm-workspace.yaml | 1 + .../pnpm-workspace.yaml | 1 + .../pnpm-workspace.yaml | 1 + .../node-express-static/package.json | 3 - .../node-express-static/pnpm-workspace.yaml | 3 + .../node-express-streaming/package.json | 3 - .../pnpm-workspace.yaml | 3 + .../node-express-v5/package.json | 3 - .../node-express-v5/pnpm-workspace.yaml | 3 + .../node-express/package.json | 3 - .../node-express/pnpm-workspace.yaml | 3 + .../node-fastify-3/pnpm-workspace.yaml | 1 + .../node-fastify-4/pnpm-workspace.yaml | 1 + .../node-fastify-5/pnpm-workspace.yaml | 1 + .../node-fastify-6/pnpm-workspace.yaml | 1 + .../node-firebase/pnpm-workspace.yaml | 5 ++ .../node-flue/pnpm-workspace.yaml | 6 ++ .../node-hapi/pnpm-workspace.yaml | 1 + .../node-koa/pnpm-workspace.yaml | 1 + .../node-mastra/pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 1 + .../pnpm-workspace.yaml | 1 + .../node-otel-sdk-node/pnpm-workspace.yaml | 3 + .../node-pi-durable/pnpm-workspace.yaml | 5 ++ .../node-prisma-8/pnpm-workspace.yaml | 3 + .../node-profiling-cjs/pnpm-workspace.yaml | 4 + .../node-profiling-electron/package.json | 2 +- .../pnpm-workspace.yaml | 1 + .../node-profiling-esm/pnpm-workspace.yaml | 4 + .../node-rolldown/pnpm-workspace.yaml | 1 + .../node-rollup/pnpm-workspace.yaml | 1 + .../node-vite/pnpm-workspace.yaml | 3 + .../node-webpack/pnpm-workspace.yaml | 1 + .../nuxt-3-dynamic-import/pnpm-workspace.yaml | 3 + .../test-applications/nuxt-3-min/package.json | 5 -- .../nuxt-3-min/pnpm-workspace.yaml | 5 ++ .../pnpm-workspace.yaml | 3 + .../nuxt-3/pnpm-workspace.yaml | 3 + .../nuxt-4-cloudflare/pnpm-workspace.yaml | 4 + .../nuxt-4-sourcemaps/pnpm-workspace.yaml | 3 + .../nuxt-4-static/pnpm-workspace.yaml | 4 + .../nuxt-4/pnpm-workspace.yaml | 4 + .../nuxt-5-sourcemaps/package.json | 3 +- .../test-applications/nuxt-5/package.json | 3 +- .../react-17-static/pnpm-workspace.yaml | 4 + .../react-17/pnpm-workspace.yaml | 4 + .../react-19/pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 4 + .../react-router-5/pnpm-workspace.yaml | 4 + .../package.json | 4 +- .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 4 + .../react-router-6/pnpm-workspace.yaml | 4 + .../react-router-7-cross-usage/package.json | 4 +- .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 3 + .../react-router-7-lazy-routes/package.json | 4 +- .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 3 + .../react-router-7-spa/pnpm-workspace.yaml | 3 + .../react-router-8-cross-usage/package.json | 4 +- .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 3 + .../react-router-8-spa/pnpm-workspace.yaml | 3 + .../pnpm-workspace.yaml | 3 + .../react-send-to-sentry/pnpm-workspace.yaml | 4 + .../remix-hydrogen/package.json | 2 +- .../remix-hydrogen/pnpm-workspace.yaml | 5 ++ .../remix-server-timing/package.json | 3 - .../remix-server-timing/pnpm-workspace.yaml | 6 ++ .../remix-sourcemaps/pnpm-workspace.yaml | 3 + .../remix-v3/pnpm-workspace.yaml | 3 + .../solid-static/pnpm-workspace.yaml | 3 + .../solid-tanstack-router/pnpm-workspace.yaml | 3 + .../solid/pnpm-workspace.yaml | 3 + .../solidstart-2/pnpm-workspace.yaml | 1 + .../pnpm-workspace.yaml | 4 + .../solidstart-spa/pnpm-workspace.yaml | 4 + .../solidstart-static/pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 4 + .../solidstart/pnpm-workspace.yaml | 4 + .../supabase-nextjs/pnpm-workspace.yaml | 4 + .../svelte-5-static/pnpm-workspace.yaml | 4 + .../svelte-5/pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 3 + .../sveltekit-2-otlp/pnpm-workspace.yaml | 3 + .../sveltekit-2-static/package.json | 5 -- .../sveltekit-2-static/pnpm-workspace.yaml | 6 ++ .../sveltekit-2-svelte-5/pnpm-workspace.yaml | 4 + .../sveltekit-2.5.0-twp/pnpm-workspace.yaml | 5 ++ .../sveltekit-2/package.json | 5 -- .../sveltekit-2/pnpm-workspace.yaml | 6 ++ .../pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 1 + .../sveltekit-3/pnpm-workspace.yaml | 1 + .../pnpm-workspace.yaml | 4 + .../tanstack-router/pnpm-workspace.yaml | 4 + .../pnpm-workspace.yaml | 4 + .../tanstackstart-react-static/package.json | 2 +- .../pnpm-workspace.yaml | 4 + .../tanstackstart-react/package.json | 2 +- .../tanstackstart-react/pnpm-workspace.yaml | 4 + .../tsx-express/package.json | 3 - .../tsx-express/pnpm-workspace.yaml | 5 ++ .../vinext-cf-workers/pnpm-workspace.yaml | 4 + .../vue-3-static/pnpm-workspace.yaml | 3 + .../vue-3/pnpm-workspace.yaml | 3 + .../vue-tanstack-router/pnpm-workspace.yaml | 4 + .../webpack-4/pnpm-workspace.yaml | 3 + .../webpack-5/pnpm-workspace.yaml | 1 + package.json | 4 +- scripts/bump-version.js | 19 ++-- scripts/bump-version.test.ts | 27 ++++-- scripts/claude-cloud-setup.sh | 3 - yarn.lock | 90 +++++++++++++++++++ 289 files changed, 997 insertions(+), 390 deletions(-) create mode 100644 dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/pnpm-workspace.yaml create mode 100644 dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/pnpm-workspace.yaml create mode 100644 dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/pnpm-workspace.yaml create mode 100644 dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/pnpm-workspace.yaml create mode 100644 dev-packages/bundler-plugin-integration-tests/fixtures/vite4/pnpm-workspace.yaml create mode 100644 dev-packages/bundler-plugin-integration-tests/fixtures/vite6/pnpm-workspace.yaml create mode 100644 dev-packages/bundler-plugin-integration-tests/fixtures/vite7/pnpm-workspace.yaml create mode 100644 dev-packages/bundler-plugin-integration-tests/fixtures/vite8/pnpm-workspace.yaml create mode 100644 dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/angular-17/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/angular-18/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/angular-19/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/angular-20/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/angular-21/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/angular-22/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/astro-4/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/astro-5-cf-workers/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/astro-5/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/astro-6-cf-workers/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/astro-6/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/astro-7-sourcemaps/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/astro-7-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/astro-7/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/aws-serverless-layer/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/aws-serverless/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/browser-bfcache/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/browser-webworker-vite/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/bun-bytecode/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/bun-express/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/bun-mysql/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-agent/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-flue-send-to-sentry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-local-workers/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-mastra/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-mcp-agent/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-mcp/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-pi-durable/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-think/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-workers-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-workers-workflow-legacy/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-workers/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-workersentrypoint/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/create-react-app/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-express/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/create-remix-app-v2/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/default-browser-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/default-browser/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/deno/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/effect-3-browser/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/effect-3-node/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/effect-4-browser/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/effect-4-node/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/elysia-bun-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/elysia-bun/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/elysia-node/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/ember-classic/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/ember-embroider/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/ember-strict-resolver/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/ember-vite/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/gen-ai-libraries/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/generic-ts5.0/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/hono-4-legacy/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/hono-4/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/lighthouse-react/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-11/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-12/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-8/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-basic/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-bullmq/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-fastify/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-graphql/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-microservices/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-websockets/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-14/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-15-basepath/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-15-intl/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-15-t3/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-15/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-standalone/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-streaming/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-trailing-slash-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-trailing-slash/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16-userfeedback/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-16/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-app-dir/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-orpc/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-otlp/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-pages-dir/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-sourcemaps/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nitro-3/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-esbuild/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-eve/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-exports-test-app/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-esm-loader/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-mcp-v1/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-mcp-v2/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-otlp/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-send-to-sentry-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-streaming/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express-v5/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-express/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-fastify-3/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-fastify-4/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-fastify-5/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-fastify-6/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-flue/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-hapi/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-koa/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-mastra/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-openai-decisions-send-to-sentry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-otel-sdk-node/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-pi-durable/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-prisma-8/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-cjs/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-electron/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-profiling-esm/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-rolldown/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-rollup/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-vite/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/node-webpack/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-3-min/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-3/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-4-cloudflare/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-4-sourcemaps/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-4-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-4/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-17-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-17/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-19/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-create-browser-router/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-create-hash-router/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-create-memory-router/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6-router-entry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6-use-routes/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-spa/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-framework/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-router-entry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-spa-streaming/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-7-spa/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-8-cross-usage/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-8-framework/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-8-router-entry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-8-spa/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-router-sourcemaps/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/react-send-to-sentry/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/remix-hydrogen/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/remix-server-timing/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/remix-sourcemaps/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/remix-v3/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/solid-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/solid-tanstack-router/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/solid/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/solidstart-2/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/solidstart-spa/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/solidstart-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/solidstart-top-level-import/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/solidstart/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/supabase-nextjs/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/svelte-5-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/svelte-5/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-kit-tracing/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-otlp/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-2/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-3-cloudflare-workers/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-3-sourcemaps/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-3/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/tanstack-router/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/tanstackstart-react-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/tanstackstart-react/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/tsx-express/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/vinext-cf-workers/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/vue-3-static/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/vue-3/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/vue-tanstack-router/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/webpack-4/pnpm-workspace.yaml create mode 100644 dev-packages/e2e-tests/test-applications/webpack-5/pnpm-workspace.yaml diff --git a/.agents/skills/e2e/SKILL.md b/.agents/skills/e2e/SKILL.md index aa7dcde7ad5a..4fad6a4efd04 100644 --- a/.agents/skills/e2e/SKILL.md +++ b/.agents/skills/e2e/SKILL.md @@ -118,7 +118,7 @@ All tests completed successfully. Your SDK changes work correctly with this test - **Test app not found**: List available apps and ask user to clarify - **Packed tarballs missing**: Run `yarn build:tarball` at the repo root, then `yarn test:prepare` in `dev-packages/e2e-tests` - **Build failures**: Fix build errors before running tests -- **pnpm errors such as `packages field missing or empty`**: The app needs the pnpm version in its `volta.pnpm`. Run with Volta, which installs that version +- **`ERR_PNPM_IGNORED_BUILDS`**: A dependency has a build script that the app's `allowBuilds` does not list. Add it to the app's `pnpm-workspace.yaml`: `true` if the app needs the script, `false` if the script only prints a message. `pnpm approve-builds ` (or `!` for `false`) in the app's temp copy writes the entry there, so copy it back to the app ## Common Test Applications diff --git a/.cursor/environment.json b/.cursor/environment.json index 0828f53fbd4a..3a181d2068ec 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -1,11 +1,11 @@ { "name": "Sentry JavaScript SDK Development", - "install": "curl https://get.volta.sh | bash && export VOLTA_HOME=\"$HOME/.volta\" && export PATH=\"$VOLTA_HOME/bin:$PATH\" && export VOLTA_FEATURE_PNPM=1 && yarn install", - "start": "export VOLTA_HOME=\"$HOME/.volta\" && export PATH=\"$VOLTA_HOME/bin:$PATH\" && export VOLTA_FEATURE_PNPM=1", + "install": "curl https://get.volta.sh | bash && export VOLTA_HOME=\"$HOME/.volta\" && export PATH=\"$VOLTA_HOME/bin:$PATH\" && yarn install", + "start": "export VOLTA_HOME=\"$HOME/.volta\" && export PATH=\"$VOLTA_HOME/bin:$PATH\"", "terminals": [ { "name": "Development", - "command": "export VOLTA_HOME=\"$HOME/.volta\" && export PATH=\"$VOLTA_HOME/bin:$PATH\" && export VOLTA_FEATURE_PNPM=1 && echo 'Volta setup complete. Node version:' && node --version && echo 'Yarn version:' && yarn --version", + "command": "export VOLTA_HOME=\"$HOME/.volta\" && export PATH=\"$VOLTA_HOME/bin:$PATH\" && echo 'Volta setup complete. Node version:' && node --version && echo 'Yarn version:' && yarn --version", "description": "Main development terminal with Volta environment configured" } ] diff --git a/.github/actions/install-pnpm/action.yml b/.github/actions/install-pnpm/action.yml index d2d28a09dbb4..9a7f2b45f132 100644 --- a/.github/actions/install-pnpm/action.yml +++ b/.github/actions/install-pnpm/action.yml @@ -1,30 +1,11 @@ name: 'Install pnpm' -description: 'Installs the pnpm version that Volta pins for a package.' -inputs: - package-json: - description: 'The package.json to read `volta.pnpm` from. `volta.extends` is followed until a version is found.' - default: 'package.json' +description: 'Puts the pnpm version from the root devDependencies on PATH. Run it after the dependencies are installed.' runs: using: 'composite' steps: - - name: Resolve pnpm version - id: pnpm-version + # The dev dependency is the one pnpm version for CI and local runs. An app that needs another version sets + # `packageManager` in its package.json, and pnpm switches to that version. + - name: Add pnpm to PATH shell: bash - env: - PACKAGE_JSON: ${{ inputs.package-json }} - # Keep in sync with `getPnpmMajor` in `dev-packages/e2e-tests/lib/pnpmOverrides.ts`. It writes the overrides in - # the format of the version it finds, so a different lookup breaks the install. - run: | - package_json="$PACKAGE_JSON" - until version=$(jq -er '.volta.pnpm' "$package_json"); do - extends=$(jq -er '.volta.extends' "$package_json") || { - echo "::error::No volta.pnpm in $PACKAGE_JSON or the files it extends" - exit 1 - } - package_json="$(dirname "$package_json")/$extends" - done - echo "version=$version" >> "$GITHUB_OUTPUT" - - uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 - with: - version: ${{ steps.pnpm-version.outputs.version }} + run: echo "$GITHUB_WORKSPACE/node_modules/pnpm" >> "$GITHUB_PATH" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb05f34f6df2..468a71ae3bb7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -895,9 +895,6 @@ jobs: uses: actions/checkout@v7 with: ref: ${{ env.HEAD_COMMIT }} - # The fixtures install the locally-packed `@sentry/bundler-plugins` tarball with pnpm. - - name: Install pnpm - uses: ./.github/actions/install-pnpm - name: Set up Node uses: actions/setup-node@v7 with: @@ -1059,10 +1056,6 @@ jobs: uses: actions/checkout@v7 with: ref: ${{ env.HEAD_COMMIT }} - - name: Install pnpm - uses: ./.github/actions/install-pnpm - with: - package-json: 'dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}/package.json' - name: Set up Node uses: actions/setup-node@v7 with: @@ -1089,6 +1082,8 @@ jobs: uses: ./.github/actions/restore-cache with: dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Install pnpm + uses: ./.github/actions/install-pnpm - name: Restore tarball artifacts uses: actions/download-artifact@v8 @@ -1212,10 +1207,6 @@ jobs: uses: actions/checkout@v7 with: ref: ${{ env.HEAD_COMMIT }} - - name: Install pnpm - uses: ./.github/actions/install-pnpm - with: - package-json: 'dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}/package.json' - name: Set up Node uses: actions/setup-node@v7 with: @@ -1236,6 +1227,8 @@ jobs: uses: ./.github/actions/restore-cache with: dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Install pnpm + uses: ./.github/actions/install-pnpm - name: Restore tarball artifacts uses: actions/download-artifact@v8 diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index f3ddfbf71bd8..aa19b79d0056 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -117,10 +117,6 @@ jobs: uses: actions/checkout@v7 with: ref: ${{ env.HEAD_COMMIT }} - - name: Install pnpm - uses: ./.github/actions/install-pnpm - with: - package-json: 'dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}/package.json' - name: Set up Node uses: actions/setup-node@v7 with: @@ -131,6 +127,8 @@ jobs: with: path: ${{ env.CACHED_BUILD_PATHS }} key: canary-${{ env.HEAD_COMMIT }} + - name: Install pnpm + uses: ./.github/actions/install-pnpm - name: Prepare e2e tests run: yarn test:prepare diff --git a/.github/workflows/lighthouse.yml b/.github/workflows/lighthouse.yml index e67fa7c7cd66..644d6d04a913 100644 --- a/.github/workflows/lighthouse.yml +++ b/.github/workflows/lighthouse.yml @@ -19,12 +19,12 @@ jobs: VITE_E2E_TEST_DSN: 'https://username@domain/123' steps: - uses: actions/checkout@v7 - - name: Install pnpm - uses: ./.github/actions/install-pnpm - uses: actions/setup-node@v7 with: node-version-file: 'package.json' - run: yarn install --frozen-lockfile --ignore-engines + - name: Install pnpm + uses: ./.github/actions/install-pnpm - run: yarn build:ci - run: yarn build:tarball - run: yarn test:prepare diff --git a/AGENTS.md b/AGENTS.md index 8d307c72d452..037ae6b0d5d6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,8 +4,7 @@ Monorepo with 40+ packages in `@sentry/*`, managed with Yarn workspaces and Nx. ## Setup -- [Volta](https://volta.sh/) for Node.js/Yarn/PNPM version management -- Requires `VOLTA_FEATURE_PNPM=1` +- [Volta](https://volta.sh/) for Node.js/Yarn version management. pnpm (for E2E tests) is a root dev dependency - After cloning: `yarn install && yarn build` - Never change Volta, Yarn, or package manager versions unless explicitly asked diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a684c338ace3..5cf300b0a086 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,10 +14,8 @@ Developer Documentation. ## Setting up an Environment -We use [Volta](https://volta.sh/) to ensure we use consistent versions of node, yarn and pnpm. - -Make sure to also enable [pnpm support in Volta](https://docs.volta.sh/advanced/pnpm) if you want to run the E2E tests -locally. +We use [Volta](https://volta.sh/) to ensure we use consistent versions of node and yarn. `yarn install` also installs +the pnpm version that the E2E tests use. `sentry-javascript` is a monorepo containing several packages, managed with Yarn workspaces and Nx. To get started, install all dependencies, and then perform an initial build, so TypeScript can read all of the linked type definitions. @@ -80,7 +78,8 @@ E2E tests verify SDK behavior in real-world framework scenarios. ### Prerequisites 1. **Docker**: Required for some E2E tests -2. **Volta with pnpm support**: Enable pnpm in Volta by setting `VOLTA_FEATURE_PNPM=1` in your environment. See [Volta pnpm docs](https://docs.volta.sh/advanced/pnpm). +2. **Volta**: The E2E tests run each app on the node version that it pins. Without Volta, they use your current node + version. ### Step-by-Step Instructions diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json index d7131b0282c7..9a48e4fc081b 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json @@ -6,12 +6,5 @@ "dependencies": { "esbuild": "0.28.0", "@sentry/bundler-plugins": "11.8.0" - }, - "pnpm": { - "overrides": { - "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz", - "@sentry/core": "file:../../../../packages/core/sentry-core-11.8.0.tgz", - "sentry": "file:../sentry-stub" - } } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/pnpm-workspace.yaml b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/pnpm-workspace.yaml new file mode 100644 index 000000000000..4e17e5e78c85 --- /dev/null +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 +overrides: + '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz' + '@sentry/core': 'file:../../../../packages/core/sentry-core-11.8.0.tgz' + 'sentry': 'file:../sentry-stub' diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json index d99807df2cab..c2f541411809 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json @@ -7,12 +7,5 @@ "@sentry/bundler-plugins": "11.8.0", "react": "19.2.4", "rolldown": "1.0.0" - }, - "pnpm": { - "overrides": { - "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz", - "@sentry/core": "file:../../../../packages/core/sentry-core-11.8.0.tgz", - "sentry": "file:../sentry-stub" - } } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/pnpm-workspace.yaml b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/pnpm-workspace.yaml new file mode 100644 index 000000000000..7544b21efe12 --- /dev/null +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +minimumReleaseAge: 0 +overrides: + '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz' + '@sentry/core': 'file:../../../../packages/core/sentry-core-11.8.0.tgz' + 'sentry': 'file:../sentry-stub' diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json index 01822b70e67e..54a11d75e287 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json @@ -9,12 +9,5 @@ "@rollup/plugin-babel": "6.0.4", "@rollup/plugin-node-resolve": "15.2.3", "@sentry/bundler-plugins": "11.8.0" - }, - "pnpm": { - "overrides": { - "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz", - "@sentry/core": "file:../../../../packages/core/sentry-core-11.8.0.tgz", - "sentry": "file:../sentry-stub" - } } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/pnpm-workspace.yaml b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/pnpm-workspace.yaml new file mode 100644 index 000000000000..7544b21efe12 --- /dev/null +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +minimumReleaseAge: 0 +overrides: + '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz' + '@sentry/core': 'file:../../../../packages/core/sentry-core-11.8.0.tgz' + 'sentry': 'file:../sentry-stub' diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json index 27dfb230ca77..62df11257c08 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json @@ -9,12 +9,5 @@ "@rollup/plugin-babel": "6.0.4", "@rollup/plugin-node-resolve": "16.0.3", "@sentry/bundler-plugins": "11.8.0" - }, - "pnpm": { - "overrides": { - "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz", - "@sentry/core": "file:../../../../packages/core/sentry-core-11.8.0.tgz", - "sentry": "file:../sentry-stub" - } } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/pnpm-workspace.yaml b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/pnpm-workspace.yaml new file mode 100644 index 000000000000..7544b21efe12 --- /dev/null +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +minimumReleaseAge: 0 +overrides: + '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz' + '@sentry/core': 'file:../../../../packages/core/sentry-core-11.8.0.tgz' + 'sentry': 'file:../sentry-stub' diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json index 8a191768e0e2..d67974b71dec 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json @@ -8,12 +8,5 @@ "vite": "4.5.14", "@vitejs/plugin-react": "5.2.0", "@sentry/bundler-plugins": "11.8.0" - }, - "pnpm": { - "overrides": { - "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz", - "@sentry/core": "file:../../../../packages/core/sentry-core-11.8.0.tgz", - "sentry": "file:../sentry-stub" - } } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/pnpm-workspace.yaml b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/pnpm-workspace.yaml new file mode 100644 index 000000000000..4e17e5e78c85 --- /dev/null +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 +overrides: + '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz' + '@sentry/core': 'file:../../../../packages/core/sentry-core-11.8.0.tgz' + 'sentry': 'file:../sentry-stub' diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json index 62c3d372beb5..c25f754eaf7a 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json @@ -6,12 +6,5 @@ "dependencies": { "vite": "6.4.1", "@sentry/bundler-plugins": "11.8.0" - }, - "pnpm": { - "overrides": { - "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz", - "@sentry/core": "file:../../../../packages/core/sentry-core-11.8.0.tgz", - "sentry": "file:../sentry-stub" - } } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/pnpm-workspace.yaml b/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/pnpm-workspace.yaml new file mode 100644 index 000000000000..4e17e5e78c85 --- /dev/null +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 +overrides: + '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz' + '@sentry/core': 'file:../../../../packages/core/sentry-core-11.8.0.tgz' + 'sentry': 'file:../sentry-stub' diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json index c2ea3fafea8c..3a8e666c0dba 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json @@ -8,12 +8,5 @@ "vite": "7.3.1", "@vitejs/plugin-react": "5.2.0", "@sentry/bundler-plugins": "11.8.0" - }, - "pnpm": { - "overrides": { - "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz", - "@sentry/core": "file:../../../../packages/core/sentry-core-11.8.0.tgz", - "sentry": "file:../sentry-stub" - } } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/pnpm-workspace.yaml b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/pnpm-workspace.yaml new file mode 100644 index 000000000000..4e17e5e78c85 --- /dev/null +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 +overrides: + '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz' + '@sentry/core': 'file:../../../../packages/core/sentry-core-11.8.0.tgz' + 'sentry': 'file:../sentry-stub' diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json index 47ba0a463cb4..38249b7cbfc8 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json @@ -8,12 +8,5 @@ "vite": "8.0.16", "@vitejs/plugin-react": "6.0.1", "@sentry/bundler-plugins": "11.8.0" - }, - "pnpm": { - "overrides": { - "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz", - "@sentry/core": "file:../../../../packages/core/sentry-core-11.8.0.tgz", - "sentry": "file:../sentry-stub" - } } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/pnpm-workspace.yaml b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/pnpm-workspace.yaml new file mode 100644 index 000000000000..7544b21efe12 --- /dev/null +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +minimumReleaseAge: 0 +overrides: + '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz' + '@sentry/core': 'file:../../../../packages/core/sentry-core-11.8.0.tgz' + 'sentry': 'file:../sentry-stub' diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json index 5078c362c088..885d0a4b7f86 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json @@ -9,12 +9,5 @@ "webpack-cli": "6.0.1", "@babel/preset-react": "7.23.3", "@sentry/bundler-plugins": "11.8.0" - }, - "pnpm": { - "overrides": { - "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz", - "@sentry/core": "file:../../../../packages/core/sentry-core-11.8.0.tgz", - "sentry": "file:../sentry-stub" - } } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/pnpm-workspace.yaml b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/pnpm-workspace.yaml new file mode 100644 index 000000000000..7544b21efe12 --- /dev/null +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +minimumReleaseAge: 0 +overrides: + '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-11.8.0.tgz' + '@sentry/core': 'file:../../../../packages/core/sentry-core-11.8.0.tgz' + 'sentry': 'file:../sentry-stub' diff --git a/dev-packages/e2e-tests/README.md b/dev-packages/e2e-tests/README.md index 1c1da7180faf..99d99d096604 100644 --- a/dev-packages/e2e-tests/README.md +++ b/dev-packages/e2e-tests/README.md @@ -98,7 +98,7 @@ A variant runs its `build-command` and `assert-command`. Without them, it runs t We build our packages, pack them into tarballs (`yarn build:tarball`), and create symlinks in the `packed/` directory that point to the versioned tarballs. When a test application is run, pnpm overrides are injected into its -`package.json` to pin all `@sentry/*` and `@sentry-internal/*` packages to those local tarballs. This means test apps +`pnpm-workspace.yaml` to pin all `@sentry/*` and `@sentry-internal/*` packages to those local tarballs. This means test apps install the packages as if they were published, but from the local build output instead of a registry. The E2E test script looks for test applications in the `test-applications` folder. These are standalone apps that use @@ -118,8 +118,14 @@ Make sure to add a `test:build` and `test:assert` command to the new app's `pack Sentry packages are automatically resolved to the local build via pnpm overrides injected at test time, so no manual registry configuration is needed. -Apps use the root pnpm version. To use another one, set `volta.pnpm` in the app's `package.json`. Volta (locally) and -CI (`.github/actions/install-pnpm`) both install that version. +Each app has a `pnpm-workspace.yaml` for its pnpm settings, because pnpm ignores the `pnpm` and `resolutions` fields in +`package.json`. Copy it from a similar app. It needs `minimumReleaseAge: 0` and an `allowBuilds` entry for each +dependency with a build script: `true` if the app needs the script, `false` if the script only prints a message. pnpm +fails the install (`ERR_PNPM_IGNORED_BUILDS`) for a build script that `allowBuilds` does not list. + +Apps use the pnpm version from the root `devDependencies`. To use another version, set `packageManager` in the app's +`package.json` (for example `"packageManager": "pnpm@10.34.6"`), and use the settings of that version in its +`pnpm-workspace.yaml`. pnpm then switches to that version, locally and in CI. The lowest supported version is pnpm 10. ### Test apps that deploy a real Cloudflare Worker diff --git a/dev-packages/e2e-tests/lib/pnpmOverrides.ts b/dev-packages/e2e-tests/lib/pnpmOverrides.ts index e9f79d05f71e..9f5dd88a9ea8 100644 --- a/dev-packages/e2e-tests/lib/pnpmOverrides.ts +++ b/dev-packages/e2e-tests/lib/pnpmOverrides.ts @@ -7,83 +7,47 @@ import { fixFileLinkDependencies } from './copyToTemp'; * For a given temp test application directory, add pnpm overrides to pin internal Sentry dependencies to their packed tarballs. * This is used to ensure that the test application uses the correct version of the internal Sentry packages. * - * pnpm 11 requires overrides in pnpm-workspace.yaml, while older versions use - * package.json. A fixture's existing overrides take precedence in both cases. + * pnpm reads overrides only from pnpm-workspace.yaml. A fixture's existing overrides take precedence. * https://pnpm.io/settings/dependency-resolution#overrides * * @param tmpDirPath - The temporary directory path of the test application. * @param packedDirPath - The path to the packed tarballs. - * @param packageNames - The names of the internal Sentry packages to pin to the packed tarballs. */ export async function addPnpmOverrides(tmpDirPath: string, packedDirPath: string): Promise { const packageJsonPath = path.join(tmpDirPath, 'package.json'); - const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')) as { - pnpm?: { overrides?: Record }; - volta?: { extends?: string; pnpm?: string }; - }; + const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')) as { pnpm?: unknown; resolutions?: unknown }; + + // pnpm 11+ ignores these fields, so their settings would silently have no effect. + if (packageJson.pnpm || packageJson.resolutions) { + throw new Error( + `Move the "pnpm" and "resolutions" settings in ${packageJsonPath} to the app's pnpm-workspace.yaml`, + ); + } const packageNames = getPublishedSentryTarballPackageNames(); const overrides = Object.fromEntries( packageNames.map(packageName => [packageName, `file:${packedDirPath}/${packedSymlinkFilename(packageName)}`]), ); - const existingOverrides = packageJson.pnpm?.overrides ?? {}; - fixFileLinkDependencies(existingOverrides); - const mergedOverrides = { ...overrides, ...existingOverrides }; - - const pnpmMajor = await getPnpmMajor(packageJsonPath, packageJson); - - if (pnpmMajor < 11) { - packageJson.pnpm = { ...packageJson.pnpm, overrides: mergedOverrides }; - } else { - // pnpm 11: https://github.com/pnpm/pnpm/issues/11536 - const pnpmWorkspacePath = path.join(tmpDirPath, 'pnpm-workspace.yaml'); - const pnpmWorkspace = await readFile(pnpmWorkspacePath, 'utf8').catch(() => ''); - const existingWorkspaceOverrides = parseWorkspaceOverrides(pnpmWorkspace); - const workspaceOverrides = formatWorkspaceOverrides({ ...mergedOverrides, ...existingWorkspaceOverrides }); - const workspaceWithoutOverrides = removeWorkspaceOverrides(pnpmWorkspace); + const pnpmWorkspacePath = path.join(tmpDirPath, 'pnpm-workspace.yaml'); + const pnpmWorkspace = await readFile(pnpmWorkspacePath, 'utf8').catch(() => ''); - await writeFile(pnpmWorkspacePath, `${workspaceWithoutOverrides.trimEnd()}\noverrides:\n${workspaceOverrides}\n`); - - delete packageJson.pnpm?.overrides; - if (packageJson.pnpm && Object.keys(packageJson.pnpm).length === 0) { - delete packageJson.pnpm; - } + // Without it, pnpm skips versions published in the last day, which apps that test the newest releases need. + if (!/^minimumReleaseAge: 0$/m.test(pnpmWorkspace)) { + throw new Error(`Set "minimumReleaseAge: 0" in the app's pnpm-workspace.yaml (${pnpmWorkspacePath})`); } - await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); + const existingOverrides = parseWorkspaceOverrides(pnpmWorkspace); + fixFileLinkDependencies(existingOverrides); + const workspaceOverrides = formatWorkspaceOverrides({ ...overrides, ...existingOverrides }); + const workspaceWithoutOverrides = removeWorkspaceOverrides(pnpmWorkspace); + + await writeFile(pnpmWorkspacePath, `${workspaceWithoutOverrides.trimEnd()}\noverrides:\n${workspaceOverrides}\n`); // oxlint-disable-next-line no-console console.log(`Added ${packageNames.length} internal Sentry package overrides`); } -// Keep in sync with `.github/actions/install-pnpm`, which installs the version this finds in CI. -async function getPnpmMajor( - packageJsonPath: string, - packageJson: { volta?: { extends?: string; pnpm?: string } }, -): Promise { - const pnpmVersion = packageJson.volta?.pnpm; - if (pnpmVersion) { - const major = /^(?\d+)(?:\.|$)/.exec(pnpmVersion)?.groups?.major; - if (!major) { - throw new Error(`Invalid Volta pnpm version in ${packageJsonPath}: ${pnpmVersion}`); - } - - return Number(major); - } - - const voltaExtends = packageJson.volta?.extends; - if (voltaExtends) { - const extendedPackageJsonPath = path.resolve(path.dirname(packageJsonPath), voltaExtends); - const extendedPackageJson = JSON.parse(await readFile(extendedPackageJsonPath, 'utf8')) as { - volta?: { extends?: string; pnpm?: string }; - }; - return getPnpmMajor(extendedPackageJsonPath, extendedPackageJson); - } - - throw new Error(`No Volta pnpm version found for ${packageJsonPath}`); -} - function parseWorkspaceOverrides(workspace: string): Record { const overrides = /^overrides:\n((?:[ \t]+.*(?:\n|$))*)/m.exec(workspace)?.[1]; if (!overrides) { @@ -95,16 +59,20 @@ function parseWorkspaceOverrides(workspace: string): Record { .trim() .split('\n') .map(line => { - const match = /^\s*("(?:[^"\\]|\\.)*"):\s*("(?:[^"\\]|\\.)*")\s*$/.exec(line); + const match = /^\s*('(?:[^']|'')*'|"(?:[^"\\]|\\.)*"):\s*('(?:[^']|'')*'|"(?:[^"\\]|\\.)*")\s*$/.exec(line); if (!match?.[1] || !match[2]) { throw new Error(`Unsupported pnpm workspace override: ${line.trim()}`); } - return [JSON.parse(match[1]) as string, JSON.parse(match[2]) as string]; + return [parseYamlString(match[1]), parseYamlString(match[2])]; }), ); } +function parseYamlString(quoted: string): string { + return quoted.startsWith("'") ? quoted.slice(1, -1).replace(/''/g, "'") : (JSON.parse(quoted) as string); +} + function removeWorkspaceOverrides(workspace: string): string { return workspace.replace(/^overrides:\n(?:[ \t]+.*(?:\n|$))*/m, ''); } diff --git a/dev-packages/e2e-tests/run.ts b/dev-packages/e2e-tests/run.ts index f3a7f115925e..3343f632a115 100644 --- a/dev-packages/e2e-tests/run.ts +++ b/dev-packages/e2e-tests/run.ts @@ -1,10 +1,10 @@ /* eslint-disable no-console */ -import { spawn } from 'child_process'; +import { execFileSync, spawn } from 'child_process'; import * as dotenv from 'dotenv'; import { mkdir, mkdtemp, readFile, rm } from 'fs/promises'; import { sync as globSync } from 'glob'; import { tmpdir } from 'os'; -import { join, resolve } from 'path'; +import { delimiter, dirname, join, resolve } from 'path'; import { copyToTemp } from './lib/copyToTemp'; import { applyRuntimeFiles } from './lib/runtimeFiles'; import { syncPackedTarballSymlinks } from './lib/syncPackedTarballSymlinks'; @@ -128,6 +128,23 @@ async function getVariantBuildCommand( }; } +/** + * The directory of the node version that the app pins. CI uses the same pin (see `node-version-file` in + * .github/workflows/build.yml). Only node comes from Volta: Volta cannot run pnpm 12 + * (https://github.com/volta-cli/volta/issues/2114), so pnpm comes from the root devDependencies. + */ +function getPinnedNodeDir(cwd: string): string | undefined { + try { + return dirname(execFileSync('volta', ['run', 'node', '-p', 'process.execPath'], { cwd, encoding: 'utf8' }).trim()); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + throw error; + } + console.warn(`Volta is not installed, so ${cwd} runs on node ${process.version} instead of the version it pins.`); + return undefined; + } +} + async function run(): Promise { // Load environment variables from .env file locally dotenv.config(); @@ -193,17 +210,7 @@ async function run(): Promise { SENTRY_E2E_WORKSPACE_ROOT: resolve(__dirname, '../..'), }; - const env = { - ...process.env, - ...envVarsToInject, - // Volta applies a project's node pin only to commands it manages, and it - // manages pnpm only when this is set. Without it, the `volta run pnpm` - // calls below build each app on whatever node is already on PATH rather - // than the version its package.json pins. CI reads that pin directly - // (see `node-version-file` in .github/workflows/build.yml), so leaving - // this unset makes local runs fail on apps CI passes. - VOLTA_FEATURE_PNPM: '1', - }; + const env = { ...process.env, ...envVarsToInject }; console.log('Syncing packed tarball symlinks...'); console.log(''); @@ -213,7 +220,9 @@ async function run(): Promise { console.log('Cleaning test-applications...'); console.log(''); - await asyncExec('pnpm clean:test-applications', { env, cwd: __dirname }); + // Not `pnpm clean:test-applications`: pnpm checks a package's dependencies before it runs a script, + // and it would install this yarn workspace package with pnpm. + await asyncExec('yarn clean:test-applications', { env, cwd: __dirname }); await asyncExec('pnpm cache delete "@sentry/*"', { env, cwd: __dirname }); const testAppPaths = appName ? [appName.trim()] : globSync('*', { cwd: `${__dirname}/test-applications/` }); @@ -258,10 +267,16 @@ async function run(): Promise { applyRuntimeFiles(tmpDirPath, runtime); } - const appEnv = { ...env, SENTRY_CONFIG_DIR: sentryConfigDir, ...(runtime ? { RUNTIME: runtime } : {}) }; + const nodeDir = getPinnedNodeDir(cwd); + const appEnv = { + ...env, + SENTRY_CONFIG_DIR: sentryConfigDir, + ...(runtime ? { RUNTIME: runtime } : {}), + ...(nodeDir ? { PATH: `${nodeDir}${delimiter}${process.env.PATH}` } : {}), + }; console.log(`Building ${testLabel} in ${tmpDirPath}...`); - await asyncExec(`volta run ${buildCommand}`, { env: appEnv, cwd }); + await asyncExec(buildCommand, { env: appEnv, cwd }); console.log(`Testing ${testLabel}...`); // Pass command as a string to support shell features (env vars, operators like &&) @@ -275,7 +290,7 @@ async function run(): Promise { } return flag; }); - const testCommand = `volta run ${assertCommand}${quotedTestFlags.length > 0 ? ` ${quotedTestFlags.join(' ')}` : ''}`; + const testCommand = `${assertCommand}${quotedTestFlags.length > 0 ? ` ${quotedTestFlags.join(' ')}` : ''}`; await asyncExec(testCommand, { env: appEnv, cwd }); // clean up (although this is tmp, still nice to do) diff --git a/dev-packages/e2e-tests/test-applications/angular-17/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/angular-17/pnpm-workspace.yaml new file mode 100644 index 000000000000..3a0f47e45c8c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/angular-17/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + nice-napi: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/angular-18/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/angular-18/pnpm-workspace.yaml new file mode 100644 index 000000000000..a34120c040e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/angular-18/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + lmdb: true + msgpackr-extract: true + nice-napi: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/angular-19/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/angular-19/pnpm-workspace.yaml new file mode 100644 index 000000000000..5e971cb1e6b4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/angular-19/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true + lmdb: true + msgpackr-extract: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/angular-20/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/angular-20/pnpm-workspace.yaml new file mode 100644 index 000000000000..5e971cb1e6b4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/angular-20/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true + lmdb: true + msgpackr-extract: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/angular-21/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/angular-21/pnpm-workspace.yaml new file mode 100644 index 000000000000..5e971cb1e6b4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/angular-21/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true + lmdb: true + msgpackr-extract: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/angular-22/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/angular-22/pnpm-workspace.yaml new file mode 100644 index 000000000000..5e971cb1e6b4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/angular-22/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true + lmdb: true + msgpackr-extract: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/astro-4/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/astro-4/pnpm-workspace.yaml new file mode 100644 index 000000000000..8e0117ebb801 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-4/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + sharp: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/pnpm-workspace.yaml new file mode 100644 index 000000000000..dd39420094df --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-5-cf-workers/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + esbuild: true + sharp: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/astro-5/package.json b/dev-packages/e2e-tests/test-applications/astro-5/package.json index 1836d478530d..5ff8c7d67cb8 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/package.json +++ b/dev-packages/e2e-tests/test-applications/astro-5/package.json @@ -19,11 +19,6 @@ "@sentry/astro": "file:../../packed/sentry-astro-packed.tgz", "astro": "^5.0.3" }, - "pnpm": { - "overrides": { - "esbuild": "0.24.0" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/astro-5/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/astro-5/pnpm-workspace.yaml new file mode 100644 index 000000000000..77e59480ba4a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-5/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + sharp: true +minimumReleaseAge: 0 +overrides: + 'esbuild': '0.24.0' diff --git a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/pnpm-workspace.yaml new file mode 100644 index 000000000000..dd39420094df --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + esbuild: true + sharp: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/astro-6/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/astro-6/pnpm-workspace.yaml new file mode 100644 index 000000000000..8e0117ebb801 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-6/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + sharp: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/astro-7-sourcemaps/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/astro-7-sourcemaps/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-7-sourcemaps/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/astro-7-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/astro-7-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-7-static/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/astro-7/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/astro-7/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-7/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-layer/package.json b/dev-packages/e2e-tests/test-applications/aws-serverless-layer/package.json index 21d7a32ade83..b326bcb1bde7 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-layer/package.json +++ b/dev-packages/e2e-tests/test-applications/aws-serverless-layer/package.json @@ -10,7 +10,6 @@ "test:build": "pnpm test:pull-sam-image && pnpm install && npx rimraf node_modules/@sentry/aws-serverless/nodejs", "test:assert": "pnpm test" }, - "//": "We just need the @sentry/aws-serverless layer zip file, not the NPM package", "devDependencies": { "@aws-sdk/client-lambda": "^3.863.0", "@playwright/test": "~1.63.0", @@ -24,12 +23,6 @@ "volta": { "extends": "../../package.json" }, - "//": "We need to override this here again to ensure this is not overwritten by the test runner", - "pnpm": { - "overrides": { - "@sentry/aws-serverless": "link:../../../../packages/aws-serverless/build/aws/dist-serverless/" - } - }, "sentryTest": { "variants": [ { diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-layer/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/aws-serverless-layer/pnpm-workspace.yaml new file mode 100644 index 000000000000..53a0ec757223 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless-layer/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +minimumReleaseAge: 0 +# Use the Lambda layer build, not the packed npm tarball. The test runner keeps an app's own overrides. +overrides: + '@sentry/aws-serverless': 'link:../../../../packages/aws-serverless/build/aws/dist-serverless/' diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/aws-serverless/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts index c4eb3658afd0..6b0b980ae5c5 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts @@ -90,14 +90,13 @@ export class LocalLambdaStack extends Stack { fs.rmSync(nodeModulesPath, { recursive: true, force: true }); } - const packageJson = { - dependencies, - pnpm: { - overrides: dependencies, - }, - }; - - fs.writeFileSync(path.join(lambdaPath, 'package.json'), JSON.stringify(packageJson, null, 2)); + fs.writeFileSync(path.join(lambdaPath, 'package.json'), JSON.stringify({ dependencies }, null, 2)); + // JSON is valid YAML. pnpm reads overrides only from pnpm-workspace.yaml, and this file also keeps + // pnpm from treating the app's own pnpm-workspace.yaml as the workspace root. + fs.writeFileSync( + path.join(lambdaPath, 'pnpm-workspace.yaml'), + JSON.stringify({ minimumReleaseAge: 0, overrides: dependencies }, null, 2), + ); execFileSync( 'pnpm', ['install', '--offline', '--prod', '--ignore-scripts', '--no-frozen-lockfile', '--config.node-linker=hoisted'], diff --git a/dev-packages/e2e-tests/test-applications/browser-bfcache/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/browser-bfcache/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-bfcache/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/browser-mfe-vite/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/browser-mfe-vite/pnpm-workspace.yaml index 8ab3e17a0de1..fdf1f588f030 100644 --- a/dev-packages/e2e-tests/test-applications/browser-mfe-vite/pnpm-workspace.yaml +++ b/dev-packages/e2e-tests/test-applications/browser-mfe-vite/pnpm-workspace.yaml @@ -1,2 +1,5 @@ +allowBuilds: + esbuild: true packages: - 'apps/*' +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/browser-webworker-vite/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/browser-webworker-vite/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-webworker-vite/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/bun-bytecode/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/bun-bytecode/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-bytecode/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/bun-express/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/bun-express/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-express/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/bun-mysql/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/bun-mysql/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-mysql/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-agent/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-agent/pnpm-workspace.yaml new file mode 100644 index 000000000000..b3722fc953bb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-agent/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + core-js-pure: false + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument/pnpm-workspace.yaml new file mode 100644 index 000000000000..b3722fc953bb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + core-js-pure: false + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-flue-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-flue-send-to-sentry/pnpm-workspace.yaml new file mode 100644 index 000000000000..9f5534f26e55 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-flue-send-to-sentry/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +allowBuilds: + '@google/genai': false + core-js-pure: false + esbuild: true + protobufjs: false + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-local-workers/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-local-workers/package.json index c2aa8fbb7bdc..79f1b08a2fe9 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-local-workers/package.json +++ b/dev-packages/e2e-tests/test-applications/cloudflare-local-workers/package.json @@ -26,10 +26,5 @@ }, "volta": { "extends": "../../package.json" - }, - "pnpm": { - "overrides": { - "strip-literal": "~2.0.0" - } } } diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-local-workers/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-local-workers/pnpm-workspace.yaml new file mode 100644 index 000000000000..42f540fbb7fd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-local-workers/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 +overrides: + 'strip-literal': '~2.0.0' diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-mastra/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-mastra/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-mastra/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-mcp-agent/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-mcp-agent/pnpm-workspace.yaml new file mode 100644 index 000000000000..b3722fc953bb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-mcp-agent/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + core-js-pure: false + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-mcp/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-mcp/pnpm-workspace.yaml new file mode 100644 index 000000000000..b3722fc953bb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-mcp/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + core-js-pure: false + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-pi-durable/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-pi-durable/pnpm-workspace.yaml new file mode 100644 index 000000000000..9f5534f26e55 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-pi-durable/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +allowBuilds: + '@google/genai': false + core-js-pure: false + esbuild: true + protobufjs: false + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-think/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-think/pnpm-workspace.yaml new file mode 100644 index 000000000000..7ebb82eba498 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-think/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +allowBuilds: + '@mongodb-js/zstd': true + core-js-pure: false + esbuild: true + node-liblzma: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers-send-to-sentry/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers-static/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-workers-static/package.json index 8a844eb74fc8..0f638b37b1c8 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-workers-static/package.json +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers-static/package.json @@ -26,10 +26,5 @@ }, "volta": { "extends": "../../package.json" - }, - "pnpm": { - "overrides": { - "strip-literal": "~2.0.0" - } } } diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-workers-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..42f540fbb7fd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers-static/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 +overrides: + 'strip-literal': '~2.0.0' diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers-workflow-legacy/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-workers-workflow-legacy/pnpm-workspace.yaml new file mode 100644 index 000000000000..dd39420094df --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers-workflow-legacy/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + esbuild: true + sharp: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-workers/package.json index e1889148b604..df197335a95c 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-workers/package.json +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers/package.json @@ -26,10 +26,5 @@ }, "volta": { "extends": "../../package.json" - }, - "pnpm": { - "overrides": { - "strip-literal": "~2.0.0" - } } } diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-workers/pnpm-workspace.yaml new file mode 100644 index 000000000000..42f540fbb7fd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 +overrides: + 'strip-literal': '~2.0.0' diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workersentrypoint/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-workersentrypoint/package.json index 56a59febe7f3..0fbdd136e308 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-workersentrypoint/package.json +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workersentrypoint/package.json @@ -26,10 +26,5 @@ }, "volta": { "extends": "../../package.json" - }, - "pnpm": { - "overrides": { - "strip-literal": "~2.0.0" - } } } diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workersentrypoint/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/cloudflare-workersentrypoint/pnpm-workspace.yaml new file mode 100644 index 000000000000..42f540fbb7fd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workersentrypoint/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 +overrides: + 'strip-literal': '~2.0.0' diff --git a/dev-packages/e2e-tests/test-applications/create-react-app/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/create-react-app/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-react-app/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/pnpm-workspace.yaml new file mode 100644 index 000000000000..c0cf0c99071e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express-vite-dev/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/create-remix-app-express/pnpm-workspace.yaml new file mode 100644 index 000000000000..c0cf0c99071e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/package.json index ae5a86e26839..d8116f81daa9 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/package.json @@ -7,7 +7,7 @@ "start": "NODE_OPTIONS='--import=./instrument.server.cjs' remix-serve build/index.js", "typecheck": "tsc", "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install && npx playwright install && pnpm build", + "test:build": "pnpm install && pnpm build", "test:assert": "pnpm playwright test" }, "dependencies": { @@ -30,9 +30,6 @@ "eslint": "^8.38.0", "typescript": "^5.0.4" }, - "resolutions": { - "@types/react": "18.2.22" - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/pnpm-workspace.yaml new file mode 100644 index 000000000000..1858a5f1d489 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-non-vite/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + unrs-resolver: true +minimumReleaseAge: 0 +overrides: + '@types/react': '18.2.22' diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/package.json index 0bce531687fb..1fbc04eb7745 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/package.json @@ -37,9 +37,6 @@ "vite": "^5.4.11", "vite-tsconfig-paths": "^4.2.1" }, - "resolutions": { - "@types/react": "18.2.22" - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..1858a5f1d489 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2-static/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + unrs-resolver: true +minimumReleaseAge: 0 +overrides: + '@types/react': '18.2.22' diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json index 0bce531687fb..1fbc04eb7745 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json @@ -37,9 +37,6 @@ "vite": "^5.4.11", "vite-tsconfig-paths": "^4.2.1" }, - "resolutions": { - "@types/react": "18.2.22" - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/pnpm-workspace.yaml new file mode 100644 index 000000000000..1858a5f1d489 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + unrs-resolver: true +minimumReleaseAge: 0 +overrides: + '@types/react': '18.2.22' diff --git a/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/default-browser-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/default-browser-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser-static/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/default-browser/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/default-browser/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/default-browser/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/deno/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/deno/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/deno/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/effect-3-browser/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/effect-3-browser/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/effect-3-browser/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/effect-3-node/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/effect-3-node/pnpm-workspace.yaml new file mode 100644 index 000000000000..89a3f939bb82 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/effect-3-node/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@parcel/watcher': true + msgpackr-extract: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/effect-4-browser/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/effect-4-browser/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/effect-4-browser/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/effect-4-node/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/effect-4-node/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/effect-4-node/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/elysia-bun-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/elysia-bun-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/elysia-bun-static/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/elysia-bun/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/elysia-bun/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/elysia-bun/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/elysia-node/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/elysia-node/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/elysia-node/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/package.json b/dev-packages/e2e-tests/test-applications/ember-classic/package.json index 87da5f5f5571..3a41cccb46d7 100644 --- a/dev-packages/e2e-tests/test-applications/ember-classic/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-classic/package.json @@ -74,18 +74,9 @@ "engines": { "node": ">=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0" }, - "resolutions": { - "@babel/traverse": "~7.25.9", - "clean-css": "^5.3.0" - }, "ember": { "edition": "octane" }, - "pnpm": { - "overrides": { - "@embroider/macros@1.21.2": "1.21.1" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/ember-classic/pnpm-workspace.yaml new file mode 100644 index 000000000000..8648917fefff --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-classic/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +allowBuilds: + core-js: false +minimumReleaseAge: 0 +overrides: + '@embroider/macros@1.21.2': '1.21.1' + '@babel/traverse': '~7.25.9' + 'clean-css': '^5.3.0' diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json index 67d7d462d9b2..0653d743b450 100644 --- a/dev-packages/e2e-tests/test-applications/ember-embroider/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/package.json @@ -66,11 +66,6 @@ "ember": { "edition": "octane" }, - "pnpm": { - "overrides": { - "@embroider/macros@1.21.2": "1.21.1" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/ember-embroider/pnpm-workspace.yaml new file mode 100644 index 000000000000..d3bb70b68dae --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + core-js: false +minimumReleaseAge: 0 +overrides: + '@embroider/macros@1.21.2': '1.21.1' diff --git a/dev-packages/e2e-tests/test-applications/ember-strict-resolver/package.json b/dev-packages/e2e-tests/test-applications/ember-strict-resolver/package.json index d1230979cc65..7acab343c96c 100644 --- a/dev-packages/e2e-tests/test-applications/ember-strict-resolver/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-strict-resolver/package.json @@ -42,14 +42,6 @@ "ember": { "edition": "octane" }, - "pnpm": { - "overrides": { - "@embroider/vite@1.7.14": "1.7.13", - "@embroider/macros@1.21.2": "1.21.1", - "@embroider/core@4.6.8": "4.6.7", - "@embroider/compat@4.1.26": "4.1.25" - } - }, "volta": { "extends": "../../package.json" }, diff --git a/dev-packages/e2e-tests/test-applications/ember-strict-resolver/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/ember-strict-resolver/pnpm-workspace.yaml new file mode 100644 index 000000000000..cac89f0c0792 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-strict-resolver/pnpm-workspace.yaml @@ -0,0 +1,8 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 +overrides: + '@embroider/vite@1.7.14': '1.7.13' + '@embroider/macros@1.21.2': '1.21.1' + '@embroider/core@4.6.8': '4.6.7' + '@embroider/compat@4.1.26': '4.1.25' diff --git a/dev-packages/e2e-tests/test-applications/ember-vite/package.json b/dev-packages/e2e-tests/test-applications/ember-vite/package.json index c1bcfd114fa5..7106f8aa2a00 100644 --- a/dev-packages/e2e-tests/test-applications/ember-vite/package.json +++ b/dev-packages/e2e-tests/test-applications/ember-vite/package.json @@ -67,14 +67,6 @@ "ember": { "edition": "octane" }, - "pnpm": { - "overrides": { - "@embroider/vite@1.7.14": "1.7.13", - "@embroider/macros@1.21.2": "1.21.1", - "@embroider/core@4.6.8": "4.6.7", - "@embroider/compat@4.1.26": "4.1.25" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/ember-vite/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/ember-vite/pnpm-workspace.yaml new file mode 100644 index 000000000000..cac89f0c0792 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/ember-vite/pnpm-workspace.yaml @@ -0,0 +1,8 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 +overrides: + '@embroider/vite@1.7.14': '1.7.13' + '@embroider/macros@1.21.2': '1.21.1' + '@embroider/core@4.6.8': '4.6.7' + '@embroider/compat@4.1.26': '4.1.25' diff --git a/dev-packages/e2e-tests/test-applications/gen-ai-libraries/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/gen-ai-libraries/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/generic-ts5.0/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/generic-ts5.0/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/generic-ts5.0/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/hono-4-legacy/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/hono-4-legacy/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/hono-4-legacy/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/hono-4/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/hono-4/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/hono-4/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json index 16155eda734c..c31ab136576a 100644 --- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json @@ -9,7 +9,7 @@ "typecheck": "tsc", "codegen": "shopify hydrogen codegen", "clean": "npx rimraf node_modules dist pnpm-lock.yaml", - "test:build": "pnpm install && npx playwright install && pnpm build", + "test:build": "pnpm install && pnpm build", "test:assert": "pnpm playwright test" }, "dependencies": { diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/lighthouse-react/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/lighthouse-react/pnpm-workspace.yaml new file mode 100644 index 000000000000..dafa2e7eca8c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/lighthouse-react/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@swc/core': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11-static/package.json b/dev-packages/e2e-tests/test-applications/nestjs-11-static/package.json index 099dc95b20f8..0b34a8473363 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-11-static/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-11-static/package.json @@ -41,11 +41,6 @@ "tsconfig-paths": "^4.2.0", "typescript": "~5.0.0" }, - "pnpm": { - "overrides": { - "minimatch": "10.0.1" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-11-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..50ece95aba3b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11-static/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 0 +overrides: + 'minimatch': '10.0.1' diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/package.json b/dev-packages/e2e-tests/test-applications/nestjs-11/package.json index 651c628bbb97..68075e0fe6d1 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-11/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/package.json @@ -41,11 +41,6 @@ "tsconfig-paths": "^4.2.0", "typescript": "~5.0.0" }, - "pnpm": { - "overrides": { - "minimatch": "10.0.1" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-11/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-11/pnpm-workspace.yaml new file mode 100644 index 000000000000..50ece95aba3b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-11/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 0 +overrides: + 'minimatch': '10.0.1' diff --git a/dev-packages/e2e-tests/test-applications/nestjs-12/package.json b/dev-packages/e2e-tests/test-applications/nestjs-12/package.json index c7294574fd2e..6a87ba65934a 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-12/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-12/package.json @@ -43,11 +43,6 @@ "tsconfig-paths": "^4.2.0", "typescript": "~5.9.0" }, - "pnpm": { - "overrides": { - "minimatch": "10.0.1" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-12/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-12/pnpm-workspace.yaml new file mode 100644 index 000000000000..50ece95aba3b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-12/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 0 +overrides: + 'minimatch': '10.0.1' diff --git a/dev-packages/e2e-tests/test-applications/nestjs-8/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-8/pnpm-workspace.yaml new file mode 100644 index 000000000000..296f92663081 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-8/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + '@nestjs/core': false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/pnpm-workspace.yaml new file mode 100644 index 000000000000..69ae6bd37c90 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic-with-graphql/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@apollo/protobufjs': false + '@nestjs/core': false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-basic/pnpm-workspace.yaml new file mode 100644 index 000000000000..296f92663081 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + '@nestjs/core': false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-bullmq/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-bullmq/pnpm-workspace.yaml new file mode 100644 index 000000000000..0f973fea7a88 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-bullmq/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + msgpackr-extract: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/pnpm-workspace.yaml new file mode 100644 index 000000000000..296f92663081 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + '@nestjs/core': false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-fastify/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-fastify/pnpm-workspace.yaml new file mode 100644 index 000000000000..296f92663081 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-fastify/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + '@nestjs/core': false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-graphql/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-graphql/pnpm-workspace.yaml new file mode 100644 index 000000000000..69ae6bd37c90 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-graphql/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@apollo/protobufjs': false + '@nestjs/core': false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-microservices/package.json b/dev-packages/e2e-tests/test-applications/nestjs-microservices/package.json index 6858a472c1e8..aa2dc4130883 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-microservices/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-microservices/package.json @@ -27,11 +27,6 @@ "@types/node": "^18.19.1", "typescript": "~5.0.0" }, - "pnpm": { - "overrides": { - "minimatch": "10.0.1" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-microservices/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-microservices/pnpm-workspace.yaml new file mode 100644 index 000000000000..50ece95aba3b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-microservices/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 0 +overrides: + 'minimatch': '10.0.1' diff --git a/dev-packages/e2e-tests/test-applications/nestjs-websockets/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-websockets/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-websockets/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/pnpm-workspace.yaml new file mode 100644 index 000000000000..296f92663081 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules-decorator/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + '@nestjs/core': false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/pnpm-workspace.yaml new file mode 100644 index 000000000000..296f92663081 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + '@nestjs/core': false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-14/pnpm-workspace.yaml new file mode 100644 index 000000000000..109e71d10262 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + sharp: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15-basepath/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-15-basepath/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-15-basepath/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15-intl/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-15-intl/pnpm-workspace.yaml new file mode 100644 index 000000000000..b5321b350e1d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-15-intl/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@parcel/watcher': true + '@swc/core': true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15-t3/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-15-t3/pnpm-workspace.yaml new file mode 100644 index 000000000000..bde9dfb7b138 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-15-t3/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-15/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/client-trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/client-trace-propagation.test.ts index 01fb5af62f05..6e56245508a0 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/client-trace-propagation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/client-trace-propagation.test.ts @@ -2,8 +2,8 @@ import { expect, test } from '@playwright/test'; import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; import { parseSemver } from '@sentry/core'; -const packageJson = require('../package.json'); -const nextjsVersion = packageJson.dependencies.next; +const nextPackageJson = require('next/package.json'); +const nextjsVersion = nextPackageJson.version; const { major, minor } = parseSemver(nextjsVersion); test('Should propagate traces from server to client in pages router', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts index 30704d284b63..eb601a3aca52 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts @@ -1,12 +1,12 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; -const packageJson = require('../package.json'); +const nextPackageJson = require('next/package.json'); test('Should capture errors from nested server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ page, }) => { - const [, minor, patch, canary] = packageJson.dependencies.next.split('.'); + const [, minor, patch, canary] = nextPackageJson.version.split('.'); test.skip( minor === '0' && diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts index 5d2d8d2db9bf..5fd20a1308c8 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts @@ -1,12 +1,12 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; -const packageJson = require('../package.json'); +const nextPackageJson = require('next/package.json'); test('Should capture errors for crashing streaming promises in server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ page, }) => { - const [, minor, patch, canary] = packageJson.dependencies.next.split('.'); + const [, minor, patch, canary] = nextPackageJson.version.split('.'); test.skip( minor === '0' && diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/pnpm-workspace.yaml new file mode 100644 index 000000000000..bde9dfb7b138 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-standalone/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16-standalone/pnpm-workspace.yaml new file mode 100644 index 000000000000..bde9dfb7b138 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-standalone/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..b471a2f773e9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-static/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + sharp: true + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/pnpm-workspace.yaml new file mode 100644 index 000000000000..bde9dfb7b138 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/pnpm-workspace.yaml new file mode 100644 index 000000000000..b471a2f773e9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + sharp: true + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-trailing-slash-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16-trailing-slash-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..109e71d10262 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-trailing-slash-static/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + sharp: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-trailing-slash/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16-trailing-slash/pnpm-workspace.yaml new file mode 100644 index 000000000000..109e71d10262 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-trailing-slash/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + sharp: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/pnpm-workspace.yaml new file mode 100644 index 000000000000..0aaed65978c9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-tunnel/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + esbuild: true + unrs-resolver: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-userfeedback/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16-userfeedback/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-userfeedback/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-16/pnpm-workspace.yaml new file mode 100644 index 000000000000..0aaed65978c9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + esbuild: true + unrs-resolver: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts index 8d6ab7ffbfef..554c41fc3108 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts @@ -2,8 +2,8 @@ import * as fs from 'fs'; import * as path from 'path'; import * as assert from 'assert/strict'; -const packageJson = require('./package.json'); -const nextjsVersion = packageJson.dependencies.next; +const nextPackageJson = require('next/package.json'); +const nextjsVersion = nextPackageJson.version; const buildStdout = fs.readFileSync('.tmp_build_stdout', 'utf-8'); const buildStderr = fs.readFileSync('.tmp_build_stderr', 'utf-8'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/client-errors.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/client-errors.test.ts index 0fef36e3d9fd..70a8cb313b05 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/client-errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/client-errors.test.ts @@ -2,10 +2,10 @@ import { expect, test } from '@playwright/test'; import { waitForError } from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; -const packageJson = require('../package.json'); +const nextPackageJson = require('next/package.json'); test('Sends a client-side exception to Sentry', async ({ page }) => { - const nextjsVersion = packageJson.dependencies.next; + const nextjsVersion = nextPackageJson.version; const nextjsMajor = Number(nextjsVersion.split('.')[0]); await page.goto('/'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts index eadfbf084d32..fe912c78b223 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts @@ -8,10 +8,10 @@ import { } from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; -const packageJson = require('../package.json'); +const nextPackageJson = require('next/package.json'); test('Sends a pageload span', async ({ page }) => { - const nextjsVersion = packageJson.dependencies.next; + const nextjsVersion = nextPackageJson.version; const nextjsMajor = Number(nextjsVersion.split('.')[0]); const pageloadSpanPromise = waitForStreamedSpan('nextjs-app-dir', span => { @@ -46,7 +46,7 @@ test('Sends a pageload span', async ({ page }) => { }); test('Should send a span for instrumented server actions', async ({ page }) => { - const nextjsVersion = packageJson.dependencies.next; + const nextjsVersion = nextPackageJson.version; const nextjsMajor = Number(nextjsVersion.split('.')[0]); test.skip(!isNaN(nextjsMajor) && nextjsMajor < 14, 'only applies to nextjs apps >= version 14'); @@ -70,7 +70,7 @@ test('Should send a span for instrumented server actions', async ({ page }) => { }); test('Should send a wrapped server action as a child of a nextjs span', async ({ page }) => { - const nextjsVersion = packageJson.dependencies.next; + const nextjsVersion = nextPackageJson.version; const nextjsMajor = Number(nextjsVersion.split('.')[0]); test.skip(!isNaN(nextjsMajor) && nextjsMajor < 14, 'only applies to nextjs apps >= version 14'); @@ -105,7 +105,7 @@ test('Should send a wrapped server action as a child of a nextjs span', async ({ }); test('Should set not_found status for server actions calling notFound()', async ({ page }) => { - const nextjsVersion = packageJson.dependencies.next; + const nextjsVersion = nextPackageJson.version; const nextjsMajor = Number(nextjsVersion.split('.')[0]); test.skip(!isNaN(nextjsMajor) && nextjsMajor < 14, 'only applies to nextjs apps >= version 14'); @@ -125,7 +125,7 @@ test('Should set not_found status for server actions calling notFound()', async test('Should not capture "NEXT_REDIRECT" control-flow errors for server actions calling redirect()', async ({ page, }) => { - const nextjsVersion = packageJson.dependencies.next; + const nextjsVersion = nextPackageJson.version; const nextjsMajor = Number(nextjsVersion.split('.')[0]); test.skip(!isNaN(nextjsMajor) && nextjsMajor < 14, 'only applies to nextjs apps >= version 14'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-orpc/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-orpc/pnpm-workspace.yaml new file mode 100644 index 000000000000..bde9dfb7b138 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-orpc/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-otlp/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-otlp/pnpm-workspace.yaml new file mode 100644 index 000000000000..bde9dfb7b138 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-otlp/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/assert-build.ts b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/assert-build.ts index b965e5fcd473..be7beeeb94e6 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/assert-build.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/assert-build.ts @@ -2,8 +2,8 @@ import * as fs from 'fs'; import * as path from 'path'; import * as assert from 'assert/strict'; -const packageJson = require('./package.json'); -const nextjsVersion = packageJson.dependencies.next; +const nextPackageJson = require('next/package.json'); +const nextjsVersion = nextPackageJson.version; const buildStdout = fs.readFileSync('.tmp_build_stdout', 'utf-8'); const buildStderr = fs.readFileSync('.tmp_build_stderr', 'utf-8'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/transactions.test.ts index 2212cba2525f..6529185e4f79 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-pages-dir/tests/transactions.test.ts @@ -2,10 +2,10 @@ import { expect, test } from '@playwright/test'; import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; -const packageJson = require('../package.json'); +const nextPackageJson = require('next/package.json'); test('Sends a pageload span', async ({ page }) => { - const nextjsVersion = packageJson.dependencies.next; + const nextjsVersion = nextPackageJson.version; const nextjsMajor = Number(nextjsVersion.split('.')[0]); const pageloadSpanPromise = waitForStreamedSpan('nextjs-pages-dir', span => { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-sourcemaps/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nextjs-sourcemaps/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-sourcemaps/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nitro-3/package.json b/dev-packages/e2e-tests/test-applications/nitro-3/package.json index cb72ac1e877a..eb77a39f32fb 100644 --- a/dev-packages/e2e-tests/test-applications/nitro-3/package.json +++ b/dev-packages/e2e-tests/test-applications/nitro-3/package.json @@ -23,11 +23,6 @@ "rolldown": "latest", "vite": "latest" }, - "pnpm": { - "overrides": { - "unstorage": "2.0.0-alpha.7" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/nitro-3/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nitro-3/pnpm-workspace.yaml new file mode 100644 index 000000000000..c6eea3e5ab0a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nitro-3/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 0 +overrides: + 'unstorage': '2.0.0-alpha.7' diff --git a/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-anthropic-send-to-sentry/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-esbuild/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-esbuild/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-esbuild/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-eve/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-eve/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-eve/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-exports-test-app/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-without-loader/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-express-mcp-v1/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-mcp-v1/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-mcp-v1/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-express-mcp-v2/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-mcp-v2/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-mcp-v2/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-express-otlp/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-otlp/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-otlp/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry-static/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-express-static/package.json b/dev-packages/e2e-tests/test-applications/node-express-static/package.json index 6ebf88fda7b9..13ca9cd17003 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-static/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-static/package.json @@ -26,9 +26,6 @@ "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "file:../../packed/sentry-core-packed.tgz" }, - "resolutions": { - "@types/qs": "6.9.17" - }, "volta": { "extends": "../../package.json" }, diff --git a/dev-packages/e2e-tests/test-applications/node-express-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..3db4c8f6a559 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-static/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 0 +overrides: + '@types/qs': '6.9.17' diff --git a/dev-packages/e2e-tests/test-applications/node-express-streaming/package.json b/dev-packages/e2e-tests/test-applications/node-express-streaming/package.json index 620ca3edc6ed..f56f070a79d2 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-streaming/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-streaming/package.json @@ -26,9 +26,6 @@ "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "file:../../packed/sentry-core-packed.tgz" }, - "resolutions": { - "@types/qs": "6.9.17" - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/node-express-streaming/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-streaming/pnpm-workspace.yaml new file mode 100644 index 000000000000..3db4c8f6a559 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-streaming/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 0 +overrides: + '@types/qs': '6.9.17' diff --git a/dev-packages/e2e-tests/test-applications/node-express-v5/package.json b/dev-packages/e2e-tests/test-applications/node-express-v5/package.json index 4e5a5d54f108..22c641ff62c0 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-v5/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express-v5/package.json @@ -27,9 +27,6 @@ "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "file:../../packed/sentry-core-packed.tgz" }, - "resolutions": { - "@types/qs": "6.9.17" - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/node-express-v5/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express-v5/pnpm-workspace.yaml new file mode 100644 index 000000000000..3db4c8f6a559 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express-v5/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 0 +overrides: + '@types/qs': '6.9.17' diff --git a/dev-packages/e2e-tests/test-applications/node-express/package.json b/dev-packages/e2e-tests/test-applications/node-express/package.json index 4c150e1155d9..a2710c97e85e 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/package.json +++ b/dev-packages/e2e-tests/test-applications/node-express/package.json @@ -26,9 +26,6 @@ "@sentry-internal/test-utils": "link:../../../test-utils", "@sentry/core": "file:../../packed/sentry-core-packed.tgz" }, - "resolutions": { - "@types/qs": "6.9.17" - }, "volta": { "extends": "../../package.json" }, diff --git a/dev-packages/e2e-tests/test-applications/node-express/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-express/pnpm-workspace.yaml new file mode 100644 index 000000000000..3db4c8f6a559 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-express/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +minimumReleaseAge: 0 +overrides: + '@types/qs': '6.9.17' diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-3/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-fastify-3/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-fastify-3/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-4/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-fastify-4/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-fastify-4/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-5/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-fastify-5/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-fastify-5/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-fastify-6/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-fastify-6/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-fastify-6/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-firebase/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-firebase/pnpm-workspace.yaml index 8a5eb172e019..3caf2f25e80a 100644 --- a/dev-packages/e2e-tests/test-applications/node-firebase/pnpm-workspace.yaml +++ b/dev-packages/e2e-tests/test-applications/node-firebase/pnpm-workspace.yaml @@ -1,3 +1,8 @@ +allowBuilds: + '@firebase/util': true + protobufjs: false + re2: true packages: - 'functions' - 'firestore-app' +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-flue/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-flue/pnpm-workspace.yaml new file mode 100644 index 000000000000..0e523a16e6f0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-flue/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + '@google/genai': false + core-js-pure: false + esbuild: true + protobufjs: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-hapi/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-hapi/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-koa/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-koa/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-koa/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-mastra/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-mastra/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-mastra/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-openai-decisions-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-openai-decisions-send-to-sentry/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-openai-decisions-send-to-sentry/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-openai-send-to-sentry/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/pnpm-workspace.yaml new file mode 100644 index 000000000000..428f42f5ee7b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + protobufjs: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-pi-durable/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-pi-durable/pnpm-workspace.yaml new file mode 100644 index 000000000000..9ca1c8a358ad --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-pi-durable/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + '@google/genai': false + esbuild: true + protobufjs: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-prisma-8/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-prisma-8/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-prisma-8/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-cjs/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/pnpm-workspace.yaml new file mode 100644 index 000000000000..677b3461f270 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-cjs/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@sentry/node-cpu-profiler': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json b/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json index 451edfd3cddb..94f671f13368 100644 --- a/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/package.json @@ -8,7 +8,7 @@ "test:assert": "$(pnpm bin)/electron-rebuild && pnpm playwright test" }, "dependencies": { - "@electron/rebuild": "^3.7.0", + "@electron/rebuild": "^4.2.1", "@playwright/test": "~1.63.0", "@sentry/electron": "latest || *", "@sentry/node": "file:../../packed/sentry-node-packed.tgz", diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-electron/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-profiling-electron/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-electron/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-profiling-esm/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-profiling-esm/pnpm-workspace.yaml new file mode 100644 index 000000000000..677b3461f270 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-profiling-esm/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@sentry/node-cpu-profiler': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-rolldown/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-rolldown/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-rolldown/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-rollup/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-rollup/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-rollup/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-vite/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-vite/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-vite/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/node-webpack/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/node-webpack/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-webpack/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-dynamic-import/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json index 6e3938b70df1..0fb481d42792 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-min/package.json @@ -25,11 +25,6 @@ "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils" }, - "pnpm": { - "overrides": { - "ofetch": "1.4.0" - } - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-min/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nuxt-3-min/pnpm-workspace.yaml new file mode 100644 index 000000000000..e21123e4ce75 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-min/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 +overrides: + 'ofetch': '1.4.0' diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-3-top-level-import/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nuxt-3/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4-cloudflare/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nuxt-4-cloudflare/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-4-cloudflare/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4-sourcemaps/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nuxt-4-sourcemaps/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-4-sourcemaps/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nuxt-4-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..8525919fe522 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-4-static/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + vue-demi: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/nuxt-4/pnpm-workspace.yaml new file mode 100644 index 000000000000..8525919fe522 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + vue-demi: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/package.json b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/package.json index ccc0df377bcd..fd1dedb71d73 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-5-sourcemaps/package.json @@ -26,8 +26,7 @@ "typescript": "~5.0.0" }, "volta": { - "extends": "../../package.json", - "pnpm": "11.10.0" + "extends": "../../package.json" }, "sentryTest": { "optional": true, diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/package.json b/dev-packages/e2e-tests/test-applications/nuxt-5/package.json index 6a38a24d8f3c..6e84b33b97fa 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-5/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/package.json @@ -41,7 +41,6 @@ "optional": true }, "volta": { - "extends": "../../package.json", - "pnpm": "11.10.0" + "extends": "../../package.json" } } diff --git a/dev-packages/e2e-tests/test-applications/react-17-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-17-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-17-static/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-17/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-17/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-17/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-19/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-19/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-19/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-create-browser-router/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-create-browser-router/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-browser-router/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-create-hash-router/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-create-memory-router/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-create-memory-router/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-create-memory-router/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-5/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json index 8cace7f29d8c..b82229297109 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/package.json @@ -20,8 +20,8 @@ "start:server": "serve -s build", "test": "playwright test", "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install && npx playwright install && pnpm build", - "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && npx playwright install && pnpm build", + "test:build": "pnpm install && pnpm build", + "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && pnpm build", "test:assert": "pnpm test" }, "eslintConfig": { diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6-descendant-routes/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-router-entry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-6-router-entry/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6-router-entry/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-6/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/package.json index c6d6a841b464..b2cf23f14583 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/package.json @@ -18,8 +18,8 @@ "start": "serve -s build", "test": "playwright test", "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install && npx playwright install && pnpm build", - "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && npx playwright install && pnpm build", + "test:build": "pnpm install && pnpm build", + "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && pnpm build", "test:assert": "pnpm test" }, "eslintConfig": { diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-cross-usage/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-instrumentation/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-spa/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-7-framework-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework-static/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-framework/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-7-framework/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-framework/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/package.json index 7ba06f2e1246..18f95b88b2dd 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/package.json @@ -18,8 +18,8 @@ "start": "serve -s build", "test": "playwright test", "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install && npx playwright install && pnpm build", - "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && npx playwright install && pnpm build", + "test:build": "pnpm install && pnpm build", + "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && pnpm build", "test:assert": "pnpm test" }, "eslintConfig": { diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-lazy-routes/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-router-entry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-7-router-entry/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-router-entry/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-spa-streaming/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-7-spa-streaming/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-spa-streaming/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-spa/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-7-spa/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-7-spa/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cross-usage/package.json b/dev-packages/e2e-tests/test-applications/react-router-8-cross-usage/package.json index 347460ff97cc..7702c1f061dd 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-8-cross-usage/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-8-cross-usage/package.json @@ -18,8 +18,8 @@ "start": "serve -s build", "test": "playwright test", "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install && npx playwright install && pnpm build", - "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && npx playwright install && pnpm build", + "test:build": "pnpm install && pnpm build", + "test:build-canary": "pnpm install && pnpm add react@canary react-dom@canary && pnpm build", "test:assert": "pnpm test" }, "eslintConfig": { diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-cross-usage/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-8-cross-usage/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-8-cross-usage/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-framework/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-8-framework/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-8-framework/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-router-entry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-8-router-entry/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-8-router-entry/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-8-spa/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-8-spa/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-8-spa/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-router-sourcemaps/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-router-sourcemaps/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-sourcemaps/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/pnpm-workspace.yaml new file mode 100644 index 000000000000..f5b00dda6609 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + core-js: false + core-js-pure: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/remix-hydrogen/package.json b/dev-packages/e2e-tests/test-applications/remix-hydrogen/package.json index 5e27e3c1046e..1b912b90be60 100644 --- a/dev-packages/e2e-tests/test-applications/remix-hydrogen/package.json +++ b/dev-packages/e2e-tests/test-applications/remix-hydrogen/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc", "codegen": "shopify hydrogen codegen", "clean": "npx rimraf node_modules dist pnpm-lock.yaml", - "test:build": "pnpm install && npx playwright install && pnpm build", + "test:build": "pnpm install && pnpm build", "test:assert": "pnpm playwright test" }, "dependencies": { diff --git a/dev-packages/e2e-tests/test-applications/remix-hydrogen/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/remix-hydrogen/pnpm-workspace.yaml new file mode 100644 index 000000000000..0aaed65978c9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/remix-hydrogen/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + esbuild: true + unrs-resolver: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/remix-server-timing/package.json b/dev-packages/e2e-tests/test-applications/remix-server-timing/package.json index 621c3ca9f116..e8aa4cd96980 100644 --- a/dev-packages/e2e-tests/test-applications/remix-server-timing/package.json +++ b/dev-packages/e2e-tests/test-applications/remix-server-timing/package.json @@ -33,9 +33,6 @@ "vite": "^5.4.11", "vite-tsconfig-paths": "^4.2.1" }, - "resolutions": { - "@types/react": "18.2.22" - }, "volta": { "extends": "../../package.json" } diff --git a/dev-packages/e2e-tests/test-applications/remix-server-timing/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/remix-server-timing/pnpm-workspace.yaml new file mode 100644 index 000000000000..1858a5f1d489 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/remix-server-timing/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + unrs-resolver: true +minimumReleaseAge: 0 +overrides: + '@types/react': '18.2.22' diff --git a/dev-packages/e2e-tests/test-applications/remix-sourcemaps/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/remix-sourcemaps/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/remix-sourcemaps/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/remix-v3/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/remix-v3/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/remix-v3/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/solid-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/solid-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-static/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/solid-tanstack-router/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid-tanstack-router/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/solid/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/solid/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solid/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/solidstart-2/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/solidstart-2/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart-2/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/pnpm-workspace.yaml new file mode 100644 index 000000000000..9355b28e8d0e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/solidstart-spa/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/solidstart-spa/pnpm-workspace.yaml new file mode 100644 index 000000000000..9355b28e8d0e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart-spa/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/solidstart-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/solidstart-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..9355b28e8d0e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart-static/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/pnpm-workspace.yaml new file mode 100644 index 000000000000..9355b28e8d0e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/solidstart/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/solidstart/pnpm-workspace.yaml new file mode 100644 index 000000000000..9355b28e8d0e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/supabase-nextjs/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/supabase-nextjs/pnpm-workspace.yaml new file mode 100644 index 000000000000..dfbd5846c66d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/supabase-nextjs/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + supabase: true + unrs-resolver: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/svelte-5-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/svelte-5-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..0b3d67452642 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/svelte-5-static/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + svelte-preprocess: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/svelte-5/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/svelte-5/pnpm-workspace.yaml new file mode 100644 index 000000000000..0b3d67452642 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/svelte-5/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + svelte-preprocess: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-kit-tracing/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-2-kit-tracing/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-kit-tracing/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-otlp/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-2-otlp/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-otlp/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-static/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2-static/package.json index 95f11c2bd094..46c0f757e7f9 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2-static/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-static/package.json @@ -36,11 +36,6 @@ "typescript": "^5.0.0", "vite": "^5.4.11" }, - "pnpm": { - "overrides": { - "rollup": "4.64.5" - } - }, "volta": { "extends": "../../package.json" }, diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-2-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..d05ee36cf34f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-static/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + svelte-preprocess: false +minimumReleaseAge: 0 +overrides: + 'rollup': '4.64.5' diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/pnpm-workspace.yaml new file mode 100644 index 000000000000..0b3d67452642 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + svelte-preprocess: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/pnpm-workspace.yaml new file mode 100644 index 000000000000..19da8807ed86 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2.5.0-twp/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + '@sveltejs/kit': true + esbuild: true + svelte-preprocess: false +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json index 73adf2924524..c26f94e9b5c7 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/package.json @@ -36,11 +36,6 @@ "typescript": "^5.0.0", "vite": "^5.4.11" }, - "pnpm": { - "overrides": { - "rollup": "4.64.5" - } - }, "volta": { "extends": "../../package.json" }, diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-2/pnpm-workspace.yaml new file mode 100644 index 000000000000..d05ee36cf34f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +allowBuilds: + esbuild: true + svelte-preprocess: false +minimumReleaseAge: 0 +overrides: + 'rollup': '4.64.5' diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-3-cloudflare-workers/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-3-cloudflare-workers/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-3-cloudflare-workers/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-3-sourcemaps/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-3-sourcemaps/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-3-sourcemaps/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-3/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-3/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-3/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/tanstack-router/pnpm-workspace.yaml new file mode 100644 index 000000000000..dafa2e7eca8c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@swc/core': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-cloudflare/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json b/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json index 46d69f6fcf7b..6ccf268417cf 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json @@ -29,7 +29,7 @@ "@tanstack/react-router": "1.170.18", "react": "^19.2.0", "react-dom": "^19.2.0", - "nitro": "latest || *", + "nitro": "^3.0.0", "ioredis": "5.10.1", "mysql": "^2.18.1" }, diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..dafa2e7eca8c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@swc/core': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json b/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json index 410f41baecbc..3b7947f69386 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json @@ -29,7 +29,7 @@ "@tanstack/react-router": "1.170.18", "react": "^19.2.0", "react-dom": "^19.2.0", - "nitro": "latest || *", + "nitro": "^3.0.0", "ioredis": "5.10.1", "mysql": "^2.18.1" }, diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/tanstackstart-react/pnpm-workspace.yaml new file mode 100644 index 000000000000..dafa2e7eca8c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + '@swc/core': true + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/package.json b/dev-packages/e2e-tests/test-applications/tsx-express/package.json index 77277c1e9394..6659dbcfc603 100644 --- a/dev-packages/e2e-tests/test-applications/tsx-express/package.json +++ b/dev-packages/e2e-tests/test-applications/tsx-express/package.json @@ -26,9 +26,6 @@ "@sentry-internal/test-utils": "link:../../../test-utils", "tsx": "^4.20.3" }, - "resolutions": { - "@types/qs": "6.9.17" - }, "volta": { "node": "20.19.5", "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/tsx-express/pnpm-workspace.yaml new file mode 100644 index 000000000000..2eda65e124c3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/tsx-express/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 +overrides: + '@types/qs': '6.9.17' diff --git a/dev-packages/e2e-tests/test-applications/vinext-cf-workers/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/vinext-cf-workers/pnpm-workspace.yaml new file mode 100644 index 000000000000..fd29a82085e0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/vinext-cf-workers/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + workerd: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/vue-3-static/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/vue-3-static/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/vue-3-static/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/vue-3/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/vue-3/pnpm-workspace.yaml new file mode 100644 index 000000000000..3cdf2ff22992 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/vue-3/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + esbuild: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/vue-tanstack-router/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/vue-tanstack-router/pnpm-workspace.yaml new file mode 100644 index 000000000000..8525919fe522 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/vue-tanstack-router/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + vue-demi: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/webpack-4/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/webpack-4/pnpm-workspace.yaml new file mode 100644 index 000000000000..05c2481d973b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/webpack-4/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + fsevents: true +minimumReleaseAge: 0 diff --git a/dev-packages/e2e-tests/test-applications/webpack-5/pnpm-workspace.yaml b/dev-packages/e2e-tests/test-applications/webpack-5/pnpm-workspace.yaml new file mode 100644 index 000000000000..a1c031fc3310 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/webpack-5/pnpm-workspace.yaml @@ -0,0 +1 @@ +minimumReleaseAge: 0 diff --git a/package.json b/package.json index 3e0beef30732..a85d65702597 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,7 @@ }, "volta": { "node": "24.21.0", - "yarn": "1.22.22", - "pnpm": "9.15.9" + "yarn": "1.22.22" }, "workspaces": [ "packages/angular", @@ -140,6 +139,7 @@ "oxfmt": "^0.70.0", "oxlint": "^1.75.0", "oxlint-tsgolint": "7", + "pnpm": "12.11.2", "rimraf": "^6.1.3", "rollup": "^4.60.3", "rollup-plugin-esbuild": "^6.2.1", diff --git a/scripts/bump-version.js b/scripts/bump-version.js index 083b2ed21eb2..7e9e208e1ab5 100644 --- a/scripts/bump-version.js +++ b/scripts/bump-version.js @@ -22,18 +22,15 @@ function updateWorkspaceDeps(pkgPath, workspaceNames, newVersion, { bumpVersion } } - if (pkg.pnpm?.overrides) { - for (const [dep, value] of Object.entries(pkg.pnpm.overrides)) { - if (!workspaceNames.has(dep)) continue; - - pkg.pnpm.overrides[dep] = value.replace(/(?<=sentry-[\w-]+-)\d+\.\d+\.\d+(-[\w.]+)?(?=\.tgz$)/, newVersion); - } - } - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); return pkg; } +function updateTarballVersions(filePath, newVersion) { + const content = fs.readFileSync(filePath, 'utf-8'); + fs.writeFileSync(filePath, content.replace(/(?<=sentry-[\w-]+-)\d+\.\d+\.\d+(-[\w.]+)?(?=\.tgz)/g, newVersion)); +} + /** * Bumps the version of all workspace packages and their internal dependencies. * This replicates the behavior of: @@ -86,6 +83,12 @@ function bumpVersions(rootDir, newVersion) { if (!fs.existsSync(fixturePkgPath)) continue; updateWorkspaceDeps(fixturePkgPath, workspaceNames, newVersion); + + const fixtureWorkspacePath = path.join(fixturesDir, entry.name, 'pnpm-workspace.yaml'); + + if (fs.existsSync(fixtureWorkspacePath)) { + updateTarballVersions(fixtureWorkspacePath, newVersion); + } } } diff --git a/scripts/bump-version.test.ts b/scripts/bump-version.test.ts index e2ebd06c94bc..46081a95d7a5 100644 --- a/scripts/bump-version.test.ts +++ b/scripts/bump-version.test.ts @@ -279,23 +279,32 @@ describe('bump-version', () => { esbuild: '0.28.0', '@sentry/bundler-plugins': '10.0.0', }, - pnpm: { - overrides: { - '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.0.0.tgz', - '@sentry/core': 'file:../../../../packages/core/sentry-core-10.0.0.tgz', - }, - }, }); + fs.writeFileSync( + path.join(fixturesDir, 'esbuild', 'pnpm-workspace.yaml'), + [ + 'overrides:', + " '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.0.0.tgz'", + " '@sentry/core': 'file:../../../../packages/core/sentry-core-10.0.0-beta.1.tgz'", + " 'sentry': 'file:../sentry-stub'", + '', + ].join('\n'), + ); bumpVersions(rootDir, '10.1.0'); const fixture = readPackageJson(path.join(fixturesDir, 'esbuild')); expect(fixture.dependencies['@sentry/bundler-plugins']).toBe('10.1.0'); expect(fixture.dependencies['esbuild']).toBe('0.28.0'); - expect(fixture.pnpm.overrides['@sentry/bundler-plugins']).toBe( - 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.1.0.tgz', + expect(fs.readFileSync(path.join(fixturesDir, 'esbuild', 'pnpm-workspace.yaml'), 'utf-8')).toBe( + [ + 'overrides:', + " '@sentry/bundler-plugins': 'file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.1.0.tgz'", + " '@sentry/core': 'file:../../../../packages/core/sentry-core-10.1.0.tgz'", + " 'sentry': 'file:../sentry-stub'", + '', + ].join('\n'), ); - expect(fixture.pnpm.overrides['@sentry/core']).toBe('file:../../../../packages/core/sentry-core-10.1.0.tgz'); }); it('throws when a workspace package.json is unreadable and does not partially update', () => { diff --git a/scripts/claude-cloud-setup.sh b/scripts/claude-cloud-setup.sh index daad33a61572..f2dd2eb72cd3 100755 --- a/scripts/claude-cloud-setup.sh +++ b/scripts/claude-cloud-setup.sh @@ -16,9 +16,6 @@ set -euo pipefail cd "$(dirname "$0")/.." -# Required by the repo's package-manager setup (some workspaces use pnpm via Volta). -export VOLTA_FEATURE_PNPM=1 - echo "node: $(node --version 2>/dev/null || echo 'not found')" echo "yarn: $(yarn --version 2>/dev/null || echo 'not found')" diff --git a/yarn.lock b/yarn.lock index a34482b44765..17ff00432901 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7212,6 +7212,76 @@ dependencies: playwright "1.63.0" +"@pnpm/exe.android-arm64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.android-arm64/-/exe.android-arm64-12.11.2.tgz#19a5bb724b6474d012183d27cf3e681a5b77676f" + integrity sha512-owLw13OfPkEoY+hLBNeeG+dCFhywr7q2d/EHPz4mNL2RhKnv8gNwMARkjkiDQu234aItVwFNDeGnmQXm+ikkAg== + +"@pnpm/exe.android-x64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.android-x64/-/exe.android-x64-12.11.2.tgz#006e181ff05ac4865f1a0403e5eb06a4c726c818" + integrity sha512-GYPkVebl4iokOA/4MPSpGoUVfFlsth7uqOCe1XiaiM23LAdvUOMJBObqHJijr89ZqjwHm9pO6vO45GRwJ2U2qA== + +"@pnpm/exe.darwin-arm64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.darwin-arm64/-/exe.darwin-arm64-12.11.2.tgz#5412671ac9eef2fabf21f7025d55f6f5a85c2b42" + integrity sha512-n9M4On66y8FDxUAKgMO1FXt0qihyG4e+56p3/F0uo8Bx9KplW8BwKVnXigzCJ8aNM6sVVPkOE6M9XXbeVIxnZA== + +"@pnpm/exe.darwin-x64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.darwin-x64/-/exe.darwin-x64-12.11.2.tgz#e638b01084f6fbda14210cb5dfb27b0b7113ce62" + integrity sha512-oSW7n3sBT0c0cyk4kj10T2Y6Mpm+OFEiCAWozgX039tdoI6yqJF1uaFD3sw7ugLdu6VV3+uY0pa1TUZIPdxDWA== + +"@pnpm/exe.freebsd-x64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.freebsd-x64/-/exe.freebsd-x64-12.11.2.tgz#ee8d8b123670fcaf2b8d00496c93f6165ae115f4" + integrity sha512-qbzc3KoDpa/Lkf8vs3jnDe6dUUbso+6eLIyssYFMiliLpYNxn8dirk11YoNnOQcdHVB50v5RFls1Yid6ttT5pg== + +"@pnpm/exe.linux-arm64-musl@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.linux-arm64-musl/-/exe.linux-arm64-musl-12.11.2.tgz#4ca69ac270510e5ef1a1d4e90b53b6f607b37706" + integrity sha512-uMEYDwKRktGwANubQ+kYCwmhNwg3Ik6qK89ahsbQ5cP30R/5FVJb/T99vr9XRUBC/A7mZI6eeT92oyVas9ACyg== + +"@pnpm/exe.linux-arm64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.linux-arm64/-/exe.linux-arm64-12.11.2.tgz#9c0032a9afa67ce04d26210ba3fd38717f8fd0b6" + integrity sha512-6e8ZAQUwFXAWHHHv5FesWqf/kEK04wuitBv5f/gxzkC0DiBfaixr+f+6JQede8SF4Ji5LcMwVFJbzAtffpWxEg== + +"@pnpm/exe.linux-ppc64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.linux-ppc64/-/exe.linux-ppc64-12.11.2.tgz#aa1ed3cd8dfb72ae7e82bd11552244e7d0cddd5f" + integrity sha512-Ui8A4nepkVupx2VpuFfysEluMvWK6gTB5rgVts7Ss/+8dj0RD28x3F/seNeH/zSypgC6yKdV6qsKVQh/uffTuQ== + +"@pnpm/exe.linux-riscv64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.linux-riscv64/-/exe.linux-riscv64-12.11.2.tgz#6c199e88fef12313af84b3fb5178bca76b36cf9b" + integrity sha512-i1yLdQ5bu+wjQ+srtFoJAaY2gN/+oXHFdjul4llCeoDxHE0rTIG3R41NhHfCjyFPW4+CpbBT58933CL9sMOCnA== + +"@pnpm/exe.linux-s390x@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.linux-s390x/-/exe.linux-s390x-12.11.2.tgz#4b39d64e334720e057f5bbc7bb2a0abd17336d94" + integrity sha512-YS1/lhPjjTsN4QL50VaFZKRa3Uc/bDnhE8eKbJMFvuX06UuFS5YaiDg04APoZdfVSGqTfX27QAg7rfLMSWaqxA== + +"@pnpm/exe.linux-x64-musl@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.linux-x64-musl/-/exe.linux-x64-musl-12.11.2.tgz#8ad50201bb69e3b647d75fd2ba58fb6706221850" + integrity sha512-7eIxQR05fvJxCujUSKp5KTtehJ7QqrgqP/HD1+BcB/SaV2Be4XI1PEHiOgA1zxsiMIeMvTF6E4G4dYHrFjeXRg== + +"@pnpm/exe.linux-x64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.linux-x64/-/exe.linux-x64-12.11.2.tgz#e5f2a6fdac29bd712ad6001d62e776a617d21fc4" + integrity sha512-FK7QKe24AYgppJwO3mpooYptduuvIGqKJPi5ac7MdnWkjUy46JBfYhCW0grnkWKC8YOf0x+vGviuQqJEj3SpQQ== + +"@pnpm/exe.win32-arm64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.win32-arm64/-/exe.win32-arm64-12.11.2.tgz#2564ef5e03ea9e039bc1271133cab12bb6248005" + integrity sha512-PCTjVDdJb6VvraZxrEeFv6EqEA0hhIsAySx28e95J2Tqpi88hLpgOXgNqV0vpxq9A+b8GLQfyk/D+1UlJPv/YQ== + +"@pnpm/exe.win32-x64@12.11.2": + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/@pnpm/exe.win32-x64/-/exe.win32-x64-12.11.2.tgz#f473dc852f50662cc535aef1bcbe36bc3ad02769" + integrity sha512-8sgX86OvRjJ3yZs/TvGR2L31wH9ScPZ+f+geKI3ikuB0RRiK3i4LlTRO8OXN6pdRqj8epMde4312MCjj7JsJdA== + "@polka/url@^1.0.0-next.24": version "1.0.0-next.28" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" @@ -22458,6 +22528,26 @@ pluralize@^8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +pnpm@12.11.2: + version "12.11.2" + resolved "https://sfw.security.sentry.io/npm/pnpm/-/pnpm-12.11.2.tgz#b52ddf8ba9850292e091eee9fbec5532159464eb" + integrity sha512-JaUWUqGWeFr18ovabf6NxYLr76SsHRgCIc4TkCMcX7dmN4IupLbkYzznSs3LCnf3Ja8RutOBd/3t2u6BipVcuQ== + optionalDependencies: + "@pnpm/exe.android-arm64" "12.11.2" + "@pnpm/exe.android-x64" "12.11.2" + "@pnpm/exe.darwin-arm64" "12.11.2" + "@pnpm/exe.darwin-x64" "12.11.2" + "@pnpm/exe.freebsd-x64" "12.11.2" + "@pnpm/exe.linux-arm64" "12.11.2" + "@pnpm/exe.linux-arm64-musl" "12.11.2" + "@pnpm/exe.linux-ppc64" "12.11.2" + "@pnpm/exe.linux-riscv64" "12.11.2" + "@pnpm/exe.linux-s390x" "12.11.2" + "@pnpm/exe.linux-x64" "12.11.2" + "@pnpm/exe.linux-x64-musl" "12.11.2" + "@pnpm/exe.win32-arm64" "12.11.2" + "@pnpm/exe.win32-x64" "12.11.2" + possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" From cc6ef9a81d92c153dd537a77a80479665942fbab Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 15 Oct 2026 07:27:12 -0700 Subject: [PATCH 009/118] fix(cloudflare): auto-instrument a re-exported default export (#25345) The Vite plugin did not wrap a Worker entry whose default export comes from another module: ```js export { default } from './handler'; export { handler as default } from './handler'; ``` The transform now imports the handler under a private name, wraps it with `withSentry`, and exports the wrapper as the default. The handler may already be wrapped in its own module, which the build cannot see. So the wrap goes through `_INTERNAL_wrapUnlessInstrumented`, the same guard that class re-exports use. For a handler object, a second `withSentry` call adds nothing anyway, because each method is patched only once. Co-authored-by: Claude Opus 5.5 (1M context) --- packages/cloudflare/src/vite/transform.ts | 27 +++++++++- .../cloudflare/test/vite/transform.test.ts | 50 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/src/vite/transform.ts b/packages/cloudflare/src/vite/transform.ts index 49b623d6c431..5307ad48336b 100644 --- a/packages/cloudflare/src/vite/transform.ts +++ b/packages/cloudflare/src/vite/transform.ts @@ -566,6 +566,28 @@ function wrapDefaultSpecifier(localName: string, state: TransformState): string return '__SENTRY_DEFAULT_EXPORT__ as default'; } +/** + * `export { default } from './handler'`. The handler may already be wrapped in its own module, + * which this transform cannot see, so the emitted guard returns such a handler as-is. + */ +function wrapDefaultReexport( + localName: string, + sourceLiteral: string, + state: TransformState, + prelude: string[], +): string { + const target = '__SENTRY_REEXPORT_default__'; + prelude.push( + localName === DEFAULT_IMPORT + ? `import ${target} from ${sourceLiteral};` + : `import { ${localName} as ${target} } from ${sourceLiteral};`, + `const __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, ${state.optionsFn}, ${target});`, + ); + state.needsImport = true; + state.autoWrapped.add(DEFAULT_EXPORT); + return '__SENTRY_DEFAULT_EXPORT__ as default'; +} + /** * Handle one export specifier, returning the `Wrapped as Exported` pair to emit for a class that * gets wrapped. The import/wrapper statements a cross-module pair depends on are pushed onto @@ -593,7 +615,10 @@ function wrapSpecifier( if (!exportedName || !localName) return undefined; if (!kind) { - return exportedName === DEFAULT_IMPORT && !sourceLiteral ? wrapDefaultSpecifier(localName, state) : undefined; + if (exportedName !== DEFAULT_IMPORT) return undefined; + return sourceLiteral + ? wrapDefaultReexport(localName, sourceLiteral, state, prelude) + : wrapDefaultSpecifier(localName, state); } // Without a `from` clause the specifier points at a module-local binding. diff --git a/packages/cloudflare/test/vite/transform.test.ts b/packages/cloudflare/test/vite/transform.test.ts index 92396bf21bae..8b3cd2ec207f 100644 --- a/packages/cloudflare/test/vite/transform.test.ts +++ b/packages/cloudflare/test/vite/transform.test.ts @@ -234,6 +234,46 @@ describe('default export wrapping', () => { expect(transform(code, ctx)).toBeUndefined(); }); + it('wraps a default re-export from another module', () => { + const code = "export { default } from './handler';"; + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + "import __SENTRY_REEXPORT_default__ from './handler';", + 'const __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, (env) => ({}), __SENTRY_REEXPORT_default__);', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + ].join('\n'), + ); + }); + + it('wraps a named export of another module re-exported as default', () => { + const code = "export { handler as default } from './handler';"; + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + "import { handler as __SENTRY_REEXPORT_default__ } from './handler';", + 'const __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, (env) => ({}), __SENTRY_REEXPORT_default__);', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + ].join('\n'), + ); + }); + + it('leaves sibling specifiers of a default re-export untouched', () => { + const code = "export { default, helper } from './handler';"; + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + "import __SENTRY_REEXPORT_default__ from './handler';", + 'const __SENTRY_DEFAULT_EXPORT__ = __SENTRY__._INTERNAL_wrapUnlessInstrumented(__SENTRY__.withSentry, (env) => ({}), __SENTRY_REEXPORT_default__);', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + "export { helper } from './handler';", + ].join('\n'), + ); + }); + it('uses custom options callback', () => { const custom: TransformContext = { classWrappers: doWrappers(), @@ -1118,6 +1158,16 @@ describe('same-worker RPC binding floor', () => { expect(result.code).toContain('rpcTracePropagationBindings: ["SELF",'); }); + it('enables a self service binding when it wrapped a default re-export', () => { + const result = transform("export { default } from './handler';", { + classWrappers: doWrappers(), + optionsFn: '() => undefined', + sameWorkerBindings: [{ bindingName: 'SELF', className: DEFAULT_EXPORT }], + })!; + + expect(result.code).toContain('rpcTracePropagationBindings: ["SELF",'); + }); + it('enables a self service binding when the default export re-exports an already wrapped class', () => { const code = [ "import { WorkerEntrypoint } from 'cloudflare:workers';", From 30449974ea1c1f7fecdc0d27da6a96d141339576 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 15 Oct 2026 15:58:09 +0100 Subject: [PATCH 010/118] ref(bundler-plugins): Drop @babel/core peer dependency (#25406) `@sentry/bundler-plugins` no longer uses Babel at runtime. The `./babel-plugin` export runs inside the user's own Babel, and imports Babel only for types. So the optional `@babel/core` peer dependency had no effect: anyone who runs a Babel plugin already has Babel installed. The peer dependency did not help with types either. The emitted `.d.ts` files imported from `@babel/core`, which ships no types of its own, so TypeScript users without `@types/babel__core` got broken declarations. The exported plugin functions now use small loose types (`BabelApi`, `BabelPlugin`). The full Babel types stay internal, so the declarations no longer import from `@babel/core`. `@babel/core` stays a dev dependency for the build and tests. --- packages/bundler-plugins/package.json | 4 ---- .../bundler-plugins/src/babel-plugin/experimental.ts | 8 +++++++- packages/bundler-plugins/src/babel-plugin/index.ts | 8 +++++++- packages/bundler-plugins/src/babel-plugin/types.ts | 10 ++++++++++ 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 packages/bundler-plugins/src/babel-plugin/types.ts diff --git a/packages/bundler-plugins/package.json b/packages/bundler-plugins/package.json index 6cd9ce64fdf8..210eda6be54c 100644 --- a/packages/bundler-plugins/package.json +++ b/packages/bundler-plugins/package.json @@ -119,14 +119,10 @@ "supports-color": "^8.1.1" }, "peerDependencies": { - "@babel/core": "^7.18.5", "rollup": ">=3.2.0", "webpack": ">=5.1.0" }, "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, "rollup": { "optional": true }, diff --git a/packages/bundler-plugins/src/babel-plugin/experimental.ts b/packages/bundler-plugins/src/babel-plugin/experimental.ts index a5d05ec7ab6e..b80dc0595907 100644 --- a/packages/bundler-plugins/src/babel-plugin/experimental.ts +++ b/packages/bundler-plugins/src/babel-plugin/experimental.ts @@ -37,6 +37,8 @@ import type * as Babel from '@babel/core'; import type { PluginObj, PluginPass } from '@babel/core'; +import type { BabelApi, BabelPlugin } from './types'; + const REACT_NATIVE_ELEMENTS: string[] = [ 'Image', 'Text', @@ -92,7 +94,11 @@ interface JSXProcessingContext { } // We must export the plugin as default, otherwise the Babel loader will not be able to resolve it when configured using its string identifier -export function experimentalComponentNameAnnotatePlugin({ types: t }: typeof Babel): AnnotationPlugin { +export function experimentalComponentNameAnnotatePlugin(babel: BabelApi): BabelPlugin { + return createPlugin(babel as typeof Babel); +} + +function createPlugin({ types: t }: typeof Babel): AnnotationPlugin { return { visitor: { Program: { diff --git a/packages/bundler-plugins/src/babel-plugin/index.ts b/packages/bundler-plugins/src/babel-plugin/index.ts index ac97b5ff2be8..94f7347a3f87 100644 --- a/packages/bundler-plugins/src/babel-plugin/index.ts +++ b/packages/bundler-plugins/src/babel-plugin/index.ts @@ -47,6 +47,7 @@ import { type ComponentAnnotationAttributeNames, } from './component-annotation'; import { KNOWN_INCOMPATIBLE_PLUGINS } from './constants'; +import type { BabelApi, BabelPlugin } from './types'; const SENTRY_LABEL_ATTRIBUTE = 'sentry-label'; const MAX_LABEL_LENGTH = 64; @@ -100,9 +101,14 @@ interface JSXProcessingContext { } export { experimentalComponentNameAnnotatePlugin } from './experimental'; +export type { BabelApi, BabelPlugin } from './types'; // We must export the plugin as default, otherwise the Babel loader will not be able to resolve it when configured using its string identifier -export default function componentNameAnnotatePlugin({ types: t }: typeof Babel): AnnotationPlugin { +export default function componentNameAnnotatePlugin(babel: BabelApi): BabelPlugin { + return createPlugin(babel as typeof Babel); +} + +function createPlugin({ types: t }: typeof Babel): AnnotationPlugin { return { visitor: { Program: { diff --git a/packages/bundler-plugins/src/babel-plugin/types.ts b/packages/bundler-plugins/src/babel-plugin/types.ts new file mode 100644 index 000000000000..c68ecc600e18 --- /dev/null +++ b/packages/bundler-plugins/src/babel-plugin/types.ts @@ -0,0 +1,10 @@ +// The public signatures use these instead of `@babel/core` types, so +// consumers do not need Babel's types installed. + +export interface BabelApi { + types: object; +} + +export interface BabelPlugin { + visitor: object; +} From a33b09f54433bd97c60c4c2d7e9d48cd24feb4a7 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 15 Oct 2026 09:42:31 -0700 Subject: [PATCH 011/118] test(node): skip the Prisma 8 suite on Node 20 (#25421) Prisma 8 requires Node 22.12: its `@prisma/cli-engine` peer declares `engines.node >=22.12.0`. On Node 20, the suite's install of `@prisma/orm-postgres` now fails outright. npm 10, which Node 20 ships, crashes with `Cannot read properties of null (reading 'edgesOut')` on a peer conflict deep in Prisma's tree, which `vitest@4.1.12` introduced: its optional `@vitest/browser-webdriverio` peer is `^4.1.11`, and that package has no 4.1.12, so it resolves to 4.1.11, which peers on `vitest@4.1.11`. The suite now runs on Node 22 and later only. That also removes its Node 20 test of `require(esm)` loading the ORM uninstrumented, which covered a setup Prisma does not support. Co-authored-by: Claude Opus 5.5 (1M context) --- .../suites/tracing/prisma-orm-v8/test.ts | 33 +++---------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v8/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v8/test.ts index d7adfe06da27..6831e3123397 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v8/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm-v8/test.ts @@ -33,13 +33,14 @@ function getOperationSpans(container: SerializedStreamedSpanContainer): Serializ // Prisma 8 has no tracing surface: the operation spans come from the orchestrion channels the runtime hook // injects, and the `pg` spans underneath stand in for v7's `db_query` spans. -describe('Prisma ORM v8 Tests', () => { +// Prisma 8 requires Node 22.12 (`engines` of its `@prisma/cli-engine` peer), and on Node 20 it does not install. +describe.runIf(supports({ min: 22 }))('Prisma ORM v8 Tests', () => { describeWithDockerCompose('Prisma ORM v8', { workingDirectory: [__dirname] }, () => { createEsmAndCjsTests( __dirname, 'scenario.mjs', 'instrument.mjs', - (createRunner, test, mode) => { + (createRunner, test) => { const testInstrumentedOperations = (): void => { test('should instrument Prisma ORM operations and nest their queries', { timeout: 75_000 }, async () => { await createRunner() @@ -96,33 +97,7 @@ describe('Prisma ORM v8 Tests', () => { }); }; - if (mode === 'esm') { - testInstrumentedOperations(); - return; - } - - // The CJS scenario loads the ESM-only package via `require(esm)`, which Node's module hooks only see - // from Node 22 on; on Node 20 the ORM terminals load uninstrumented. - describe.runIf(supports({ min: 22 }))( - 'with require(esm) reaching the module hooks', - testInstrumentedOperations, - ); - - describe.runIf(supports({ max: 21 }))('with require(esm) bypassing the module hooks', () => { - test('should keep the pg spans on the transaction', { timeout: 75_000 }, async () => { - await createRunner() - .unordered() - .expect({ - span: container => { - const segment = getSegment(container); - expect(getOperationSpans(container)).toEqual([]); - getQuerySpans(container).forEach(span => expect(span.parent_span_id).toBe(segment.span_id)); - }, - }) - .start() - .completed(); - }); - }); + testInstrumentedOperations(); }, { additionalDependencies: { From 0778826f7133ab7fa82a9a181ff1fcddd159bf86 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 15 Oct 2026 12:03:17 -0700 Subject: [PATCH 012/118] fix(astro): defer to `@sentry/cloudflare/vite` on Cloudflare Workers (#25347) On Cloudflare Workers, an Astro app needed a hand-written `sentry.server.config.*` that wraps the adapter's handler, set as the wrangler `main`. The integration's own Cloudflare plugin wraps `astrojs-ssr-virtual-entry`, which `@astrojs/cloudflare` v13 and later no longer emit, so on those versions it wraps nothing. `@sentry/cloudflare/vite` can now wrap a package `main`, such as `@astrojs/cloudflare/entrypoints/server`. When the user adds `sentryCloudflareVitePlugin()` to the Vite plugins in `astro.config`, the integration now leaves the Worker to it: - It does not add its own Cloudflare wrap plugin. - It does not add its orchestrion plugin, because the Cloudflare plugin adds one with the workerd-safe `diagnostics_channel` module. - It does not inject the default server init. That init reads `PUBLIC_SENTRY_DSN`, which is not set on Workers, so it disabled the transport. The Cloudflare plugin inits the SDK from `instrument.server.*` beside the wrangler config, or from `env`. The integration detects the plugin by name. Importing it would load `@sentry/cloudflare` and `wrangler` for every Astro app. Apps without the plugin keep the integration's own wrap plugin. It now also matches `virtual:cloudflare/worker-entry`, the entry module of `@cloudflare/vite-plugin`, which v13 and later build through. That entry ends in `export default mod.default ?? {};`, so the plugin now wraps any default export expression, not only a dotted name. When the user's `main` already wraps the handler, the second `withSentry` call does nothing new, because each handler method is patched only once. The `astro-6-cf-workers` e2e app now uses the new setup: `main` is the adapter's entry, and the options live in `instrument.server.js`. This replaces the `cloudflare-autoinstrument-package-entry` e2e app, as its coverage largely overlaps with it. Fixes JS-3934 Fixes JS-2893 Fixes #21901 Fixes #25344 --------- Co-authored-by: Claude Opus 5.5 (1M context) --- .../astro-6-cf-workers/astro.config.mjs | 4 + .../astro-6-cf-workers/instrument.server.js | 9 +++ .../sentry.server.config.js | 13 ---- .../worker-configuration.d.ts | 2 +- .../astro-6-cf-workers/wrangler.jsonc | 2 +- .../.gitignore | 4 - .../instrument.server.ts | 10 --- .../package.json | 29 ------- .../playwright.config.ts | 12 --- .../start-event-proxy.mjs | 6 -- .../tests/package-entry.test.ts | 35 --------- .../vite.config.ts | 7 -- .../worker-entry/fetch-handler.js | 11 --- .../worker-entry/package.json | 9 --- .../wrangler.jsonc | 8 -- packages/astro/src/integration/cloudflare.ts | 10 ++- packages/astro/src/integration/index.ts | 38 +++++++++- .../integration/cloudflareVitePlugin.test.ts | 48 ++++++++++++ packages/astro/test/integration/index.test.ts | 75 +++++++++++++++++++ 19 files changed, 179 insertions(+), 153 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/astro-6-cf-workers/instrument.server.js delete mode 100644 dev-packages/e2e-tests/test-applications/astro-6-cf-workers/sentry.server.config.js delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/.gitignore delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/instrument.server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/playwright.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/tests/package-entry.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/vite.config.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/worker-entry/fetch-handler.js delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/worker-entry/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/wrangler.jsonc create mode 100644 packages/astro/test/integration/cloudflareVitePlugin.test.ts diff --git a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/astro.config.mjs b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/astro.config.mjs index 8f620f57591f..0894958462b2 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/astro.config.mjs +++ b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/astro.config.mjs @@ -2,6 +2,7 @@ import sentry from '@sentry/astro'; // @ts-check import { defineConfig } from 'astro/config'; import cloudflare from '@astrojs/cloudflare'; +import { sentryCloudflareVitePlugin } from '@sentry/cloudflare/vite'; // https://astro.build/config export default defineConfig({ @@ -18,4 +19,7 @@ export default defineConfig({ allowedDomains: [{ hostname: 'localhost' }], }, adapter: cloudflare(), + vite: { + plugins: [sentryCloudflareVitePlugin()], + }, }); diff --git a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/instrument.server.js b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/instrument.server.js new file mode 100644 index 000000000000..eeb325d68c3f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/instrument.server.js @@ -0,0 +1,9 @@ +import { defineCloudflareOptions } from '@sentry/cloudflare'; + +export default defineCloudflareOptions(env => ({ + dsn: env.E2E_TEST_DSN, + environment: 'qa', + tracesSampleRate: 1.0, + tunnel: 'http://localhost:3031/', // proxy server + debug: true, +})); diff --git a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/sentry.server.config.js b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/sentry.server.config.js deleted file mode 100644 index 8f1a839bc05f..000000000000 --- a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/sentry.server.config.js +++ /dev/null @@ -1,13 +0,0 @@ -import * as Sentry from '@sentry/cloudflare'; -import handler from '@astrojs/cloudflare/entrypoints/server'; - -export default Sentry.withSentry( - env => ({ - dsn: env.E2E_TEST_DSN, - environment: 'qa', - tracesSampleRate: 1.0, - tunnel: 'http://localhost:3031/', // proxy server - debug: true, - }), - handler, -); diff --git a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/worker-configuration.d.ts b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/worker-configuration.d.ts index 03c757e9dcc2..96d062b4c7ac 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/worker-configuration.d.ts +++ b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/worker-configuration.d.ts @@ -5,7 +5,7 @@ interface __BaseEnv_Env { } declare namespace Cloudflare { interface GlobalProps { - mainModule: typeof import('./sentry.server.config'); + mainModule: typeof import('@astrojs/cloudflare/entrypoints/server'); } interface Env extends __BaseEnv_Env {} } diff --git a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/wrangler.jsonc b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/wrangler.jsonc index eed026ecf585..ff0744ef24d8 100644 --- a/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/wrangler.jsonc +++ b/dev-packages/e2e-tests/test-applications/astro-6-cf-workers/wrangler.jsonc @@ -2,7 +2,7 @@ "compatibility_date": "2026-03-12", "compatibility_flags": ["global_fetch_strictly_public", "nodejs_compat"], "name": "astro-6-cf-workers", - "main": "./sentry.server.config.js", + "main": "@astrojs/cloudflare/entrypoints/server", "assets": { "directory": "./dist", "binding": "ASSETS", diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/.gitignore b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/.gitignore deleted file mode 100644 index 3f64191fe034..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -dist -.wrangler -test-results -pnpm-lock.yaml diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/instrument.server.ts b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/instrument.server.ts deleted file mode 100644 index 10d0f17fbcdb..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/instrument.server.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineCloudflareOptions } from '@sentry/cloudflare'; - -// The worker entry is a package, so the plugin reads this file from beside -// the wrangler config. -export default defineCloudflareOptions((env: { E2E_TEST_DSN: string }) => ({ - dsn: env.E2E_TEST_DSN, - environment: 'qa', - tunnel: 'http://localhost:3031/', - tracesSampleRate: 1.0, -})); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/package.json deleted file mode 100644 index 2211b7f3455b..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "cloudflare-autoinstrument-package-entry", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "build": "vite build", - "preview": "wrangler dev --var \"E2E_TEST_DSN:$E2E_TEST_DSN\" --log-level=$(test $CI && echo 'none' || echo 'log')", - "test": "playwright test", - "test:build": "pnpm install && pnpm build", - "test:assert": "pnpm test" - }, - "dependencies": { - "@sentry/cloudflare": "file:../../packed/sentry-cloudflare-packed.tgz", - "worker-entry": "file:../../test-applications/cloudflare-autoinstrument-package-entry/worker-entry" - }, - "devDependencies": { - "@cloudflare/vite-plugin": "^1.47.0", - "@cloudflare/workers-types": "^5.20260727.1", - "@playwright/test": "~1.63.0", - "@sentry/conventions": "0.27.0", - "@sentry-internal/test-utils": "link:../../../test-utils", - "vite": "^8.1.5", - "wrangler": "^4.114.0" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/playwright.config.ts b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/playwright.config.ts deleted file mode 100644 index 9633de63b272..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/playwright.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; - -export default getPlaywrightConfig( - { - startCommand: 'pnpm preview', - port: 8787, - }, - { - workers: '100%', - retries: 0, - }, -); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/start-event-proxy.mjs deleted file mode 100644 index 553dac81dc3c..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/start-event-proxy.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'cloudflare-autoinstrument-package-entry', -}); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/tests/package-entry.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/tests/package-entry.test.ts deleted file mode 100644 index 7baf4af55892..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/tests/package-entry.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { SENTRY_ENVIRONMENT, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; -import { HTTP_SERVER } from '@sentry/conventions/op'; -import { getSpanOp, waitForError, waitForStreamedSpan } from '@sentry-internal/test-utils'; - -// The worker entry is a package with no Sentry calls. Any event that arrives -// proves the plugin wrapped the module that wrangler's `main` resolved to. - -test('wraps a package entry with withSentry (options from instrument.server.ts)', async ({ baseURL }) => { - const spanPromise = waitForStreamedSpan( - 'cloudflare-autoinstrument-package-entry', - span => getSpanOp(span) === HTTP_SERVER && span.is_segment, - ); - - const res = await fetch(baseURL!); - expect(res.status).toBe(200); - - const span = await spanPromise; - expect(span.attributes[SENTRY_ORIGIN]?.value).toBe('auto.http.cloudflare'); - // Only `instrument.server.ts` sets this environment. - expect(span.attributes[SENTRY_ENVIRONMENT]?.value).toBe('qa'); -}); - -test('captures an error thrown by a package entry', async ({ baseURL }) => { - const errorPromise = waitForError( - 'cloudflare-autoinstrument-package-entry', - event => event.exception?.values?.[0]?.value === 'Thrown from a package entry', - ); - - const res = await fetch(`${baseURL}/throw`); - expect(res.status).toBe(500); - - const error = await errorPromise; - expect(error.environment).toBe('qa'); -}); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/vite.config.ts b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/vite.config.ts deleted file mode 100644 index 005f4448f6cb..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/vite.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { cloudflare } from '@cloudflare/vite-plugin'; -import { sentryCloudflareVitePlugin } from '@sentry/cloudflare/vite'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [cloudflare(), sentryCloudflareVitePlugin()], -}); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/worker-entry/fetch-handler.js b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/worker-entry/fetch-handler.js deleted file mode 100644 index 1d8a976634e6..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/worker-entry/fetch-handler.js +++ /dev/null @@ -1,11 +0,0 @@ -// Shaped like bundler output (e.g. `@astrojs/cloudflare`): the default export -// is a specifier, not `export default `. -var fetch_handler_default = { - async fetch(request) { - if (new URL(request.url).pathname === '/throw') { - throw new Error('Thrown from a package entry'); - } - return new Response('ok'); - }, -}; -export { fetch_handler_default as default }; diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/worker-entry/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/worker-entry/package.json deleted file mode 100644 index 08018b205e6e..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/worker-entry/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "worker-entry", - "version": "0.0.0", - "private": true, - "type": "module", - "exports": { - "./fetch-handler": "./fetch-handler.js" - } -} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/wrangler.jsonc b/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/wrangler.jsonc deleted file mode 100644 index 4f9bc84616ee..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-autoinstrument-package-entry/wrangler.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "node_modules/wrangler/config-schema.json", - "name": "cloudflare-autoinstrument-package-entry", - // A package export, not a file path. The Sentry plugin resolves it with Vite. - "main": "worker-entry/fetch-handler", - "compatibility_date": "2026-05-20", - "compatibility_flags": ["nodejs_compat"], -} diff --git a/packages/astro/src/integration/cloudflare.ts b/packages/astro/src/integration/cloudflare.ts index 93311a735248..69e7ff7be99e 100644 --- a/packages/astro/src/integration/cloudflare.ts +++ b/packages/astro/src/integration/cloudflare.ts @@ -37,6 +37,10 @@ export function sentryCloudflareNodeWarningPlugin(): VitePlugin { }; } +// The Worker entry module: `@astrojs/cloudflare` v12 builds its own virtual entry, and v13+ builds +// through `@cloudflare/vite-plugin`, whose entry ends in `export default mod.default ?? {};`. +const WORKER_ENTRY_IDS = ['astrojs-ssr-virtual-entry', 'virtual:cloudflare/worker-entry']; + /** * A Vite plugin that ensures the Sentry server config is loaded at the * top level of the Cloudflare Worker entry module, rather than only being @@ -57,16 +61,14 @@ export function sentryCloudflareVitePlugin(): VitePlugin { enforce: 'post', transform(code, id) { - // Match the Astro SSR virtual entry โ€” this becomes dist/_worker.js/index.js - // The resolved virtual module ID is `\0@astrojs-ssr-virtual-entry` - if (!id.includes('astrojs-ssr-virtual-entry')) { + if (!WORKER_ENTRY_IDS.some(entryId => id.includes(entryId))) { return undefined; } // In @astrojs/cloudflare v12, the virtual entry module structure is: // https://github.com/withastro/astro/blob/09bbdbb1e62c388eb405eeea03554c15e01f2957/packages/integrations/cloudflare/src/entrypoints/server.ts#L23 // We need to wrap `default` with `withSentry` before it's exported. - const defaultExportMatch = code.match(/export\s+default\s+([\w.]+)\s*;/); + const defaultExportMatch = code.match(/export\s+default\s+([^;]+?)\s*;/); if (!defaultExportMatch) { return undefined; diff --git a/packages/astro/src/integration/index.ts b/packages/astro/src/integration/index.ts index b4e54545a8d3..396413b64939 100644 --- a/packages/astro/src/integration/index.ts +++ b/packages/astro/src/integration/index.ts @@ -142,6 +142,10 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { const isCloudflare = config?.adapter?.name?.startsWith('@astrojs/cloudflare'); const isCloudflareWorkers = isCloudflare && !isCloudflarePages(); + // That plugin adds its own orchestrion transform and wraps the Worker entry, which inits the + // SDK from `instrument.server.*` or `env`. With its `autoInstrumentation: false`, the user's + // own `withSentry` does the init. Either way, none of ours are needed. + const usesCloudflareVitePlugin = isCloudflareWorkers && hasCloudflareVitePlugin(config); // Wire up the orchestrion code transform so instrumented server-side dependencies (e.g. // `mysql`, `ioredis`) get `diagnostics_channel` publishers injected into the SSR bundle at @@ -149,7 +153,7 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { // `buildTimeInstrumentation` is `false`. Cloudflare Pages is skipped: it gets no // `withSentry` wrap, so nothing would read the marker the injected snippets write, and // keeping the transform off avoids bundling dead subscriber code. - if (sdkEnabled.server && (!isCloudflare || isCloudflareWorkers)) { + if (sdkEnabled.server && (!isCloudflare || isCloudflareWorkers) && !usesCloudflareVitePlugin) { updateConfig({ vite: { plugins: [sentryOrchestrionPlugin({ buildTimeInstrumentation }) as VitePlugin], @@ -179,7 +183,7 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { // This ensures Sentry.init() runs in dev mode (where the Vite plugin doesn't fire) // and also serves as the fallback for non-Cloudflare adapters in production. injectScript('page-ssr', buildSdkInitFileImportSnippet(pathToServerInit)); - } else { + } else if (!usesCloudflareVitePlugin) { debug && logger.info('Using default server init.'); injectScript('page-ssr', buildServerSnippet(options || {})); } @@ -199,7 +203,9 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { debug && logger.info('Adding Cloudflare Vite plugin to wrap Worker entry with withSentry.'); updateConfig({ vite: { - plugins: [sentryCloudflareNodeWarningPlugin(), sentryCloudflareVitePlugin()], + plugins: usesCloudflareVitePlugin + ? [sentryCloudflareNodeWarningPlugin()] + : [sentryCloudflareNodeWarningPlugin(), sentryCloudflareVitePlugin()], }, }); } else if (isCloudflare) { @@ -236,6 +242,32 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { }; }; +// Every sub-plugin of `sentryCloudflareVitePlugin()` but its orchestrion transform has this prefix, +// and some are added whatever its options are. +const CLOUDFLARE_VITE_PLUGIN_NAME_PREFIX = 'sentry-cloudflare-'; + +/** + * Whether the user added `sentryCloudflareVitePlugin()` from `@sentry/cloudflare/vite`. It is + * detected by name because importing it here would load `@sentry/cloudflare` and `wrangler` for + * every Astro app. + */ +function hasCloudflareVitePlugin(config: AstroConfig): boolean { + const pending: unknown[] = [config.vite?.plugins]; + while (pending.length > 0) { + const plugin = pending.pop(); + if (Array.isArray(plugin)) { + pending.push(...plugin); + } else if ( + typeof plugin === 'object' && + plugin !== null && + String((plugin as { name?: unknown }).name).startsWith(CLOUDFLARE_VITE_PLUGIN_NAME_PREFIX) + ) { + return true; + } + } + return false; +} + const possibleFileExtensions = ['ts', 'js', 'tsx', 'jsx', 'mjs', 'cjs', 'mts']; function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined { diff --git a/packages/astro/test/integration/cloudflareVitePlugin.test.ts b/packages/astro/test/integration/cloudflareVitePlugin.test.ts new file mode 100644 index 000000000000..0464acd321e2 --- /dev/null +++ b/packages/astro/test/integration/cloudflareVitePlugin.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; +import { sentryCloudflareVitePlugin } from '../../src/integration/cloudflare'; + +function transform(code: string, id: string) { + const plugin = sentryCloudflareVitePlugin() as { transform: (code: string, id: string) => unknown }; + return plugin.transform(code, id); +} + +describe('sentryCloudflareVitePlugin', () => { + it('wraps the default export of the @astrojs/cloudflare v12 virtual entry', () => { + const code = ['const _exports = createExports(manifest);', 'export default _exports.default;'].join('\n'); + + expect(transform(code, '\0@astrojs-ssr-virtual-entry')).toEqual({ + code: [ + "import { withSentry } from '@sentry/cloudflare';", + 'const _exports = createExports(manifest);', + 'export default withSentry(() => undefined, _exports.default);', + ].join('\n'), + map: null, + }); + }); + + it('wraps the default export of the @cloudflare/vite-plugin worker entry', () => { + const code = [ + 'import { getExportTypes } from "virtual:cloudflare/export-types";', + 'import * as mod from "virtual:cloudflare/user-entry";', + 'export * from "virtual:cloudflare/user-entry";', + 'export default mod.default ?? {};', + 'if (import.meta.hot) { import.meta.hot.accept(); }', + ].join('\n'); + + expect(transform(code, '\0virtual:cloudflare/worker-entry')).toEqual({ + code: [ + "import { withSentry } from '@sentry/cloudflare';", + 'import { getExportTypes } from "virtual:cloudflare/export-types";', + 'import * as mod from "virtual:cloudflare/user-entry";', + 'export * from "virtual:cloudflare/user-entry";', + 'export default withSentry(() => undefined, mod.default ?? {});', + 'if (import.meta.hot) { import.meta.hot.accept(); }', + ].join('\n'), + map: null, + }); + }); + + it('leaves other modules alone', () => { + expect(transform('export default handler;', '/src/pages/index.astro')).toBeUndefined(); + }); +}); diff --git a/packages/astro/test/integration/index.test.ts b/packages/astro/test/integration/index.test.ts index 54b199086c7c..42e9af789247 100644 --- a/packages/astro/test/integration/index.test.ts +++ b/packages/astro/test/integration/index.test.ts @@ -451,6 +451,81 @@ describe('sentryAstro integration', () => { }); }); + // The plugin returns an array, so it arrives nested. + describe.each([ + { + setup: 'sentryCloudflareVitePlugin()', + plugins: [ + { name: 'code-transformer' }, + { name: 'sentry-cloudflare-mastra-observability-provider' }, + { name: 'sentry-cloudflare-flue-runtime-provider' }, + { name: 'sentry-cloudflare-auto-instrument' }, + ], + }, + { + setup: 'sentryCloudflareVitePlugin({ autoInstrumentation: false })', + plugins: [ + { name: 'code-transformer' }, + { name: 'sentry-cloudflare-mastra-observability-provider' }, + { name: 'sentry-cloudflare-flue-runtime-provider' }, + ], + }, + ])('with `$setup` from `@sentry/cloudflare/vite` in the vite config', ({ plugins }) => { + const cloudflareConfig = { + ...config, + adapter: { name: '@astrojs/cloudflare' }, + vite: { plugins: [plugins] }, + } as AstroConfig; + + it('leaves the orchestrion transform and the Worker wrap to that plugin', async () => { + const integration = sentryAstro({}); + + // @ts-expect-error - the hook exists and we only need to pass what we actually use + await integration.hooks['astro:config:setup']({ + ...baseConfigHookObject, + updateConfig, + injectScript, + config: cloudflareConfig, + }); + + expect(orchestrionVite).not.toHaveBeenCalled(); + expect(updateConfig).toHaveBeenCalledWith({ + vite: { plugins: [expect.objectContaining({ name: 'sentry-astro-cloudflare-suppress-node-warnings' })] }, + }); + expect(updateConfig).not.toHaveBeenCalledWith({ + vite: { plugins: expect.arrayContaining([expect.objectContaining({ name: 'sentry-astro-cloudflare' })]) }, + }); + }); + + it('does not inject the default server init', async () => { + const integration = sentryAstro({}); + + // @ts-expect-error - the hook exists and we only need to pass what we actually use + await integration.hooks['astro:config:setup']({ + ...baseConfigHookObject, + updateConfig, + injectScript, + config: cloudflareConfig, + }); + + expect(injectScript).not.toHaveBeenCalledWith('page-ssr', expect.anything()); + }); + + it('still injects a server init file', async () => { + const integration = sentryAstro({ serverInitPath: 'my-server-init-path.js' }); + + // @ts-expect-error - the hook exists and we only need to pass what we actually use + await integration.hooks['astro:config:setup']({ + ...baseConfigHookObject, + updateConfig, + injectScript, + config: cloudflareConfig, + }); + + expect(injectScript).toHaveBeenCalledWith('page-ssr', expect.stringContaining('my-server-init-path.js')); + }); + }); + it("doesn't add the orchestrion plugin for the cloudflare pages adapter", async () => { // Simulate a Pages project: a wrangler config containing `pages_build_output_dir`. wranglerPagesConfig = '{ "pages_build_output_dir": "./dist" }'; From 6b728c6c0e042e716d11b2d3c39b76672f346ac1 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 15 Oct 2026 12:03:17 -0700 Subject: [PATCH 013/118] fix(cloudflare): keep default exports intact when auto-instrumenting (#25372) The Vite transform rewrote `export default ` to `const __SENTRY_DEFAULT_EXPORT__ = ` and set the wrapped default export at the end of the module. That broke the module in four ways: - A parenthesized default export, such as `export default (handler);`, became a syntax error. The rewrite replaced everything up to the expression, which dropped the `(` and kept the `)`. - An anonymous class, function or arrow took the name `__SENTRY_DEFAULT_EXPORT__` in place of `default`. Libraries can read `.name`, as #24700 found for named classes. - `export default class Handler {}` lost the `Handler` binding, so other code in the module that used `Handler` threw. - A circular import that read the default export after the original `export default` statement, but before the end of the module, threw. The transform now replaces only the `export default` keywords, keeps a named class or function declaration as it is, and gives an anonymous one the name `default` by way of an object property. The default export is set in two steps, as for `export { handler as default }`. Where the original statement set it, it is bound to the value as-is, so a circular import gets the handler. At the end of the module, it is rebound to the wrapper, so methods added to the handler after `export default` are instrumented too. --------- Co-authored-by: Claude Opus 5.5 (1M context) --- packages/cloudflare/src/vite/transform.ts | 43 +++++++- .../test/vite/autoInstrument.test.ts | 32 +++--- .../cloudflare/test/vite/transform.test.ts | 102 ++++++++++++++++-- 3 files changed, 152 insertions(+), 25 deletions(-) diff --git a/packages/cloudflare/src/vite/transform.ts b/packages/cloudflare/src/vite/transform.ts index 5307ad48336b..273cfb16a766 100644 --- a/packages/cloudflare/src/vite/transform.ts +++ b/packages/cloudflare/src/vite/transform.ts @@ -393,14 +393,49 @@ function wrapDefaultExport(node: ExportDefaultNode, ctx: TransformContext, state } } - // `export default ` โ†’ `const __SENTRY_DEFAULT_EXPORT__ = ` - // MagicString positions are always relative to the original source. - state.ms.overwrite(node.start, decl.start, 'const __SENTRY_DEFAULT_EXPORT__ = '); - state.ms.append(`\nexport default __SENTRY__.withSentry(${state.optionsFn}, __SENTRY_DEFAULT_EXPORT__);\n`); + // MagicString positions are always relative to the original source. Only the `export default` + // keywords are replaced, so parentheses around the expression stay. + const source = state.ms.original; + const keywordsEnd = node.start + source.slice(node.start, decl.start).lastIndexOf('default') + 'default'.length; + const declaredName = + decl.type === 'ClassDeclaration' || decl.type === 'FunctionDeclaration' + ? (decl as ClassDeclarationNode).id?.name + : undefined; + + // As with `export { handler as default }`, the export is set in two steps: bound as-is where the + // original statement set it, then rebound at the end of the module to the wrapper, which sees + // methods added in between. See `wrapDefaultSpecifier`. + let wrapped = '__SENTRY_DEFAULT_EXPORT__'; + if (declaredName) { + // `export default class Handler {}`: the declaration stays, so `Handler` stays bound in the module. + state.ms.remove(node.start, decl.start); + state.ms.appendLeft(node.end, `\nvar __SENTRY_DEFAULT_EXPORT__ = ${declaredName};`); + wrapped = declaredName; + } else if (isAnonymousFunctionOrClass(decl)) { + // As a property value, the class or function gets the name `default`, as `export default` gives it. + const hasSemicolon = source[node.end - 1] === ';'; + state.ms.overwrite(node.start, keywordsEnd, 'var __SENTRY_DEFAULT_EXPORT__ = { default:'); + state.ms.appendLeft(hasSemicolon ? node.end - 1 : node.end, hasSemicolon ? ' }.default' : ' }.default;'); + } else { + state.ms.overwrite(node.start, keywordsEnd, 'var __SENTRY_DEFAULT_EXPORT__ ='); + } + state.ms.appendLeft(node.end, '\nexport { __SENTRY_DEFAULT_EXPORT__ as default };'); + state.ms.append(`\n__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(${state.optionsFn}, ${wrapped});`); state.needsImport = true; state.autoWrapped.add(DEFAULT_EXPORT); } +function isAnonymousFunctionOrClass(node: BaseNode): boolean { + if (node.type === 'ArrowFunctionExpression') return true; + const isFunctionOrClass = [ + 'FunctionExpression', + 'FunctionDeclaration', + 'ClassExpression', + 'ClassDeclaration', + ].includes(node.type); + return isFunctionOrClass && !(node as ClassDeclarationNode).id; +} + function handleNamedExport(node: ExportNamedNode, ctx: TransformContext, state: TransformState): void { const decl = node.declaration; diff --git a/packages/cloudflare/test/vite/autoInstrument.test.ts b/packages/cloudflare/test/vite/autoInstrument.test.ts index 9f987f6ea78a..7212a8fe55c8 100644 --- a/packages/cloudflare/test/vite/autoInstrument.test.ts +++ b/packages/cloudflare/test/vite/autoInstrument.test.ts @@ -76,8 +76,9 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => { expect(resolve).toHaveBeenCalledWith('@sentry/nextjs/cloudflare', join(dir, 'src/index.ts')); expect(result.code).toBe( "import * as __SENTRY__ from '@sentry/nextjs/cloudflare';\n" + - 'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' + - 'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);\n', + 'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' + + 'export { __SENTRY_DEFAULT_EXPORT__ as default };\n' + + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);', ); }); @@ -98,8 +99,9 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => { expect(result.code).toBe( "import * as __SENTRY__ from '@sentry/cloudflare';\n" + - 'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' + - 'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);\n', + 'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' + + 'export { __SENTRY_DEFAULT_EXPORT__ as default };\n' + + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);', ); }); @@ -118,8 +120,9 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => { expect(result.code).toBe( "import * as __SENTRY__ from '@sentry/cloudflare';\n" + - 'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' + - 'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);\n', + 'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' + + 'export { __SENTRY_DEFAULT_EXPORT__ as default };\n' + + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);', ); }); @@ -142,8 +145,9 @@ describe('sentryCloudflareAutoInstrumentPlugin', () => { expect(result.code).toBe( "import * as __SENTRY__ from '@sentry/cloudflare';\n" + - 'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' + - 'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);\n', + 'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };\n' + + 'export { __SENTRY_DEFAULT_EXPORT__ as default };\n' + + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);', ); }); @@ -518,9 +522,9 @@ describe('wranglerConfigPath option', () => { expect(result.code).toBe( [ "import * as __SENTRY__ from '@sentry/cloudflare';", - 'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };', - 'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);', - '', + 'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);', ].join('\n'), ); }); @@ -596,9 +600,9 @@ describe('package specifier as wrangler `main`', () => { expect(result.code).toBe( [ "import * as __SENTRY__ from '@sentry/cloudflare';", - 'const __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };', - 'export default __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);', - '', + 'var __SENTRY_DEFAULT_EXPORT__ = { fetch() { return new Response("ok"); } };', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry(() => undefined, __SENTRY_DEFAULT_EXPORT__);', ].join('\n'), ); }); diff --git a/packages/cloudflare/test/vite/transform.test.ts b/packages/cloudflare/test/vite/transform.test.ts index 8b3cd2ec207f..539b66c98961 100644 --- a/packages/cloudflare/test/vite/transform.test.ts +++ b/packages/cloudflare/test/vite/transform.test.ts @@ -52,7 +52,7 @@ describe('default export wrapping', () => { const result = transform(code, ctx)!; expect(result).toBeDefined(); expect(result.code).toContain("import * as __SENTRY__ from '@sentry/cloudflare'"); - expect(result.code).toContain('const __SENTRY_DEFAULT_EXPORT__ = handler'); + expect(result.code).toContain('var __SENTRY_DEFAULT_EXPORT__ = handler'); expect(result.code).toContain('__SENTRY__.withSentry((env) => ({}), __SENTRY_DEFAULT_EXPORT__)'); expect(result.code).not.toContain('export default handler'); expect(result.map).toBeDefined(); @@ -62,7 +62,7 @@ describe('default export wrapping', () => { const code = 'export default { fetch() { return new Response("ok"); } };'; const result = transform(code, ctx)!; expect(result).toBeDefined(); - expect(result.code).toContain('const __SENTRY_DEFAULT_EXPORT__ ='); + expect(result.code).toContain('var __SENTRY_DEFAULT_EXPORT__ ='); expect(result.code).toContain('__SENTRY__.withSentry('); }); @@ -286,6 +286,94 @@ describe('default export wrapping', () => { expect(result.code).toContain('tracesSampleRate: 1.0'); }); + /** + * Writes `files` to a temp dir, with `a.mjs` transformed and `withSentry` stubbed to mark each + * method it finds as instrumented, and returns what `node ` prints. + */ + function runWithEntry(files: Record, entry: string, transformEntry = true): string { + const dir = mkdtempSync(join(tmpdir(), 'sentry-cf-default-')); + try { + for (const [name, code] of Object.entries(files)) { + const out = + name === 'a.mjs' && transformEntry + ? transform(code, ctx)!.code.replace( + "import * as __SENTRY__ from '@sentry/cloudflare';", + 'const __SENTRY__ = { withSentry: (_options, handler) => { for (const key of Object.keys(handler)) handler[key].instrumented = true; return handler; } };', + ) + : code; + writeFileSync(join(dir, name), out); + } + return execFileSync(process.execPath, [join(dir, entry)], { encoding: 'utf8' }).trim(); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + } + + it.each([ + 'export default class { fetch() {} }', + 'export default function () {}', + 'export default () => {};', + 'export default (class { fetch() {} });', + 'export default class Handler { fetch() {} }', + 'export default function handler() {}', + ])('keeps the name of the default export of `%s`', code => { + const files = { 'a.mjs': code, 'b.mjs': "import handler from './a.mjs';\nconsole.log(handler.name);" }; + expect(runWithEntry(files, 'b.mjs')).toBe(runWithEntry(files, 'b.mjs', false)); + }); + + it('wraps a parenthesized default export', () => { + const code = ['const handler = { fetch() {} };', 'export default (handler);'].join('\n'); + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + 'const handler = { fetch() {} };', + 'var __SENTRY_DEFAULT_EXPORT__ = (handler);', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({}), __SENTRY_DEFAULT_EXPORT__);', + ].join('\n'), + ); + }); + + it('keeps the module binding of a named default class', () => { + const code = ['export default class Handler { fetch() {} }', 'Handler.ready = true;'].join('\n'); + + expect(transform(code, ctx)!.code).toBe( + [ + "import * as __SENTRY__ from '@sentry/cloudflare';", + 'class Handler { fetch() {} }', + 'var __SENTRY_DEFAULT_EXPORT__ = Handler;', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + 'Handler.ready = true;', + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({}), Handler);', + ].join('\n'), + ); + }); + + it('wraps a method added to the default export after `export default`', () => { + const files = { + 'a.mjs': ['const handler = {};', 'export default handler;', 'handler.fetch = function () {};'].join('\n'), + 'main.mjs': "import handler from './a.mjs';\nconsole.log(String(handler.fetch.instrumented));", + }; + + expect(runWithEntry(files, 'main.mjs')).toBe('true'); + }); + + it('sets the default export where the original statement set it', () => { + // `b` reads the default export of `a` while `a` is still evaluating, after its `export default`. + const files = { + 'a.mjs': [ + "import { readDefault } from './b.mjs';", + 'export default { fetch() {} };', + 'export const seen = readDefault();', + ].join('\n'), + 'b.mjs': "import handler from './a.mjs';\nexport function readDefault() { return typeof handler.fetch; }", + 'main.mjs': "import { seen } from './a.mjs';\nconsole.log(seen);", + }; + + expect(runWithEntry(files, 'main.mjs')).toBe('function'); + }); + it('skips when already wrapped with withSentry', () => { const code = [ "import { withSentry } from '@sentry/cloudflare';", @@ -932,9 +1020,9 @@ describe('agent class wrapping', () => { 'const __SENTRY_WRAPPED_MyDO__ = __SENTRY__.instrumentDurableObjectWithSentry((env) => ({ dsn: env.SENTRY_DSN }), MyDO);', 'export { __SENTRY_WRAPPED_MyDO__ as MyDO };', '', - 'const __SENTRY_DEFAULT_EXPORT__ = { fetch() {} };', - 'export default __SENTRY__.withSentry((env) => ({ dsn: env.SENTRY_DSN }), __SENTRY_DEFAULT_EXPORT__);', - '', + 'var __SENTRY_DEFAULT_EXPORT__ = { fetch() {} };', + 'export { __SENTRY_DEFAULT_EXPORT__ as default };', + '__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry((env) => ({ dsn: env.SENTRY_DSN }), __SENTRY_DEFAULT_EXPORT__);', ].join('\n'), ); }); @@ -1044,8 +1132,8 @@ describe('combined transforms', () => { expect(result.code).toContain('export { __SENTRY_WRAPPED_MyDO__ as MyDO };'); // Default export wrapped - expect(result.code).toContain('const __SENTRY_DEFAULT_EXPORT__ ='); - expect(result.code).toContain('export default __SENTRY__.withSentry('); + expect(result.code).toContain('var __SENTRY_DEFAULT_EXPORT__ ='); + expect(result.code).toContain('__SENTRY_DEFAULT_EXPORT__ = __SENTRY__.withSentry('); // Single import const importCount = (result.code.match(/import \* as __SENTRY__/g) ?? []).length; From 5a11c08ddc6ff4de314b075898e024dd8f7fc94a Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 16 Oct 2026 10:51:30 +0200 Subject: [PATCH 014/118] test(react): Fix flaky react-router-6 aborted SSE console assertion (#25398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `Waits for sse streaming when sse has been explicitly aborted` test checked for the abort error in the console immediately after the spans arrived. The `sse fetch call` span ends when `fetch` resolves, but the `console.error` only happens after the aborted `reader.read()` rejects, and Playwright delivers console events asynchronously. Nothing orders the two, so the console message sometimes hadn't been recorded yet. The assertion now polls for it. The assertion also matched on `BodyStreamBuffer was aborted`, which only older Chromium logs. Since Playwright 1.64 (#25382), Chromium rejects the read with the signal's abort reason (`AbortError: signal is aborted without reason`), so the test failed on every run there. It now matches any `AbortError`. ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- .../react-router-6/tests/sse.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts index 6b859c9e789c..ef967790bab4 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts @@ -85,11 +85,13 @@ test('Waits for sse streaming when sse has been explicitly aborted', async ({ pa expect(durationInSeconds(httpGet)).toBe(0); // Spans carry no breadcrumbs, so the abort error is validated on the console directly - expect( - consoleMessages.some( - message => message.includes('Could not fetch sse') && message.includes('BodyStreamBuffer was aborted'), - ), - ).toBe(true); + // The abort error is logged asynchronously after the span is sent, so it may not have arrived yet. + // Older Chromium rejects with "BodyStreamBuffer was aborted", newer with the signal's abort reason. + await expect + .poll(() => + consoleMessages.some(message => message.includes('Could not fetch sse') && message.includes('AbortError')), + ) + .toBe(true); }); test('Aborts when stream takes longer than 5s, by not updating the span duration', async ({ page }) => { From c4df4013912ed97bb30250b3ca93e4a3e01ecc4f Mon Sep 17 00:00:00 2001 From: "javascript-sdk-gitflow[bot]" <255134079+javascript-sdk-gitflow[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 09:35:47 +0000 Subject: [PATCH 015/118] chore(size-limit): weekly auto-bump (#25442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Size limit auto-bump | Entry | Old limit | New limit | ฮ” | | --- | --- | --- | --- | | @sentry/browser - with treeshaking flags | 33 KB | 34 KB | +1 KB | | @sentry/browser (incl. Tracing) | 57 KB | 58 KB | +1 KB | | @sentry/browser (incl. Tracing + Span Streaming) | 57 KB | 58 KB | +1 KB | | @sentry/browser (incl. Tracing, Profiling) | 60 KB | 61 KB | +1 KB | | @sentry/browser (incl. FeedbackAsync) | 45 KB | 46 KB | +1 KB | | @sentry/vue | 43 KB | 44 KB | +1 KB | | @sentry/vue (incl. Tracing) | 60 KB | 61 KB | +1 KB | | @sentry/remix (Remix 3 client bundle) | 62 KB | 63 KB | +1 KB | | CDN Bundle (incl. Tracing, Replay, Logs, Metrics) | 97 KB | 98 KB | +1 KB | | CDN Bundle - uncompressed | 98 KB | 99 KB | +1 KB | | CDN Bundle (incl. Tracing) - uncompressed | 161 KB | 162 KB | +1 KB | | CDN Bundle (incl. Tracing, Replay) - uncompressed | 281 KB | 282 KB | +1 KB | | CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed | 287 KB | 288 KB | +1 KB | | @sentry/core/server | 46 KB | 47 KB | +1 KB | | @sentry/node | 156 KB | 158 KB | +2 KB | | @sentry/node - without channel injection | 135 KB | 136 KB | +1 KB | | @sentry/cloudflare (withSentry) - minified | 210 KiB | 211 KiB | +1 KiB | | @sentry/cloudflare (withSentry) | 514 KiB | 516 KiB | +2 KiB | | @sentry/nextjs/cloudflare (withSentry) - minified | 228 KiB | 229 KiB | +1 KiB | Co-authored-by: chargome <20254395+chargome@users.noreply.github.com> --- .size-limit.js | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index b276522b06e3..7d94f22c1440 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -16,7 +16,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init'), gzip: true, - limit: '33 KB', + limit: '34 KB', disablePlugins: ['@size-limit/esbuild'], modifyWebpackConfig: function (config) { const webpack = require('webpack'); @@ -65,7 +65,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration'), gzip: true, - limit: '57 KB', + limit: '58 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -73,7 +73,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'spanStreamingIntegration'), gzip: true, - limit: '57 KB', + limit: '58 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -81,7 +81,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'browserProfilingIntegration'), gzip: true, - limit: '60 KB', + limit: '61 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -153,7 +153,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'feedbackAsyncIntegration'), gzip: true, - limit: '45 KB', + limit: '46 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -205,7 +205,7 @@ module.exports = [ path: 'packages/vue/build/esm/index.js', import: createImport('init'), gzip: true, - limit: '43 KB', + limit: '44 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -213,7 +213,7 @@ module.exports = [ path: 'packages/vue/build/esm/index.js', import: createImport('init', 'browserTracingIntegration'), gzip: true, - limit: '60 KB', + limit: '61 KB', disablePlugins: ['@size-limit/esbuild'], }, // Svelte SDK (ESM) @@ -235,7 +235,7 @@ module.exports = [ path: 'packages/remix/build/esm/v3/client-bundle.js', import: '*', gzip: true, - limit: '62 KB', + limit: '63 KB', disablePlugins: ['@size-limit/esbuild'], }, // Browser CDN bundles @@ -285,7 +285,7 @@ module.exports = [ name: 'CDN Bundle (incl. Tracing, Replay, Logs, Metrics)', path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'), gzip: true, - limit: '97 KB', + limit: '98 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -308,7 +308,7 @@ module.exports = [ path: createCDNPath('bundle.min.js'), gzip: false, brotli: false, - limit: '98 KB', + limit: '99 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -316,7 +316,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.min.js'), gzip: false, brotli: false, - limit: '161 KB', + limit: '162 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -348,7 +348,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.min.js'), gzip: false, brotli: false, - limit: '281 KB', + limit: '282 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -356,7 +356,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'), gzip: false, brotli: false, - limit: '287 KB', + limit: '288 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -401,7 +401,7 @@ module.exports = [ path: 'packages/core/build/esm/server.js', import: '*', gzip: true, - limit: '46 KB', + limit: '47 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -419,7 +419,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '156 KB', + limit: '158 KB', disablePlugins: ['@size-limit/esbuild'], }, { @@ -465,7 +465,7 @@ module.exports = [ path: 'packages/node/build/esm/index.js', import: createImport('init'), gzip: true, - limit: '135 KB', + limit: '136 KB', disablePlugins: ['@size-limit/esbuild'], ignore: [...builtinModules, ...nodePrefixedBuiltinModules], modifyWebpackConfig: function (config) { @@ -497,7 +497,7 @@ module.exports = [ ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, brotli: false, - limit: '210 KiB', + limit: '211 KiB', disablePlugins: ['@size-limit/webpack'], webpack: false, modifyEsbuildConfig: function (config) { @@ -517,7 +517,7 @@ module.exports = [ ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, brotli: false, - limit: '514 KiB', + limit: '516 KiB', disablePlugins: ['@size-limit/webpack'], webpack: false, modifyEsbuildConfig: function (config) { @@ -542,7 +542,7 @@ module.exports = [ ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, brotli: false, - limit: '228 KiB', + limit: '229 KiB', disablePlugins: ['@size-limit/webpack'], webpack: false, modifyEsbuildConfig: function (config) { From 800514fd69018442930e6c0c8a045b705c81b086 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:24:24 +0200 Subject: [PATCH 016/118] feat(deps): Bump recast from 0.20.5 to 0.24.0 (#25416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [recast](https://github.com/benjamn/recast) from 0.20.5 to 0.24.0.
Release notes

Sourced from recast's releases.

v0.21.1

What's Changed

New Contributors

Full Changelog: https://github.com/benjamn/recast/compare/v0.21.0...v0.21.1

Commits
  • 93325e3 0.24.0
  • e06a58b Reapply "chore: assume LTS versions of Node.js"
  • 02bcdf3 0.23.21
  • 2bbebf6 Revert "chore: assume LTS versions of Node.js"
  • e8ac5e0 0.23.20
  • 1821520 Merge pull request #1430 from mahirhir/fix/optional-chain-operand-parens
  • 1005588 Merge pull request #1438 from theRizwan/fix/comment-tokens-hide-parens
  • a3d2bd9 Remove unnecessary blank line in comments test
  • 20a42ef fix: comments in loc.tokens hide parentheses from hasParens
  • 11471f2 Merge pull request #1437 from benjamn/chore/assume-lts-nodejs
  • Additional commits viewable in compare view
Install script changes

This version adds prepare script that runs during installation. Review the package contents before updating.


Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-packages/rollup-utils/package.json | 2 +- yarn.lock | 26 ++++++++++---------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json index 0cba489d146a..dae0678b90d8 100644 --- a/dev-packages/rollup-utils/package.json +++ b/dev-packages/rollup-utils/package.json @@ -10,7 +10,7 @@ "main": "./index.mjs", "dependencies": { "acorn": "^8.7.0", - "recast": "^0.20.5" + "recast": "^0.24.0" }, "devDependencies": { "eslint-plugin-regexp": "^3.1.0" diff --git a/yarn.lock b/yarn.lock index 17ff00432901..3eea2f47b24e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11442,13 +11442,6 @@ ast-types@0.13.3: resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.3.tgz#50da3f28d17bdbc7969a3a2d83a0e4a72ae755a7" integrity sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA== -ast-types@0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd" - integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA== - dependencies: - tslib "^2.0.1" - ast-types@^0.16.1: version "0.16.1" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.16.1.tgz#7a9da1617c9081bc121faafe91711b4c8bb81da2" @@ -23821,20 +23814,21 @@ recast@^0.18.1: private "^0.1.8" source-map "~0.6.1" -recast@^0.20.5: - version "0.20.5" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.20.5.tgz#8e2c6c96827a1b339c634dd232957d230553ceae" - integrity sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ== +recast@^0.23.4: + version "0.23.11" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.11.tgz#8885570bb28cf773ba1dc600da7f502f7883f73f" + integrity sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA== dependencies: - ast-types "0.14.2" + ast-types "^0.16.1" esprima "~4.0.0" source-map "~0.6.1" + tiny-invariant "^1.3.3" tslib "^2.0.1" -recast@^0.23.4: - version "0.23.11" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.23.11.tgz#8885570bb28cf773ba1dc600da7f502f7883f73f" - integrity sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA== +recast@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.24.0.tgz#78e2eb15b13222d00deb4ede0bfacbd1b48ee710" + integrity sha512-dN08tw9Kl2i8oDBy16NhxBh6qOqsEqmGYpX1xaxPmgz6/6ukopSuysLfvLbieuDMY57jEz6eTmNz6lqH54kuqQ== dependencies: ast-types "^0.16.1" esprima "~4.0.0" From 11ed5c2d1533181a9f93d27f2557dcc64cfe5125 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:38:47 +0200 Subject: [PATCH 017/118] feat(deps): Bump @actions/glob from 0.6.1 to 0.7.0 (#25411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@actions/glob](https://github.com/actions/toolkit/tree/HEAD/packages/glob) from 0.6.1 to 0.7.0.
Changelog

Sourced from @โ€‹actions/glob's changelog.

0.7.0

  • Bump minimatch from ^3.0.4 to ^10.2.5 #2355
  • Bump undici from 6.23.0 to 6.24.0 #2345
  • Bump brace-expansion in /packages/glob #2369
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@actions/glob&package-manager=npm_and_yarn&previous-version=0.6.1&new-version=0.7.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lukas Stracke --- .../size-limit-gh-action/package.json | 2 +- yarn.lock | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json index dd29b9ad6132..cc5f98509f15 100644 --- a/dev-packages/size-limit-gh-action/package.json +++ b/dev-packages/size-limit-gh-action/package.json @@ -18,7 +18,7 @@ "@actions/core": "1.10.1", "@actions/exec": "1.1.1", "@actions/github": "^5.0.0", - "@actions/glob": "0.6.1", + "@actions/glob": "0.7.0", "@actions/io": "3.0.2", "bytes-iec": "3.1.1", "markdown-table": "3.0.3" diff --git a/yarn.lock b/yarn.lock index 3eea2f47b24e..ad2e99570c10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -75,13 +75,13 @@ "@octokit/request-error" "^7.1.0" undici "^6.23.0" -"@actions/glob@0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@actions/glob/-/glob-0.6.1.tgz#4820bfd86175dd98cd63d3d557e47bb09a21c7d0" - integrity sha512-K4+2Ac5ILcf2ySdJCha+Pop9NcKjxqCL4xL4zI50dgB2PbXgC0+AcP011xfH4Of6b4QEJJg8dyZYv7zl4byTsw== +"@actions/glob@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@actions/glob/-/glob-0.7.0.tgz#f88c79eb1b0a185d1cbdc662c79537d568756937" + integrity sha512-+7s3wM+cXapDLmLL1NVWHawqcJOZzXZy2df/VhNn8DnZtS/x83iTCKaUn9F0llur4h3CII0AilvKKH4CMPL8Gw== dependencies: "@actions/core" "^3.0.0" - minimatch "^3.0.4" + minimatch "^10.2.5" "@actions/http-client@^2.0.1": version "2.2.3" @@ -12136,7 +12136,7 @@ brace-expansion@^2.0.1, brace-expansion@^2.0.2: dependencies: balanced-match "^1.0.0" -brace-expansion@^5.0.5: +brace-expansion@^5.0.5, brace-expansion@^5.0.8: version "5.0.12" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.12.tgz#995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec" integrity sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ== @@ -20125,7 +20125,7 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@10.2.5, minimatch@^10.2.2: +minimatch@10.2.5: version "10.2.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== @@ -20146,6 +20146,13 @@ minimatch@5.1.0, minimatch@5.1.9, minimatch@^5.0.1, minimatch@^5.1.0: dependencies: brace-expansion "^2.0.1" +minimatch@^10.2.2, minimatch@^10.2.5: + version "10.2.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef" + integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A== + dependencies: + brace-expansion "^5.0.8" + minimatch@^8.0.2: version "8.0.7" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.7.tgz#954766e22da88a3e0a17ad93b58c15c9d8a579de" From 28a69bca4606ffc8812b11eba136a58108af88de Mon Sep 17 00:00:00 2001 From: Rola Abuhasna Date: Fri, 16 Oct 2026 13:37:14 +0200 Subject: [PATCH 018/118] feat(server-utils): Emit `execute_tool` spans for Anthropic's `beta.messages.toolRunner()` (#25320) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `beta.messages.toolRunner()` runs the user's tools, but Sentry only recorded its chat calls. Tool calls got no span, and when a tool threw, the runner turned the error into an `is_error` result for the model, so nobody saw it. This wraps each tool's `run()` in place with `fill()`. When a tool throws, its span is marked as an error. The exception is captured as handled only when the call came from a runner. A direct call rethrows to its caller, so it isn't captured, and neither are intentional `ToolError` results and aborted calls. Toolsets are skipped, since their `run` takes `(ctx, toolUse)`. `sessions.events.toolRunner()` isn't covered yet. Fixes #25288 ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 --- .../anthropic/v0.129/scenario-tool-runner.mjs | 136 +++++++++ .../suites/tracing/anthropic/v0.129/test.ts | 67 ++++- .../src/ai/anthropic-ai/constants.ts | 2 + .../server-utils/src/ai/anthropic-ai/index.ts | 10 +- .../src/ai/anthropic-ai/tool-runner.ts | 139 +++++++++ .../src/integrations/anthropic.ts | 12 +- .../src/orchestrion/config/anthropic-ai.ts | 6 + .../tracing/anthropic-ai-tool-runner.test.ts | 266 ++++++++++++++++++ 8 files changed, 632 insertions(+), 6 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/anthropic/v0.129/scenario-tool-runner.mjs create mode 100644 packages/server-utils/src/ai/anthropic-ai/tool-runner.ts create mode 100644 packages/server-utils/test/ai/lib/tracing/anthropic-ai-tool-runner.test.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/v0.129/scenario-tool-runner.mjs b/dev-packages/node-integration-tests/suites/tracing/anthropic/v0.129/scenario-tool-runner.mjs new file mode 100644 index 000000000000..851c8cd4aee5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/v0.129/scenario-tool-runner.mjs @@ -0,0 +1,136 @@ +import Anthropic from '@anthropic-ai/sdk'; +import * as Sentry from '@sentry/node'; +import express from 'express'; + +const MODEL = 'claude-3-haiku-20240307'; + +function toolUseEvents() { + const events = [ + { + type: 'message_start', + message: { + id: 'msg_tools', + type: 'message', + role: 'assistant', + model: MODEL, + content: [], + usage: { input_tokens: 10 }, + }, + }, + ]; + [ + { id: 'toolu_weather', name: 'weather', input: { city: 'Vienna' } }, + { id: 'toolu_explode', name: 'explode', input: {} }, + ].forEach((call, index) => { + events.push( + { + type: 'content_block_start', + index, + content_block: { type: 'tool_use', id: call.id, name: call.name, input: {} }, + }, + { + type: 'content_block_delta', + index, + delta: { type: 'input_json_delta', partial_json: JSON.stringify(call.input) }, + }, + { type: 'content_block_stop', index }, + ); + }); + events.push( + { type: 'message_delta', delta: { stop_reason: 'tool_use', stop_sequence: null }, usage: { output_tokens: 20 } }, + { type: 'message_stop' }, + ); + return events; +} + +function finalEvents() { + return [ + { + type: 'message_start', + message: { + id: 'msg_final', + type: 'message', + role: 'assistant', + model: MODEL, + content: [], + usage: { input_tokens: 30 }, + }, + }, + { type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } }, + { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: 'Sunny.' } }, + { type: 'content_block_stop', index: 0 }, + { type: 'message_delta', delta: { stop_reason: 'end_turn', stop_sequence: null }, usage: { output_tokens: 5 } }, + { type: 'message_stop' }, + ]; +} + +function startMockAnthropicServer() { + const app = express(); + app.use(express.json()); + + let requests = 0; + app.post('/anthropic/v1/messages', (_req, res) => { + res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' }); + + const events = requests++ === 0 ? toolUseEvents() : finalEvents(); + events.forEach((event, index) => { + setTimeout(() => { + res.write(`event: ${event.type}\n`); + res.write(`data: ${JSON.stringify(event)}\n\n`); + if (index === events.length - 1) { + res.end(); + } + }, index * 10); + }); + }); + + return new Promise(resolve => { + const server = app.listen(0, () => { + resolve(server); + }); + }); +} + +async function run() { + const server = await startMockAnthropicServer(); + + await Sentry.startSpan({ op: 'function', name: 'main' }, async () => { + const client = new Anthropic({ + apiKey: 'mock-api-key', + baseURL: `http://localhost:${server.address().port}/anthropic`, + }); + + const runner = client.beta.messages.toolRunner({ + model: MODEL, + max_tokens: 100, + messages: [{ role: 'user', content: 'What is the weather in Vienna?' }], + tools: [ + { + name: 'weather', + description: 'Get the weather by city', + input_schema: { type: 'object', properties: { city: { type: 'string' } } }, + run: () => 'sunny', + }, + { + name: 'explode', + description: 'Always fails', + input_schema: { type: 'object', properties: {} }, + run: () => { + throw new Error('Tool exploded'); + }, + }, + ], + stream: true, + runToolsEagerly: true, + }); + for await (const stream of runner) { + await stream.finalMessage(); + } + }); + + await Sentry.flush(2000); + + server.close(); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/v0.129/test.ts b/dev-packages/node-integration-tests/suites/tracing/anthropic/v0.129/test.ts index 05eac304d0e1..bc4c9fef4d35 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/v0.129/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/v0.129/test.ts @@ -1,5 +1,13 @@ -import { GEN_AI_RESPONSE_ID, GEN_AI_RESPONSE_STREAMING, SENTRY_OP } from '@sentry/conventions/attributes'; +import type { Event, SerializedStreamedSpanContainer } from '@sentry/core'; +import { + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_STREAMING, + GEN_AI_TOOL_NAME, + SENTRY_OP, +} from '@sentry/conventions/attributes'; +import { GEN_AI_EXECUTE_TOOL } from '@sentry/conventions/op'; import { afterAll, describe, expect } from 'vitest'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../../../packages/server-utils/src/ai/core/gen-ai-attributes'; import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner'; // The attribute extraction is version-independent and covered by the suite pinned to 0.63. What a @@ -72,4 +80,61 @@ describe('Anthropic integration (0.129)', () => { }, }, ); + + createEsmAndCjsTests( + __dirname, + 'scenario-tool-runner.mjs', + 'instrument.mjs', + (createRunner, test) => { + test('emits an execute_tool span for each tool the eager runner starts', async () => { + let receivedEvent: Event | undefined; + let receivedSpans: SerializedStreamedSpanContainer['items'] = []; + + await createRunner() + .unordered() + .expect({ + span: container => { + expect(container.items.find(span => span.is_segment && span.name === 'main')).toBeDefined(); + receivedSpans = container.items; + }, + }) + .expect({ + event: event => { + receivedEvent = event; + }, + }) + .start() + .completed(); + + const main = receivedSpans.find(span => span.is_segment && span.name === 'main')!; + const chat = receivedSpans.find(span => span.attributes[GEN_AI_RESPONSE_ID]?.value === 'msg_tools')!; + const weather = receivedSpans.find(span => span.attributes[GEN_AI_TOOL_NAME]?.value === 'weather')!; + const explode = receivedSpans.find(span => span.attributes[GEN_AI_TOOL_NAME]?.value === 'explode')!; + + expect(weather).toMatchObject({ + name: 'execute_tool weather', + parent_span_id: main.span_id, + status: 'ok', + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: GEN_AI_EXECUTE_TOOL }, + [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: { type: 'string', value: 'toolu_weather' }, + }), + }); + // The eager runner starts the first tool while the reply that called it is still streaming. + expect(weather.start_timestamp).toBeLessThan(chat.end_timestamp); + + expect(explode).toMatchObject({ name: 'execute_tool explode', parent_span_id: main.span_id, status: 'error' }); + expect(receivedEvent).toMatchObject({ + exception: { + values: [{ value: 'Tool exploded', mechanism: { type: 'auto.ai.anthropic', handled: true } }], + }, + }); + }); + }, + { + additionalDependencies: { + '@anthropic-ai/sdk': '0.129.0', + }, + }, + ); }); diff --git a/packages/server-utils/src/ai/anthropic-ai/constants.ts b/packages/server-utils/src/ai/anthropic-ai/constants.ts index 13cc9fea5d4e..4d19d399021b 100644 --- a/packages/server-utils/src/ai/anthropic-ai/constants.ts +++ b/packages/server-utils/src/ai/anthropic-ai/constants.ts @@ -9,3 +9,5 @@ export const ANTHROPIC_METHOD_REGISTRY = { 'completions.create': { operation: 'chat' }, 'beta.messages.create': { operation: 'chat' }, } as const satisfies InstrumentedMethodRegistry; + +export const ANTHROPIC_AI_ORIGIN = 'auto.ai.anthropic'; diff --git a/packages/server-utils/src/ai/anthropic-ai/index.ts b/packages/server-utils/src/ai/anthropic-ai/index.ts index 173ff3c638d4..199649884f62 100644 --- a/packages/server-utils/src/ai/anthropic-ai/index.ts +++ b/packages/server-utils/src/ai/anthropic-ai/index.ts @@ -25,8 +25,9 @@ import { resolveAIRecordingOptions, wrapPromiseWithMethods, } from '../core/utils'; -import { ANTHROPIC_METHOD_REGISTRY } from './constants'; +import { ANTHROPIC_AI_ORIGIN, ANTHROPIC_METHOD_REGISTRY } from './constants'; import { instrumentAsyncIterableStream, instrumentMessageStream } from './streaming'; +import { instrumentToolRunnerMethod } from './tool-runner'; import type { AnthropicAiOptions, AnthropicAiResponse, AnthropicAiStreamingEvent, ContentBlock } from './types'; import { handleResponseError, messagesFromParams, setMessagesAttribute } from './utils'; @@ -50,7 +51,7 @@ export function extractRequestAttributes( const attributes: Record = { [GEN_AI_PROVIDER_NAME]: 'anthropic', [GEN_AI_OPERATION_NAME]: operationName, - [SENTRY_ORIGIN]: 'auto.ai.anthropic', + [SENTRY_ORIGIN]: ANTHROPIC_AI_ORIGIN, }; if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) { @@ -350,6 +351,11 @@ function instrumentClientInPlace(client: T, options: Anthropic owner[methodName] = instrumented; } + const betaMessages = (client as { beta?: { messages?: object } }).beta?.messages; + if (betaMessages) { + instrumentToolRunnerMethod(betaMessages, options); + } + return client; } diff --git a/packages/server-utils/src/ai/anthropic-ai/tool-runner.ts b/packages/server-utils/src/ai/anthropic-ai/tool-runner.ts new file mode 100644 index 000000000000..795b17cffef1 --- /dev/null +++ b/packages/server-utils/src/ai/anthropic-ai/tool-runner.ts @@ -0,0 +1,139 @@ +import type { Span, WrappedFunction } from '@sentry/core'; +import { + captureException, + fill, + getOriginalFunction, + handleCallbackErrors, + SPAN_STATUS_ERROR, + startSpan, + stringify, +} from '@sentry/core'; +import { + GEN_AI_OPERATION_NAME, + GEN_AI_TOOL_CALL_ARGUMENTS, + GEN_AI_TOOL_CALL_RESULT, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_NAME, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; +import { GEN_AI_EXECUTE_TOOL } from '@sentry/conventions/op'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../core/gen-ai-attributes'; +import { resolveAIRecordingOptions } from '../core/utils'; +import { ANTHROPIC_AI_ORIGIN } from './constants'; +import type { AnthropicAiOptions } from './types'; + +type AnyFunction = (...args: unknown[]) => unknown; +type RunnableTool = { name: string; description?: unknown }; +type RunContext = { toolUse?: { id?: unknown }; toolUseBlock?: { id?: unknown }; signal?: AbortSignal } | undefined; +type ToolRunner = { params?: { tools?: unknown } }; + +/** Wrap `beta.messages.toolRunner()` so the runners it returns trace their tool calls. */ +export function instrumentToolRunnerMethod(messages: object, options: AnthropicAiOptions): void { + wrapInPlace(messages, 'toolRunner', (toolRunner, thisArg, args) => { + const runner = toolRunner.apply(thisArg, args); + instrumentToolRunner(runner, options); + return runner; + }); +} + +/** Give each tool call of a tool runner a `gen_ai.execute_tool` span. */ +export function instrumentToolRunner(runner: unknown, options: AnthropicAiOptions): void { + if (!runner || typeof runner !== 'object') { + return; + } + + instrumentTools((runner as ToolRunner).params?.tools, options); + + // `addTools()` keeps its tools out of `params`. + wrapInPlace(runner, 'addTools', (addTools, thisArg, tools) => { + instrumentTools(tools, options); + return addTools.apply(thisArg, tools); + }); + wrapInPlace(runner, 'setMessagesParams', (setMessagesParams, thisArg, args) => { + const result = setMessagesParams.apply(thisArg, args); + instrumentTools((runner as ToolRunner).params?.tools, options); + return result; + }); +} + +function instrumentTools(tools: unknown, options: AnthropicAiOptions): void { + if (!Array.isArray(tools)) { + return; + } + + for (const tool of tools) { + // Toolsets are nameless entries whose `run` takes `(ctx, toolUse)`, so only named tools are wrapped. + if (tool && typeof tool === 'object' && typeof (tool as RunnableTool).name === 'string') { + wrapInPlace(tool, 'run', (run, thisArg, args) => runWithSpan(tool as RunnableTool, run, thisArg, args, options)); + } + } +} + +function wrapInPlace( + owner: object, + key: string, + call: (original: AnyFunction, thisArg: unknown, args: unknown[]) => unknown, +): void { + const current = (owner as Record)[key]; + if (typeof current === 'function' && !getOriginalFunction(current as WrappedFunction)) { + fill( + owner, + key, + (original: AnyFunction) => + function (this: unknown, ...args: unknown[]): unknown { + return call(original, this, args); + }, + ); + } +} + +function runWithSpan( + tool: RunnableTool, + run: AnyFunction, + thisArg: unknown, + args: unknown[], + options: AnthropicAiOptions, +): unknown { + const { recordInputs, recordOutputs } = resolveAIRecordingOptions(options); + const [input, context] = args as [unknown, RunContext]; + // SDK versions before `toolUse` existed pass only `toolUseBlock`. + const callId = (context?.toolUse ?? context?.toolUseBlock)?.id; + + return startSpan( + { + name: `execute_tool ${tool.name}`, + op: GEN_AI_EXECUTE_TOOL, + attributes: { + [SENTRY_ORIGIN]: ANTHROPIC_AI_ORIGIN, + [GEN_AI_OPERATION_NAME]: 'execute_tool', + [GEN_AI_TOOL_NAME]: tool.name, + ...(typeof tool.description === 'string' ? { [GEN_AI_TOOL_DESCRIPTION]: tool.description } : {}), + ...(typeof callId === 'string' ? { [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: callId } : {}), + ...(recordInputs ? { [GEN_AI_TOOL_CALL_ARGUMENTS]: stringify(input) } : {}), + }, + }, + span => + handleCallbackErrors( + () => run.apply(thisArg, args), + error => handleToolError(span, error, context), + undefined, + result => recordOutputs && span.setAttribute(GEN_AI_TOOL_CALL_RESULT, stringify(result)), + ), + ); +} + +function handleToolError(span: Span, error: unknown, context: RunContext): void { + if (context?.signal?.aborted) { + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' }); + return; + } + + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + + // Only a runner passes `toolUse`/`toolUseBlock` (SDK 0.85+), and it hands the throw to the model as a result. + // A direct call rethrows to its caller, and a `ToolError` is sent on purpose, so neither is captured. + const fromRunner = !!(context?.toolUse ?? context?.toolUseBlock); + if (fromRunner && (error as Error | undefined)?.name !== 'ToolError') { + captureException(error, { mechanism: { handled: true, type: ANTHROPIC_AI_ORIGIN } }); + } +} diff --git a/packages/server-utils/src/integrations/anthropic.ts b/packages/server-utils/src/integrations/anthropic.ts index a45bbf02496f..f2905c397abf 100644 --- a/packages/server-utils/src/integrations/anthropic.ts +++ b/packages/server-utils/src/integrations/anthropic.ts @@ -12,7 +12,9 @@ import { import { getGenAiSpanOp, resolveAIRecordingOptions } from '../ai/core/utils'; import { wrapApiPromiseResponse } from '../ai/core/apiPromise'; import { addPrivateRequestAttributes, addResponseAttributes, extractRequestAttributes } from '../ai/anthropic-ai'; +import { ANTHROPIC_AI_ORIGIN } from '../ai/anthropic-ai/constants'; import { instrumentAsyncIterableStream, instrumentMessageStream } from '../ai/anthropic-ai/streaming'; +import { instrumentToolRunner } from '../ai/anthropic-ai/tool-runner'; import type { AnthropicAiOptions, AnthropicAiResponse } from '../ai/anthropic-ai/types'; import type { OrchestrionChannelContext } from '../orchestrion/types'; import { CHANNELS } from '../orchestrion/channels'; @@ -24,8 +26,6 @@ import { invokeOrchestrionInstrumentation } from '../orchestrion/instrumentation // integration is deduplicated out of the default set. const INTEGRATION_NAME = 'Anthropic_AI' as const; -const ORIGIN = 'auto.ai.anthropic'; - // `stream` determines how the span is ended const INSTRUMENTED_CHANNELS = [ { channel: CHANNELS.ANTHROPIC_CHAT, operation: 'chat', stream: 'async-iterable' }, @@ -74,6 +74,12 @@ function instrumentAnthropic(options: AnthropicAiOptions): void { }, ); } + + diagnosticsChannel + .tracingChannel(CHANNELS.ANTHROPIC_TOOL_RUNNER) + .end.subscribe(message => { + instrumentToolRunner((message as OrchestrionChannelContext).result, options); + }); } /** @@ -110,7 +116,7 @@ function createGenAiSpan( const attributes = extractRequestAttributes(args, operation, recordInputs); const model = (attributes[GEN_AI_REQUEST_MODEL] as string) || 'unknown'; - attributes[SENTRY_ORIGIN] = ORIGIN; + attributes[SENTRY_ORIGIN] = ANTHROPIC_AI_ORIGIN; const client = getClient(); const span = startInactiveSpan({ diff --git a/packages/server-utils/src/orchestrion/config/anthropic-ai.ts b/packages/server-utils/src/orchestrion/config/anthropic-ai.ts index 0f530e1db778..214b73a09fc0 100644 --- a/packages/server-utils/src/orchestrion/config/anthropic-ai.ts +++ b/packages/server-utils/src/orchestrion/config/anthropic-ai.ts @@ -26,6 +26,11 @@ export const anthropicAiConfig = [ module: { name: '@anthropic-ai/sdk', versionRange: '>=0.19.2 <1', filePath }, functionQuery: { className: 'Messages', methodName: 'stream', kind: 'Sync' as const }, })), + ...['resources/beta/messages/messages.js', 'resources/beta/messages/messages.mjs'].map(filePath => ({ + channelName: 'tool-runner', + module: { name: '@anthropic-ai/sdk', versionRange: '>=0.63.0 <1', filePath }, + functionQuery: { className: 'Messages', methodName: 'toolRunner', kind: 'Sync' as const }, + })), ] satisfies InstrumentationConfig[]; export const anthropicAiModuleNames = getModuleNames(anthropicAiConfig); @@ -33,4 +38,5 @@ export const anthropicAiModuleNames = getModuleNames(anthropicAiConfig); export const anthropicAiChannels = { ANTHROPIC_CHAT: 'orchestrion:@anthropic-ai/sdk:chat', ANTHROPIC_MESSAGES_STREAM: 'orchestrion:@anthropic-ai/sdk:messages-stream', + ANTHROPIC_TOOL_RUNNER: 'orchestrion:@anthropic-ai/sdk:tool-runner', } as const; diff --git a/packages/server-utils/test/ai/lib/tracing/anthropic-ai-tool-runner.test.ts b/packages/server-utils/test/ai/lib/tracing/anthropic-ai-tool-runner.test.ts new file mode 100644 index 000000000000..4c99901f21a5 --- /dev/null +++ b/packages/server-utils/test/ai/lib/tracing/anthropic-ai-tool-runner.test.ts @@ -0,0 +1,266 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { ErrorEvent, Span } from '@sentry/core'; +import { getMainCarrier, setCurrentClient, spanToStaticSpanJSON, startSpan } from '@sentry/core'; +import { + GEN_AI_OPERATION_NAME, + GEN_AI_TOOL_CALL_ARGUMENTS, + GEN_AI_TOOL_CALL_RESULT, + GEN_AI_TOOL_DESCRIPTION, + GEN_AI_TOOL_NAME, +} from '@sentry/conventions/attributes'; +import { GEN_AI_EXECUTE_TOOL } from '@sentry/conventions/op'; +import { instrumentAnthropicAiClient } from '../../../../src/ai/anthropic-ai'; +import { GEN_AI_TOOL_CALL_ID_ATTRIBUTE } from '../../../../src/ai/core/gen-ai-attributes'; +import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client'; + +type Tool = { name: string; description?: string; input_schema: object; run: (...args: unknown[]) => unknown }; +type Params = { tools?: unknown[] }; + +function tool(name: string, run: (...args: unknown[]) => unknown): Tool { + return { name, description: `${name} tool`, input_schema: { type: 'object' }, run }; +} + +// Mirrors the SDK runner: `params` reads the live params, `addTools()` keeps its tools out of them. +class FakeRunner { + public added: unknown[] = []; + public constructor(public params: Params) {} + public addTools(...tools: unknown[]): void { + this.added.push(...tools); + } + public setMessagesParams(paramsOrMutator: Params | ((prev: Params) => Params)): void { + this.params = typeof paramsOrMutator === 'function' ? paramsOrMutator(this.params) : paramsOrMutator; + } +} + +function fakeClient() { + return { + beta: { + messages: { + create: vi.fn(), + toolRunner: vi.fn((params: Params) => { + if (!params) { + throw new TypeError('params required'); + } + return new FakeRunner(params); + }), + }, + }, + }; +} + +describe('instrumentAnthropicAiClient tool runner', () => { + let endedSpans: Span[]; + let events: ErrorEvent[]; + + beforeEach(() => { + getMainCarrier().__SENTRY__ = undefined; + endedSpans = []; + events = []; + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1, + beforeSend: event => { + events.push(event); + return null; + }, + }), + ); + setCurrentClient(client); + client.init(); + client.on('spanEnd', span => endedSpans.push(span)); + }); + + afterEach(() => { + getMainCarrier().__SENTRY__ = undefined; + }); + + function toolSpans() { + return endedSpans.map(span => spanToStaticSpanJSON(span)).filter(span => span.op === GEN_AI_EXECUTE_TOOL); + } + + it('wraps each tool call of the runner in an execute_tool span under the active span', async () => { + const weather = tool('weather', async () => 'sunny'); + const serverTool = { type: 'web_search_20250305', name: 'web_search' }; + const serialized = JSON.stringify([weather, serverTool]); + + instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [weather, serverTool] }); + const parent = await startSpan({ name: 'agent' }, async span => { + await weather.run({ city: 'Vienna' }, { toolUse: { id: 'toolu_1' } }); + return spanToStaticSpanJSON(span); + }); + + expect(JSON.stringify([weather, serverTool])).toBe(serialized); + expect(toolSpans()).toEqual([ + expect.objectContaining({ + description: 'execute_tool weather', + parent_span_id: parent.span_id, + origin: 'auto.ai.anthropic', + data: expect.objectContaining({ + [GEN_AI_OPERATION_NAME]: 'execute_tool', + [GEN_AI_TOOL_NAME]: 'weather', + [GEN_AI_TOOL_DESCRIPTION]: 'weather tool', + [GEN_AI_TOOL_CALL_ID_ATTRIBUTE]: 'toolu_1', + [GEN_AI_TOOL_CALL_ARGUMENTS]: '{"city":"Vienna"}', + [GEN_AI_TOOL_CALL_RESULT]: 'sunny', + }), + }), + ]); + }); + + it('reads the call id from toolUseBlock on SDKs that pass only that', () => { + const weather = tool('weather', () => 'sunny'); + + instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [weather] }); + weather.run({}, { toolUseBlock: { id: 'toolu_old' } }); + + expect(toolSpans()[0]!.data[GEN_AI_TOOL_CALL_ID_ATTRIBUTE]).toBe('toolu_old'); + }); + + it('leaves out the attributes a call does not have', () => { + const bare = { name: 'bare', input_schema: {}, run: () => undefined }; + + instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [bare] }); + bare.run({}); + + const { data } = toolSpans()[0]!; + for (const key of [GEN_AI_TOOL_DESCRIPTION, GEN_AI_TOOL_CALL_ID_ATTRIBUTE, GEN_AI_TOOL_CALL_RESULT]) { + expect(data).not.toHaveProperty([key]); + } + }); + + it('keeps a synchronous run synchronous', () => { + const weather = tool('weather', () => 'sunny'); + + instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [weather] }); + + expect(weather.run({})).toBe('sunny'); + expect(toolSpans()).toHaveLength(1); + }); + + it.each([ + ['async', async () => Promise.reject(new Error('boom'))], + [ + 'sync', + () => { + throw new Error('boom'); + }, + ], + ])('marks a failed %s tool call and captures the error the runner swallows', async (_, run) => { + const explode = tool('explode', run); + + instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [explode] }); + await expect(async () => explode.run({}, { toolUse: { id: 'toolu_1' } })).rejects.toThrow('boom'); + + expect(toolSpans()[0]!.status).toBe('internal_error'); + await vi.waitFor(() => expect(events).toHaveLength(1)); + expect(events[0]!.exception?.values?.[0]).toMatchObject({ + value: 'boom', + mechanism: { type: 'auto.ai.anthropic', handled: true }, + }); + }); + + it('marks but does not capture a throw from a direct call, which reaches its caller', async () => { + const explode = tool('explode', async () => { + throw new Error('boom'); + }); + + instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [explode] }); + await expect(explode.run({})).rejects.toThrow('boom'); + + expect(toolSpans()[0]!.status).toBe('internal_error'); + expect(events).toHaveLength(0); + }); + + it('does not capture a ToolError or a call aborted by the runner', async () => { + const toolError = Object.assign(new Error('bad input'), { name: 'ToolError' }); + const refuses = tool('refuses', async () => { + throw toolError; + }); + const aborted = tool('aborted', async () => { + throw new Error('aborted'); + }); + + instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [refuses, aborted] }); + await expect(refuses.run({}, { toolUse: { id: 'toolu_1' } })).rejects.toBe(toolError); + await expect(aborted.run({}, { toolUse: { id: 'toolu_2' }, signal: AbortSignal.abort() })).rejects.toThrow( + 'aborted', + ); + + expect(toolSpans().map(span => span.status)).toEqual(['internal_error', 'cancelled']); + expect(events).toHaveLength(0); + }); + + it('wraps tools added through addTools() and setMessagesParams()', () => { + const added = tool('added', () => 'a'); + const fromParams = tool('fromParams', () => 'b'); + const fromMutator = tool('fromMutator', () => 'c'); + + const runner = instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [] }); + runner.addTools(added); + runner.setMessagesParams({ tools: [fromParams] }); + runner.setMessagesParams(prev => ({ ...prev, tools: [fromMutator] })); + + added.run({}); + fromParams.run({}); + fromMutator.run({}); + + expect(toolSpans().map(span => span.description)).toEqual([ + 'execute_tool added', + 'execute_tool fromParams', + 'execute_tool fromMutator', + ]); + }); + + it('wraps a tool once when it is shared between runners or instrumented twice', () => { + const weather = tool('weather', () => 'sunny'); + + const instrumented = instrumentAnthropicAiClient(instrumentAnthropicAiClient(fakeClient())); + instrumented.beta.messages.toolRunner({ tools: [weather] }); + instrumented.beta.messages.toolRunner({ tools: [weather] }); + weather.run({}); + + expect(toolSpans()).toHaveLength(1); + }); + + it('leaves toolsets alone, since their run takes (ctx, toolUse)', async () => { + const plainRun = vi.fn(async () => { + throw new Error('aborted by user'); + }); + const plainToolset = { type: 'browser_toolset_20260801', run: plainRun }; + class ComputerToolset { + public readonly type = 'computer_toolset_20260801'; + public readonly toolsetName = 'computer'; + public async run(): Promise { + return []; + } + } + const classToolset = new ComputerToolset(); + const classRun = classToolset.run; + + instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [plainToolset, classToolset] }); + const toolUse = { id: 'toolu_1', name: 'navigate', toolset_name: 'browser', input: {} }; + await expect(plainToolset.run({ toolUse, signal: AbortSignal.abort() }, toolUse)).rejects.toThrow(); + await classToolset.run(); + + expect(plainToolset.run).toBe(plainRun); + expect(classToolset.run).toBe(classRun); + expect(toolSpans()).toHaveLength(0); + expect(events).toHaveLength(0); + }); + + it('runs a frozen tool without a span', () => { + const frozen = Object.freeze(tool('frozen', () => 'ok')); + + instrumentAnthropicAiClient(fakeClient()).beta.messages.toolRunner({ tools: [frozen] }); + + expect(frozen.run({})).toBe('ok'); + expect(toolSpans()).toHaveLength(0); + }); + + it('passes a toolRunner() error through', () => { + const instrumented = instrumentAnthropicAiClient(fakeClient()); + + expect(() => instrumented.beta.messages.toolRunner(undefined as unknown as Params)).toThrow('params required'); + }); +}); From 9a31690c079cd7cfba7681e1147e3882a20aca71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:38:06 +0200 Subject: [PATCH 019/118] feat(deps): Bump @anthropic-ai/sdk from 0.63.0 to 0.133.0 (#25417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript) from 0.63.0 to 0.133.0.
Release notes

Sourced from @โ€‹anthropic-ai/sdk's releases.

sdk-v0.133.0

Features

  • api: add types for the Chat and Cowork unified analytics metrics
  • api: add workflows, multiagent configuration and thread status filtering to Managed Agents

Chores

  • internal: remove the next branch rule from CLAUDE.md

sdk-v0.132.1

Bug Fixes

  • client: throw before sending a request with an empty path parameter

Chores

  • ci: check that pull requests update the changelog
  • ci: publish every package from the publish workflow
  • docs: note that listing Claude Console spend limits is in early access

Documentation

  • split CLAUDE.md into shared, TypeScript, and repository rules

sdk: v0.132.0

0.132.0 (2026-10-07)

Full Changelog: sdk-v0.131.0...sdk-v0.132.0

Features

  • api: add claude-haiku-5-5 and typed computer and browser toolset tool calls (1fd8261)
  • api: add disabled to thinking types in model capabilities (2b75b49)
  • api: add display_name to RBAC roles and deprecate name (7735ac3)
  • api: add include_default parameter to list workspaces (c4019df)
  • api: add lifecycle stage fields and filter to /v1/models (fe8a762)
  • api: add line to model objects (018aed4)
  • api: add url_sources to the Managed Agents web_fetch tool config (2b75b49)
  • api: add web search and code execution support to model capabilities (2b75b49)

Bug Fixes

  • api: send the beta header by default when listing spend limits (8f58ca4)
  • client: throw when a JSONL stream is iterated twice (a34266c)
  • tools: keep a local edit made after a memory upload whose response was lost (#893) (2b75b49)

... (truncated)

Changelog

Sourced from @โ€‹anthropic-ai/sdk's changelog.

0.133.0 (2026-10-09)

Features

  • api: add types for the Chat and Cowork unified analytics metrics
  • api: add workflows, multiagent configuration and thread status filtering to Managed Agents

Chores

  • internal: remove the next branch rule from CLAUDE.md

0.132.1 (2026-10-08)

Bug Fixes

  • client: throw before sending a request with an empty path parameter

Chores

  • ci: check that pull requests update the changelog
  • ci: publish every package from the publish workflow
  • docs: note that listing Claude Console spend limits is in early access

Documentation

  • split CLAUDE.md into shared, TypeScript, and repository rules

0.132.0 (2026-10-07)

Full Changelog: sdk-v0.131.0...sdk-v0.132.0

Features

  • api: add claude-haiku-5-5 and typed computer and browser toolset tool calls (1fd8261)
  • api: add disabled to thinking types in model capabilities (2b75b49)
  • api: add display_name to RBAC roles and deprecate name (7735ac3)
  • api: add include_default parameter to list workspaces (c4019df)
  • api: add lifecycle stage fields and filter to /v1/models (fe8a762)
  • api: add line to model objects (018aed4)
  • api: add url_sources to the Managed Agents web_fetch tool config (2b75b49)
  • api: add web search and code execution support to model capabilities (2b75b49)

Bug Fixes

  • api: send the beta header by default when listing spend limits (8f58ca4)
  • client: throw when a JSONL stream is iterated twice (a34266c)

... (truncated)

Commits
  • 2979ed5 release: . 0.133.0
  • 8486d9b Merge pull request #908 from anthropics/synced/published
  • e0995e4 feat(api): add workflows, multiagent configuration and thread status filterin...
  • 31eb3be codegen metadata
  • 5127b32 codegen metadata
  • f702a46 codegen metadata
  • d64e976 codegen metadata
  • 9f75465 codegen metadata
  • 6ec2ea1 chore(internal): remove the next branch rule from CLAUDE.md
  • 373c33d codegen metadata
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for @โ€‹anthropic-ai/sdk since your current version.


Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../cloudflare-integration-tests/package.json | 2 +- .../node-integration-tests/package.json | 2 +- yarn.lock | 27 ++++++++++++++++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json index 592903afa3a5..dd830255c0a8 100644 --- a/dev-packages/cloudflare-integration-tests/package.json +++ b/dev-packages/cloudflare-integration-tests/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "ai": "^6.0.0", - "@anthropic-ai/sdk": "0.63.0", + "@anthropic-ai/sdk": "0.133.0", "@google/genai": "^1.20.0", "@langchain/core": "^1.2.17", "@langchain/langgraph": "^1.0.1", diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index ef3fdb556cac..c44960c5004f 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -25,7 +25,7 @@ "test:watch": "yarn test --watch" }, "dependencies": { - "@anthropic-ai/sdk": "0.63.0", + "@anthropic-ai/sdk": "0.133.0", "@apollo/server": "^5.5.0", "@aws-sdk/client-dynamodb": "^3.1046.0", "@aws-sdk/client-kinesis": "^3.1146.0", diff --git a/yarn.lock b/yarn.lock index ad2e99570c10..ef8f10db812c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -390,12 +390,13 @@ dependencies: tslib "^2.3.0" -"@anthropic-ai/sdk@0.63.0": - version "0.63.0" - resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.63.0.tgz#725ea136ebf2b0fc7ebfdcb655a1d69b60bd3927" - integrity sha512-g2KzDcVXxT2d/SMuVJHeJ6T2loj6jFMt+Nj+I6bfwXWNDMoOP0HhiWr+5RivRV7Yv++jBurDGr76XBCc66R79A== +"@anthropic-ai/sdk@0.133.0": + version "0.133.0" + resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.133.0.tgz#08db7874dde5e2e02b42d12680315efa72fa9799" + integrity sha512-jlqUN8p8ESTJj+GJnA4KjzbEfF1waEoGzRT8jwytlYGFFgv+/DUQOxlYGvYw10UzhD1kwLqYzxvA3FrTB2YWuQ== dependencies: json-schema-to-ts "^3.1.1" + standardwebhooks "^1.0.0" "@anthropic-ai/sdk@^0.65.0": version "0.65.0" @@ -8801,6 +8802,11 @@ resolved "https://registry.yarnpkg.com/@speed-highlight/core/-/core-1.2.15.tgz#88c45609a2b5c2293a2e1935417c507f98f39d0b" integrity sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw== +"@stablelib/base64@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/base64/-/base64-1.0.1.tgz#bdfc1c6d3a62d7a3b7bbc65b6cce1bb4561641be" + integrity sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ== + "@standard-schema/spec@1.1.0", "@standard-schema/spec@^1.0.0", "@standard-schema/spec@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" @@ -15804,6 +15810,11 @@ fast-safe-stringify@2.1.1: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== +fast-sha256@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-sha256/-/fast-sha256-1.3.0.tgz#7916ba2054eeb255982608cccd0f6660c79b7ae6" + integrity sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ== + fast-sourcemap-concat@^2.1.0, fast-sourcemap-concat@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-sourcemap-concat/-/fast-sourcemap-concat-2.1.1.tgz#3647fd5876deace53d9a1c418061199417911801" @@ -25665,6 +25676,14 @@ standard-as-callback@2.1.0, standard-as-callback@^2.1.0: resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== +standardwebhooks@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/standardwebhooks/-/standardwebhooks-1.1.1.tgz#25b98fa2bbe4edad7b42778135c923563e3d5e2f" + integrity sha512-bCbX9ZEyFkWPsRz7Bl3NuQUJohmwGSev/yhr7vhaGPlc4AfIrspIRa6cPTBuI1ItmrTDJ4d/S2hCsfe4+vQGnQ== + dependencies: + "@stablelib/base64" "^1.0.0" + fast-sha256 "^1.3.0" + "statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0, statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" From 3d0c76970a56fc7e1f113066e4d0f369d3e6983a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Oct 2026 14:02:49 +0200 Subject: [PATCH 020/118] test(deps): Upgrade TanStack Start, Router, and Nitro (#25445) Upgrade the TanStack Start React E2E app to Start 1.168.60 and Router 1.170.41 together, matching the Router version required by the published Start package. Upgrade Nitro to include the upstream POST cancellation fix while retaining Vite 7 compatibility ([later-release regression](https://github.com/nitrojs/nitro/issues/4471)). supersedes https://github.com/getsentry/sentry-javascript/pull/25444#issuecomment-6214287856 Fixes https://github.com/getsentry/sentry-javascript/security/dependabot/2785 (GHSA-qx66-fv34-fjm8: reflected XSS in TanStack Start server-function responses). --------- Co-authored-by: GPT-6 --- .../test-applications/tanstackstart-react/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json b/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json index 3b7947f69386..9faeed291419 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json @@ -25,11 +25,11 @@ "//": "Need to use ioredis 5.10.1 because that's the last version before they support tracing channels", "dependencies": { "@sentry/tanstackstart-react": "file:../../packed/sentry-tanstackstart-react-packed.tgz", - "@tanstack/react-start": "1.168.35", - "@tanstack/react-router": "1.170.18", + "@tanstack/react-start": "1.168.60", + "@tanstack/react-router": "1.170.41", "react": "^19.2.0", "react-dom": "^19.2.0", - "nitro": "^3.0.0", + "nitro": "^3.0.260429-beta", "ioredis": "5.10.1", "mysql": "^2.18.1" }, From 20ba3ef8ce5e5fe0bd86e8dc0b868de8bbcce7af Mon Sep 17 00:00:00 2001 From: Sigrid <32902192+s1gr1d@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:12:59 +0200 Subject: [PATCH 021/118] fix(tanstackstart-react): Delete only the source maps that Sentry enabled (#25362) The default `filesToDeleteAfterUpload: ['./**/*.map']` does not enter dot directories like `.output`, so the client source maps that Sentry enabled stayed in the build output of Nitro apps. - Deletes the source maps in the output directory of each environment where only Sentry turned them on. - Keeps source maps that the user enabled with `build.sourcemap`, an environment setting or `build.rolldownOptions.output.sourcemap`. - Deletes only in the output directories of the build. The old default `./**/*.map` also deleted source maps of dependencies in `node_modules` (npm and yarn layouts). - Warns on Vite 7, where Sentry only knows `build.outDir`. Refs #21012 --- .../src/vite/sourceMaps.ts | 122 ++++++-- .../test/vite/sourceMaps.test.ts | 266 +++++++++++++----- 2 files changed, 294 insertions(+), 94 deletions(-) diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index a0cf3f4e84c7..f84c52692899 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -1,6 +1,6 @@ import { sentryVitePlugin } from '@sentry/bundler-plugins/vite'; import * as path from 'node:path'; -import type { Plugin, UserConfig } from 'vite'; +import type { BuildEnvironmentOptions, Plugin, ResolvedConfig, UserConfig } from 'vite'; import type { SentryTanstackStartOptions } from './sentryTanstackStart'; type FilesToDeleteAfterUpload = string | string[] | undefined; @@ -30,6 +30,12 @@ export function rewriteTanstackStartSource(source: string, _map: unknown, contex return path.relative(process.cwd(), absolutePath).split(path.sep).join('/'); } +/** + * Tells the plugin that deletes source maps after the upload whether the source maps of an environment are only + * there because Sentry enabled them. + */ +type SourceMapsEnablerApi = { hasSentryEnabledSourceMaps(environmentName: string): boolean }; + /** * A Sentry plugin for adding the @sentry/bundler-plugins/vite to automatically upload source maps to Sentry. */ @@ -62,21 +68,29 @@ export function makeAddSentryVitePlugin(options: SentryTanstackStartOptions): Pl name: 'sentry-tanstackstart-files-to-delete-after-upload-plugin', apply: 'build', enforce: 'post', - config(config) { - const userFilesToDelete = sourcemaps?.filesToDeleteAfterUpload; - - // Only auto-delete source maps if the user didn't configure sourcemaps at all - if (typeof userFilesToDelete === 'undefined' && typeof config.build?.sourcemap === 'undefined') { - if (debug) { - // eslint-disable-next-line no-console - console.log( - '[Sentry] Automatically setting `sourcemaps.filesToDeleteAfterUpload: ["./**/*.map"]` to delete generated source maps after they were uploaded to Sentry.', - ); - } - resolveFilesToDeleteAfterUpload?.(['./**/*.map']); - } else { - resolveFilesToDeleteAfterUpload?.(userFilesToDelete); + configResolved(config) { + if (sourcemaps?.filesToDeleteAfterUpload !== undefined) { + resolveFilesToDeleteAfterUpload?.(sourcemaps.filesToDeleteAfterUpload); + return; + } + + if (!isVite8OrLater(this.meta.viteVersion)) { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] TanStack Start requires Vite 8 or later. With an older Vite, Sentry deletes the source maps it generated only in \`build.outDir\` of each environment, so source maps in other output directories can stay in the build output.`, + ); + } + + const filesToDelete = getSentryEnabledSourceMapGlobs(config); + if (debug) { + // eslint-disable-next-line no-console + console.log( + filesToDelete.length > 0 + ? `[Sentry] Automatically setting \`sourcemaps.filesToDeleteAfterUpload: ${JSON.stringify(filesToDelete)}\` to delete generated source maps after they were uploaded to Sentry.` + : '[Sentry] No source maps are deleted after the upload, because Sentry did not enable source map generation for this build.', + ); } + resolveFilesToDeleteAfterUpload?.(filesToDelete); }, }; @@ -116,21 +130,72 @@ export function makeAddSentryVitePlugin(options: SentryTanstackStartOptions): Pl return [configPlugin, ...sentryPlugins]; } +/** + * Globs for the source maps in the output directories that only have source maps because Sentry enabled them. A glob + * such as `./**\/*.map` does not enter dot directories like `.output`, where Nitro puts the client build. + */ +function getSentryEnabledSourceMapGlobs(config: ResolvedConfig): string[] { + const outDirs = Object.entries(config.environments) + .filter(([name]) => hasSentryEnabledSourceMaps(config, name)) + .filter(([, environment]) => !hasOutputSourceMapSetting(environment.build)) + .flatMap(([, environment]) => getOutputDirs(config.root, environment.build)); + + return [...new Set(outDirs)].map(outDir => { + const relativeOutDir = path.relative(process.cwd(), outDir).split(path.sep).join('/'); + return `${relativeOutDir || '.'}/**/*.map`; + }); +} + +/** `output.dir` takes precedence over `build.outDir`. Nitro sets only `output.dir` for its server build. */ +function getOutputDirs(root: string, build: ResolvedConfig['environments'][string]['build']): string[] { + // Vite 7 has no `rolldownOptions`. + const outputs = [build.rolldownOptions?.output ?? {}].flat(); + return outputs.map(output => path.resolve(root, output.dir ?? build.outDir)); +} + +/** `output.sourcemap` of the bundler config overrides `build.sourcemap` in the build output. */ +function hasOutputSourceMapSetting(build: Pick | undefined): boolean { + return [build?.rolldownOptions?.output ?? []].flat().some(output => output.sourcemap !== undefined); +} + +/** Vite tells plugins its version since Vite 7. */ +function isVite8OrLater(viteVersion: string | undefined): boolean { + return Number(viteVersion?.split('.')[0]) >= 8; +} + +function hasSentryEnabledSourceMaps(config: ResolvedConfig, environmentName: string): boolean { + return config.plugins.some(plugin => + (plugin.api as Partial | undefined)?.hasSentryEnabledSourceMaps?.(environmentName), + ); +} + /** * A Sentry plugin for TanStack Start React to enable "hidden" source maps if they are unset. */ export function makeEnableSourceMapsVitePlugin(options: SentryTanstackStartOptions): Plugin[] { + let enabledSourceMaps = false; + let environmentsWithSourceMapSetting: string[] = []; + + const api: SourceMapsEnablerApi = { + hasSentryEnabledSourceMaps: environmentName => + enabledSourceMaps && !environmentsWithSourceMapSetting.includes(environmentName), + }; + return [ { name: 'sentry-tanstackstart-react-source-maps', apply: 'build', enforce: 'post', + api, config(viteConfig) { - return { - build: { - sourcemap: getUpdatedSourceMapSettings(viteConfig, options), - }, - }; + const sourcemap = getUpdatedSourceMapSettings(viteConfig, options); + enabledSourceMaps = sourcemap !== viteConfig.build?.sourcemap; + // The setting of an environment overrides the top-level setting, e.g. `nitro/vite` sets it for its environments. + environmentsWithSourceMapSetting = Object.entries(viteConfig.environments ?? {}) + .filter(([, environment]) => environment?.build?.sourcemap !== undefined) + .map(([name]) => name); + + return { build: { sourcemap } }; }, }, ]; @@ -142,19 +207,19 @@ export function makeEnableSourceMapsVitePlugin(options: SentryTanstackStartOptio * - keep this setting (emit a warning that errors won't be unminified in Sentry) * - We won't upload anything * - * 2. Users enabled source map generation (true, 'hidden', 'inline'). + * 2. Users enabled source map generation (true, 'hidden', 'inline') or set `build.rolldownOptions.output.sourcemap`. * - keep this setting (don't do anything - like deletion - besides uploading) * * 3. Users didn't set source maps generation * - we enable 'hidden' source maps generation - * - configure `filesToDeleteAfterUpload` to delete all .map files (we emit a log about this) + * - configure `filesToDeleteAfterUpload` to delete these .map files after the upload (we emit a log about this) * - * --> only exported for testing + * @internal Only exported for testing. */ export function getUpdatedSourceMapSettings( viteConfig: UserConfig, sentryPluginOptions?: SentryTanstackStartOptions, -): boolean | 'inline' | 'hidden' { +): boolean | 'inline' | 'hidden' | undefined { viteConfig.build = viteConfig.build || {}; const viteUserSourceMapSetting = viteConfig.build?.sourcemap; @@ -183,6 +248,15 @@ export function getUpdatedSourceMapSettings( } return viteUserSourceMapSetting; + } else if (hasOutputSourceMapSetting(viteConfig.build)) { + if (debug) { + // eslint-disable-next-line no-console + console.log( + '[Sentry] We discovered `vite.build.rolldownOptions.output.sourcemap`, which overrides `vite.build.sourcemap`. Sentry will keep this source map setting.', + ); + } + + return undefined; } // If the user did not specify a source map setting, we enable 'hidden' by default diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts index eb4b3740b658..0f41d8d261ef 100644 --- a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -1,6 +1,6 @@ // @vitest-environment node import type { SentryVitePluginOptions } from '@sentry/bundler-plugins/vite'; -import type { UserConfig } from 'vite'; +import type { Plugin, ResolvedConfig, UserConfig } from 'vite'; import { mergeConfig } from 'vite'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import * as path from 'path'; @@ -10,13 +10,19 @@ import { makeEnableSourceMapsVitePlugin, rewriteTanstackStartSource, } from '../../src/vite/sourceMaps'; +import type { SentryTanstackStartOptions } from '../../src/vite/sentryTanstackStart'; const mockedSentryVitePlugin = { name: 'sentry-vite-debug-id-upload-plugin', writeBundle: vi.fn(), }; -const sentryVitePluginSpy = vi.fn((_options: SentryVitePluginOptions) => [mockedSentryVitePlugin]); +let sentryVitePluginOptions: SentryVitePluginOptions | undefined; + +const sentryVitePluginSpy = vi.fn((options: SentryVitePluginOptions) => { + sentryVitePluginOptions = options; + return [mockedSentryVitePlugin]; +}); vi.mock('@sentry/bundler-plugins/vite', () => ({ sentryVitePlugin: (options: SentryVitePluginOptions) => sentryVitePluginSpy(options), @@ -24,6 +30,7 @@ vi.mock('@sentry/bundler-plugins/vite', () => ({ beforeEach(() => { vi.clearAllMocks(); + sentryVitePluginOptions = undefined; }); describe('makeEnableSourceMapsVitePlugin()', () => { @@ -39,6 +46,36 @@ describe('makeEnableSourceMapsVitePlugin()', () => { expect(sourceMapsConfigPlugins).toHaveLength(1); }); + it.each([ + { label: 'the user set no source maps', userConfig: {}, expected: { client: true, ssr: true } }, + { + label: 'the user set `build.sourcemap`', + userConfig: { build: { sourcemap: true } }, + expected: { client: false, ssr: false }, + }, + { + label: 'the user set the source maps of an environment', + userConfig: { environments: { client: { build: { sourcemap: true } } } }, + expected: { client: false, ssr: true }, + }, + { + label: 'the user set `build.rolldownOptions.output.sourcemap`', + userConfig: { build: { rolldownOptions: { output: { sourcemap: true } } } }, + expected: { client: false, ssr: false }, + }, + ] as const)( + 'tells which environments only have source maps because of Sentry when $label', + ({ userConfig, expected }) => { + const plugin = makeEnableSourceMapsVitePlugin({})[0]!; + (plugin.config as (config: UserConfig) => UserConfig)(structuredClone(userConfig) as UserConfig); + + expect({ + client: plugin.api.hasSentryEnabledSourceMaps('client'), + ssr: plugin.api.hasSentryEnabledSourceMaps('ssr'), + }).toEqual(expected); + }, + ); + it.each([undefined, false, true, 'hidden', 'inline'] as const)( 'preserves user config without duplicating arrays when sourcemap is %s', sourcemap => { @@ -97,15 +134,14 @@ describe('makeAddSentryVitePlugin()', () => { }); // The filesToDeleteAfterUpload is now a Promise that resolves when the config hook runs - const calledOptions = sentryVitePluginSpy.mock.calls[0]?.[0]; - expect(calledOptions?.sourcemaps?.filesToDeleteAfterUpload).toBeInstanceOf(Promise); + expect(sentryVitePluginOptions?.sourcemaps?.filesToDeleteAfterUpload).toBeInstanceOf(Promise); - // Trigger the config hook to resolve the Promise + // Trigger the configResolved hook to resolve the Promise const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); - (configPlugin?.config as (config: UserConfig) => void)({}); + (configPlugin?.configResolved as (config: ResolvedConfig) => void)({} as ResolvedConfig); // Verify the Promise resolves to the user-specified value - const resolvedFilesToDelete = await calledOptions?.sourcemaps?.filesToDeleteAfterUpload; + const resolvedFilesToDelete = await sentryVitePluginOptions?.sourcemaps?.filesToDeleteAfterUpload; expect(resolvedFilesToDelete).toEqual(['baz/*.js']); expect(sentryVitePluginSpy).toHaveBeenCalledWith( @@ -171,89 +207,163 @@ describe('makeAddSentryVitePlugin()', () => { expect(configPlugin).toBeDefined(); expect(configPlugin?.apply).toBe('build'); expect(configPlugin?.enforce).toBe('post'); - expect(typeof configPlugin?.config).toBe('function'); + expect(typeof configPlugin?.configResolved).toBe('function'); }); - it('uses default filesToDeleteAfterUpload when not specified', async () => { - const plugins = makeAddSentryVitePlugin({ - org: 'my-org', - authToken: 'my-token', - }); - - // The filesToDeleteAfterUpload is now a Promise - const calledOptions = sentryVitePluginSpy.mock.calls[0]?.[0]; - expect(calledOptions?.sourcemaps?.filesToDeleteAfterUpload).toBeInstanceOf(Promise); - - // Trigger the config hook to resolve the Promise (with no sourcemap configured) - const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); - (configPlugin?.config as (config: UserConfig) => void)({}); + describe('default filesToDeleteAfterUpload', () => { + type TestEnvironment = { + consumer: 'client' | 'server'; + outDir: string; + outputDir?: string; + outputSourceMap?: boolean; + }; + + const NITRO_ENVIRONMENTS: Record = { + client: { consumer: 'client', outDir: '.output/public' }, + // Nitro sets the output directory of its server build with `output.dir` only. + nitro: { consumer: 'server', outDir: 'dist', outputDir: '.output/server' }, + }; + + /** A plugin that enabled the source maps of the given environments, like the Sentry plugins that enable them. */ + function sourceMapsEnabler(environmentNames: string[]): Plugin { + return { + name: 'source-maps-enabler', + api: { hasSentryEnabledSourceMaps: (environmentName: string) => environmentNames.includes(environmentName) }, + }; + } - // Verify the Promise resolves to the default value - const resolvedFilesToDelete = await calledOptions?.sourcemaps?.filesToDeleteAfterUpload; - expect(resolvedFilesToDelete).toEqual(['./**/*.map']); - }); + function resolveFilesToDelete( + environments: Record, + { + sentryEnabledEnvironments = Object.keys(environments), + options = {}, + viteVersion = '8.3.1', + }: { sentryEnabledEnvironments?: string[]; options?: SentryTanstackStartOptions; viteVersion?: string } = {}, + ): Promise { + const isVite7 = viteVersion.startsWith('7.'); + const plugins = makeAddSentryVitePlugin(options); + const config = { + root: process.cwd(), + plugins: [...plugins, sourceMapsEnabler(sentryEnabledEnvironments)], + environments: Object.fromEntries( + Object.entries(environments).map(([name, { consumer, outDir, outputDir, outputSourceMap }]) => [ + name, + { + consumer, + build: { + outDir, + ...(!isVite7 && { rolldownOptions: { output: { dir: outputDir, sourcemap: outputSourceMap } } }), + }, + }, + ]), + ), + } as unknown as ResolvedConfig; + + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); + (configPlugin?.configResolved as (this: unknown, config: ResolvedConfig) => void).call( + { meta: { viteVersion } }, + config, + ); + + return Promise.resolve(sentryVitePluginOptions?.sourcemaps?.filesToDeleteAfterUpload); + } - it('does not auto-delete when user configured build.sourcemap', async () => { - const plugins = makeAddSentryVitePlugin({ - org: 'my-org', - authToken: 'my-token', + it.each([ + { + label: 'a Nitro build', + environments: NITRO_ENVIRONMENTS, + expected: ['.output/public/**/*.map', '.output/server/**/*.map'], + }, + { + label: 'a build without Nitro', + environments: { + client: { consumer: 'client', outDir: 'dist/client' }, + ssr: { consumer: 'server', outDir: 'dist/server' }, + }, + expected: ['dist/client/**/*.map', 'dist/server/**/*.map'], + }, + { + label: 'environments that share an output directory once', + environments: { + client: { consumer: 'client', outDir: 'dist' }, + ssr: { consumer: 'server', outDir: 'dist' }, + }, + expected: ['dist/**/*.map'], + }, + ] as const)('deletes the source maps in each output directory of $label', async ({ environments, expected }) => { + await expect(resolveFilesToDelete(environments)).resolves.toEqual(expected); }); - // The filesToDeleteAfterUpload is a Promise - const calledOptions = sentryVitePluginSpy.mock.calls[0]?.[0]; - expect(calledOptions?.sourcemaps?.filesToDeleteAfterUpload).toBeInstanceOf(Promise); + it.each([ + { label: 'the client', sentryEnabledEnvironments: ['nitro'], expected: ['.output/server/**/*.map'] }, + { label: 'the Nitro server', sentryEnabledEnvironments: ['client'], expected: ['.output/public/**/*.map'] }, + { label: 'any environment', sentryEnabledEnvironments: [], expected: [] }, + ])( + 'keeps the source maps of $label when Sentry did not enable them', + async ({ sentryEnabledEnvironments, expected }) => { + await expect(resolveFilesToDelete(NITRO_ENVIRONMENTS, { sentryEnabledEnvironments })).resolves.toEqual( + expected, + ); + }, + ); - // Trigger the config hook with sourcemap configured by user - const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); - (configPlugin?.config as (config: UserConfig) => void)({ build: { sourcemap: true } }); + it.each([true, false])( + 'keeps the source maps of an environment that sets `output.sourcemap: %s`', + async outputSourceMap => { + const environments = { + ...NITRO_ENVIRONMENTS, + client: { ...NITRO_ENVIRONMENTS.client!, outputSourceMap }, + }; - // Verify the Promise resolves to undefined (no auto-delete) - const resolvedFilesToDelete = await calledOptions?.sourcemaps?.filesToDeleteAfterUpload; - expect(resolvedFilesToDelete).toBeUndefined(); - }); + await expect(resolveFilesToDelete(environments)).resolves.toEqual(['.output/server/**/*.map']); + }, + ); - it('logs auto-delete message when user did not configure sourcemap', () => { - const plugins = makeAddSentryVitePlugin({ - org: 'my-org', - authToken: 'my-token', - debug: true, + it('warns and deletes the source maps in `build.outDir` with Vite 7', async () => { + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const environments = { + client: { consumer: 'client', outDir: 'dist/client' }, + ssr: { consumer: 'server', outDir: 'dist/server' }, + } as const; + + await expect(resolveFilesToDelete(environments, { viteVersion: '7.3.2' })).resolves.toEqual([ + 'dist/client/**/*.map', + 'dist/server/**/*.map', + ]); + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('TanStack Start requires Vite 8 or later.')); + consoleWarnSpy.mockRestore(); }); - const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); - expect(configPlugin).toBeDefined(); + it('does not warn with Vite 8', async () => { + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + await resolveFilesToDelete(NITRO_ENVIRONMENTS); - // Simulate config hook with no sourcemap configured - if (configPlugin?.config) { - (configPlugin.config as (config: UserConfig) => void)({}); - } + expect(consoleWarnSpy).not.toHaveBeenCalled(); + consoleWarnSpy.mockRestore(); + }); - expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Automatically setting')); + it('logs the default in debug mode', async () => { + const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - consoleSpy.mockRestore(); - }); + await resolveFilesToDelete(NITRO_ENVIRONMENTS, { options: { debug: true } }); - it('does not log auto-delete message when user configured sourcemap', () => { - const plugins = makeAddSentryVitePlugin({ - org: 'my-org', - authToken: 'my-token', - debug: true, + expect(consoleSpy).toHaveBeenCalledWith( + '[Sentry] Automatically setting `sourcemaps.filesToDeleteAfterUpload: [".output/public/**/*.map",".output/server/**/*.map"]` to delete generated source maps after they were uploaded to Sentry.', + ); + consoleSpy.mockRestore(); }); - const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); - expect(configPlugin).toBeDefined(); + it('logs that it deletes no source maps when Sentry enabled none', async () => { + const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + await resolveFilesToDelete(NITRO_ENVIRONMENTS, { sentryEnabledEnvironments: [], options: { debug: true } }); - // Simulate config hook with sourcemap configured - if (configPlugin?.config) { - (configPlugin.config as (config: UserConfig) => void)({ build: { sourcemap: true } }); - } - - expect(consoleSpy).not.toHaveBeenCalledWith(expect.stringContaining('Automatically setting')); - - consoleSpy.mockRestore(); + expect(consoleSpy).toHaveBeenCalledWith( + '[Sentry] No source maps are deleted after the upload, because Sentry did not enable source map generation for this build.', + ); + consoleSpy.mockRestore(); + }); }); it('passes rewriteSources to the vite plugin', () => { @@ -359,6 +469,22 @@ describe('getUpdatedSourceMapSettings', () => { expect(result).toBe(expected); }); + it.each([true, false, 'hidden'] as const)( + 'should keep `build.sourcemap` unset when `build.rolldownOptions.output.sourcemap` is %s', + sourcemap => { + const result = getUpdatedSourceMapSettings( + { build: { rolldownOptions: { output: { sourcemap } } } }, + { debug: true }, + ); + + expect(result).toBeUndefined(); + // eslint-disable-next-line no-console + expect(console.log).toHaveBeenCalledWith( + '[Sentry] We discovered `vite.build.rolldownOptions.output.sourcemap`, which overrides `vite.build.sourcemap`. Sentry will keep this source map setting.', + ); + }, + ); + it('should set sourcemap to hidden when not configured', () => { const result = getUpdatedSourceMapSettings({}); From 848fec26ad7815213cfafd16c3e852b50f44dbb3 Mon Sep 17 00:00:00 2001 From: Sigrid <32902192+s1gr1d@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:14:10 +0200 Subject: [PATCH 022/118] test(e2e): Use nitro beta and Vite 8 in tanstackstart apps (#25451) Follow-up to #25334. The tanstackstart-react apps now use the nitro beta instead of the deprecated `nitro@3.0.0`. The beta needs Vite 8, so the apps also use Vite 8 and `@vitejs/plugin-react-swc` 4, which is the first version that supports Vite 8. The SDK still supports Vite 7. Only the test apps changed. --- .../tanstackstart-react-static/package.json | 6 +++--- .../test-applications/tanstackstart-react/package.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json b/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json index 6ccf268417cf..39ab417ae8ad 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react-static/package.json @@ -29,7 +29,7 @@ "@tanstack/react-router": "1.170.18", "react": "^19.2.0", "react-dom": "^19.2.0", - "nitro": "^3.0.0", + "nitro": "3.0.260903-beta", "ioredis": "5.10.1", "mysql": "^2.18.1" }, @@ -39,9 +39,9 @@ "@types/node": "^24.10.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", - "@vitejs/plugin-react-swc": "^3.5.0", + "@vitejs/plugin-react-swc": "^4.3.3", "typescript": "^5.9.0", - "vite": "7.3.2", + "vite": "8.3.4", "vite-tsconfig-paths": "^5.1.4", "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils" diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json b/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json index 9faeed291419..555271be7beb 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/package.json @@ -29,7 +29,7 @@ "@tanstack/react-router": "1.170.41", "react": "^19.2.0", "react-dom": "^19.2.0", - "nitro": "^3.0.260429-beta", + "nitro": "3.0.260903-beta", "ioredis": "5.10.1", "mysql": "^2.18.1" }, @@ -39,9 +39,9 @@ "@types/node": "^24.10.0", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", - "@vitejs/plugin-react-swc": "^3.5.0", + "@vitejs/plugin-react-swc": "^4.3.3", "typescript": "^5.9.0", - "vite": "7.3.2", + "vite": "8.3.4", "vite-tsconfig-paths": "^5.1.4", "@playwright/test": "~1.63.0", "@sentry-internal/test-utils": "link:../../../test-utils" From f4a3cbabb500a0a1d4d1a45422133bea76a0bd66 Mon Sep 17 00:00:00 2001 From: Faizan Saiyed Date: Fri, 16 Oct 2026 18:18:51 +0530 Subject: [PATCH 023/118] feat(browser): Add `LoaderSentry` type export (#25357) Adds an exported `LoaderSentry` type for TypeScript users of the CDN loader. The type encapsulate the subset of callable SDK APIs, including the signature divergence (mostly, the loader versions only return `void` while the actual versions return something) --------- Co-authored-by: Lukas Stracke Co-authored-by: Claude Opus 5.5 --- packages/browser/src/exports.ts | 1 + packages/browser/src/loader-types.ts | 46 +++++++++++++++++++ packages/browser/test/loader-types.test-d.ts | 48 ++++++++++++++++++++ packages/browser/vite.config.ts | 5 ++ 4 files changed, 100 insertions(+) create mode 100644 packages/browser/src/loader-types.ts create mode 100644 packages/browser/test/loader-types.test-d.ts diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 48b09d3c1acc..b56b786dc877 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -23,6 +23,7 @@ export type { } from '@sentry/core'; export type { BrowserOptions } from './client'; +export type { LoaderSentry } from './loader-types'; export { addEventProcessor, diff --git a/packages/browser/src/loader-types.ts b/packages/browser/src/loader-types.ts new file mode 100644 index 000000000000..871bdf80089a --- /dev/null +++ b/packages/browser/src/loader-types.ts @@ -0,0 +1,46 @@ +import type { addBreadcrumb, captureEvent, captureException, captureMessage, Scope } from '@sentry/core'; +import type { showReportDialog } from './report-dialog'; +import type { forceLoad, init, onLoad } from './sdk'; + +/** Queued calls are replayed once the SDK loads, so they can't return the SDK method's result. */ +type Queued = Fn extends (...args: infer Args) => unknown ? (...args: Args) => void : never; + +/** + * The subset of the Sentry browser API that is safe to call on the CDN loader's + * `window.Sentry` stub before the full SDK has loaded. + * + * Any other SDK API needs to be wrapped in `Sentry.onLoad()`. Inside the callback, the full SDK is + * loaded, so `window.Sentry` can be cast to the full SDK type. + * + * @example + * ```ts + * import type * as SentryBrowser from '@sentry/browser'; + * import type { LoaderSentry } from '@sentry/browser'; + * + * declare global { + * interface Window { + * Sentry: LoaderSentry; + * } + * } + * + * window.Sentry.captureException(new Error('Something went wrong')); + * + * window.Sentry.onLoad(() => { + * const Sentry = window.Sentry as typeof SentryBrowser; + * Sentry.init({ release: 'my-app@1.0.0' }); + * Sentry.addIntegration(Sentry.replayIntegration()); + * }); + * ``` + */ +export interface LoaderSentry { + init: Queued; + addBreadcrumb: Queued; + captureMessage: Queued; + captureException: Queued; + captureEvent: Queued; + showReportDialog: Queued; + // Only the callback overload: there's no way to get hold of a `Scope` before the SDK has loaded. + withScope: (callback: (scope: Scope) => void) => void; + onLoad: typeof onLoad; + forceLoad: typeof forceLoad; +} diff --git a/packages/browser/test/loader-types.test-d.ts b/packages/browser/test/loader-types.test-d.ts new file mode 100644 index 000000000000..e3b8e29621cd --- /dev/null +++ b/packages/browser/test/loader-types.test-d.ts @@ -0,0 +1,48 @@ +import { expectTypeOf, it, describe } from 'vitest'; +import type { Scope } from '@sentry/core'; +import type { LoaderSentry } from '../src/loader-types'; + +describe('LoaderSentry', () => { + it('exposes the APIs supported by the loader', () => { + expectTypeOf().toHaveProperty('init'); + expectTypeOf().toHaveProperty('addBreadcrumb'); + expectTypeOf().toHaveProperty('captureMessage'); + expectTypeOf().toHaveProperty('captureException'); + expectTypeOf().toHaveProperty('captureEvent'); + expectTypeOf().toHaveProperty('withScope'); + expectTypeOf().toHaveProperty('showReportDialog'); + expectTypeOf().toHaveProperty('onLoad'); + expectTypeOf().toHaveProperty('forceLoad'); + }); + + it('does not expose unsupported APIs', () => { + expectTypeOf().not.toHaveProperty('addIntegration'); + expectTypeOf().not.toHaveProperty('captureFeedback'); + expectTypeOf().not.toHaveProperty('flush'); + expectTypeOf().not.toHaveProperty('close'); + }); + + it('uses void return types for queued methods', () => { + expectTypeOf().returns.toBeVoid(); + expectTypeOf().returns.toBeVoid(); + expectTypeOf().returns.toBeVoid(); + expectTypeOf().returns.toBeVoid(); + expectTypeOf().returns.toBeVoid(); + expectTypeOf().returns.toBeVoid(); + expectTypeOf().returns.toBeVoid(); + }); + + it('keeps the parameters of the SDK methods', () => { + expectTypeOf().toBeCallableWith({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + expectTypeOf().toBeCallableWith(new Error('test'), { tags: { foo: 'bar' } }); + expectTypeOf().toBeCallableWith('test', 'warning'); + expectTypeOf().toBeCallableWith(() => {}); + }); + + it('only exposes the callback overload of withScope', () => { + expectTypeOf().toBeCallableWith(scope => { + scope.setTag('foo', 'bar'); + }); + expectTypeOf().parameters.toEqualTypeOf<[callback: (scope: Scope) => void]>(); + }); +}); diff --git a/packages/browser/vite.config.ts b/packages/browser/vite.config.ts index 0869faa66b74..2513b813e44b 100644 --- a/packages/browser/vite.config.ts +++ b/packages/browser/vite.config.ts @@ -9,5 +9,10 @@ export default defineConfig({ fakeTimers: { toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setImmediate', 'clearImmediate', 'Date'], }, + typecheck: { + enabled: true, + tsconfig: './tsconfig.test.json', + ignoreSourceErrors: true, + }, }, }); From 636127c0416294d54bdbbedfece7a58f7394d19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Fri, 16 Oct 2026 19:09:09 +0200 Subject: [PATCH 024/118] test(nextjs): Use React 19.3.1 in the vinext e2e variants (#25449) This makes the `nextjs-16` vinext build install React 19.3.1, and raises `react-server-dom-webpack` to `^19.3.1`. ## Why vinext 1.2.1 does not build App Router apps with React older than 19.3.1, because `react-server-dom-webpack` 19.3.0 shares typed-array buffers across Flight chunks. The build script installed `react@19.3.0`, so both `nextjs-16 (vinext)` variants failed: https://github.com/getsentry/sentry-javascript/actions/runs/38828620355/job/116743139345 Co-authored-by: Claude Opus 5.5 --- .../e2e-tests/test-applications/nextjs-16/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json index 0415c053a37e..d2fbc3a64a15 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/package.json @@ -24,7 +24,7 @@ "test:build:cloudflare": "pnpm install && opennextjs-cloudflare build --config wrangler.opennext.jsonc", "test:build-latest:cloudflare": "pnpm install && pnpm add next@16.3 && opennextjs-cloudflare build --config wrangler.opennext.jsonc", "start:cloudflare": "opennextjs-cloudflare preview --config wrangler.opennext.jsonc --var \"E2E_TEST_DSN:$E2E_TEST_DSN\"", - "test:build:vinext": "pnpm install && pnpm add react@19.3.0 react-dom@19.3.0 && npm pkg set type=module && vinext build", + "test:build:vinext": "pnpm install && pnpm add react@19.3.1 react-dom@19.3.1 && npm pkg set type=module && vinext build", "test:assert:vinext": "VINEXT=1 pnpm test:prod" }, "//": "Pin `ioredis` to 5.10.1: it's the last version before the driver publishes its own native diagnostics channels; orchestrion's config covers `ioredis <5.11.0`.", @@ -57,7 +57,7 @@ "eslint-config-next": "^16", "@vitejs/plugin-react": "^6.1.1", "@vitejs/plugin-rsc": "^0.5.35", - "react-server-dom-webpack": "^19.3.0", + "react-server-dom-webpack": "^19.3.1", "typescript": "^5", "vinext": "^1.0.1", "vite": "^8.3.0", From 50ed82b44e800ae2c8c7231151ca98f0245628cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20de=20Faria?= Date: Fri, 16 Oct 2026 14:33:25 -0300 Subject: [PATCH 025/118] fix(node): restore tsx runtime instrumentation (#25351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore runtime instrumentation for CommonJS dependencies loaded through tsx on Node 24.13+. Keep synchronous module hooks and use the CommonJS compiler for custom require loaders. Match canonical file URLs and source fingerprints to avoid transforming the same source twice while preserving the fallback for source changed by another loader. Regression coverage checks native Node and tsx with CommonJS reloads, query and fragment imports, and cache eviction after queried imports. The tsx-express E2E application now inherits Node 24 and all 10 existing tests pass locally. The two existing nft tracing tests fail on Windows with both this patch and the original code. Fixes #24787 Signed-off-by: Joรฃo Pedro de Faria Co-authored-by: GPT-6.1 Sol --- .../tsx-express/package.json | 1 - .../orchestrion-tsx/scenario-reload.mjs | 28 +++++++++++ .../tracing/orchestrion-tsx/scenario.cjs | 29 +++++++++++ .../tracing/orchestrion-tsx/scenario.mjs | 22 +++++++++ .../suites/tracing/orchestrion-tsx/test.ts | 49 +++++++++++++++++++ .../server-runtime-injection/src/register.ts | 45 ++++++++++++++++- .../test/register.test.ts | 13 +++++ 7 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/package.json b/dev-packages/e2e-tests/test-applications/tsx-express/package.json index 6659dbcfc603..df56af29737e 100644 --- a/dev-packages/e2e-tests/test-applications/tsx-express/package.json +++ b/dev-packages/e2e-tests/test-applications/tsx-express/package.json @@ -27,7 +27,6 @@ "tsx": "^4.20.3" }, "volta": { - "node": "20.19.5", "extends": "../../package.json" } } diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs new file mode 100644 index 000000000000..07e8afb65e04 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs @@ -0,0 +1,28 @@ +import * as Sentry from '@sentry/node'; +import { tracingChannel } from 'node:diagnostics_channel'; +import { createRequire } from 'node:module'; + +Sentry.init({ tracesSampleRate: 0 }); + +let loads = 0; +let injections = 0; +Sentry.getClient().on('orchestrion.module-injected', moduleName => { + if (moduleName === 'dataloader') { + injections++; + } +}); +tracingChannel('orchestrion:dataloader:load').start.subscribe(() => { + loads++; +}); + +const url = import.meta.resolve('dataloader'); +await import(`${url}?first`); +await import(`${url}?second`); + +const require = createRequire(import.meta.url); +delete require.cache[require.resolve('dataloader')]; +injections = 0; + +const DataLoader = require('dataloader'); +const value = await new DataLoader(async keys => keys).load('real-key'); +process.stdout.write(JSON.stringify([{ value, loads, injections }])); diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs new file mode 100644 index 000000000000..7f567101fd87 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs @@ -0,0 +1,29 @@ +const Sentry = require('@sentry/node'); +const { tracingChannel } = require('node:diagnostics_channel'); + +Sentry.init({ tracesSampleRate: 0 }); + +let loads = 0; +let injections = 0; +Sentry.getClient().on('orchestrion.module-injected', moduleName => { + if (moduleName === 'dataloader') { + injections++; + } +}); +tracingChannel('orchestrion:dataloader:load').start.subscribe(() => { + loads++; +}); + +async function run() { + const observations = []; + for (let attempt = 0; attempt < 2; attempt++) { + const DataLoader = require('dataloader'); + const value = await new DataLoader(async keys => keys).load('real-key'); + observations.push({ value, loads, injections }); + delete require.cache[require.resolve('dataloader')]; + } + await Sentry.close(1000); + process.stdout.write(JSON.stringify(observations)); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs new file mode 100644 index 000000000000..58772fbed931 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs @@ -0,0 +1,22 @@ +import * as Sentry from '@sentry/node'; +import { tracingChannel } from 'node:diagnostics_channel'; + +Sentry.init({ tracesSampleRate: 0 }); + +let loads = 0; +let injections = 0; +Sentry.getClient().on('orchestrion.module-injected', moduleName => { + if (moduleName === 'dataloader') { + injections++; + } +}); +tracingChannel('orchestrion:dataloader:load').start.subscribe(() => { + loads++; +}); + +const { default: DataLoader } = await import( + `${import.meta.resolve('dataloader')}${process.argv[2] ?? '?instrumentation-test'}` +); +const value = await new DataLoader(async keys => keys).load('real-key'); +await Sentry.close(1000); +process.stdout.write(JSON.stringify([{ value, loads, injections }])); diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts new file mode 100644 index 000000000000..09bbe2b284b1 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts @@ -0,0 +1,49 @@ +import { spawnSync } from 'node:child_process'; +import { join } from 'node:path'; +import { expect, test } from 'vitest'; + +test.each([ + { name: 'plain Node CommonJS', flags: [], scenario: 'scenario.cjs', operations: 2 }, + { name: 'tsx CommonJS', flags: ['--require', 'tsx/cjs'], scenario: 'scenario.cjs', operations: 2 }, + { name: 'plain Node ESM', flags: [], scenario: 'scenario.mjs', operations: 1 }, + // tsx can replace the hook's source, requiring another transform before compilation. + { + name: 'tsx ESM', + flags: ['--import', 'tsx'], + scenario: 'scenario.mjs', + operations: 1, + injections: expect.any(Number), + }, + { + name: 'plain Node ESM fragment', + flags: [], + scenario: 'scenario.mjs', + operations: 1, + suffix: '#instrumentation-test', + }, + { + name: 'tsx ESM fragment', + flags: ['--import', 'tsx'], + scenario: 'scenario.mjs', + operations: 1, + suffix: '#instrumentation-test', + injections: expect.any(Number), + }, + { name: 'plain Node after queried imports', flags: [], scenario: 'scenario-reload.mjs', operations: 1 }, + { name: 'tsx after queried imports', flags: ['--import', 'tsx'], scenario: 'scenario-reload.mjs', operations: 1 }, +])('instruments each operation exactly once with $name', ({ flags, scenario, operations, suffix, injections }) => { + const result = spawnSync(process.execPath, [...flags, join(__dirname, scenario), suffix ?? '?instrumentation-test'], { + encoding: 'utf8', + timeout: 30_000, + }); + + expect(result.error).toBeUndefined(); + expect(result.status, result.stderr).toBe(0); + expect(JSON.parse(result.stdout)).toEqual( + Array.from({ length: operations }, (_, index) => ({ + value: 'real-key', + loads: index + 1, + injections: injections ?? index + 1, + })), + ); +}); diff --git a/packages/server-runtime-injection/src/register.ts b/packages/server-runtime-injection/src/register.ts index fd9289aeb26d..4b51eea9a053 100644 --- a/packages/server-runtime-injection/src/register.ts +++ b/packages/server-runtime-injection/src/register.ts @@ -1,4 +1,5 @@ import { consoleSandbox, debug, getClient, GLOBAL_OBJ, parseSemver } from '@sentry/core'; +import { createHash } from 'node:crypto'; import { existsSync, readFileSync } from 'node:fs'; import * as Module from 'node:module'; import { dirname, join } from 'node:path'; @@ -10,6 +11,11 @@ import { initialize, load, resolve, createDiagnosticsPort } from '@apm-js-collab import { setDiagnosticsHook } from '@apm-js-collab/tracing-hooks/lib/diagnostics.js'; type NodeModule = { + Module: { + prototype: { + _compile: (this: unknown, content: string, filename: string, ...args: unknown[]) => unknown; + }; + }; registerHooks?: (options: { load: Function; resolve: Function }) => { deregister: () => void }; register?: typeof register; }; @@ -192,7 +198,7 @@ export function registerDiagnosticsChannelInjection(): void { // `Module.registerHooks` / `Module.register` are newer than the @types/node // we build against, hence the cast. - const mod = Module as NodeModule; + const mod = Module as unknown as NodeModule; setDiagnosticsHook(({ url, moduleName, error }): void => { if (error) { @@ -224,7 +230,42 @@ export function registerDiagnosticsChannelInjection(): void { try { if (typeof mod.registerHooks === 'function' && stableSyncHooks) { initialize({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS }); - mod.registerHooks({ resolve, load: globalAny.Deno ? withDenoFormats(load) : load }); + const filesLoadedByHook = new Map(); + const loadHook = globalAny.Deno ? withDenoFormats(load) : load; + mod.registerHooks({ + resolve, + load: (url: string, context: { format?: string }, nextLoad: Function) => { + const result = loadHook(url, context, nextLoad) as + | { format?: string; source?: string | Uint8Array; shortCircuit?: boolean } + | undefined; + const format = result?.format ?? context.format; + if ( + (format == null || format === 'commonjs') && + result?.shortCircuit === true && + result?.source != null && + url.startsWith('file:') + ) { + const sourceHash = createHash('sha256').update(result.source).digest('hex'); + filesLoadedByHook.set(pathToFileURL(fileURLToPath(url)).href, sourceHash); + } + return result; + }, + }); + + // Custom require hooks bypass load. Avoid transforming files twice when load already handled them. + const prototype = mod.Module.prototype; + const originalCompile = prototype._compile; + new ModulePatch({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS }).patch(); + const patchedCompile = prototype._compile; + prototype._compile = function (content, filename, ...args) { + const url = pathToFileURL(filename).href; + const loadedSourceHash = filesLoadedByHook.get(url); + filesLoadedByHook.delete(url); + const alreadyTransformed = + loadedSourceHash !== undefined && loadedSourceHash === createHash('sha256').update(content).digest('hex'); + const compile = alreadyTransformed ? originalCompile : patchedCompile; + return compile.call(this, content, filename, ...args); + }; debug.log('Registered diagnostics-channel injection via Module.registerHooks()'); } else if (typeof mod.register === 'function' && !globalAny.Bun && !globalAny.Deno) { // `Module.register` + the `_compile` patch is Node 18.19โ€“24.12 / 25.0 diff --git a/packages/server-runtime-injection/test/register.test.ts b/packages/server-runtime-injection/test/register.test.ts index f34408f98009..3dfa3bdb4b15 100644 --- a/packages/server-runtime-injection/test/register.test.ts +++ b/packages/server-runtime-injection/test/register.test.ts @@ -15,10 +15,12 @@ type LoadHook = (url: string, context: unknown, nextLoad: (url: string, context: // the real `Module.registerHooks({ resolve, load })` with the mocked (undefined-returning) callbacks, // leaving a broken resolve hook installed process-wide that crashes vitest's next dynamic `import()`. const registerHooksMock = vi.fn<(options: { load: LoadHook; resolve: unknown }) => void>(); +const modulePrototypeMock = vi.hoisted(() => ({ _compile: vi.fn() })); vi.mock('node:module', async importOriginal => { const actual = await importOriginal(); return { ...actual, + Module: { prototype: modulePrototypeMock }, registerHooks: (options: { load: LoadHook; resolve: unknown }) => registerHooksMock(options), register: vi.fn(), }; @@ -81,6 +83,7 @@ describe('registerDiagnosticsChannelInjection - bundled/tree-shaken detection', } beforeEach(async () => { + modulePrototypeMock._compile = vi.fn(); delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__; setDiagnosticsHookMock.mockClear(); warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); @@ -93,9 +96,19 @@ describe('registerDiagnosticsChannelInjection - bundled/tree-shaken detection', afterEach(() => { delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__; + vi.unstubAllGlobals(); warnSpy.mockRestore(); }); + it('installs the CommonJS compiler fallback on Deno', () => { + vi.stubGlobal('Deno', { version: { deno: '2.8.3' } }); + const originalCompile = modulePrototypeMock._compile; + + registerDiagnosticsChannelInjection(); + + expect(modulePrototypeMock._compile).not.toBe(originalCompile); + }); + it('includes the underlying error when registering the hooks fails', () => { // Deno always takes the `Module.registerHooks` path, independent of the Node version running the tests. (globalThis as { Deno?: unknown }).Deno = { version: { deno: '2.8.3' } }; From 298a6737aff537a0ae1b6efe3bea25d37375daf4 Mon Sep 17 00:00:00 2001 From: Michael Warkentin Date: Fri, 16 Oct 2026 14:15:29 -0400 Subject: [PATCH 026/118] fix(node,vercel-edge,bundler-plugins): INC-2566 - Ignore commit env vars set to their own name (#25244) Some builds have `VERCEL_GIT_COMMIT_SHA` set to the literal string `VERCEL_GIT_COMMIT_SHA` instead of a SHA. Release auto-detection took that value as the release name, so every build of the app uploaded its source maps into, and reported events against, one release that never changes. This is not theoretical: Sentry has received source map uploads and events whose release is the literal string `VERCEL_GIT_COMMIT_SHA` from a number of organizations deploying on Vercel. Some were large enough to contribute to [INC-2566](https://firetower.getsentry.net/INC-2566). How those builds end up with that value is still being investigated; getsentry/sentry#127101 logs the placeholder name at upload time to help with that. This guard stops the value from becoming the release either way. Release detection in the bundler plugins and the Node and Vercel Edge SDKs now skips a commit env var whose value is its own name, warns, and moves on to the next candidate. Candidates are read in order and only until one is found, so a placeholder is only warned about when it would otherwise have become the release. In the bundler plugins, the Vercel commit association in `setCommits` uses the same check, so a placeholder SHA falls back to automatic commit association instead of being sent as the commit. Claude-Session: https://claude.ai/code/session_01WgDm3ijhQZQUHGAFFqQzKQ Co-authored-by: Claude --- .../src/core/options-mapping.ts | 9 +- packages/bundler-plugins/src/core/utils.ts | 121 +++++++++++------- .../test/core/option-mappings.test.ts | 30 +++++ .../bundler-plugins/test/core/utils.test.ts | 41 ++++++ packages/node/src/sdk/api.ts | 119 ++++++++++------- .../node/test/sdk/getSentryRelease.test.ts | 37 ++++++ packages/vercel-edge/src/sdk.ts | 118 ++++++++++------- .../vercel-edge/test/getSentryRelease.test.ts | 53 ++++++++ 8 files changed, 380 insertions(+), 148 deletions(-) create mode 100644 packages/vercel-edge/test/getSentryRelease.test.ts diff --git a/packages/bundler-plugins/src/core/options-mapping.ts b/packages/bundler-plugins/src/core/options-mapping.ts index 90de13daa8c9..4847b11b0415 100644 --- a/packages/bundler-plugins/src/core/options-mapping.ts +++ b/packages/bundler-plugins/src/core/options-mapping.ts @@ -9,7 +9,7 @@ import type { ModuleMetadata, ModuleMetadataCallback, } from './types'; -import { determineReleaseName } from './utils'; +import { determineReleaseName, readCommitEnvVar } from './utils'; export type NormalizedOptions = { org: string | undefined; @@ -135,9 +135,10 @@ export function normalizeUserOptions(userOptions: UserOptions): NormalizedOption }; if (options.release.setCommits === undefined) { + const vercelCommitSha = readCommitEnvVar('VERCEL_GIT_COMMIT_SHA'); if ( process.env['VERCEL'] && - process.env['VERCEL_GIT_COMMIT_SHA'] && + vercelCommitSha && process.env['VERCEL_GIT_REPO_SLUG'] && process.env['VERCEL_GIT_REPO_OWNER'] && // We only want to set commits for the production env because Sentry becomes extremely noisy (eg on slack) for @@ -147,8 +148,8 @@ export function normalizeUserOptions(userOptions: UserOptions): NormalizedOption ) { options.release.setCommits = { shouldNotThrowOnFailure: true, - commit: process.env['VERCEL_GIT_COMMIT_SHA'], - previousCommit: process.env['VERCEL_GIT_PREVIOUS_SHA'], + commit: vercelCommitSha, + previousCommit: readCommitEnvVar('VERCEL_GIT_PREVIOUS_SHA'), repo: `${process.env['VERCEL_GIT_REPO_OWNER']}/${process.env['VERCEL_GIT_REPO_SLUG']}`, ignoreEmpty: true, ignoreMissing: true, diff --git a/packages/bundler-plugins/src/core/utils.ts b/packages/bundler-plugins/src/core/utils.ts index c8dd57597cc0..6cf8c6af8e81 100644 --- a/packages/bundler-plugins/src/core/utils.ts +++ b/packages/bundler-plugins/src/core/utils.ts @@ -221,93 +221,120 @@ export function determineReleaseName(): string | undefined { // 1. Git providers // 2. CI providers with specific environment variables (has the provider name in the variable name) // 3. CI providers with generic environment variables (checked for last to prevent possible false positives) + // Each group is only read if the earlier ones found nothing, so a placeholder value is only + // warned about when it would otherwise have become the release. - const possibleReleaseNameOfGitProvider = + const possibleReleaseNameOfGitProvider = (): string | undefined => // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables - process.env['GITHUB_SHA'] || + readCommitEnvVar('GITHUB_SHA') || // GitLab CI - https://docs.gitlab.com/ee/ci/variables/predefined_variables.html - process.env['CI_MERGE_REQUEST_SOURCE_BRANCH_SHA'] || - process.env['CI_BUILD_REF'] || - process.env['CI_COMMIT_SHA'] || + readCommitEnvVar('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || + readCommitEnvVar('CI_BUILD_REF') || + readCommitEnvVar('CI_COMMIT_SHA') || // Bitbucket - https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ - process.env['BITBUCKET_COMMIT']; + readCommitEnvVar('BITBUCKET_COMMIT'); - const possibleReleaseNameOfCiProvidersWithSpecificEnvVar = + const possibleReleaseNameOfCiProvidersWithSpecificEnvVar = (): string | undefined => // AppVeyor - https://www.appveyor.com/docs/environment-variables/ - process.env['APPVEYOR_PULL_REQUEST_HEAD_COMMIT'] || - process.env['APPVEYOR_REPO_COMMIT'] || + readCommitEnvVar('APPVEYOR_PULL_REQUEST_HEAD_COMMIT') || + readCommitEnvVar('APPVEYOR_REPO_COMMIT') || // AWS CodeBuild - https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - process.env['CODEBUILD_RESOLVED_SOURCE_VERSION'] || + readCommitEnvVar('CODEBUILD_RESOLVED_SOURCE_VERSION') || // AWS Amplify - https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html - process.env['AWS_COMMIT_ID'] || + readCommitEnvVar('AWS_COMMIT_ID') || // Azure Pipelines - https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml - process.env['BUILD_SOURCEVERSION'] || + readCommitEnvVar('BUILD_SOURCEVERSION') || // Bitrise - https://devcenter.bitrise.io/builds/available-environment-variables/ - process.env['GIT_CLONE_COMMIT_HASH'] || + readCommitEnvVar('GIT_CLONE_COMMIT_HASH') || // Buddy CI - https://buddy.works/docs/pipelines/environment-variables#default-environment-variables - process.env['BUDDY_EXECUTION_REVISION'] || + readCommitEnvVar('BUDDY_EXECUTION_REVISION') || // Builtkite - https://buildkite.com/docs/pipelines/environment-variables - process.env['BUILDKITE_COMMIT'] || + readCommitEnvVar('BUILDKITE_COMMIT') || // CircleCI - https://circleci.com/docs/variables/ - process.env['CIRCLE_SHA1'] || + readCommitEnvVar('CIRCLE_SHA1') || // Cirrus CI - https://cirrus-ci.org/guide/writing-tasks/#environment-variables - process.env['CIRRUS_CHANGE_IN_REPO'] || + readCommitEnvVar('CIRRUS_CHANGE_IN_REPO') || // Codefresh - https://codefresh.io/docs/docs/codefresh-yaml/variables/ - process.env['CF_REVISION'] || + readCommitEnvVar('CF_REVISION') || // Codemagic - https://docs.codemagic.io/yaml-basic-configuration/environment-variables/ - process.env['CM_COMMIT'] || + readCommitEnvVar('CM_COMMIT') || // Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables - process.env['CF_PAGES_COMMIT_SHA'] || + readCommitEnvVar('CF_PAGES_COMMIT_SHA') || // Drone - https://docs.drone.io/pipeline/environment/reference/ - process.env['DRONE_COMMIT_SHA'] || + readCommitEnvVar('DRONE_COMMIT_SHA') || // Flightcontrol - https://www.flightcontrol.dev/docs/guides/flightcontrol/environment-variables#built-in-environment-variables - process.env['FC_GIT_COMMIT_SHA'] || + readCommitEnvVar('FC_GIT_COMMIT_SHA') || // Heroku #1 https://devcenter.heroku.com/articles/heroku-ci - process.env['HEROKU_TEST_RUN_COMMIT_VERSION'] || + readCommitEnvVar('HEROKU_TEST_RUN_COMMIT_VERSION') || // Heroku #2 https://docs.sentry.io/product/integrations/deployment/heroku/#configure-releases - process.env['HEROKU_SLUG_COMMIT'] || + readCommitEnvVar('HEROKU_SLUG_COMMIT') || // Railway - https://docs.railway.app/reference/variables#git-variables - process.env['RAILWAY_GIT_COMMIT_SHA'] || + readCommitEnvVar('RAILWAY_GIT_COMMIT_SHA') || // Render - https://render.com/docs/environment-variables - process.env['RENDER_GIT_COMMIT'] || + readCommitEnvVar('RENDER_GIT_COMMIT') || // Semaphore CI - https://docs.semaphoreci.com/ci-cd-environment/environment-variables - process.env['SEMAPHORE_GIT_SHA'] || + readCommitEnvVar('SEMAPHORE_GIT_SHA') || // TravisCI - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables - process.env['TRAVIS_PULL_REQUEST_SHA'] || + readCommitEnvVar('TRAVIS_PULL_REQUEST_SHA') || // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables - process.env['VERCEL_GIT_COMMIT_SHA'] || - process.env['VERCEL_GITHUB_COMMIT_SHA'] || - process.env['VERCEL_GITLAB_COMMIT_SHA'] || - process.env['VERCEL_BITBUCKET_COMMIT_SHA'] || + readCommitEnvVar('VERCEL_GIT_COMMIT_SHA') || + readCommitEnvVar('VERCEL_GITHUB_COMMIT_SHA') || + readCommitEnvVar('VERCEL_GITLAB_COMMIT_SHA') || + readCommitEnvVar('VERCEL_BITBUCKET_COMMIT_SHA') || // Zeit (now known as Vercel) - process.env['ZEIT_GITHUB_COMMIT_SHA'] || - process.env['ZEIT_GITLAB_COMMIT_SHA'] || - process.env['ZEIT_BITBUCKET_COMMIT_SHA']; + readCommitEnvVar('ZEIT_GITHUB_COMMIT_SHA') || + readCommitEnvVar('ZEIT_GITLAB_COMMIT_SHA') || + readCommitEnvVar('ZEIT_BITBUCKET_COMMIT_SHA'); - const possibleReleaseNameOfCiProvidersWithGenericEnvVar = + const possibleReleaseNameOfCiProvidersWithGenericEnvVar = (): string | undefined => // CloudBees CodeShip - https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables - process.env['CI_COMMIT_ID'] || + readCommitEnvVar('CI_COMMIT_ID') || // Coolify - https://coolify.io/docs/knowledge-base/environment-variables - process.env['SOURCE_COMMIT'] || + readCommitEnvVar('SOURCE_COMMIT') || // Heroku #3 https://devcenter.heroku.com/changelog-items/630 - process.env['SOURCE_VERSION'] || + readCommitEnvVar('SOURCE_VERSION') || // Jenkins - https://plugins.jenkins.io/git/#environment-variables - process.env['GIT_COMMIT'] || + readCommitEnvVar('GIT_COMMIT') || // Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata - process.env['COMMIT_REF'] || + readCommitEnvVar('COMMIT_REF') || // TeamCity - https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html - process.env['BUILD_VCS_NUMBER'] || - // Woodpecker CI - https://woodpecker-ci.org/docs/usage/environment - process.env['CI_COMMIT_SHA']; + readCommitEnvVar('BUILD_VCS_NUMBER'); + // Woodpecker CI (https://woodpecker-ci.org/docs/usage/environment) sets CI_COMMIT_SHA, which the + // GitLab check above already reads. return ( - possibleReleaseNameOfGitProvider || - possibleReleaseNameOfCiProvidersWithSpecificEnvVar || - possibleReleaseNameOfCiProvidersWithGenericEnvVar || + possibleReleaseNameOfGitProvider() || + possibleReleaseNameOfCiProvidersWithSpecificEnvVar() || + possibleReleaseNameOfCiProvidersWithGenericEnvVar() || gitRevision() ); } +// Release detection and commit association can read the same variable in one build. +const warnedPlaceholderEnvVars = new Set(); + +/** + * Reads an environment variable that should hold a commit SHA. A value that is the variable's own + * name, such as `VERCEL_GIT_COMMIT_SHA=VERCEL_GIT_COMMIT_SHA`, is ignored: it never changes, so as + * a release name it would put every build into one release. + */ +export function readCommitEnvVar(name: string): string | undefined { + const value = process.env[name]; + if (value?.trim() === name) { + if (!warnedPlaceholderEnvVars.has(name)) { + warnedPlaceholderEnvVars.add(name); + // This runs before the plugin logger exists, so `silent: true` does not suppress it. That is + // deliberate: a warning that is easy to miss is how this misconfiguration went unnoticed. + // eslint-disable-next-line no-console + console.warn( + `[@sentry/bundler-plugins] Ignoring the ${name} environment variable as a release name, because its value is its own name. Set the release explicitly, or fix the variable in your build environment.`, + ); + } + return undefined; + } + return value; +} + function stringifyForCodeInjection(value: unknown): string | undefined { // Bundles may be inlined in HTML, where JSON quoting does not escape script tags or HTML comments. return JSON.stringify(value)?.replace(/ { expect(normalizedOptions.release.deploy).toEqual(manualDeployConfig); }); + test('should associate commits from Vercel env vars on production builds', () => { + process.env['VERCEL'] = '1'; + process.env['VERCEL_TARGET_ENV'] = 'production'; + process.env['VERCEL_GIT_COMMIT_SHA'] = 'abc123'; + process.env['VERCEL_GIT_PREVIOUS_SHA'] = 'def456'; + process.env['VERCEL_GIT_REPO_OWNER'] = 'my-owner'; + process.env['VERCEL_GIT_REPO_SLUG'] = 'my-repo'; + + const normalizedOptions = normalizeUserOptions({ release: { name: 'my-release' } }); + + expect(normalizedOptions.release.setCommits).toEqual( + expect.objectContaining({ commit: 'abc123', previousCommit: 'def456', repo: 'my-owner/my-repo' }), + ); + }); + + test('should fall back to automatic commit association when VERCEL_GIT_COMMIT_SHA is its own name', () => { + vi.spyOn(console, 'warn').mockImplementation(() => undefined); + process.env['VERCEL'] = '1'; + process.env['VERCEL_TARGET_ENV'] = 'production'; + process.env['VERCEL_GIT_COMMIT_SHA'] = 'VERCEL_GIT_COMMIT_SHA'; + process.env['VERCEL_GIT_REPO_OWNER'] = 'my-owner'; + process.env['VERCEL_GIT_REPO_SLUG'] = 'my-repo'; + + const normalizedOptions = normalizeUserOptions({ release: { name: 'my-release' } }); + + expect(normalizedOptions.release.setCommits).toEqual(expect.objectContaining({ auto: true })); + expect(normalizedOptions.release.setCommits).not.toHaveProperty('commit'); + vi.restoreAllMocks(); + }); + test('should not create deploy config when Vercel env vars are missing', () => { const userOptions: Options = { org: 'my-org', diff --git a/packages/bundler-plugins/test/core/utils.test.ts b/packages/bundler-plugins/test/core/utils.test.ts index 426f7c2553c9..e387230a4ada 100644 --- a/packages/bundler-plugins/test/core/utils.test.ts +++ b/packages/bundler-plugins/test/core/utils.test.ts @@ -345,4 +345,45 @@ describe('determineReleaseName', () => { execSyncSpy.mockRestore(); } }); + it('ignores a commit env var whose value is its own name', () => { + const originalEnv = process.env; + process.env = { VERCEL_GIT_COMMIT_SHA: 'VERCEL_GIT_COMMIT_SHA', COMMIT_REF: 'abc123' }; + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + try { + expect(determineReleaseName()).toBe('abc123'); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('VERCEL_GIT_COMMIT_SHA')); + } finally { + process.env = originalEnv; + warnSpy.mockRestore(); + } + }); + it('does not warn about a placeholder checked after the variable that supplies the release', () => { + const originalEnv = process.env; + process.env = { VERCEL_GIT_COMMIT_SHA: 'abc123', COMMIT_REF: 'COMMIT_REF' }; + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + try { + expect(determineReleaseName()).toBe('abc123'); + expect(warnSpy).not.toHaveBeenCalled(); + } finally { + process.env = originalEnv; + warnSpy.mockRestore(); + } + }); + it('warns once about a CI_COMMIT_SHA placeholder', () => { + const originalEnv = process.env; + process.env = { CI_COMMIT_SHA: 'CI_COMMIT_SHA' }; + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + const execSyncSpy = vi.spyOn(childProcess, 'execSync').mockReturnValue(Buffer.from('0'.repeat(40))); + + try { + expect(determineReleaseName()).toBe('0'.repeat(40)); + expect(warnSpy).toHaveBeenCalledTimes(1); + } finally { + process.env = originalEnv; + warnSpy.mockRestore(); + execSyncSpy.mockRestore(); + } + }); }); diff --git a/packages/node/src/sdk/api.ts b/packages/node/src/sdk/api.ts index f3d02265ea4d..572b96fafd48 100644 --- a/packages/node/src/sdk/api.ts +++ b/packages/node/src/sdk/api.ts @@ -1,7 +1,7 @@ // PUBLIC APIS import type { StackParser } from '@sentry/core'; -import { createStackParser, GLOBAL_OBJ } from '@sentry/core'; +import { consoleSandbox, createStackParser, GLOBAL_OBJ } from '@sentry/core'; import { nodeStackLineParser } from '@sentry/core/server'; import { createGetModuleFromFilename } from '../utils/module'; @@ -24,94 +24,115 @@ export function getSentryRelease(fallback?: string): string | undefined { // 1. Git providers // 2. CI providers with specific environment variables (has the provider name in the variable name) // 3. CI providers with generic environment variables (checked for last to prevent possible false positives) + // Each group is only read if the earlier ones found nothing, so a placeholder value is only + // warned about when it would otherwise have become the release. - const possibleReleaseNameOfGitProvider = + const possibleReleaseNameOfGitProvider = (): string | undefined => // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables - process.env['GITHUB_SHA'] || + readCommitEnvVar('GITHUB_SHA') || // GitLab CI - https://docs.gitlab.com/ee/ci/variables/predefined_variables.html - process.env['CI_MERGE_REQUEST_SOURCE_BRANCH_SHA'] || - process.env['CI_BUILD_REF'] || - process.env['CI_COMMIT_SHA'] || + readCommitEnvVar('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || + readCommitEnvVar('CI_BUILD_REF') || + readCommitEnvVar('CI_COMMIT_SHA') || // Bitbucket - https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ - process.env['BITBUCKET_COMMIT']; + readCommitEnvVar('BITBUCKET_COMMIT'); - const possibleReleaseNameOfCiProvidersWithSpecificEnvVar = + const possibleReleaseNameOfCiProvidersWithSpecificEnvVar = (): string | undefined => // AppVeyor - https://www.appveyor.com/docs/environment-variables/ - process.env['APPVEYOR_PULL_REQUEST_HEAD_COMMIT'] || - process.env['APPVEYOR_REPO_COMMIT'] || + readCommitEnvVar('APPVEYOR_PULL_REQUEST_HEAD_COMMIT') || + readCommitEnvVar('APPVEYOR_REPO_COMMIT') || // AWS CodeBuild - https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - process.env['CODEBUILD_RESOLVED_SOURCE_VERSION'] || + readCommitEnvVar('CODEBUILD_RESOLVED_SOURCE_VERSION') || // AWS Amplify - https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html - process.env['AWS_COMMIT_ID'] || + readCommitEnvVar('AWS_COMMIT_ID') || // Azure Pipelines - https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml - process.env['BUILD_SOURCEVERSION'] || + readCommitEnvVar('BUILD_SOURCEVERSION') || // Bitrise - https://devcenter.bitrise.io/builds/available-environment-variables/ - process.env['GIT_CLONE_COMMIT_HASH'] || + readCommitEnvVar('GIT_CLONE_COMMIT_HASH') || // Buddy CI - https://buddy.works/docs/pipelines/environment-variables#default-environment-variables - process.env['BUDDY_EXECUTION_REVISION'] || + readCommitEnvVar('BUDDY_EXECUTION_REVISION') || // Builtkite - https://buildkite.com/docs/pipelines/environment-variables - process.env['BUILDKITE_COMMIT'] || + readCommitEnvVar('BUILDKITE_COMMIT') || // CircleCI - https://circleci.com/docs/variables/ - process.env['CIRCLE_SHA1'] || + readCommitEnvVar('CIRCLE_SHA1') || // Cirrus CI - https://cirrus-ci.org/guide/writing-tasks/#environment-variables - process.env['CIRRUS_CHANGE_IN_REPO'] || + readCommitEnvVar('CIRRUS_CHANGE_IN_REPO') || // Codefresh - https://codefresh.io/docs/docs/codefresh-yaml/variables/ - process.env['CF_REVISION'] || + readCommitEnvVar('CF_REVISION') || // Codemagic - https://docs.codemagic.io/yaml-basic-configuration/environment-variables/ - process.env['CM_COMMIT'] || + readCommitEnvVar('CM_COMMIT') || // Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables - process.env['CF_PAGES_COMMIT_SHA'] || + readCommitEnvVar('CF_PAGES_COMMIT_SHA') || // Drone - https://docs.drone.io/pipeline/environment/reference/ - process.env['DRONE_COMMIT_SHA'] || + readCommitEnvVar('DRONE_COMMIT_SHA') || // Flightcontrol - https://www.flightcontrol.dev/docs/guides/flightcontrol/environment-variables#built-in-environment-variables - process.env['FC_GIT_COMMIT_SHA'] || + readCommitEnvVar('FC_GIT_COMMIT_SHA') || // Heroku #1 https://devcenter.heroku.com/articles/heroku-ci - process.env['HEROKU_TEST_RUN_COMMIT_VERSION'] || + readCommitEnvVar('HEROKU_TEST_RUN_COMMIT_VERSION') || // Heroku #2 https://devcenter.heroku.com/articles/dyno-metadata#dyno-metadata - process.env['HEROKU_BUILD_COMMIT'] || + readCommitEnvVar('HEROKU_BUILD_COMMIT') || // Heroku #3 (deprecated by Heroku, kept for backward compatibility) - process.env['HEROKU_SLUG_COMMIT'] || + readCommitEnvVar('HEROKU_SLUG_COMMIT') || // Railway - https://docs.railway.app/reference/variables#git-variables - process.env['RAILWAY_GIT_COMMIT_SHA'] || + readCommitEnvVar('RAILWAY_GIT_COMMIT_SHA') || // Render - https://render.com/docs/environment-variables - process.env['RENDER_GIT_COMMIT'] || + readCommitEnvVar('RENDER_GIT_COMMIT') || // Semaphore CI - https://docs.semaphoreci.com/ci-cd-environment/environment-variables - process.env['SEMAPHORE_GIT_SHA'] || + readCommitEnvVar('SEMAPHORE_GIT_SHA') || // TravisCI - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables - process.env['TRAVIS_PULL_REQUEST_SHA'] || + readCommitEnvVar('TRAVIS_PULL_REQUEST_SHA') || // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables - process.env['VERCEL_GIT_COMMIT_SHA'] || - process.env['VERCEL_GITHUB_COMMIT_SHA'] || - process.env['VERCEL_GITLAB_COMMIT_SHA'] || - process.env['VERCEL_BITBUCKET_COMMIT_SHA'] || + readCommitEnvVar('VERCEL_GIT_COMMIT_SHA') || + readCommitEnvVar('VERCEL_GITHUB_COMMIT_SHA') || + readCommitEnvVar('VERCEL_GITLAB_COMMIT_SHA') || + readCommitEnvVar('VERCEL_BITBUCKET_COMMIT_SHA') || // Zeit (now known as Vercel) - process.env['ZEIT_GITHUB_COMMIT_SHA'] || - process.env['ZEIT_GITLAB_COMMIT_SHA'] || - process.env['ZEIT_BITBUCKET_COMMIT_SHA']; + readCommitEnvVar('ZEIT_GITHUB_COMMIT_SHA') || + readCommitEnvVar('ZEIT_GITLAB_COMMIT_SHA') || + readCommitEnvVar('ZEIT_BITBUCKET_COMMIT_SHA'); - const possibleReleaseNameOfCiProvidersWithGenericEnvVar = + const possibleReleaseNameOfCiProvidersWithGenericEnvVar = (): string | undefined => // CloudBees CodeShip - https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables - process.env['CI_COMMIT_ID'] || + readCommitEnvVar('CI_COMMIT_ID') || // Coolify - https://coolify.io/docs/knowledge-base/environment-variables - process.env['SOURCE_COMMIT'] || + readCommitEnvVar('SOURCE_COMMIT') || // Heroku #3 https://devcenter.heroku.com/changelog-items/630 - process.env['SOURCE_VERSION'] || + readCommitEnvVar('SOURCE_VERSION') || // Jenkins - https://plugins.jenkins.io/git/#environment-variables - process.env['GIT_COMMIT'] || + readCommitEnvVar('GIT_COMMIT') || // Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata - process.env['COMMIT_REF'] || + readCommitEnvVar('COMMIT_REF') || // TeamCity - https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html - process.env['BUILD_VCS_NUMBER'] || - // Woodpecker CI - https://woodpecker-ci.org/docs/usage/environment - process.env['CI_COMMIT_SHA']; + readCommitEnvVar('BUILD_VCS_NUMBER'); + // Woodpecker CI (https://woodpecker-ci.org/docs/usage/environment) sets CI_COMMIT_SHA, which the + // GitLab check above already reads. return ( - possibleReleaseNameOfGitProvider || - possibleReleaseNameOfCiProvidersWithSpecificEnvVar || - possibleReleaseNameOfCiProvidersWithGenericEnvVar || + possibleReleaseNameOfGitProvider() || + possibleReleaseNameOfCiProvidersWithSpecificEnvVar() || + possibleReleaseNameOfCiProvidersWithGenericEnvVar() || fallback ); } +/** + * Reads an environment variable that should hold a commit SHA. A value that is the variable's own + * name, such as `VERCEL_GIT_COMMIT_SHA=VERCEL_GIT_COMMIT_SHA`, is ignored: it never changes, so as + * a release name it would put every build into one release. + */ +function readCommitEnvVar(name: string): string | undefined { + const value = process.env[name]; + if (value?.trim() === name) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] Ignoring the ${name} environment variable as a release name, because its value is its own name. Set the release explicitly, or fix the variable in your build environment.`, + ); + }); + return undefined; + } + return value; +} + /** Node.js stack parser */ export const defaultStackParser: StackParser = createStackParser(nodeStackLineParser(createGetModuleFromFilename())); diff --git a/packages/node/test/sdk/getSentryRelease.test.ts b/packages/node/test/sdk/getSentryRelease.test.ts index 3e29b94e4e9e..ea190cf98a7d 100644 --- a/packages/node/test/sdk/getSentryRelease.test.ts +++ b/packages/node/test/sdk/getSentryRelease.test.ts @@ -20,6 +20,7 @@ beforeEach(() => { afterEach(() => { vi.unstubAllEnvs(); + vi.restoreAllMocks(); }); describe('getSentryRelease', () => { @@ -41,4 +42,40 @@ describe('getSentryRelease', () => { expect(getSentryRelease()).toBe('heroku-build-commit-sha'); }); + + it('ignores a commit env var whose value is its own name', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.stubEnv('VERCEL_GIT_COMMIT_SHA', 'VERCEL_GIT_COMMIT_SHA'); + vi.stubEnv('COMMIT_REF', 'netlify-commit-ref'); + + expect(getSentryRelease()).toBe('netlify-commit-ref'); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('VERCEL_GIT_COMMIT_SHA')); + warnSpy.mockRestore(); + }); + + it('falls back when the only commit env var is its own name', () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.stubEnv('VERCEL_GIT_COMMIT_SHA', 'VERCEL_GIT_COMMIT_SHA'); + + expect(getSentryRelease('fallback')).toBe('fallback'); + }); + + it('does not warn about a placeholder checked after the variable that supplies the release', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.stubEnv('HEROKU_BUILD_COMMIT', 'heroku-build-commit-sha'); + vi.stubEnv('COMMIT_REF', 'COMMIT_REF'); + + expect(getSentryRelease()).toBe('heroku-build-commit-sha'); + expect(warnSpy).not.toHaveBeenCalled(); + warnSpy.mockRestore(); + }); + + it('warns once about a CI_COMMIT_SHA placeholder', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.stubEnv('CI_COMMIT_SHA', 'CI_COMMIT_SHA'); + + expect(getSentryRelease('fallback')).toBe('fallback'); + expect(warnSpy).toHaveBeenCalledTimes(1); + warnSpy.mockRestore(); + }); }); diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index ba3c4ce71830..bc2d74822d65 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -3,6 +3,7 @@ import type { Client, Integration } from '@sentry/core'; import { _INTERNAL_warnIfClientIsActive, consoleIntegration, + consoleSandbox, conversationIdIntegration, createStackParser, debug, @@ -155,95 +156,116 @@ export function getSentryRelease(fallback?: string): string | undefined { // 1. Git providers // 2. CI providers with specific environment variables (has the provider name in the variable name) // 3. CI providers with generic environment variables (checked for last to prevent possible false positives) + // Each group is only read if the earlier ones found nothing, so a placeholder value is only + // warned about when it would otherwise have become the release. - const possibleReleaseNameOfGitProvider = + const possibleReleaseNameOfGitProvider = (): string | undefined => // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables - process.env['GITHUB_SHA'] || + readCommitEnvVar('GITHUB_SHA') || // GitLab CI - https://docs.gitlab.com/ee/ci/variables/predefined_variables.html - process.env['CI_MERGE_REQUEST_SOURCE_BRANCH_SHA'] || - process.env['CI_BUILD_REF'] || - process.env['CI_COMMIT_SHA'] || + readCommitEnvVar('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || + readCommitEnvVar('CI_BUILD_REF') || + readCommitEnvVar('CI_COMMIT_SHA') || // Bitbucket - https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ - process.env['BITBUCKET_COMMIT']; + readCommitEnvVar('BITBUCKET_COMMIT'); - const possibleReleaseNameOfCiProvidersWithSpecificEnvVar = + const possibleReleaseNameOfCiProvidersWithSpecificEnvVar = (): string | undefined => // AppVeyor - https://www.appveyor.com/docs/environment-variables/ - process.env['APPVEYOR_PULL_REQUEST_HEAD_COMMIT'] || - process.env['APPVEYOR_REPO_COMMIT'] || + readCommitEnvVar('APPVEYOR_PULL_REQUEST_HEAD_COMMIT') || + readCommitEnvVar('APPVEYOR_REPO_COMMIT') || // AWS CodeBuild - https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - process.env['CODEBUILD_RESOLVED_SOURCE_VERSION'] || + readCommitEnvVar('CODEBUILD_RESOLVED_SOURCE_VERSION') || // AWS Amplify - https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html - process.env['AWS_COMMIT_ID'] || + readCommitEnvVar('AWS_COMMIT_ID') || // Azure Pipelines - https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml - process.env['BUILD_SOURCEVERSION'] || + readCommitEnvVar('BUILD_SOURCEVERSION') || // Bitrise - https://devcenter.bitrise.io/builds/available-environment-variables/ - process.env['GIT_CLONE_COMMIT_HASH'] || + readCommitEnvVar('GIT_CLONE_COMMIT_HASH') || // Buddy CI - https://buddy.works/docs/pipelines/environment-variables#default-environment-variables - process.env['BUDDY_EXECUTION_REVISION'] || + readCommitEnvVar('BUDDY_EXECUTION_REVISION') || // Builtkite - https://buildkite.com/docs/pipelines/environment-variables - process.env['BUILDKITE_COMMIT'] || + readCommitEnvVar('BUILDKITE_COMMIT') || // CircleCI - https://circleci.com/docs/variables/ - process.env['CIRCLE_SHA1'] || + readCommitEnvVar('CIRCLE_SHA1') || // Cirrus CI - https://cirrus-ci.org/guide/writing-tasks/#environment-variables - process.env['CIRRUS_CHANGE_IN_REPO'] || + readCommitEnvVar('CIRRUS_CHANGE_IN_REPO') || // Codefresh - https://codefresh.io/docs/docs/codefresh-yaml/variables/ - process.env['CF_REVISION'] || + readCommitEnvVar('CF_REVISION') || // Codemagic - https://docs.codemagic.io/yaml-basic-configuration/environment-variables/ - process.env['CM_COMMIT'] || + readCommitEnvVar('CM_COMMIT') || // Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables - process.env['CF_PAGES_COMMIT_SHA'] || + readCommitEnvVar('CF_PAGES_COMMIT_SHA') || // Drone - https://docs.drone.io/pipeline/environment/reference/ - process.env['DRONE_COMMIT_SHA'] || + readCommitEnvVar('DRONE_COMMIT_SHA') || // Flightcontrol - https://www.flightcontrol.dev/docs/guides/flightcontrol/environment-variables#built-in-environment-variables - process.env['FC_GIT_COMMIT_SHA'] || + readCommitEnvVar('FC_GIT_COMMIT_SHA') || // Heroku #1 https://devcenter.heroku.com/articles/heroku-ci - process.env['HEROKU_TEST_RUN_COMMIT_VERSION'] || + readCommitEnvVar('HEROKU_TEST_RUN_COMMIT_VERSION') || // Heroku #2 https://devcenter.heroku.com/articles/dyno-metadata#dyno-metadata - process.env['HEROKU_BUILD_COMMIT'] || + readCommitEnvVar('HEROKU_BUILD_COMMIT') || // Heroku #3 (deprecated by Heroku, kept for backward compatibility) - process.env['HEROKU_SLUG_COMMIT'] || + readCommitEnvVar('HEROKU_SLUG_COMMIT') || // Railway - https://docs.railway.app/reference/variables#git-variables - process.env['RAILWAY_GIT_COMMIT_SHA'] || + readCommitEnvVar('RAILWAY_GIT_COMMIT_SHA') || // Render - https://render.com/docs/environment-variables - process.env['RENDER_GIT_COMMIT'] || + readCommitEnvVar('RENDER_GIT_COMMIT') || // Semaphore CI - https://docs.semaphoreci.com/ci-cd-environment/environment-variables - process.env['SEMAPHORE_GIT_SHA'] || + readCommitEnvVar('SEMAPHORE_GIT_SHA') || // TravisCI - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables - process.env['TRAVIS_PULL_REQUEST_SHA'] || + readCommitEnvVar('TRAVIS_PULL_REQUEST_SHA') || // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables - process.env['VERCEL_GIT_COMMIT_SHA'] || - process.env['VERCEL_GITHUB_COMMIT_SHA'] || - process.env['VERCEL_GITLAB_COMMIT_SHA'] || - process.env['VERCEL_BITBUCKET_COMMIT_SHA'] || + readCommitEnvVar('VERCEL_GIT_COMMIT_SHA') || + readCommitEnvVar('VERCEL_GITHUB_COMMIT_SHA') || + readCommitEnvVar('VERCEL_GITLAB_COMMIT_SHA') || + readCommitEnvVar('VERCEL_BITBUCKET_COMMIT_SHA') || // Zeit (now known as Vercel) - process.env['ZEIT_GITHUB_COMMIT_SHA'] || - process.env['ZEIT_GITLAB_COMMIT_SHA'] || - process.env['ZEIT_BITBUCKET_COMMIT_SHA']; + readCommitEnvVar('ZEIT_GITHUB_COMMIT_SHA') || + readCommitEnvVar('ZEIT_GITLAB_COMMIT_SHA') || + readCommitEnvVar('ZEIT_BITBUCKET_COMMIT_SHA'); - const possibleReleaseNameOfCiProvidersWithGenericEnvVar = + const possibleReleaseNameOfCiProvidersWithGenericEnvVar = (): string | undefined => // CloudBees CodeShip - https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables - process.env['CI_COMMIT_ID'] || + readCommitEnvVar('CI_COMMIT_ID') || // Coolify - https://coolify.io/docs/knowledge-base/environment-variables - process.env['SOURCE_COMMIT'] || + readCommitEnvVar('SOURCE_COMMIT') || // Heroku #3 https://devcenter.heroku.com/changelog-items/630 - process.env['SOURCE_VERSION'] || + readCommitEnvVar('SOURCE_VERSION') || // Jenkins - https://plugins.jenkins.io/git/#environment-variables - process.env['GIT_COMMIT'] || + readCommitEnvVar('GIT_COMMIT') || // Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata - process.env['COMMIT_REF'] || + readCommitEnvVar('COMMIT_REF') || // TeamCity - https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html - process.env['BUILD_VCS_NUMBER'] || - // Woodpecker CI - https://woodpecker-ci.org/docs/usage/environment - process.env['CI_COMMIT_SHA']; + readCommitEnvVar('BUILD_VCS_NUMBER'); + // Woodpecker CI (https://woodpecker-ci.org/docs/usage/environment) sets CI_COMMIT_SHA, which the + // GitLab check above already reads. return ( - possibleReleaseNameOfGitProvider || - possibleReleaseNameOfCiProvidersWithSpecificEnvVar || - possibleReleaseNameOfCiProvidersWithGenericEnvVar || + possibleReleaseNameOfGitProvider() || + possibleReleaseNameOfCiProvidersWithSpecificEnvVar() || + possibleReleaseNameOfCiProvidersWithGenericEnvVar() || fallback ); } +/** + * Reads an environment variable that should hold a commit SHA. A value that is the variable's own + * name, such as `VERCEL_GIT_COMMIT_SHA=VERCEL_GIT_COMMIT_SHA`, is ignored: it never changes, so as + * a release name it would put every build into one release. + */ +function readCommitEnvVar(name: string): string | undefined { + const value = process.env[name]; + if (value?.trim() === name) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] Ignoring the ${name} environment variable as a release name, because its value is its own name. Set the release explicitly, or fix the variable in your build environment.`, + ); + }); + return undefined; + } + return value; +} + function getTraceLifecycleFromEnv(envVar: string | undefined): 'static' | 'stream' | undefined { return envVar === 'stream' || envVar === 'static' ? envVar : undefined; } diff --git a/packages/vercel-edge/test/getSentryRelease.test.ts b/packages/vercel-edge/test/getSentryRelease.test.ts new file mode 100644 index 000000000000..785a8a63617d --- /dev/null +++ b/packages/vercel-edge/test/getSentryRelease.test.ts @@ -0,0 +1,53 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { getSentryRelease } from '../src/sdk'; + +// Commit env vars set on CI (e.g. GITHUB_SHA on GitHub Actions) take precedence over the ones tested here. +const HIGHER_PRIORITY_ENV_VARS = [ + 'SENTRY_RELEASE', + 'GITHUB_SHA', + 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', + 'CI_BUILD_REF', + 'CI_COMMIT_SHA', + 'BITBUCKET_COMMIT', +]; + +function clearHigherPriorityEnvVars(): void { + for (const key of HIGHER_PRIORITY_ENV_VARS) { + vi.stubEnv(key, ''); + } +} + +afterEach(() => { + vi.unstubAllEnvs(); + vi.restoreAllMocks(); +}); + +describe('getSentryRelease', () => { + it('ignores a commit env var whose value is its own name', () => { + clearHigherPriorityEnvVars(); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.stubEnv('VERCEL_GIT_COMMIT_SHA', 'VERCEL_GIT_COMMIT_SHA'); + + expect(getSentryRelease('fallback')).toBe('fallback'); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('VERCEL_GIT_COMMIT_SHA')); + }); + + it('does not warn about a placeholder checked after the variable that supplies the release', () => { + clearHigherPriorityEnvVars(); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.stubEnv('VERCEL_GIT_COMMIT_SHA', 'vercel-commit-sha'); + vi.stubEnv('COMMIT_REF', 'COMMIT_REF'); + + expect(getSentryRelease('fallback')).toBe('vercel-commit-sha'); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('warns once about a CI_COMMIT_SHA placeholder', () => { + clearHigherPriorityEnvVars(); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.stubEnv('CI_COMMIT_SHA', 'CI_COMMIT_SHA'); + + expect(getSentryRelease('fallback')).toBe('fallback'); + expect(warnSpy).toHaveBeenCalledTimes(1); + }); +}); From df934cd47d1d37c2836200bea35b37f08404ee1c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 20:11:21 +0000 Subject: [PATCH 027/118] ref(core): Share the commit env var list used for release detection The Node SDK, the Vercel Edge SDK and the bundler plugins each kept their own copy of the commit environment variables they read to detect a release, along with their own placeholder check. The copies had already drifted: the bundler plugins did not read HEROKU_BUILD_COMMIT. The list and the placeholder check now live in @sentry/core/server as getReleaseFromCommitEnv() and readCommitEnvVar(). Node and Vercel Edge keep their SENTRY_RELEASE and injected-release checks, and the bundler plugins keep their git fallback and their own once-per-build warning. The bundler plugins now also read HEROKU_BUILD_COMMIT. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WgDm3ijhQZQUHGAFFqQzKQ --- packages/bundler-plugins/src/core/utils.ts | 127 +++------------- .../bundler-plugins/test/core/utils.test.ts | 10 ++ packages/core/src/server.ts | 2 + packages/core/src/utils/commitEnv.ts | 139 ++++++++++++++++++ .../core/test/lib/utils/commitEnv.test.ts | 95 ++++++++++++ packages/node/src/sdk/api.ts | 118 +-------------- packages/vercel-edge/src/sdk.ts | 117 +-------------- 7 files changed, 269 insertions(+), 339 deletions(-) create mode 100644 packages/core/src/utils/commitEnv.ts create mode 100644 packages/core/test/lib/utils/commitEnv.test.ts diff --git a/packages/bundler-plugins/src/core/utils.ts b/packages/bundler-plugins/src/core/utils.ts index 6cf8c6af8e81..8dfccd8f5b74 100644 --- a/packages/bundler-plugins/src/core/utils.ts +++ b/packages/bundler-plugins/src/core/utils.ts @@ -6,6 +6,7 @@ import crypto from 'crypto'; import childProcess from 'child_process'; import type { SourceMap } from 'magic-string'; import MagicString from 'magic-string'; +import { getReleaseFromCommitEnv, readCommitEnvVar as readCoreCommitEnvVar } from '@sentry/core/server'; /** * Checks whether the given input is already an array, and if it isn't, wraps it in one. @@ -215,124 +216,32 @@ function gitRevision(): string | undefined { /** * Tries to guess a release name based on environmental data. */ -// oxlint-disable-next-line complexity export function determineReleaseName(): string | undefined { - // This list is in approximate alpha order, separated into 3 categories: - // 1. Git providers - // 2. CI providers with specific environment variables (has the provider name in the variable name) - // 3. CI providers with generic environment variables (checked for last to prevent possible false positives) - // Each group is only read if the earlier ones found nothing, so a placeholder value is only - // warned about when it would otherwise have become the release. - - const possibleReleaseNameOfGitProvider = (): string | undefined => - // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables - readCommitEnvVar('GITHUB_SHA') || - // GitLab CI - https://docs.gitlab.com/ee/ci/variables/predefined_variables.html - readCommitEnvVar('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || - readCommitEnvVar('CI_BUILD_REF') || - readCommitEnvVar('CI_COMMIT_SHA') || - // Bitbucket - https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ - readCommitEnvVar('BITBUCKET_COMMIT'); - - const possibleReleaseNameOfCiProvidersWithSpecificEnvVar = (): string | undefined => - // AppVeyor - https://www.appveyor.com/docs/environment-variables/ - readCommitEnvVar('APPVEYOR_PULL_REQUEST_HEAD_COMMIT') || - readCommitEnvVar('APPVEYOR_REPO_COMMIT') || - // AWS CodeBuild - https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - readCommitEnvVar('CODEBUILD_RESOLVED_SOURCE_VERSION') || - // AWS Amplify - https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html - readCommitEnvVar('AWS_COMMIT_ID') || - // Azure Pipelines - https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml - readCommitEnvVar('BUILD_SOURCEVERSION') || - // Bitrise - https://devcenter.bitrise.io/builds/available-environment-variables/ - readCommitEnvVar('GIT_CLONE_COMMIT_HASH') || - // Buddy CI - https://buddy.works/docs/pipelines/environment-variables#default-environment-variables - readCommitEnvVar('BUDDY_EXECUTION_REVISION') || - // Builtkite - https://buildkite.com/docs/pipelines/environment-variables - readCommitEnvVar('BUILDKITE_COMMIT') || - // CircleCI - https://circleci.com/docs/variables/ - readCommitEnvVar('CIRCLE_SHA1') || - // Cirrus CI - https://cirrus-ci.org/guide/writing-tasks/#environment-variables - readCommitEnvVar('CIRRUS_CHANGE_IN_REPO') || - // Codefresh - https://codefresh.io/docs/docs/codefresh-yaml/variables/ - readCommitEnvVar('CF_REVISION') || - // Codemagic - https://docs.codemagic.io/yaml-basic-configuration/environment-variables/ - readCommitEnvVar('CM_COMMIT') || - // Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables - readCommitEnvVar('CF_PAGES_COMMIT_SHA') || - // Drone - https://docs.drone.io/pipeline/environment/reference/ - readCommitEnvVar('DRONE_COMMIT_SHA') || - // Flightcontrol - https://www.flightcontrol.dev/docs/guides/flightcontrol/environment-variables#built-in-environment-variables - readCommitEnvVar('FC_GIT_COMMIT_SHA') || - // Heroku #1 https://devcenter.heroku.com/articles/heroku-ci - readCommitEnvVar('HEROKU_TEST_RUN_COMMIT_VERSION') || - // Heroku #2 https://docs.sentry.io/product/integrations/deployment/heroku/#configure-releases - readCommitEnvVar('HEROKU_SLUG_COMMIT') || - // Railway - https://docs.railway.app/reference/variables#git-variables - readCommitEnvVar('RAILWAY_GIT_COMMIT_SHA') || - // Render - https://render.com/docs/environment-variables - readCommitEnvVar('RENDER_GIT_COMMIT') || - // Semaphore CI - https://docs.semaphoreci.com/ci-cd-environment/environment-variables - readCommitEnvVar('SEMAPHORE_GIT_SHA') || - // TravisCI - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables - readCommitEnvVar('TRAVIS_PULL_REQUEST_SHA') || - // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables - readCommitEnvVar('VERCEL_GIT_COMMIT_SHA') || - readCommitEnvVar('VERCEL_GITHUB_COMMIT_SHA') || - readCommitEnvVar('VERCEL_GITLAB_COMMIT_SHA') || - readCommitEnvVar('VERCEL_BITBUCKET_COMMIT_SHA') || - // Zeit (now known as Vercel) - readCommitEnvVar('ZEIT_GITHUB_COMMIT_SHA') || - readCommitEnvVar('ZEIT_GITLAB_COMMIT_SHA') || - readCommitEnvVar('ZEIT_BITBUCKET_COMMIT_SHA'); - - const possibleReleaseNameOfCiProvidersWithGenericEnvVar = (): string | undefined => - // CloudBees CodeShip - https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables - readCommitEnvVar('CI_COMMIT_ID') || - // Coolify - https://coolify.io/docs/knowledge-base/environment-variables - readCommitEnvVar('SOURCE_COMMIT') || - // Heroku #3 https://devcenter.heroku.com/changelog-items/630 - readCommitEnvVar('SOURCE_VERSION') || - // Jenkins - https://plugins.jenkins.io/git/#environment-variables - readCommitEnvVar('GIT_COMMIT') || - // Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata - readCommitEnvVar('COMMIT_REF') || - // TeamCity - https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html - readCommitEnvVar('BUILD_VCS_NUMBER'); - // Woodpecker CI (https://woodpecker-ci.org/docs/usage/environment) sets CI_COMMIT_SHA, which the - // GitLab check above already reads. - - return ( - possibleReleaseNameOfGitProvider() || - possibleReleaseNameOfCiProvidersWithSpecificEnvVar() || - possibleReleaseNameOfCiProvidersWithGenericEnvVar() || - gitRevision() - ); + return getReleaseFromCommitEnv(warnAboutPlaceholderCommitEnvVar) || gitRevision(); } // Release detection and commit association can read the same variable in one build. const warnedPlaceholderEnvVars = new Set(); +function warnAboutPlaceholderCommitEnvVar(name: string): void { + if (warnedPlaceholderEnvVars.has(name)) { + return; + } + warnedPlaceholderEnvVars.add(name); + // This runs before the plugin logger exists, so `silent: true` does not suppress it. That is + // deliberate: a warning that is easy to miss is how this misconfiguration went unnoticed. + // eslint-disable-next-line no-console + console.warn( + `[@sentry/bundler-plugins] Ignoring the ${name} environment variable as a release name, because its value is its own name. Set the release explicitly, or fix the variable in your build environment.`, + ); +} + /** - * Reads an environment variable that should hold a commit SHA. A value that is the variable's own - * name, such as `VERCEL_GIT_COMMIT_SHA=VERCEL_GIT_COMMIT_SHA`, is ignored: it never changes, so as - * a release name it would put every build into one release. + * Reads an environment variable that should hold a commit SHA, ignoring a value that is the + * variable's own name. See `readCommitEnvVar` in `@sentry/core/server`. */ export function readCommitEnvVar(name: string): string | undefined { - const value = process.env[name]; - if (value?.trim() === name) { - if (!warnedPlaceholderEnvVars.has(name)) { - warnedPlaceholderEnvVars.add(name); - // This runs before the plugin logger exists, so `silent: true` does not suppress it. That is - // deliberate: a warning that is easy to miss is how this misconfiguration went unnoticed. - // eslint-disable-next-line no-console - console.warn( - `[@sentry/bundler-plugins] Ignoring the ${name} environment variable as a release name, because its value is its own name. Set the release explicitly, or fix the variable in your build environment.`, - ); - } - return undefined; - } - return value; + return readCoreCommitEnvVar(name, warnAboutPlaceholderCommitEnvVar); } function stringifyForCodeInjection(value: unknown): string | undefined { diff --git a/packages/bundler-plugins/test/core/utils.test.ts b/packages/bundler-plugins/test/core/utils.test.ts index e387230a4ada..68e62701f0fa 100644 --- a/packages/bundler-plugins/test/core/utils.test.ts +++ b/packages/bundler-plugins/test/core/utils.test.ts @@ -345,6 +345,16 @@ describe('determineReleaseName', () => { execSyncSpy.mockRestore(); } }); + it('detects HEROKU_BUILD_COMMIT', () => { + const originalEnv = process.env; + process.env = { HEROKU_BUILD_COMMIT: 'heroku-build-commit' }; + + try { + expect(determineReleaseName()).toBe('heroku-build-commit'); + } finally { + process.env = originalEnv; + } + }); it('ignores a commit env var whose value is its own name', () => { const originalEnv = process.env; process.env = { VERCEL_GIT_COMMIT_SHA: 'VERCEL_GIT_COMMIT_SHA', COMMIT_REF: 'abc123' }; diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index e44bb9c2df8b..f981065c14c8 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -12,6 +12,8 @@ export { wrapMcpServerWithSentry } from './integrations/mcp-server'; export { isNodeEnv, loadModule } from './utils/node'; export { filenameIsInApp, node, nodeStackLineParser } from './utils/node-stack-trace'; export { vercelWaitUntil } from './utils/vercelWaitUntil'; +export { getReleaseFromCommitEnv, readCommitEnvVar } from './utils/commitEnv'; +export type { PlaceholderCommitEnvVarHandler } from './utils/commitEnv'; export { flushIfServerless } from './utils/flushIfServerless'; export { callFrameToStackFrame, watchdogTimer } from './utils/anr'; export { safeUnref as _INTERNAL_safeUnref } from './utils/timer'; diff --git a/packages/core/src/utils/commitEnv.ts b/packages/core/src/utils/commitEnv.ts new file mode 100644 index 000000000000..ca5d6fe75de1 --- /dev/null +++ b/packages/core/src/utils/commitEnv.ts @@ -0,0 +1,139 @@ +import { consoleSandbox } from './debug-logger'; + +/** + * Environment variables that CI and hosting providers set to the commit being built, in the order + * release detection checks them. The list is in approximate alpha order, separated into 3 categories: + * 1. Git providers + * 2. CI providers with specific environment variables (has the provider name in the variable name) + * 3. CI providers with generic environment variables (checked for last to prevent possible false positives) + */ +const COMMIT_ENV_VARS = [ + // 1. Git providers + // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables + 'GITHUB_SHA', + // GitLab CI - https://docs.gitlab.com/ee/ci/variables/predefined_variables.html + // Woodpecker CI also sets CI_COMMIT_SHA - https://woodpecker-ci.org/docs/usage/environment + 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', + 'CI_BUILD_REF', + 'CI_COMMIT_SHA', + // Bitbucket - https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ + 'BITBUCKET_COMMIT', + + // 2. CI providers with specific environment variables + // AppVeyor - https://www.appveyor.com/docs/environment-variables/ + 'APPVEYOR_PULL_REQUEST_HEAD_COMMIT', + 'APPVEYOR_REPO_COMMIT', + // AWS CodeBuild - https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html + 'CODEBUILD_RESOLVED_SOURCE_VERSION', + // AWS Amplify - https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html + 'AWS_COMMIT_ID', + // Azure Pipelines - https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml + 'BUILD_SOURCEVERSION', + // Bitrise - https://devcenter.bitrise.io/builds/available-environment-variables/ + 'GIT_CLONE_COMMIT_HASH', + // Buddy CI - https://buddy.works/docs/pipelines/environment-variables#default-environment-variables + 'BUDDY_EXECUTION_REVISION', + // Builtkite - https://buildkite.com/docs/pipelines/environment-variables + 'BUILDKITE_COMMIT', + // CircleCI - https://circleci.com/docs/variables/ + 'CIRCLE_SHA1', + // Cirrus CI - https://cirrus-ci.org/guide/writing-tasks/#environment-variables + 'CIRRUS_CHANGE_IN_REPO', + // Codefresh - https://codefresh.io/docs/docs/codefresh-yaml/variables/ + 'CF_REVISION', + // Codemagic - https://docs.codemagic.io/yaml-basic-configuration/environment-variables/ + 'CM_COMMIT', + // Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables + 'CF_PAGES_COMMIT_SHA', + // Drone - https://docs.drone.io/pipeline/environment/reference/ + 'DRONE_COMMIT_SHA', + // Flightcontrol - https://www.flightcontrol.dev/docs/guides/flightcontrol/environment-variables#built-in-environment-variables + 'FC_GIT_COMMIT_SHA', + // Heroku #1 https://devcenter.heroku.com/articles/heroku-ci + 'HEROKU_TEST_RUN_COMMIT_VERSION', + // Heroku #2 https://devcenter.heroku.com/articles/dyno-metadata#dyno-metadata + 'HEROKU_BUILD_COMMIT', + // Heroku #3 (deprecated by Heroku, kept for backward compatibility) + 'HEROKU_SLUG_COMMIT', + // Railway - https://docs.railway.app/reference/variables#git-variables + 'RAILWAY_GIT_COMMIT_SHA', + // Render - https://render.com/docs/environment-variables + 'RENDER_GIT_COMMIT', + // Semaphore CI - https://docs.semaphoreci.com/ci-cd-environment/environment-variables + 'SEMAPHORE_GIT_SHA', + // TravisCI - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables + 'TRAVIS_PULL_REQUEST_SHA', + // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables + 'VERCEL_GIT_COMMIT_SHA', + 'VERCEL_GITHUB_COMMIT_SHA', + 'VERCEL_GITLAB_COMMIT_SHA', + 'VERCEL_BITBUCKET_COMMIT_SHA', + // Zeit (now known as Vercel) + 'ZEIT_GITHUB_COMMIT_SHA', + 'ZEIT_GITLAB_COMMIT_SHA', + 'ZEIT_BITBUCKET_COMMIT_SHA', + + // 3. CI providers with generic environment variables + // CloudBees CodeShip - https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables + 'CI_COMMIT_ID', + // Coolify - https://coolify.io/docs/knowledge-base/environment-variables + 'SOURCE_COMMIT', + // Heroku #4 https://devcenter.heroku.com/changelog-items/630 + 'SOURCE_VERSION', + // Jenkins - https://plugins.jenkins.io/git/#environment-variables + 'GIT_COMMIT', + // Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata + 'COMMIT_REF', + // TeamCity - https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html + 'BUILD_VCS_NUMBER', +] as const; + +/** Called with the name of a commit environment variable whose value is its own name. */ +export type PlaceholderCommitEnvVarHandler = (name: string) => void; + +function warnAboutPlaceholderCommitEnvVar(name: string): void { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] Ignoring the ${name} environment variable as a release name, because its value is its own name. Set the release explicitly, or fix the variable in your build environment.`, + ); + }); +} + +/** + * Reads an environment variable that should hold a commit SHA. A value that is the variable's own + * name, such as `VERCEL_GIT_COMMIT_SHA=VERCEL_GIT_COMMIT_SHA`, is ignored: it never changes, so as + * a release name it would put every build into one release. `onPlaceholder` is called for such a + * value and warns on the console by default. + */ +export function readCommitEnvVar( + name: string, + onPlaceholder: PlaceholderCommitEnvVarHandler = warnAboutPlaceholderCommitEnvVar, +): string | undefined { + if (typeof process === 'undefined') { + return undefined; + } + const value = process.env[name]; + if (value?.trim() === name) { + onPlaceholder(name); + return undefined; + } + return value || undefined; +} + +/** + * Returns the commit that CI or the hosting provider is building, read from the first of the known + * commit environment variables that is set. Variables are read in order and only until one is + * found, so a placeholder value is only reported when it would otherwise have become the release. + */ +export function getReleaseFromCommitEnv( + onPlaceholder: PlaceholderCommitEnvVarHandler = warnAboutPlaceholderCommitEnvVar, +): string | undefined { + for (const name of COMMIT_ENV_VARS) { + const value = readCommitEnvVar(name, onPlaceholder); + if (value) { + return value; + } + } + return undefined; +} diff --git a/packages/core/test/lib/utils/commitEnv.test.ts b/packages/core/test/lib/utils/commitEnv.test.ts new file mode 100644 index 000000000000..5954785256fb --- /dev/null +++ b/packages/core/test/lib/utils/commitEnv.test.ts @@ -0,0 +1,95 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { getReleaseFromCommitEnv, readCommitEnvVar } from '../../../src/utils/commitEnv'; + +describe('commitEnv', () => { + const originalEnv = process.env; + + beforeEach(() => { + // CI sets some of these variables (e.g. GITHUB_SHA on GitHub Actions), so start from an empty env. + process.env = {}; + }); + + afterEach(() => { + process.env = originalEnv; + vi.restoreAllMocks(); + }); + + describe('readCommitEnvVar', () => { + it('returns the value of the variable', () => { + process.env.VERCEL_GIT_COMMIT_SHA = 'abc123'; + + expect(readCommitEnvVar('VERCEL_GIT_COMMIT_SHA')).toBe('abc123'); + }); + + it('returns undefined for an unset or empty variable', () => { + process.env.VERCEL_GIT_COMMIT_SHA = ''; + + expect(readCommitEnvVar('VERCEL_GIT_COMMIT_SHA')).toBeUndefined(); + expect(readCommitEnvVar('GITHUB_SHA')).toBeUndefined(); + }); + + it('ignores a value that is its own name and warns by default', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + process.env.VERCEL_GIT_COMMIT_SHA = ' VERCEL_GIT_COMMIT_SHA '; + + expect(readCommitEnvVar('VERCEL_GIT_COMMIT_SHA')).toBeUndefined(); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('VERCEL_GIT_COMMIT_SHA')); + }); + + it('passes a placeholder to a custom handler instead of warning', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const onPlaceholder = vi.fn(); + process.env.VERCEL_GIT_COMMIT_SHA = 'VERCEL_GIT_COMMIT_SHA'; + + expect(readCommitEnvVar('VERCEL_GIT_COMMIT_SHA', onPlaceholder)).toBeUndefined(); + expect(onPlaceholder).toHaveBeenCalledWith('VERCEL_GIT_COMMIT_SHA'); + expect(warnSpy).not.toHaveBeenCalled(); + }); + }); + + describe('getReleaseFromCommitEnv', () => { + it('returns undefined when no commit variable is set', () => { + expect(getReleaseFromCommitEnv()).toBeUndefined(); + }); + + it('prefers git provider variables over generic CI variables', () => { + process.env.COMMIT_REF = 'netlify-commit'; + process.env.GITHUB_SHA = 'github-commit'; + + expect(getReleaseFromCommitEnv()).toBe('github-commit'); + }); + + it('reads HEROKU_BUILD_COMMIT before HEROKU_SLUG_COMMIT', () => { + process.env.HEROKU_BUILD_COMMIT = 'heroku-build-commit'; + process.env.HEROKU_SLUG_COMMIT = 'heroku-slug-commit'; + + expect(getReleaseFromCommitEnv()).toBe('heroku-build-commit'); + }); + + it('skips a placeholder and uses the next variable that is set', () => { + const onPlaceholder = vi.fn(); + process.env.VERCEL_GIT_COMMIT_SHA = 'VERCEL_GIT_COMMIT_SHA'; + process.env.COMMIT_REF = 'netlify-commit'; + + expect(getReleaseFromCommitEnv(onPlaceholder)).toBe('netlify-commit'); + expect(onPlaceholder).toHaveBeenCalledWith('VERCEL_GIT_COMMIT_SHA'); + }); + + it('does not report a placeholder checked after the variable that supplies the release', () => { + const onPlaceholder = vi.fn(); + process.env.VERCEL_GIT_COMMIT_SHA = 'vercel-commit'; + process.env.COMMIT_REF = 'COMMIT_REF'; + + expect(getReleaseFromCommitEnv(onPlaceholder)).toBe('vercel-commit'); + expect(onPlaceholder).not.toHaveBeenCalled(); + }); + + it('reports each placeholder once', () => { + const onPlaceholder = vi.fn(); + process.env.CI_COMMIT_SHA = 'CI_COMMIT_SHA'; + + expect(getReleaseFromCommitEnv(onPlaceholder)).toBeUndefined(); + expect(onPlaceholder).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/packages/node/src/sdk/api.ts b/packages/node/src/sdk/api.ts index 572b96fafd48..71fc95c3095e 100644 --- a/packages/node/src/sdk/api.ts +++ b/packages/node/src/sdk/api.ts @@ -1,14 +1,13 @@ // PUBLIC APIS import type { StackParser } from '@sentry/core'; -import { consoleSandbox, createStackParser, GLOBAL_OBJ } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { createStackParser, GLOBAL_OBJ } from '@sentry/core'; +import { getReleaseFromCommitEnv, nodeStackLineParser } from '@sentry/core/server'; import { createGetModuleFromFilename } from '../utils/module'; /** * Returns a release dynamically from environment variables. */ -// eslint-disable-next-line complexity export function getSentryRelease(fallback?: string): string | undefined { // Always read first as Sentry takes this as precedence if (process.env.SENTRY_RELEASE) { @@ -20,118 +19,7 @@ export function getSentryRelease(fallback?: string): string | undefined { return GLOBAL_OBJ.SENTRY_RELEASE.id; } - // This list is in approximate alpha order, separated into 3 categories: - // 1. Git providers - // 2. CI providers with specific environment variables (has the provider name in the variable name) - // 3. CI providers with generic environment variables (checked for last to prevent possible false positives) - // Each group is only read if the earlier ones found nothing, so a placeholder value is only - // warned about when it would otherwise have become the release. - - const possibleReleaseNameOfGitProvider = (): string | undefined => - // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables - readCommitEnvVar('GITHUB_SHA') || - // GitLab CI - https://docs.gitlab.com/ee/ci/variables/predefined_variables.html - readCommitEnvVar('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || - readCommitEnvVar('CI_BUILD_REF') || - readCommitEnvVar('CI_COMMIT_SHA') || - // Bitbucket - https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ - readCommitEnvVar('BITBUCKET_COMMIT'); - - const possibleReleaseNameOfCiProvidersWithSpecificEnvVar = (): string | undefined => - // AppVeyor - https://www.appveyor.com/docs/environment-variables/ - readCommitEnvVar('APPVEYOR_PULL_REQUEST_HEAD_COMMIT') || - readCommitEnvVar('APPVEYOR_REPO_COMMIT') || - // AWS CodeBuild - https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - readCommitEnvVar('CODEBUILD_RESOLVED_SOURCE_VERSION') || - // AWS Amplify - https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html - readCommitEnvVar('AWS_COMMIT_ID') || - // Azure Pipelines - https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml - readCommitEnvVar('BUILD_SOURCEVERSION') || - // Bitrise - https://devcenter.bitrise.io/builds/available-environment-variables/ - readCommitEnvVar('GIT_CLONE_COMMIT_HASH') || - // Buddy CI - https://buddy.works/docs/pipelines/environment-variables#default-environment-variables - readCommitEnvVar('BUDDY_EXECUTION_REVISION') || - // Builtkite - https://buildkite.com/docs/pipelines/environment-variables - readCommitEnvVar('BUILDKITE_COMMIT') || - // CircleCI - https://circleci.com/docs/variables/ - readCommitEnvVar('CIRCLE_SHA1') || - // Cirrus CI - https://cirrus-ci.org/guide/writing-tasks/#environment-variables - readCommitEnvVar('CIRRUS_CHANGE_IN_REPO') || - // Codefresh - https://codefresh.io/docs/docs/codefresh-yaml/variables/ - readCommitEnvVar('CF_REVISION') || - // Codemagic - https://docs.codemagic.io/yaml-basic-configuration/environment-variables/ - readCommitEnvVar('CM_COMMIT') || - // Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables - readCommitEnvVar('CF_PAGES_COMMIT_SHA') || - // Drone - https://docs.drone.io/pipeline/environment/reference/ - readCommitEnvVar('DRONE_COMMIT_SHA') || - // Flightcontrol - https://www.flightcontrol.dev/docs/guides/flightcontrol/environment-variables#built-in-environment-variables - readCommitEnvVar('FC_GIT_COMMIT_SHA') || - // Heroku #1 https://devcenter.heroku.com/articles/heroku-ci - readCommitEnvVar('HEROKU_TEST_RUN_COMMIT_VERSION') || - // Heroku #2 https://devcenter.heroku.com/articles/dyno-metadata#dyno-metadata - readCommitEnvVar('HEROKU_BUILD_COMMIT') || - // Heroku #3 (deprecated by Heroku, kept for backward compatibility) - readCommitEnvVar('HEROKU_SLUG_COMMIT') || - // Railway - https://docs.railway.app/reference/variables#git-variables - readCommitEnvVar('RAILWAY_GIT_COMMIT_SHA') || - // Render - https://render.com/docs/environment-variables - readCommitEnvVar('RENDER_GIT_COMMIT') || - // Semaphore CI - https://docs.semaphoreci.com/ci-cd-environment/environment-variables - readCommitEnvVar('SEMAPHORE_GIT_SHA') || - // TravisCI - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables - readCommitEnvVar('TRAVIS_PULL_REQUEST_SHA') || - // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables - readCommitEnvVar('VERCEL_GIT_COMMIT_SHA') || - readCommitEnvVar('VERCEL_GITHUB_COMMIT_SHA') || - readCommitEnvVar('VERCEL_GITLAB_COMMIT_SHA') || - readCommitEnvVar('VERCEL_BITBUCKET_COMMIT_SHA') || - // Zeit (now known as Vercel) - readCommitEnvVar('ZEIT_GITHUB_COMMIT_SHA') || - readCommitEnvVar('ZEIT_GITLAB_COMMIT_SHA') || - readCommitEnvVar('ZEIT_BITBUCKET_COMMIT_SHA'); - - const possibleReleaseNameOfCiProvidersWithGenericEnvVar = (): string | undefined => - // CloudBees CodeShip - https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables - readCommitEnvVar('CI_COMMIT_ID') || - // Coolify - https://coolify.io/docs/knowledge-base/environment-variables - readCommitEnvVar('SOURCE_COMMIT') || - // Heroku #3 https://devcenter.heroku.com/changelog-items/630 - readCommitEnvVar('SOURCE_VERSION') || - // Jenkins - https://plugins.jenkins.io/git/#environment-variables - readCommitEnvVar('GIT_COMMIT') || - // Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata - readCommitEnvVar('COMMIT_REF') || - // TeamCity - https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html - readCommitEnvVar('BUILD_VCS_NUMBER'); - // Woodpecker CI (https://woodpecker-ci.org/docs/usage/environment) sets CI_COMMIT_SHA, which the - // GitLab check above already reads. - - return ( - possibleReleaseNameOfGitProvider() || - possibleReleaseNameOfCiProvidersWithSpecificEnvVar() || - possibleReleaseNameOfCiProvidersWithGenericEnvVar() || - fallback - ); -} - -/** - * Reads an environment variable that should hold a commit SHA. A value that is the variable's own - * name, such as `VERCEL_GIT_COMMIT_SHA=VERCEL_GIT_COMMIT_SHA`, is ignored: it never changes, so as - * a release name it would put every build into one release. - */ -function readCommitEnvVar(name: string): string | undefined { - const value = process.env[name]; - if (value?.trim() === name) { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - `[Sentry] Ignoring the ${name} environment variable as a release name, because its value is its own name. Set the release explicitly, or fix the variable in your build environment.`, - ); - }); - return undefined; - } - return value; + return getReleaseFromCommitEnv() || fallback; } /** Node.js stack parser */ diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index bc2d74822d65..c79468692456 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -3,7 +3,6 @@ import type { Client, Integration } from '@sentry/core'; import { _INTERNAL_warnIfClientIsActive, consoleIntegration, - consoleSandbox, conversationIdIntegration, createStackParser, debug, @@ -18,7 +17,7 @@ import { requestDataIntegration, stackParserFromStackParserOptions, } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { getReleaseFromCommitEnv, nodeStackLineParser } from '@sentry/core/server'; import { SentryPropagator, SentryTracerProvider, @@ -140,7 +139,6 @@ function setupOpenTelemetryLogger(): void { /** * Returns a release dynamically from environment variables. */ -// eslint-disable-next-line complexity export function getSentryRelease(fallback?: string): string | undefined { // Always read first as Sentry takes this as precedence if (process.env.SENTRY_RELEASE) { @@ -152,118 +150,7 @@ export function getSentryRelease(fallback?: string): string | undefined { return GLOBAL_OBJ.SENTRY_RELEASE.id; } - // This list is in approximate alpha order, separated into 3 categories: - // 1. Git providers - // 2. CI providers with specific environment variables (has the provider name in the variable name) - // 3. CI providers with generic environment variables (checked for last to prevent possible false positives) - // Each group is only read if the earlier ones found nothing, so a placeholder value is only - // warned about when it would otherwise have become the release. - - const possibleReleaseNameOfGitProvider = (): string | undefined => - // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables - readCommitEnvVar('GITHUB_SHA') || - // GitLab CI - https://docs.gitlab.com/ee/ci/variables/predefined_variables.html - readCommitEnvVar('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || - readCommitEnvVar('CI_BUILD_REF') || - readCommitEnvVar('CI_COMMIT_SHA') || - // Bitbucket - https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ - readCommitEnvVar('BITBUCKET_COMMIT'); - - const possibleReleaseNameOfCiProvidersWithSpecificEnvVar = (): string | undefined => - // AppVeyor - https://www.appveyor.com/docs/environment-variables/ - readCommitEnvVar('APPVEYOR_PULL_REQUEST_HEAD_COMMIT') || - readCommitEnvVar('APPVEYOR_REPO_COMMIT') || - // AWS CodeBuild - https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - readCommitEnvVar('CODEBUILD_RESOLVED_SOURCE_VERSION') || - // AWS Amplify - https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html - readCommitEnvVar('AWS_COMMIT_ID') || - // Azure Pipelines - https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml - readCommitEnvVar('BUILD_SOURCEVERSION') || - // Bitrise - https://devcenter.bitrise.io/builds/available-environment-variables/ - readCommitEnvVar('GIT_CLONE_COMMIT_HASH') || - // Buddy CI - https://buddy.works/docs/pipelines/environment-variables#default-environment-variables - readCommitEnvVar('BUDDY_EXECUTION_REVISION') || - // Builtkite - https://buildkite.com/docs/pipelines/environment-variables - readCommitEnvVar('BUILDKITE_COMMIT') || - // CircleCI - https://circleci.com/docs/variables/ - readCommitEnvVar('CIRCLE_SHA1') || - // Cirrus CI - https://cirrus-ci.org/guide/writing-tasks/#environment-variables - readCommitEnvVar('CIRRUS_CHANGE_IN_REPO') || - // Codefresh - https://codefresh.io/docs/docs/codefresh-yaml/variables/ - readCommitEnvVar('CF_REVISION') || - // Codemagic - https://docs.codemagic.io/yaml-basic-configuration/environment-variables/ - readCommitEnvVar('CM_COMMIT') || - // Cloudflare Pages - https://developers.cloudflare.com/pages/platform/build-configuration/#environment-variables - readCommitEnvVar('CF_PAGES_COMMIT_SHA') || - // Drone - https://docs.drone.io/pipeline/environment/reference/ - readCommitEnvVar('DRONE_COMMIT_SHA') || - // Flightcontrol - https://www.flightcontrol.dev/docs/guides/flightcontrol/environment-variables#built-in-environment-variables - readCommitEnvVar('FC_GIT_COMMIT_SHA') || - // Heroku #1 https://devcenter.heroku.com/articles/heroku-ci - readCommitEnvVar('HEROKU_TEST_RUN_COMMIT_VERSION') || - // Heroku #2 https://devcenter.heroku.com/articles/dyno-metadata#dyno-metadata - readCommitEnvVar('HEROKU_BUILD_COMMIT') || - // Heroku #3 (deprecated by Heroku, kept for backward compatibility) - readCommitEnvVar('HEROKU_SLUG_COMMIT') || - // Railway - https://docs.railway.app/reference/variables#git-variables - readCommitEnvVar('RAILWAY_GIT_COMMIT_SHA') || - // Render - https://render.com/docs/environment-variables - readCommitEnvVar('RENDER_GIT_COMMIT') || - // Semaphore CI - https://docs.semaphoreci.com/ci-cd-environment/environment-variables - readCommitEnvVar('SEMAPHORE_GIT_SHA') || - // TravisCI - https://docs.travis-ci.com/user/environment-variables/#default-environment-variables - readCommitEnvVar('TRAVIS_PULL_REQUEST_SHA') || - // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables - readCommitEnvVar('VERCEL_GIT_COMMIT_SHA') || - readCommitEnvVar('VERCEL_GITHUB_COMMIT_SHA') || - readCommitEnvVar('VERCEL_GITLAB_COMMIT_SHA') || - readCommitEnvVar('VERCEL_BITBUCKET_COMMIT_SHA') || - // Zeit (now known as Vercel) - readCommitEnvVar('ZEIT_GITHUB_COMMIT_SHA') || - readCommitEnvVar('ZEIT_GITLAB_COMMIT_SHA') || - readCommitEnvVar('ZEIT_BITBUCKET_COMMIT_SHA'); - - const possibleReleaseNameOfCiProvidersWithGenericEnvVar = (): string | undefined => - // CloudBees CodeShip - https://docs.cloudbees.com/docs/cloudbees-codeship/latest/pro-builds-and-configuration/environment-variables - readCommitEnvVar('CI_COMMIT_ID') || - // Coolify - https://coolify.io/docs/knowledge-base/environment-variables - readCommitEnvVar('SOURCE_COMMIT') || - // Heroku #3 https://devcenter.heroku.com/changelog-items/630 - readCommitEnvVar('SOURCE_VERSION') || - // Jenkins - https://plugins.jenkins.io/git/#environment-variables - readCommitEnvVar('GIT_COMMIT') || - // Netlify - https://docs.netlify.com/configure-builds/environment-variables/#build-metadata - readCommitEnvVar('COMMIT_REF') || - // TeamCity - https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html - readCommitEnvVar('BUILD_VCS_NUMBER'); - // Woodpecker CI (https://woodpecker-ci.org/docs/usage/environment) sets CI_COMMIT_SHA, which the - // GitLab check above already reads. - - return ( - possibleReleaseNameOfGitProvider() || - possibleReleaseNameOfCiProvidersWithSpecificEnvVar() || - possibleReleaseNameOfCiProvidersWithGenericEnvVar() || - fallback - ); -} - -/** - * Reads an environment variable that should hold a commit SHA. A value that is the variable's own - * name, such as `VERCEL_GIT_COMMIT_SHA=VERCEL_GIT_COMMIT_SHA`, is ignored: it never changes, so as - * a release name it would put every build into one release. - */ -function readCommitEnvVar(name: string): string | undefined { - const value = process.env[name]; - if (value?.trim() === name) { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - `[Sentry] Ignoring the ${name} environment variable as a release name, because its value is its own name. Set the release explicitly, or fix the variable in your build environment.`, - ); - }); - return undefined; - } - return value; + return getReleaseFromCommitEnv() || fallback; } function getTraceLifecycleFromEnv(envVar: string | undefined): 'static' | 'stream' | undefined { From 045bcb0977c01cac4fbbcc3d9a1a26720b125c44 Mon Sep 17 00:00:00 2001 From: mika Date: Sat, 17 Oct 2026 19:10:23 -0700 Subject: [PATCH 028/118] fix(elysia): use Node transport on Node runtimes (#25015) Elysia on Node currently defaults to Bun's fetch transport, bypassing the Node transport's proxy and TLS configuration. Choose the default transport by runtime so Node uses `makeNodeTransport`, while Bun keeps `makeFetchTransport` and explicit transport overrides continue to work. Regression tests cover both runtime defaults. Fixes #24048 Fixes JS-3583 Co-authored-by: mika <211269698+mikamikasuki@users.noreply.github.com> Co-authored-by: GPT-6.1 Sol --- packages/elysia/src/sdk.ts | 3 ++- packages/elysia/test/sdk.test.ts | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/elysia/src/sdk.ts b/packages/elysia/src/sdk.ts index f8d8dd9d8a35..be7225026a63 100644 --- a/packages/elysia/src/sdk.ts +++ b/packages/elysia/src/sdk.ts @@ -3,6 +3,7 @@ import { bunServerIntegration, getDefaultIntegrations as getBunDefaultIntegrations, makeFetchTransport, + makeNodeTransport, } from '@sentry/bun'; import type { Integration, Options } from '@sentry/core'; import { applySdkMetadata } from '@sentry/core'; @@ -50,7 +51,7 @@ export function init(userOptions: ElysiaOptions = {}): NodeClient | undefined { applySdkMetadata(userOptions, 'elysia', ['elysia', options.runtime.name]); - options.transport = options.transport || makeFetchTransport; + options.transport = options.transport || (options.runtime.name === 'bun' ? makeFetchTransport : makeNodeTransport); if (options.defaultIntegrations === undefined) { options.defaultIntegrations = getDefaultIntegrations(options); diff --git a/packages/elysia/test/sdk.test.ts b/packages/elysia/test/sdk.test.ts index 142c3f4ff456..2670802b21f3 100644 --- a/packages/elysia/test/sdk.test.ts +++ b/packages/elysia/test/sdk.test.ts @@ -5,6 +5,7 @@ const mockApplySdkMetadata = vi.fn(); const mockInitNode = vi.fn(); const mockGetBunDefaultIntegrations = vi.fn(() => [] as Integration[]); const mockMakeFetchTransport = vi.fn(); +const mockMakeNodeTransport = vi.fn(); vi.mock('@sentry/core', async importActual => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports @@ -19,6 +20,7 @@ vi.mock('@sentry/bun', () => ({ init: mockInitNode, getDefaultIntegrations: mockGetBunDefaultIntegrations, makeFetchTransport: mockMakeFetchTransport, + makeNodeTransport: mockMakeNodeTransport, bunServerIntegration: () => ({ name: 'BunServer', setupOnce: vi.fn() }), })); @@ -53,16 +55,24 @@ describe('init', () => { ); }); - it('uses makeFetchTransport by default', () => { + it('uses makeNodeTransport by default on Node', () => { init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }); expect(mockInitNode).toHaveBeenCalledWith( expect.objectContaining({ - transport: mockMakeFetchTransport, + transport: mockMakeNodeTransport, }), ); }); + it('uses makeFetchTransport by default on Bun', () => { + vi.stubGlobal('Bun', { version: '1.2.3' }); + + init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }); + + expect(mockInitNode).toHaveBeenCalledWith(expect.objectContaining({ transport: mockMakeFetchTransport })); + }); + it('allows overriding transport', () => { const customTransport = vi.fn(); init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0', transport: customTransport }); From 6244a8b3a16ad1aa3c49c3977efc3517d2d0a12d Mon Sep 17 00:00:00 2001 From: Shubham Padkonde Date: Sun, 18 Oct 2026 08:40:28 +0530 Subject: [PATCH 029/118] fix(elysia): Set SDK metadata on the options passed to init (#24521) `init()` copied `userOptions` into `options` and then applied SDK metadata to `userOptions`. When metadata was initially absent, the copied options passed to the underlying SDK did not receive Elysia's SDK name. Apply SDK metadata to the options passed to the underlying SDK. The regression test uses the real metadata helper and checks that `initNode` receives `sentry.javascript.elysia` metadata. Fixes #24045 Fixes JS-3580 --------- Co-authored-by: Claude Opus 5 Co-authored-by: OpenAI Codex Co-authored-by: isaacs --- .../elysia-bun/tests/errors.test.ts | 1 + .../elysia-node/tests/errors.test.ts | 1 + packages/elysia/src/sdk.ts | 2 +- packages/elysia/test/sdk.test.ts | 53 +++++++++++-------- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/elysia-bun/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/elysia-bun/tests/errors.test.ts index 5e587e28fd41..0b3ac95d7cb4 100644 --- a/dev-packages/e2e-tests/test-applications/elysia-bun/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/elysia-bun/tests/errors.test.ts @@ -19,6 +19,7 @@ test('Captures an error thrown in a route handler', async ({ baseURL, request }) }); expect(errorEvent.transaction).toEqual('GET /test-exception/:id'); + expect(errorEvent.sdk?.name).toBe('sentry.javascript.elysia'); expect(errorEvent.contexts?.trace).toEqual( expect.objectContaining({ diff --git a/dev-packages/e2e-tests/test-applications/elysia-node/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/elysia-node/tests/errors.test.ts index 258d7abc5adf..64fc4a132d80 100644 --- a/dev-packages/e2e-tests/test-applications/elysia-node/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/elysia-node/tests/errors.test.ts @@ -19,6 +19,7 @@ test('Captures an error thrown in a route handler', async ({ baseURL, request }) }); expect(errorEvent.transaction).toEqual('GET /test-exception/:id'); + expect(errorEvent.sdk?.name).toBe('sentry.javascript.elysia'); expect(errorEvent.contexts?.trace).toEqual( expect.objectContaining({ diff --git a/packages/elysia/src/sdk.ts b/packages/elysia/src/sdk.ts index be7225026a63..2877338fe03f 100644 --- a/packages/elysia/src/sdk.ts +++ b/packages/elysia/src/sdk.ts @@ -49,7 +49,7 @@ export function init(userOptions: ElysiaOptions = {}): NodeClient | undefined { serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname(), }; - applySdkMetadata(userOptions, 'elysia', ['elysia', options.runtime.name]); + applySdkMetadata(options, 'elysia', ['elysia', options.runtime.name]); options.transport = options.transport || (options.runtime.name === 'bun' ? makeFetchTransport : makeNodeTransport); diff --git a/packages/elysia/test/sdk.test.ts b/packages/elysia/test/sdk.test.ts index 2670802b21f3..afd3cf00e88b 100644 --- a/packages/elysia/test/sdk.test.ts +++ b/packages/elysia/test/sdk.test.ts @@ -1,21 +1,11 @@ import type { Integration } from '@sentry/core'; import { afterEach, describe, expect, it, vi } from 'vitest'; -const mockApplySdkMetadata = vi.fn(); const mockInitNode = vi.fn(); const mockGetBunDefaultIntegrations = vi.fn(() => [] as Integration[]); const mockMakeFetchTransport = vi.fn(); const mockMakeNodeTransport = vi.fn(); -vi.mock('@sentry/core', async importActual => { - // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const actual = await importActual(); - return { - ...actual, - applySdkMetadata: mockApplySdkMetadata, - }; -}); - vi.mock('@sentry/bun', () => ({ init: mockInitNode, getDefaultIntegrations: mockGetBunDefaultIntegrations, @@ -34,13 +24,21 @@ describe('init', () => { vi.unstubAllGlobals(); }); - it('sets SDK metadata to elysia', () => { + it('passes Elysia SDK metadata to initNode', () => { init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }); - expect(mockApplySdkMetadata).toHaveBeenCalledWith( - expect.objectContaining({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }), - 'elysia', - ['elysia', 'node'], + expect(mockInitNode).toHaveBeenCalledWith( + expect.objectContaining({ + _metadata: { + sdk: expect.objectContaining({ + name: 'sentry.javascript.elysia', + packages: [ + expect.objectContaining({ name: 'npm:@sentry/elysia' }), + expect.objectContaining({ name: 'npm:@sentry/node' }), + ], + }), + }, + }), ); }); @@ -114,10 +112,12 @@ describe('init', () => { it('detects runtime correctly', () => { init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }); - const calledOptions = mockInitNode.mock.calls[0]![0]; // In vitest (Node), Bun is not defined, so runtime should be node - expect(calledOptions.runtime.name).toBe('node'); - expect(calledOptions.runtime.version).toBe(process.version); + expect(mockInitNode).toHaveBeenCalledWith( + expect.objectContaining({ + runtime: { name: 'node', version: process.version }, + }), + ); }); it('detects bun runtime when Bun is defined', () => { @@ -125,9 +125,20 @@ describe('init', () => { init({ dsn: 'https://***@o0.ingest.sentry.io/0' }); - const calledOptions = mockInitNode.mock.calls[0]![0]; - expect(calledOptions.runtime).toEqual({ name: 'bun', version: '1.2.3' }); - expect(mockApplySdkMetadata).toHaveBeenCalledWith(expect.anything(), 'elysia', ['elysia', 'bun']); + expect(mockInitNode).toHaveBeenCalledWith( + expect.objectContaining({ + runtime: { name: 'bun', version: '1.2.3' }, + _metadata: { + sdk: expect.objectContaining({ + name: 'sentry.javascript.elysia', + packages: [ + expect.objectContaining({ name: 'npm:@sentry/elysia' }), + expect.objectContaining({ name: 'npm:@sentry/bun' }), + ], + }), + }, + }), + ); }); }); From e1c21bc3450de15f905e337a6468860c69a29989 Mon Sep 17 00:00:00 2001 From: Aditya Reddy <82123567+adityareddy-dev@users.noreply.github.com> Date: Sat, 17 Oct 2026 23:14:03 -0500 Subject: [PATCH 030/118] fix(vercel-edge): expose portable tracing and feature flag helpers (#25377) The Vercel Edge entry point is missing `setConversationId`, `updateSpanName`, `growthbookIntegration` and `instrumentCreateReactAgent`, even though their implementations work without diagnostics channels. Re-export them through the existing runtime-safe entries and add public-entry tests for their behavior. This covers the portable helpers in #24043. Vercel AI and Prisma need diagnostics channels, the Cloudflare HTTP integration is consumed by its request wrapper, and the existing fetch-based Spotlight integration lives in the Cloudflare package rather than a shared entry point, so those remain separate work. Fixes #24043 --- packages/vercel-edge/src/index.ts | 8 ++++ packages/vercel-edge/test/exports.test.ts | 48 +++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 packages/vercel-edge/test/exports.test.ts diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index abe436f67705..d1f92c146338 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -50,6 +50,7 @@ export { setAttribute, setAttributes, setUser, + setConversationId, getSpanStatusFromHttpCode, setHttpStatus, withScope, @@ -92,9 +93,11 @@ export { spanToJSON, spanToTraceHeader, spanToBaggageHeader, + updateSpanName, consoleLoggingIntegration, createConsolaReporter, featureFlagsIntegration, + growthbookIntegration, logger, metrics, withStaticSpan, @@ -114,6 +117,7 @@ export { createLangChainCallbackHandler, instrumentLangChainEmbeddings, instrumentStateGraph, + instrumentCreateReactAgent, } from '@sentry/server-utils/no-diagnostic-channels'; export { VercelEdgeClient } from './client'; @@ -121,3 +125,7 @@ export { getDefaultIntegrations, init } from './sdk'; export { winterCGFetchIntegration } from './integrations/wintercg-fetch'; export type { FetchIntegrationOptions } from '@sentry/core'; + +// vercelAIIntegration and prismaIntegration require diagnostics channels, unavailable on Vercel Edge. +// Cloudflare's httpServerIntegration is consumed by its request wrapper, which Vercel Edge does not use. +// The fetch-based spotlightIntegration lives in the Cloudflare package, not a shared entry point. diff --git a/packages/vercel-edge/test/exports.test.ts b/packages/vercel-edge/test/exports.test.ts new file mode 100644 index 000000000000..e80e485cf9fc --- /dev/null +++ b/packages/vercel-edge/test/exports.test.ts @@ -0,0 +1,48 @@ +import { SentrySpan } from '@sentry/core'; +import { afterEach, describe, expect, it } from 'vitest'; +import { + getIsolationScope, + growthbookIntegration, + instrumentCreateReactAgent, + setConversationId, + updateSpanName, +} from '../src'; + +afterEach(() => { + setConversationId(null); +}); + +describe('portable exports', () => { + it('sets the conversation context through the public entry point', () => { + setConversationId('edge-conversation'); + + expect(getIsolationScope().getScopeData().conversationId).toBe('edge-conversation'); + }); + + it('updates a span name through the public entry point', () => { + const span = new SentrySpan({ name: 'original' }); + updateSpanName(span, 'renamed'); + expect(span.getSpanJSON().name).toBe('renamed'); + span.end(); + }); + + it('creates the GrowthBook integration through the public entry point', () => { + class GrowthBook { + public isOn(): boolean { + return true; + } + public getFeatureValue(): unknown { + return false; + } + } + + expect(growthbookIntegration({ growthbookClass: GrowthBook }).name).toBe('GrowthBook'); + }); + + it('wraps a React agent factory and preserves its result', () => { + const graph = {}; + const createAgent = instrumentCreateReactAgent(() => graph); + + expect(createAgent()).toBe(graph); + }); +}); From a5ed55a5a9818026e3e91887e79cd650e11dcd33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Mon, 19 Oct 2026 08:56:05 +0200 Subject: [PATCH 031/118] chore: Move the monorepo from Yarn 1 to pnpm 12 (#25354) This moves the monorepo from Yarn 1 to pnpm 12.11.2 (`packageManager`): scripts, CI workflows, the release script and the contributor docs. Review it commit by commit: 1. A plain `pnpm import` of `yarn.lock`. Most of the added lines of the PR are here. 2. The dependency changes against that import: the root `workspace:*` entries, `h3` and `unstorage` for `@sentry/nitro` and `@sentry/nuxt`, the `yarn.lock` versions of the npm aliases (`pnpm import` resolves them again), and `pnpm dedupe`. 3. The move from Yarn to pnpm. This commit does not change the lockfile. 4. The bump to pnpm 12: the settings move to `pnpm-workspace.yaml`, the pnpm dev dependency of #25334 is removed, and `pnpm dedupe` with pnpm 12 merges 10 versions (no new versions). Other changes beyond the rename: - Scripts are called with `pnpm run