diff --git a/.craft.yml b/.craft.yml
index f2d6b03894d0..a0e006e275da 100644
--- a/.craft.yml
+++ b/.craft.yml
@@ -16,6 +16,9 @@ targets:
- name: npm
id: '@sentry/node-core'
includeNames: /^sentry-node-core-\d.*\.tgz$/
+ - name: npm
+ id: '@sentry/bundler-plugins'
+ includeNames: /^sentry-bundler-plugins-\d.*\.tgz$/
- name: npm
id: '@sentry-internal/server-utils'
includeNames: /^sentry-internal-server-utils-\d.*\.tgz$/
diff --git a/.github/dependency-review-config.yml b/.github/dependency-review-config.yml
index 8608d2381ace..f427f8274aeb 100644
--- a/.github/dependency-review-config.yml
+++ b/.github/dependency-review-config.yml
@@ -11,3 +11,13 @@ allow-ghsas:
- GHSA-gp8f-8m3g-qvj9
# devalue vulnerability - this is just used by nuxt & astro as transitive dependency
- GHSA-vj54-72f3-p5jv
+ # The bundler-plugin integration test fixtures deliberately pin multiple Vite
+ # major versions (4/6/7/8) to test the plugin against each. These are dev-only
+ # test fixtures that run `vite build` (never the dev server) and are never
+ # shipped, so the following Vite dev-server / transitive advisories do not apply:
+ # launch-editor command injection (via Vite 4 fixture)
+ - GHSA-c27g-q93r-2cwf
+ # Vite arbitrary file read via dev server WebSocket (Vite 6/7/8 fixtures)
+ - GHSA-p9ff-h696-f583
+ # Vite `server.fs.deny` bypassed with queries (Vite 7/8 fixtures)
+ - GHSA-v2wj-q39q-566r
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index dd1ad4dee071..8a3099122f3e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -172,6 +172,9 @@ jobs:
changed_bun_integration:
${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected,
'@sentry-internal/bun-integration-tests') }}
+ changed_bundler_plugin_integration:
+ ${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected,
+ '@sentry-internal/bundler-plugin-integration-tests') }}
changed_browser_integration:
${{ needs.job_get_metadata.outputs.changed_ci == 'true' || contains(steps.checkForAffected.outputs.affected,
'@sentry-internal/browser-integration-tests') }}
@@ -904,6 +907,33 @@ jobs:
working-directory: dev-packages/bun-integration-tests
run: yarn test
+ job_bundler_plugin_integration_tests:
+ name: Bundler Plugin Integration Tests
+ needs: [job_get_metadata, job_build]
+ if: needs.job_build.outputs.changed_bundler_plugin_integration == 'true' || github.event_name != 'pull_request'
+ runs-on: ubuntu-24.04
+ timeout-minutes: 15
+ steps:
+ - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
+ uses: actions/checkout@v6
+ with:
+ ref: ${{ env.HEAD_COMMIT }}
+ - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
+ with:
+ version: 9.15.9
+ - name: Set up Node
+ uses: actions/setup-node@v6
+ with:
+ node-version-file: 'package.json'
+ - name: Restore caches
+ uses: ./.github/actions/restore-cache
+ with:
+ dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }}
+
+ - name: Run integration tests
+ working-directory: dev-packages/bundler-plugin-integration-tests
+ run: yarn test
+
job_build_tarballs:
name: Build tarballs
# We want to run this if:
@@ -1201,6 +1231,7 @@ jobs:
job_node_core_integration_tests,
job_cloudflare_integration_tests,
job_bun_integration_tests,
+ job_bundler_plugin_integration_tests,
job_browser_playwright_tests,
job_browser_loader_tests,
job_e2e_tests,
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
index fb07734e0d6e..2b1062743db8 100644
--- a/.oxfmtrc.json
+++ b/.oxfmtrc.json
@@ -9,7 +9,9 @@
"ignorePatterns": [
"packages/browser/test/loader.js",
"packages/replay-worker/examples/worker.min.js",
+ "packages/bundler-plugins/test/core/fixtures",
"dev-packages/browser-integration-tests/fixtures",
+ "dev-packages/bundler-plugin-integration-tests/fixtures",
"**/test.ts-snapshots/**",
"/.nx/cache",
"/.nx/workspace-data"
diff --git a/.oxlintrc.json b/.oxlintrc.json
index 83ff1674daf4..a40db51a379f 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -38,6 +38,7 @@
"test/manual/**",
"types/**",
"scripts/*.js",
- "node_modules/**"
+ "node_modules/**",
+ "packages/bundler-plugins/test/core/fixtures/**"
]
}
diff --git a/dev-packages/bundler-plugin-integration-tests/.gitignore b/dev-packages/bundler-plugin-integration-tests/.gitignore
new file mode 100644
index 000000000000..0ec71ed6e79a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/.gitignore
@@ -0,0 +1,4 @@
+fixtures/*/out/**
+
+# Regenerated on every run by `pnpm install --force` in setup.mjs
+fixtures/*/pnpm-lock.yaml
diff --git a/dev-packages/bundler-plugin-integration-tests/.oxlintrc.json b/dev-packages/bundler-plugin-integration-tests/.oxlintrc.json
new file mode 100644
index 000000000000..56eb82e78276
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/.oxlintrc.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "../../node_modules/oxlint/configuration_schema.json",
+ "extends": ["../.oxlintrc.json"],
+ "env": {
+ "node": true
+ },
+ "ignorePatterns": ["fixtures/**/src/**", "fixtures/**/out/**"]
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion-promise.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion-promise.config.d.ts
new file mode 100644
index 000000000000..660a4f9ef18d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion-promise.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare function getSentryConfig(outDir: string): SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion-promise.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion-promise.config.js
new file mode 100644
index 000000000000..a165efa18482
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion-promise.config.js
@@ -0,0 +1,17 @@
+// Config that uses a Promise for filesToDeleteAfterUpload
+// This tests that the plugin can handle async file deletion patterns
+export function getSentryConfig(outDir) {
+ const fileDeletionPromise = new Promise((resolve) => {
+ setTimeout(() => {
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ resolve([`${outDir}/basic.js.map`]);
+ }, 100);
+ });
+
+ return {
+ telemetry: false,
+ sourcemaps: {
+ filesToDeleteAfterUpload: fileDeletionPromise,
+ },
+ };
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion.config.js
new file mode 100644
index 000000000000..ae8efaa2113b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/after-upload-deletion.config.js
@@ -0,0 +1,6 @@
+export const sentryConfig = {
+ telemetry: false,
+ sourcemaps: {
+ filesToDeleteAfterUpload: ["out/after-upload-deletion/basic.js.map"],
+ },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/application-key.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/application-key.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/application-key.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/application-key.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/application-key.config.js
new file mode 100644
index 000000000000..26041ce4c6b5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/application-key.config.js
@@ -0,0 +1,4 @@
+export const sentryConfig = {
+ telemetry: false,
+ applicationKey: "1234567890abcdef",
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-release-disabled.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-release-disabled.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-release-disabled.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-release-disabled.config.js
new file mode 100644
index 000000000000..9d6c407e73ea
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-release-disabled.config.js
@@ -0,0 +1,4 @@
+export const sentryConfig = {
+ telemetry: false,
+ release: { create: false, inject: false },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-sourcemaps.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-sourcemaps.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-sourcemaps.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-sourcemaps.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-sourcemaps.config.js
new file mode 100644
index 000000000000..d96d240d0612
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic-sourcemaps.config.js
@@ -0,0 +1,6 @@
+export const sentryConfig = {
+ telemetry: false,
+ authToken: "fake-auth",
+ org: "fake-org",
+ project: "fake-project",
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic.config.cjs b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic.config.cjs
new file mode 100644
index 000000000000..3e4aac9eb97d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic.config.cjs
@@ -0,0 +1,6 @@
+exports.sentryConfig = {
+ telemetry: false,
+ authToken: "fake-auth",
+ org: "fake-org",
+ project: "fake-project",
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic.config.js
new file mode 100644
index 000000000000..d96d240d0612
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/basic.config.js
@@ -0,0 +1,6 @@
+export const sentryConfig = {
+ telemetry: false,
+ authToken: "fake-auth",
+ org: "fake-org",
+ project: "fake-project",
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/build-info.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/build-info.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/build-info.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/build-info.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/build-info.config.js
new file mode 100644
index 000000000000..5eaff477b3b4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/build-info.config.js
@@ -0,0 +1,7 @@
+export const sentryConfig = {
+ telemetry: false,
+ release: {
+ name: "build-information-injection-test",
+ },
+ _experiments: { injectBuildInformation: true },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/bundle-size-optimizations.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/bundle-size-optimizations.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/bundle-size-optimizations.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/bundle-size-optimizations.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/bundle-size-optimizations.config.js
new file mode 100644
index 000000000000..6e7f6b72ed29
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/bundle-size-optimizations.config.js
@@ -0,0 +1,11 @@
+export const sentryConfig = {
+ telemetry: false,
+ bundleSizeOptimizations: {
+ excludeDebugStatements: true,
+ excludeTracing: true,
+ excludeReplayCanvas: true,
+ excludeReplayIframe: true,
+ excludeReplayShadowDom: true,
+ excludeReplayWorker: true,
+ },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-disabled.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-disabled.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-disabled.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-disabled.config.js
new file mode 100644
index 000000000000..8891d92b0722
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-disabled.config.js
@@ -0,0 +1,3 @@
+export const sentryConfig = {
+ telemetry: false,
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-next.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-next.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-next.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-next.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-next.config.js
new file mode 100644
index 000000000000..6ef1a7fe6bdb
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation-next.config.js
@@ -0,0 +1,4 @@
+export const sentryConfig = {
+ telemetry: false,
+ reactComponentAnnotation: { enabled: true, _experimentalInjectIntoHtml: true },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation.config.js
new file mode 100644
index 000000000000..484acf9dae69
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/component-annotation.config.js
@@ -0,0 +1,4 @@
+export const sentryConfig = {
+ telemetry: false,
+ reactComponentAnnotation: { enabled: true },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugid-disabled.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugid-disabled.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugid-disabled.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugid-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugid-disabled.config.js
new file mode 100644
index 000000000000..471befe91aba
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugid-disabled.config.js
@@ -0,0 +1,9 @@
+export const sentryConfig = {
+ telemetry: false,
+ sourcemaps: {
+ disable: true,
+ },
+ release: {
+ inject: false,
+ },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugids-already-injected.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugids-already-injected.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugids-already-injected.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugids-already-injected.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugids-already-injected.config.js
new file mode 100644
index 000000000000..e9dc3f978644
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/debugids-already-injected.config.js
@@ -0,0 +1,7 @@
+export const sentryConfig = {
+ telemetry: false,
+ // We need to specify these so that upload is attempted. Debug IDs will be injected before then...
+ authToken: "fake-auth",
+ org: "fake-org",
+ project: "fake-project",
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/dont-mess-up-user-code.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/dont-mess-up-user-code.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/dont-mess-up-user-code.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/dont-mess-up-user-code.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/dont-mess-up-user-code.config.js
new file mode 100644
index 000000000000..615bdc33b044
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/dont-mess-up-user-code.config.js
@@ -0,0 +1,4 @@
+export const sentryConfig = {
+ telemetry: false,
+ release: { name: "I am release!", create: false },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/errorhandling.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/errorhandling.config.d.ts
new file mode 100644
index 000000000000..3f0fa420f859
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/errorhandling.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare function getErrorHandlingConfig(port: string): SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/errorhandling.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/errorhandling.config.js
new file mode 100644
index 000000000000..d5b3f5648d07
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/errorhandling.config.js
@@ -0,0 +1,13 @@
+export function getErrorHandlingConfig(port) {
+ return {
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ url: `http://localhost:${port}`,
+ authToken: "fake-auth",
+ org: "fake-org",
+ project: "fake-project",
+ release: {
+ name: "1.0.0",
+ },
+ debug: true,
+ };
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/module-metadata.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/module-metadata.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/module-metadata.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/module-metadata.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/module-metadata.config.js
new file mode 100644
index 000000000000..e0a7cb1f50bf
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/module-metadata.config.js
@@ -0,0 +1,4 @@
+export const sentryConfig = {
+ telemetry: false,
+ moduleMetadata: { something: "value", another: 999 },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/multiple-entry-points.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/multiple-entry-points.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/multiple-entry-points.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/multiple-entry-points.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/multiple-entry-points.config.js
new file mode 100644
index 000000000000..9e60b27eec9e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/multiple-entry-points.config.js
@@ -0,0 +1,4 @@
+export const sentryConfig = {
+ telemetry: false,
+ release: { inject: false },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/package.json
new file mode 100644
index 000000000000..3dbc1ca591c0
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/package.json
@@ -0,0 +1,3 @@
+{
+ "type": "module"
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/query-param.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/query-param.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/query-param.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/query-param.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/query-param.config.js
new file mode 100644
index 000000000000..8891d92b0722
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/query-param.config.js
@@ -0,0 +1,3 @@
+export const sentryConfig = {
+ telemetry: false,
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-disabled.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-disabled.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-disabled.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-disabled.config.js
new file mode 100644
index 000000000000..b0c5a5fb9113
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-disabled.config.js
@@ -0,0 +1,7 @@
+export const sentryConfig = {
+ telemetry: false,
+ authToken: "fake-auth",
+ org: "fake-org",
+ project: "fake-project",
+ release: { create: false },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-value-with-quotes.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-value-with-quotes.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-value-with-quotes.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-value-with-quotes.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-value-with-quotes.config.js
new file mode 100644
index 000000000000..95fd62fd0ae2
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/release-value-with-quotes.config.js
@@ -0,0 +1,6 @@
+export const sentryConfig = {
+ telemetry: false,
+ release: {
+ name: 'i am a dangerous release value because I contain a "',
+ },
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/telemetry.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/telemetry.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/telemetry.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/telemetry.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/telemetry.config.js
new file mode 100644
index 000000000000..789aad8b25d4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/telemetry.config.js
@@ -0,0 +1 @@
+export const sentryConfig = {};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/vite-mpa-extra-modules.config.d.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/vite-mpa-extra-modules.config.d.ts
new file mode 100644
index 000000000000..192b614bc29b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/vite-mpa-extra-modules.config.d.ts
@@ -0,0 +1,2 @@
+import type { SentryRollupPluginOptions } from "@sentry/bundler-plugins/rollup";
+export declare const sentryConfig: SentryRollupPluginOptions;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/configs/vite-mpa-extra-modules.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/vite-mpa-extra-modules.config.js
new file mode 100644
index 000000000000..8891d92b0722
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/configs/vite-mpa-extra-modules.config.js
@@ -0,0 +1,3 @@
+export const sentryConfig = {
+ telemetry: false,
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion-promise.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion-promise.config.js
new file mode 100644
index 000000000000..a4a73199f57a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion-promise.config.js
@@ -0,0 +1,15 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { getSentryConfig } from "../configs/after-upload-deletion-promise.config.js";
+
+const outDir = "./out/after-upload-deletion-promise";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: `${outDir}/basic.js`,
+ minify: false,
+ format: "iife",
+ sourcemap: true,
+ plugins: [sentryEsbuildPlugin(getSentryConfig(outDir))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion-promise.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion-promise.test.ts
new file mode 100644
index 000000000000..92c6bd553f5a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion-promise.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+
+test(import.meta.url, ({ runBundler, outDir, runFileInNode }) => {
+ runBundler();
+
+ // Verify the JS file exists and works
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+
+ // Verify the sourcemap was deleted (by the Promise)
+ const sourcemapPath = join(outDir, "basic.js.map");
+ expect(existsSync(sourcemapPath)).toBe(false);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion.config.js
new file mode 100644
index 000000000000..3dfb233b5061
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion.config.js
@@ -0,0 +1,13 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/after-upload-deletion.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/after-upload-deletion/basic.js",
+ minify: false,
+ format: "iife",
+ sourcemap: true,
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion.test.ts
new file mode 100644
index 000000000000..3fd6de385093
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/after-upload-deletion.test.ts
@@ -0,0 +1,41 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ //# sourceMappingURL=basic.js.map
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/application-key.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/application-key.config.js
new file mode 100644
index 000000000000..cb45f1f75d58
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/application-key.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/application-key.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/application-key/application-key.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/application-key.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/application-key.test.ts
new file mode 100644
index 000000000000..b9c0ae9182b5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/application-key.test.ts
@@ -0,0 +1,47 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "application-key.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = (function(e2) {
+ for (var n = 1; n < arguments.length; n++) {
+ var a = arguments[n];
+ if (null != a) for (var t in a) a.hasOwnProperty(t) && (e2[t] = a[t]);
+ }
+ return e2;
+ })({}, e._sentryModuleMetadata[new e.Error().stack], { "_sentryBundlerPluginAppKey:1234567890abcdef": true });
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ ",
+ }
+ `);
+
+ const output = runFileInNode("application-key.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-cjs.config.cjs b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-cjs.config.cjs
new file mode 100644
index 000000000000..388b1df7aee9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-cjs.config.cjs
@@ -0,0 +1,16 @@
+const esbuild = require("esbuild");
+const { sentryEsbuildPlugin } = require("@sentry/bundler-plugins/esbuild");
+const { sentryConfig } = require("../configs/basic.config.cjs");
+
+// no top-level await, so the build promise is intentionally floating.
+// on failure, node will crash the same as if we did await it, which is
+// the intended behavior, and the build will keep the event loop open
+// while it runs.
+void esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/basic-cjs/basic.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-cjs.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-cjs.test.ts
new file mode 100644
index 000000000000..cc4449e10efe
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-cjs.test.ts
@@ -0,0 +1,45 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-release-disabled.config.js
new file mode 100644
index 000000000000..29feddddabcf
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-release-disabled.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/basic-release-disabled.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/basic-release-disabled/basic-release-disabled.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-release-disabled.test.ts
new file mode 100644
index 000000000000..0668bd0305a0
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-release-disabled.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic-release-disabled.js": "(() => {
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic-release-disabled.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-sourcemaps.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-sourcemaps.config.js
new file mode 100644
index 000000000000..ac762c808061
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-sourcemaps.config.js
@@ -0,0 +1,13 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/basic-sourcemaps.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/basic-sourcemaps/basic-sourcemaps.js",
+ minify: false,
+ format: "iife",
+ sourcemap: true,
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-sourcemaps.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-sourcemaps.test.ts
new file mode 100644
index 000000000000..23695e5ccdce
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic-sourcemaps.test.ts
@@ -0,0 +1,47 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic-sourcemaps.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ //# sourceMappingURL=basic-sourcemaps.js.map
+ ",
+ "basic-sourcemaps.js.map": "{"version":3,"sources":["../../_sentry-injection-stub","sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000","../../src/basic.js","../../src/basic.js"],"sourcesContent":["!function(){try{var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e.SENTRY_RELEASE={id:\\"CURRENT_SHA\\"};}catch(e){}}();","!function(){try{var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"00000000-0000-0000-0000-000000000000\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-00000000-0000-0000-0000-000000000000\\");}catch(e){}}();","// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n","\\n import \\"_sentry-debug-id-injection-stub\\";\\n import * as OriginalModule from \\"./src/basic.js\\";\\n export default OriginalModule.default;\\n export * from \\"./src/basic.js\\";"],"mappings":";;AAAA,IAAC,WAAU;AAAC,QAAG;AAAC,UAAI,IAAE,eAAa,OAAO,SAAO,SAAO,eAAa,OAAO,SAAO,SAAO,eAAa,OAAO,aAAW,aAAW,eAAa,OAAO,OAAK,OAAK,CAAC;AAAE,QAAE,iBAAe,EAAC,IAAG,2CAA0C;AAAA,IAAE,SAAOA,IAAE;AAAA,IAAC;AAAA,EAAC,GAAE;;;ACAnP,IAAC,WAAU;AAAC,QAAG;AAAC,UAAI,IAAE,eAAa,OAAO,SAAO,SAAO,eAAa,OAAO,SAAO,SAAO,eAAa,OAAO,aAAW,aAAW,eAAa,OAAO,OAAK,OAAK,CAAC;AAAE,UAAI,IAAG,IAAI,EAAE,QAAO;AAAM,YAAI,EAAE,kBAAgB,EAAE,mBAAiB,CAAC,GAAE,EAAE,gBAAgB,CAAC,IAAE,wCAAuC,EAAE,2BAAyB;AAAA,IAAoD,SAAOC,IAAE;AAAA,IAAC;AAAA,EAAC,GAAE;;;ACCnY,UAAQ,IAAI,aAAa;;;ACEX,MAAO,gBAAuB;","names":["e","e"]}",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic-sourcemaps.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic.config.js
new file mode 100644
index 000000000000..fc1f40c87f2d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/basic.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/basic/basic.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic.test.ts
new file mode 100644
index 000000000000..cc4449e10efe
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/basic.test.ts
@@ -0,0 +1,45 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/build-info.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/build-info.config.js
new file mode 100644
index 000000000000..b3b36f12acdd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/build-info.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/build-info.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/build-info/build-info.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/build-info.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/build-info.test.ts
new file mode 100644
index 000000000000..2b62c1e20628
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/build-info.test.ts
@@ -0,0 +1,41 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "build-info.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "build-information-injection-test" };
+ e.SENTRY_BUILD_INFO = { "deps": ["@sentry/bundler-plugins", "esbuild"], "depsVersions": {}, "nodeVersion":"NODE_VERSION" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ ",
+ }
+ `);
+
+ const output = runFileInNode("build-info.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/bundle-size-optimizations.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/bundle-size-optimizations.config.js
new file mode 100644
index 000000000000..5c1eb0957175
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/bundle-size-optimizations.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/bundle-size-optimizations.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/bundle.js"],
+ bundle: true,
+ outfile: "./out/bundle-size-optimizations/bundle-size-optimizations.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/bundle-size-optimizations.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/bundle-size-optimizations.test.ts
new file mode 100644
index 000000000000..2f472a5a68af
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/bundle-size-optimizations.test.ts
@@ -0,0 +1,51 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "bundle-size-optimizations.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/bundle.js
+ console.log(
+ JSON.stringify({
+ debug: false ? "a" : "b",
+ trace: false ? "a" : "b",
+ replayCanvas: true ? "a" : "b",
+ replayIframe: true ? "a" : "b",
+ replayShadowDom: true ? "a" : "b",
+ replayWorker: true ? "a" : "b"
+ })
+ );
+
+ // src/bundle.js?sentryDebugIdProxy=true
+ var bundle_default = void 0;
+ })();
+ ",
+ }
+ `);
+
+ const output = runFileInNode("bundle-size-optimizations.js");
+ expect(output).toBe(
+ '{"debug":"b","trace":"b","replayCanvas":"a","replayIframe":"a","replayShadowDom":"a","replayWorker":"a"}\n'
+ );
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/debugid-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/debugid-disabled.config.js
new file mode 100644
index 000000000000..4290671d11ec
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/debugid-disabled.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/debugid-disabled.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/debugid-disabled/debugid-disabled.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/debugid-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/debugid-disabled.test.ts
new file mode 100644
index 000000000000..46615320b23a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/debugid-disabled.test.ts
@@ -0,0 +1,19 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "debugid-disabled.js": ""use strict";
+ (() => {
+ // src/basic.js
+ console.log("hello world");
+ })();
+ ",
+ }
+ `);
+
+ const output = runFileInNode("debugid-disabled.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/dont-mess-up-user-code.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/dont-mess-up-user-code.config.js
new file mode 100644
index 000000000000..556a108bd033
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/dont-mess-up-user-code.config.js
@@ -0,0 +1,13 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/dont-mess-up-user-code.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/index.js"],
+ bundle: true,
+ outfile: "./out/dont-mess-up-user-code/index.js",
+ minify: false,
+ format: "iife",
+ sourcemap: true,
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/dont-mess-up-user-code.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/dont-mess-up-user-code.test.ts
new file mode 100644
index 000000000000..571aad51c5a7
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/dont-mess-up-user-code.test.ts
@@ -0,0 +1,46 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "I am release!" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/import.js
+ console.log("I am import!");
+
+ // src/index.js
+ console.log("I am index!");
+
+ // src/index.js?sentryDebugIdProxy=true
+ var index_default = void 0;
+ })();
+ //# sourceMappingURL=index.js.map
+ ",
+ "index.js.map": "{"version":3,"sources":["../../_sentry-injection-stub","sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000","../../src/import.js","../../src/index.js","../../src/index.js"],"sourcesContent":["!function(){try{var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};e.SENTRY_RELEASE={id:\\"I am release!\\"};}catch(e){}}();","!function(){try{var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"00000000-0000-0000-0000-000000000000\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-00000000-0000-0000-0000-000000000000\\");}catch(e){}}();","// eslint-disable-next-line no-console\\nconsole.log(\\"I am import!\\");\\n\\nexport {};\\n","import \\"./import\\";\\n\\n// eslint-disable-next-line no-console\\nconsole.log(\\"I am index!\\");\\n","\\n import \\"_sentry-debug-id-injection-stub\\";\\n import * as OriginalModule from \\"./src/index.js\\";\\n export default OriginalModule.default;\\n export * from \\"./src/index.js\\";"],"mappings":";;AAAA,IAAC,WAAU;AAAC,QAAG;AAAC,UAAI,IAAE,eAAa,OAAO,SAAO,SAAO,eAAa,OAAO,SAAO,SAAO,eAAa,OAAO,aAAW,aAAW,eAAa,OAAO,OAAK,OAAK,CAAC;AAAE,QAAE,iBAAe,EAAC,IAAG,gBAAe;AAAA,IAAE,SAAOA,IAAE;AAAA,IAAC;AAAA,EAAC,GAAE;;;ACAxN,IAAC,WAAU;AAAC,QAAG;AAAC,UAAI,IAAE,eAAa,OAAO,SAAO,SAAO,eAAa,OAAO,SAAO,SAAO,eAAa,OAAO,aAAW,aAAW,eAAa,OAAO,OAAK,OAAK,CAAC;AAAE,UAAI,IAAG,IAAI,EAAE,QAAO;AAAM,YAAI,EAAE,kBAAgB,EAAE,mBAAiB,CAAC,GAAE,EAAE,gBAAgB,CAAC,IAAE,wCAAuC,EAAE,2BAAyB;AAAA,IAAoD,SAAOC,IAAE;AAAA,IAAC;AAAA,EAAC,GAAE;;;ACCnY,UAAQ,IAAI,cAAc;;;ACE1B,UAAQ,IAAI,aAAa;;;ACAX,MAAO,gBAAuB;","names":["e","e"]}",
+ }
+ `);
+
+ const output = runFileInNode("index.js");
+ expect(output).toContain("I am import!");
+ expect(output).toContain("I am index!");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/errorhandling.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/errorhandling.config.js
new file mode 100644
index 000000000000..8fba7a9ccb7f
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/errorhandling.config.js
@@ -0,0 +1,15 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { getErrorHandlingConfig } from "../configs/errorhandling.config.js";
+
+const FAKE_SENTRY_PORT = process.env.FAKE_SENTRY_PORT || "9876";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/errorhandling/basic.js",
+ minify: false,
+ format: "cjs",
+ sourcemap: true,
+ plugins: [sentryEsbuildPlugin(getErrorHandlingConfig(FAKE_SENTRY_PORT))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/errorhandling.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/errorhandling.test.ts
new file mode 100644
index 000000000000..f2672a372553
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/errorhandling.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { withFakeSentryServer } from "../utils";
+
+test(import.meta.url, async ({ runBundler }) => {
+ await withFakeSentryServer((port) => {
+ // Run bundler with fake server - should succeed despite server errors
+ runBundler({
+ FAKE_SENTRY_PORT: port,
+ SENTRY_HTTP_MAX_RETRIES: "1", // Only retry once to avoid timeout
+ });
+
+ // If we get here, the build succeeded (didn't throw)
+ expect(true).toBe(true);
+ });
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/esbuild-inject-compat.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/esbuild-inject-compat.config.js
new file mode 100644
index 000000000000..152a6ff70b4c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/esbuild-inject-compat.config.js
@@ -0,0 +1,17 @@
+import * as esbuild from "esbuild";
+import * as path from "path";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+
+await esbuild.build({
+ entryPoints: ["./src/inject-compat-index.ts"],
+ bundle: true,
+ outfile: "./out/esbuild-inject-compat/index.js",
+ inject: [path.resolve("./src/inject.ts")],
+ minify: false,
+ format: "iife",
+ plugins: [
+ sentryEsbuildPlugin({
+ telemetry: false,
+ }),
+ ],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/esbuild-inject-compat.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/esbuild-inject-compat.test.ts
new file mode 100644
index 000000000000..dae1cb7b9289
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/esbuild-inject-compat.test.ts
@@ -0,0 +1,51 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ readOutputFiles, runBundler, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js": "(() => {
+ // src/inject.ts
+ var process = {
+ env: {
+ FOO: "some-injected-value"
+ }
+ };
+ var global2 = globalThis;
+
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global2 ? global2 : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/inject-compat-index.ts
+ console.log(process.env["FOO"]);
+
+ // src/inject-compat-index.ts?sentryDebugIdProxy=true
+ var inject_compat_index_default = void 0;
+ })();
+ ",
+ }
+ `);
+
+ const output = runFileInNode("index.js");
+ expect(output).toMatchInlineSnapshot(`
+ "some-injected-value
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/module-metadata.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/module-metadata.config.js
new file mode 100644
index 000000000000..d7b89f95f0d8
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/module-metadata.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/module-metadata.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/module-metadata/module-metadata.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/module-metadata.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/module-metadata.test.ts
new file mode 100644
index 000000000000..a09d56d23521
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/module-metadata.test.ts
@@ -0,0 +1,47 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "module-metadata.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = (function(e2) {
+ for (var n = 1; n < arguments.length; n++) {
+ var a = arguments[n];
+ if (null != a) for (var t in a) a.hasOwnProperty(t) && (e2[t] = a[t]);
+ }
+ return e2;
+ })({}, e._sentryModuleMetadata[new e.Error().stack], { "something": "value", "another": 999 });
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ ",
+ }
+ `);
+
+ const output = runFileInNode("module-metadata.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/multiple-entry-points.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/multiple-entry-points.config.js
new file mode 100644
index 000000000000..feea71f341ea
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/multiple-entry-points.config.js
@@ -0,0 +1,14 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/multiple-entry-points.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/entry1.js", "./src/entry2.js"],
+ bundle: true,
+ outdir: "./out/multiple-entry-points",
+ minify: false,
+ format: "esm",
+ splitting: true,
+ chunkNames: "[name]",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/multiple-entry-points.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/multiple-entry-points.test.ts
new file mode 100644
index 000000000000..6a73daad7b2e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/multiple-entry-points.test.ts
@@ -0,0 +1,76 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "chunk.js": "// src/common.js
+ function add(a, b) {
+ return a + b;
+ }
+
+ export {
+ add
+ };
+ ",
+ "entry1.js": "import {
+ add
+ } from "./chunk.js";
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/entry1.js
+ console.log(add(1, 2));
+
+ // src/entry1.js?sentryDebugIdProxy=true
+ var entry1_default = void 0;
+ export {
+ entry1_default as default
+ };
+ ",
+ "entry2.js": "import {
+ add
+ } from "./chunk.js";
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/entry2.js
+ console.log(add(2, 4));
+
+ // src/entry2.js?sentryDebugIdProxy=true
+ var entry2_default = void 0;
+ export {
+ entry2_default as default
+ };
+ ",
+ }
+ `);
+
+ const output1 = runFileInNode("entry1.js");
+ expect(output1).toMatchInlineSnapshot(`
+ "3
+ "
+ `);
+ const output2 = runFileInNode("entry2.js");
+ expect(output2).toMatchInlineSnapshot(`
+ "6
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json
new file mode 100644
index 000000000000..b88bd2c6ed33
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "esbuild-integration-tests",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0",
+ "esbuild": "0.28.0"
+ },
+ "pnpm": {
+ "overrides": {
+ "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins.tgz"
+ },
+ "patchedDependencies": {
+ "@sentry/cli": "../patches/@sentry__cli.patch"
+ }
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-disabled.config.js
new file mode 100644
index 000000000000..97cb727a6b99
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-disabled.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/release-disabled.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/release-disabled/release-disabled.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-disabled.test.ts
new file mode 100644
index 000000000000..214529d8fe34
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-disabled.test.ts
@@ -0,0 +1,44 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "release-disabled.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ ",
+ "sentry-cli-mock.json": "["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("release-disabled.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-value-with-quotes.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-value-with-quotes.config.js
new file mode 100644
index 000000000000..89265d03c695
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-value-with-quotes.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/release-value-with-quotes.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/release-value-with-quotes.js"],
+ bundle: true,
+ outfile: "./out/release-value-with-quotes/bundle.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-value-with-quotes.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-value-with-quotes.test.ts
new file mode 100644
index 000000000000..314a41b865f6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/release-value-with-quotes.test.ts
@@ -0,0 +1,8 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, runFileInNode }) => {
+ runBundler();
+ const output = runFileInNode("bundle.js");
+ expect(output.trimEnd()).toBe('"i am a dangerous release value because I contain a \\""');
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/app.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/app.jsx
new file mode 100644
index 000000000000..614d38c834aa
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/app.jsx
@@ -0,0 +1,9 @@
+import { ComponentA } from "./component-a";
+
+export default function App() {
+ return (
+
+ ;
+
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/basic.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/basic.js
new file mode 100644
index 000000000000..7ef02afbd244
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/basic.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-console
+console.log("hello world");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/bundle.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/bundle.js
new file mode 100644
index 000000000000..0d62e559347d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/bundle.js
@@ -0,0 +1,10 @@
+console.log(
+ JSON.stringify({
+ debug: __SENTRY_DEBUG__ ? "a" : "b",
+ trace: __SENTRY_TRACING__ ? "a" : "b",
+ replayCanvas: __RRWEB_EXCLUDE_CANVAS__ ? "a" : "b",
+ replayIframe: __RRWEB_EXCLUDE_IFRAME__ ? "a" : "b",
+ replayShadowDom: __RRWEB_EXCLUDE_SHADOW_DOM__ ? "a" : "b",
+ replayWorker: __SENTRY_EXCLUDE_REPLAY_WORKER__ ? "a" : "b",
+ })
+);
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/common.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/common.js
new file mode 100644
index 000000000000..7d658310b0d9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/common.js
@@ -0,0 +1,3 @@
+export function add(a, b) {
+ return a + b;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/component-a.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/component-a.jsx
new file mode 100644
index 000000000000..5d57ab2215cd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/component-a.jsx
@@ -0,0 +1,3 @@
+export function ComponentA() {
+ return Component A;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/entry1.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/entry1.js
new file mode 100644
index 000000000000..480816663453
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/entry1.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(1, 2));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/entry2.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/entry2.js
new file mode 100644
index 000000000000..f64af1ea7ae5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/entry2.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(2, 4));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/import.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/import.js
new file mode 100644
index 000000000000..733d5d48f137
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/import.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line no-console
+console.log("I am import!");
+
+export {};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/index.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/index.js
new file mode 100644
index 000000000000..719d8428cac6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/index.js
@@ -0,0 +1,4 @@
+import "./import";
+
+// eslint-disable-next-line no-console
+console.log("I am index!");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/inject-compat-index.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/inject-compat-index.ts
new file mode 100644
index 000000000000..25401bb5c6e1
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/inject-compat-index.ts
@@ -0,0 +1,4 @@
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore just a test file
+// eslint-disable-next-line no-console
+console.log(process.env["FOO"]);
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/inject.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/inject.ts
new file mode 100644
index 000000000000..089b32ff1122
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/inject.ts
@@ -0,0 +1,7 @@
+export const process = {
+ env: {
+ FOO: "some-injected-value",
+ },
+};
+// eslint-disable-next-line no-undef
+export const global = globalThis;
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/release-value-with-quotes.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/release-value-with-quotes.js
new file mode 100644
index 000000000000..aa73bfa8be00
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/src/release-value-with-quotes.js
@@ -0,0 +1,3 @@
+// Simply output the metadata to the console so it can be checked in a test
+// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-member-access
+console.log(JSON.stringify(global.SENTRY_RELEASE.id));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/telemetry.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/telemetry.config.js
new file mode 100644
index 000000000000..367b33acaac7
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/telemetry.config.js
@@ -0,0 +1,12 @@
+import * as esbuild from "esbuild";
+import { sentryEsbuildPlugin } from "@sentry/bundler-plugins/esbuild";
+import { sentryConfig } from "../configs/telemetry.config.js";
+
+await esbuild.build({
+ entryPoints: ["./src/basic.js"],
+ bundle: true,
+ outfile: "./out/telemetry/telemetry.js",
+ minify: false,
+ format: "iife",
+ plugins: [sentryEsbuildPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/telemetry.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/telemetry.test.ts
new file mode 100644
index 000000000000..d70b593257da
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/telemetry.test.ts
@@ -0,0 +1,44 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "sentry-telemetry.json": "[{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":true,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"ok","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ [{"event_id":"UUID","sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"},"trace":{"environment":"production","release":"PLUGIN_VERSION","public_key":"UUID","trace_id":"UUID","org_id":"1","transaction":"Sentry Bundler Plugin execution","sampled":"true","sample_rand":"SAMPLE_RAND","sample_rate":"1"}},[[{"type":"transaction"},{"contexts":{"trace":{"span_id":"SHORT_UUID","trace_id":"UUID","data":{"sentry.origin":"manual","sentry.source":"custom","sentry.sample_rate":1},"origin":"manual"},"runtime":{"name":"node","version":"NODE_VERSION"}},"spans":[],"start_timestamp":START_TIMESTAMP,"timestamp":TIMESTAMP,"transaction":"Sentry Bundler Plugin execution","type":"transaction","transaction_info":{"source":"custom"},"platform":"PLATFORM","event_id":"UUID","environment":"production","release":"PLUGIN_VERSION","tags":{"upload-legacy-sourcemaps":false,"module-metadata":false,"inject-build-information":false,"set-commits":"auto","finalize-release":true,"deploy-options":false,"custom-error-handler":false,"sourcemaps-assets":false,"delete-after-upload":false,"sourcemaps-disabled":false,"react-annotate":false,"node":"NODE_VERSION","platform":"PLATFORM","meta-framework":"none","application-key-set":false,"ci":true,"project":"undefined","bundler":"esbuild","bundler-major-version":"28"},"user":{},"sdk":{"name":"sentry.javascript.node","version":"10.56.0","integrations":[],"packages":[{"name":"npm:@sentry/node","version":"10.56.0"}]}}]]],
+ [{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":false,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"exited","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ ",
+ "telemetry.js": "(() => {
+ // _sentry-injection-stub
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ } catch (e2) {
+ }
+ })();
+
+ // sentry-debug-id-stub:_sentry-debug-id-injection-stub?sentry-module-id=00000000-0000-0000-0000-000000000000
+ !(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+
+ // src/basic.js
+ console.log("hello world");
+
+ // src/basic.js?sentryDebugIdProxy=true
+ var basic_default = void 0;
+ })();
+ ",
+ }
+ `);
+
+ const output = runFileInNode("telemetry.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/utils.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/utils.ts
new file mode 100644
index 000000000000..d083a33ed570
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/utils.ts
@@ -0,0 +1,68 @@
+import { basename, dirname, join } from "node:path";
+import { createTempDir, readAllFiles, runBundler } from "../utils";
+import { fileURLToPath } from "node:url";
+import { rmSync } from "node:fs";
+import type { TestContext } from "vitest";
+import { test as vitestTest } from "vitest";
+import { execSync } from "node:child_process";
+
+const cwd = dirname(fileURLToPath(import.meta.url));
+
+type TestCallback = (props: {
+ outDir: string;
+ runBundler: (env?: Record) => void;
+ readOutputFiles: () => Record;
+ runFileInNode: (file: string) => string;
+ createTempDir: () => string;
+ ctx: TestContext;
+}) => void | Promise;
+
+function esbuildReplacer(content: string): string {
+ // esbuild ends up with different debug IDs and UUIDs on different platforms
+ // so we replace them with placeholders to make snapshots deterministic
+ return content.replace(
+ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g,
+ "00000000-0000-0000-0000-000000000000"
+ );
+}
+
+export function test(url: string, callback: TestCallback) {
+ const filePath = fileURLToPath(url);
+ const filename = basename(filePath);
+ const testName = filename.replace(/\.test\.ts$/, "");
+ const outDir = join(cwd, "out", testName);
+
+ // Clear the output directory before running the test
+ rmSync(outDir, { recursive: true, force: true });
+
+ // Detect CJS config files by test name suffix
+ const configExt = testName.endsWith("-cjs") ? ".config.cjs" : ".config.js";
+
+ vitestTest(`esbuild > ${testName}`, (ctx) =>
+ callback({
+ outDir,
+ runBundler: (env) =>
+ runBundler(
+ `node ${testName}${configExt}`,
+ {
+ cwd,
+ env: {
+ ...process.env,
+ ...env,
+ },
+ },
+ outDir
+ ),
+ readOutputFiles: () => readAllFiles(outDir, esbuildReplacer),
+ runFileInNode: (file) => {
+ const fullPath = join(outDir, file);
+ return execSync(`node ${fullPath}`, {
+ cwd,
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
+ }).toString();
+ },
+ createTempDir: () => createTempDir(),
+ ctx,
+ })
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/patches/@sentry__cli.patch b/dev-packages/bundler-plugin-integration-tests/fixtures/patches/@sentry__cli.patch
new file mode 100644
index 000000000000..d57a008a264c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/patches/@sentry__cli.patch
@@ -0,0 +1,18 @@
+diff --git a/js/helper.js b/js/helper.js
+index 56f95c9784d32a48e98b8341bfa336f0ed61a6e7..b97e41b4dcf96b9dcd9e171b91b575898b6cfbc3 100644
+--- a/js/helper.js
++++ b/js/helper.js
+@@ -295,6 +295,13 @@ function execute(args_1, live_1, silent_1, configFile_1) {
+ if (config.vcsRemote) {
+ env.SENTRY_VCS_REMOTE = config.vcsRemote;
+ }
++
++ if (process.env['SENTRY_TEST_OUT_DIR']) {
++ const out = path.join(process.env['SENTRY_TEST_OUT_DIR'], 'sentry-cli-mock.json');
++ fs.appendFileSync(out, JSON.stringify(args) + ',\n');
++ return Promise.resolve();
++ }
++
+ if (config.customHeader) {
+ env.CUSTOM_HEADER = config.customHeader;
+ }
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion-promise.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion-promise.config.ts
new file mode 100644
index 000000000000..fc7d77508b2d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion-promise.config.ts
@@ -0,0 +1,14 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { getSentryConfig } from "../configs/after-upload-deletion-promise.config.js";
+
+const outDir = "out/after-upload-deletion-promise";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: `${outDir}/basic.js`,
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(getSentryConfig(outDir))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion-promise.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion-promise.test.ts
new file mode 100644
index 000000000000..92c6bd553f5a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion-promise.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+
+test(import.meta.url, ({ runBundler, outDir, runFileInNode }) => {
+ runBundler();
+
+ // Verify the JS file exists and works
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+
+ // Verify the sourcemap was deleted (by the Promise)
+ const sourcemapPath = join(outDir, "basic.js.map");
+ expect(existsSync(sourcemapPath)).toBe(false);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion.config.ts
new file mode 100644
index 000000000000..d204614054de
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion.config.ts
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/after-upload-deletion.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/after-upload-deletion/basic.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion.test.ts
new file mode 100644
index 000000000000..c0e84898b270
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/after-upload-deletion.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+
+ //# sourceMappingURL=basic.js.map",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/application-key.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/application-key.config.ts
new file mode 100644
index 000000000000..3b55d0dcec9b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/application-key.config.ts
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/application-key.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/application-key/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/application-key.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/application-key.test.ts
new file mode 100644
index 000000000000..88a7d0a92d9a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/application-key.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = function(e) {
+ for (var n = 1; n < arguments.length; n++) {
+ var a = arguments[n];
+ if (null != a) for (var t in a) a.hasOwnProperty(t) && (e[t] = a[t]);
+ }
+ return e;
+ }({}, e._sentryModuleMetadata[new e.Error().stack], { "_sentryBundlerPluginAppKey:1234567890abcdef": true });
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-cjs.config.cjs b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-cjs.config.cjs
new file mode 100644
index 000000000000..bcd4faa1d23e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-cjs.config.cjs
@@ -0,0 +1,11 @@
+const { sentryRollupPlugin } = require("@sentry/bundler-plugins/rollup");
+const { defineConfig } = require("rolldown");
+const { sentryConfig } = require("../configs/basic.config.cjs");
+
+module.exports = defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic-cjs/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-cjs.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-cjs.test.ts
new file mode 100644
index 000000000000..ef26f379ba3b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-cjs.test.ts
@@ -0,0 +1,30 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-release-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-release-disabled.config.ts
new file mode 100644
index 000000000000..85f252aeffec
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-release-disabled.config.ts
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/basic-release-disabled.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic-release-disabled/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-release-disabled.test.ts
new file mode 100644
index 000000000000..b39ba31eec49
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-release-disabled.test.ts
@@ -0,0 +1,21 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-sourcemaps.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-sourcemaps.config.ts
new file mode 100644
index 000000000000..7191e6466a69
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-sourcemaps.config.ts
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/basic-sourcemaps.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic-sourcemaps/basic.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-sourcemaps.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-sourcemaps.test.ts
new file mode 100644
index 000000000000..11726184aff9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic-sourcemaps.test.ts
@@ -0,0 +1,32 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+
+ //# sourceMappingURL=basic.js.map",
+ "basic.js.map": "{"version":3,"file":"basic.js","names":[],"sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,cAAc"}",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic.config.ts
new file mode 100644
index 000000000000..58aaeeace153
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic.config.ts
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/basic.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic.test.ts
new file mode 100644
index 000000000000..ef26f379ba3b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/basic.test.ts
@@ -0,0 +1,30 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/build-info.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/build-info.config.ts
new file mode 100644
index 000000000000..468d4d4d11f3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/build-info.config.ts
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/build-info.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/build-info/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/build-info.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/build-info.test.ts
new file mode 100644
index 000000000000..1035e8759244
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/build-info.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "build-information-injection-test" };
+ e.SENTRY_BUILD_INFO = {
+ "deps": [
+ "@sentry/bundler-plugins",
+ "react",
+ "rolldown"
+ ],
+ "depsVersions": { "react": 19 },
+ "nodeVersion":"NODE_VERSION"
+ };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/bundle-size-optimizations.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/bundle-size-optimizations.config.ts
new file mode 100644
index 000000000000..d448d43b46e4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/bundle-size-optimizations.config.ts
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/bundle-size-optimizations.config.js";
+
+export default defineConfig({
+ input: "src/bundle.js",
+ output: {
+ file: "out/bundle-size-optimizations/bundle.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/bundle-size-optimizations.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/bundle-size-optimizations.test.ts
new file mode 100644
index 000000000000..eeb6bf284506
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/bundle-size-optimizations.test.ts
@@ -0,0 +1,35 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "bundle.js": "//#region src/bundle.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log(JSON.stringify({
+ debug: "b",
+ trace: "b",
+ replayCanvas: "a",
+ replayIframe: "a",
+ replayShadowDom: "a",
+ replayWorker: "a"
+ }));
+ //#endregion
+ ",
+ }
+ `);
+
+ const output = runFileInNode("bundle.js");
+ expect(output).toMatchInlineSnapshot(`
+ "{"debug":"b","trace":"b","replayCanvas":"a","replayIframe":"a","replayShadowDom":"a","replayWorker":"a"}
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-disabled.config.ts
new file mode 100644
index 000000000000..17a0888ae52b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-disabled.config.ts
@@ -0,0 +1,14 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/component-annotation-disabled.config.js";
+
+export default defineConfig({
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ file: "out/component-annotation-disabled/app.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-disabled.test.ts
new file mode 100644
index 000000000000..3866198ed816
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-disabled.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { jsx, jsxs } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-runtime.js";
+ //#region src/component-a.jsx
+ function ComponentA() {
+ return /* @__PURE__ */ jsx("span", { children: "Component A" });
+ }
+ //#endregion
+ //#region src/app.jsx
+ function App() {
+ return /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx(ComponentA, {}), ";"] });
+ }
+ //#endregion
+ export { App as default };
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-next.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-next.config.ts
new file mode 100644
index 000000000000..7f87168c15dc
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-next.config.ts
@@ -0,0 +1,14 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/component-annotation-next.config.js";
+
+export default defineConfig({
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ file: "out/component-annotation-next/app.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-next.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-next.test.ts
new file mode 100644
index 000000000000..4e6b7ba923f9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation-next.test.ts
@@ -0,0 +1,37 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { jsx, jsxs } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-runtime.js";
+ //#region src/component-a.jsx
+ function ComponentA() {
+ return /* @__PURE__ */ jsx("span", {
+ "data-sentry-component": "ComponentA",
+ children: "Component A"
+ });
+ }
+ //#endregion
+ //#region src/app.jsx
+ function App() {
+ return /* @__PURE__ */ jsxs("span", {
+ "data-sentry-component": "App",
+ children: [/* @__PURE__ */ jsx(ComponentA, {}), ";"]
+ });
+ }
+ //#endregion
+ export { App as default };
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation.config.ts
new file mode 100644
index 000000000000..7286ec4f4587
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation.config.ts
@@ -0,0 +1,14 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/component-annotation.config.js";
+
+export default defineConfig({
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ file: "out/component-annotation/app.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation.test.ts
new file mode 100644
index 000000000000..98cd6ee88fed
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/component-annotation.test.ts
@@ -0,0 +1,42 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { jsx, jsxs } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-runtime.js";
+ //#region src/component-a.jsx
+ function ComponentA() {
+ return /* @__PURE__ */ jsx("span", {
+ "data-sentry-component": "ComponentA",
+ "data-sentry-source-file": "component-a.jsx",
+ children: "Component A"
+ });
+ }
+ //#endregion
+ //#region src/app.jsx
+ function App() {
+ return /* @__PURE__ */ jsxs("span", {
+ "data-sentry-component": "App",
+ "data-sentry-source-file": "app.jsx",
+ children: [/* @__PURE__ */ jsx(ComponentA, {
+ "data-sentry-element": "ComponentA",
+ "data-sentry-source-file": "app.jsx"
+ }), ";"]
+ });
+ }
+ //#endregion
+ export { App as default };
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugid-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugid-disabled.config.ts
new file mode 100644
index 000000000000..c1d4dfc8e5ab
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugid-disabled.config.ts
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/debugid-disabled.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/debugid-disabled/basic.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugid-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugid-disabled.test.ts
new file mode 100644
index 000000000000..0502dbb79f0c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugid-disabled.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ console.log("hello world");
+ //#endregion
+
+ //# sourceMappingURL=basic.js.map",
+ "basic.js.map": "{"version":3,"file":"basic.js","names":[],"sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"mappings":";AACA,QAAQ,IAAI,cAAc"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugids-already-injected.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugids-already-injected.config.ts
new file mode 100644
index 000000000000..2158d8c510be
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugids-already-injected.config.ts
@@ -0,0 +1,13 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/debugids-already-injected.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/debugids-already-injected/basic.js",
+ sourcemap: true,
+ sourcemapDebugIds: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugids-already-injected.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugids-already-injected.test.ts
new file mode 100644
index 000000000000..c5a04f975b7c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/debugids-already-injected.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { readAllFiles } from "../utils";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, createTempDir }) => {
+ const tempDir = createTempDir();
+
+ runBundler({ SENTRY_TEST_OVERRIDE_TEMP_DIR: tempDir });
+ const files = readAllFiles(tempDir);
+ expect(files).toMatchInlineSnapshot(`
+ {
+ "b699d9c1-b033-4536-aa25-233c92609b54-0.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+
+ //# debugId=00000000-0000-0000-0000-000000000000
+ //# sourceMappingURL=basic.js.map",
+ "b699d9c1-b033-4536-aa25-233c92609b54-0.js.map": "{"version":3,"file":"basic.js","names":[],"sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,cAAc","debugId":"b699d9c1-b033-4536-aa25-233c92609b54","debug_id":"b699d9c1-b033-4536-aa25-233c92609b54"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/dont-mess-up-user-code.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/dont-mess-up-user-code.config.ts
new file mode 100644
index 000000000000..734d5ae4fb6d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/dont-mess-up-user-code.config.ts
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/dont-mess-up-user-code.config.js";
+
+export default defineConfig({
+ input: "src/index.js",
+ output: {
+ file: "out/dont-mess-up-user-code/index.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/dont-mess-up-user-code.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/dont-mess-up-user-code.test.ts
new file mode 100644
index 000000000000..08a0afffca51
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/dont-mess-up-user-code.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js": "//#region src/import.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "I am release!" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("I am import!");
+ //#endregion
+ //#region src/index.js
+ console.log("I am index!");
+ //#endregion
+
+ //# sourceMappingURL=index.js.map",
+ "index.js.map": "{"version":3,"file":"index.js","names":[],"sources":["../../src/import.js","../../src/index.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"I am import!\\");\\n\\nexport {};\\n","import \\"./import\\";\\n\\n// eslint-disable-next-line no-console\\nconsole.log(\\"I am index!\\");\\n"],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,eAAe;;;ACE3B,QAAQ,IAAI,cAAc"}",
+ }
+ `);
+
+ const output = runFileInNode("index.js");
+ expect(output).toContain("I am import!");
+ expect(output).toContain("I am index!");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/errorhandling.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/errorhandling.config.ts
new file mode 100644
index 000000000000..77826d119317
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/errorhandling.config.ts
@@ -0,0 +1,15 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { getErrorHandlingConfig } from "../configs/errorhandling.config.js";
+
+const FAKE_SENTRY_PORT = process.env["FAKE_SENTRY_PORT"] || "9876";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/errorhandling/basic.js",
+ format: "cjs",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(getErrorHandlingConfig(FAKE_SENTRY_PORT))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/errorhandling.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/errorhandling.test.ts
new file mode 100644
index 000000000000..f2672a372553
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/errorhandling.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { withFakeSentryServer } from "../utils";
+
+test(import.meta.url, async ({ runBundler }) => {
+ await withFakeSentryServer((port) => {
+ // Run bundler with fake server - should succeed despite server errors
+ runBundler({
+ FAKE_SENTRY_PORT: port,
+ SENTRY_HTTP_MAX_RETRIES: "1", // Only retry once to avoid timeout
+ });
+
+ // If we get here, the build succeeded (didn't throw)
+ expect(true).toBe(true);
+ });
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/module-metadata.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/module-metadata.config.ts
new file mode 100644
index 000000000000..10409bead2d9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/module-metadata.config.ts
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/module-metadata.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/module-metadata/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/module-metadata.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/module-metadata.test.ts
new file mode 100644
index 000000000000..05c0653cd811
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/module-metadata.test.ts
@@ -0,0 +1,35 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = function(e) {
+ for (var n = 1; n < arguments.length; n++) {
+ var a = arguments[n];
+ if (null != a) for (var t in a) a.hasOwnProperty(t) && (e[t] = a[t]);
+ }
+ return e;
+ }({}, e._sentryModuleMetadata[new e.Error().stack], {
+ "something": "value",
+ "another": 999
+ });
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/multiple-entry-points.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/multiple-entry-points.config.ts
new file mode 100644
index 000000000000..d75963e3ec8f
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/multiple-entry-points.config.ts
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/multiple-entry-points.config.js";
+
+export default defineConfig({
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/multiple-entry-points",
+ chunkFileNames: "[name].js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/multiple-entry-points.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/multiple-entry-points.test.ts
new file mode 100644
index 000000000000..63b5f353f8f4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/multiple-entry-points.test.ts
@@ -0,0 +1,59 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js": "//#region src/common.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ function add(a, b) {
+ return a + b;
+ }
+ //#endregion
+ export { add as t };
+ ",
+ "entry1.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { t as add } from "./common.js";
+ //#region src/entry1.js
+ console.log(add(1, 2));
+ //#endregion
+ ",
+ "entry2.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { t as add } from "./common.js";
+ //#region src/entry2.js
+ console.log(add(2, 4));
+ //#endregion
+ ",
+ }
+ `);
+
+ const output1 = runFileInNode("entry1.js");
+ expect(output1).toMatchInlineSnapshot(`
+ "3
+ "
+ `);
+ const output2 = runFileInNode("entry2.js");
+ expect(output2).toMatchInlineSnapshot(`
+ "6
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json
new file mode 100644
index 000000000000..a87b7d3ba5ea
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "rolldown-integration-tests",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0",
+ "react": "19.2.4",
+ "rolldown": "1.0.0"
+ },
+ "pnpm": {
+ "overrides": {
+ "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins.tgz"
+ },
+ "patchedDependencies": {
+ "@sentry/cli": "../patches/@sentry__cli.patch"
+ }
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/query-param.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/query-param.config.ts
new file mode 100644
index 000000000000..324ae310be8e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/query-param.config.ts
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/query-param.config.js";
+
+export default defineConfig({
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/query-param",
+ chunkFileNames: "[name].js?seP58q4g",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/query-param.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/query-param.test.ts
new file mode 100644
index 000000000000..96666e28df19
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/query-param.test.ts
@@ -0,0 +1,56 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, ctx }) => {
+ if (process.platform === "win32") {
+ ctx.skip("Query params do not work in paths on Windows");
+ return;
+ }
+
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js?seP58q4g": "//#region src/common.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ function add(a, b) {
+ return a + b;
+ }
+ //#endregion
+ export { add as t };
+ ",
+ "entry1.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { t as add } from "./common.js?seP58q4g";
+ //#region src/entry1.js
+ console.log(add(1, 2));
+ //#endregion
+ ",
+ "entry2.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { t as add } from "./common.js?seP58q4g";
+ //#region src/entry2.js
+ console.log(add(2, 4));
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-disabled.config.ts
new file mode 100644
index 000000000000..17ee804dbd07
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-disabled.config.ts
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/release-disabled.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/release-disabled/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-disabled.test.ts
new file mode 100644
index 000000000000..18e4c8f80f47
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-disabled.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ "sentry-cli-mock.json": "["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-value-with-quotes.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-value-with-quotes.config.ts
new file mode 100644
index 000000000000..b43b982da0c5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-value-with-quotes.config.ts
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/release-value-with-quotes.config.js";
+
+export default defineConfig({
+ input: "src/release-value-with-quotes.js",
+ output: {
+ file: "out/release-value-with-quotes/bundle.js",
+ format: "iife",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-value-with-quotes.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-value-with-quotes.test.ts
new file mode 100644
index 000000000000..314a41b865f6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/release-value-with-quotes.test.ts
@@ -0,0 +1,8 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, runFileInNode }) => {
+ runBundler();
+ const output = runFileInNode("bundle.js");
+ expect(output.trimEnd()).toBe('"i am a dangerous release value because I contain a \\""');
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/app.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/app.jsx
new file mode 100644
index 000000000000..614d38c834aa
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/app.jsx
@@ -0,0 +1,9 @@
+import { ComponentA } from "./component-a";
+
+export default function App() {
+ return (
+
+ ;
+
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/basic.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/basic.js
new file mode 100644
index 000000000000..7ef02afbd244
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/basic.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-console
+console.log("hello world");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/bundle.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/bundle.js
new file mode 100644
index 000000000000..0d62e559347d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/bundle.js
@@ -0,0 +1,10 @@
+console.log(
+ JSON.stringify({
+ debug: __SENTRY_DEBUG__ ? "a" : "b",
+ trace: __SENTRY_TRACING__ ? "a" : "b",
+ replayCanvas: __RRWEB_EXCLUDE_CANVAS__ ? "a" : "b",
+ replayIframe: __RRWEB_EXCLUDE_IFRAME__ ? "a" : "b",
+ replayShadowDom: __RRWEB_EXCLUDE_SHADOW_DOM__ ? "a" : "b",
+ replayWorker: __SENTRY_EXCLUDE_REPLAY_WORKER__ ? "a" : "b",
+ })
+);
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/common.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/common.js
new file mode 100644
index 000000000000..7d658310b0d9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/common.js
@@ -0,0 +1,3 @@
+export function add(a, b) {
+ return a + b;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/component-a.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/component-a.jsx
new file mode 100644
index 000000000000..5d57ab2215cd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/component-a.jsx
@@ -0,0 +1,3 @@
+export function ComponentA() {
+ return Component A;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/entry1.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/entry1.js
new file mode 100644
index 000000000000..480816663453
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/entry1.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(1, 2));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/entry2.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/entry2.js
new file mode 100644
index 000000000000..f64af1ea7ae5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/entry2.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(2, 4));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/import.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/import.js
new file mode 100644
index 000000000000..733d5d48f137
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/import.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line no-console
+console.log("I am import!");
+
+export {};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/index.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/index.js
new file mode 100644
index 000000000000..719d8428cac6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/index.js
@@ -0,0 +1,4 @@
+import "./import";
+
+// eslint-disable-next-line no-console
+console.log("I am index!");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/release-value-with-quotes.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/release-value-with-quotes.js
new file mode 100644
index 000000000000..aa73bfa8be00
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/src/release-value-with-quotes.js
@@ -0,0 +1,3 @@
+// Simply output the metadata to the console so it can be checked in a test
+// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-member-access
+console.log(JSON.stringify(global.SENTRY_RELEASE.id));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/telemetry.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/telemetry.config.ts
new file mode 100644
index 000000000000..15f0f72ec867
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/telemetry.config.ts
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rolldown";
+import { sentryConfig } from "../configs/telemetry.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/telemetry/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/telemetry.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/telemetry.test.ts
new file mode 100644
index 000000000000..fc8101ef5972
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/telemetry.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ "sentry-telemetry.json": "[{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":true,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"ok","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ [{"event_id":"UUID","sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"},"trace":{"environment":"production","release":"PLUGIN_VERSION","public_key":"UUID","trace_id":"UUID","org_id":"1","transaction":"Sentry Bundler Plugin execution","sampled":"true","sample_rand":"SAMPLE_RAND","sample_rate":"1"}},[[{"type":"transaction"},{"contexts":{"trace":{"span_id":"SHORT_UUID","trace_id":"UUID","data":{"sentry.origin":"manual","sentry.source":"custom","sentry.sample_rate":1},"origin":"manual"},"runtime":{"name":"node","version":"NODE_VERSION"}},"spans":[],"start_timestamp":START_TIMESTAMP,"timestamp":TIMESTAMP,"transaction":"Sentry Bundler Plugin execution","type":"transaction","transaction_info":{"source":"custom"},"platform":"PLATFORM","event_id":"UUID","environment":"production","release":"PLUGIN_VERSION","tags":{"upload-legacy-sourcemaps":false,"module-metadata":false,"inject-build-information":false,"set-commits":"auto","finalize-release":true,"deploy-options":false,"custom-error-handler":false,"sourcemaps-assets":false,"delete-after-upload":false,"sourcemaps-disabled":false,"react-annotate":false,"node":"NODE_VERSION","platform":"PLATFORM","meta-framework":"none","application-key-set":false,"ci":true,"project":"undefined","bundler":"rollup","bundler-major-version":"4"},"user":{},"sdk":{"name":"sentry.javascript.node","version":"10.56.0","integrations":[],"packages":[{"name":"npm:@sentry/node","version":"10.56.0"}]}}]]],
+ [{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":false,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"exited","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/utils.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/utils.ts
new file mode 100644
index 000000000000..9ff656adbfda
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/utils.ts
@@ -0,0 +1,63 @@
+import { basename, dirname, join } from "node:path";
+import { createTempDir, readAllFiles, runBundler } from "../utils";
+import { fileURLToPath } from "node:url";
+import { rmSync } from "node:fs";
+import type { TestContext } from "vitest";
+import { test as vitestTest } from "vitest";
+import { execSync } from "node:child_process";
+
+const cwd = dirname(fileURLToPath(import.meta.url));
+const NODE_MAJOR_VERSION = parseInt(process.versions.node.split(".")[0] || "0", 10);
+
+type TestCallback = (props: {
+ outDir: string;
+ runBundler: (env?: Record) => void;
+ readOutputFiles: () => Record;
+ runFileInNode: (file: string) => string;
+ createTempDir: () => string;
+ ctx: TestContext;
+}) => void | Promise;
+
+export function test(url: string, callback: TestCallback) {
+ const filePath = fileURLToPath(url);
+ const filename = basename(filePath);
+ const testName = filename.replace(/\.test\.ts$/, "");
+ const outDir = join(cwd, "out", testName);
+
+ // Clear the output directory before running the test
+ rmSync(outDir, { recursive: true, force: true });
+
+ // Detect CJS config files by test name suffix
+ const configExt = testName.endsWith("-cjs") ? ".config.cjs" : ".config.ts";
+
+ // Rolldown requires Node 20+. Register the test under its real name either way
+ // and skip it on older Node, so the coverage gap is visible (reported as a
+ // skipped `rolldown > ${testName}`) rather than silently dropped.
+ vitestTest.skipIf(NODE_MAJOR_VERSION < 20)(`rolldown > ${testName}`, (ctx) =>
+ callback({
+ outDir,
+ runBundler: (env) =>
+ runBundler(
+ `pnpm rolldown --config ${testName}${configExt}`,
+ {
+ cwd,
+ env: {
+ ...process.env,
+ ...env,
+ },
+ },
+ outDir
+ ),
+ readOutputFiles: () => readAllFiles(outDir),
+ runFileInNode: (file) => {
+ const fullPath = join(outDir, file);
+ return execSync(`node ${fullPath}`, {
+ cwd,
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
+ }).toString();
+ },
+ createTempDir: () => createTempDir(),
+ ctx,
+ })
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion-promise.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion-promise.config.js
new file mode 100644
index 000000000000..ccdf75e7df88
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion-promise.config.js
@@ -0,0 +1,14 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { getSentryConfig } from "../configs/after-upload-deletion-promise.config.js";
+
+const outDir = "out/after-upload-deletion-promise";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: `${outDir}/basic.js`,
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(getSentryConfig(outDir))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion-promise.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion-promise.test.ts
new file mode 100644
index 000000000000..92c6bd553f5a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion-promise.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+
+test(import.meta.url, ({ runBundler, outDir, runFileInNode }) => {
+ runBundler();
+
+ // Verify the JS file exists and works
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+
+ // Verify the sourcemap was deleted (by the Promise)
+ const sourcemapPath = join(outDir, "basic.js.map");
+ expect(existsSync(sourcemapPath)).toBe(false);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion.config.js
new file mode 100644
index 000000000000..70e6d56989b4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/after-upload-deletion.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/after-upload-deletion/basic.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion.test.ts
new file mode 100644
index 000000000000..a4305945f767
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/after-upload-deletion.test.ts
@@ -0,0 +1,17 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/application-key.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/application-key.config.js
new file mode 100644
index 000000000000..2a5f80ffd1fd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/application-key.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/application-key.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/application-key/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/application-key.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/application-key.test.ts
new file mode 100644
index 000000000000..893eb03cfbb1
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/application-key.test.ts
@@ -0,0 +1,13 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=function(e){for(var n=1;n {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-release-disabled.config.js
new file mode 100644
index 000000000000..077cf55061f3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-release-disabled.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/basic-release-disabled.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic-release-disabled/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-release-disabled.test.ts
new file mode 100644
index 000000000000..ebf24e57ed36
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-release-disabled.test.ts
@@ -0,0 +1,13 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-sourcemaps.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-sourcemaps.config.js
new file mode 100644
index 000000000000..9da6d6c13bdb
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-sourcemaps.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/basic-sourcemaps.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic-sourcemaps/basic.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-sourcemaps.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-sourcemaps.test.ts
new file mode 100644
index 000000000000..0f4f0bed3833
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic-sourcemaps.test.ts
@@ -0,0 +1,23 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ "basic.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":"AAAA,CAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA,IAAA,CAAA,IAAA,CAAA,EAAA,CAAA;scACA,OAAO,CAAC,GAAG,CAAC,CAAA,KAAA,CAAA,KAAA,CAAa,CAAC"}",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic.config.js
new file mode 100644
index 000000000000..5bdc55282122
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/basic.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic.test.ts
new file mode 100644
index 000000000000..1d68d3104370
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/basic.test.ts
@@ -0,0 +1,21 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/build-info.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/build-info.config.js
new file mode 100644
index 000000000000..c7b389a4be46
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/build-info.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/build-info.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/build-info/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/build-info.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/build-info.test.ts
new file mode 100644
index 000000000000..e9b8e53ac2e3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/build-info.test.ts
@@ -0,0 +1,13 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"build-information-injection-test"};e.SENTRY_BUILD_INFO={"deps":["@rollup/plugin-babel","@rollup/plugin-node-resolve","@sentry/bundler-plugins","react","rollup"],"depsVersions":{"react":19,"rollup":3},"nodeVersion":"NODE_VERSION"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/bundle-size-optimizations.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/bundle-size-optimizations.config.js
new file mode 100644
index 000000000000..f1a0e8d61f74
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/bundle-size-optimizations.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/bundle-size-optimizations.config.js";
+
+export default defineConfig({
+ input: "src/bundle.js",
+ output: {
+ file: "out/bundle-size-optimizations/bundle.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/bundle-size-optimizations.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/bundle-size-optimizations.test.ts
new file mode 100644
index 000000000000..c685a2b794e2
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/bundle-size-optimizations.test.ts
@@ -0,0 +1,27 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "bundle.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log(
+ JSON.stringify({
+ debug: "b",
+ trace: "b",
+ replayCanvas: "a" ,
+ replayIframe: "a" ,
+ replayShadowDom: "a" ,
+ replayWorker: "a" ,
+ })
+ );
+ ",
+ }
+ `);
+
+ const output = runFileInNode("bundle.js");
+ expect(output).toMatchInlineSnapshot(`
+ "{"debug":"b","trace":"b","replayCanvas":"a","replayIframe":"a","replayShadowDom":"a","replayWorker":"a"}
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-disabled.config.js
new file mode 100644
index 000000000000..457b3d540d41
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-disabled.config.js
@@ -0,0 +1,28 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/component-annotation-disabled.config.js";
+import { babel } from "@rollup/plugin-babel";
+import resolve from "@rollup/plugin-node-resolve";
+
+const RESOLVABLE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx"];
+
+export default defineConfig({
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ file: "out/component-annotation-disabled/app.js",
+ },
+ plugins: [
+ resolve({
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ sentryRollupPlugin(sentryConfig),
+ babel({
+ babelHelpers: "bundled",
+ presets: [["@babel/preset-react", { runtime: "automatic" }]],
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ ],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-disabled.test.ts
new file mode 100644
index 000000000000..054e90c0e827
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-disabled.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { jsx, jsxs } from 'react/jsx-runtime';
+
+ function ComponentA() {
+ return /*#__PURE__*/jsx("span", {
+ children: "Component A"
+ });
+ }
+
+ function App() {
+ return /*#__PURE__*/jsxs("span", {
+ children: [/*#__PURE__*/jsx(ComponentA, {}), ";"]
+ });
+ }
+
+ export { App as default };
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-next.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-next.config.js
new file mode 100644
index 000000000000..de1cd4b6f83c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-next.config.js
@@ -0,0 +1,28 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/component-annotation-next.config.js";
+import { babel } from "@rollup/plugin-babel";
+import resolve from "@rollup/plugin-node-resolve";
+
+const RESOLVABLE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx"];
+
+export default defineConfig({
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ file: "out/component-annotation-next/app.js",
+ },
+ plugins: [
+ resolve({
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ sentryRollupPlugin(sentryConfig),
+ babel({
+ babelHelpers: "bundled",
+ presets: [["@babel/preset-react", { runtime: "automatic" }]],
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ ],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-next.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-next.test.ts
new file mode 100644
index 000000000000..62cfe0816cc5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation-next.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { jsx, jsxs } from 'react/jsx-runtime';
+
+ function ComponentA() {
+ return /*#__PURE__*/jsx("span", {
+ "data-sentry-component": "ComponentA",
+ children: "Component A"
+ });
+ }
+
+ function App() {
+ return /*#__PURE__*/jsxs("span", {
+ "data-sentry-component": "App",
+ children: [/*#__PURE__*/jsx(ComponentA, {}), ";"]
+ });
+ }
+
+ export { App as default };
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation.config.js
new file mode 100644
index 000000000000..58befe4f35e3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation.config.js
@@ -0,0 +1,28 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/component-annotation.config.js";
+import { babel } from "@rollup/plugin-babel";
+import resolve from "@rollup/plugin-node-resolve";
+
+const RESOLVABLE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx"];
+
+export default defineConfig({
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ file: "out/component-annotation/app.js",
+ },
+ plugins: [
+ resolve({
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ sentryRollupPlugin(sentryConfig),
+ babel({
+ babelHelpers: "bundled",
+ presets: [["@babel/preset-react", { runtime: "automatic" }]],
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ ],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation.test.ts
new file mode 100644
index 000000000000..387beb7fda07
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/component-annotation.test.ts
@@ -0,0 +1,33 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { jsx, jsxs } from 'react/jsx-runtime';
+
+ function ComponentA() {
+ return /*#__PURE__*/jsx("span", {
+ "data-sentry-component": "ComponentA",
+ "data-sentry-source-file": "component-a.jsx",
+ children: "Component A"
+ });
+ }
+
+ function App() {
+ return /*#__PURE__*/jsxs("span", {
+ "data-sentry-component": "App",
+ "data-sentry-source-file": "app.jsx",
+ children: [/*#__PURE__*/jsx(ComponentA, {
+ "data-sentry-element": "ComponentA",
+ "data-sentry-source-file": "app.jsx"
+ }), ";"]
+ });
+ }
+
+ export { App as default };
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/debugid-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/debugid-disabled.config.js
new file mode 100644
index 000000000000..557debb33d77
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/debugid-disabled.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/debugid-disabled.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/debugid-disabled/basic.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/debugid-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/debugid-disabled.test.ts
new file mode 100644
index 000000000000..4385c076de7b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/debugid-disabled.test.ts
@@ -0,0 +1,15 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ "basic.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":"AAAA;AACA,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/dont-mess-up-user-code.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/dont-mess-up-user-code.config.js
new file mode 100644
index 000000000000..20c07ae03669
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/dont-mess-up-user-code.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/dont-mess-up-user-code.config.js";
+
+export default defineConfig({
+ input: "src/index.js",
+ output: {
+ file: "out/dont-mess-up-user-code/index.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/dont-mess-up-user-code.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/dont-mess-up-user-code.test.ts
new file mode 100644
index 000000000000..42d6d3679b9c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/dont-mess-up-user-code.test.ts
@@ -0,0 +1,22 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"I am release!"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("I am import!");
+
+ // eslint-disable-next-line no-console
+ console.log("I am index!");
+ //# sourceMappingURL=index.js.map
+ ",
+ "index.js.map": "{"version":3,"file":"index.js","sources":["../../src/import.js","../../src/index.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"I am import!\\");\\n\\nexport {};\\n","import \\"./import\\";\\n\\n// eslint-disable-next-line no-console\\nconsole.log(\\"I am index!\\");\\n"],"names":[],"mappings":"AAAA,CAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA,IAAA,CAAA,IAAA,CAAA,EAAA,CAAA;2aACA,OAAO,CAAC,GAAG,CAAC,CAAA,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAc,CAAC;;ACC3B,CAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA,IAAA,CAAA,IAAA,CAAA,EAAA,CAAA;AACA,OAAO,CAAC,GAAG,CAAC,CAAA,CAAA,CAAA,EAAA,CAAA,KAAA,CAAA,CAAa,CAAC"}",
+ }
+ `);
+
+ const output = runFileInNode("index.js");
+ expect(output).toContain("I am import!");
+ expect(output).toContain("I am index!");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/errorhandling.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/errorhandling.config.js
new file mode 100644
index 000000000000..8b08902e7940
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/errorhandling.config.js
@@ -0,0 +1,15 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { getErrorHandlingConfig } from "../configs/errorhandling.config.js";
+
+const FAKE_SENTRY_PORT = process.env.FAKE_SENTRY_PORT || "9876";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/errorhandling/basic.js",
+ format: "cjs",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(getErrorHandlingConfig(FAKE_SENTRY_PORT))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/errorhandling.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/errorhandling.test.ts
new file mode 100644
index 000000000000..f2672a372553
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/errorhandling.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { withFakeSentryServer } from "../utils";
+
+test(import.meta.url, async ({ runBundler }) => {
+ await withFakeSentryServer((port) => {
+ // Run bundler with fake server - should succeed despite server errors
+ runBundler({
+ FAKE_SENTRY_PORT: port,
+ SENTRY_HTTP_MAX_RETRIES: "1", // Only retry once to avoid timeout
+ });
+
+ // If we get here, the build succeeded (didn't throw)
+ expect(true).toBe(true);
+ });
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/module-metadata.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/module-metadata.config.js
new file mode 100644
index 000000000000..b916797d11e4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/module-metadata.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/module-metadata.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/module-metadata/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/module-metadata.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/module-metadata.test.ts
new file mode 100644
index 000000000000..b4acc59902a5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/module-metadata.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=function(e){for(var n=1;n {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();function add(a, b) {
+ return a + b;
+ }
+
+ export { add as a };
+ ",
+ "entry1.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { a as add } from './common.js';
+
+ console.log(add(1, 2));
+ ",
+ "entry2.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { a as add } from './common.js';
+
+ console.log(add(2, 4));
+ ",
+ }
+ `);
+
+ const output1 = runFileInNode("entry1.js");
+ expect(output1).toMatchInlineSnapshot(`
+ "3
+ "
+ `);
+ const output2 = runFileInNode("entry2.js");
+ expect(output2).toMatchInlineSnapshot(`
+ "6
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json
new file mode 100644
index 000000000000..86a90268920f
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "rollup3-integration-tests",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0",
+ "react": "19.2.4",
+ "rollup": "3.30.0",
+ "@rollup/plugin-babel": "6.0.4",
+ "@rollup/plugin-node-resolve": "15.2.3"
+ },
+ "pnpm": {
+ "overrides": {
+ "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins.tgz"
+ },
+ "patchedDependencies": {
+ "@sentry/cli": "../patches/@sentry__cli.patch"
+ }
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/query-param.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/query-param.config.js
new file mode 100644
index 000000000000..94711b1592be
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/query-param.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/query-param.config.js";
+
+export default defineConfig({
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/query-param",
+ chunkFileNames: "[name].js?seP58q4g",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/query-param.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/query-param.test.ts
new file mode 100644
index 000000000000..d65cb2e349da
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/query-param.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, ctx }) => {
+ if (process.platform === "win32") {
+ ctx.skip("Query params do not work in paths on Windows");
+ return;
+ }
+
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js?seP58q4g": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();function add(a, b) {
+ return a + b;
+ }
+
+ export { add as a };
+ ",
+ "entry1.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { a as add } from './common.js?seP58q4g';
+
+ console.log(add(1, 2));
+ ",
+ "entry2.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { a as add } from './common.js?seP58q4g';
+
+ console.log(add(2, 4));
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-disabled.config.js
new file mode 100644
index 000000000000..defa3610e956
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-disabled.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/release-disabled.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/release-disabled/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-disabled.test.ts
new file mode 100644
index 000000000000..b8e6f6b9d32f
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-disabled.test.ts
@@ -0,0 +1,17 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-value-with-quotes.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-value-with-quotes.config.js
new file mode 100644
index 000000000000..92d51d0379e3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-value-with-quotes.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/release-value-with-quotes.config.js";
+
+export default defineConfig({
+ input: "src/release-value-with-quotes.js",
+ output: {
+ file: "out/release-value-with-quotes/bundle.js",
+ format: "iife",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-value-with-quotes.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-value-with-quotes.test.ts
new file mode 100644
index 000000000000..314a41b865f6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/release-value-with-quotes.test.ts
@@ -0,0 +1,8 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, runFileInNode }) => {
+ runBundler();
+ const output = runFileInNode("bundle.js");
+ expect(output.trimEnd()).toBe('"i am a dangerous release value because I contain a \\""');
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/app.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/app.jsx
new file mode 100644
index 000000000000..614d38c834aa
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/app.jsx
@@ -0,0 +1,9 @@
+import { ComponentA } from "./component-a";
+
+export default function App() {
+ return (
+
+ ;
+
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/basic.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/basic.js
new file mode 100644
index 000000000000..7ef02afbd244
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/basic.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-console
+console.log("hello world");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/bundle.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/bundle.js
new file mode 100644
index 000000000000..0d62e559347d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/bundle.js
@@ -0,0 +1,10 @@
+console.log(
+ JSON.stringify({
+ debug: __SENTRY_DEBUG__ ? "a" : "b",
+ trace: __SENTRY_TRACING__ ? "a" : "b",
+ replayCanvas: __RRWEB_EXCLUDE_CANVAS__ ? "a" : "b",
+ replayIframe: __RRWEB_EXCLUDE_IFRAME__ ? "a" : "b",
+ replayShadowDom: __RRWEB_EXCLUDE_SHADOW_DOM__ ? "a" : "b",
+ replayWorker: __SENTRY_EXCLUDE_REPLAY_WORKER__ ? "a" : "b",
+ })
+);
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/common.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/common.js
new file mode 100644
index 000000000000..7d658310b0d9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/common.js
@@ -0,0 +1,3 @@
+export function add(a, b) {
+ return a + b;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/component-a.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/component-a.jsx
new file mode 100644
index 000000000000..5d57ab2215cd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/component-a.jsx
@@ -0,0 +1,3 @@
+export function ComponentA() {
+ return Component A;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/entry1.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/entry1.js
new file mode 100644
index 000000000000..480816663453
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/entry1.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(1, 2));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/entry2.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/entry2.js
new file mode 100644
index 000000000000..f64af1ea7ae5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/entry2.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(2, 4));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/import.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/import.js
new file mode 100644
index 000000000000..733d5d48f137
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/import.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line no-console
+console.log("I am import!");
+
+export {};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/index.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/index.js
new file mode 100644
index 000000000000..719d8428cac6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/index.js
@@ -0,0 +1,4 @@
+import "./import";
+
+// eslint-disable-next-line no-console
+console.log("I am index!");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/release-value-with-quotes.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/release-value-with-quotes.js
new file mode 100644
index 000000000000..aa73bfa8be00
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/src/release-value-with-quotes.js
@@ -0,0 +1,3 @@
+// Simply output the metadata to the console so it can be checked in a test
+// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-member-access
+console.log(JSON.stringify(global.SENTRY_RELEASE.id));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/telemetry.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/telemetry.config.js
new file mode 100644
index 000000000000..5467793d80df
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/telemetry.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/telemetry.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/telemetry/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/telemetry.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/telemetry.test.ts
new file mode 100644
index 000000000000..2a108cc61461
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/telemetry.test.ts
@@ -0,0 +1,20 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ "sentry-telemetry.json": "[{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":true,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"ok","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ [{"event_id":"UUID","sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"},"trace":{"environment":"production","release":"PLUGIN_VERSION","public_key":"UUID","trace_id":"UUID","org_id":"1","transaction":"Sentry Bundler Plugin execution","sampled":"true","sample_rand":"SAMPLE_RAND","sample_rate":"1"}},[[{"type":"transaction"},{"contexts":{"trace":{"span_id":"SHORT_UUID","trace_id":"UUID","data":{"sentry.origin":"manual","sentry.source":"custom","sentry.sample_rate":1},"origin":"manual"},"runtime":{"name":"node","version":"NODE_VERSION"}},"spans":[],"start_timestamp":START_TIMESTAMP,"timestamp":TIMESTAMP,"transaction":"Sentry Bundler Plugin execution","type":"transaction","transaction_info":{"source":"custom"},"platform":"PLATFORM","event_id":"UUID","environment":"production","release":"PLUGIN_VERSION","tags":{"upload-legacy-sourcemaps":false,"module-metadata":false,"inject-build-information":false,"set-commits":"auto","finalize-release":true,"deploy-options":false,"custom-error-handler":false,"sourcemaps-assets":false,"delete-after-upload":false,"sourcemaps-disabled":false,"react-annotate":false,"node":"NODE_VERSION","platform":"PLATFORM","meta-framework":"none","application-key-set":false,"ci":true,"project":"undefined","bundler":"rollup","bundler-major-version":"3"},"user":{},"sdk":{"name":"sentry.javascript.node","version":"10.56.0","integrations":[],"packages":[{"name":"npm:@sentry/node","version":"10.56.0"}]}}]]],
+ [{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":false,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"exited","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/utils.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/utils.ts
new file mode 100644
index 000000000000..313d0e7397b4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/utils.ts
@@ -0,0 +1,59 @@
+import { basename, dirname, join } from "node:path";
+import { createTempDir, readAllFiles, runBundler } from "../utils";
+import { fileURLToPath } from "node:url";
+import { rmSync } from "node:fs";
+import type { TestContext } from "vitest";
+import { test as vitestTest } from "vitest";
+import { execSync } from "node:child_process";
+
+const cwd = dirname(fileURLToPath(import.meta.url));
+
+type TestCallback = (props: {
+ outDir: string;
+ runBundler: (env?: Record) => void;
+ readOutputFiles: () => Record;
+ runFileInNode: (file: string) => string;
+ createTempDir: () => string;
+ ctx: TestContext;
+}) => void | Promise;
+
+export function test(url: string, callback: TestCallback) {
+ const filePath = fileURLToPath(url);
+ const filename = basename(filePath);
+ const testName = filename.replace(/\.test\.ts$/, "");
+ const outDir = join(cwd, "out", testName);
+
+ // Clear the output directory before running the test
+ rmSync(outDir, { recursive: true, force: true });
+
+ // Detect CJS config files by test name suffix
+ const configExt = testName.endsWith("-cjs") ? ".config.cjs" : ".config.js";
+
+ vitestTest(`rollup v3 > ${testName}`, (ctx) =>
+ callback({
+ outDir,
+ runBundler: (env) =>
+ runBundler(
+ `pnpm rollup --config ${testName}${configExt}`,
+ {
+ cwd,
+ env: {
+ ...process.env,
+ ...env,
+ },
+ },
+ outDir
+ ),
+ readOutputFiles: () => readAllFiles(outDir),
+ runFileInNode: (file) => {
+ const fullPath = join(outDir, file);
+ return execSync(`node ${fullPath}`, {
+ cwd,
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
+ }).toString();
+ },
+ createTempDir: () => createTempDir(),
+ ctx,
+ })
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion-promise.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion-promise.config.js
new file mode 100644
index 000000000000..ccdf75e7df88
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion-promise.config.js
@@ -0,0 +1,14 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { getSentryConfig } from "../configs/after-upload-deletion-promise.config.js";
+
+const outDir = "out/after-upload-deletion-promise";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: `${outDir}/basic.js`,
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(getSentryConfig(outDir))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion-promise.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion-promise.test.ts
new file mode 100644
index 000000000000..92c6bd553f5a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion-promise.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+
+test(import.meta.url, ({ runBundler, outDir, runFileInNode }) => {
+ runBundler();
+
+ // Verify the JS file exists and works
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+
+ // Verify the sourcemap was deleted (by the Promise)
+ const sourcemapPath = join(outDir, "basic.js.map");
+ expect(existsSync(sourcemapPath)).toBe(false);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion.config.js
new file mode 100644
index 000000000000..70e6d56989b4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/after-upload-deletion.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/after-upload-deletion/basic.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion.test.ts
new file mode 100644
index 000000000000..a4305945f767
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/after-upload-deletion.test.ts
@@ -0,0 +1,17 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/application-key.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/application-key.config.js
new file mode 100644
index 000000000000..2a5f80ffd1fd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/application-key.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/application-key.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/application-key/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/application-key.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/application-key.test.ts
new file mode 100644
index 000000000000..893eb03cfbb1
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/application-key.test.ts
@@ -0,0 +1,13 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=function(e){for(var n=1;n {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-release-disabled.config.js
new file mode 100644
index 000000000000..077cf55061f3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-release-disabled.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/basic-release-disabled.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic-release-disabled/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-release-disabled.test.ts
new file mode 100644
index 000000000000..ebf24e57ed36
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-release-disabled.test.ts
@@ -0,0 +1,13 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-sourcemaps.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-sourcemaps.config.js
new file mode 100644
index 000000000000..9da6d6c13bdb
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-sourcemaps.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/basic-sourcemaps.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic-sourcemaps/basic.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-sourcemaps.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-sourcemaps.test.ts
new file mode 100644
index 000000000000..0f4f0bed3833
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic-sourcemaps.test.ts
@@ -0,0 +1,23 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ "basic.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":"AAAA,CAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA,IAAA,CAAA,IAAA,CAAA,EAAA,CAAA;scACA,OAAO,CAAC,GAAG,CAAC,CAAA,KAAA,CAAA,KAAA,CAAa,CAAC"}",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic.config.js
new file mode 100644
index 000000000000..5bdc55282122
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/basic.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/basic/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic.test.ts
new file mode 100644
index 000000000000..1d68d3104370
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/basic.test.ts
@@ -0,0 +1,21 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/build-info.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/build-info.config.js
new file mode 100644
index 000000000000..c7b389a4be46
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/build-info.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/build-info.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/build-info/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/build-info.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/build-info.test.ts
new file mode 100644
index 000000000000..3dca9559e716
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/build-info.test.ts
@@ -0,0 +1,13 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"build-information-injection-test"};e.SENTRY_BUILD_INFO={"deps":["@rollup/plugin-babel","@rollup/plugin-node-resolve","@sentry/bundler-plugins","react","rollup"],"depsVersions":{"react":19,"rollup":4},"nodeVersion":"NODE_VERSION"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/bundle-size-optimizations.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/bundle-size-optimizations.config.js
new file mode 100644
index 000000000000..f1a0e8d61f74
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/bundle-size-optimizations.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/bundle-size-optimizations.config.js";
+
+export default defineConfig({
+ input: "src/bundle.js",
+ output: {
+ file: "out/bundle-size-optimizations/bundle.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/bundle-size-optimizations.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/bundle-size-optimizations.test.ts
new file mode 100644
index 000000000000..c685a2b794e2
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/bundle-size-optimizations.test.ts
@@ -0,0 +1,27 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "bundle.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log(
+ JSON.stringify({
+ debug: "b",
+ trace: "b",
+ replayCanvas: "a" ,
+ replayIframe: "a" ,
+ replayShadowDom: "a" ,
+ replayWorker: "a" ,
+ })
+ );
+ ",
+ }
+ `);
+
+ const output = runFileInNode("bundle.js");
+ expect(output).toMatchInlineSnapshot(`
+ "{"debug":"b","trace":"b","replayCanvas":"a","replayIframe":"a","replayShadowDom":"a","replayWorker":"a"}
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-disabled.config.js
new file mode 100644
index 000000000000..457b3d540d41
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-disabled.config.js
@@ -0,0 +1,28 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/component-annotation-disabled.config.js";
+import { babel } from "@rollup/plugin-babel";
+import resolve from "@rollup/plugin-node-resolve";
+
+const RESOLVABLE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx"];
+
+export default defineConfig({
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ file: "out/component-annotation-disabled/app.js",
+ },
+ plugins: [
+ resolve({
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ sentryRollupPlugin(sentryConfig),
+ babel({
+ babelHelpers: "bundled",
+ presets: [["@babel/preset-react", { runtime: "automatic" }]],
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ ],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-disabled.test.ts
new file mode 100644
index 000000000000..054e90c0e827
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-disabled.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { jsx, jsxs } from 'react/jsx-runtime';
+
+ function ComponentA() {
+ return /*#__PURE__*/jsx("span", {
+ children: "Component A"
+ });
+ }
+
+ function App() {
+ return /*#__PURE__*/jsxs("span", {
+ children: [/*#__PURE__*/jsx(ComponentA, {}), ";"]
+ });
+ }
+
+ export { App as default };
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-next.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-next.config.js
new file mode 100644
index 000000000000..de1cd4b6f83c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-next.config.js
@@ -0,0 +1,28 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/component-annotation-next.config.js";
+import { babel } from "@rollup/plugin-babel";
+import resolve from "@rollup/plugin-node-resolve";
+
+const RESOLVABLE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx"];
+
+export default defineConfig({
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ file: "out/component-annotation-next/app.js",
+ },
+ plugins: [
+ resolve({
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ sentryRollupPlugin(sentryConfig),
+ babel({
+ babelHelpers: "bundled",
+ presets: [["@babel/preset-react", { runtime: "automatic" }]],
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ ],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-next.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-next.test.ts
new file mode 100644
index 000000000000..62cfe0816cc5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation-next.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { jsx, jsxs } from 'react/jsx-runtime';
+
+ function ComponentA() {
+ return /*#__PURE__*/jsx("span", {
+ "data-sentry-component": "ComponentA",
+ children: "Component A"
+ });
+ }
+
+ function App() {
+ return /*#__PURE__*/jsxs("span", {
+ "data-sentry-component": "App",
+ children: [/*#__PURE__*/jsx(ComponentA, {}), ";"]
+ });
+ }
+
+ export { App as default };
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation.config.js
new file mode 100644
index 000000000000..58befe4f35e3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation.config.js
@@ -0,0 +1,28 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/component-annotation.config.js";
+import { babel } from "@rollup/plugin-babel";
+import resolve from "@rollup/plugin-node-resolve";
+
+const RESOLVABLE_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx"];
+
+export default defineConfig({
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ file: "out/component-annotation/app.js",
+ },
+ plugins: [
+ resolve({
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ sentryRollupPlugin(sentryConfig),
+ babel({
+ babelHelpers: "bundled",
+ presets: [["@babel/preset-react", { runtime: "automatic" }]],
+ extensions: RESOLVABLE_EXTENSIONS,
+ }),
+ ],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation.test.ts
new file mode 100644
index 000000000000..387beb7fda07
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/component-annotation.test.ts
@@ -0,0 +1,33 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { jsx, jsxs } from 'react/jsx-runtime';
+
+ function ComponentA() {
+ return /*#__PURE__*/jsx("span", {
+ "data-sentry-component": "ComponentA",
+ "data-sentry-source-file": "component-a.jsx",
+ children: "Component A"
+ });
+ }
+
+ function App() {
+ return /*#__PURE__*/jsxs("span", {
+ "data-sentry-component": "App",
+ "data-sentry-source-file": "app.jsx",
+ children: [/*#__PURE__*/jsx(ComponentA, {
+ "data-sentry-element": "ComponentA",
+ "data-sentry-source-file": "app.jsx"
+ }), ";"]
+ });
+ }
+
+ export { App as default };
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugid-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugid-disabled.config.js
new file mode 100644
index 000000000000..557debb33d77
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugid-disabled.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/debugid-disabled.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/debugid-disabled/basic.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugid-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugid-disabled.test.ts
new file mode 100644
index 000000000000..4385c076de7b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugid-disabled.test.ts
@@ -0,0 +1,15 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ "basic.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":"AAAA;AACA,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugids-already-injected.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugids-already-injected.config.js
new file mode 100644
index 000000000000..e256c534bef5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugids-already-injected.config.js
@@ -0,0 +1,13 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/debugids-already-injected.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/debugids-already-injected/basic.js",
+ sourcemap: true,
+ sourcemapDebugIds: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugids-already-injected.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugids-already-injected.test.ts
new file mode 100644
index 000000000000..de92ed454402
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/debugids-already-injected.test.ts
@@ -0,0 +1,20 @@
+import { expect } from "vitest";
+import { readAllFiles } from "../utils";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, createTempDir }) => {
+ const tempDir = createTempDir();
+
+ runBundler({ SENTRY_TEST_OVERRIDE_TEMP_DIR: tempDir });
+ const files = readAllFiles(tempDir);
+ expect(files).toMatchInlineSnapshot(`
+ {
+ "252e0338-8927-4f52-bd57-188131defd0f-0.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ //# debugId=00000000-0000-0000-0000-000000000000
+ //# sourceMappingURL=basic.js.map
+ ",
+ "252e0338-8927-4f52-bd57-188131defd0f-0.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":"AAAA,CAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA,IAAA,CAAA,IAAA,CAAA,EAAA,CAAA;scACA,OAAO,CAAC,GAAG,CAAC,CAAA,KAAA,CAAA,KAAA,CAAa,CAAC","debugId":"252e0338-8927-4f52-bd57-188131defd0f","debug_id":"252e0338-8927-4f52-bd57-188131defd0f"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/dont-mess-up-user-code.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/dont-mess-up-user-code.config.js
new file mode 100644
index 000000000000..20c07ae03669
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/dont-mess-up-user-code.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/dont-mess-up-user-code.config.js";
+
+export default defineConfig({
+ input: "src/index.js",
+ output: {
+ file: "out/dont-mess-up-user-code/index.js",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/dont-mess-up-user-code.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/dont-mess-up-user-code.test.ts
new file mode 100644
index 000000000000..42d6d3679b9c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/dont-mess-up-user-code.test.ts
@@ -0,0 +1,22 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"I am release!"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("I am import!");
+
+ // eslint-disable-next-line no-console
+ console.log("I am index!");
+ //# sourceMappingURL=index.js.map
+ ",
+ "index.js.map": "{"version":3,"file":"index.js","sources":["../../src/import.js","../../src/index.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"I am import!\\");\\n\\nexport {};\\n","import \\"./import\\";\\n\\n// eslint-disable-next-line no-console\\nconsole.log(\\"I am index!\\");\\n"],"names":[],"mappings":"AAAA,CAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA,IAAA,CAAA,IAAA,CAAA,EAAA,CAAA;2aACA,OAAO,CAAC,GAAG,CAAC,CAAA,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAc,CAAC;;ACC3B,CAAA,CAAA,CAAA,MAAA,CAAA,OAAA,CAAA,IAAA,CAAA,IAAA,CAAA,EAAA,CAAA;AACA,OAAO,CAAC,GAAG,CAAC,CAAA,CAAA,CAAA,EAAA,CAAA,KAAA,CAAA,CAAa,CAAC"}",
+ }
+ `);
+
+ const output = runFileInNode("index.js");
+ expect(output).toContain("I am import!");
+ expect(output).toContain("I am index!");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/errorhandling.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/errorhandling.config.js
new file mode 100644
index 000000000000..8b08902e7940
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/errorhandling.config.js
@@ -0,0 +1,15 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { getErrorHandlingConfig } from "../configs/errorhandling.config.js";
+
+const FAKE_SENTRY_PORT = process.env.FAKE_SENTRY_PORT || "9876";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/errorhandling/basic.js",
+ format: "cjs",
+ sourcemap: true,
+ },
+ plugins: [sentryRollupPlugin(getErrorHandlingConfig(FAKE_SENTRY_PORT))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/errorhandling.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/errorhandling.test.ts
new file mode 100644
index 000000000000..f2672a372553
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/errorhandling.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { withFakeSentryServer } from "../utils";
+
+test(import.meta.url, async ({ runBundler }) => {
+ await withFakeSentryServer((port) => {
+ // Run bundler with fake server - should succeed despite server errors
+ runBundler({
+ FAKE_SENTRY_PORT: port,
+ SENTRY_HTTP_MAX_RETRIES: "1", // Only retry once to avoid timeout
+ });
+
+ // If we get here, the build succeeded (didn't throw)
+ expect(true).toBe(true);
+ });
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/module-metadata.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/module-metadata.config.js
new file mode 100644
index 000000000000..b916797d11e4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/module-metadata.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/module-metadata.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/module-metadata/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/module-metadata.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/module-metadata.test.ts
new file mode 100644
index 000000000000..b4acc59902a5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/module-metadata.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=function(e){for(var n=1;n {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();function add(a, b) {
+ return a + b;
+ }
+
+ export { add as a };
+ ",
+ "entry1.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { a as add } from './common.js';
+
+ console.log(add(1, 2));
+ ",
+ "entry2.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { a as add } from './common.js';
+
+ console.log(add(2, 4));
+ ",
+ }
+ `);
+
+ const output1 = runFileInNode("entry1.js");
+ expect(output1).toMatchInlineSnapshot(`
+ "3
+ "
+ `);
+ const output2 = runFileInNode("entry2.js");
+ expect(output2).toMatchInlineSnapshot(`
+ "6
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json
new file mode 100644
index 000000000000..0b58ab96ec63
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "rollup4-integration-tests",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0",
+ "react": "19.2.4",
+ "rollup": "4.59.0",
+ "@rollup/plugin-babel": "6.0.4",
+ "@rollup/plugin-node-resolve": "16.0.3"
+ },
+ "pnpm": {
+ "overrides": {
+ "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins.tgz"
+ },
+ "patchedDependencies": {
+ "@sentry/cli": "../patches/@sentry__cli.patch"
+ }
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/query-param.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/query-param.config.js
new file mode 100644
index 000000000000..94711b1592be
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/query-param.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/query-param.config.js";
+
+export default defineConfig({
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/query-param",
+ chunkFileNames: "[name].js?seP58q4g",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/query-param.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/query-param.test.ts
new file mode 100644
index 000000000000..d65cb2e349da
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/query-param.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, ctx }) => {
+ if (process.platform === "win32") {
+ ctx.skip("Query params do not work in paths on Windows");
+ return;
+ }
+
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js?seP58q4g": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();function add(a, b) {
+ return a + b;
+ }
+
+ export { add as a };
+ ",
+ "entry1.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { a as add } from './common.js?seP58q4g';
+
+ console.log(add(1, 2));
+ ",
+ "entry2.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();import { a as add } from './common.js?seP58q4g';
+
+ console.log(add(2, 4));
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-disabled.config.js
new file mode 100644
index 000000000000..defa3610e956
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-disabled.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/release-disabled.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/release-disabled/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-disabled.test.ts
new file mode 100644
index 000000000000..b8e6f6b9d32f
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-disabled.test.ts
@@ -0,0 +1,17 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-value-with-quotes.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-value-with-quotes.config.js
new file mode 100644
index 000000000000..92d51d0379e3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-value-with-quotes.config.js
@@ -0,0 +1,12 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/release-value-with-quotes.config.js";
+
+export default defineConfig({
+ input: "src/release-value-with-quotes.js",
+ output: {
+ file: "out/release-value-with-quotes/bundle.js",
+ format: "iife",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-value-with-quotes.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-value-with-quotes.test.ts
new file mode 100644
index 000000000000..314a41b865f6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/release-value-with-quotes.test.ts
@@ -0,0 +1,8 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, runFileInNode }) => {
+ runBundler();
+ const output = runFileInNode("bundle.js");
+ expect(output.trimEnd()).toBe('"i am a dangerous release value because I contain a \\""');
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/app.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/app.jsx
new file mode 100644
index 000000000000..614d38c834aa
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/app.jsx
@@ -0,0 +1,9 @@
+import { ComponentA } from "./component-a";
+
+export default function App() {
+ return (
+
+ ;
+
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/basic.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/basic.js
new file mode 100644
index 000000000000..7ef02afbd244
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/basic.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-console
+console.log("hello world");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/bundle.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/bundle.js
new file mode 100644
index 000000000000..0d62e559347d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/bundle.js
@@ -0,0 +1,10 @@
+console.log(
+ JSON.stringify({
+ debug: __SENTRY_DEBUG__ ? "a" : "b",
+ trace: __SENTRY_TRACING__ ? "a" : "b",
+ replayCanvas: __RRWEB_EXCLUDE_CANVAS__ ? "a" : "b",
+ replayIframe: __RRWEB_EXCLUDE_IFRAME__ ? "a" : "b",
+ replayShadowDom: __RRWEB_EXCLUDE_SHADOW_DOM__ ? "a" : "b",
+ replayWorker: __SENTRY_EXCLUDE_REPLAY_WORKER__ ? "a" : "b",
+ })
+);
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/common.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/common.js
new file mode 100644
index 000000000000..7d658310b0d9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/common.js
@@ -0,0 +1,3 @@
+export function add(a, b) {
+ return a + b;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/component-a.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/component-a.jsx
new file mode 100644
index 000000000000..5d57ab2215cd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/component-a.jsx
@@ -0,0 +1,3 @@
+export function ComponentA() {
+ return Component A;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/entry1.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/entry1.js
new file mode 100644
index 000000000000..480816663453
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/entry1.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(1, 2));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/entry2.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/entry2.js
new file mode 100644
index 000000000000..f64af1ea7ae5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/entry2.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(2, 4));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/import.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/import.js
new file mode 100644
index 000000000000..733d5d48f137
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/import.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line no-console
+console.log("I am import!");
+
+export {};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/index.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/index.js
new file mode 100644
index 000000000000..719d8428cac6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/index.js
@@ -0,0 +1,4 @@
+import "./import";
+
+// eslint-disable-next-line no-console
+console.log("I am index!");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/release-value-with-quotes.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/release-value-with-quotes.js
new file mode 100644
index 000000000000..aa73bfa8be00
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/src/release-value-with-quotes.js
@@ -0,0 +1,3 @@
+// Simply output the metadata to the console so it can be checked in a test
+// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-member-access
+console.log(JSON.stringify(global.SENTRY_RELEASE.id));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/telemetry.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/telemetry.config.js
new file mode 100644
index 000000000000..5467793d80df
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/telemetry.config.js
@@ -0,0 +1,11 @@
+import { sentryRollupPlugin } from "@sentry/bundler-plugins/rollup";
+import { defineConfig } from "rollup";
+import { sentryConfig } from "../configs/telemetry.config.js";
+
+export default defineConfig({
+ input: "src/basic.js",
+ output: {
+ file: "out/telemetry/basic.js",
+ },
+ plugins: [sentryRollupPlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/telemetry.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/telemetry.test.ts
new file mode 100644
index 000000000000..bb27238e08b9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/telemetry.test.ts
@@ -0,0 +1,20 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "// eslint-disable-next-line no-console
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();console.log("hello world");
+ ",
+ "sentry-telemetry.json": "[{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":true,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"ok","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ [{"event_id":"UUID","sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"},"trace":{"environment":"production","release":"PLUGIN_VERSION","public_key":"UUID","trace_id":"UUID","org_id":"1","transaction":"Sentry Bundler Plugin execution","sampled":"true","sample_rand":"SAMPLE_RAND","sample_rate":"1"}},[[{"type":"transaction"},{"contexts":{"trace":{"span_id":"SHORT_UUID","trace_id":"UUID","data":{"sentry.origin":"manual","sentry.source":"custom","sentry.sample_rate":1},"origin":"manual"},"runtime":{"name":"node","version":"NODE_VERSION"}},"spans":[],"start_timestamp":START_TIMESTAMP,"timestamp":TIMESTAMP,"transaction":"Sentry Bundler Plugin execution","type":"transaction","transaction_info":{"source":"custom"},"platform":"PLATFORM","event_id":"UUID","environment":"production","release":"PLUGIN_VERSION","tags":{"upload-legacy-sourcemaps":false,"module-metadata":false,"inject-build-information":false,"set-commits":"auto","finalize-release":true,"deploy-options":false,"custom-error-handler":false,"sourcemaps-assets":false,"delete-after-upload":false,"sourcemaps-disabled":false,"react-annotate":false,"node":"NODE_VERSION","platform":"PLATFORM","meta-framework":"none","application-key-set":false,"ci":true,"project":"undefined","bundler":"rollup","bundler-major-version":"4"},"user":{},"sdk":{"name":"sentry.javascript.node","version":"10.56.0","integrations":[],"packages":[{"name":"npm:@sentry/node","version":"10.56.0"}]}}]]],
+ [{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":false,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"exited","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/utils.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/utils.ts
new file mode 100644
index 000000000000..f93617917192
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/utils.ts
@@ -0,0 +1,59 @@
+import { basename, dirname, join } from "node:path";
+import { createTempDir, readAllFiles, runBundler } from "../utils";
+import { fileURLToPath } from "node:url";
+import { rmSync } from "node:fs";
+import type { TestContext } from "vitest";
+import { test as vitestTest } from "vitest";
+import { execSync } from "node:child_process";
+
+const cwd = dirname(fileURLToPath(import.meta.url));
+
+type TestCallback = (props: {
+ outDir: string;
+ runBundler: (env?: Record) => void;
+ readOutputFiles: () => Record;
+ runFileInNode: (file: string) => string;
+ createTempDir: () => string;
+ ctx: TestContext;
+}) => void | Promise;
+
+export function test(url: string, callback: TestCallback) {
+ const filePath = fileURLToPath(url);
+ const filename = basename(filePath);
+ const testName = filename.replace(/\.test\.ts$/, "");
+ const outDir = join(cwd, "out", testName);
+
+ // Clear the output directory before running the test
+ rmSync(outDir, { recursive: true, force: true });
+
+ // Detect CJS config files by test name suffix
+ const configExt = testName.endsWith("-cjs") ? ".config.cjs" : ".config.js";
+
+ vitestTest(`rollup v4 > ${testName}`, (ctx) =>
+ callback({
+ outDir,
+ runBundler: (env) =>
+ runBundler(
+ `pnpm rollup --config ${testName}${configExt}`,
+ {
+ cwd,
+ env: {
+ ...process.env,
+ ...env,
+ },
+ },
+ outDir
+ ),
+ readOutputFiles: () => readAllFiles(outDir),
+ runFileInNode: (file) => {
+ const fullPath = join(outDir, file);
+ return execSync(`node ${fullPath}`, {
+ cwd,
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
+ }).toString();
+ },
+ createTempDir: () => createTempDir(),
+ ctx,
+ })
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/utils.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/utils.ts
new file mode 100644
index 000000000000..dc19b9bf49dc
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/utils.ts
@@ -0,0 +1,164 @@
+import type { ExecSyncOptions } from "node:child_process";
+import { execSync } from "node:child_process";
+import { randomUUID } from "node:crypto";
+import { mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+
+const DEBUG = !!process.env["DEBUG"];
+const CURRENT_SHA = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
+
+type SourceMap = {
+ sources: string[];
+ sourcesContent: string[];
+};
+
+export function runBundler(command: string, opt: ExecSyncOptions, outDir?: string): void {
+ if (outDir) {
+ // We've patched the sentry-cli helper to write the args to a file instead of actually executing the command
+ opt.env = { ...opt.env, SENTRY_TEST_OUT_DIR: outDir };
+ }
+
+ execSync(command, { stdio: DEBUG ? "inherit" : "ignore", ...opt });
+}
+
+export function readAllFiles(
+ directory: string,
+ customReplacer?: (content: string) => string
+): Record {
+ const files: Record = {};
+
+ function readDirRecursive(currentDir: string, relativePath = ""): void {
+ const entries = readdirSync(currentDir);
+
+ for (const entry of entries) {
+ const fullPath = join(currentDir, entry);
+ const stat = statSync(fullPath);
+ const relativeFilePath = relativePath ? join(relativePath, entry) : entry;
+
+ if (stat.isDirectory()) {
+ // Recursively read subdirectories
+ readDirRecursive(fullPath, relativeFilePath);
+ } else if (stat.isFile()) {
+ let contents = readFileSync(fullPath, "utf-8");
+ // We replace the current SHA with a placeholder to make snapshots deterministic
+ contents = contents
+ .replaceAll(CURRENT_SHA, "CURRENT_SHA")
+ .replaceAll(/"nodeVersion":\d+/g, `"nodeVersion":"NODE_VERSION"`)
+ .replaceAll(/"nodeVersion": \d+/g, `"nodeVersion":"NODE_VERSION"`)
+ .replaceAll(/nodeVersion:\d+/g, `nodeVersion:"NODE_VERSION"`)
+ .replaceAll(/nodeVersion: \d+/g, `nodeVersion:"NODE_VERSION"`)
+ .replaceAll(process.cwd().replace(/\\/g, "/"), "");
+
+ if (customReplacer) {
+ contents = customReplacer(contents);
+ }
+
+ // Normalize Windows stuff in .map paths
+ if (entry.endsWith(".map")) {
+ const map = JSON.parse(contents) as SourceMap;
+ map.sources = map.sources.map((c) => c.replace(/\\/g, "/"));
+ map.sourcesContent = map.sourcesContent.map((c) => c.replace(/\r\n/g, "\n"));
+ contents = JSON.stringify(map);
+ } else if (entry === "sentry-cli-mock.json") {
+ // Remove the temporary directory path too
+ contents = contents.replace(
+ /"[^"]+sentry-bundler-plugin-upload.+?",/g,
+ '"sentry-bundler-plugin-upload-path",'
+ );
+ } else if (entry === "sentry-telemetry.json") {
+ // Remove the temporary directory path too
+ contents = contents
+ .replace(
+ /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/g,
+ "TIMESTAMP"
+ )
+ .replace(/[a-f0-9]{32}/g, "UUID")
+ .replace(/"[a-f0-9]{16}"/g, '"SHORT_UUID"')
+ .replaceAll(process.version, "NODE_VERSION")
+ .replace(/"ci":false/g, '"ci":true')
+ .replace(/"platform":".+?"/g, '"platform":"PLATFORM"')
+ .replace(/"duration":[\d.]+/g, '"duration":DURATION')
+ .replace(/"start_timestamp":[\d.]+/g, '"start_timestamp":START_TIMESTAMP')
+ .replace(/"timestamp":[\d.]+/g, '"timestamp":TIMESTAMP')
+ .replace(/"release":"[\d.]+"/g, '"release":"PLUGIN_VERSION"')
+ .replace(/"sample_rand":"\d.?\d*"/g, '"sample_rand":"SAMPLE_RAND"');
+ } else {
+ // Normalize Windows line endings for cross-platform snapshots
+ contents = contents.replace(/\r\n/g, "\n");
+ // Normalize debug IDs to make snapshots deterministic across environments
+ contents = contents.replace(
+ /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi,
+ "00000000-0000-0000-0000-000000000000"
+ );
+ }
+ // Use forward slashes for consistent cross-platform keys
+ files[relativeFilePath.replace(/\\/g, "/")] = contents;
+ }
+ }
+ }
+
+ readDirRecursive(directory);
+ return files;
+}
+
+const tempDirs: string[] = [];
+
+export function createTempDir(): string {
+ const tempDir = mkdtempSync(join(tmpdir(), `sentry-bundler-plugin-${randomUUID()}`));
+ tempDirs.push(tempDir);
+ return tempDir;
+}
+
+process.on("exit", () => {
+ for (const dir of tempDirs) {
+ rmSync(dir, { recursive: true, force: true });
+ }
+});
+
+/**
+ * Runs a callback with a fake Sentry server running on an auto-allocated port.
+ * The server returns 503 errors for all requests.
+ * Automatically starts and stops the server.
+ * The allocated port is passed to the callback.
+ */
+export async function withFakeSentryServer(
+ callback: (port: string) => void | Promise
+): Promise {
+ const { createServer } = await import("node:http");
+
+ const server = createServer((req, res) => {
+ if (DEBUG) {
+ // eslint-disable-next-line no-console
+ console.log("[FAKE SENTRY] incoming request", req.url);
+ }
+ res.statusCode = 503;
+ res.end("Error: Sentry unreachable");
+ });
+
+ // Listen on port 0 to get an auto-allocated port
+ await new Promise((resolve) => {
+ server.listen(0, () => {
+ resolve();
+ });
+ });
+
+ const address = server.address();
+ if (!address || typeof address === "string") {
+ throw new Error("Failed to get server port");
+ }
+ const port = address.port.toString();
+
+ if (DEBUG) {
+ // eslint-disable-next-line no-console
+ console.log(`[FAKE SENTRY] running on http://localhost:${port}/`);
+ }
+
+ try {
+ await callback(port);
+ } finally {
+ await new Promise((resolve) => {
+ server.close(() => resolve());
+ });
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite-type-compat.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite-type-compat.test.ts
new file mode 100644
index 000000000000..a14e1381e406
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite-type-compat.test.ts
@@ -0,0 +1,124 @@
+import { describe, expect, it } from "vitest";
+import * as ts from "typescript";
+import { dirname, isAbsolute, join, normalize, relative } from "node:path";
+import { fileURLToPath } from "node:url";
+import { createRequire } from "node:module";
+
+const fixturesDir = fileURLToPath(new URL(".", import.meta.url));
+// Resolve against the built declaration files (the published type surface),
+// mirroring how the plugin's types are consumed by end users. Built by the
+// integration test setup step before this suite runs.
+const pluginSourceFile = fileURLToPath(
+ new URL("../../../packages/bundler-plugins/dist/types/vite/index.d.ts", import.meta.url)
+);
+const pluginViteTypesFixtureDir = join(fixturesDir, "vite6");
+
+const configSource = `
+import { defineConfig } from "vite";
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+
+export default defineConfig({
+ plugins: [sentryVitePlugin()],
+});
+`;
+
+function assertFixtureViteVersion(fixtureDir: string, expectedMajor: string): void {
+ const requireFromFixture = createRequire(join(fixtureDir, "package.json"));
+ const vitePackageJsonPath = requireFromFixture.resolve("vite/package.json");
+ const relativeVitePackageJsonPath = relative(fixtureDir, vitePackageJsonPath);
+
+ expect(isAbsolute(relativeVitePackageJsonPath)).toBe(false);
+ expect(relativeVitePackageJsonPath.startsWith("..")).toBe(false);
+ expect(relativeVitePackageJsonPath.split(/[\\/]/)[0]).toBe("node_modules");
+
+ const vitePackageJson = requireFromFixture("vite/package.json") as { version: string };
+ expect(vitePackageJson.version.split(".")[0]).toBe(expectedMajor);
+}
+
+function getDiagnosticsForFixture(fixtureName: string, expectedMajor: string): string[] {
+ const fixtureDir = join(fixturesDir, fixtureName);
+ const fileName = join(fixtureDir, "sentry-vite-plugin-type-compat.mts");
+ const isVirtualConfigFile = (path: string) => normalize(path) === normalize(fileName);
+
+ assertFixtureViteVersion(fixtureDir, expectedMajor);
+ assertFixtureViteVersion(pluginViteTypesFixtureDir, "6");
+
+ const compilerOptions: ts.CompilerOptions = {
+ esModuleInterop: true,
+ module: ts.ModuleKind.Node16,
+ moduleResolution: ts.ModuleResolutionKind.Node16,
+ noEmit: true,
+ skipLibCheck: true,
+ strict: true,
+ target: ts.ScriptTarget.ES2020,
+ types: ["node"],
+ };
+
+ const host = ts.createCompilerHost(compilerOptions);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ const getSourceFile = host.getSourceFile;
+
+ host.fileExists = (path) => isVirtualConfigFile(path) || ts.sys.fileExists(path);
+ host.readFile = (path) => (isVirtualConfigFile(path) ? configSource : ts.sys.readFile(path));
+ host.getSourceFile = (path, languageVersion, onError, shouldCreateNewSourceFile) =>
+ isVirtualConfigFile(path)
+ ? ts.createSourceFile(path, configSource, languageVersion, true)
+ : getSourceFile(path, languageVersion, onError, shouldCreateNewSourceFile);
+ host.resolveModuleNames = (moduleNames, containingFile) =>
+ moduleNames.map((moduleName) => {
+ if (moduleName === "@sentry/bundler-plugins/vite") {
+ return {
+ resolvedFileName: pluginSourceFile,
+ extension: ts.Extension.Dts,
+ };
+ }
+
+ // The consolidated plugin's declaration files use relative directory
+ // imports (e.g. `../rollup`, `../core`). Node16 module resolution does not
+ // support directory-index resolution for relative specifiers, so resolve
+ // those against the built type tree ourselves with an `/index.d.ts` fallback.
+ if (moduleName.startsWith(".")) {
+ const base = join(dirname(containingFile), moduleName);
+ const candidates = [base, `${base}.d.ts`, join(base, "index.d.ts")];
+ const resolvedFileName = candidates.find(candidate => ts.sys.fileExists(candidate));
+ if (resolvedFileName) {
+ return {
+ resolvedFileName,
+ extension: ts.Extension.Dts,
+ };
+ }
+ }
+
+ const resolutionContainingFile =
+ moduleName === "vite" && containingFile === pluginSourceFile
+ ? join(pluginViteTypesFixtureDir, "sentry-vite-plugin-type-compat.mts")
+ : containingFile;
+
+ return ts.resolveModuleName(
+ moduleName,
+ resolutionContainingFile,
+ compilerOptions,
+ ts.sys,
+ undefined,
+ undefined,
+ ts.ModuleKind.ESNext
+ ).resolvedModule;
+ });
+
+ const program = ts.createProgram([fileName], compilerOptions, host);
+
+ return ts
+ .getPreEmitDiagnostics(program)
+ .map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"))
+ .filter((message) => !message.includes("The current file is a CommonJS module"));
+}
+
+describe("sentryVitePlugin type compatibility", () => {
+ it.each([
+ ["vite6", "6"],
+ ["vite7", "7"],
+ ["vite8", "8"],
+ ])("is compatible with %s defineConfig plugins", (fixtureName, expectedMajor) => {
+ expect(getDiagnosticsForFixture(fixtureName, expectedMajor)).toEqual([]);
+ });
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion-promise.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion-promise.config.ts
new file mode 100644
index 000000000000..c99b4ce44dab
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion-promise.config.ts
@@ -0,0 +1,20 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { getSentryConfig } from "../configs/after-upload-deletion-promise.config.js";
+
+const outDir = "out/after-upload-deletion-promise";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: outDir,
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(getSentryConfig(outDir))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion-promise.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion-promise.test.ts
new file mode 100644
index 000000000000..92c6bd553f5a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion-promise.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+
+test(import.meta.url, ({ runBundler, outDir, runFileInNode }) => {
+ runBundler();
+
+ // Verify the JS file exists and works
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+
+ // Verify the sourcemap was deleted (by the Promise)
+ const sourcemapPath = join(outDir, "basic.js.map");
+ expect(existsSync(sourcemapPath)).toBe(false);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion.config.ts
new file mode 100644
index 000000000000..6cf383546533
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/after-upload-deletion.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/after-upload-deletion",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion.test.ts
new file mode 100644
index 000000000000..dcb7e717f69f
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/after-upload-deletion.test.ts
@@ -0,0 +1,25 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/application-key.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/application-key.config.ts
new file mode 100644
index 000000000000..e57d70e7008b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/application-key.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/application-key.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/application-key",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/application-key.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/application-key.test.ts
new file mode 100644
index 000000000000..971a984e7e74
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/application-key.test.ts
@@ -0,0 +1,30 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = function(e2) {
+ for (var n2 = 1; n2 < arguments.length; n2++) {
+ var a = arguments[n2];
+ if (null != a)
+ for (var t in a)
+ a.hasOwnProperty(t) && (e2[t] = a[t]);
+ }
+ return e2;
+ }({}, e._sentryModuleMetadata[new e.Error().stack], { "_sentryBundlerPluginAppKey:1234567890abcdef": true });
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.config.cjs b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.config.cjs
new file mode 100644
index 000000000000..cd61918c8b68
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.config.cjs
@@ -0,0 +1,17 @@
+const { sentryVitePlugin } = require("@sentry/bundler-plugins/vite");
+const { defineConfig } = require("vite");
+const { sentryConfig } = require("../configs/basic.config.cjs");
+
+module.exports = defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic-cjs",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.test.ts
new file mode 100644
index 000000000000..10e0aad0e5e9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-release-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-release-disabled.config.ts
new file mode 100644
index 000000000000..81467f1d1722
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-release-disabled.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/basic-release-disabled.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic-release-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-release-disabled.test.ts
new file mode 100644
index 000000000000..abe9dc12208a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-release-disabled.test.ts
@@ -0,0 +1,20 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-sourcemaps.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-sourcemaps.config.ts
new file mode 100644
index 000000000000..cb5be073b9db
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-sourcemaps.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/basic-sourcemaps.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic-sourcemaps",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-sourcemaps.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-sourcemaps.test.ts
new file mode 100644
index 000000000000..423bc333ea17
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-sourcemaps.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ "basic.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,aAAa;"}",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.config.ts
new file mode 100644
index 000000000000..5347b599b3e6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/basic.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.test.ts
new file mode 100644
index 000000000000..10e0aad0e5e9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/build-info.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/build-info.config.ts
new file mode 100644
index 000000000000..6217e404d978
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/build-info.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/build-info.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/build-info",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/build-info.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/build-info.test.ts
new file mode 100644
index 000000000000..48616bec5281
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/build-info.test.ts
@@ -0,0 +1,22 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "build-information-injection-test" };
+ e.SENTRY_BUILD_INFO = { "deps": ["@sentry/bundler-plugins", "@vitejs/plugin-react", "react", "vite"], "depsVersions": { "react": 19, "vite": 4 }, "nodeVersion":"NODE_VERSION" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/bundle-size-optimizations.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/bundle-size-optimizations.config.ts
new file mode 100644
index 000000000000..62595fc43311
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/bundle-size-optimizations.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/bundle-size-optimizations.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/bundle.js",
+ output: {
+ dir: "out/bundle-size-optimizations",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/bundle-size-optimizations.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/bundle-size-optimizations.test.ts
new file mode 100644
index 000000000000..c0292d9d8264
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/bundle-size-optimizations.test.ts
@@ -0,0 +1,36 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "bundle.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log(
+ JSON.stringify({
+ debug: "b",
+ trace: "b",
+ replayCanvas: "a",
+ replayIframe: "a",
+ replayShadowDom: "a",
+ replayWorker: "a"
+ })
+ );
+ ",
+ }
+ `);
+
+ const output = runFileInNode("bundle.js");
+ expect(output).toMatchInlineSnapshot(`
+ "{"debug":"b","trace":"b","replayCanvas":"a","replayIframe":"a","replayShadowDom":"a","replayWorker":"a"}
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-disabled.config.ts
new file mode 100644
index 000000000000..75a8f88dc484
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-disabled.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/component-annotation-disabled.config.js";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ dir: "out/component-annotation-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [react({ jsxRuntime: "automatic" }), sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-disabled.test.ts
new file mode 100644
index 000000000000..d08e2db52ad4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-disabled.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ import { jsx, jsxs } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-runtime.js";
+ function ComponentA() {
+ return /* @__PURE__ */ jsx("span", { children: "Component A" });
+ }
+ function App() {
+ return /* @__PURE__ */ jsxs("span", { children: [
+ /* @__PURE__ */ jsx(ComponentA, {}),
+ ";"
+ ] });
+ }
+ console.log(App());
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-next.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-next.config.ts
new file mode 100644
index 000000000000..67de4e598197
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-next.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/component-annotation-next.config.js";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ dir: "out/component-annotation-next",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [react({ jsxRuntime: "automatic" }), sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-next.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-next.test.ts
new file mode 100644
index 000000000000..13f07b4f1490
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation-next.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ import { jsx, jsxs } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-runtime.js";
+ function ComponentA() {
+ return /* @__PURE__ */ jsx("span", { "data-sentry-component": "ComponentA", children: "Component A" });
+ }
+ function App() {
+ return /* @__PURE__ */ jsxs("span", { "data-sentry-component": "App", children: [
+ /* @__PURE__ */ jsx(ComponentA, {}),
+ ";"
+ ] });
+ }
+ console.log(App());
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation.config.ts
new file mode 100644
index 000000000000..a338b95ef50c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/component-annotation.config.js";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ dir: "out/component-annotation",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [react(), sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation.test.ts
new file mode 100644
index 000000000000..d1ff5500914e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/component-annotation.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ import { jsx, jsxs } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-runtime.js";
+ function ComponentA() {
+ return /* @__PURE__ */ jsx("span", { "data-sentry-component": "ComponentA", "data-sentry-source-file": "component-a.jsx", children: "Component A" });
+ }
+ function App() {
+ return /* @__PURE__ */ jsxs("span", { "data-sentry-component": "App", "data-sentry-source-file": "app.jsx", children: [
+ /* @__PURE__ */ jsx(ComponentA, { "data-sentry-element": "ComponentA", "data-sentry-source-file": "app.jsx" }),
+ ";"
+ ] });
+ }
+ console.log(App());
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/debugid-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/debugid-disabled.config.ts
new file mode 100644
index 000000000000..067eb176d349
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/debugid-disabled.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/debugid-disabled.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/debugid-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/debugid-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/debugid-disabled.test.ts
new file mode 100644
index 000000000000..1c99d55fbec9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/debugid-disabled.test.ts
@@ -0,0 +1,14 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ "basic.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":"AACA,QAAQ,IAAI,aAAa;"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/dont-mess-up-user-code.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/dont-mess-up-user-code.config.ts
new file mode 100644
index 000000000000..18ffd2ba79fa
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/dont-mess-up-user-code.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/dont-mess-up-user-code.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/index.js",
+ output: {
+ dir: "out/dont-mess-up-user-code",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/dont-mess-up-user-code.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/dont-mess-up-user-code.test.ts
new file mode 100644
index 000000000000..014726a3a510
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/dont-mess-up-user-code.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "I am release!" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("I am import!");
+ console.log("I am index!");
+ //# sourceMappingURL=index.js.map
+ ",
+ "index.js.map": "{"version":3,"file":"index.js","sources":["../../src/import.js","../../src/index.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"I am import!\\");\\n\\nexport {};\\n","import \\"./import\\";\\n\\n// eslint-disable-next-line no-console\\nconsole.log(\\"I am index!\\");\\n"],"names":[],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,cAAc;ACE1B,QAAQ,IAAI,aAAa;"}",
+ }
+ `);
+
+ const output = runFileInNode("index.js");
+ expect(output).toContain("I am import!");
+ expect(output).toContain("I am index!");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/errorhandling.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/errorhandling.config.ts
new file mode 100644
index 000000000000..192f7c5db647
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/errorhandling.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { getErrorHandlingConfig } from "../configs/errorhandling.config.js";
+
+const FAKE_SENTRY_PORT = process.env.FAKE_SENTRY_PORT || "9876";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/errorhandling",
+ entryFileNames: "[name].js",
+ format: "cjs",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(getErrorHandlingConfig(FAKE_SENTRY_PORT))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/errorhandling.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/errorhandling.test.ts
new file mode 100644
index 000000000000..f2672a372553
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/errorhandling.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { withFakeSentryServer } from "../utils";
+
+test(import.meta.url, async ({ runBundler }) => {
+ await withFakeSentryServer((port) => {
+ // Run bundler with fake server - should succeed despite server errors
+ runBundler({
+ FAKE_SENTRY_PORT: port,
+ SENTRY_HTTP_MAX_RETRIES: "1", // Only retry once to avoid timeout
+ });
+
+ // If we get here, the build succeeded (didn't throw)
+ expect(true).toBe(true);
+ });
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/module-metadata.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/module-metadata.config.ts
new file mode 100644
index 000000000000..59433418bb9d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/module-metadata.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/module-metadata.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/module-metadata",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/module-metadata.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/module-metadata.test.ts
new file mode 100644
index 000000000000..57bcc9864223
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/module-metadata.test.ts
@@ -0,0 +1,33 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = function(e2) {
+ for (var n2 = 1; n2 < arguments.length; n2++) {
+ var a = arguments[n2];
+ if (null != a)
+ for (var t in a)
+ a.hasOwnProperty(t) && (e2[t] = a[t]);
+ }
+ return e2;
+ }({}, e._sentryModuleMetadata[new e.Error().stack], { "something": "value", "another": 999 });
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/multiple-entry-points.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/multiple-entry-points.config.ts
new file mode 100644
index 000000000000..da0b49d90362
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/multiple-entry-points.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/multiple-entry-points.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/multiple-entry-points",
+ chunkFileNames: "[name].js",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/multiple-entry-points.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/multiple-entry-points.test.ts
new file mode 100644
index 000000000000..f2738df8a503
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/multiple-entry-points.test.ts
@@ -0,0 +1,58 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ function add(a, b) {
+ return a + b;
+ }
+ export {
+ add as a
+ };
+ ",
+ "entry1.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ import { a as add } from "./common.js";
+ console.log(add(1, 2));
+ ",
+ "entry2.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ import { a as add } from "./common.js";
+ console.log(add(2, 4));
+ ",
+ }
+ `);
+
+ const output1 = runFileInNode("entry1.js");
+ expect(output1).toMatchInlineSnapshot(`
+ "3
+ "
+ `);
+ const output2 = runFileInNode("entry2.js");
+ expect(output2).toMatchInlineSnapshot(`
+ "6
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json
new file mode 100644
index 000000000000..b82a2237478f
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "vite4-integration-tests",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0",
+ "react": "19.2.4",
+ "vite": "4.5.14",
+ "@vitejs/plugin-react": "5.2.0"
+ },
+ "pnpm": {
+ "overrides": {
+ "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins.tgz"
+ },
+ "patchedDependencies": {
+ "@sentry/cli": "../patches/@sentry__cli.patch"
+ }
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/query-param.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/query-param.config.ts
new file mode 100644
index 000000000000..185106f97cff
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/query-param.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/query-param.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/query-param",
+ chunkFileNames: "[name].js?seP58q4g",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/query-param.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/query-param.test.ts
new file mode 100644
index 000000000000..f31483760d4b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/query-param.test.ts
@@ -0,0 +1,55 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, ctx }) => {
+ if (process.platform === "win32") {
+ ctx.skip("Query params do not work in paths on Windows");
+ return;
+ }
+
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js?seP58q4g": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ function add(a, b) {
+ return a + b;
+ }
+ export {
+ add as a
+ };
+ ",
+ "entry1.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ import { a as add } from "./common.js?seP58q4g";
+ console.log(add(1, 2));
+ ",
+ "entry2.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ import { a as add } from "./common.js?seP58q4g";
+ console.log(add(2, 4));
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.config.ts
new file mode 100644
index 000000000000..ab823129454d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/release-disabled.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/release-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.test.ts
new file mode 100644
index 000000000000..b8a755e78265
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.test.ts
@@ -0,0 +1,25 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-value-with-quotes.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-value-with-quotes.config.ts
new file mode 100644
index 000000000000..8a50f7f5416e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-value-with-quotes.config.ts
@@ -0,0 +1,17 @@
+import { defineConfig } from "vite";
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { sentryConfig } from "../configs/release-value-with-quotes.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ outDir: "./out/release-value-with-quotes",
+ rollupOptions: {
+ input: "./src/release-value-with-quotes.js",
+ output: {
+ entryFileNames: "bundle.js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-value-with-quotes.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-value-with-quotes.test.ts
new file mode 100644
index 000000000000..314a41b865f6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-value-with-quotes.test.ts
@@ -0,0 +1,8 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, runFileInNode }) => {
+ runBundler();
+ const output = runFileInNode("bundle.js");
+ expect(output.trimEnd()).toBe('"i am a dangerous release value because I contain a \\""');
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/app.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/app.jsx
new file mode 100644
index 000000000000..5be821b44e72
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/app.jsx
@@ -0,0 +1,11 @@
+import { ComponentA } from "./component-a";
+
+export default function App() {
+ return (
+
+ ;
+
+ );
+}
+
+console.log(App());
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/basic.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/basic.js
new file mode 100644
index 000000000000..7ef02afbd244
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/basic.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-console
+console.log("hello world");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/bundle.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/bundle.js
new file mode 100644
index 000000000000..0d62e559347d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/bundle.js
@@ -0,0 +1,10 @@
+console.log(
+ JSON.stringify({
+ debug: __SENTRY_DEBUG__ ? "a" : "b",
+ trace: __SENTRY_TRACING__ ? "a" : "b",
+ replayCanvas: __RRWEB_EXCLUDE_CANVAS__ ? "a" : "b",
+ replayIframe: __RRWEB_EXCLUDE_IFRAME__ ? "a" : "b",
+ replayShadowDom: __RRWEB_EXCLUDE_SHADOW_DOM__ ? "a" : "b",
+ replayWorker: __SENTRY_EXCLUDE_REPLAY_WORKER__ ? "a" : "b",
+ })
+);
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/common.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/common.js
new file mode 100644
index 000000000000..7d658310b0d9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/common.js
@@ -0,0 +1,3 @@
+export function add(a, b) {
+ return a + b;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/component-a.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/component-a.jsx
new file mode 100644
index 000000000000..5d57ab2215cd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/component-a.jsx
@@ -0,0 +1,3 @@
+export function ComponentA() {
+ return Component A;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/entry1.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/entry1.js
new file mode 100644
index 000000000000..480816663453
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/entry1.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(1, 2));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/entry2.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/entry2.js
new file mode 100644
index 000000000000..f64af1ea7ae5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/entry2.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(2, 4));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/import.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/import.js
new file mode 100644
index 000000000000..733d5d48f137
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/import.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line no-console
+console.log("I am import!");
+
+export {};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/index.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/index.js
new file mode 100644
index 000000000000..719d8428cac6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/index.js
@@ -0,0 +1,4 @@
+import "./import";
+
+// eslint-disable-next-line no-console
+console.log("I am index!");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/release-value-with-quotes.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/release-value-with-quotes.js
new file mode 100644
index 000000000000..aa73bfa8be00
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/release-value-with-quotes.js
@@ -0,0 +1,3 @@
+// Simply output the metadata to the console so it can be checked in a test
+// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-member-access
+console.log(JSON.stringify(global.SENTRY_RELEASE.id));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/shared-module.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/shared-module.js
new file mode 100644
index 000000000000..07182654d2da
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/shared-module.js
@@ -0,0 +1,10 @@
+// This is a shared module that is used by multiple HTML pages
+export function greet(name) {
+ // eslint-disable-next-line no-console
+ console.log(`Hello, ${String(name)}!`);
+}
+
+export const VERSION = "1.0.0";
+
+// Side effect: greet on load
+greet("World");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/vite-mpa-index.html b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/vite-mpa-index.html
new file mode 100644
index 000000000000..b18731ac6fab
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/vite-mpa-index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Index Page
+
+
+ Index Page - No Scripts
+
+
+
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/vite-mpa-page1.html b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/vite-mpa-page1.html
new file mode 100644
index 000000000000..44cb873cef18
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/vite-mpa-page1.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Page 1
+
+
+ Page 1 - With Shared Module
+
+
+
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/vite-mpa-page2.html b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/vite-mpa-page2.html
new file mode 100644
index 000000000000..6cac57fe9b22
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/src/vite-mpa-page2.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Page 2
+
+
+ Page 2 - With Shared Module
+
+
+
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/telemetry.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/telemetry.config.ts
new file mode 100644
index 000000000000..e55014fa5da8
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/telemetry.config.ts
@@ -0,0 +1,19 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/telemetry.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/telemetry",
+ entryFileNames: "[name].js",
+ },
+ },
+ // We already delete the directory and don't want our telemetry file to be deleted
+ emptyOutDir: false,
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/telemetry.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/telemetry.test.ts
new file mode 100644
index 000000000000..560fd499147c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/telemetry.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ console.log("hello world");
+ ",
+ "sentry-telemetry.json": "[{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":true,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"ok","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ [{"event_id":"UUID","sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"},"trace":{"environment":"production","release":"PLUGIN_VERSION","public_key":"UUID","trace_id":"UUID","org_id":"1","transaction":"Sentry Bundler Plugin execution","sampled":"true","sample_rand":"SAMPLE_RAND","sample_rate":"1"}},[[{"type":"transaction"},{"contexts":{"trace":{"span_id":"SHORT_UUID","trace_id":"UUID","data":{"sentry.origin":"manual","sentry.source":"custom","sentry.sample_rate":1},"origin":"manual"},"runtime":{"name":"node","version":"NODE_VERSION"}},"spans":[],"start_timestamp":START_TIMESTAMP,"timestamp":TIMESTAMP,"transaction":"Sentry Bundler Plugin execution","type":"transaction","transaction_info":{"source":"custom"},"platform":"PLATFORM","event_id":"UUID","environment":"production","release":"PLUGIN_VERSION","tags":{"upload-legacy-sourcemaps":false,"module-metadata":false,"inject-build-information":false,"set-commits":"auto","finalize-release":true,"deploy-options":false,"custom-error-handler":false,"sourcemaps-assets":false,"delete-after-upload":false,"sourcemaps-disabled":false,"react-annotate":false,"node":"NODE_VERSION","platform":"PLATFORM","meta-framework":"none","application-key-set":false,"ci":true,"project":"undefined","bundler":"vite","bundler-major-version":"4"},"user":{},"sdk":{"name":"sentry.javascript.node","version":"10.56.0","integrations":[],"packages":[{"name":"npm:@sentry/node","version":"10.56.0"}]}}]]],
+ [{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":false,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"exited","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/utils.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/utils.ts
new file mode 100644
index 000000000000..1b2845bca232
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/utils.ts
@@ -0,0 +1,60 @@
+import { basename, dirname, join } from "node:path";
+import { createTempDir, readAllFiles, runBundler } from "../utils";
+import { fileURLToPath } from "node:url";
+import { rmSync } from "node:fs";
+import type { TestContext } from "vitest";
+import { test as vitestTest } from "vitest";
+import { execSync } from "node:child_process";
+
+const cwd = dirname(fileURLToPath(import.meta.url));
+
+type TestCallback = (props: {
+ outDir: string;
+ runBundler: (env?: Record) => void;
+ readOutputFiles: () => Record;
+ runFileInNode: (file: string) => string;
+ createTempDir: () => string;
+ ctx: TestContext;
+}) => void | Promise;
+
+export function test(url: string, callback: TestCallback) {
+ const filePath = fileURLToPath(url);
+ const filename = basename(filePath);
+ const testName = filename.replace(/\.test\.ts$/, "");
+ const outDir = join(cwd, "out", testName);
+
+ // Clear the output directory before running the test
+ rmSync(outDir, { recursive: true, force: true });
+
+ // Detect CJS config files by test name suffix
+ const configExt = testName.endsWith("-cjs") ? ".config.cjs" : ".config.ts";
+
+ vitestTest(`Vite v4 > ${testName}`, (ctx) =>
+ callback({
+ outDir,
+ runBundler: (env) =>
+ runBundler(
+ `pnpm vite build --config ${testName}${configExt}`,
+ {
+ cwd,
+ env: {
+ ...process.env,
+ ...env,
+ NODE_ENV: "production",
+ },
+ },
+ outDir
+ ),
+ readOutputFiles: () => readAllFiles(outDir),
+ runFileInNode: (file) => {
+ const fullPath = join(outDir, file);
+ return execSync(`node ${fullPath}`, {
+ cwd,
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
+ }).toString();
+ },
+ createTempDir: () => createTempDir(),
+ ctx,
+ })
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/vite-mpa-extra-modules.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/vite-mpa-extra-modules.config.ts
new file mode 100644
index 000000000000..087194faa653
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/vite-mpa-extra-modules.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig } from "vite";
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { sentryConfig } from "../configs/vite-mpa-extra-modules.config.js";
+import { resolve } from "node:path";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ outDir: "./out/vite-mpa-extra-modules",
+ rollupOptions: {
+ input: {
+ index: resolve("./src/vite-mpa-index.html"),
+ page1: resolve("./src/vite-mpa-page1.html"),
+ page2: resolve("./src/vite-mpa-page2.html"),
+ },
+ output: {
+ chunkFileNames: "[name].js",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/vite-mpa-extra-modules.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/vite-mpa-extra-modules.test.ts
new file mode 100644
index 000000000000..148ecbe301c0
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/vite-mpa-extra-modules.test.ts
@@ -0,0 +1,108 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js.map": "{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}",
+ "page1.js.map": "{"version":3,"file":"page1.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}",
+ "page2.js.map": "{"version":3,"file":"page2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}",
+ "shared-module.js": "!function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ }();
+ (function polyfill() {
+ const relList = document.createElement("link").relList;
+ if (relList && relList.supports && relList.supports("modulepreload")) {
+ return;
+ }
+ for (const link of document.querySelectorAll('link[rel="modulepreload"]')) {
+ processPreload(link);
+ }
+ new MutationObserver((mutations) => {
+ for (const mutation of mutations) {
+ if (mutation.type !== "childList") {
+ continue;
+ }
+ for (const node of mutation.addedNodes) {
+ if (node.tagName === "LINK" && node.rel === "modulepreload")
+ processPreload(node);
+ }
+ }
+ }).observe(document, { childList: true, subtree: true });
+ function getFetchOpts(link) {
+ const fetchOpts = {};
+ if (link.integrity)
+ fetchOpts.integrity = link.integrity;
+ if (link.referrerPolicy)
+ fetchOpts.referrerPolicy = link.referrerPolicy;
+ if (link.crossOrigin === "use-credentials")
+ fetchOpts.credentials = "include";
+ else if (link.crossOrigin === "anonymous")
+ fetchOpts.credentials = "omit";
+ else
+ fetchOpts.credentials = "same-origin";
+ return fetchOpts;
+ }
+ function processPreload(link) {
+ if (link.ep)
+ return;
+ link.ep = true;
+ const fetchOpts = getFetchOpts(link);
+ fetch(link.href, fetchOpts);
+ }
+ })();
+ function greet(name) {
+ console.log(\`Hello, \${String(name)}!\`);
+ }
+ greet("World");
+ //# sourceMappingURL=shared-module.js.map
+ ",
+ "shared-module.js.map": "{"version":3,"file":"shared-module.js","sources":["../../src/shared-module.js"],"sourcesContent":["// This is a shared module that is used by multiple HTML pages\\nexport function greet(name) {\\n // eslint-disable-next-line no-console\\n console.log(\`Hello, \${String(name)}!\`);\\n}\\n\\nexport const VERSION = \\"1.0.0\\";\\n\\n// Side effect: greet on load\\ngreet(\\"World\\");\\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACO,SAAS,MAAM,MAAM;AAE1B,UAAQ,IAAI,UAAU,OAAO,IAAI,CAAC,GAAG;AACvC;AAKA,MAAM,OAAO;"}",
+ "src/vite-mpa-index.html": "
+
+
+
+ Index Page
+
+
+ Index Page - No Scripts
+
+
+
+ ",
+ "src/vite-mpa-page1.html": "
+
+
+
+ Page 1
+
+
+
+ Page 1 - With Shared Module
+
+
+
+ ",
+ "src/vite-mpa-page2.html": "
+
+
+
+ Page 2
+
+
+
+ Page 2 - With Shared Module
+
+
+
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json
new file mode 100644
index 000000000000..ad30c1523139
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "vite6-integration-tests",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0",
+ "vite": "6.4.1"
+ },
+ "pnpm": {
+ "overrides": {
+ "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins.tgz"
+ },
+ "patchedDependencies": {
+ "@sentry/cli": "../patches/@sentry__cli.patch"
+ }
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion-promise.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion-promise.config.ts
new file mode 100644
index 000000000000..c99b4ce44dab
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion-promise.config.ts
@@ -0,0 +1,20 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { getSentryConfig } from "../configs/after-upload-deletion-promise.config.js";
+
+const outDir = "out/after-upload-deletion-promise";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: outDir,
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(getSentryConfig(outDir))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion-promise.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion-promise.test.ts
new file mode 100644
index 000000000000..92c6bd553f5a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion-promise.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+
+test(import.meta.url, ({ runBundler, outDir, runFileInNode }) => {
+ runBundler();
+
+ // Verify the JS file exists and works
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+
+ // Verify the sourcemap was deleted (by the Promise)
+ const sourcemapPath = join(outDir, "basic.js.map");
+ expect(existsSync(sourcemapPath)).toBe(false);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion.config.ts
new file mode 100644
index 000000000000..6cf383546533
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/after-upload-deletion.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/after-upload-deletion",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion.test.ts
new file mode 100644
index 000000000000..1345b6fb3e7b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/after-upload-deletion.test.ts
@@ -0,0 +1,25 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/application-key.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/application-key.config.ts
new file mode 100644
index 000000000000..e57d70e7008b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/application-key.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/application-key.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/application-key",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/application-key.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/application-key.test.ts
new file mode 100644
index 000000000000..72768296ba91
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/application-key.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = (function(e2) {
+ for (var n2 = 1; n2 < arguments.length; n2++) {
+ var a = arguments[n2];
+ if (null != a) for (var t in a) a.hasOwnProperty(t) && (e2[t] = a[t]);
+ }
+ return e2;
+ })({}, e._sentryModuleMetadata[new e.Error().stack], { "_sentryBundlerPluginAppKey:1234567890abcdef": true });
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.config.cjs b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.config.cjs
new file mode 100644
index 000000000000..cd61918c8b68
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.config.cjs
@@ -0,0 +1,17 @@
+const { sentryVitePlugin } = require("@sentry/bundler-plugins/vite");
+const { defineConfig } = require("vite");
+const { sentryConfig } = require("../configs/basic.config.cjs");
+
+module.exports = defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic-cjs",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.test.ts
new file mode 100644
index 000000000000..4b334c167dbb
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-release-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-release-disabled.config.ts
new file mode 100644
index 000000000000..81467f1d1722
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-release-disabled.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/basic-release-disabled.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic-release-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-release-disabled.test.ts
new file mode 100644
index 000000000000..3f31dcee5a9b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-release-disabled.test.ts
@@ -0,0 +1,20 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-sourcemaps.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-sourcemaps.config.ts
new file mode 100644
index 000000000000..cb5be073b9db
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-sourcemaps.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/basic-sourcemaps.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic-sourcemaps",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-sourcemaps.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-sourcemaps.test.ts
new file mode 100644
index 000000000000..89553dea9a3f
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-sourcemaps.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ "basic.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,aAAa;"}",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.config.ts
new file mode 100644
index 000000000000..5347b599b3e6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/basic.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.test.ts
new file mode 100644
index 000000000000..4b334c167dbb
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/build-info.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/build-info.config.ts
new file mode 100644
index 000000000000..6217e404d978
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/build-info.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/build-info.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/build-info",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/build-info.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/build-info.test.ts
new file mode 100644
index 000000000000..17bc8f672cb1
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/build-info.test.ts
@@ -0,0 +1,22 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "build-information-injection-test" };
+ e.SENTRY_BUILD_INFO = { "deps": ["@sentry/bundler-plugins", "@vitejs/plugin-react", "react", "vite"], "depsVersions": { "react": 19, "vite": 7 }, "nodeVersion":"NODE_VERSION" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/bundle-size-optimizations.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/bundle-size-optimizations.config.ts
new file mode 100644
index 000000000000..62595fc43311
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/bundle-size-optimizations.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/bundle-size-optimizations.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/bundle.js",
+ output: {
+ dir: "out/bundle-size-optimizations",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/bundle-size-optimizations.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/bundle-size-optimizations.test.ts
new file mode 100644
index 000000000000..ee0a41658dad
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/bundle-size-optimizations.test.ts
@@ -0,0 +1,36 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "bundle.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log(
+ JSON.stringify({
+ debug: "b",
+ trace: "b",
+ replayCanvas: "a",
+ replayIframe: "a",
+ replayShadowDom: "a",
+ replayWorker: "a"
+ })
+ );
+ ",
+ }
+ `);
+
+ const output = runFileInNode("bundle.js");
+ expect(output).toMatchInlineSnapshot(`
+ "{"debug":"b","trace":"b","replayCanvas":"a","replayIframe":"a","replayShadowDom":"a","replayWorker":"a"}
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-disabled.config.ts
new file mode 100644
index 000000000000..75a8f88dc484
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-disabled.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/component-annotation-disabled.config.js";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ dir: "out/component-annotation-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [react({ jsxRuntime: "automatic" }), sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-disabled.test.ts
new file mode 100644
index 000000000000..23f18a092d31
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-disabled.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ import { jsx, jsxs } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-runtime.js";
+ function ComponentA() {
+ return /* @__PURE__ */ jsx("span", { children: "Component A" });
+ }
+ function App() {
+ return /* @__PURE__ */ jsxs("span", { children: [
+ /* @__PURE__ */ jsx(ComponentA, {}),
+ ";"
+ ] });
+ }
+ console.log(App());
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-next.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-next.config.ts
new file mode 100644
index 000000000000..67de4e598197
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-next.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/component-annotation-next.config.js";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ dir: "out/component-annotation-next",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [react({ jsxRuntime: "automatic" }), sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-next.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-next.test.ts
new file mode 100644
index 000000000000..f27ee2946afa
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation-next.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ import { jsx, jsxs } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-runtime.js";
+ function ComponentA() {
+ return /* @__PURE__ */ jsx("span", { "data-sentry-component": "ComponentA", children: "Component A" });
+ }
+ function App() {
+ return /* @__PURE__ */ jsxs("span", { "data-sentry-component": "App", children: [
+ /* @__PURE__ */ jsx(ComponentA, {}),
+ ";"
+ ] });
+ }
+ console.log(App());
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation.config.ts
new file mode 100644
index 000000000000..a338b95ef50c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/component-annotation.config.js";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ dir: "out/component-annotation",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [react(), sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation.test.ts
new file mode 100644
index 000000000000..1331f9de1e68
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/component-annotation.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ import { jsx, jsxs } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-runtime.js";
+ function ComponentA() {
+ return /* @__PURE__ */ jsx("span", { "data-sentry-component": "ComponentA", "data-sentry-source-file": "component-a.jsx", children: "Component A" });
+ }
+ function App() {
+ return /* @__PURE__ */ jsxs("span", { "data-sentry-component": "App", "data-sentry-source-file": "app.jsx", children: [
+ /* @__PURE__ */ jsx(ComponentA, { "data-sentry-element": "ComponentA", "data-sentry-source-file": "app.jsx" }),
+ ";"
+ ] });
+ }
+ console.log(App());
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugid-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugid-disabled.config.ts
new file mode 100644
index 000000000000..067eb176d349
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugid-disabled.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/debugid-disabled.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/debugid-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugid-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugid-disabled.test.ts
new file mode 100644
index 000000000000..1c99d55fbec9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugid-disabled.test.ts
@@ -0,0 +1,14 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "console.log("hello world");
+ //# sourceMappingURL=basic.js.map
+ ",
+ "basic.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":"AACA,QAAQ,IAAI,aAAa;"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugids-already-injected.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugids-already-injected.config.ts
new file mode 100644
index 000000000000..19c7914aa2b0
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugids-already-injected.config.ts
@@ -0,0 +1,19 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/debugids-already-injected.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/debugids-already-injected",
+ entryFileNames: "[name].js",
+ sourcemapDebugIds: true,
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugids-already-injected.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugids-already-injected.test.ts
new file mode 100644
index 000000000000..997b0c3e4b1e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/debugids-already-injected.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "vitest";
+import { readAllFiles } from "../utils";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, createTempDir }) => {
+ const tempDir = createTempDir();
+
+ runBundler({ SENTRY_TEST_OVERRIDE_TEMP_DIR: tempDir });
+ const files = readAllFiles(tempDir);
+ expect(files).toMatchInlineSnapshot(`
+ {
+ "252e0338-8927-4f52-bd57-188131defd0f-0.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ //# debugId=00000000-0000-0000-0000-000000000000
+ //# sourceMappingURL=basic.js.map
+ ",
+ "252e0338-8927-4f52-bd57-188131defd0f-0.js.map": "{"version":3,"file":"basic.js","sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,aAAa;","debugId":"252e0338-8927-4f52-bd57-188131defd0f","debug_id":"252e0338-8927-4f52-bd57-188131defd0f"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/dont-mess-up-user-code.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/dont-mess-up-user-code.config.ts
new file mode 100644
index 000000000000..18ffd2ba79fa
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/dont-mess-up-user-code.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/dont-mess-up-user-code.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/index.js",
+ output: {
+ dir: "out/dont-mess-up-user-code",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/dont-mess-up-user-code.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/dont-mess-up-user-code.test.ts
new file mode 100644
index 000000000000..90177fb7b5ba
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/dont-mess-up-user-code.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "I am release!" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("I am import!");
+ console.log("I am index!");
+ //# sourceMappingURL=index.js.map
+ ",
+ "index.js.map": "{"version":3,"file":"index.js","sources":["../../src/import.js","../../src/index.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"I am import!\\");\\n\\nexport {};\\n","import \\"./import\\";\\n\\n// eslint-disable-next-line no-console\\nconsole.log(\\"I am index!\\");\\n"],"names":[],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,cAAc;ACE1B,QAAQ,IAAI,aAAa;"}",
+ }
+ `);
+
+ const output = runFileInNode("index.js");
+ expect(output).toContain("I am import!");
+ expect(output).toContain("I am index!");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/errorhandling.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/errorhandling.config.ts
new file mode 100644
index 000000000000..192f7c5db647
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/errorhandling.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { getErrorHandlingConfig } from "../configs/errorhandling.config.js";
+
+const FAKE_SENTRY_PORT = process.env.FAKE_SENTRY_PORT || "9876";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/errorhandling",
+ entryFileNames: "[name].js",
+ format: "cjs",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(getErrorHandlingConfig(FAKE_SENTRY_PORT))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/errorhandling.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/errorhandling.test.ts
new file mode 100644
index 000000000000..f2672a372553
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/errorhandling.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { withFakeSentryServer } from "../utils";
+
+test(import.meta.url, async ({ runBundler }) => {
+ await withFakeSentryServer((port) => {
+ // Run bundler with fake server - should succeed despite server errors
+ runBundler({
+ FAKE_SENTRY_PORT: port,
+ SENTRY_HTTP_MAX_RETRIES: "1", // Only retry once to avoid timeout
+ });
+
+ // If we get here, the build succeeded (didn't throw)
+ expect(true).toBe(true);
+ });
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/module-metadata.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/module-metadata.config.ts
new file mode 100644
index 000000000000..59433418bb9d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/module-metadata.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/module-metadata.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/module-metadata",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/module-metadata.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/module-metadata.test.ts
new file mode 100644
index 000000000000..d807e3d04b30
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/module-metadata.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = (function(e2) {
+ for (var n2 = 1; n2 < arguments.length; n2++) {
+ var a = arguments[n2];
+ if (null != a) for (var t in a) a.hasOwnProperty(t) && (e2[t] = a[t]);
+ }
+ return e2;
+ })({}, e._sentryModuleMetadata[new e.Error().stack], { "something": "value", "another": 999 });
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/multiple-entry-points.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/multiple-entry-points.config.ts
new file mode 100644
index 000000000000..da0b49d90362
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/multiple-entry-points.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/multiple-entry-points.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/multiple-entry-points",
+ chunkFileNames: "[name].js",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/multiple-entry-points.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/multiple-entry-points.test.ts
new file mode 100644
index 000000000000..e3ee93ed40a5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/multiple-entry-points.test.ts
@@ -0,0 +1,58 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ function add(a, b) {
+ return a + b;
+ }
+ export {
+ add as a
+ };
+ ",
+ "entry1.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ import { a as add } from "./common.js";
+ console.log(add(1, 2));
+ ",
+ "entry2.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ import { a as add } from "./common.js";
+ console.log(add(2, 4));
+ ",
+ }
+ `);
+
+ const output1 = runFileInNode("entry1.js");
+ expect(output1).toMatchInlineSnapshot(`
+ "3
+ "
+ `);
+ const output2 = runFileInNode("entry2.js");
+ expect(output2).toMatchInlineSnapshot(`
+ "6
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json
new file mode 100644
index 000000000000..9fbd6d2b3763
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "vite7-integration-tests",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0",
+ "react": "19.2.4",
+ "vite": "7.3.1",
+ "@vitejs/plugin-react": "5.2.0"
+ },
+ "pnpm": {
+ "overrides": {
+ "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins.tgz"
+ },
+ "patchedDependencies": {
+ "@sentry/cli": "../patches/@sentry__cli.patch"
+ }
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/query-param.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/query-param.config.ts
new file mode 100644
index 000000000000..185106f97cff
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/query-param.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/query-param.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/query-param",
+ chunkFileNames: "[name].js?seP58q4g",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/query-param.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/query-param.test.ts
new file mode 100644
index 000000000000..dbf701028f0c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/query-param.test.ts
@@ -0,0 +1,55 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, ctx }) => {
+ if (process.platform === "win32") {
+ ctx.skip("Query params do not work in paths on Windows");
+ return;
+ }
+
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js?seP58q4g": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ function add(a, b) {
+ return a + b;
+ }
+ export {
+ add as a
+ };
+ ",
+ "entry1.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ import { a as add } from "./common.js?seP58q4g";
+ console.log(add(1, 2));
+ ",
+ "entry2.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ import { a as add } from "./common.js?seP58q4g";
+ console.log(add(2, 4));
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.config.ts
new file mode 100644
index 000000000000..ab823129454d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/release-disabled.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/release-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.test.ts
new file mode 100644
index 000000000000..be2e8688f19a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.test.ts
@@ -0,0 +1,25 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ ",
+ "sentry-cli-mock.json": "["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-value-with-quotes.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-value-with-quotes.config.ts
new file mode 100644
index 000000000000..8a50f7f5416e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-value-with-quotes.config.ts
@@ -0,0 +1,17 @@
+import { defineConfig } from "vite";
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { sentryConfig } from "../configs/release-value-with-quotes.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ outDir: "./out/release-value-with-quotes",
+ rollupOptions: {
+ input: "./src/release-value-with-quotes.js",
+ output: {
+ entryFileNames: "bundle.js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-value-with-quotes.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-value-with-quotes.test.ts
new file mode 100644
index 000000000000..314a41b865f6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-value-with-quotes.test.ts
@@ -0,0 +1,8 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, runFileInNode }) => {
+ runBundler();
+ const output = runFileInNode("bundle.js");
+ expect(output.trimEnd()).toBe('"i am a dangerous release value because I contain a \\""');
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/app.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/app.jsx
new file mode 100644
index 000000000000..5be821b44e72
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/app.jsx
@@ -0,0 +1,11 @@
+import { ComponentA } from "./component-a";
+
+export default function App() {
+ return (
+
+ ;
+
+ );
+}
+
+console.log(App());
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/basic.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/basic.js
new file mode 100644
index 000000000000..7ef02afbd244
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/basic.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-console
+console.log("hello world");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/bundle.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/bundle.js
new file mode 100644
index 000000000000..0d62e559347d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/bundle.js
@@ -0,0 +1,10 @@
+console.log(
+ JSON.stringify({
+ debug: __SENTRY_DEBUG__ ? "a" : "b",
+ trace: __SENTRY_TRACING__ ? "a" : "b",
+ replayCanvas: __RRWEB_EXCLUDE_CANVAS__ ? "a" : "b",
+ replayIframe: __RRWEB_EXCLUDE_IFRAME__ ? "a" : "b",
+ replayShadowDom: __RRWEB_EXCLUDE_SHADOW_DOM__ ? "a" : "b",
+ replayWorker: __SENTRY_EXCLUDE_REPLAY_WORKER__ ? "a" : "b",
+ })
+);
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/common.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/common.js
new file mode 100644
index 000000000000..7d658310b0d9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/common.js
@@ -0,0 +1,3 @@
+export function add(a, b) {
+ return a + b;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/component-a.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/component-a.jsx
new file mode 100644
index 000000000000..5d57ab2215cd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/component-a.jsx
@@ -0,0 +1,3 @@
+export function ComponentA() {
+ return Component A;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/entry1.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/entry1.js
new file mode 100644
index 000000000000..480816663453
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/entry1.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(1, 2));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/entry2.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/entry2.js
new file mode 100644
index 000000000000..f64af1ea7ae5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/entry2.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(2, 4));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/import.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/import.js
new file mode 100644
index 000000000000..733d5d48f137
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/import.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line no-console
+console.log("I am import!");
+
+export {};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/index.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/index.js
new file mode 100644
index 000000000000..719d8428cac6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/index.js
@@ -0,0 +1,4 @@
+import "./import";
+
+// eslint-disable-next-line no-console
+console.log("I am index!");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/release-value-with-quotes.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/release-value-with-quotes.js
new file mode 100644
index 000000000000..aa73bfa8be00
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/release-value-with-quotes.js
@@ -0,0 +1,3 @@
+// Simply output the metadata to the console so it can be checked in a test
+// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-member-access
+console.log(JSON.stringify(global.SENTRY_RELEASE.id));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/shared-module.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/shared-module.js
new file mode 100644
index 000000000000..07182654d2da
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/shared-module.js
@@ -0,0 +1,10 @@
+// This is a shared module that is used by multiple HTML pages
+export function greet(name) {
+ // eslint-disable-next-line no-console
+ console.log(`Hello, ${String(name)}!`);
+}
+
+export const VERSION = "1.0.0";
+
+// Side effect: greet on load
+greet("World");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/vite-mpa-index.html b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/vite-mpa-index.html
new file mode 100644
index 000000000000..b18731ac6fab
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/vite-mpa-index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Index Page
+
+
+ Index Page - No Scripts
+
+
+
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/vite-mpa-page1.html b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/vite-mpa-page1.html
new file mode 100644
index 000000000000..44cb873cef18
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/vite-mpa-page1.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Page 1
+
+
+ Page 1 - With Shared Module
+
+
+
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/vite-mpa-page2.html b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/vite-mpa-page2.html
new file mode 100644
index 000000000000..6cac57fe9b22
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/src/vite-mpa-page2.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Page 2
+
+
+ Page 2 - With Shared Module
+
+
+
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/telemetry.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/telemetry.config.ts
new file mode 100644
index 000000000000..e55014fa5da8
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/telemetry.config.ts
@@ -0,0 +1,19 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/telemetry.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/telemetry",
+ entryFileNames: "[name].js",
+ },
+ },
+ // We already delete the directory and don't want our telemetry file to be deleted
+ emptyOutDir: false,
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/telemetry.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/telemetry.test.ts
new file mode 100644
index 000000000000..196ff3ab6447
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/telemetry.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ console.log("hello world");
+ ",
+ "sentry-telemetry.json": "[{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":true,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"ok","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ [{"event_id":"UUID","sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"},"trace":{"environment":"production","release":"PLUGIN_VERSION","public_key":"UUID","trace_id":"UUID","org_id":"1","transaction":"Sentry Bundler Plugin execution","sampled":"true","sample_rand":"SAMPLE_RAND","sample_rate":"1"}},[[{"type":"transaction"},{"contexts":{"trace":{"span_id":"SHORT_UUID","trace_id":"UUID","data":{"sentry.origin":"manual","sentry.source":"custom","sentry.sample_rate":1},"origin":"manual"},"runtime":{"name":"node","version":"NODE_VERSION"}},"spans":[],"start_timestamp":START_TIMESTAMP,"timestamp":TIMESTAMP,"transaction":"Sentry Bundler Plugin execution","type":"transaction","transaction_info":{"source":"custom"},"platform":"PLATFORM","event_id":"UUID","environment":"production","release":"PLUGIN_VERSION","tags":{"upload-legacy-sourcemaps":false,"module-metadata":false,"inject-build-information":false,"set-commits":"auto","finalize-release":true,"deploy-options":false,"custom-error-handler":false,"sourcemaps-assets":false,"delete-after-upload":false,"sourcemaps-disabled":false,"react-annotate":false,"node":"NODE_VERSION","platform":"PLATFORM","meta-framework":"none","application-key-set":false,"ci":true,"project":"undefined","bundler":"vite","bundler-major-version":"7"},"user":{},"sdk":{"name":"sentry.javascript.node","version":"10.56.0","integrations":[],"packages":[{"name":"npm:@sentry/node","version":"10.56.0"}]}}]]],
+ [{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":false,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"exited","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/utils.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/utils.ts
new file mode 100644
index 000000000000..01e358bc6a5c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/utils.ts
@@ -0,0 +1,67 @@
+import { basename, dirname, join } from "node:path";
+import { createTempDir, readAllFiles, runBundler } from "../utils";
+import { fileURLToPath } from "node:url";
+import { rmSync } from "node:fs";
+import type { TestContext } from "vitest";
+import { test as vitestTest } from "vitest";
+import { execSync } from "node:child_process";
+
+const cwd = dirname(fileURLToPath(import.meta.url));
+const NODE_MAJOR_VERSION = parseInt(process.versions.node.split(".")[0] || "0", 10);
+
+type TestCallback = (props: {
+ outDir: string;
+ runBundler: (env?: Record) => void;
+ readOutputFiles: () => Record;
+ runFileInNode: (file: string) => string;
+ createTempDir: () => string;
+ ctx: TestContext;
+}) => void | Promise;
+
+export function test(url: string, callback: TestCallback) {
+ const filePath = fileURLToPath(url);
+ const filename = basename(filePath);
+ const testName = filename.replace(/\.test\.ts$/, "");
+ const outDir = join(cwd, "out", testName);
+
+ // Clear the output directory before running the test
+ rmSync(outDir, { recursive: true, force: true });
+
+ // Detect CJS config files by test name suffix
+ const configExt = testName.endsWith("-cjs") ? ".config.cjs" : ".config.ts";
+
+ // Vite v7 requires Node 20+
+ if (NODE_MAJOR_VERSION < 20) {
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ vitestTest.skip(testName);
+ } else {
+ vitestTest(`Vite v7 > ${testName}`, (ctx) =>
+ callback({
+ outDir,
+ runBundler: (env) =>
+ runBundler(
+ `pnpm vite build --config ${testName}${configExt}`,
+ {
+ cwd,
+ env: {
+ ...process.env,
+ ...env,
+ NODE_ENV: "production",
+ },
+ },
+ outDir
+ ),
+ readOutputFiles: () => readAllFiles(outDir),
+ runFileInNode: (file) => {
+ const fullPath = join(outDir, file);
+ return execSync(`node ${fullPath}`, {
+ cwd,
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
+ }).toString();
+ },
+ createTempDir: () => createTempDir(),
+ ctx,
+ })
+ );
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/vite-mpa-extra-modules.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/vite-mpa-extra-modules.config.ts
new file mode 100644
index 000000000000..087194faa653
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/vite-mpa-extra-modules.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig } from "vite";
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { sentryConfig } from "../configs/vite-mpa-extra-modules.config.js";
+import { resolve } from "node:path";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ outDir: "./out/vite-mpa-extra-modules",
+ rollupOptions: {
+ input: {
+ index: resolve("./src/vite-mpa-index.html"),
+ page1: resolve("./src/vite-mpa-page1.html"),
+ page2: resolve("./src/vite-mpa-page2.html"),
+ },
+ output: {
+ chunkFileNames: "[name].js",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/vite-mpa-extra-modules.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/vite-mpa-extra-modules.test.ts
new file mode 100644
index 000000000000..0d9b6cb56090
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/vite-mpa-extra-modules.test.ts
@@ -0,0 +1,94 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js.map": "{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}",
+ "page1.js.map": "{"version":3,"file":"page1.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}",
+ "page2.js.map": "{"version":3,"file":"page2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}",
+ "shared-module.js": "!(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e2) {
+ }
+ })();
+ (function polyfill() {
+ const relList = document.createElement("link").relList;
+ if (relList && relList.supports && relList.supports("modulepreload")) return;
+ for (const link of document.querySelectorAll('link[rel="modulepreload"]')) processPreload(link);
+ new MutationObserver((mutations) => {
+ for (const mutation of mutations) {
+ if (mutation.type !== "childList") continue;
+ for (const node of mutation.addedNodes) if (node.tagName === "LINK" && node.rel === "modulepreload") processPreload(node);
+ }
+ }).observe(document, {
+ childList: true,
+ subtree: true
+ });
+ function getFetchOpts(link) {
+ const fetchOpts = {};
+ if (link.integrity) fetchOpts.integrity = link.integrity;
+ if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;
+ if (link.crossOrigin === "use-credentials") fetchOpts.credentials = "include";
+ else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";
+ else fetchOpts.credentials = "same-origin";
+ return fetchOpts;
+ }
+ function processPreload(link) {
+ if (link.ep) return;
+ link.ep = true;
+ const fetchOpts = getFetchOpts(link);
+ fetch(link.href, fetchOpts);
+ }
+ })();
+ function greet(name) {
+ console.log(\`Hello, \${String(name)}!\`);
+ }
+ greet("World");
+ //# sourceMappingURL=shared-module.js.map
+ ",
+ "shared-module.js.map": "{"version":3,"file":"shared-module.js","sources":["../../src/shared-module.js"],"sourcesContent":["// This is a shared module that is used by multiple HTML pages\\nexport function greet(name) {\\n // eslint-disable-next-line no-console\\n console.log(\`Hello, \${String(name)}!\`);\\n}\\n\\nexport const VERSION = \\"1.0.0\\";\\n\\n// Side effect: greet on load\\ngreet(\\"World\\");\\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACO,SAAS,MAAM,MAAM;AAE1B,UAAQ,IAAI,UAAU,OAAO,IAAI,CAAC,GAAG;AACvC;AAKA,MAAM,OAAO;"}",
+ "src/vite-mpa-index.html": "
+
+
+
+ Index Page
+
+
+ Index Page - No Scripts
+
+
+
+ ",
+ "src/vite-mpa-page1.html": "
+
+
+
+ Page 1
+
+
+
+ Page 1 - With Shared Module
+
+
+ ",
+ "src/vite-mpa-page2.html": "
+
+
+
+ Page 2
+
+
+
+ Page 2 - With Shared Module
+
+
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion-promise.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion-promise.config.ts
new file mode 100644
index 000000000000..c99b4ce44dab
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion-promise.config.ts
@@ -0,0 +1,20 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { getSentryConfig } from "../configs/after-upload-deletion-promise.config.js";
+
+const outDir = "out/after-upload-deletion-promise";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: outDir,
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(getSentryConfig(outDir))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion-promise.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion-promise.test.ts
new file mode 100644
index 000000000000..92c6bd553f5a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion-promise.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+
+test(import.meta.url, ({ runBundler, outDir, runFileInNode }) => {
+ runBundler();
+
+ // Verify the JS file exists and works
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+
+ // Verify the sourcemap was deleted (by the Promise)
+ const sourcemapPath = join(outDir, "basic.js.map");
+ expect(existsSync(sourcemapPath)).toBe(false);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion.config.ts
new file mode 100644
index 000000000000..6cf383546533
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/after-upload-deletion.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/after-upload-deletion",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion.test.ts
new file mode 100644
index 000000000000..c0e84898b270
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/after-upload-deletion.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+
+ //# sourceMappingURL=basic.js.map",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/application-key.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/application-key.config.ts
new file mode 100644
index 000000000000..e57d70e7008b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/application-key.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/application-key.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/application-key",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/application-key.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/application-key.test.ts
new file mode 100644
index 000000000000..88a7d0a92d9a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/application-key.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = function(e) {
+ for (var n = 1; n < arguments.length; n++) {
+ var a = arguments[n];
+ if (null != a) for (var t in a) a.hasOwnProperty(t) && (e[t] = a[t]);
+ }
+ return e;
+ }({}, e._sentryModuleMetadata[new e.Error().stack], { "_sentryBundlerPluginAppKey:1234567890abcdef": true });
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.config.cjs b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.config.cjs
new file mode 100644
index 000000000000..cd61918c8b68
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.config.cjs
@@ -0,0 +1,17 @@
+const { sentryVitePlugin } = require("@sentry/bundler-plugins/vite");
+const { defineConfig } = require("vite");
+const { sentryConfig } = require("../configs/basic.config.cjs");
+
+module.exports = defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic-cjs",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.test.ts
new file mode 100644
index 000000000000..ef26f379ba3b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.test.ts
@@ -0,0 +1,30 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-release-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-release-disabled.config.ts
new file mode 100644
index 000000000000..81467f1d1722
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-release-disabled.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/basic-release-disabled.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic-release-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-release-disabled.test.ts
new file mode 100644
index 000000000000..b39ba31eec49
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-release-disabled.test.ts
@@ -0,0 +1,21 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-sourcemaps.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-sourcemaps.config.ts
new file mode 100644
index 000000000000..cb5be073b9db
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-sourcemaps.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/basic-sourcemaps.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic-sourcemaps",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-sourcemaps.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-sourcemaps.test.ts
new file mode 100644
index 000000000000..11726184aff9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-sourcemaps.test.ts
@@ -0,0 +1,32 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+
+ //# sourceMappingURL=basic.js.map",
+ "basic.js.map": "{"version":3,"file":"basic.js","names":[],"sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,cAAc"}",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.config.ts
new file mode 100644
index 000000000000..5347b599b3e6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/basic.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/basic",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.test.ts
new file mode 100644
index 000000000000..ef26f379ba3b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.test.ts
@@ -0,0 +1,30 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/build-info.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/build-info.config.ts
new file mode 100644
index 000000000000..6217e404d978
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/build-info.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/build-info.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/build-info",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/build-info.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/build-info.test.ts
new file mode 100644
index 000000000000..7966268035db
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/build-info.test.ts
@@ -0,0 +1,35 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "build-information-injection-test" };
+ e.SENTRY_BUILD_INFO = {
+ "deps": [
+ "@sentry/bundler-plugins",
+ "@vitejs/plugin-react",
+ "react",
+ "vite"
+ ],
+ "depsVersions": {
+ "react": 19,
+ "vite": 8
+ },
+ "nodeVersion":"NODE_VERSION"
+ };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/bundle-size-optimizations.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/bundle-size-optimizations.config.ts
new file mode 100644
index 000000000000..62595fc43311
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/bundle-size-optimizations.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/bundle-size-optimizations.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/bundle.js",
+ output: {
+ dir: "out/bundle-size-optimizations",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/bundle-size-optimizations.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/bundle-size-optimizations.test.ts
new file mode 100644
index 000000000000..eeb6bf284506
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/bundle-size-optimizations.test.ts
@@ -0,0 +1,35 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "bundle.js": "//#region src/bundle.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log(JSON.stringify({
+ debug: "b",
+ trace: "b",
+ replayCanvas: "a",
+ replayIframe: "a",
+ replayShadowDom: "a",
+ replayWorker: "a"
+ }));
+ //#endregion
+ ",
+ }
+ `);
+
+ const output = runFileInNode("bundle.js");
+ expect(output).toMatchInlineSnapshot(`
+ "{"debug":"b","trace":"b","replayCanvas":"a","replayIframe":"a","replayShadowDom":"a","replayWorker":"a"}
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-disabled.config.ts
new file mode 100644
index 000000000000..75a8f88dc484
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-disabled.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/component-annotation-disabled.config.js";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ dir: "out/component-annotation-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [react({ jsxRuntime: "automatic" }), sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-disabled.test.ts
new file mode 100644
index 000000000000..6f32107615e6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-disabled.test.ts
@@ -0,0 +1,45 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { jsxDEV } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-dev-runtime.js";
+ //#region src/component-a.jsx
+ var _jsxFileName$1 = "/fixtures/vite8/src/component-a.jsx";
+ function ComponentA() {
+ return /* @__PURE__ */ jsxDEV("span", { children: "Component A" }, void 0, false, {
+ fileName: _jsxFileName$1,
+ lineNumber: 2,
+ columnNumber: 10
+ }, this);
+ }
+ //#endregion
+ //#region src/app.jsx
+ var _jsxFileName = "/fixtures/vite8/src/app.jsx";
+ function App() {
+ return /* @__PURE__ */ jsxDEV("span", { children: [/* @__PURE__ */ jsxDEV(ComponentA, {}, void 0, false, {
+ fileName: _jsxFileName,
+ lineNumber: 6,
+ columnNumber: 7
+ }, this), ";"] }, void 0, true, {
+ fileName: _jsxFileName,
+ lineNumber: 5,
+ columnNumber: 5
+ }, this);
+ }
+ console.log(App());
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-next.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-next.config.ts
new file mode 100644
index 000000000000..67de4e598197
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-next.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/component-annotation-next.config.js";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ dir: "out/component-annotation-next",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [react({ jsxRuntime: "automatic" }), sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-next.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-next.test.ts
new file mode 100644
index 000000000000..ad52e996a9f5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation-next.test.ts
@@ -0,0 +1,51 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { jsxDEV } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-dev-runtime.js";
+ //#region src/component-a.jsx
+ var _jsxFileName$1 = "/fixtures/vite8/src/component-a.jsx";
+ function ComponentA() {
+ return /* @__PURE__ */ jsxDEV("span", {
+ "data-sentry-component": "ComponentA",
+ children: "Component A"
+ }, void 0, false, {
+ fileName: _jsxFileName$1,
+ lineNumber: 2,
+ columnNumber: 10
+ }, this);
+ }
+ //#endregion
+ //#region src/app.jsx
+ var _jsxFileName = "/fixtures/vite8/src/app.jsx";
+ function App() {
+ return /* @__PURE__ */ jsxDEV("span", {
+ "data-sentry-component": "App",
+ children: [/* @__PURE__ */ jsxDEV(ComponentA, {}, void 0, false, {
+ fileName: _jsxFileName,
+ lineNumber: 4,
+ columnNumber: 7
+ }, this), ";"]
+ }, void 0, true, {
+ fileName: _jsxFileName,
+ lineNumber: 3,
+ columnNumber: 10
+ }, this);
+ }
+ console.log(App());
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation.config.ts
new file mode 100644
index 000000000000..a338b95ef50c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/component-annotation.config.js";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/app.jsx",
+ // We exclude these to keep the snapshot small
+ external: [/node_modules/],
+ makeAbsoluteExternalsRelative: true,
+ output: {
+ dir: "out/component-annotation",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [react(), sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation.test.ts
new file mode 100644
index 000000000000..b6991cfcd6f0
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/component-annotation.test.ts
@@ -0,0 +1,56 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { jsxDEV } from "../node_modules/.pnpm/react@19.2.4/node_modules/react/jsx-dev-runtime.js";
+ //#region src/component-a.jsx
+ var _jsxFileName$1 = "/fixtures/vite8/src/component-a.jsx";
+ function ComponentA() {
+ return /* @__PURE__ */ jsxDEV("span", {
+ "data-sentry-component": "ComponentA",
+ "data-sentry-source-file": "component-a.jsx",
+ children: "Component A"
+ }, void 0, false, {
+ fileName: _jsxFileName$1,
+ lineNumber: 2,
+ columnNumber: 10
+ }, this);
+ }
+ //#endregion
+ //#region src/app.jsx
+ var _jsxFileName = "/fixtures/vite8/src/app.jsx";
+ function App() {
+ return /* @__PURE__ */ jsxDEV("span", {
+ "data-sentry-component": "App",
+ "data-sentry-source-file": "app.jsx",
+ children: [/* @__PURE__ */ jsxDEV(ComponentA, {
+ "data-sentry-element": "ComponentA",
+ "data-sentry-source-file": "app.jsx"
+ }, void 0, false, {
+ fileName: _jsxFileName,
+ lineNumber: 4,
+ columnNumber: 7
+ }, this), ";"]
+ }, void 0, true, {
+ fileName: _jsxFileName,
+ lineNumber: 3,
+ columnNumber: 10
+ }, this);
+ }
+ console.log(App());
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugid-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugid-disabled.config.ts
new file mode 100644
index 000000000000..067eb176d349
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugid-disabled.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/debugid-disabled.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/debugid-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugid-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugid-disabled.test.ts
new file mode 100644
index 000000000000..0502dbb79f0c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugid-disabled.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ console.log("hello world");
+ //#endregion
+
+ //# sourceMappingURL=basic.js.map",
+ "basic.js.map": "{"version":3,"file":"basic.js","names":[],"sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"mappings":";AACA,QAAQ,IAAI,cAAc"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugids-already-injected.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugids-already-injected.config.ts
new file mode 100644
index 000000000000..19c7914aa2b0
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugids-already-injected.config.ts
@@ -0,0 +1,19 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/debugids-already-injected.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/debugids-already-injected",
+ entryFileNames: "[name].js",
+ sourcemapDebugIds: true,
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugids-already-injected.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugids-already-injected.test.ts
new file mode 100644
index 000000000000..c5a04f975b7c
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/debugids-already-injected.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { readAllFiles } from "../utils";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, createTempDir }) => {
+ const tempDir = createTempDir();
+
+ runBundler({ SENTRY_TEST_OVERRIDE_TEMP_DIR: tempDir });
+ const files = readAllFiles(tempDir);
+ expect(files).toMatchInlineSnapshot(`
+ {
+ "b699d9c1-b033-4536-aa25-233c92609b54-0.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+
+ //# debugId=00000000-0000-0000-0000-000000000000
+ //# sourceMappingURL=basic.js.map",
+ "b699d9c1-b033-4536-aa25-233c92609b54-0.js.map": "{"version":3,"file":"basic.js","names":[],"sources":["../../src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,cAAc","debugId":"b699d9c1-b033-4536-aa25-233c92609b54","debug_id":"b699d9c1-b033-4536-aa25-233c92609b54"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/dont-mess-up-user-code.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/dont-mess-up-user-code.config.ts
new file mode 100644
index 000000000000..18ffd2ba79fa
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/dont-mess-up-user-code.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/dont-mess-up-user-code.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/index.js",
+ output: {
+ dir: "out/dont-mess-up-user-code",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/dont-mess-up-user-code.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/dont-mess-up-user-code.test.ts
new file mode 100644
index 000000000000..08a0afffca51
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/dont-mess-up-user-code.test.ts
@@ -0,0 +1,31 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "index.js": "//#region src/import.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "I am release!" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("I am import!");
+ //#endregion
+ //#region src/index.js
+ console.log("I am index!");
+ //#endregion
+
+ //# sourceMappingURL=index.js.map",
+ "index.js.map": "{"version":3,"file":"index.js","names":[],"sources":["../../src/import.js","../../src/index.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"I am import!\\");\\n\\nexport {};\\n","import \\"./import\\";\\n\\n// eslint-disable-next-line no-console\\nconsole.log(\\"I am index!\\");\\n"],"mappings":";;;;;;;;;AACA,QAAQ,IAAI,eAAe;;;ACE3B,QAAQ,IAAI,cAAc"}",
+ }
+ `);
+
+ const output = runFileInNode("index.js");
+ expect(output).toContain("I am import!");
+ expect(output).toContain("I am index!");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/errorhandling.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/errorhandling.config.ts
new file mode 100644
index 000000000000..192f7c5db647
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/errorhandling.config.ts
@@ -0,0 +1,21 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { getErrorHandlingConfig } from "../configs/errorhandling.config.js";
+
+const FAKE_SENTRY_PORT = process.env.FAKE_SENTRY_PORT || "9876";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/errorhandling",
+ entryFileNames: "[name].js",
+ format: "cjs",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(getErrorHandlingConfig(FAKE_SENTRY_PORT))],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/errorhandling.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/errorhandling.test.ts
new file mode 100644
index 000000000000..f2672a372553
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/errorhandling.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { withFakeSentryServer } from "../utils";
+
+test(import.meta.url, async ({ runBundler }) => {
+ await withFakeSentryServer((port) => {
+ // Run bundler with fake server - should succeed despite server errors
+ runBundler({
+ FAKE_SENTRY_PORT: port,
+ SENTRY_HTTP_MAX_RETRIES: "1", // Only retry once to avoid timeout
+ });
+
+ // If we get here, the build succeeded (didn't throw)
+ expect(true).toBe(true);
+ });
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/module-metadata.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/module-metadata.config.ts
new file mode 100644
index 000000000000..59433418bb9d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/module-metadata.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/module-metadata.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/module-metadata",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/module-metadata.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/module-metadata.test.ts
new file mode 100644
index 000000000000..05c0653cd811
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/module-metadata.test.ts
@@ -0,0 +1,35 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ e._sentryModuleMetadata = e._sentryModuleMetadata || {}, e._sentryModuleMetadata[new e.Error().stack] = function(e) {
+ for (var n = 1; n < arguments.length; n++) {
+ var a = arguments[n];
+ if (null != a) for (var t in a) a.hasOwnProperty(t) && (e[t] = a[t]);
+ }
+ return e;
+ }({}, e._sentryModuleMetadata[new e.Error().stack], {
+ "something": "value",
+ "another": 999
+ });
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/multiple-entry-points.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/multiple-entry-points.config.ts
new file mode 100644
index 000000000000..da0b49d90362
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/multiple-entry-points.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/multiple-entry-points.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/multiple-entry-points",
+ chunkFileNames: "[name].js",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/multiple-entry-points.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/multiple-entry-points.test.ts
new file mode 100644
index 000000000000..63b5f353f8f4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/multiple-entry-points.test.ts
@@ -0,0 +1,59 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js": "//#region src/common.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ function add(a, b) {
+ return a + b;
+ }
+ //#endregion
+ export { add as t };
+ ",
+ "entry1.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { t as add } from "./common.js";
+ //#region src/entry1.js
+ console.log(add(1, 2));
+ //#endregion
+ ",
+ "entry2.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { t as add } from "./common.js";
+ //#region src/entry2.js
+ console.log(add(2, 4));
+ //#endregion
+ ",
+ }
+ `);
+
+ const output1 = runFileInNode("entry1.js");
+ expect(output1).toMatchInlineSnapshot(`
+ "3
+ "
+ `);
+ const output2 = runFileInNode("entry2.js");
+ expect(output2).toMatchInlineSnapshot(`
+ "6
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json
new file mode 100644
index 000000000000..6015ab1697f1
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "vite8-integration-tests",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0",
+ "react": "19.2.4",
+ "vite": "8.0.1",
+ "@vitejs/plugin-react": "6.0.1"
+ },
+ "pnpm": {
+ "overrides": {
+ "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins.tgz"
+ },
+ "patchedDependencies": {
+ "@sentry/cli": "../patches/@sentry__cli.patch"
+ }
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/query-param.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/query-param.config.ts
new file mode 100644
index 000000000000..185106f97cff
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/query-param.config.ts
@@ -0,0 +1,18 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/query-param.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: ["src/entry1.js", "src/entry2.js"],
+ output: {
+ dir: "out/query-param",
+ chunkFileNames: "[name].js?seP58q4g",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/query-param.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/query-param.test.ts
new file mode 100644
index 000000000000..96666e28df19
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/query-param.test.ts
@@ -0,0 +1,56 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, ctx }) => {
+ if (process.platform === "win32") {
+ ctx.skip("Query params do not work in paths on Windows");
+ return;
+ }
+
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "common.js?seP58q4g": "//#region src/common.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ function add(a, b) {
+ return a + b;
+ }
+ //#endregion
+ export { add as t };
+ ",
+ "entry1.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { t as add } from "./common.js?seP58q4g";
+ //#region src/entry1.js
+ console.log(add(1, 2));
+ //#endregion
+ ",
+ "entry2.js": "(function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ import { t as add } from "./common.js?seP58q4g";
+ //#region src/entry2.js
+ console.log(add(2, 4));
+ //#endregion
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.config.ts
new file mode 100644
index 000000000000..ab823129454d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.config.ts
@@ -0,0 +1,17 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/release-disabled.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/release-disabled",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.test.ts
new file mode 100644
index 000000000000..18e4c8f80f47
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ "sentry-cli-mock.json": "["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-value-with-quotes.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-value-with-quotes.config.ts
new file mode 100644
index 000000000000..8a50f7f5416e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-value-with-quotes.config.ts
@@ -0,0 +1,17 @@
+import { defineConfig } from "vite";
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { sentryConfig } from "../configs/release-value-with-quotes.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ outDir: "./out/release-value-with-quotes",
+ rollupOptions: {
+ input: "./src/release-value-with-quotes.js",
+ output: {
+ entryFileNames: "bundle.js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-value-with-quotes.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-value-with-quotes.test.ts
new file mode 100644
index 000000000000..314a41b865f6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-value-with-quotes.test.ts
@@ -0,0 +1,8 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, runFileInNode }) => {
+ runBundler();
+ const output = runFileInNode("bundle.js");
+ expect(output.trimEnd()).toBe('"i am a dangerous release value because I contain a \\""');
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/app.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/app.jsx
new file mode 100644
index 000000000000..5be821b44e72
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/app.jsx
@@ -0,0 +1,11 @@
+import { ComponentA } from "./component-a";
+
+export default function App() {
+ return (
+
+ ;
+
+ );
+}
+
+console.log(App());
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/basic.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/basic.js
new file mode 100644
index 000000000000..7ef02afbd244
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/basic.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-console
+console.log("hello world");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/bundle.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/bundle.js
new file mode 100644
index 000000000000..0d62e559347d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/bundle.js
@@ -0,0 +1,10 @@
+console.log(
+ JSON.stringify({
+ debug: __SENTRY_DEBUG__ ? "a" : "b",
+ trace: __SENTRY_TRACING__ ? "a" : "b",
+ replayCanvas: __RRWEB_EXCLUDE_CANVAS__ ? "a" : "b",
+ replayIframe: __RRWEB_EXCLUDE_IFRAME__ ? "a" : "b",
+ replayShadowDom: __RRWEB_EXCLUDE_SHADOW_DOM__ ? "a" : "b",
+ replayWorker: __SENTRY_EXCLUDE_REPLAY_WORKER__ ? "a" : "b",
+ })
+);
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/common.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/common.js
new file mode 100644
index 000000000000..7d658310b0d9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/common.js
@@ -0,0 +1,3 @@
+export function add(a, b) {
+ return a + b;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/component-a.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/component-a.jsx
new file mode 100644
index 000000000000..5d57ab2215cd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/component-a.jsx
@@ -0,0 +1,3 @@
+export function ComponentA() {
+ return Component A;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/entry1.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/entry1.js
new file mode 100644
index 000000000000..480816663453
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/entry1.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(1, 2));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/entry2.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/entry2.js
new file mode 100644
index 000000000000..f64af1ea7ae5
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/entry2.js
@@ -0,0 +1,3 @@
+import { add } from "./common";
+
+console.log(add(2, 4));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/import.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/import.js
new file mode 100644
index 000000000000..733d5d48f137
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/import.js
@@ -0,0 +1,4 @@
+// eslint-disable-next-line no-console
+console.log("I am import!");
+
+export {};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/index.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/index.js
new file mode 100644
index 000000000000..719d8428cac6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/index.js
@@ -0,0 +1,4 @@
+import "./import";
+
+// eslint-disable-next-line no-console
+console.log("I am index!");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/release-value-with-quotes.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/release-value-with-quotes.js
new file mode 100644
index 000000000000..aa73bfa8be00
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/release-value-with-quotes.js
@@ -0,0 +1,3 @@
+// Simply output the metadata to the console so it can be checked in a test
+// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-member-access
+console.log(JSON.stringify(global.SENTRY_RELEASE.id));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/shared-module.js b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/shared-module.js
new file mode 100644
index 000000000000..07182654d2da
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/shared-module.js
@@ -0,0 +1,10 @@
+// This is a shared module that is used by multiple HTML pages
+export function greet(name) {
+ // eslint-disable-next-line no-console
+ console.log(`Hello, ${String(name)}!`);
+}
+
+export const VERSION = "1.0.0";
+
+// Side effect: greet on load
+greet("World");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/vite-mpa-index.html b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/vite-mpa-index.html
new file mode 100644
index 000000000000..b18731ac6fab
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/vite-mpa-index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Index Page
+
+
+ Index Page - No Scripts
+
+
+
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/vite-mpa-page1.html b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/vite-mpa-page1.html
new file mode 100644
index 000000000000..44cb873cef18
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/vite-mpa-page1.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Page 1
+
+
+ Page 1 - With Shared Module
+
+
+
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/vite-mpa-page2.html b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/vite-mpa-page2.html
new file mode 100644
index 000000000000..6cac57fe9b22
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/src/vite-mpa-page2.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Page 2
+
+
+ Page 2 - With Shared Module
+
+
+
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/telemetry.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/telemetry.config.ts
new file mode 100644
index 000000000000..e55014fa5da8
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/telemetry.config.ts
@@ -0,0 +1,19 @@
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { defineConfig } from "vite";
+import { sentryConfig } from "../configs/telemetry.config.js";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ rollupOptions: {
+ input: "src/basic.js",
+ output: {
+ dir: "out/telemetry",
+ entryFileNames: "[name].js",
+ },
+ },
+ // We already delete the directory and don't want our telemetry file to be deleted
+ emptyOutDir: false,
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/telemetry.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/telemetry.test.ts
new file mode 100644
index 000000000000..31b799241081
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/telemetry.test.ts
@@ -0,0 +1,29 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "//#region src/basic.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ console.log("hello world");
+ //#endregion
+ ",
+ "sentry-telemetry.json": "[{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":true,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"ok","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ [{"event_id":"UUID","sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"},"trace":{"environment":"production","release":"PLUGIN_VERSION","public_key":"UUID","trace_id":"UUID","org_id":"1","transaction":"Sentry Bundler Plugin execution","sampled":"true","sample_rand":"SAMPLE_RAND","sample_rate":"1"}},[[{"type":"transaction"},{"contexts":{"trace":{"span_id":"SHORT_UUID","trace_id":"UUID","data":{"sentry.origin":"manual","sentry.source":"custom","sentry.sample_rate":1},"origin":"manual"},"runtime":{"name":"node","version":"NODE_VERSION"}},"spans":[],"start_timestamp":START_TIMESTAMP,"timestamp":TIMESTAMP,"transaction":"Sentry Bundler Plugin execution","type":"transaction","transaction_info":{"source":"custom"},"platform":"PLATFORM","event_id":"UUID","environment":"production","release":"PLUGIN_VERSION","tags":{"upload-legacy-sourcemaps":false,"module-metadata":false,"inject-build-information":false,"set-commits":"auto","finalize-release":true,"deploy-options":false,"custom-error-handler":false,"sourcemaps-assets":false,"delete-after-upload":false,"sourcemaps-disabled":false,"react-annotate":false,"node":"NODE_VERSION","platform":"PLATFORM","meta-framework":"none","application-key-set":false,"ci":true,"project":"undefined","bundler":"vite","bundler-major-version":"8"},"user":{},"sdk":{"name":"sentry.javascript.node","version":"10.56.0","integrations":[],"packages":[{"name":"npm:@sentry/node","version":"10.56.0"}]}}]]],
+ [{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":false,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"exited","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/utils.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/utils.ts
new file mode 100644
index 000000000000..0d7f44cb0a04
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/utils.ts
@@ -0,0 +1,66 @@
+import { basename, dirname, join } from "node:path";
+import { createTempDir, readAllFiles, runBundler } from "../utils";
+import { fileURLToPath } from "node:url";
+import { rmSync } from "node:fs";
+import type { TestContext } from "vitest";
+import { test as vitestTest } from "vitest";
+import { execSync } from "node:child_process";
+
+const cwd = dirname(fileURLToPath(import.meta.url));
+const NODE_MAJOR_VERSION = parseInt(process.versions.node.split(".")[0] || "0", 10);
+
+type TestCallback = (props: {
+ outDir: string;
+ runBundler: (env?: Record) => void;
+ readOutputFiles: () => Record;
+ runFileInNode: (file: string) => string;
+ createTempDir: () => string;
+ ctx: TestContext;
+}) => void | Promise;
+
+export function test(url: string, callback: TestCallback) {
+ const filePath = fileURLToPath(url);
+ const filename = basename(filePath);
+ const testName = filename.replace(/\.test\.ts$/, "");
+ const outDir = join(cwd, "out", testName);
+
+ // Clear the output directory before running the test
+ rmSync(outDir, { recursive: true, force: true });
+
+ // Detect CJS config files by test name suffix
+ const configExt = testName.endsWith("-cjs") ? ".config.cjs" : ".config.ts";
+
+ // Vite v8 requires Node 20+
+ if (NODE_MAJOR_VERSION < 20) {
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ vitestTest.skip(testName);
+ } else {
+ vitestTest(`Vite v8 > ${testName}`, (ctx) =>
+ callback({
+ outDir,
+ runBundler: (env) =>
+ runBundler(
+ `pnpm vite build --config ${testName}${configExt}`,
+ {
+ cwd,
+ env: {
+ ...process.env,
+ ...env,
+ },
+ },
+ outDir
+ ),
+ readOutputFiles: () => readAllFiles(outDir),
+ runFileInNode: (file) => {
+ const fullPath = join(outDir, file);
+ return execSync(`node ${fullPath}`, {
+ cwd,
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
+ }).toString();
+ },
+ createTempDir: () => createTempDir(),
+ ctx,
+ })
+ );
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/vite-mpa-extra-modules.config.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/vite-mpa-extra-modules.config.ts
new file mode 100644
index 000000000000..087194faa653
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/vite-mpa-extra-modules.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig } from "vite";
+import { sentryVitePlugin } from "@sentry/bundler-plugins/vite";
+import { sentryConfig } from "../configs/vite-mpa-extra-modules.config.js";
+import { resolve } from "node:path";
+
+export default defineConfig({
+ build: {
+ minify: false,
+ sourcemap: true,
+ outDir: "./out/vite-mpa-extra-modules",
+ rollupOptions: {
+ input: {
+ index: resolve("./src/vite-mpa-index.html"),
+ page1: resolve("./src/vite-mpa-page1.html"),
+ page2: resolve("./src/vite-mpa-page2.html"),
+ },
+ output: {
+ chunkFileNames: "[name].js",
+ entryFileNames: "[name].js",
+ },
+ },
+ },
+ plugins: [sentryVitePlugin(sentryConfig)],
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/vite-mpa-extra-modules.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/vite-mpa-extra-modules.test.ts
new file mode 100644
index 000000000000..78cdb819c730
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/vite-mpa-extra-modules.test.ts
@@ -0,0 +1,94 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "shared-module.js": "//#region \\0vite/modulepreload-polyfill.js
+ (function() {
+ try {
+ var e = "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof globalThis ? globalThis : "undefined" != typeof self ? self : {};
+ e.SENTRY_RELEASE = { id: "CURRENT_SHA" };
+ var n = new e.Error().stack;
+ n && (e._sentryDebugIds = e._sentryDebugIds || {}, e._sentryDebugIds[n] = "00000000-0000-0000-0000-000000000000", e._sentryDebugIdIdentifier = "sentry-dbid-00000000-0000-0000-0000-000000000000");
+ } catch (e) {}
+ })();
+ (function polyfill() {
+ const relList = document.createElement("link").relList;
+ if (relList && relList.supports && relList.supports("modulepreload")) return;
+ for (const link of document.querySelectorAll("link[rel=\\"modulepreload\\"]")) processPreload(link);
+ new MutationObserver((mutations) => {
+ for (const mutation of mutations) {
+ if (mutation.type !== "childList") continue;
+ for (const node of mutation.addedNodes) if (node.tagName === "LINK" && node.rel === "modulepreload") processPreload(node);
+ }
+ }).observe(document, {
+ childList: true,
+ subtree: true
+ });
+ function getFetchOpts(link) {
+ const fetchOpts = {};
+ if (link.integrity) fetchOpts.integrity = link.integrity;
+ if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy;
+ if (link.crossOrigin === "use-credentials") fetchOpts.credentials = "include";
+ else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit";
+ else fetchOpts.credentials = "same-origin";
+ return fetchOpts;
+ }
+ function processPreload(link) {
+ if (link.ep) return;
+ link.ep = true;
+ const fetchOpts = getFetchOpts(link);
+ fetch(link.href, fetchOpts);
+ }
+ })();
+ //#endregion
+ //#region src/shared-module.js
+ function greet(name) {
+ console.log(\`Hello, \${String(name)}!\`);
+ }
+ greet("World");
+ //#endregion
+
+ //# sourceMappingURL=shared-module.js.map",
+ "shared-module.js.map": "{"version":3,"file":"shared-module.js","names":[],"sources":["../../src/shared-module.js"],"sourcesContent":["// This is a shared module that is used by multiple HTML pages\\nexport function greet(name) {\\n // eslint-disable-next-line no-console\\n console.log(\`Hello, \${String(name)}!\`);\\n}\\n\\nexport const VERSION = \\"1.0.0\\";\\n\\n// Side effect: greet on load\\ngreet(\\"World\\");\\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,SAAgB,MAAM,MAAM;AAE1B,SAAQ,IAAI,UAAU,OAAO,KAAK,CAAC,GAAG;;AAMxC,MAAM,QAAQ"}",
+ "src/vite-mpa-index.html": "
+
+
+
+ Index Page
+
+
+ Index Page - No Scripts
+
+
+
+ ",
+ "src/vite-mpa-page1.html": "
+
+
+
+ Page 1
+
+
+
+ Page 1 - With Shared Module
+
+
+ ",
+ "src/vite-mpa-page2.html": "
+
+
+
+ Page 2
+
+
+
+ Page 2 - With Shared Module
+
+
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion-promise.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion-promise.config.js
new file mode 100644
index 000000000000..603265043084
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion-promise.config.js
@@ -0,0 +1,20 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { getSentryConfig } from "../configs/after-upload-deletion-promise.config.js";
+import { resolve } from "node:path";
+
+const outDir = "./out/after-upload-deletion-promise";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve(outDir),
+ filename: "basic.js",
+ },
+ devtool: "source-map",
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(getSentryConfig(outDir))],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion-promise.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion-promise.test.ts
new file mode 100644
index 000000000000..92c6bd553f5a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion-promise.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { existsSync } from "node:fs";
+import { join } from "node:path";
+
+test(import.meta.url, ({ runBundler, outDir, runFileInNode }) => {
+ runBundler();
+
+ // Verify the JS file exists and works
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+
+ // Verify the sourcemap was deleted (by the Promise)
+ const sourcemapPath = join(outDir, "basic.js.map");
+ expect(existsSync(sourcemapPath)).toBe(false);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion.config.js
new file mode 100644
index 000000000000..6b13fdc78dab
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion.config.js
@@ -0,0 +1,18 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/after-upload-deletion.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/after-upload-deletion"),
+ filename: "basic.js",
+ },
+ devtool: "source-map",
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion.test.ts
new file mode 100644
index 000000000000..a4bcd8a767cb
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/after-upload-deletion.test.ts
@@ -0,0 +1,22 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;
+ //# sourceMappingURL=basic.js.map",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/application-key.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/application-key.config.js
new file mode 100644
index 000000000000..e613744c9d9a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/application-key.config.js
@@ -0,0 +1,17 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/application-key.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/application-key"),
+ filename: "basic.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/application-key.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/application-key.test.ts
new file mode 100644
index 000000000000..76b6d9adb0e8
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/application-key.test.ts
@@ -0,0 +1,18 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=function(e){for(var n=1;n { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-cjs.config.cjs b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-cjs.config.cjs
new file mode 100644
index 000000000000..0f353dc75fe7
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-cjs.config.cjs
@@ -0,0 +1,17 @@
+const { sentryWebpackPlugin } = require("@sentry/bundler-plugins/webpack");
+const { sentryConfig } = require("../configs/basic.config.cjs");
+const { resolve } = require("node:path");
+
+module.exports = {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/basic-cjs"),
+ filename: "basic.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-cjs.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-cjs.test.ts
new file mode 100644
index 000000000000..baa396d20e70
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-cjs.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-release-disabled.config.js
new file mode 100644
index 000000000000..11a5ee8a53ae
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-release-disabled.config.js
@@ -0,0 +1,17 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/basic-release-disabled.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/basic-release-disabled"),
+ filename: "basic.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-release-disabled.test.ts
new file mode 100644
index 000000000000..c7fc5905957a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-release-disabled.test.ts
@@ -0,0 +1,18 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-sourcemaps.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-sourcemaps.config.js
new file mode 100644
index 000000000000..63a0a50ded5e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-sourcemaps.config.js
@@ -0,0 +1,18 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/basic-sourcemaps.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/basic-sourcemaps"),
+ filename: "basic.js",
+ },
+ devtool: "source-map",
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-sourcemaps.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-sourcemaps.test.ts
new file mode 100644
index 000000000000..c9fedd3d47f8
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic-sourcemaps.test.ts
@@ -0,0 +1,28 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;
+ //# sourceMappingURL=basic.js.map",
+ "basic.js.map": "{"version":3,"file":"basic.js","mappings":";;;AAAA;AACA","sources":["webpack://webpack5-integration-tests/./src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"sourceRoot":""}",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic.config.js
new file mode 100644
index 000000000000..c0a6913548cb
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic.config.js
@@ -0,0 +1,17 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/basic.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/basic"),
+ filename: "basic.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic.test.ts
new file mode 100644
index 000000000000..baa396d20e70
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/basic.test.ts
@@ -0,0 +1,26 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;",
+ "sentry-cli-mock.json": "["releases","new","CURRENT_SHA"],
+ ["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/build-info.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/build-info.config.js
new file mode 100644
index 000000000000..84e1a0100064
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/build-info.config.js
@@ -0,0 +1,17 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/build-info.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/build-info"),
+ filename: "basic.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/build-info.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/build-info.test.ts
new file mode 100644
index 000000000000..4742786c9b76
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/build-info.test.ts
@@ -0,0 +1,18 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"build-information-injection-test"};e.SENTRY_BUILD_INFO={"deps":["@babel/preset-react","@sentry/bundler-plugins","babel-loader","webpack","webpack-cli"],"depsVersions":{"webpack":5},"nodeVersion":"NODE_VERSION"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/bundle-size-optimizations.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/bundle-size-optimizations.config.js
new file mode 100644
index 000000000000..c0f65b4bef2b
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/bundle-size-optimizations.config.js
@@ -0,0 +1,17 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/bundle-size-optimizations.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/bundle.js",
+ output: {
+ path: resolve("./out/bundle-size-optimizations"),
+ filename: "bundle.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/bundle-size-optimizations.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/bundle-size-optimizations.test.ts
new file mode 100644
index 000000000000..7313767b10ad
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/bundle-size-optimizations.test.ts
@@ -0,0 +1,32 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "bundle.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ console.log(
+ JSON.stringify({
+ debug: false ? 0 : "b",
+ trace: false ? 0 : "b",
+ replayCanvas: true ? "a" : 0,
+ replayIframe: true ? "a" : 0,
+ replayShadowDom: true ? "a" : 0,
+ replayWorker: true ? "a" : 0,
+ })
+ );
+
+ /******/ })()
+ ;",
+ }
+ `);
+
+ const output = runFileInNode("bundle.js");
+ expect(output).toMatchInlineSnapshot(`
+ "{"debug":"b","trace":"b","replayCanvas":"a","replayIframe":"a","replayShadowDom":"a","replayWorker":"a"}
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-disabled.config.js
new file mode 100644
index 000000000000..2ec70eb34dc3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-disabled.config.js
@@ -0,0 +1,38 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/component-annotation-disabled.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/app.jsx",
+ output: {
+ path: resolve("./out/component-annotation-disabled"),
+ filename: "app.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: [["@babel/preset-react", { runtime: "automatic" }]],
+ },
+ },
+ },
+ ],
+ },
+ resolve: {
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
+ },
+ externals: {
+ react: "react",
+ "react/jsx-runtime": "react/jsx-runtime",
+ },
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-disabled.test.ts
new file mode 100644
index 000000000000..39a5b67c4108
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-disabled.test.ts
@@ -0,0 +1,39 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+
+ // UNUSED EXPORTS: default
+
+ ;// external "react/jsx-runtime"
+ const jsx_runtime_namespaceObject = react/jsx-runtime;
+ ;// ./src/component-a.jsx
+ /* unused harmony import specifier */ var _jsx;
+
+ function ComponentA() {
+ return /*#__PURE__*/_jsx("span", {
+ children: "Component A"
+ });
+ }
+ ;// ./src/app.jsx
+ /* unused harmony import specifier */ var app_ComponentA;
+ /* unused harmony import specifier */ var _jsxs;
+ /* unused harmony import specifier */ var app_jsx;
+
+
+ function App() {
+ return /*#__PURE__*/_jsxs("span", {
+ children: [/*#__PURE__*/app_jsx(app_ComponentA, {}), ";"]
+ });
+ }
+ /******/ })()
+ ;",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-next.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-next.config.js
new file mode 100644
index 000000000000..8f971939c428
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-next.config.js
@@ -0,0 +1,38 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/component-annotation-next.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/app.jsx",
+ output: {
+ path: resolve("./out/component-annotation-next"),
+ filename: "app.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: [["@babel/preset-react", { runtime: "automatic" }]],
+ },
+ },
+ },
+ ],
+ },
+ resolve: {
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
+ },
+ externals: {
+ react: "react",
+ "react/jsx-runtime": "react/jsx-runtime",
+ },
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-next.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-next.test.ts
new file mode 100644
index 000000000000..28d9443ae50d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation-next.test.ts
@@ -0,0 +1,46 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, ctx }) => {
+ if (process.platform === "win32") {
+ ctx.skip("Windows Debug IDs do not match snapshots");
+ return;
+ }
+
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+
+ // UNUSED EXPORTS: default
+
+ ;// external "react/jsx-runtime"
+ const jsx_runtime_namespaceObject = react/jsx-runtime;
+ ;// ./src/component-a.jsx
+ /* unused harmony import specifier */ var _jsx;
+
+ function ComponentA() {
+ return /*#__PURE__*/_jsx("span", {
+ "data-sentry-component": "ComponentA",
+ children: "Component A"
+ });
+ }
+ ;// ./src/app.jsx
+ /* unused harmony import specifier */ var app_ComponentA;
+ /* unused harmony import specifier */ var _jsxs;
+ /* unused harmony import specifier */ var app_jsx;
+
+
+ function App() {
+ return /*#__PURE__*/_jsxs("span", {
+ "data-sentry-component": "App",
+ children: [/*#__PURE__*/app_jsx(app_ComponentA, {}), ";"]
+ });
+ }
+ /******/ })()
+ ;",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation.config.js
new file mode 100644
index 000000000000..8cf60d7816d1
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation.config.js
@@ -0,0 +1,38 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/component-annotation.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/app.jsx",
+ output: {
+ path: resolve("./out/component-annotation"),
+ filename: "app.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ module: {
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: {
+ loader: "babel-loader",
+ options: {
+ presets: [["@babel/preset-react", { runtime: "automatic" }]],
+ },
+ },
+ },
+ ],
+ },
+ resolve: {
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
+ },
+ externals: {
+ react: "react",
+ "react/jsx-runtime": "react/jsx-runtime",
+ },
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation.test.ts
new file mode 100644
index 000000000000..1c5f9f8cc6ac
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/component-annotation.test.ts
@@ -0,0 +1,51 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, ctx }) => {
+ if (process.platform === "win32") {
+ ctx.skip("Windows Debug IDs do not match snapshots");
+ return;
+ }
+
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "app.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+
+ // UNUSED EXPORTS: default
+
+ ;// external "react/jsx-runtime"
+ const jsx_runtime_namespaceObject = react/jsx-runtime;
+ ;// ./src/component-a.jsx
+ /* unused harmony import specifier */ var _jsx;
+
+ function ComponentA() {
+ return /*#__PURE__*/_jsx("span", {
+ "data-sentry-component": "ComponentA",
+ "data-sentry-source-file": "component-a.jsx",
+ children: "Component A"
+ });
+ }
+ ;// ./src/app.jsx
+ /* unused harmony import specifier */ var app_ComponentA;
+ /* unused harmony import specifier */ var _jsxs;
+ /* unused harmony import specifier */ var app_jsx;
+
+
+ function App() {
+ return /*#__PURE__*/_jsxs("span", {
+ "data-sentry-component": "App",
+ "data-sentry-source-file": "app.jsx",
+ children: [/*#__PURE__*/app_jsx(app_ComponentA, {
+ "data-sentry-element": "ComponentA",
+ "data-sentry-source-file": "app.jsx"
+ }), ";"]
+ });
+ }
+ /******/ })()
+ ;",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugid-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugid-disabled.config.js
new file mode 100644
index 000000000000..dd297e2e14de
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugid-disabled.config.js
@@ -0,0 +1,18 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/debugid-disabled.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/debugid-disabled"),
+ filename: "basic.js",
+ },
+ devtool: "source-map",
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugid-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugid-disabled.test.ts
new file mode 100644
index 000000000000..c8fba65d43dc
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugid-disabled.test.ts
@@ -0,0 +1,19 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "/******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;
+ //# sourceMappingURL=basic.js.map",
+ "basic.js.map": "{"version":3,"file":"basic.js","mappings":";;AAAA;AACA","sources":["webpack://webpack5-integration-tests/./src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"sourceRoot":""}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugids-already-injected.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugids-already-injected.config.js
new file mode 100644
index 000000000000..ed9b816d0b92
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugids-already-injected.config.js
@@ -0,0 +1,18 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/debugids-already-injected.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/debugids-already-injected"),
+ filename: "basic.js",
+ },
+ devtool: "source-map",
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugids-already-injected.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugids-already-injected.test.ts
new file mode 100644
index 000000000000..3509e3431c86
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/debugids-already-injected.test.ts
@@ -0,0 +1,25 @@
+import { expect } from "vitest";
+import { readAllFiles } from "../utils";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, createTempDir }) => {
+ const tempDir = createTempDir();
+
+ runBundler({ SENTRY_TEST_OVERRIDE_TEMP_DIR: tempDir });
+ const files = readAllFiles(tempDir);
+ expect(files).toMatchInlineSnapshot(`
+ {
+ "33730b8e-5b8d-4795-94b2-666cea28fce6-0.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;
+ //# sourceMappingURL=basic.js.map
+ //# debugId=00000000-0000-0000-0000-000000000000",
+ "33730b8e-5b8d-4795-94b2-666cea28fce6-0.js.map": "{"version":3,"file":"basic.js","mappings":";;;AAAA;AACA","sources":["webpack5-integration-tests/./src/basic.js"],"sourcesContent":["// eslint-disable-next-line no-console\\nconsole.log(\\"hello world\\");\\n"],"names":[],"sourceRoot":"","debug_id":"33730b8e-5b8d-4795-94b2-666cea28fce6","debugId":"33730b8e-5b8d-4795-94b2-666cea28fce6"}",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/errorhandling.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/errorhandling.config.js
new file mode 100644
index 000000000000..867fbf77a76f
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/errorhandling.config.js
@@ -0,0 +1,21 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { getErrorHandlingConfig } from "../configs/errorhandling.config.js";
+import { resolve } from "node:path";
+
+const FAKE_SENTRY_PORT = process.env.FAKE_SENTRY_PORT || "9876";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/errorhandling"),
+ filename: "basic.js",
+ libraryTarget: "commonjs2",
+ },
+ devtool: "source-map",
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(getErrorHandlingConfig(FAKE_SENTRY_PORT))],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/errorhandling.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/errorhandling.test.ts
new file mode 100644
index 000000000000..f2672a372553
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/errorhandling.test.ts
@@ -0,0 +1,16 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+import { withFakeSentryServer } from "../utils";
+
+test(import.meta.url, async ({ runBundler }) => {
+ await withFakeSentryServer((port) => {
+ // Run bundler with fake server - should succeed despite server errors
+ runBundler({
+ FAKE_SENTRY_PORT: port,
+ SENTRY_HTTP_MAX_RETRIES: "1", // Only retry once to avoid timeout
+ });
+
+ // If we get here, the build succeeded (didn't throw)
+ expect(true).toBe(true);
+ });
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/module-metadata.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/module-metadata.config.js
new file mode 100644
index 000000000000..c9effe710b11
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/module-metadata.config.js
@@ -0,0 +1,17 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/module-metadata.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/module-metadata"),
+ filename: "basic.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/module-metadata.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/module-metadata.test.ts
new file mode 100644
index 000000000000..2672e9edf824
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/module-metadata.test.ts
@@ -0,0 +1,21 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};e._sentryModuleMetadata=e._sentryModuleMetadata||{},e._sentryModuleMetadata[(new e.Error).stack]=function(e){for(var n=1;n { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/multiple-entry-points.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/multiple-entry-points.config.js
new file mode 100644
index 000000000000..d30a7c85a6ef
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/multiple-entry-points.config.js
@@ -0,0 +1,20 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/multiple-entry-points.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: {
+ entry1: "./src/entry1.js",
+ entry2: "./src/entry2.js",
+ },
+ output: {
+ path: resolve("./out/multiple-entry-points"),
+ filename: "[name].js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/multiple-entry-points.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/multiple-entry-points.test.ts
new file mode 100644
index 000000000000..5f054a9ba2da
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/multiple-entry-points.test.ts
@@ -0,0 +1,53 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "entry1.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+
+ ;// ./src/common.js
+ function add(a, b) {
+ return a + b;
+ }
+
+ ;// ./src/entry1.js
+
+
+ console.log(add(1, 2));
+
+ /******/ })()
+ ;",
+ "entry2.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+
+ ;// ./src/common.js
+ function add(a, b) {
+ return a + b;
+ }
+
+ ;// ./src/entry2.js
+
+
+ console.log(add(2, 4));
+
+ /******/ })()
+ ;",
+ }
+ `);
+
+ const output1 = runFileInNode("entry1.js");
+ expect(output1).toMatchInlineSnapshot(`
+ "3
+ "
+ `);
+ const output2 = runFileInNode("entry2.js");
+ expect(output2).toMatchInlineSnapshot(`
+ "6
+ "
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json
new file mode 100644
index 000000000000..bc65da4163de
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "webpack5-integration-tests",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0",
+ "babel-loader": "^8.0.0",
+ "webpack": "5.105.4",
+ "webpack-cli": "6.0.1",
+ "@babel/preset-react": "7.23.3"
+ },
+ "pnpm": {
+ "overrides": {
+ "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins.tgz"
+ },
+ "patchedDependencies": {
+ "@sentry/cli": "../patches/@sentry__cli.patch"
+ }
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-disabled.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-disabled.config.js
new file mode 100644
index 000000000000..831ca2fc78f7
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-disabled.config.js
@@ -0,0 +1,17 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/release-disabled.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/release-disabled"),
+ filename: "basic.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-disabled.test.ts
new file mode 100644
index 000000000000..5f0e210be09d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-disabled.test.ts
@@ -0,0 +1,22 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;",
+ "sentry-cli-mock.json": "["releases","set-commits","CURRENT_SHA","--auto","--ignore-missing"],
+ ["releases","finalize","CURRENT_SHA"],
+ ["sourcemaps","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path","--ignore","node_modules","--no-rewrite"],
+ ",
+ }
+ `);
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-value-with-quotes.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-value-with-quotes.config.js
new file mode 100644
index 000000000000..322d3820639e
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-value-with-quotes.config.js
@@ -0,0 +1,17 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/release-value-with-quotes.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/release-value-with-quotes.js",
+ output: {
+ path: resolve("./out/release-value-with-quotes"),
+ filename: "bundle.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-value-with-quotes.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-value-with-quotes.test.ts
new file mode 100644
index 000000000000..314a41b865f6
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/release-value-with-quotes.test.ts
@@ -0,0 +1,8 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, runFileInNode }) => {
+ runBundler();
+ const output = runFileInNode("bundle.js");
+ expect(output.trimEnd()).toBe('"i am a dangerous release value because I contain a \\""');
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/app.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/app.jsx
new file mode 100644
index 000000000000..614d38c834aa
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/app.jsx
@@ -0,0 +1,9 @@
+import { ComponentA } from "./component-a";
+
+export default function App() {
+ return (
+
+ ;
+
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/basic.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/basic.js
new file mode 100644
index 000000000000..7ef02afbd244
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/basic.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line no-console
+console.log("hello world");
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/bundle.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/bundle.js
new file mode 100644
index 000000000000..0d62e559347d
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/bundle.js
@@ -0,0 +1,10 @@
+console.log(
+ JSON.stringify({
+ debug: __SENTRY_DEBUG__ ? "a" : "b",
+ trace: __SENTRY_TRACING__ ? "a" : "b",
+ replayCanvas: __RRWEB_EXCLUDE_CANVAS__ ? "a" : "b",
+ replayIframe: __RRWEB_EXCLUDE_IFRAME__ ? "a" : "b",
+ replayShadowDom: __RRWEB_EXCLUDE_SHADOW_DOM__ ? "a" : "b",
+ replayWorker: __SENTRY_EXCLUDE_REPLAY_WORKER__ ? "a" : "b",
+ })
+);
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/common.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/common.js
new file mode 100644
index 000000000000..7d658310b0d9
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/common.js
@@ -0,0 +1,3 @@
+export function add(a, b) {
+ return a + b;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/component-a.jsx b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/component-a.jsx
new file mode 100644
index 000000000000..5d57ab2215cd
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/component-a.jsx
@@ -0,0 +1,3 @@
+export function ComponentA() {
+ return Component A;
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/entry1.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/entry1.js
new file mode 100644
index 000000000000..6ab03d8de7e3
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/entry1.js
@@ -0,0 +1,3 @@
+import { add } from "./common.js";
+
+console.log(add(1, 2));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/entry2.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/entry2.js
new file mode 100644
index 000000000000..675580cf5752
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/entry2.js
@@ -0,0 +1,3 @@
+import { add } from "./common.js";
+
+console.log(add(2, 4));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/release-value-with-quotes.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/release-value-with-quotes.js
new file mode 100644
index 000000000000..aa73bfa8be00
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/src/release-value-with-quotes.js
@@ -0,0 +1,3 @@
+// Simply output the metadata to the console so it can be checked in a test
+// eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-member-access
+console.log(JSON.stringify(global.SENTRY_RELEASE.id));
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/telemetry.config.js b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/telemetry.config.js
new file mode 100644
index 000000000000..fa8488285de4
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/telemetry.config.js
@@ -0,0 +1,17 @@
+import { sentryWebpackPlugin } from "@sentry/bundler-plugins/webpack";
+import { sentryConfig } from "../configs/telemetry.config.js";
+import { resolve } from "node:path";
+
+export default {
+ cache: false,
+ entry: "./src/basic.js",
+ output: {
+ path: resolve("./out/telemetry"),
+ filename: "basic.js",
+ },
+ optimization: {
+ minimize: false,
+ },
+ mode: "production",
+ plugins: [sentryWebpackPlugin(sentryConfig)],
+};
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/telemetry.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/telemetry.test.ts
new file mode 100644
index 000000000000..4644423c0bad
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/telemetry.test.ts
@@ -0,0 +1,25 @@
+import { expect } from "vitest";
+import { test } from "./utils";
+
+test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => {
+ runBundler();
+ expect(readOutputFiles()).toMatchInlineSnapshot(`
+ {
+ "basic.js": "!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{};e.SENTRY_RELEASE={id:"CURRENT_SHA"};var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="00000000-0000-0000-0000-000000000000",e._sentryDebugIdIdentifier="sentry-dbid-00000000-0000-0000-0000-000000000000");}catch(e){}}();
+ /******/ (() => { // webpackBootstrap
+ /******/ "use strict";
+ // eslint-disable-next-line no-console
+ console.log("hello world");
+
+ /******/ })()
+ ;",
+ "sentry-telemetry.json": "[{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":true,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"ok","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ [{"event_id":"UUID","sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"},"trace":{"environment":"production","release":"PLUGIN_VERSION","public_key":"UUID","trace_id":"UUID","org_id":"1","transaction":"Sentry Bundler Plugin execution","sampled":"true","sample_rand":"SAMPLE_RAND","sample_rate":"1"}},[[{"type":"transaction"},{"contexts":{"trace":{"span_id":"SHORT_UUID","trace_id":"UUID","data":{"sentry.origin":"manual","sentry.source":"custom","sentry.sample_rate":1},"origin":"manual"},"runtime":{"name":"node","version":"NODE_VERSION"}},"spans":[],"start_timestamp":START_TIMESTAMP,"timestamp":TIMESTAMP,"transaction":"Sentry Bundler Plugin execution","type":"transaction","transaction_info":{"source":"custom"},"platform":"PLATFORM","event_id":"UUID","environment":"production","release":"PLUGIN_VERSION","tags":{"upload-legacy-sourcemaps":false,"module-metadata":false,"inject-build-information":false,"set-commits":"auto","finalize-release":true,"deploy-options":false,"custom-error-handler":false,"sourcemaps-assets":false,"delete-after-upload":false,"sourcemaps-disabled":false,"react-annotate":false,"node":"NODE_VERSION","platform":"PLATFORM","meta-framework":"none","application-key-set":false,"ci":true,"project":"undefined","bundler":"webpack","bundler-major-version":"5"},"user":{},"sdk":{"name":"sentry.javascript.node","version":"10.56.0","integrations":[],"packages":[{"name":"npm:@sentry/node","version":"10.56.0"}]}}]]],
+ [{"sent_at":"TIMESTAMP","sdk":{"name":"sentry.javascript.node","version":"10.56.0"}},[[{"type":"session"},{"sid":"UUID","init":false,"started":"TIMESTAMP","timestamp":"TIMESTAMP","status":"exited","errors":0,"duration":DURATION,"attrs":{"release":"PLUGIN_VERSION","environment":"production"}}]]],
+ ",
+ }
+ `);
+
+ const output = runFileInNode("basic.js");
+ expect(output).toBe("hello world\n");
+});
diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/utils.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/utils.ts
new file mode 100644
index 000000000000..48aedc3f11e7
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/utils.ts
@@ -0,0 +1,59 @@
+import { basename, dirname, join } from "node:path";
+import { createTempDir, readAllFiles, runBundler } from "../utils";
+import { fileURLToPath } from "node:url";
+import { rmSync } from "node:fs";
+import type { TestContext } from "vitest";
+import { test as vitestTest } from "vitest";
+import { execSync } from "node:child_process";
+
+const cwd = dirname(fileURLToPath(import.meta.url));
+
+type TestCallback = (props: {
+ outDir: string;
+ runBundler: (env?: Record) => void;
+ readOutputFiles: () => Record;
+ runFileInNode: (file: string) => string;
+ createTempDir: () => string;
+ ctx: TestContext;
+}) => void | Promise;
+
+export function test(url: string, callback: TestCallback) {
+ const filePath = fileURLToPath(url);
+ const filename = basename(filePath);
+ const testName = filename.replace(/\.test\.ts$/, "");
+ const outDir = join(cwd, "out", testName);
+
+ // Clear the output directory before running the test
+ rmSync(outDir, { recursive: true, force: true });
+
+ // Detect CJS config files by test name suffix
+ const configExt = testName.endsWith("-cjs") ? ".config.cjs" : ".config.js";
+
+ vitestTest(`webpack v5 > ${testName}`, (ctx) =>
+ callback({
+ outDir,
+ runBundler: (env) =>
+ runBundler(
+ `pnpm webpack --config ${testName}${configExt}`,
+ {
+ cwd,
+ env: {
+ ...process.env,
+ ...env,
+ },
+ },
+ outDir
+ ),
+ readOutputFiles: () => readAllFiles(outDir),
+ runFileInNode: (file) => {
+ const fullPath = join(outDir, file);
+ return execSync(`node ${fullPath}`, {
+ cwd,
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
+ }).toString();
+ },
+ createTempDir: () => createTempDir(),
+ ctx,
+ })
+ );
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/package.json b/dev-packages/bundler-plugin-integration-tests/package.json
new file mode 100644
index 000000000000..ba10bfb4358a
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@sentry-internal/bundler-plugin-integration-tests",
+ "version": "10.56.0",
+ "license": "MIT",
+ "private": true,
+ "scripts": {
+ "test": "node setup.mjs && vitest run --pool threads --test-timeout=10000",
+ "check:types": "tsc --project ./tsconfig.json --noEmit",
+ "clean": "run-s clean:build",
+ "clean:all": "run-p clean clean:deps",
+ "clean:build": "premove ./fixtures/*/out",
+ "clean:deps": "premove node_modules"
+ },
+ "dependencies": {
+ "@sentry/bundler-plugins": "5.3.0"
+ },
+ "devDependencies": {
+ "premove": "^4.0.0",
+ "vitest": "^3.2.4"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/dev-packages/bundler-plugin-integration-tests/setup.mjs b/dev-packages/bundler-plugin-integration-tests/setup.mjs
new file mode 100644
index 000000000000..260f7b59ac43
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/setup.mjs
@@ -0,0 +1,55 @@
+/* eslint-disable no-console */
+import { promises as fs } from 'fs';
+import { join } from 'path';
+import { fileURLToPath } from 'url';
+import { execSync } from 'child_process';
+
+const __dirname = fileURLToPath(new URL('.', import.meta.url));
+
+// Build the @sentry/bundler-plugins package and produce the tarball that the
+// fixtures install via pnpm `file:` overrides.
+const bundlerPluginsDir = join(__dirname, '..', '..', 'packages', 'bundler-plugins');
+console.log('Building @sentry/bundler-plugins...');
+execSync('yarn build', {
+ cwd: bundlerPluginsDir,
+ stdio: 'inherit',
+});
+
+// `npm pack` (run as part of the build) names the tarball after the current
+// package version (e.g. `sentry-bundler-plugins-5.3.0.tgz`). The fixtures
+// reference it via a version-independent `file:` override, so copy it to a
+// stable name. This avoids having to update every fixture override whenever the
+// package version changes.
+const { version } = JSON.parse(await fs.readFile(join(bundlerPluginsDir, 'package.json'), { encoding: 'utf-8' }));
+await fs.copyFile(
+ join(bundlerPluginsDir, `sentry-bundler-plugins-${version}.tgz`),
+ join(bundlerPluginsDir, 'sentry-bundler-plugins.tgz'),
+);
+
+console.log('Installing all dependencies for fixtures...');
+
+const fixturesDir = join(__dirname, 'fixtures');
+const entries = await fs.readdir(fixturesDir, { withFileTypes: true });
+
+// Get all directories
+const directories = entries.filter(entry => entry.isDirectory()).map(entry => join(fixturesDir, entry.name));
+
+for (const dir of directories) {
+ try {
+ const pkgString = await fs.readFile(join(dir, 'package.json'), { encoding: 'utf-8' });
+ const packageJson = JSON.parse(pkgString);
+ // If there are no dependencies, skip installation
+ if (!packageJson.dependencies) {
+ continue;
+ }
+ } catch {
+ continue;
+ }
+
+ execSync('pnpm install --force', {
+ cwd: dir,
+ stdio: 'inherit',
+ });
+}
+
+console.log('All fixture dependencies installed successfully!');
diff --git a/dev-packages/bundler-plugin-integration-tests/tsconfig.json b/dev-packages/bundler-plugin-integration-tests/tsconfig.json
new file mode 100644
index 000000000000..901692e1d120
--- /dev/null
+++ b/dev-packages/bundler-plugin-integration-tests/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "../../tsconfig.json",
+ "include": ["./**/*"],
+ // We exclude the Vite files for now.
+ // Because the Vite plugin 'references" a different `vite` package when devDeps are loaded, it doesn't
+ // match all the tested Vite versions. This isn't an issue when the plugin is used with end-users Vite versions.
+ "exclude": ["./fixtures/vite*/**/*"],
+ "compilerOptions": {
+ "types": ["node"],
+ // `bundler` resolution lets tsc resolve the `@sentry/bundler-plugins/*`
+ // subpath exports (their `types` conditions) that the fixtures import from.
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "lib": ["ES2021"]
+ }
+}
diff --git a/package.json b/package.json
index 44ac0d95b9a3..804377ebe1dc 100644
--- a/package.json
+++ b/package.json
@@ -34,11 +34,11 @@
"dedupe-deps:check": "yarn-deduplicate yarn.lock --list --fail",
"dedupe-deps:fix": "yarn-deduplicate yarn.lock",
"postpublish": "nx run-many -t postpublish --parallel=1",
- "test": "nx run-many -t test --exclude \"@sentry-internal/{browser-integration-tests,bun-integration-tests,e2e-tests,integration-shims,node-integration-tests,node-core-integration-tests,cloudflare-integration-tests}\"",
+ "test": "nx run-many -t test --exclude \"@sentry-internal/{browser-integration-tests,bun-integration-tests,bundler-plugin-integration-tests,e2e-tests,integration-shims,node-integration-tests,node-core-integration-tests,cloudflare-integration-tests}\"",
"test:scripts": "vitest run scripts/*.test.ts",
- "test:unit": "nx run-many -t test:unit --exclude \"@sentry-internal/{browser-integration-tests,bun-integration-tests,e2e-tests,integration-shims,node-integration-tests,node-core-integration-tests,cloudflare-integration-tests}\"",
+ "test:unit": "nx run-many -t test:unit --exclude \"@sentry-internal/{browser-integration-tests,bun-integration-tests,bundler-plugin-integration-tests,e2e-tests,integration-shims,node-integration-tests,node-core-integration-tests,cloudflare-integration-tests}\"",
"test:update-snapshots": "nx run-many -t test:update-snapshots",
- "test:pr": "nx affected -t test --exclude \"@sentry-internal/{browser-integration-tests,bun-integration-tests,e2e-tests,integration-shims,node-integration-tests,node-core-integration-tests,cloudflare-integration-tests}\"",
+ "test:pr": "nx affected -t test --exclude \"@sentry-internal/{browser-integration-tests,bun-integration-tests,bundler-plugin-integration-tests,e2e-tests,integration-shims,node-integration-tests,node-core-integration-tests,cloudflare-integration-tests}\"",
"test:pr:browser": "UNIT_TEST_ENV=browser ts-node ./scripts/ci-unit-tests.ts --affected",
"test:pr:node": "UNIT_TEST_ENV=node ts-node ./scripts/ci-unit-tests.ts --affected",
"test:ci:browser": "UNIT_TEST_ENV=browser ts-node ./scripts/ci-unit-tests.ts",
@@ -59,6 +59,7 @@
"packages/browser",
"packages/browser-utils",
"packages/bun",
+ "packages/bundler-plugins",
"packages/core",
"packages/cloudflare",
"packages/deno",
@@ -100,6 +101,7 @@
"packages/vue",
"packages/wasm",
"dev-packages/browser-integration-tests",
+ "dev-packages/bundler-plugin-integration-tests",
"dev-packages/e2e-tests",
"dev-packages/node-integration-tests",
"dev-packages/bun-integration-tests",
diff --git a/packages/bundler-plugins/.gitignore b/packages/bundler-plugins/.gitignore
new file mode 100644
index 000000000000..2f291fa0da30
--- /dev/null
+++ b/packages/bundler-plugins/.gitignore
@@ -0,0 +1,4 @@
+dist
+
+# Generated by the `prebuild` script from the package version
+src/core/version.ts
diff --git a/packages/bundler-plugins/package.json b/packages/bundler-plugins/package.json
new file mode 100644
index 000000000000..6e01b1e2f236
--- /dev/null
+++ b/packages/bundler-plugins/package.json
@@ -0,0 +1,146 @@
+{
+ "name": "@sentry/bundler-plugins",
+ "version": "5.3.0",
+ "description": "Sentry Bundler Plugins",
+ "repository": "git://github.com/getsentry/sentry-javascript-bundler-plugins.git",
+ "homepage": "https://github.com/getsentry/sentry-javascript-bundler-plugins/tree/main/packages/bundler-plugins",
+ "author": "Sentry",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "files": [
+ "dist",
+ "sentry-release-injection-file.js",
+ "sentry-esbuild-debugid-injection-file.js"
+ ],
+ "exports": {
+ "./webpack": {
+ "types": "./dist/types/webpack/index.d.ts",
+ "import": "./dist/esm/webpack/index.mjs",
+ "require": "./dist/cjs/webpack/index.js"
+ },
+ "./webpack5": {
+ "types": "./dist/types/webpack/webpack5.d.ts",
+ "import": "./dist/esm/webpack/webpack5.mjs",
+ "require": "./dist/cjs/webpack/webpack5.js"
+ },
+ "./rollup": {
+ "types": "./dist/types/rollup/index.d.ts",
+ "import": "./dist/esm/rollup/index.mjs",
+ "require": "./dist/cjs/rollup/index.js"
+ },
+ "./vite": {
+ "types": "./dist/types/vite/index.d.ts",
+ "import": "./dist/esm/vite/index.mjs",
+ "require": "./dist/cjs/vite/index.js"
+ },
+ "./esbuild": {
+ "types": "./dist/types/esbuild/index.d.ts",
+ "import": "./dist/esm/esbuild/index.mjs",
+ "require": "./dist/cjs/esbuild/index.js"
+ },
+ "./core": {
+ "types": "./dist/types/core/index.d.ts",
+ "import": "./dist/esm/core/index.mjs",
+ "require": "./dist/cjs/core/index.js"
+ },
+ "./babel-plugin": {
+ "types": "./dist/types/babel-plugin/index.d.ts",
+ "import": "./dist/esm/babel-plugin/index.mjs",
+ "require": "./dist/cjs/babel-plugin/index.js"
+ },
+ "./sentry-release-injection-file": {
+ "import": "./sentry-release-injection-file.js",
+ "require": "./sentry-release-injection-file.js"
+ },
+ "./sentry-esbuild-debugid-injection-file": {
+ "import": "./sentry-esbuild-debugid-injection-file.js",
+ "require": "./sentry-esbuild-debugid-injection-file.js"
+ },
+ "./webpack-loader": {
+ "require": "./dist/cjs/webpack/component-annotation-transform.js"
+ }
+ },
+ "scripts": {
+ "prebuild": "node -p \"'export const LIB_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > src/core/version.ts",
+ "build": "yarn prebuild && premove ./dist && run-p build:rollup build:types && run-s build:tarball",
+ "build:dev": "yarn prebuild && run-p build:rollup build:types",
+ "build:transpile": "yarn prebuild && yarn build:rollup",
+ "build:watch": "run-p build:rollup:watch build:types:watch",
+ "build:rollup": "rolldown --config rollup.config.mjs",
+ "build:rollup:watch": "rolldown --config rollup.config.mjs --watch --no-watch.clearScreen",
+ "build:types": "tsc --project types.tsconfig.json",
+ "build:types:watch": "tsc --project types.tsconfig.json --watch --preserveWatchOutput",
+ "build:tarball": "npm pack",
+ "check:types": "run-p check:types:src check:types:test",
+ "check:types:src": "tsc --project ./tsconfig.json --noEmit",
+ "check:types:test": "tsc --project ./test/tsconfig.json --noEmit",
+ "clean": "run-s clean:build",
+ "clean:all": "run-p clean clean:deps",
+ "clean:build": "premove ./dist *.tgz",
+ "clean:deps": "premove node_modules",
+ "pretest": "yarn prebuild",
+ "test": "vitest run"
+ },
+ "dependencies": {
+ "@babel/core": "^7.18.5",
+ "@sentry/cli": "^2.58.6",
+ "dotenv": "^16.3.1",
+ "find-up": "^5.0.0",
+ "glob": "^13.0.6",
+ "magic-string": "~0.30.8"
+ },
+ "peerDependencies": {
+ "rollup": ">=3.2.0",
+ "webpack": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ },
+ "devDependencies": {
+ "@babel/preset-react": "^7.23.3",
+ "@sentry/core": "10.56.0",
+ "@sentry/types": "10.56.0",
+ "@types/babel__core": "^7.20.5",
+ "@types/node": "^18.6.3",
+ "@types/webpack": "npm:@types/webpack@^4",
+ "premove": "^4.0.0",
+ "rolldown": "^1.0.0",
+ "vitest": "^3.2.4",
+ "webpack": "^5.95.0"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "sideEffects": [
+ "./sentry-release-injection-file.js",
+ "./sentry-esbuild-debugid-injection-file.js"
+ ],
+ "nx": {
+ "targets": {
+ "build:transpile": {
+ "outputs": [
+ "{projectRoot}/dist"
+ ]
+ },
+ "build:types": {
+ "dependsOn": [
+ "^build:types",
+ "build:transpile"
+ ],
+ "outputs": [
+ "{projectRoot}/dist/types"
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/bundler-plugins/rollup.config.mjs b/packages/bundler-plugins/rollup.config.mjs
new file mode 100644
index 000000000000..984d838ba0cf
--- /dev/null
+++ b/packages/bundler-plugins/rollup.config.mjs
@@ -0,0 +1,60 @@
+import packageJson from './package.json' with { type: 'json' };
+import modulePackage from 'module';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const srcDir = path.resolve(__dirname, 'src');
+
+const external = [
+ ...Object.keys(packageJson.dependencies || {}),
+ ...modulePackage.builtinModules,
+ 'webpack',
+ 'rollup',
+ 'vite',
+];
+
+export default {
+ platform: 'node',
+ input: [
+ 'src/babel-plugin/index.ts',
+ 'src/core/index.ts',
+ 'src/rollup/index.ts',
+ 'src/vite/index.ts',
+ 'src/esbuild/index.ts',
+ 'src/webpack/index.ts',
+ 'src/webpack/webpack5.ts',
+ 'src/webpack/component-annotation-transform.ts',
+ ],
+ external,
+ output: [
+ {
+ dir: './dist/esm',
+ format: 'esm',
+ exports: 'named',
+ sourcemap: true,
+ entryFileNames: chunkInfo => {
+ if (chunkInfo.facadeModuleId) {
+ const rel = path.relative(srcDir, chunkInfo.facadeModuleId);
+ return rel.replace(/\.ts$/, '.mjs');
+ }
+ return '[name].mjs';
+ },
+ chunkFileNames: '_chunks/[name]-[hash].mjs',
+ },
+ {
+ dir: './dist/cjs',
+ format: 'cjs',
+ exports: 'named',
+ sourcemap: true,
+ entryFileNames: chunkInfo => {
+ if (chunkInfo.facadeModuleId) {
+ const rel = path.relative(srcDir, chunkInfo.facadeModuleId);
+ return rel.replace(/\.ts$/, '.js');
+ }
+ return '[name].js';
+ },
+ chunkFileNames: '_chunks/[name]-[hash].js',
+ },
+ ],
+};
diff --git a/packages/bundler-plugins/sentry-esbuild-debugid-injection-file.js b/packages/bundler-plugins/sentry-esbuild-debugid-injection-file.js
new file mode 100644
index 000000000000..23fdd648eb75
--- /dev/null
+++ b/packages/bundler-plugins/sentry-esbuild-debugid-injection-file.js
@@ -0,0 +1,20 @@
+try {
+ let globalObject =
+ 'undefined' != typeof window
+ ? window
+ : 'undefined' != typeof global
+ ? global
+ : 'undefined' != typeof globalThis
+ ? global
+ : 'undefined' != typeof self
+ ? self
+ : {};
+
+ let stack = new globalObject.Error().stack;
+
+ if (stack) {
+ globalObject._sentryDebugIds = globalObject._sentryDebugIds || {};
+ globalObject._sentryDebugIds[stack] = '__SENTRY_DEBUG_ID__';
+ globalObject._sentryDebugIdIdentifier = 'sentry-dbid-__SENTRY_DEBUG_ID__';
+ }
+} catch {}
diff --git a/packages/bundler-plugins/sentry-release-injection-file.js b/packages/bundler-plugins/sentry-release-injection-file.js
new file mode 100644
index 000000000000..51de6958a7c0
--- /dev/null
+++ b/packages/bundler-plugins/sentry-release-injection-file.js
@@ -0,0 +1,4 @@
+// This const is used for nothing except to make this file identifiable via its content.
+// We search for "_sentry_release_injection_file" in the plugin to determine for sure that the file we look at is the release injection file.
+
+// _sentry_release_injection_file
diff --git a/packages/bundler-plugins/src/babel-plugin/constants.ts b/packages/bundler-plugins/src/babel-plugin/constants.ts
new file mode 100644
index 000000000000..dc229f331e8b
--- /dev/null
+++ b/packages/bundler-plugins/src/babel-plugin/constants.ts
@@ -0,0 +1,146 @@
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020 Engineering at FullStory
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+export const KNOWN_INCOMPATIBLE_PLUGINS = [
+ // This module might be causing an issue preventing clicks. For safety, we won't run on this module.
+ 'react-native-testfairy',
+ // This module checks for unexpected property keys and throws an exception.
+ '@react-navigation',
+];
+
+export const DEFAULT_IGNORED_ELEMENTS = [
+ 'a',
+ 'abbr',
+ 'address',
+ 'area',
+ 'article',
+ 'aside',
+ 'audio',
+ 'b',
+ 'base',
+ 'bdi',
+ 'bdo',
+ 'blockquote',
+ 'body',
+ 'br',
+ 'button',
+ 'canvas',
+ 'caption',
+ 'cite',
+ 'code',
+ 'col',
+ 'colgroup',
+ 'data',
+ 'datalist',
+ 'dd',
+ 'del',
+ 'details',
+ 'dfn',
+ 'dialog',
+ 'div',
+ 'dl',
+ 'dt',
+ 'em',
+ 'embed',
+ 'fieldset',
+ 'figure',
+ 'footer',
+ 'form',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'head',
+ 'header',
+ 'hgroup',
+ 'hr',
+ 'html',
+ 'i',
+ 'iframe',
+ 'img',
+ 'input',
+ 'ins',
+ 'kbd',
+ 'keygen',
+ 'label',
+ 'legend',
+ 'li',
+ 'link',
+ 'main',
+ 'map',
+ 'mark',
+ 'menu',
+ 'menuitem',
+ 'meter',
+ 'nav',
+ 'noscript',
+ 'object',
+ 'ol',
+ 'optgroup',
+ 'option',
+ 'output',
+ 'p',
+ 'param',
+ 'pre',
+ 'progress',
+ 'q',
+ 'rb',
+ 'rp',
+ 'rt',
+ 'rtc',
+ 'ruby',
+ 's',
+ 'samp',
+ 'script',
+ 'section',
+ 'select',
+ 'small',
+ 'source',
+ 'span',
+ 'strong',
+ 'style',
+ 'sub',
+ 'summary',
+ 'sup',
+ 'table',
+ 'tbody',
+ 'td',
+ 'template',
+ 'textarea',
+ 'tfoot',
+ 'th',
+ 'thead',
+ 'time',
+ 'title',
+ 'tr',
+ 'track',
+ 'u',
+ 'ul',
+ 'var',
+ 'video',
+ 'wbr',
+];
diff --git a/packages/bundler-plugins/src/babel-plugin/experimental.ts b/packages/bundler-plugins/src/babel-plugin/experimental.ts
new file mode 100644
index 000000000000..a5d05ec7ab6e
--- /dev/null
+++ b/packages/bundler-plugins/src/babel-plugin/experimental.ts
@@ -0,0 +1,583 @@
+/* oxlint-disable max-lines */
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020 Engineering at FullStory
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+/**
+ * The following code is based on the FullStory Babel plugin, but has been modified to work
+ * with Sentry products:
+ *
+ * - Added `sentry` to data properties, i.e `data-sentry-component`
+ * - Converted to TypeScript
+ * - Code cleanups
+ * - Highly modified to inject the data attributes into the root HTML elements of a component.
+ */
+
+import type * as Babel from '@babel/core';
+import type { PluginObj, PluginPass } from '@babel/core';
+
+const REACT_NATIVE_ELEMENTS: string[] = [
+ 'Image',
+ 'Text',
+ 'View',
+ 'ScrollView',
+ 'TextInput',
+ 'TouchableOpacity',
+ 'TouchableHighlight',
+ 'TouchableWithoutFeedback',
+ 'FlatList',
+ 'SectionList',
+ 'ActivityIndicator',
+ 'Button',
+ 'Switch',
+ 'Modal',
+ 'SafeAreaView',
+ 'StatusBar',
+ 'KeyboardAvoidingView',
+ 'RefreshControl',
+ 'Picker',
+ 'Slider',
+];
+
+interface AnnotationOpts {
+ native?: boolean;
+ ignoredComponents?: string[];
+}
+
+interface FragmentContext {
+ fragmentAliases: Set;
+ reactNamespaceAliases: Set;
+}
+
+interface AnnotationPluginPass extends PluginPass {
+ opts: AnnotationOpts;
+ sentryFragmentContext?: FragmentContext;
+}
+
+type AnnotationPlugin = PluginObj;
+
+// Shared context object for all JSX processing functions
+interface JSXProcessingContext {
+ /** Babel types object */
+ t: typeof Babel.types;
+ /** Name of the React component */
+ componentName: string;
+ /** AAttribute name for the component */
+ attributeName: string;
+ /** Array of component names to ignore */
+ ignoredComponents: string[];
+ /** Fragment context for identifying React fragments */
+ fragmentContext?: FragmentContext;
+}
+
+// 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 {
+ return {
+ visitor: {
+ Program: {
+ enter(path, state) {
+ const fragmentContext = collectFragmentContext(path);
+ state.sentryFragmentContext = fragmentContext;
+ },
+ },
+ FunctionDeclaration(path, state) {
+ if (!path.node.id?.name) {
+ return;
+ }
+
+ const context = createJSXProcessingContext(state, t, path.node.id.name);
+ functionBodyPushAttributes(context, path);
+ },
+ ArrowFunctionExpression(path, state) {
+ // We're expecting a `VariableDeclarator` like `const MyComponent =`
+ const parent = path.parent;
+
+ if (!parent || !('id' in parent) || !parent.id || !('name' in parent.id) || !parent.id.name) {
+ return;
+ }
+
+ const context = createJSXProcessingContext(state, t, parent.id.name);
+ functionBodyPushAttributes(context, path);
+ },
+ ClassDeclaration(path, state) {
+ const name = path.get('id');
+ const properties = path.get('body').get('body');
+ const render = properties.find(prop => {
+ return prop.isClassMethod() && prop.get('key').isIdentifier({ name: 'render' });
+ });
+
+ if (!render?.traverse) {
+ return;
+ }
+
+ const context = createJSXProcessingContext(state, t, name.node?.name || '');
+
+ render.traverse({
+ ReturnStatement(returnStatement) {
+ const arg = returnStatement.get('argument');
+
+ if (!arg.isJSXElement() && !arg.isJSXFragment()) {
+ return;
+ }
+
+ processJSX(context, arg);
+ },
+ });
+ },
+ },
+ };
+}
+
+/**
+ * Checks if an element name represents an HTML element (as opposed to a React component).
+ * HTML elements include standard lowercase HTML tags and React Native elements.
+ */
+function isHtmlElement(elementName: string): boolean {
+ // Unknown elements are not HTML elements
+ if (elementName === UNKNOWN_ELEMENT_NAME) {
+ return false;
+ }
+
+ // Check for lowercase first letter (standard HTML elements)
+ if (elementName.length > 0 && elementName.charAt(0) === elementName.charAt(0).toLowerCase()) {
+ return true;
+ }
+
+ // React Native elements typically start with uppercase but are still "native" elements
+ // We consider them HTML-like elements for annotation purposes
+ if (REACT_NATIVE_ELEMENTS.includes(elementName)) {
+ return true;
+ }
+
+ // Otherwise, assume it's a React component (PascalCase)
+ return false;
+}
+
+/**
+ * Creates a JSX processing context from the plugin state
+ */
+function createJSXProcessingContext(
+ state: AnnotationPluginPass,
+ t: typeof Babel.types,
+ componentName: string,
+): JSXProcessingContext {
+ return {
+ t,
+ componentName,
+ attributeName: attributeNamesFromState(state),
+ ignoredComponents: state.opts.ignoredComponents ?? [],
+ fragmentContext: state.sentryFragmentContext,
+ };
+}
+
+/**
+ * Processes the body of a function to add Sentry tracking attributes to JSX elements.
+ * Handles various function body structures including direct JSX returns, conditional expressions,
+ * and nested JSX elements.
+ */
+function functionBodyPushAttributes(context: JSXProcessingContext, path: Babel.NodePath): void {
+ let jsxNode: Babel.NodePath;
+
+ const functionBody = path.get('body').get('body');
+
+ if (
+ !('length' in functionBody) &&
+ functionBody.parent &&
+ (functionBody.parent.type === 'JSXElement' || functionBody.parent.type === 'JSXFragment')
+ ) {
+ const maybeJsxNode = functionBody.find(c => {
+ return c.type === 'JSXElement' || c.type === 'JSXFragment';
+ });
+
+ if (!maybeJsxNode) {
+ return;
+ }
+
+ jsxNode = maybeJsxNode;
+ } else {
+ const returnStatement = functionBody.find(c => {
+ return c.type === 'ReturnStatement';
+ });
+ if (!returnStatement) {
+ return;
+ }
+
+ const arg = returnStatement.get('argument');
+ if (!arg) {
+ return;
+ }
+
+ if (Array.isArray(arg)) {
+ return;
+ }
+
+ // Handle the case of a function body returning a ternary operation.
+ // `return (maybeTrue ? '' : ())`
+ if (arg.isConditionalExpression()) {
+ const consequent = arg.get('consequent');
+ if (consequent.isJSXFragment() || consequent.isJSXElement()) {
+ processJSX(context, consequent);
+ }
+ const alternate = arg.get('alternate');
+ if (alternate.isJSXFragment() || alternate.isJSXElement()) {
+ processJSX(context, alternate);
+ }
+ return;
+ }
+
+ if (!arg.isJSXFragment() && !arg.isJSXElement()) {
+ return;
+ }
+
+ jsxNode = arg;
+ }
+
+ if (!jsxNode) {
+ return;
+ }
+
+ processJSX(context, jsxNode);
+}
+
+/**
+ * Recursively processes JSX elements to add Sentry tracking attributes.
+ * Handles both JSX elements and fragments, applying appropriate attributes
+ * based on configuration and component context.
+ */
+function processJSX(context: JSXProcessingContext, jsxNode: Babel.NodePath): void {
+ if (!jsxNode) {
+ return;
+ }
+
+ // NOTE: I don't know of a case where `openingElement` would have more than one item,
+ // but it's safer to always iterate
+ const paths = jsxNode.get('openingElement');
+ const openingElements = (Array.isArray(paths) ? paths : [paths]) as Babel.NodePath[];
+
+ const hasInjectedAttributes = openingElements.reduce(
+ (prev, openingElement) => prev || applyAttributes(context, openingElement, context.componentName),
+ false,
+ );
+
+ if (hasInjectedAttributes) {
+ return;
+ }
+
+ let children = jsxNode.get('children');
+ // TODO: See why `Array.isArray` doesn't have correct behaviour here
+ if (children && !('length' in children)) {
+ // A single child was found, maybe a bit of static text
+ children = [children];
+ }
+
+ children.forEach(child => {
+ // Happens for some node types like plain text
+ if (!child.node) {
+ return;
+ }
+
+ // If the current element is a fragment, children are still considered at root level
+ // Otherwise, children are not at root level
+ const openingElement = child.get('openingElement');
+ // TODO: Improve this. We never expect to have multiple opening elements
+ // but if it's possible, this should work
+ if (Array.isArray(openingElement)) {
+ return;
+ }
+
+ processJSX(context, child);
+ });
+}
+
+/**
+ * Applies Sentry tracking attributes to a JSX opening element.
+ * Adds component name, element name, and source file attributes while
+ * respecting ignore lists and fragment detection.
+ */
+function applyAttributes(
+ context: JSXProcessingContext,
+ openingElement: Babel.NodePath,
+ componentName: string,
+): boolean {
+ const { t, attributeName: componentAttributeName, ignoredComponents, fragmentContext } = context;
+
+ // e.g., Raw JSX text like the `A` in `a
`
+ if (!openingElement.node) {
+ return false;
+ }
+
+ // Check if this is a React fragment - if so, skip attribute addition entirely
+ const isFragment = isReactFragment(t, openingElement, fragmentContext);
+ if (isFragment) {
+ return false;
+ }
+
+ if (!openingElement.node.attributes) {
+ openingElement.node.attributes = [];
+ }
+
+ const elementName = getPathName(t, openingElement);
+
+ if (!isHtmlElement(elementName)) {
+ return false;
+ }
+
+ const isAnIgnoredComponent = ignoredComponents.some(
+ ignoredComponent => ignoredComponent === componentName || ignoredComponent === elementName,
+ );
+
+ // Add a stable attribute for the component name (only for root elements)
+ if (!isAnIgnoredComponent && !hasAttributeWithName(openingElement, componentAttributeName)) {
+ if (componentAttributeName) {
+ openingElement.node.attributes.push(
+ t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName)),
+ );
+ }
+ }
+
+ return true;
+}
+
+function attributeNamesFromState(state: AnnotationPluginPass): string {
+ if (state.opts.native) {
+ return 'dataSentryComponent';
+ }
+
+ return 'data-sentry-component';
+}
+
+function collectFragmentContext(programPath: Babel.NodePath): FragmentContext {
+ const fragmentAliases = new Set();
+ const reactNamespaceAliases = new Set(['React']); // Default React namespace
+
+ programPath.traverse({
+ ImportDeclaration(importPath) {
+ const source = importPath.node.source.value;
+
+ // Handle React imports
+ if (source === 'react' || source === 'React') {
+ importPath.node.specifiers.forEach(spec => {
+ if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
+ // Detect aliased React.Fragment imports (e.g., `Fragment as F`)
+ // so we can later identify as a fragment in JSX.
+ if (spec.imported.name === 'Fragment') {
+ fragmentAliases.add(spec.local.name);
+ }
+ } else if (spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
+ // import React from 'react' -> React OR
+ // import * as React from 'react' -> React
+ reactNamespaceAliases.add(spec.local.name);
+ }
+ });
+ }
+ },
+
+ // Handle simple variable assignments only (avoid complex cases)
+ VariableDeclarator(varPath) {
+ if (varPath.node.init) {
+ const init = varPath.node.init;
+
+ // Handle identifier assignments: const MyFragment = Fragment
+ if (varPath.node.id.type === 'Identifier') {
+ // Handle: const MyFragment = Fragment (only if Fragment is a known alias)
+ if (init.type === 'Identifier' && fragmentAliases.has(init.name)) {
+ fragmentAliases.add(varPath.node.id.name);
+ }
+
+ // Handle: const MyFragment = React.Fragment (only for known React namespaces)
+ if (
+ init.type === 'MemberExpression' &&
+ init.object.type === 'Identifier' &&
+ init.property.type === 'Identifier' &&
+ init.property.name === 'Fragment' &&
+ reactNamespaceAliases.has(init.object.name)
+ ) {
+ fragmentAliases.add(varPath.node.id.name);
+ }
+ }
+
+ // Handle destructuring assignments: const { Fragment } = React
+ if (varPath.node.id.type === 'ObjectPattern') {
+ if (init.type === 'Identifier' && reactNamespaceAliases.has(init.name)) {
+ const properties = varPath.node.id.properties;
+
+ for (const prop of properties) {
+ if (
+ prop.type === 'ObjectProperty' &&
+ prop.key?.type === 'Identifier' &&
+ prop.value?.type === 'Identifier' &&
+ prop.key.name === 'Fragment'
+ ) {
+ fragmentAliases.add(prop.value.name);
+ }
+ }
+ }
+ }
+ }
+ },
+ });
+
+ return { fragmentAliases, reactNamespaceAliases };
+}
+
+function isReactFragment(
+ t: typeof Babel.types,
+ openingElement: Babel.NodePath,
+ context?: FragmentContext, // Add this optional parameter
+): boolean {
+ // Handle JSX fragments (<>)
+ if (openingElement.isJSXFragment()) {
+ return true;
+ }
+
+ const elementName = getPathName(t, openingElement);
+
+ // Direct fragment references
+ if (elementName === 'Fragment' || elementName === 'React.Fragment') {
+ return true;
+ }
+
+ // TODO: All these objects are typed as unknown, maybe an oversight in Babel types?
+
+ // Check if the element name is a known fragment alias
+ if (context && elementName && context.fragmentAliases.has(elementName)) {
+ return true;
+ }
+
+ // Handle JSXMemberExpression
+ if (
+ openingElement.node &&
+ 'name' in openingElement.node &&
+ openingElement.node.name &&
+ typeof openingElement.node.name === 'object' &&
+ 'type' in openingElement.node.name &&
+ openingElement.node.name.type === 'JSXMemberExpression'
+ ) {
+ const nodeName = openingElement.node.name;
+ if (typeof nodeName !== 'object' || !nodeName) {
+ return false;
+ }
+
+ if ('object' in nodeName && 'property' in nodeName) {
+ const nodeNameObject = nodeName.object;
+ const nodeNameProperty = nodeName.property;
+
+ if (typeof nodeNameObject !== 'object' || typeof nodeNameProperty !== 'object') {
+ return false;
+ }
+
+ if (!nodeNameObject || !nodeNameProperty) {
+ return false;
+ }
+
+ const objectName = 'name' in nodeNameObject && nodeNameObject.name;
+ const propertyName = 'name' in nodeNameProperty && nodeNameProperty.name;
+
+ // React.Fragment check
+ if (objectName === 'React' && propertyName === 'Fragment') {
+ return true;
+ }
+
+ // Enhanced checks using context
+ if (context) {
+ // Check React.Fragment pattern with known React namespaces
+ if (context.reactNamespaceAliases.has(objectName as string) && propertyName === 'Fragment') {
+ return true;
+ }
+
+ // Check MyFragment.Fragment pattern
+ if (context.fragmentAliases.has(objectName as string) && propertyName === 'Fragment') {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+function hasAttributeWithName(
+ openingElement: Babel.NodePath,
+ name: string | undefined | null,
+): boolean {
+ if (!name) {
+ return false;
+ }
+
+ return openingElement.node.attributes.some(node => {
+ if (node.type === 'JSXAttribute') {
+ return node.name.name === name;
+ }
+
+ return false;
+ });
+}
+
+function getPathName(t: typeof Babel.types, path: Babel.NodePath): string {
+ if (!path.node) return UNKNOWN_ELEMENT_NAME;
+ if (!('name' in path.node)) {
+ return UNKNOWN_ELEMENT_NAME;
+ }
+
+ const name = path.node.name;
+
+ if (typeof name === 'string') {
+ return name;
+ }
+
+ if (t.isIdentifier(name) || t.isJSXIdentifier(name)) {
+ return name.name;
+ }
+
+ if (t.isJSXNamespacedName(name)) {
+ return name.name.name;
+ }
+
+ // Handle JSX member expressions like Tab.Group
+ if (t.isJSXMemberExpression(name)) {
+ const objectName = getJSXMemberExpressionObjectName(t, name.object);
+ const propertyName = name.property.name;
+ return `${objectName}.${propertyName}`;
+ }
+
+ return UNKNOWN_ELEMENT_NAME;
+}
+
+// Recursively handle nested member expressions (e.g. Components.UI.Header)
+function getJSXMemberExpressionObjectName(
+ t: typeof Babel.types,
+ object: Babel.types.JSXMemberExpression | Babel.types.JSXIdentifier,
+): string {
+ if (t.isJSXIdentifier(object)) {
+ return object.name;
+ }
+ if (t.isJSXMemberExpression(object)) {
+ const objectName = getJSXMemberExpressionObjectName(t, object.object);
+ return `${objectName}.${object.property.name}`;
+ }
+
+ return UNKNOWN_ELEMENT_NAME;
+}
+
+const UNKNOWN_ELEMENT_NAME = 'unknown';
diff --git a/packages/bundler-plugins/src/babel-plugin/index.ts b/packages/bundler-plugins/src/babel-plugin/index.ts
new file mode 100644
index 000000000000..fa3884f8dcaa
--- /dev/null
+++ b/packages/bundler-plugins/src/babel-plugin/index.ts
@@ -0,0 +1,846 @@
+/* oxlint-disable max-lines */
+/**
+ * MIT License
+ *
+ * Copyright (c) 2020 Engineering at FullStory
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+/**
+ * The following code is based on the FullStory Babel plugin, but has been modified to work
+ * with Sentry products:
+ *
+ * - Added `sentry` to data properties, i.e `data-sentry-component`
+ * - Converted to TypeScript
+ * - Code cleanups
+ */
+
+import type * as Babel from '@babel/core';
+import type { PluginObj, PluginPass } from '@babel/core';
+
+import { DEFAULT_IGNORED_ELEMENTS, KNOWN_INCOMPATIBLE_PLUGINS } from './constants';
+
+const webComponentName = 'data-sentry-component';
+const webElementName = 'data-sentry-element';
+const webSourceFileName = 'data-sentry-source-file';
+
+const nativeComponentName = 'dataSentryComponent';
+const nativeElementName = 'dataSentryElement';
+const nativeSourceFileName = 'dataSentrySourceFile';
+
+const SENTRY_LABEL_ATTRIBUTE = 'sentry-label';
+const MAX_LABEL_LENGTH = 64;
+const DEFAULT_TEXT_COMPONENT_NAMES = ['Text', 'text'];
+const MAX_TEXT_SEARCH_DEPTH = 3;
+
+interface AutoInjectSentryLabelOpts {
+ textComponentNames?: string[];
+}
+
+interface AnnotationOpts {
+ native?: boolean;
+ 'annotate-fragments'?: boolean;
+ ignoredComponents?: string[];
+ /** @hidden */
+ autoInjectSentryLabel?: boolean | AutoInjectSentryLabelOpts;
+}
+
+interface FragmentContext {
+ fragmentAliases: Set;
+ reactNamespaceAliases: Set;
+}
+
+interface AnnotationPluginPass extends PluginPass {
+ opts: AnnotationOpts;
+ sentryFragmentContext?: FragmentContext;
+}
+
+type AnnotationPlugin = PluginObj;
+
+// Shared context object for all JSX processing functions
+interface JSXProcessingContext {
+ /** Whether to annotate React fragments */
+ annotateFragments: boolean;
+ /** Babel types object */
+ t: typeof Babel.types;
+ /** Name of the React component */
+ componentName: string;
+ /** Source file name (optional) */
+ sourceFileName?: string;
+ /** Array of attribute names [component, element, sourceFile] */
+ attributeNames: string[];
+ /** Array of component names to ignore */
+ ignoredComponents: string[];
+ /** Fragment context for identifying React fragments */
+ fragmentContext?: FragmentContext;
+ /** Whether to auto-inject sentry-label from static text children */
+ autoInjectSentryLabel: boolean;
+ /** Component names whose JSXText children are considered text content */
+ textComponentNames: string[];
+}
+
+export { experimentalComponentNameAnnotatePlugin } from './experimental';
+
+// 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 {
+ return {
+ visitor: {
+ Program: {
+ enter(path, state) {
+ const fragmentContext = collectFragmentContext(path);
+ state.sentryFragmentContext = fragmentContext;
+ },
+ },
+ FunctionDeclaration(path, state) {
+ if (!path.node.id?.name) {
+ return;
+ }
+ if (isKnownIncompatiblePluginFromState(state)) {
+ return;
+ }
+
+ const context = createJSXProcessingContext(state, t, path.node.id.name);
+ functionBodyPushAttributes(context, path);
+ },
+ ArrowFunctionExpression(path, state) {
+ // We're expecting a `VariableDeclarator` like `const MyComponent =`
+ const parent = path.parent;
+
+ if (!parent || !('id' in parent) || !parent.id || !('name' in parent.id) || !parent.id.name) {
+ return;
+ }
+
+ if (isKnownIncompatiblePluginFromState(state)) {
+ return;
+ }
+
+ const context = createJSXProcessingContext(state, t, parent.id.name);
+ functionBodyPushAttributes(context, path);
+ },
+ ClassDeclaration(path, state) {
+ const name = path.get('id');
+ const properties = path.get('body').get('body');
+ const render = properties.find(prop => {
+ return prop.isClassMethod() && prop.get('key').isIdentifier({ name: 'render' });
+ });
+
+ if (!render?.traverse || isKnownIncompatiblePluginFromState(state)) {
+ return;
+ }
+
+ const context = createJSXProcessingContext(state, t, name.node?.name || '');
+
+ render.traverse({
+ ReturnStatement(returnStatement) {
+ const arg = returnStatement.get('argument');
+
+ if (!arg.isJSXElement() && !arg.isJSXFragment()) {
+ return;
+ }
+
+ processJSX(context, arg);
+ },
+ });
+ },
+ },
+ };
+}
+
+/**
+ * Creates a JSX processing context from the plugin state
+ */
+function createJSXProcessingContext(
+ state: AnnotationPluginPass,
+ t: typeof Babel.types,
+ componentName: string,
+): JSXProcessingContext {
+ return {
+ annotateFragments: state.opts['annotate-fragments'] === true,
+ t,
+ componentName,
+ sourceFileName: sourceFileNameFromState(state),
+ attributeNames: attributeNamesFromState(state),
+ ignoredComponents: state.opts.ignoredComponents ?? [],
+ fragmentContext: state.sentryFragmentContext,
+ autoInjectSentryLabel: !!state.opts.autoInjectSentryLabel,
+ textComponentNames:
+ (state.opts.autoInjectSentryLabel && typeof state.opts.autoInjectSentryLabel === 'object'
+ ? state.opts.autoInjectSentryLabel.textComponentNames
+ : undefined) ?? DEFAULT_TEXT_COMPONENT_NAMES,
+ };
+}
+
+/**
+ * Processes the body of a function to add Sentry tracking attributes to JSX elements.
+ * Handles various function body structures including direct JSX returns, conditional expressions,
+ * and nested JSX elements.
+ */
+function functionBodyPushAttributes(context: JSXProcessingContext, path: Babel.NodePath): void {
+ let jsxNode: Babel.NodePath;
+
+ const functionBody = path.get('body').get('body');
+
+ if (
+ !('length' in functionBody) &&
+ functionBody.parent &&
+ (functionBody.parent.type === 'JSXElement' || functionBody.parent.type === 'JSXFragment')
+ ) {
+ const maybeJsxNode = functionBody.find(c => {
+ return c.type === 'JSXElement' || c.type === 'JSXFragment';
+ });
+
+ if (!maybeJsxNode) {
+ return;
+ }
+
+ jsxNode = maybeJsxNode;
+ } else {
+ const returnStatement = functionBody.find(c => {
+ return c.type === 'ReturnStatement';
+ });
+ if (!returnStatement) {
+ return;
+ }
+
+ const arg = returnStatement.get('argument');
+ if (!arg) {
+ return;
+ }
+
+ if (Array.isArray(arg)) {
+ return;
+ }
+
+ // Handle the case of a function body returning a ternary operation.
+ // `return (maybeTrue ? '' : ())`
+ if (arg.isConditionalExpression()) {
+ const consequent = arg.get('consequent');
+ if (consequent.isJSXFragment() || consequent.isJSXElement()) {
+ processJSX(context, consequent);
+ }
+ const alternate = arg.get('alternate');
+ if (alternate.isJSXFragment() || alternate.isJSXElement()) {
+ processJSX(context, alternate);
+ }
+ return;
+ }
+
+ if (!arg.isJSXFragment() && !arg.isJSXElement()) {
+ return;
+ }
+
+ jsxNode = arg;
+ }
+
+ if (!jsxNode) {
+ return;
+ }
+
+ processJSX(context, jsxNode);
+}
+
+/**
+ * Recursively processes JSX elements to add Sentry tracking attributes.
+ * Handles both JSX elements and fragments, applying appropriate attributes
+ * based on configuration and component context.
+ */
+function processJSX(context: JSXProcessingContext, jsxNode: Babel.NodePath, componentName?: string): void {
+ if (!jsxNode) {
+ return;
+ }
+
+ // Use provided componentName or fall back to context componentName
+ const currentComponentName = componentName ?? context.componentName;
+ const isRootElement = componentName === undefined;
+
+ // NOTE: I don't know of a case where `openingElement` would have more than one item,
+ // but it's safer to always iterate
+ const paths = jsxNode.get('openingElement');
+ const openingElements = Array.isArray(paths) ? paths : [paths];
+
+ openingElements.forEach(openingElement => {
+ applyAttributes(context, openingElement as Babel.NodePath, currentComponentName);
+ });
+
+ let children = jsxNode.get('children');
+ // TODO: See why `Array.isArray` doesn't have correct behaviour here
+ if (children && !('length' in children)) {
+ // A single child was found, maybe a bit of static text
+ children = [children];
+ }
+
+ let shouldSetComponentName = context.annotateFragments;
+
+ children.forEach(child => {
+ // Happens for some node types like plain text
+ if (!child.node) {
+ return;
+ }
+
+ // Children don't receive the data-component attribute so we pass null for componentName unless it's the first child of a Fragment with a node and `annotateFragments` is true
+ const openingElement = child.get('openingElement');
+ // TODO: Improve this. We never expect to have multiple opening elements
+ // but if it's possible, this should work
+ if (Array.isArray(openingElement)) {
+ return;
+ }
+
+ if (shouldSetComponentName && openingElement?.node) {
+ shouldSetComponentName = false;
+ processJSX(context, child, currentComponentName);
+ } else {
+ processJSX(context, child, '');
+ }
+ });
+
+ if (isRootElement && context.autoInjectSentryLabel) {
+ maybeInjectSentryLabel(context, jsxNode);
+ }
+}
+
+/**
+ * Applies Sentry tracking attributes to a JSX opening element.
+ * Adds component name, element name, and source file attributes while
+ * respecting ignore lists and fragment detection.
+ */
+function applyAttributes(
+ context: JSXProcessingContext,
+ openingElement: Babel.NodePath,
+ componentName: string,
+): void {
+ const { t, attributeNames, ignoredComponents, fragmentContext, sourceFileName } = context;
+ const [componentAttributeName, elementAttributeName, sourceFileAttributeName] = attributeNames;
+
+ // e.g., Raw JSX text like the `A` in `a
`
+ if (!openingElement.node) {
+ return;
+ }
+
+ // Check if this is a React fragment - if so, skip attribute addition entirely
+ const isFragment = isReactFragment(t, openingElement, fragmentContext);
+ if (isFragment) {
+ return;
+ }
+
+ if (!openingElement.node.attributes) openingElement.node.attributes = [];
+ const elementName = getPathName(t, openingElement);
+
+ const isAnIgnoredComponent = ignoredComponents.some(
+ ignoredComponent => ignoredComponent === componentName || ignoredComponent === elementName,
+ );
+
+ // Add a stable attribute for the element name but only for non-DOM names
+ let isAnIgnoredElement = false;
+ if (!isAnIgnoredComponent && !hasAttributeWithName(openingElement, elementAttributeName)) {
+ if (DEFAULT_IGNORED_ELEMENTS.includes(elementName)) {
+ isAnIgnoredElement = true;
+ } else {
+ // Always add element attribute for non-ignored elements
+ if (elementAttributeName) {
+ openingElement.node.attributes.push(
+ t.jSXAttribute(t.jSXIdentifier(elementAttributeName), t.stringLiteral(elementName)),
+ );
+ }
+ }
+ }
+
+ // Add a stable attribute for the component name (absent for non-root elements)
+ if (componentName && !isAnIgnoredComponent && !hasAttributeWithName(openingElement, componentAttributeName)) {
+ if (componentAttributeName) {
+ openingElement.node.attributes.push(
+ t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName)),
+ );
+ }
+ }
+
+ // Add a stable attribute for the source file name
+ // Updated condition: add source file for elements that have either:
+ // 1. A component name (root elements), OR
+ // 2. An element name that's not ignored (child elements)
+ if (
+ sourceFileName &&
+ !isAnIgnoredComponent &&
+ (componentName || !isAnIgnoredElement) &&
+ !hasAttributeWithName(openingElement, sourceFileAttributeName)
+ ) {
+ if (sourceFileAttributeName) {
+ openingElement.node.attributes.push(
+ t.jSXAttribute(t.jSXIdentifier(sourceFileAttributeName), t.stringLiteral(sourceFileName)),
+ );
+ }
+ }
+}
+
+function sourceFileNameFromState(state: AnnotationPluginPass): string | undefined {
+ const name = fullSourceFileNameFromState(state);
+ if (!name) {
+ return undefined;
+ }
+
+ if (name.indexOf('/') !== -1) {
+ return name.split('/').pop();
+ } else if (name.indexOf('\\') !== -1) {
+ return name.split('\\').pop();
+ } else {
+ return name;
+ }
+}
+
+function fullSourceFileNameFromState(state: AnnotationPluginPass): string | null {
+ // @ts-expect-error This type is incorrect in Babel, `sourceFileName` is the correct type
+ const name = state.file.opts.parserOpts?.sourceFileName as unknown;
+
+ if (typeof name === 'string') {
+ return name;
+ }
+
+ return null;
+}
+
+function isKnownIncompatiblePluginFromState(state: AnnotationPluginPass): boolean {
+ const fullSourceFileName = fullSourceFileNameFromState(state);
+
+ if (!fullSourceFileName) {
+ return false;
+ }
+
+ return KNOWN_INCOMPATIBLE_PLUGINS.some(pluginName => {
+ if (
+ fullSourceFileName.includes(`/node_modules/${pluginName}/`) ||
+ fullSourceFileName.includes(`\\node_modules\\${pluginName}\\`)
+ ) {
+ return true;
+ }
+
+ return false;
+ });
+}
+
+function attributeNamesFromState(state: AnnotationPluginPass): [string, string, string] {
+ if (state.opts.native) {
+ return [nativeComponentName, nativeElementName, nativeSourceFileName];
+ }
+
+ return [webComponentName, webElementName, webSourceFileName];
+}
+
+function collectFragmentContext(programPath: Babel.NodePath): FragmentContext {
+ const fragmentAliases = new Set();
+ const reactNamespaceAliases = new Set(['React']); // Default React namespace
+
+ programPath.traverse({
+ ImportDeclaration(importPath) {
+ const source = importPath.node.source.value;
+
+ // Handle React imports
+ if (source === 'react' || source === 'React') {
+ importPath.node.specifiers.forEach(spec => {
+ if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
+ // Detect aliased React.Fragment imports (e.g., `Fragment as F`)
+ // so we can later identify as a fragment in JSX.
+ if (spec.imported.name === 'Fragment') {
+ fragmentAliases.add(spec.local.name);
+ }
+ } else if (spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
+ // import React from 'react' -> React OR
+ // import * as React from 'react' -> React
+ reactNamespaceAliases.add(spec.local.name);
+ }
+ });
+ }
+ },
+
+ // Handle simple variable assignments only (avoid complex cases)
+ VariableDeclarator(varPath) {
+ if (varPath.node.init) {
+ const init = varPath.node.init;
+
+ // Handle identifier assignments: const MyFragment = Fragment
+ if (varPath.node.id.type === 'Identifier') {
+ // Handle: const MyFragment = Fragment (only if Fragment is a known alias)
+ if (init.type === 'Identifier' && fragmentAliases.has(init.name)) {
+ fragmentAliases.add(varPath.node.id.name);
+ }
+
+ // Handle: const MyFragment = React.Fragment (only for known React namespaces)
+ if (
+ init.type === 'MemberExpression' &&
+ init.object.type === 'Identifier' &&
+ init.property.type === 'Identifier' &&
+ init.property.name === 'Fragment' &&
+ reactNamespaceAliases.has(init.object.name)
+ ) {
+ fragmentAliases.add(varPath.node.id.name);
+ }
+ }
+
+ // Handle destructuring assignments: const { Fragment } = React
+ if (varPath.node.id.type === 'ObjectPattern') {
+ if (init.type === 'Identifier' && reactNamespaceAliases.has(init.name)) {
+ const properties = varPath.node.id.properties;
+
+ for (const prop of properties) {
+ if (
+ prop.type === 'ObjectProperty' &&
+ prop.key?.type === 'Identifier' &&
+ prop.value?.type === 'Identifier' &&
+ prop.key.name === 'Fragment'
+ ) {
+ fragmentAliases.add(prop.value.name);
+ }
+ }
+ }
+ }
+ }
+ },
+ });
+
+ return { fragmentAliases, reactNamespaceAliases };
+}
+
+function isReactFragment(
+ t: typeof Babel.types,
+ openingElement: Babel.NodePath,
+ context?: FragmentContext, // Add this optional parameter
+): boolean {
+ // Handle JSX fragments (<>)
+ if (openingElement.isJSXFragment()) {
+ return true;
+ }
+
+ const elementName = getPathName(t, openingElement);
+
+ // Direct fragment references
+ if (elementName === 'Fragment' || elementName === 'React.Fragment') {
+ return true;
+ }
+
+ // TODO: All these objects are typed as unknown, maybe an oversight in Babel types?
+
+ // Check if the element name is a known fragment alias
+ if (context && elementName && context.fragmentAliases.has(elementName)) {
+ return true;
+ }
+
+ // Handle JSXMemberExpression
+ if (
+ openingElement.node &&
+ 'name' in openingElement.node &&
+ openingElement.node.name &&
+ typeof openingElement.node.name === 'object' &&
+ 'type' in openingElement.node.name &&
+ openingElement.node.name.type === 'JSXMemberExpression'
+ ) {
+ const nodeName = openingElement.node.name;
+ if (typeof nodeName !== 'object' || !nodeName) {
+ return false;
+ }
+
+ if ('object' in nodeName && 'property' in nodeName) {
+ const nodeNameObject = nodeName.object;
+ const nodeNameProperty = nodeName.property;
+
+ if (typeof nodeNameObject !== 'object' || typeof nodeNameProperty !== 'object') {
+ return false;
+ }
+
+ if (!nodeNameObject || !nodeNameProperty) {
+ return false;
+ }
+
+ const objectName = 'name' in nodeNameObject && nodeNameObject.name;
+ const propertyName = 'name' in nodeNameProperty && nodeNameProperty.name;
+
+ // React.Fragment check
+ if (objectName === 'React' && propertyName === 'Fragment') {
+ return true;
+ }
+
+ // Enhanced checks using context
+ if (context) {
+ // Check React.Fragment pattern with known React namespaces
+ if (context.reactNamespaceAliases.has(objectName as string) && propertyName === 'Fragment') {
+ return true;
+ }
+
+ // Check MyFragment.Fragment pattern
+ if (context.fragmentAliases.has(objectName as string) && propertyName === 'Fragment') {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+function hasAttributeWithName(
+ openingElement: Babel.NodePath,
+ name: string | undefined | null,
+): boolean {
+ if (!name) {
+ return false;
+ }
+
+ return openingElement.node.attributes.some(node => {
+ if (node.type === 'JSXAttribute') {
+ return node.name.name === name;
+ }
+
+ return false;
+ });
+}
+
+function getPathName(t: typeof Babel.types, path: Babel.NodePath): string {
+ if (!path.node) return UNKNOWN_ELEMENT_NAME;
+ if (!('name' in path.node)) {
+ return UNKNOWN_ELEMENT_NAME;
+ }
+
+ const name = path.node.name;
+
+ if (typeof name === 'string') {
+ return name;
+ }
+
+ if (t.isIdentifier(name) || t.isJSXIdentifier(name)) {
+ return name.name;
+ }
+
+ if (t.isJSXNamespacedName(name)) {
+ return name.name.name;
+ }
+
+ // Handle JSX member expressions like Tab.Group
+ if (t.isJSXMemberExpression(name)) {
+ const objectName = getJSXMemberExpressionObjectName(t, name.object);
+ const propertyName = name.property.name;
+ return `${objectName}.${propertyName}`;
+ }
+
+ return UNKNOWN_ELEMENT_NAME;
+}
+
+// Recursively handle nested member expressions (e.g. Components.UI.Header)
+function getJSXMemberExpressionObjectName(
+ t: typeof Babel.types,
+ object: Babel.types.JSXMemberExpression | Babel.types.JSXIdentifier,
+): string {
+ if (t.isJSXIdentifier(object)) {
+ return object.name;
+ }
+ if (t.isJSXMemberExpression(object)) {
+ const objectName = getJSXMemberExpressionObjectName(t, object.object);
+ return `${objectName}.${object.property.name}`;
+ }
+
+ return UNKNOWN_ELEMENT_NAME;
+}
+
+/**
+ * Extracts static text content from JSX children, searching up to a depth limit.
+ * Collects text from JSXText nodes of the root element and from recognized
+ * text components (e.g. ). Non-text custom components are traversed
+ * but their own JSXText is not collected.
+ *
+ * Returns null when dynamic content is found anywhere in the subtree,
+ * signaling that the entire label should be skipped.
+ */
+function extractStaticTextFromChildren(
+ t: typeof Babel.types,
+ node: Babel.types.JSXElement | Babel.types.JSXFragment,
+ textComponentNames: string[],
+ depth: number,
+ isRoot: boolean,
+): string[] | null {
+ if (depth <= 0) {
+ return [];
+ }
+
+ const texts: string[] = [];
+
+ for (const child of node.children) {
+ if (t.isJSXText(child)) {
+ if (isRoot) {
+ const trimmed = child.value.replace(/\s+/g, ' ').trim();
+ if (trimmed) {
+ texts.push(trimmed);
+ }
+ }
+ } else if (t.isJSXElement(child)) {
+ const childName = getElementName(t, child.openingElement);
+
+ if (textComponentNames.includes(childName)) {
+ const innerTexts = extractTextFromTextComponent(t, child, textComponentNames);
+ if (innerTexts === null) {
+ return null;
+ }
+ texts.push(...innerTexts);
+ } else {
+ const result = extractStaticTextFromChildren(t, child, textComponentNames, depth - 1, false);
+ if (result === null) {
+ return null;
+ }
+ texts.push(...result);
+ }
+ } else if (t.isJSXFragment(child)) {
+ const result = extractStaticTextFromChildren(t, child, textComponentNames, depth, isRoot);
+ if (result === null) {
+ return null;
+ }
+ texts.push(...result);
+ } else if (t.isJSXExpressionContainer(child)) {
+ if (!t.isJSXEmptyExpression(child.expression)) {
+ return null;
+ }
+ } else if (t.isJSXSpreadChild(child)) {
+ return null;
+ }
+ }
+
+ return texts;
+}
+
+/**
+ * Recursively extracts static text from within a recognized text component.
+ * Handles nested text components (e.g. Hello world)
+ * which is the standard React Native pattern for inline styling.
+ *
+ * Returns null when any dynamic content is found, signaling bail-out.
+ */
+function extractTextFromTextComponent(
+ t: typeof Babel.types,
+ node: Babel.types.JSXElement | Babel.types.JSXFragment,
+ textComponentNames: string[],
+): string[] | null {
+ const texts: string[] = [];
+
+ for (const child of node.children) {
+ if (t.isJSXText(child)) {
+ const trimmed = child.value.replace(/\s+/g, ' ').trim();
+ if (trimmed) {
+ texts.push(trimmed);
+ }
+ } else if (t.isJSXExpressionContainer(child)) {
+ if (!t.isJSXEmptyExpression(child.expression)) {
+ return null;
+ }
+ } else if (t.isJSXElement(child)) {
+ const childName = getElementName(t, child.openingElement);
+ if (textComponentNames.includes(childName)) {
+ const innerTexts = extractTextFromTextComponent(t, child, textComponentNames);
+ if (innerTexts === null) {
+ return null;
+ }
+ texts.push(...innerTexts);
+ } else {
+ const innerTexts = extractTextFromTextComponent(t, child, textComponentNames);
+ if (innerTexts === null) {
+ return null;
+ }
+ }
+ } else if (t.isJSXFragment(child)) {
+ const innerTexts = extractTextFromTextComponent(t, child, textComponentNames);
+ if (innerTexts === null) {
+ return null;
+ }
+ texts.push(...innerTexts);
+ } else if (t.isJSXSpreadChild(child)) {
+ return null;
+ }
+ }
+
+ return texts;
+}
+
+function getElementName(t: typeof Babel.types, openingElement: Babel.types.JSXOpeningElement): string {
+ const name = openingElement.name;
+ if (t.isJSXIdentifier(name)) {
+ return name.name;
+ }
+ if (t.isJSXMemberExpression(name)) {
+ return `${getJSXMemberExpressionObjectName(t, name.object)}.${name.property.name}`;
+ }
+ return '';
+}
+
+/**
+ * Injects a sentry-label attribute on the root JSX element of a component if
+ * static text content can be extracted from its children.
+ *
+ * When the root is a JSX fragment, the first JSXElement child is used as the
+ * target for both text extraction and attribute injection (since fragments
+ * cannot carry attributes).
+ */
+function maybeInjectSentryLabel(context: JSXProcessingContext, jsxNode: Babel.NodePath): void {
+ const { t, textComponentNames, ignoredComponents, componentName } = context;
+ const node = jsxNode.node;
+
+ let targetElement: Babel.types.JSXElement;
+
+ if (t.isJSXElement(node)) {
+ targetElement = node;
+ } else if (t.isJSXFragment(node)) {
+ const firstChild = node.children.find((c): c is Babel.types.JSXElement => t.isJSXElement(c));
+ if (!firstChild) {
+ return;
+ }
+ targetElement = firstChild;
+ } else {
+ return;
+ }
+
+ const targetElementName = getElementName(t, targetElement.openingElement);
+
+ if (ignoredComponents.some(ignored => ignored === componentName || ignored === targetElementName)) {
+ return;
+ }
+
+ if (
+ targetElement.openingElement.attributes.some(
+ attr => t.isJSXAttribute(attr) && attr.name.name === SENTRY_LABEL_ATTRIBUTE,
+ )
+ ) {
+ return;
+ }
+
+ const texts = extractStaticTextFromChildren(t, targetElement, textComponentNames, MAX_TEXT_SEARCH_DEPTH, true);
+
+ if (texts === null) {
+ return;
+ }
+
+ let label = texts.join(' ').replace(/\s+/g, ' ').trim();
+
+ if (!label) {
+ return;
+ }
+
+ if (label.length > MAX_LABEL_LENGTH) {
+ label = `${label.substring(0, MAX_LABEL_LENGTH - 3)}...`;
+ }
+
+ targetElement.openingElement.attributes.push(
+ t.jSXAttribute(t.jSXIdentifier(SENTRY_LABEL_ATTRIBUTE), t.stringLiteral(label)),
+ );
+}
+
+const UNKNOWN_ELEMENT_NAME = 'unknown';
diff --git a/packages/bundler-plugins/src/core/build-plugin-manager.ts b/packages/bundler-plugins/src/core/build-plugin-manager.ts
new file mode 100644
index 000000000000..522e629f942b
--- /dev/null
+++ b/packages/bundler-plugins/src/core/build-plugin-manager.ts
@@ -0,0 +1,815 @@
+/* oxlint-disable max-lines */
+import SentryCli from '@sentry/cli';
+import { closeSession, DEFAULT_ENVIRONMENT, getTraceData, makeSession, setMeasurement, startSpan } from '@sentry/core';
+import * as dotenv from 'dotenv';
+import * as fs from 'fs';
+import * as os from 'os';
+import * as path from 'path';
+import type { NormalizedOptions } from './options-mapping';
+import { normalizeUserOptions, validateOptions } from './options-mapping';
+import type { Logger } from './logger';
+import { createLogger } from './logger';
+import { allowedToSendTelemetry, createSentryInstance, safeFlushTelemetry } from './sentry/telemetry';
+import type { Options, SentrySDKBuildFlags } from './types';
+import {
+ arrayify,
+ getProjects,
+ getTurborepoEnvPassthroughWarning,
+ serializeIgnoreOptions,
+ stripQueryAndHashFromPath,
+} from './utils';
+import { defaultRewriteSourcesHook, prepareBundleForDebugIdUpload } from './debug-id-upload';
+import { globFiles } from './glob';
+import { LIB_VERSION } from './version';
+
+// Module-level guard to prevent duplicate deploy records when multiple bundler plugin
+// instances run in the same process (e.g. Next.js creates separate webpack compilers
+// for client, server, and edge). Keyed by release name.
+const _deployedReleases = new Set();
+
+/** @internal Exported for testing only. */
+export function _resetDeployedReleasesForTesting(): void {
+ _deployedReleases.clear();
+}
+
+export type SentryBuildPluginManager = {
+ /**
+ * A logger instance that takes the options passed to the build plugin manager into account. (for silencing and log level etc.)
+ */
+ logger: Logger;
+
+ /**
+ * Options after normalization. Includes things like the inferred release name.
+ */
+ normalizedOptions: NormalizedOptions;
+ /**
+ * Magic strings and their replacement values that can be used for bundle size optimizations. This already takes
+ * into account the options passed to the build plugin manager.
+ */
+ bundleSizeOptimizationReplacementValues: SentrySDKBuildFlags;
+ /**
+ * Metadata that should be injected into bundles if possible. Takes into account options passed to the build plugin manager.
+ */
+ // See `generateModuleMetadataInjectorCode` for how this should be used exactly
+ bundleMetadata: Record;
+
+ /**
+ * Contains utility functions for emitting telemetry via the build plugin manager.
+ */
+ telemetry: {
+ /**
+ * Emits a `Sentry Bundler Plugin execution` signal.
+ */
+ emitBundlerPluginExecutionSignal(): Promise;
+ };
+
+ /**
+ * Will potentially create a release based on the build plugin manager options.
+ *
+ * Also
+ * - finalizes the release
+ * - sets commits
+ * - uploads legacy sourcemaps
+ * - adds deploy information
+ */
+ createRelease(): Promise;
+
+ /**
+ * Injects debug IDs into the build artifacts.
+ *
+ * This is a separate function from `uploadSourcemaps` because that needs to run before the sourcemaps are uploaded.
+ * Usually the respective bundler-plugin will take care of this before the sourcemaps are uploaded.
+ * Only use this if you need to manually inject debug IDs into the build artifacts.
+ */
+ injectDebugIds(buildArtifactPaths: string[]): Promise;
+
+ /**
+ * Uploads sourcemaps using the "Debug ID" method. This function takes a list of build artifact paths that will be uploaded
+ */
+ uploadSourcemaps(buildArtifactPaths: string[], opts?: { prepareArtifacts?: boolean }): Promise;
+
+ /**
+ * Will delete artifacts based on the passed `sourcemaps.filesToDeleteAfterUpload` option.
+ */
+ deleteArtifacts(): Promise;
+
+ createDependencyOnBuildArtifacts: () => () => void;
+};
+
+function createCliInstance(options: NormalizedOptions): SentryCli {
+ return new SentryCli(null, {
+ authToken: options.authToken,
+ org: options.org,
+ // Default to the first project if multiple projects are specified
+ project: getProjects(options.project)?.[0],
+ silent: options.silent,
+ url: options.url,
+ vcsRemote: options.release.vcsRemote,
+ headers: {
+ ...(options.telemetry ? getTraceData() : {}),
+ ...options.headers,
+ },
+ });
+}
+
+/**
+ * Creates a build plugin manager that exposes primitives for everything that a Sentry JavaScript SDK or build tooling may do during a build.
+ *
+ * The build plugin manager's behavior strongly depends on the options that are passed in.
+ */
+export function createSentryBuildPluginManager(
+ userOptions: Options,
+ bundlerPluginMetaContext: {
+ /**
+ * E.g. `webpack` or `nextjs` or `turbopack`
+ */
+ buildTool: string;
+ /**
+ * E.g. `5` for webpack v5 or `4` for Rollup v4
+ */
+ buildToolMajorVersion?: string;
+ /**
+ * E.g. `[sentry-webpack-plugin]` or `[@sentry/nextjs]`
+ */
+ loggerPrefix: string;
+ },
+): SentryBuildPluginManager {
+ const logger = createLogger({
+ prefix: bundlerPluginMetaContext.loggerPrefix,
+ silent: userOptions.silent ?? false,
+ debug: userOptions.debug ?? false,
+ });
+
+ try {
+ const dotenvFile = fs.readFileSync(path.join(process.cwd(), '.env.sentry-build-plugin'), 'utf-8');
+ // NOTE: Do not use the dotenv.config API directly to read the dotenv file! For some ungodly reason, it falls back to reading `${process.cwd()}/.env` which is absolutely not what we want.
+ const dotenvResult = dotenv.parse(dotenvFile);
+
+ // Vite has a bug/behaviour where spreading into process.env will cause it to crash
+ // https://github.com/vitest-dev/vitest/issues/1870#issuecomment-1501140251
+ Object.assign(process.env, dotenvResult);
+
+ logger.info('Using environment variables configured in ".env.sentry-build-plugin".');
+ } catch (e: unknown) {
+ // Ignore "file not found" errors but throw all others
+ if (typeof e === 'object' && e && 'code' in e && e.code !== 'ENOENT') {
+ throw e;
+ }
+ }
+
+ const options = normalizeUserOptions(userOptions);
+
+ if (options.disable) {
+ // Early-return a noop build plugin manager instance so that we
+ // don't continue validating options, setting up Sentry, etc.
+ // Otherwise we might create side-effects or log messages that
+ // users don't expect from a disabled plugin.
+ return {
+ normalizedOptions: options,
+ logger,
+ bundleSizeOptimizationReplacementValues: {},
+ telemetry: {
+ emitBundlerPluginExecutionSignal: async () => {
+ /* noop */
+ },
+ },
+ bundleMetadata: {},
+ createRelease: async () => {
+ /* noop */
+ },
+ uploadSourcemaps: async () => {
+ /* noop */
+ },
+ deleteArtifacts: async () => {
+ /* noop */
+ },
+ createDependencyOnBuildArtifacts: () => () => {
+ /* noop */
+ },
+ injectDebugIds: async () => {
+ /* noop */
+ },
+ };
+ }
+
+ const shouldSendTelemetry = allowedToSendTelemetry(options);
+ const { sentryScope, sentryClient } = createSentryInstance(
+ options,
+ shouldSendTelemetry,
+ bundlerPluginMetaContext.buildTool,
+ bundlerPluginMetaContext.buildToolMajorVersion,
+ );
+
+ const { release, environment = DEFAULT_ENVIRONMENT } = sentryClient.getOptions();
+
+ const sentrySession = makeSession({ release, environment });
+ sentryScope.setSession(sentrySession);
+ // Send the start of the session
+ sentryClient.captureSession(sentrySession);
+
+ let sessionHasEnded = false; // Just to prevent infinite loops with beforeExit, which is called whenever the event loop empties out
+
+ function endSession(): void {
+ if (sessionHasEnded) {
+ return;
+ }
+
+ closeSession(sentrySession);
+ sentryClient.captureSession(sentrySession);
+ sessionHasEnded = true;
+ }
+
+ // We also need to manually end sessions on errors because beforeExit is not called on crashes
+ process.on('beforeExit', () => {
+ endSession();
+ });
+
+ // Set the User-Agent that Sentry CLI will use when interacting with Sentry
+ process.env['SENTRY_PIPELINE'] = `${bundlerPluginMetaContext.buildTool}-plugin/${LIB_VERSION}`;
+
+ // Propagate debug flag to Sentry CLI via environment variable
+ // Only set if not already defined to respect user's explicit configuration
+ if (options.debug && !process.env['SENTRY_LOG_LEVEL']) {
+ process.env['SENTRY_LOG_LEVEL'] = 'debug';
+ }
+
+ // Not a bulletproof check but should be good enough to at least sometimes determine
+ // if the plugin is called in dev/watch mode or for a prod build. The important part
+ // here is to avoid a false positive. False negatives are okay.
+ const isDevMode = process.env['NODE_ENV'] === 'development';
+
+ /**
+ * Handles errors caught and emitted in various areas of the plugin.
+ *
+ * Also sets the sentry session status according to the error handling.
+ *
+ * If users specify their custom `errorHandler` we'll leave the decision to throw
+ * or continue up to them. By default, @param throwByDefault controls if the plugin
+ * should throw an error (which causes a build fail in most bundlers) or continue.
+ */
+ function handleRecoverableError(unknownError: unknown, throwByDefault: boolean): void {
+ sentrySession.status = 'abnormal';
+ try {
+ if (options.errorHandler) {
+ try {
+ if (unknownError instanceof Error) {
+ options.errorHandler(unknownError);
+ } else {
+ options.errorHandler(new Error('An unknown error occurred'));
+ }
+ } catch (e) {
+ sentrySession.status = 'crashed';
+ throw e;
+ }
+ } else {
+ // setting the session to "crashed" b/c from a plugin perspective this run failed.
+ // However, we're intentionally not rethrowing the error to avoid breaking the user build.
+ sentrySession.status = 'crashed';
+ if (throwByDefault) {
+ throw unknownError;
+ }
+ logger.error("An error occurred. Couldn't finish all operations:", unknownError);
+ }
+ } finally {
+ endSession();
+ }
+ }
+
+ if (!validateOptions(options, logger)) {
+ // Throwing by default to avoid a misconfigured plugin going unnoticed.
+ handleRecoverableError(new Error('Options were not set correctly. See output above for more details.'), true);
+ }
+
+ // We have multiple plugins depending on generated source map files. (debug ID upload, legacy upload)
+ // Additionally, we also want to have the functionality to delete files after uploading sourcemaps.
+ // All of these plugins and the delete functionality need to run in the same hook (`writeBundle`).
+ // Since the plugins among themselves are not aware of when they run and finish, we need a system to
+ // track their dependencies on the generated files, so that we can initiate the file deletion only after
+ // nothing depends on the files anymore.
+ const dependenciesOnBuildArtifacts = new Set();
+ const buildArtifactsDependencySubscribers: (() => void)[] = [];
+
+ function notifyBuildArtifactDependencySubscribers(): void {
+ buildArtifactsDependencySubscribers.forEach(subscriber => {
+ subscriber();
+ });
+ }
+
+ function createDependencyOnBuildArtifacts(): () => void {
+ const dependencyIdentifier = Symbol();
+ dependenciesOnBuildArtifacts.add(dependencyIdentifier);
+
+ return function freeDependencyOnBuildArtifacts() {
+ dependenciesOnBuildArtifacts.delete(dependencyIdentifier);
+ notifyBuildArtifactDependencySubscribers();
+ };
+ }
+
+ /**
+ * Returns a Promise that resolves when all the currently active dependencies are freed again.
+ *
+ * It is very important that this function is called as late as possible before wanting to await the Promise to give
+ * the dependency producers as much time as possible to register themselves.
+ */
+ function waitUntilBuildArtifactDependenciesAreFreed(): Promise {
+ return new Promise(resolve => {
+ buildArtifactsDependencySubscribers.push(() => {
+ if (dependenciesOnBuildArtifacts.size === 0) {
+ resolve();
+ }
+ });
+
+ if (dependenciesOnBuildArtifacts.size === 0) {
+ resolve();
+ }
+ });
+ }
+
+ const bundleSizeOptimizationReplacementValues: SentrySDKBuildFlags = {};
+ if (options.bundleSizeOptimizations) {
+ const { bundleSizeOptimizations } = options;
+
+ if (bundleSizeOptimizations.excludeDebugStatements) {
+ bundleSizeOptimizationReplacementValues['__SENTRY_DEBUG__'] = false;
+ }
+ if (bundleSizeOptimizations.excludeTracing) {
+ bundleSizeOptimizationReplacementValues['__SENTRY_TRACING__'] = false;
+ }
+ if (bundleSizeOptimizations.excludeReplayCanvas) {
+ bundleSizeOptimizationReplacementValues['__RRWEB_EXCLUDE_CANVAS__'] = true;
+ }
+ if (bundleSizeOptimizations.excludeReplayIframe) {
+ bundleSizeOptimizationReplacementValues['__RRWEB_EXCLUDE_IFRAME__'] = true;
+ }
+ if (bundleSizeOptimizations.excludeReplayShadowDom) {
+ bundleSizeOptimizationReplacementValues['__RRWEB_EXCLUDE_SHADOW_DOM__'] = true;
+ }
+ if (bundleSizeOptimizations.excludeReplayWorker) {
+ bundleSizeOptimizationReplacementValues['__SENTRY_EXCLUDE_REPLAY_WORKER__'] = true;
+ }
+ }
+
+ let bundleMetadata: Record = {};
+ if (options.moduleMetadata || options.applicationKey) {
+ if (options.applicationKey) {
+ // We use different keys so that if user-code receives multiple bundling passes, we will store the application keys of all the passes.
+ // It is a bit unfortunate that we have to inject the metadata snippet at the top, because after multiple
+ // injections, the first injection will always "win" because it comes last in the code. We would generally be
+ // fine with making the last bundling pass win. But because it cannot win, we have to use a workaround of storing
+ // the app keys in different object keys.
+ // We can simply use the `_sentryBundlerPluginAppKey:` to filter for app keys in the SDK.
+ bundleMetadata[`_sentryBundlerPluginAppKey:${options.applicationKey}`] = true;
+ }
+
+ if (typeof options.moduleMetadata === 'function') {
+ const args = {
+ org: options.org,
+ project: getProjects(options.project)?.[0],
+ projects: getProjects(options.project),
+ release: options.release.name,
+ };
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ bundleMetadata = { ...bundleMetadata, ...options.moduleMetadata(args) };
+ } else {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ bundleMetadata = { ...bundleMetadata, ...options.moduleMetadata };
+ }
+ }
+
+ return {
+ /**
+ * A logger instance that takes the options passed to the build plugin manager into account. (for silencing and log level etc.)
+ */
+ logger,
+
+ /**
+ * Options after normalization. Includes things like the inferred release name.
+ */
+ normalizedOptions: options,
+
+ /**
+ * Magic strings and their replacement values that can be used for bundle size optimizations. This already takes
+ * into account the options passed to the build plugin manager.
+ */
+ bundleSizeOptimizationReplacementValues,
+
+ /**
+ * Metadata that should be injected into bundles if possible. Takes into account options passed to the build plugin manager.
+ */
+ // See `generateModuleMetadataInjectorCode` for how this should be used exactly
+ bundleMetadata,
+
+ /**
+ * Contains utility functions for emitting telemetry via the build plugin manager.
+ */
+ telemetry: {
+ /**
+ * Emits a `Sentry Bundler Plugin execution` signal.
+ */
+ async emitBundlerPluginExecutionSignal() {
+ if (await shouldSendTelemetry) {
+ logger.info(
+ 'Sending telemetry data on issues and performance to Sentry. To disable telemetry, set `options.telemetry` to `false`.',
+ );
+ startSpan({ name: 'Sentry Bundler Plugin execution', scope: sentryScope }, () => {
+ //
+ });
+ await safeFlushTelemetry(sentryClient);
+ }
+ },
+ },
+
+ /**
+ * Will potentially create a release based on the build plugin manager options.
+ *
+ * Also
+ * - finalizes the release
+ * - sets commits
+ * - uploads legacy sourcemaps
+ * - adds deploy information
+ */
+ async createRelease() {
+ if (!options.release.name) {
+ logger.debug(
+ 'No release name provided. Will not create release. Please set the `release.name` option to identify your release.',
+ );
+ return;
+ } else if (isDevMode) {
+ logger.debug('Running in development mode. Will not create release.');
+ return;
+ } else if (!options.authToken) {
+ logger.warn(
+ `No auth token provided. Will not create release. Please set the \`authToken\` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/${getTurborepoEnvPassthroughWarning('SENTRY_AUTH_TOKEN')}`,
+ );
+ return;
+ } else if (!options.org && !options.authToken.startsWith('sntrys_')) {
+ logger.warn(
+ `No organization slug provided. Will not create release. Please set the \`org\` option to your Sentry organization slug.${getTurborepoEnvPassthroughWarning('SENTRY_ORG')}`,
+ );
+ return;
+ } else if (!options.project || (Array.isArray(options.project) && options.project.length === 0)) {
+ logger.warn(
+ `No project provided. Will not create release. Please set the \`project\` option to your Sentry project slug.${getTurborepoEnvPassthroughWarning('SENTRY_PROJECT')}`,
+ );
+ return;
+ }
+
+ // It is possible that this writeBundle hook is called multiple times in one build (for example when reusing the plugin, or when using build tooling like `@vitejs/plugin-legacy`)
+ // Therefore we need to actually register the execution of this hook as dependency on the sourcemap files.
+ const freeWriteBundleInvocationDependencyOnSourcemapFiles = createDependencyOnBuildArtifacts();
+
+ try {
+ const cliInstance = createCliInstance(options);
+
+ if (options.release.create) {
+ const releaseOutput = await cliInstance.releases.new(options.release.name);
+ logger.debug('Release created:', releaseOutput);
+ }
+
+ if (options.release.uploadLegacySourcemaps) {
+ const normalizedInclude = arrayify(options.release.uploadLegacySourcemaps)
+ .map(includeItem => (typeof includeItem === 'string' ? { paths: [includeItem] } : includeItem))
+ .map(includeEntry => ({
+ ...includeEntry,
+ validate: includeEntry.validate ?? false,
+ ext: includeEntry.ext
+ ? includeEntry.ext.map(extension => `.${extension.replace(/^\./, '')}`)
+ : ['.js', '.map', '.jsbundle', '.bundle'],
+ ignore: includeEntry.ignore ? arrayify(includeEntry.ignore) : undefined,
+ }));
+
+ await cliInstance.releases.uploadSourceMaps(options.release.name, {
+ include: normalizedInclude,
+ dist: options.release.dist,
+ projects: getProjects(options.project),
+ // We want this promise to throw if the sourcemaps fail to upload so that we know about it.
+ // see: https://github.com/getsentry/sentry-cli/pull/2605
+ live: 'rejectOnError',
+ });
+ }
+
+ if (options.release.setCommits !== false) {
+ try {
+ await cliInstance.releases.setCommits(
+ options.release.name,
+ // set commits always exists due to the normalize function
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ options.release.setCommits!,
+ );
+ } catch (e) {
+ // shouldNotThrowOnFailure being present means that the plugin defaulted to `{ auto: true }` for the setCommitsOptions, meaning that wee should not throw when CLI throws because there is no repo
+ if (
+ options.release.setCommits &&
+ 'shouldNotThrowOnFailure' in options.release.setCommits &&
+ options.release.setCommits.shouldNotThrowOnFailure
+ ) {
+ logger.debug(
+ 'An error occurred setting commits on release (this message can be ignored unless you commits on release are desired):',
+ e,
+ );
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ if (options.release.finalize) {
+ await cliInstance.releases.finalize(options.release.name);
+ }
+
+ if (options.release.deploy && !_deployedReleases.has(options.release.name)) {
+ await cliInstance.releases.newDeploy(options.release.name, options.release.deploy);
+ _deployedReleases.add(options.release.name);
+ }
+ } catch (e) {
+ sentryScope.captureException('Error in "releaseManagementPlugin" writeBundle hook');
+ await safeFlushTelemetry(sentryClient);
+ handleRecoverableError(e, false);
+ } finally {
+ freeWriteBundleInvocationDependencyOnSourcemapFiles();
+ }
+ },
+
+ /*
+ Injects debug IDs into the build artifacts.
+
+ This is a separate function from `uploadSourcemaps` because that needs to run before the sourcemaps are uploaded.
+ Usually the respective bundler-plugin will take care of this before the sourcemaps are uploaded.
+ Only use this if you need to manually inject debug IDs into the build artifacts.
+ */
+ async injectDebugIds(buildArtifactPaths: string[]) {
+ await startSpan({ name: 'inject-debug-ids', scope: sentryScope, forceTransaction: true }, async () => {
+ try {
+ const cliInstance = createCliInstance(options);
+ await cliInstance.execute(
+ ['sourcemaps', 'inject', ...serializeIgnoreOptions(options.sourcemaps?.ignore), ...buildArtifactPaths],
+ options.debug ? 'rejectOnError' : false,
+ );
+ } catch (e) {
+ sentryScope.captureException('Error in "debugIdInjectionPlugin" writeBundle hook');
+ handleRecoverableError(e, false);
+ } finally {
+ await safeFlushTelemetry(sentryClient);
+ }
+ });
+ },
+
+ /**
+ * Uploads sourcemaps using the "Debug ID" method.
+ *
+ * By default, this prepares bundles in a temporary folder before uploading. You can opt into an
+ * in-place, direct upload path by setting `prepareArtifacts` to `false`. If `prepareArtifacts` is set to
+ * `false`, no preparation (e.g. adding `//# debugId=...` and writing adjusted source maps) is performed and no temp folder is used.
+ *
+ * @param buildArtifactPaths - The paths of the build artifacts to upload
+ * @param opts - Optional flags to control temp folder usage and preparation
+ */
+ async uploadSourcemaps(buildArtifactPaths: string[], opts?: { prepareArtifacts?: boolean }) {
+ if (!canUploadSourceMaps(options, logger, isDevMode)) {
+ return;
+ }
+
+ // Early exit if assets is explicitly set to an empty array
+ const assets = options.sourcemaps?.assets;
+ if (Array.isArray(assets) && assets.length === 0) {
+ logger.debug('Empty `sourcemaps.assets` option provided. Will not upload sourcemaps with debug ID.');
+ return;
+ }
+
+ await startSpan(
+ // This is `forceTransaction`ed because this span is used in dashboards in the form of indexed transactions.
+ { name: 'debug-id-sourcemap-upload', scope: sentryScope, forceTransaction: true },
+ async () => {
+ // If we're not using a temp folder, we must not prepare artifacts in-place (to avoid mutating user files)
+ const shouldPrepare = opts?.prepareArtifacts ?? true;
+
+ let folderToCleanUp: string | undefined;
+
+ // It is possible that this writeBundle hook (which calls this function) is called multiple times in one build (for example when reusing the plugin, or when using build tooling like `@vitejs/plugin-legacy`)
+ // Therefore we need to actually register the execution of this hook as dependency on the sourcemap files.
+ const freeUploadDependencyOnBuildArtifacts = createDependencyOnBuildArtifacts();
+
+ try {
+ if (!shouldPrepare) {
+ // Direct CLI upload from existing artifact paths (no globbing, no preparation)
+ let pathsToUpload: string[];
+
+ if (assets) {
+ pathsToUpload = Array.isArray(assets) ? assets : [assets];
+ logger.debug(
+ `Direct upload mode: passing user-provided assets directly to CLI: ${pathsToUpload.join(', ')}`,
+ );
+ } else {
+ // Use original paths e.g. like ['.next/server'] directly –> preferred way when no globbing is done
+ pathsToUpload = buildArtifactPaths;
+ }
+
+ const ignorePaths = options.sourcemaps?.ignore
+ ? Array.isArray(options.sourcemaps?.ignore)
+ ? options.sourcemaps?.ignore
+ : [options.sourcemaps?.ignore]
+ : [];
+ await startSpan({ name: 'upload', scope: sentryScope }, async () => {
+ const cliInstance = createCliInstance(options);
+ await cliInstance.releases.uploadSourceMaps(options.release.name ?? 'undefined', {
+ include: [
+ {
+ paths: pathsToUpload,
+ rewrite: true,
+ dist: options.release.dist,
+ },
+ ],
+ ignore: ignorePaths,
+ projects: getProjects(options.project),
+ live: 'rejectOnError',
+ });
+ });
+
+ logger.info('Successfully uploaded source maps to Sentry');
+ } else {
+ // Prepare artifacts in temp folder before uploading
+ let globAssets: string | string[];
+ if (assets) {
+ globAssets = assets;
+ } else {
+ logger.debug(
+ 'No `sourcemaps.assets` option provided, falling back to uploading detected build artifacts.',
+ );
+ globAssets = buildArtifactPaths;
+ }
+
+ const globResult = await startSpan(
+ { name: 'glob', scope: sentryScope },
+ async () => await globFiles(globAssets, { ignore: options.sourcemaps?.ignore }),
+ );
+
+ const debugIdChunkFilePaths = globResult.filter(debugIdChunkFilePath => {
+ return !!stripQueryAndHashFromPath(debugIdChunkFilePath).match(/\.(js|mjs|cjs)$/);
+ });
+
+ // The order of the files output by glob() is not deterministic
+ // Ensure order within the files so that {debug-id}-{chunkIndex} coupling is consistent
+ debugIdChunkFilePaths.sort();
+
+ if (debugIdChunkFilePaths.length === 0) {
+ logger.warn(
+ "Didn't find any matching sources for debug ID upload. Please check the `sourcemaps.assets` option.",
+ );
+ } else {
+ const tmpUploadFolder = await startSpan({ name: 'mkdtemp', scope: sentryScope }, async () => {
+ return (
+ process.env?.['SENTRY_TEST_OVERRIDE_TEMP_DIR'] ||
+ (await fs.promises.mkdtemp(path.join(os.tmpdir(), 'sentry-bundler-plugin-upload-')))
+ );
+ });
+ folderToCleanUp = tmpUploadFolder;
+
+ // Prepare into temp folder, then upload
+ await startSpan({ name: 'prepare-bundles', scope: sentryScope }, async prepBundlesSpan => {
+ // Preparing the bundles can be a lot of work and doing it all at once has the potential of nuking the heap so
+ // instead we do it with a maximum of 16 concurrent workers
+ const preparationTasks = debugIdChunkFilePaths.map((chunkFilePath, chunkIndex) => async () => {
+ await prepareBundleForDebugIdUpload(
+ chunkFilePath,
+ tmpUploadFolder,
+ chunkIndex,
+ logger,
+ options.sourcemaps?.rewriteSources ?? defaultRewriteSourcesHook,
+ options.sourcemaps?.resolveSourceMap,
+ );
+ });
+ const workers: Promise[] = [];
+ const worker = async (): Promise => {
+ while (preparationTasks.length > 0) {
+ const task = preparationTasks.shift();
+ if (task) {
+ await task();
+ }
+ }
+ };
+ for (let workerIndex = 0; workerIndex < 16; workerIndex++) {
+ workers.push(worker());
+ }
+
+ await Promise.all(workers);
+
+ const files = await fs.promises.readdir(tmpUploadFolder);
+ const stats = files.map(file => fs.promises.stat(path.join(tmpUploadFolder, file)));
+ const uploadSize = (await Promise.all(stats)).reduce(
+ (accumulator, { size }) => accumulator + size,
+ 0,
+ );
+
+ setMeasurement('files', files.length, 'none', prepBundlesSpan);
+ setMeasurement('upload_size', uploadSize, 'byte', prepBundlesSpan);
+
+ await startSpan({ name: 'upload', scope: sentryScope }, async () => {
+ const cliInstance = createCliInstance(options);
+ await cliInstance.releases.uploadSourceMaps(options.release.name ?? 'undefined', {
+ include: [
+ {
+ paths: [tmpUploadFolder],
+ rewrite: false,
+ dist: options.release.dist,
+ },
+ ],
+ projects: getProjects(options.project),
+ live: 'rejectOnError',
+ });
+ });
+ });
+
+ logger.info('Successfully uploaded source maps to Sentry');
+ }
+ }
+ } catch (e) {
+ sentryScope.captureException('Error in "debugIdUploadPlugin" writeBundle hook');
+ handleRecoverableError(e, false);
+ } finally {
+ if (folderToCleanUp && !process.env?.['SENTRY_TEST_OVERRIDE_TEMP_DIR']) {
+ logger.debug('Cleaning up temporary files...');
+ void startSpan({ name: 'cleanup', scope: sentryScope }, async () => {
+ if (folderToCleanUp) {
+ await fs.promises.rm(folderToCleanUp, { recursive: true, force: true });
+ logger.debug(`Temporary folder deleted: ${folderToCleanUp}`);
+ }
+ });
+ }
+ logger.debug('Freeing upload dependencies...');
+ freeUploadDependencyOnBuildArtifacts();
+ logger.debug('Flushing telemetry data...');
+ await safeFlushTelemetry(sentryClient);
+ logger.debug('Telemetry flushed. Plugin upload process complete.');
+ }
+ },
+ );
+ },
+
+ /**
+ * Will delete artifacts based on the passed `sourcemaps.filesToDeleteAfterUpload` option.
+ */
+ async deleteArtifacts() {
+ try {
+ const filesToDelete = await options.sourcemaps?.filesToDeleteAfterUpload;
+ if (filesToDelete !== undefined) {
+ const filePathsToDelete = await globFiles(filesToDelete);
+
+ logger.debug('Waiting for dependencies on generated files to be freed before deleting...');
+
+ await waitUntilBuildArtifactDependenciesAreFreed();
+
+ filePathsToDelete.forEach(filePathToDelete => {
+ logger.debug(`Deleting asset after upload: ${filePathToDelete}`);
+ });
+
+ await Promise.all(
+ filePathsToDelete.map(filePathToDelete =>
+ fs.promises.rm(filePathToDelete, { force: true }).catch(e => {
+ // This is allowed to fail - we just don't do anything
+ logger.debug(`An error occurred while attempting to delete asset: ${filePathToDelete}`, e);
+ }),
+ ),
+ );
+ }
+ } catch (e) {
+ sentryScope.captureException('Error in "sentry-file-deletion-plugin" buildEnd hook');
+ await safeFlushTelemetry(sentryClient);
+ // We throw by default if we get here b/c not being able to delete
+ // source maps could leak them to production
+ handleRecoverableError(e, true);
+ }
+ },
+ createDependencyOnBuildArtifacts,
+ };
+}
+
+function canUploadSourceMaps(options: NormalizedOptions, logger: Logger, isDevMode: boolean): boolean {
+ if (options.sourcemaps?.disable) {
+ logger.debug('Source map upload was disabled. Will not upload sourcemaps using debug ID process.');
+ return false;
+ }
+ if (isDevMode) {
+ logger.debug('Running in development mode. Will not upload sourcemaps.');
+ return false;
+ }
+ if (!options.authToken) {
+ logger.warn(
+ `No auth token provided. Will not upload source maps. Please set the \`authToken\` option. You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/${getTurborepoEnvPassthroughWarning('SENTRY_AUTH_TOKEN')}`,
+ );
+ return false;
+ }
+ if (!options.org && !options.authToken.startsWith('sntrys_')) {
+ logger.warn(
+ `No org provided. Will not upload source maps. Please set the \`org\` option to your Sentry organization slug.${getTurborepoEnvPassthroughWarning('SENTRY_ORG')}`,
+ );
+ return false;
+ }
+ if (!getProjects(options.project)?.[0]) {
+ logger.warn(
+ `No project provided. Will not upload source maps. Please set the \`project\` option to your Sentry project slug.${getTurborepoEnvPassthroughWarning('SENTRY_PROJECT')}`,
+ );
+ return false;
+ }
+
+ return true;
+}
diff --git a/packages/bundler-plugins/src/core/debug-id-upload.ts b/packages/bundler-plugins/src/core/debug-id-upload.ts
new file mode 100644
index 000000000000..932f326e1e98
--- /dev/null
+++ b/packages/bundler-plugins/src/core/debug-id-upload.ts
@@ -0,0 +1,234 @@
+import fs from 'fs';
+import path from 'path';
+import * as url from 'url';
+import * as util from 'util';
+import { promisify } from 'util';
+import type { SentryBuildPluginManager } from './build-plugin-manager';
+import type { Logger } from './logger';
+import type { ResolveSourceMapHook, RewriteSourcesHook } from './types';
+import { stripQueryAndHashFromPath } from './utils';
+
+interface DebugIdUploadPluginOptions {
+ sentryBuildPluginManager: SentryBuildPluginManager;
+}
+
+export function createDebugIdUploadFunction({ sentryBuildPluginManager }: DebugIdUploadPluginOptions) {
+ return async (buildArtifactPaths: string[]) => {
+ // Webpack and perhaps other bundlers allow you to append query strings to
+ // filenames for cache busting purposes. We should strip these before upload.
+ const cleanedPaths = buildArtifactPaths.map(stripQueryAndHashFromPath);
+ await sentryBuildPluginManager.uploadSourcemaps(cleanedPaths);
+ };
+}
+
+export async function prepareBundleForDebugIdUpload(
+ bundleFilePath: string,
+ uploadFolder: string,
+ chunkIndex: number,
+ logger: Logger,
+ rewriteSourcesHook: RewriteSourcesHook,
+ resolveSourceMapHook: ResolveSourceMapHook | undefined,
+): Promise {
+ let bundleContent;
+ try {
+ bundleContent = await promisify(fs.readFile)(bundleFilePath, 'utf8');
+ } catch (e) {
+ logger.error(`Could not read bundle to determine debug ID and source map: ${bundleFilePath}`, e);
+ return;
+ }
+
+ const debugId = determineDebugIdFromBundleSource(bundleContent);
+ if (debugId === undefined) {
+ logger.debug(
+ `Could not determine debug ID from bundle. This can happen if you did not clean your output folder before installing the Sentry plugin. File will not be source mapped: ${bundleFilePath}`,
+ );
+ return;
+ }
+
+ const uniqueUploadName = `${debugId}-${chunkIndex}`;
+
+ bundleContent = addDebugIdToBundleSource(bundleContent, debugId);
+ const writeSourceFilePromise = fs.promises.writeFile(
+ path.join(uploadFolder, `${uniqueUploadName}.js`),
+ bundleContent,
+ 'utf-8',
+ );
+
+ const writeSourceMapFilePromise = determineSourceMapPathFromBundle(
+ bundleFilePath,
+ bundleContent,
+ logger,
+ resolveSourceMapHook,
+ ).then(async sourceMapPath => {
+ if (sourceMapPath) {
+ await prepareSourceMapForDebugIdUpload(
+ sourceMapPath,
+ path.join(uploadFolder, `${uniqueUploadName}.js.map`),
+ debugId,
+ rewriteSourcesHook,
+ logger,
+ );
+ }
+ });
+
+ await writeSourceFilePromise;
+ await writeSourceMapFilePromise;
+}
+
+/**
+ * Looks for a particular string pattern (`sdbid-[debug ID]`) in the bundle
+ * source and extracts the bundle's debug ID from it.
+ *
+ * The string pattern is injected via the debug ID injection snipped.
+ */
+function determineDebugIdFromBundleSource(code: string): string | undefined {
+ const match = code.match(
+ /sentry-dbid-([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})/,
+ );
+
+ if (match) {
+ return match[1];
+ } else {
+ return undefined;
+ }
+}
+
+const SPEC_LAST_DEBUG_ID_REGEX = /\/\/# debugId=([a-fA-F0-9-]+)(?![\s\S]*\/\/# debugId=)/m;
+
+function hasSpecCompliantDebugId(bundleSource: string): boolean {
+ return SPEC_LAST_DEBUG_ID_REGEX.test(bundleSource);
+}
+
+function addDebugIdToBundleSource(bundleSource: string, debugId: string): string {
+ if (hasSpecCompliantDebugId(bundleSource)) {
+ return bundleSource.replace(SPEC_LAST_DEBUG_ID_REGEX, `//# debugId=${debugId}`);
+ } else {
+ return `${bundleSource}\n//# debugId=${debugId}`;
+ }
+}
+
+/**
+ * Applies a set of heuristics to find the source map for a particular bundle.
+ *
+ * @returns the path to the bundle's source map or `undefined` if none could be found.
+ */
+export async function determineSourceMapPathFromBundle(
+ bundlePath: string,
+ bundleSource: string,
+ logger: Logger,
+ resolveSourceMapHook: ResolveSourceMapHook | undefined,
+): Promise {
+ const sourceMappingUrlMatch = bundleSource.match(/^\s*\/\/# sourceMappingURL=(.*)$/m);
+ const sourceMappingUrl = sourceMappingUrlMatch ? (sourceMappingUrlMatch[1] as string) : undefined;
+
+ const searchLocations: string[] = [];
+
+ if (resolveSourceMapHook) {
+ logger.debug(
+ `Calling sourcemaps.resolveSourceMap(${JSON.stringify(bundlePath)}, ${JSON.stringify(sourceMappingUrl)})`,
+ );
+ const customPath = await resolveSourceMapHook(bundlePath, sourceMappingUrl);
+ logger.debug(`resolveSourceMap hook returned: ${JSON.stringify(customPath)}`);
+
+ if (customPath) {
+ searchLocations.push(customPath);
+ }
+ }
+
+ // 1. try to find source map at `sourceMappingURL` location
+ if (sourceMappingUrl) {
+ let parsedUrl: URL | undefined;
+ try {
+ parsedUrl = new URL(sourceMappingUrl);
+ } catch {
+ // noop
+ }
+
+ if (parsedUrl?.protocol === 'file:') {
+ searchLocations.push(url.fileURLToPath(sourceMappingUrl));
+ } else if (parsedUrl) {
+ // noop, non-file urls don't translate to a local sourcemap file
+ } else if (path.isAbsolute(sourceMappingUrl)) {
+ searchLocations.push(path.normalize(sourceMappingUrl));
+ } else {
+ searchLocations.push(path.normalize(path.join(path.dirname(bundlePath), sourceMappingUrl)));
+ }
+ }
+
+ // 2. try to find source map at path adjacent to chunk source, but with `.map` appended
+ searchLocations.push(`${bundlePath}.map`);
+
+ for (const searchLocation of searchLocations) {
+ try {
+ await util.promisify(fs.access)(searchLocation);
+ logger.debug(`Source map found for bundle \`${bundlePath}\`: \`${searchLocation}\``);
+ return searchLocation;
+ } catch {
+ // noop
+ }
+ }
+
+ // This is just a debug message because it can be quite spammy for some frameworks
+ logger.debug(
+ `Could not determine source map path for bundle \`${bundlePath}\`` +
+ ` with sourceMappingURL=${sourceMappingUrl === undefined ? 'undefined' : `\`${sourceMappingUrl}\``}` +
+ ` - Did you turn on source map generation in your bundler?` +
+ ` (Attempted paths: ${searchLocations.map(e => `\`${e}\``).join(', ')})`,
+ );
+ return undefined;
+}
+
+/**
+ * Reads a source map, injects debug ID fields, and writes the source map to the target path.
+ */
+async function prepareSourceMapForDebugIdUpload(
+ sourceMapPath: string,
+ targetPath: string,
+ debugId: string,
+ rewriteSourcesHook: RewriteSourcesHook,
+ logger: Logger,
+): Promise {
+ let sourceMapFileContent: string;
+ try {
+ sourceMapFileContent = await util.promisify(fs.readFile)(sourceMapPath, {
+ encoding: 'utf8',
+ });
+ } catch (e) {
+ logger.error(`Failed to read source map for debug ID upload: ${sourceMapPath}`, e);
+ return;
+ }
+
+ let map: Record;
+ try {
+ map = JSON.parse(sourceMapFileContent) as { sources: unknown; [key: string]: unknown };
+ // For now we write both fields until we know what will become the standard - if ever.
+ map['debug_id'] = debugId;
+ map['debugId'] = debugId;
+ } catch {
+ logger.error(`Failed to parse source map for debug ID upload: ${sourceMapPath}`);
+ return;
+ }
+
+ if (map['sources'] && Array.isArray(map['sources'])) {
+ const mapDir = path.dirname(sourceMapPath);
+ map['sources'] = map['sources'].map((source: string) => rewriteSourcesHook(source, map, { mapDir }));
+ }
+
+ try {
+ await util.promisify(fs.writeFile)(targetPath, JSON.stringify(map), {
+ encoding: 'utf8',
+ });
+ } catch (e) {
+ logger.error(`Failed to prepare source map for debug ID upload: ${sourceMapPath}`, e);
+ return;
+ }
+}
+
+const PROTOCOL_REGEX = /^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//;
+export function defaultRewriteSourcesHook(source: string): string {
+ if (source.match(PROTOCOL_REGEX)) {
+ return source.replace(PROTOCOL_REGEX, '');
+ } else {
+ return path.relative(process.cwd(), path.normalize(source));
+ }
+}
diff --git a/packages/bundler-plugins/src/core/glob.ts b/packages/bundler-plugins/src/core/glob.ts
new file mode 100644
index 000000000000..0329b62b10a3
--- /dev/null
+++ b/packages/bundler-plugins/src/core/glob.ts
@@ -0,0 +1,8 @@
+import { glob } from 'glob';
+
+export function globFiles(
+ patterns: string | string[],
+ options?: { root?: string; ignore?: string | string[] },
+): Promise {
+ return glob(patterns, { absolute: true, nodir: true, ...options });
+}
diff --git a/packages/bundler-plugins/src/core/index.ts b/packages/bundler-plugins/src/core/index.ts
new file mode 100644
index 000000000000..bd58dbcd557e
--- /dev/null
+++ b/packages/bundler-plugins/src/core/index.ts
@@ -0,0 +1,133 @@
+import { transformAsync } from '@babel/core';
+import componentNameAnnotatePlugin, { experimentalComponentNameAnnotatePlugin } from '../babel-plugin';
+import SentryCli from '@sentry/cli';
+import { debug } from '@sentry/core';
+import * as fs from 'fs';
+import { CodeInjection, containsOnlyImports, stripQueryAndHashFromPath } from './utils';
+
+/**
+ * Determines whether the Sentry CLI binary is in its expected location.
+ * This function is useful since `@sentry/cli` installs the binary via a post-install
+ * script and post-install scripts may not always run. E.g. with `npm i --ignore-scripts`.
+ */
+export function sentryCliBinaryExists(): boolean {
+ return fs.existsSync(SentryCli.getPath());
+}
+
+// We need to be careful not to inject the snippet before any `"use strict";`s.
+// As an additional complication `"use strict";`s may come after any number of comments.
+export const COMMENT_USE_STRICT_REGEX =
+ // Note: CodeQL complains that this regex potentially has n^2 runtime. This likely won't affect realistic files.
+ /^(?:\s*|\/\*(?:.|\r|\n)*?\*\/|\/\/.*[\n\r])*(?:"[^"]*";|'[^']*';)?/;
+
+/**
+ * Checks if a file is a JavaScript file based on its extension.
+ * Handles query strings and hashes in the filename.
+ */
+export function isJsFile(fileName: string): boolean {
+ const cleanFileName = stripQueryAndHashFromPath(fileName);
+ return ['.js', '.mjs', '.cjs'].some(ext => cleanFileName.endsWith(ext));
+}
+
+/**
+ * Checks if a chunk should be skipped for code injection
+ *
+ * This is necessary to handle Vite's MPA (multi-page application) mode where
+ * HTML entry points create "facade" chunks that should not contain injected code.
+ * See: https://github.com/getsentry/sentry-javascript-bundler-plugins/issues/829
+ *
+ * However, in SPA mode, the main bundle also has an HTML facade but contains
+ * substantial application code. We should NOT skip injection for these bundles.
+ *
+ * @param code - The chunk's code content
+ * @param facadeModuleId - The facade module ID (if any) - HTML files create facade chunks
+ * @returns true if the chunk should be skipped
+ */
+export function shouldSkipCodeInjection(code: string, facadeModuleId: string | null | undefined): boolean {
+ // Skip empty chunks - these are placeholder chunks that should be optimized away
+ if (code.trim().length === 0) {
+ return true;
+ }
+
+ // For HTML facade chunks, only skip if they contain only import statements
+ if (facadeModuleId && stripQueryAndHashFromPath(facadeModuleId).endsWith('.html')) {
+ return containsOnlyImports(code);
+ }
+
+ return false;
+}
+
+export { globFiles } from './glob';
+
+// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+export function createComponentNameAnnotateHooks(ignoredComponents: string[], injectIntoHtml: boolean) {
+ type ParserPlugins = NonNullable[1]>['parserOpts']>['plugins'];
+
+ return {
+ async transform(this: void, code: string, id: string) {
+ // id may contain query and hash which will trip up our file extension logic below
+ const idWithoutQueryAndHash = stripQueryAndHashFromPath(id);
+
+ if (idWithoutQueryAndHash.match(/\\node_modules\\|\/node_modules\//)) {
+ return null;
+ }
+
+ // We will only apply this plugin on jsx and tsx files
+ if (!['.jsx', '.tsx'].some(ending => idWithoutQueryAndHash.endsWith(ending))) {
+ return null;
+ }
+
+ const parserPlugins: ParserPlugins = [];
+ if (idWithoutQueryAndHash.endsWith('.jsx')) {
+ parserPlugins.push('jsx');
+ } else if (idWithoutQueryAndHash.endsWith('.tsx')) {
+ parserPlugins.push('jsx', 'typescript');
+ }
+
+ const plugin = injectIntoHtml ? experimentalComponentNameAnnotatePlugin : componentNameAnnotatePlugin;
+
+ try {
+ const result = await transformAsync(code, {
+ plugins: [[plugin, { ignoredComponents }]],
+ filename: id,
+ parserOpts: {
+ sourceType: 'module',
+ allowAwaitOutsideFunction: true,
+ plugins: parserPlugins,
+ },
+ generatorOpts: {
+ decoratorsBeforeExport: true,
+ },
+ sourceMaps: true,
+ });
+
+ return {
+ code: result?.code ?? code,
+ map: result?.map,
+ };
+ } catch (e) {
+ debug.error(`Failed to apply react annotate plugin`, e);
+ }
+
+ return { code };
+ },
+ };
+}
+
+export function getDebugIdSnippet(debugId: string): CodeInjection {
+ return new CodeInjection(
+ `var n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="${debugId}",e._sentryDebugIdIdentifier="sentry-dbid-${debugId}");`,
+ );
+}
+
+export type { Logger } from './logger';
+export type { Options, SentrySDKBuildFlags } from './types';
+export {
+ CodeInjection,
+ replaceBooleanFlagsInCode,
+ stringToUUID,
+ generateReleaseInjectorCode,
+ generateModuleMetadataInjectorCode,
+} from './utils';
+export { createSentryBuildPluginManager } from './build-plugin-manager';
+export { createDebugIdUploadFunction } from './debug-id-upload';
diff --git a/packages/bundler-plugins/src/core/logger.ts b/packages/bundler-plugins/src/core/logger.ts
new file mode 100644
index 000000000000..70528517808f
--- /dev/null
+++ b/packages/bundler-plugins/src/core/logger.ts
@@ -0,0 +1,42 @@
+interface LoggerOptions {
+ silent: boolean;
+ debug: boolean;
+ prefix: string;
+}
+
+export type Logger = {
+ info(message: string, ...params: unknown[]): void;
+ warn(message: string, ...params: unknown[]): void;
+ error(message: string, ...params: unknown[]): void;
+ debug(message: string, ...params: unknown[]): void;
+};
+
+// Logging everything to stderr not to interfere with stdout
+export function createLogger(options: LoggerOptions): Logger {
+ return {
+ info(message: string, ...params: unknown[]) {
+ if (!options.silent) {
+ // eslint-disable-next-line no-console
+ console.info(`${options.prefix} Info: ${message}`, ...params);
+ }
+ },
+ warn(message: string, ...params: unknown[]) {
+ if (!options.silent) {
+ // eslint-disable-next-line no-console
+ console.warn(`${options.prefix} Warning: ${message}`, ...params);
+ }
+ },
+ error(message: string, ...params: unknown[]) {
+ if (!options.silent) {
+ // eslint-disable-next-line no-console
+ console.error(`${options.prefix} Error: ${message}`, ...params);
+ }
+ },
+ debug(message: string, ...params: unknown[]) {
+ if (!options.silent && options.debug) {
+ // eslint-disable-next-line no-console
+ console.debug(`${options.prefix} Debug: ${message}`, ...params);
+ }
+ },
+ };
+}
diff --git a/packages/bundler-plugins/src/core/options-mapping.ts b/packages/bundler-plugins/src/core/options-mapping.ts
new file mode 100644
index 000000000000..f1774d772752
--- /dev/null
+++ b/packages/bundler-plugins/src/core/options-mapping.ts
@@ -0,0 +1,233 @@
+import type { Logger } from './logger';
+import type {
+ Options as UserOptions,
+ SetCommitsOptions,
+ RewriteSourcesHook,
+ ResolveSourceMapHook,
+ IncludeEntry,
+ ModuleMetadata,
+ ModuleMetadataCallback,
+} from './types';
+import { determineReleaseName } from './utils';
+
+export type NormalizedOptions = {
+ org: string | undefined;
+ project: string | string[] | undefined;
+ authToken: string | undefined;
+ url: string;
+ headers: Record | undefined;
+ debug: boolean;
+ silent: boolean;
+ errorHandler: ((err: Error) => void) | undefined;
+ telemetry: boolean;
+ disable: boolean;
+ sourcemaps:
+ | {
+ disable?: boolean | 'disable-upload';
+ assets?: string | string[];
+ ignore?: string | string[];
+ rewriteSources?: RewriteSourcesHook;
+ resolveSourceMap?: ResolveSourceMapHook;
+ filesToDeleteAfterUpload?: string | string[] | Promise;
+ }
+ | undefined;
+ release: {
+ name: string | undefined;
+ inject: boolean;
+ create: boolean;
+ finalize: boolean;
+ vcsRemote: string;
+ setCommits:
+ | (SetCommitsOptions & {
+ shouldNotThrowOnFailure?: boolean;
+ })
+ | false
+ | undefined;
+ dist?: string;
+ deploy?:
+ | {
+ env: string;
+ started?: number | string;
+ finished?: number | string;
+ time?: number;
+ name?: string;
+ url?: string;
+ }
+ | false;
+ uploadLegacySourcemaps?: string | IncludeEntry | Array;
+ };
+ bundleSizeOptimizations:
+ | {
+ excludeDebugStatements?: boolean;
+ excludeTracing?: boolean;
+ excludeReplayCanvas?: boolean;
+ excludeReplayShadowDom?: boolean;
+ excludeReplayIframe?: boolean;
+ excludeReplayWorker?: boolean;
+ }
+ | undefined;
+ reactComponentAnnotation:
+ | {
+ enabled?: boolean;
+ ignoredComponents?: string[];
+ _experimentalInjectIntoHtml?: boolean;
+ }
+ | undefined;
+ _metaOptions: {
+ telemetry: {
+ metaFramework: string | undefined;
+ };
+ };
+ applicationKey: string | undefined;
+ moduleMetadata: ModuleMetadata | ModuleMetadataCallback | undefined;
+ _experiments: {
+ injectBuildInformation?: boolean;
+ } & Record;
+};
+
+export const SENTRY_SAAS_URL = 'https://sentry.io';
+
+// oxlint-disable-next-line complexity
+export function normalizeUserOptions(userOptions: UserOptions): NormalizedOptions {
+ const options = {
+ org: userOptions.org ?? process.env['SENTRY_ORG'],
+ project:
+ userOptions.project ??
+ (process.env['SENTRY_PROJECT']?.includes(',')
+ ? process.env['SENTRY_PROJECT'].split(',').map(p => p.trim())
+ : process.env['SENTRY_PROJECT']),
+ authToken: userOptions.authToken ?? process.env['SENTRY_AUTH_TOKEN'],
+ url: userOptions.url ?? process.env['SENTRY_URL'] ?? SENTRY_SAAS_URL,
+ headers: userOptions.headers,
+ debug: userOptions.debug ?? false,
+ silent: userOptions.silent ?? false,
+ errorHandler: userOptions.errorHandler,
+ telemetry: userOptions.telemetry ?? true,
+ disable: userOptions.disable ?? false,
+ sourcemaps: userOptions.sourcemaps,
+ release: {
+ ...userOptions.release,
+ name: userOptions.release?.name ?? process.env['SENTRY_RELEASE'] ?? determineReleaseName(),
+ inject: userOptions.release?.inject ?? true,
+ create: userOptions.release?.create ?? true,
+ finalize: userOptions.release?.finalize ?? true,
+ vcsRemote: userOptions.release?.vcsRemote ?? process.env['SENTRY_VSC_REMOTE'] ?? 'origin',
+ setCommits: userOptions.release?.setCommits as
+ | (SetCommitsOptions & { shouldNotThrowOnFailure?: boolean })
+ | false
+ | undefined,
+ },
+ bundleSizeOptimizations: userOptions.bundleSizeOptimizations,
+ reactComponentAnnotation: userOptions.reactComponentAnnotation,
+ _metaOptions: {
+ telemetry: {
+ metaFramework: userOptions._metaOptions?.telemetry?.metaFramework,
+ bundlerMajorVersion: userOptions._metaOptions?.telemetry?.bundlerMajorVersion,
+ },
+ },
+ applicationKey: userOptions.applicationKey,
+ moduleMetadata: userOptions.moduleMetadata,
+ _experiments: userOptions._experiments ?? {},
+ };
+
+ if (options.release.setCommits === undefined) {
+ if (
+ process.env['VERCEL'] &&
+ process.env['VERCEL_GIT_COMMIT_SHA'] &&
+ 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
+ // preview environments because the previous commit is always the "stem" commit of the preview/PR causing Sentry
+ // to notify you for other people creating PRs.
+ process.env['VERCEL_TARGET_ENV'] === 'production'
+ ) {
+ options.release.setCommits = {
+ shouldNotThrowOnFailure: true,
+ commit: process.env['VERCEL_GIT_COMMIT_SHA'],
+ previousCommit: process.env['VERCEL_GIT_PREVIOUS_SHA'],
+ repo: `${process.env['VERCEL_GIT_REPO_OWNER']}/${process.env['VERCEL_GIT_REPO_SLUG']}`,
+ ignoreEmpty: true,
+ ignoreMissing: true,
+ };
+ } else {
+ options.release.setCommits = {
+ shouldNotThrowOnFailure: true,
+ auto: true,
+ ignoreEmpty: true,
+ ignoreMissing: true,
+ };
+ }
+ }
+
+ if (options.release.deploy === undefined && process.env['VERCEL'] && process.env['VERCEL_TARGET_ENV']) {
+ options.release.deploy = {
+ env: `vercel-${process.env['VERCEL_TARGET_ENV']}`,
+ url: process.env['VERCEL_URL'] ? `https://${process.env['VERCEL_URL']}` : undefined,
+ };
+ }
+
+ return options;
+}
+
+/**
+ * Validates a few combinations of options that are not checked by Sentry CLI.
+ *
+ * For all other options, we can rely on Sentry CLI to validate them. In fact,
+ * we can't validate them in the plugin because Sentry CLI might pick up options from
+ * its config file.
+ *
+ * @param options the internal options
+ * @param logger the logger
+ *
+ * @returns `true` if the options are valid, `false` otherwise
+ */
+export function validateOptions(options: NormalizedOptions, logger: Logger): boolean {
+ const setCommits = options.release?.setCommits;
+ if (setCommits) {
+ if (!setCommits.auto && !(setCommits.repo && setCommits.commit)) {
+ logger.error(
+ 'The `setCommits` option was specified but is missing required properties.',
+ 'Please set either `auto` or both, `repo` and `commit`.',
+ );
+ return false;
+ }
+ // `auto` is mutually exclusive with `repo`/`commit` in the type definitions,
+ // so TS narrows those away once `auto` is truthy. Users can still pass all
+ // three at runtime though, which is exactly the conflict we want to warn
+ // about, so read `repo`/`commit` without that narrowing.
+ const { repo, commit } = setCommits as { repo?: string; commit?: string };
+ if (setCommits.auto && repo && commit) {
+ logger.warn(
+ 'The `setCommits` options includes `auto` but also `repo` and `commit`.',
+ 'Ignoring `repo` and `commit`.',
+ 'Please only set either `auto` or both, `repo` and `commit`.',
+ );
+ }
+ }
+
+ if (options.release?.deploy && typeof options.release.deploy === 'object' && !options.release.deploy.env) {
+ logger.error(
+ 'The `deploy` option was specified but is missing the required `env` property.',
+ 'Please set the `env` property.',
+ );
+ return false;
+ }
+
+ if (options.project && Array.isArray(options.project)) {
+ if (options.project.length === 0) {
+ logger.error(
+ 'The `project` option was specified as an array but is empty.',
+ 'Please provide at least one project slug.',
+ );
+ return false;
+ }
+ // Check each project is a non-empty string
+ const invalidProjects = options.project.filter(p => typeof p !== 'string' || p.trim() === '');
+ if (invalidProjects.length > 0) {
+ logger.error('The `project` option contains invalid project slugs.', 'All projects must be non-empty strings.');
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/packages/bundler-plugins/src/core/sentry/telemetry.ts b/packages/bundler-plugins/src/core/sentry/telemetry.ts
new file mode 100644
index 000000000000..cb0709a6b468
--- /dev/null
+++ b/packages/bundler-plugins/src/core/sentry/telemetry.ts
@@ -0,0 +1,181 @@
+import SentryCli from '@sentry/cli';
+import type { Client } from '@sentry/types';
+import type { ServerRuntimeClientOptions } from '@sentry/core';
+import { applySdkMetadata, ServerRuntimeClient } from '@sentry/core';
+import type { NormalizedOptions } from '../options-mapping';
+import { SENTRY_SAAS_URL } from '../options-mapping';
+import { Scope } from '@sentry/core';
+import { createStackParser, nodeStackLineParser } from '@sentry/core';
+import { makeOptionallyEnabledNodeTransport } from './transports';
+import { getProjects } from '../utils';
+import { LIB_VERSION } from '../version';
+
+const SENTRY_SAAS_HOSTNAME = 'sentry.io';
+
+const stackParser = createStackParser(nodeStackLineParser());
+
+export function createSentryInstance(
+ options: NormalizedOptions,
+ shouldSendTelemetry: Promise,
+ buildTool: string,
+ buildToolMajorVersion: string | undefined,
+): { sentryScope: Scope; sentryClient: Client } {
+ const clientOptions: ServerRuntimeClientOptions = {
+ platform: 'node',
+ runtime: { name: 'node', version: global.process.version },
+
+ dsn: 'https://4c2bae7d9fbc413e8f7385f55c515d51@o1.ingest.sentry.io/6690737',
+
+ tracesSampleRate: 1,
+ sampleRate: 1,
+
+ release: LIB_VERSION,
+ integrations: [],
+ tracePropagationTargets: ['sentry.io/api'],
+
+ stackParser,
+
+ beforeSend: event => {
+ event.exception?.values?.forEach(exception => {
+ delete exception.stacktrace;
+ });
+
+ delete event.server_name; // Server name might contain PII
+ return event;
+ },
+
+ beforeSendTransaction: event => {
+ delete event.server_name; // Server name might contain PII
+ return event;
+ },
+
+ // We create a transport that stalls sending events until we know that we're allowed to (i.e. when Sentry CLI told
+ // us that the upload URL is the Sentry SaaS URL)
+ transport: makeOptionallyEnabledNodeTransport(shouldSendTelemetry),
+ };
+
+ applySdkMetadata(clientOptions, 'node');
+
+ const client = new ServerRuntimeClient(clientOptions);
+ const scope = new Scope();
+ scope.setClient(client);
+
+ setTelemetryDataOnScope(options, scope, buildTool, buildToolMajorVersion);
+
+ return { sentryScope: scope, sentryClient: client };
+}
+
+export function setTelemetryDataOnScope(
+ options: NormalizedOptions,
+ scope: Scope,
+ buildTool: string,
+ buildToolMajorVersion?: string,
+): void {
+ const { org, project, release, errorHandler, sourcemaps, reactComponentAnnotation } = options;
+
+ scope.setTag('upload-legacy-sourcemaps', !!release.uploadLegacySourcemaps);
+ if (release.uploadLegacySourcemaps) {
+ scope.setTag(
+ 'uploadLegacySourcemapsEntries',
+ Array.isArray(release.uploadLegacySourcemaps) ? release.uploadLegacySourcemaps.length : 1,
+ );
+ }
+
+ scope.setTag('module-metadata', !!options.moduleMetadata);
+ scope.setTag('inject-build-information', !!options._experiments.injectBuildInformation);
+
+ // Optional release pipeline steps
+ if (release.setCommits) {
+ scope.setTag('set-commits', release.setCommits.auto === true ? 'auto' : 'manual');
+ } else {
+ scope.setTag('set-commits', 'undefined');
+ }
+ scope.setTag('finalize-release', release.finalize);
+ scope.setTag('deploy-options', !!release.deploy);
+
+ // Miscellaneous options
+ scope.setTag('custom-error-handler', !!errorHandler);
+ scope.setTag('sourcemaps-assets', !!sourcemaps?.assets);
+ scope.setTag('delete-after-upload', !!sourcemaps?.filesToDeleteAfterUpload);
+ scope.setTag('sourcemaps-disabled', !!sourcemaps?.disable);
+
+ scope.setTag('react-annotate', !!reactComponentAnnotation?.enabled);
+
+ scope.setTag('node', process.version);
+ scope.setTag('platform', process.platform);
+
+ scope.setTag('meta-framework', options._metaOptions.telemetry.metaFramework ?? 'none');
+
+ scope.setTag('application-key-set', options.applicationKey !== undefined);
+
+ scope.setTag('ci', !!process.env['CI']);
+
+ scope.setTags({
+ organization: org,
+ project: Array.isArray(project) ? project.join(', ') : (project ?? 'undefined'),
+ bundler: buildTool,
+ });
+
+ if (buildToolMajorVersion) {
+ scope.setTag('bundler-major-version', buildToolMajorVersion);
+ }
+
+ scope.setUser({ id: org });
+}
+
+export async function allowedToSendTelemetry(options: NormalizedOptions): Promise {
+ const { silent, org, project, authToken, url, headers, telemetry, release } = options;
+
+ // `options.telemetry` defaults to true
+ if (telemetry === false) {
+ return false;
+ }
+
+ if (url === SENTRY_SAAS_URL) {
+ return true;
+ }
+
+ const cli = new SentryCli(null, {
+ url,
+ authToken,
+ org,
+ project: getProjects(project)?.[0],
+ vcsRemote: release.vcsRemote,
+ silent,
+ headers,
+ });
+
+ let cliInfo;
+ try {
+ // Makes a call to SentryCLI to get the Sentry server URL the CLI uses.
+ // We need to check and decide to use telemetry based on the CLI's response to this call
+ // because only at this time we checked a possibly existing .sentryclirc file. This file
+ // could point to another URL than the default URL.
+ cliInfo = await cli.execute(['info'], false);
+ } catch {
+ return false;
+ }
+
+ const cliInfoUrl = cliInfo
+ .split(/(\r\n|\n|\r)/)[0]
+ ?.replace(/^Sentry Server: /, '')
+ ?.trim();
+
+ if (cliInfoUrl === undefined) {
+ return false;
+ }
+
+ return new URL(cliInfoUrl).hostname === SENTRY_SAAS_HOSTNAME;
+}
+
+/**
+ * Flushing the SDK client can fail. We never want to crash the plugin because of telemetry.
+ */
+export async function safeFlushTelemetry(sentryClient: Client): Promise {
+ try {
+ await sentryClient.flush(2000);
+ } catch {
+ // Noop when flushing fails.
+ // We don't even need to log anything because there's likely nothing the user can do and they likely will not care.
+ }
+}
diff --git a/packages/bundler-plugins/src/core/sentry/transports.ts b/packages/bundler-plugins/src/core/sentry/transports.ts
new file mode 100644
index 000000000000..a679ed968a19
--- /dev/null
+++ b/packages/bundler-plugins/src/core/sentry/transports.ts
@@ -0,0 +1,137 @@
+/**
+ * This is a simplified version of the Sentry Node SDK's HTTP transport.
+ */
+import * as https from 'node:https';
+import { Readable } from 'node:stream';
+import { createGzip } from 'node:zlib';
+import { createTransport, suppressTracing } from '@sentry/core';
+import type {
+ BaseTransportOptions,
+ Transport,
+ TransportMakeRequestResponse,
+ TransportRequest,
+ TransportRequestExecutor,
+} from '@sentry/types';
+import { join } from 'node:path';
+import { appendFileSync, mkdirSync } from 'node:fs';
+
+// Estimated maximum size for reasonable standalone event
+const GZIP_THRESHOLD = 1024 * 32;
+
+/**
+ * Gets a stream from a Uint8Array or string
+ * Readable.from is ideal but was added in node.js v12.3.0 and v10.17.0
+ */
+function streamFromBody(body: Uint8Array | string): Readable {
+ return new Readable({
+ read() {
+ this.push(body);
+ this.push(null);
+ },
+ });
+}
+
+/**
+ * Creates a RequestExecutor to be used with `createTransport`.
+ */
+function createRequestExecutor(options: BaseTransportOptions): TransportRequestExecutor {
+ const { hostname, pathname, port, protocol, search } = new URL(options.url);
+ return function makeRequest(request: TransportRequest): Promise {
+ return new Promise((resolve, reject) => {
+ suppressTracing(() => {
+ let body = streamFromBody(request.body);
+
+ const headers: Record = {};
+
+ if (request.body.length > GZIP_THRESHOLD) {
+ headers['content-encoding'] = 'gzip';
+ body = body.pipe(createGzip());
+ }
+
+ const req = https.request(
+ {
+ method: 'POST',
+ headers,
+ hostname,
+ path: `${pathname}${search}`,
+ port,
+ protocol,
+ },
+ res => {
+ res.on('data', () => {
+ // Drain socket
+ });
+
+ res.on('end', () => {
+ // Drain socket
+ });
+
+ res.setEncoding('utf8');
+
+ // "Key-value pairs of header names and values. Header names are lower-cased."
+ // https://nodejs.org/api/http.html#http_message_headers
+ const retryAfterHeader = res.headers['retry-after'] ?? null;
+ const rateLimitsHeader = res.headers['x-sentry-rate-limits'] ?? null;
+
+ resolve({
+ statusCode: res.statusCode,
+ headers: {
+ 'retry-after': retryAfterHeader,
+ 'x-sentry-rate-limits': Array.isArray(rateLimitsHeader)
+ ? rateLimitsHeader[0] || null
+ : rateLimitsHeader,
+ },
+ });
+ },
+ );
+
+ req.on('error', reject);
+ body.pipe(req);
+ });
+ });
+ };
+}
+
+/**
+ * Creates a Transport that uses native the native 'http' and 'https' modules to send events to Sentry.
+ */
+function makeNodeTransport(options: BaseTransportOptions): Transport {
+ const requestExecutor = createRequestExecutor(options);
+ return createTransport(options, requestExecutor);
+}
+
+/** A transport that can be optionally enabled as a later time than it's
+ * creation */
+export function makeOptionallyEnabledNodeTransport(
+ shouldSendTelemetry: Promise,
+): (options: BaseTransportOptions) => Transport {
+ return nodeTransportOptions => {
+ const nodeTransport = makeNodeTransport(nodeTransportOptions);
+
+ return {
+ flush: timeout => nodeTransport.flush(timeout),
+ send: async request => {
+ // If global.__SENTRY_INTERCEPT_TRANSPORT__ is an array, we push the
+ // envelope into it for testing purposes.
+ if ('__SENTRY_INTERCEPT_TRANSPORT__' in global && Array.isArray(global.__SENTRY_INTERCEPT_TRANSPORT__)) {
+ global.__SENTRY_INTERCEPT_TRANSPORT__.push(request);
+ return { statusCode: 200 };
+ }
+
+ if (await shouldSendTelemetry) {
+ if (process.env['SENTRY_TEST_OUT_DIR']) {
+ const outDir = process.env['SENTRY_TEST_OUT_DIR'];
+ mkdirSync(outDir, { recursive: true });
+ const path = join(outDir, 'sentry-telemetry.json');
+ appendFileSync(path, `${JSON.stringify(request)},\n`);
+ return { statusCode: 200 };
+ }
+
+ return nodeTransport.send(request);
+ }
+
+ return { statusCode: 200 };
+ },
+ };
+ };
+}
diff --git a/packages/bundler-plugins/src/core/types.ts b/packages/bundler-plugins/src/core/types.ts
new file mode 100644
index 000000000000..5f641d3a21a4
--- /dev/null
+++ b/packages/bundler-plugins/src/core/types.ts
@@ -0,0 +1,640 @@
+export interface Options {
+ /**
+ * The slug of the Sentry organization associated with the app.
+ *
+ * This value can also be specified via the `SENTRY_ORG` environment variable.
+ */
+ org?: string;
+
+ /**
+ * The slug of the Sentry project associated with the app.
+ *
+ * When uploading source maps, you can specify multiple projects (as an array) to upload
+ * the same source maps to multiple projects. This is useful in monorepo environments
+ * where multiple projects share the same release.
+ *
+ * This value can also be specified via the `SENTRY_PROJECT` environment variable.
+ */
+ project?: string | string[];
+
+ /**
+ * The authentication token to use for all communication with Sentry.
+ * Can be obtained from https://sentry.io/orgredirect/organizations/:orgslug/settings/auth-tokens/.
+ *
+ * This value can also be specified via the `SENTRY_AUTH_TOKEN` environment variable.
+ *
+ * @see https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens
+ */
+ authToken?: string | undefined;
+
+ /**
+ * The base URL of your Sentry instance. Use this if you are using a self-hosted
+ * or Sentry instance other than sentry.io.
+ *
+ * This value can also be set via the `SENTRY_URL` environment variable.
+ *
+ * @default "https://sentry.io" (correct value for SaaS customers)
+ */
+ url?: string;
+
+ /**
+ * Additional headers to send with every outgoing request to Sentry.
+ */
+ headers?: Record;
+
+ /**
+ * Enable debug information logs during build-time.
+ * Enabling this will give you, for example, logs about source maps.
+ *
+ * This option also propagates the debug flag to the Sentry CLI by setting
+ * the `SENTRY_LOG_LEVEL` environment variable to `"debug"` if it's not already set.
+ * If you have explicitly set `SENTRY_LOG_LEVEL`, this option will be ignored.
+ *
+ * @default false
+ */
+ debug?: boolean;
+
+ /**
+ * Suppresses all build logs (all log levels, including errors).
+ *
+ * @default false
+ */
+ silent?: boolean;
+
+ /**
+ * When an error occurs during release creation or sourcemaps upload, the plugin will call this function.
+ *
+ * By default, the plugin will simply throw an error, thereby stopping the bundling process.
+ * If an `errorHandler` callback is provided, compilation will continue, unless an error is
+ * thrown in the provided callback.
+ *
+ * To allow compilation to continue but still emit a warning, set this option to the following:
+ *
+ * ```js
+ * (err) => {
+ * console.warn(err);
+ * }
+ * ```
+ */
+ errorHandler?: (err: Error) => void;
+
+ /**
+ * If this flag is `true`, internal plugin errors and performance data will be sent to Sentry.
+ * It will not collect any sensitive or user-specific data.
+ *
+ * At Sentry, we like to use Sentry ourselves to deliver faster and more stable products.
+ * We're very careful of what we're sending. We won't collect anything other than error
+ * and high-level performance data. We will never collect your code or any details of the
+ * projects in which you're using this plugin.
+ *
+ * @default true
+ */
+ telemetry?: boolean;
+
+ /**
+ * Completely disables all functionality of the plugin.
+ *
+ * Defaults to `false`.
+ */
+ disable?: boolean;
+
+ /**
+ * Options related to source maps upload and processing.
+ */
+ sourcemaps?: {
+ /**
+ * Disables all functionality related to sourcemaps if set to `true`.
+ *
+ * If set to `"disable-upload"`, the plugin will not upload sourcemaps to Sentry, but will inject debug IDs into the build artifacts.
+ * This is useful if you want to manually upload sourcemaps to Sentry at a later point in time.
+ *
+ * @default false
+ */
+ disable?: boolean | 'disable-upload';
+
+ /**
+ * A glob or an array of globs that specify the build artifacts and source maps that will be uploaded to Sentry.
+ *
+ * The globbing patterns must follow the implementation of the `glob` package: https://www.npmjs.com/package/glob#glob-primer
+ *
+ * If this option is not specified, the plugin will try to upload all JavaScript files and source map files that are created during build.
+ *
+ * Use the `debug` option to print information about which files end up being uploaded.
+ *
+ */
+ assets?: string | string[];
+
+ /**
+ * A glob or an array of globs that specifies which build artifacts should not be uploaded to Sentry.
+ *
+ * The globbing patterns must follow the implementation of the `glob` package: https://www.npmjs.com/package/glob#glob-primer
+ *
+ * Use the `debug` option to print information about which files end up being uploaded.
+ *
+ * @default []
+ */
+ ignore?: string | string[];
+
+ /**
+ * Hook to rewrite the `sources` field inside the source map before being uploaded to Sentry. Does not modify the actual source map.
+ *
+ * The hook receives the source path, the parsed source map object, and a context object containing `mapDir` -
+ * the directory of the source map file, useful for resolving relative source paths.
+ *
+ * Defaults to making all sources relative to `process.cwd()` while building.
+ */
+ rewriteSources?: RewriteSourcesHook;
+
+ /**
+ * Hook to customize source map file resolution.
+ *
+ * The hook is called with the absolute path of the build artifact and the value of the `//# sourceMappingURL=`
+ * comment, if present. The hook should then return an absolute path (or a promise that resolves to one) indicating
+ * where to find the artifact's corresponding source map file. If no path is returned or the returned path doesn't
+ * exist, the standard source map resolution process will be used.
+ *
+ * The standard process first tries to resolve based on the `//# sourceMappingURL=` value (it supports `file://`
+ * urls and absolute/relative paths). If that path doesn't exist, it then looks for a file named
+ * `${artifactName}.map` in the same directory as the artifact.
+ *
+ * Note: This is mostly helpful for complex builds with custom source map generation. For example, if you put source
+ * maps into a separate directory and rewrite the `//# sourceMappingURL=` comment to something other than a relative
+ * directory, sentry will be unable to locate the source maps for a given build artifact. This hook allows you to
+ * implement the resolution process yourself.
+ *
+ * Use the `debug` option to print information about source map resolution.
+ */
+ resolveSourceMap?: ResolveSourceMapHook;
+
+ /**
+ * A glob or an array of globs that specifies the build artifacts that should be deleted after the artifact upload to Sentry has been completed.
+ *
+ * Note: If you pass in a Promise that resolves to a string or array, the plugin will await the Promise and use
+ * the resolved value globs. This is useful if you need to dynamically determine the files to delete. Some
+ * higher-level Sentry SDKs or options use this feature (e.g., SvelteKit).
+ *
+ * The globbing patterns must follow the implementation of the `glob` package: https://www.npmjs.com/package/glob#glob-primer
+ *
+ * Use the `debug` option to print information about which files end up being deleted.
+ */
+ filesToDeleteAfterUpload?: string | string[] | Promise;
+ };
+
+ /**
+ * Options related to managing the Sentry releases for a build.
+ *
+ * More info: https://docs.sentry.io/product/releases/
+ */
+ release?: {
+ /**
+ * Unique identifier for the release you want to create.
+ *
+ * This value can also be specified via the `SENTRY_RELEASE` environment variable.
+ *
+ * Defaults to automatically detecting a value for your environment.
+ * This includes values for Cordova, Heroku, AWS CodeBuild, CircleCI, Xcode, and Gradle, and otherwise uses the git `HEAD`'s commit SHA
+ * (the latter requires access to git CLI and for the root directory to be a valid repository).
+ *
+ * If no `name` is provided and the plugin can't automatically detect one, no release will be created.
+ */
+ name?: string;
+
+ /**
+ * Whether the plugin should inject release information into the build for the SDK to pick it up when sending events. (recommended)
+ *
+ * Defaults to `true`.
+ */
+ inject?: boolean;
+
+ /**
+ * Whether the plugin should create a release on Sentry during the build.
+ *
+ * Note that a release may still appear in Sentry even if this value is `false`. Any Sentry event that has a release value attached
+ * will automatically create a release (for example, via the `inject` option).
+ *
+ * @default true
+ */
+ create?: boolean;
+
+ /**
+ * Whether to automatically finalize the release. The release is finalized by adding an end timestamp after the build ends.
+ *
+ * @default true
+ */
+ finalize?: boolean;
+
+ /**
+ * Unique distribution identifier for the release. Used to further segment the release.
+ *
+ * Usually your build number.
+ */
+ dist?: string;
+
+ /**
+ * Version control system (VCS) remote name.
+ *
+ * This value can also be specified via the `SENTRY_VSC_REMOTE` environment variable.
+ *
+ * @default "origin"
+ */
+ vcsRemote?: string;
+
+ /**
+ * Configuration for associating the release with its commits in Sentry.
+ *
+ * Set to `false` to disable commit association.
+ *
+ * @default { auto: true }
+ */
+ setCommits?: SetCommitsOptions | false;
+
+ /**
+ * Configuration for adding deployment information to the release in Sentry.
+ *
+ * Set to `false` to disable automatic deployment detection and creation.
+ */
+ deploy?: DeployOptions | false;
+
+ /**
+ * Legacy method of uploading source maps. (not recommended unless necessary)
+ *
+ * One or more paths that should be scanned recursively for sources.
+ *
+ * Each path can be given as a string or an object with more specific options.
+ *
+ * The modern version of doing source maps upload is more robust and way easier to get working but has to inject a very small snippet of JavaScript into your output bundles.
+ * In situations where this leads to problems (e.g subresource integrity) you can use this option as a fallback.
+ */
+ uploadLegacySourcemaps?: string | IncludeEntry | Array;
+ };
+
+ /**
+ * Options for bundle size optimizations by excluding certain features.
+ */
+ bundleSizeOptimizations?: {
+ /**
+ * Exclude debug statements from the bundle, thus disabling features like the SDK's `debug` option.
+ *
+ * If set to `true`, the plugin will attempt to tree-shake (remove) any debugging code within the Sentry SDK during the build.
+ * Note that the success of this depends on tree-shaking being enabled in your build tooling.
+ *
+ * @default false
+ */
+ excludeDebugStatements?: boolean;
+
+ /**
+ * Exclude tracing functionality from the bundle, thus disabling features like performance monitoring.
+ *
+ * If set to `true`, the plugin will attempt to tree-shake (remove) code within the Sentry SDK that is related to tracing and performance monitoring.
+ * Note that the success of this depends on tree-shaking being enabled in your build tooling.
+ *
+ * **Notice:** Do not enable this when you're using any performance monitoring-related SDK features (e.g. `Sentry.startTransaction()`).
+
+ * @default false
+ */
+ excludeTracing?: boolean;
+
+ /**
+ * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay Canvas recording functionality.
+ * Note that the success of this depends on tree-shaking being enabled in your build tooling.
+ *
+ * You can safely do this when you do not want to capture any Canvas activity via Sentry Session Replay.
+ *
+ * @deprecated In versions v7.78.0 and later of the Sentry JavaScript SDKs, canvas recording is opt-in making this option redundant.
+ */
+ excludeReplayCanvas?: boolean;
+
+ /**
+ * Exclude Replay Shadow DOM functionality from the bundle.
+ *
+ * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay Shadow DOM recording functionality.
+ * Note that the success of this depends on tree-shaking being enabled in your build tooling.
+ *
+ * This option is safe to be used when you do not want to capture any Shadow DOM activity via Sentry Session Replay.
+ *
+ * @default false
+ */
+ excludeReplayShadowDom?: boolean;
+
+ /**
+ * Exclude Replay iFrame functionality from the bundle.
+ *
+ * If set to `true`, the Sentry SDK will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay `iframe` recording functionality.
+ * Note that the success of this depends on tree-shaking being enabled in your build tooling.
+ *
+ * You can safely do this when you do not want to capture any `iframe` activity via Sentry Session Replay.
+ *
+ * @default false
+ */
+ excludeReplayIframe?: boolean;
+
+ /**
+ * Exclude Replay worker functionality from the bundle.
+ *
+ * If set to `true`, the Sentry SDK will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay's Compression Web Worker.
+ * Note that the success of this depends on tree-shaking being enabled in your build tooling.
+ *
+ * **Notice:** You should only use this option if you manually host a compression worker and configure it in your Sentry Session Replay integration config via the `workerUrl` option.
+ *
+ * @default false
+ */
+ excludeReplayWorker?: boolean;
+ };
+
+ /**
+ * Options related to react component name annotations.
+ * Disabled by default, unless a value is set for this option.
+ * When enabled, your app's DOM will automatically be annotated during build-time with their respective component names.
+ * This will unlock the capability to search for Replays in Sentry by component name, as well as see component names in breadcrumbs and performance monitoring.
+ * Please note that this feature is not currently supported by the esbuild bundler plugins, and will only annotate React components
+ */
+ reactComponentAnnotation?: {
+ /**
+ * Whether the component name annotate plugin should be enabled or not.
+ */
+ enabled?: boolean;
+ /**
+ * A list of strings representing the names of components to ignore. The plugin will not apply `data-sentry` annotations on the DOM element for these components.
+ */
+ ignoredComponents?: string[];
+ /**
+ * An experimental component annotation injection mode that injects
+ * annotations into HTML rather than React components.
+ */
+ _experimentalInjectIntoHtml?: boolean;
+ };
+
+ /**
+ * Metadata that should be associated with the built application.
+ *
+ * The metadata is serialized and can be looked up at runtime from within the SDK (for example in the `beforeSend`,
+ * event processors, or the transport), allowing for custom event filtering logic or routing of events.
+ *
+ * Metadata can either be passed directly or alternatively a callback can be provided that will be
+ * called with the following parameters:
+ * - `org`: The organization slug.
+ * - `project`: The project slug (when multiple projects are configured, this is the first project).
+ * - `projects`: An array of all project slugs (available when multiple projects are configured).
+ * - `release`: The release name.
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ moduleMetadata?: ModuleMetadata | ModuleMetadataCallback;
+
+ /**
+ * A key which will embedded in all the bundled files. The SDK will be able to use the key to apply filtering
+ * rules, for example using the `thirdPartyErrorFilterIntegration`.
+ */
+ applicationKey?: string;
+
+ /**
+ * Options that are considered experimental and subject to change.
+ *
+ * @experimental API that does not follow semantic versioning and may change in any release
+ */
+ _experiments?: {
+ /**
+ * If set to true, the plugin will inject an additional `SENTRY_BUILD_INFO` variable.
+ * This contains information about the build, e.g. dependencies, node version and other useful data.
+ *
+ * Defaults to `false`.
+ */
+ injectBuildInformation?: boolean;
+ } & Record;
+
+ /**
+ * Options that are useful for building wrappers around the plugin. You likely don't need these options unless you
+ * are distributing a tool that depends on this plugin
+ */
+ _metaOptions?: {
+ /**
+ * Overrides the prefix that come before logger messages. (e.g. `[some-prefix] Info: Some log message`)
+ *
+ * Example value: `[sentry-webpack-plugin (client)]`
+ */
+ loggerPrefixOverride?: string;
+
+ /**
+ * Arbitrary telemetry items.
+ */
+ telemetry?: {
+ /**
+ * The meta framework using the plugin.
+ */
+ metaFramework?: string;
+ /**
+ * The major version of the bundler (e.g., "4" or "5" for webpack).
+ */
+ bundlerMajorVersion?: string;
+ };
+ };
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type RewriteSourcesHook = (source: string, map: any, context?: { mapDir: string }) => string;
+
+export type ResolveSourceMapHook = (
+ artifactPath: string,
+ sourceMappingUrl: string | undefined,
+) => string | undefined | Promise;
+
+export interface ModuleMetadata {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ [key: string]: any;
+}
+
+export interface ModuleMetadataCallbackArgs {
+ org?: string;
+ project?: string;
+ projects?: string[];
+ release?: string;
+}
+
+export type ModuleMetadataCallback = (args: ModuleMetadataCallbackArgs) => ModuleMetadata;
+
+export type IncludeEntry = {
+ /**
+ * One or more paths to scan for files to upload.
+ */
+ paths: string[];
+
+ /**
+ * One or more paths to ignore during upload.
+ * Overrides entries in ignoreFile file.
+ *
+ * Defaults to `['node_modules']` if neither `ignoreFile` nor `ignore` is set.
+ */
+ ignore?: string | string[];
+
+ /**
+ * Path to a file containing list of files/directories to ignore.
+ *
+ * Can point to `.gitignore` or anything with the same format.
+ */
+ ignoreFile?: string;
+
+ /**
+ * Array of file extensions of files to be collected for the file upload.
+ *
+ * By default the following file extensions are processed: js, map, jsbundle and bundle.
+ */
+ ext?: string[];
+
+ /**
+ * URL prefix to add to the beginning of all filenames.
+ * Defaults to '~/' but you might want to set this to the full URL.
+ *
+ * This is also useful if your files are stored in a sub folder. eg: url-prefix '~/static/js'.
+ */
+ urlPrefix?: string;
+
+ /**
+ * URL suffix to add to the end of all filenames.
+ * Useful for appending query parameters.
+ */
+ urlSuffix?: string;
+
+ /**
+ * When paired with the `rewrite` option, this will remove a prefix from filename references inside of
+ * sourcemaps. For instance you can use this to remove a path that is build machine specific.
+ * Note that this will NOT change the names of uploaded files.
+ */
+ stripPrefix?: string[];
+
+ /**
+ * When paired with the `rewrite` option, this will add `~` to the `stripPrefix` array.
+ *
+ * Defaults to `false`.
+ */
+ stripCommonPrefix?: boolean;
+
+ /**
+ * Determines whether sentry-cli should attempt to link minified files with their corresponding maps.
+ * By default, it will match files and maps based on name, and add a Sourcemap header to each minified file
+ * for which it finds a map. Can be disabled if all minified files contain sourceMappingURL.
+ *
+ * Defaults to true.
+ */
+ sourceMapReference?: boolean;
+
+ /**
+ * Enables rewriting of matching source maps so that indexed maps are flattened and missing sources
+ * are inlined if possible.
+ *
+ * Defaults to true
+ */
+ rewrite?: boolean;
+
+ /**
+ * When `true`, attempts source map validation before upload if rewriting is not enabled.
+ * It will spot a variety of issues with source maps and cancel the upload if any are found.
+ *
+ * Defaults to `false` as this can cause false positives.
+ */
+ validate?: boolean;
+};
+
+export interface SentrySDKBuildFlags extends Record {
+ __SENTRY_DEBUG__?: boolean;
+ __SENTRY_TRACING__?: boolean;
+ __RRWEB_EXCLUDE_CANVAS__?: boolean;
+ __RRWEB_EXCLUDE_IFRAME__?: boolean;
+ __RRWEB_EXCLUDE_SHADOW_DOM__?: boolean;
+ __SENTRY_EXCLUDE_REPLAY_WORKER__?: boolean;
+}
+
+export type SetCommitsOptions = (AutoSetCommitsOptions | ManualSetCommitsOptions) & {
+ /**
+ * The commit before the beginning of this release (in other words,
+ * the last commit of the previous release).
+ *
+ * Defaults to the last commit of the previous release in Sentry.
+ *
+ * If there was no previous release, the last 10 commits will be used.
+ */
+ previousCommit?: string;
+
+ /**
+ * If the flag is to `true` and the previous release commit was not found
+ * in the repository, the plugin creates a release with the default commits
+ * count instead of failing the command.
+ *
+ * Defaults to `false`.
+ */
+ ignoreMissing?: boolean;
+
+ /**
+ * If this flag is set, the setCommits step will not fail and just exit
+ * silently if no new commits for a given release have been found.
+ *
+ * Defaults to `false`.
+ */
+ ignoreEmpty?: boolean;
+};
+
+type AutoSetCommitsOptions = {
+ /**
+ * Automatically sets `commit` and `previousCommit`. Sets `commit` to `HEAD`
+ * and `previousCommit` as described in the option's documentation.
+ *
+ * If you set this to `true`, manually specified `commit` and `previousCommit`
+ * options will be overridden. It is best to not specify them at all if you
+ * set this option to `true`.
+ */
+ auto: true;
+
+ repo?: undefined;
+ commit?: undefined;
+};
+
+type ManualSetCommitsOptions = {
+ auto?: false | undefined;
+
+ /**
+ * The full repo name as defined in Sentry.
+ *
+ * Required if the `auto` option is not set to `true`.
+ */
+ repo: string;
+
+ /**
+ * The current (last) commit in the release.
+ *
+ * Required if the `auto` option is not set to `true`.
+ */
+ commit: string;
+};
+
+type DeployOptions = {
+ /**
+ * Environment for this release. Values that make sense here would
+ * be `production` or `staging`.
+ */
+ env: string;
+
+ /**
+ * Deployment start time in Unix timestamp (in seconds) or ISO 8601 format.
+ */
+ started?: number | string;
+
+ /**
+ * Deployment finish time in Unix timestamp (in seconds) or ISO 8601 format.
+ */
+ finished?: number | string;
+
+ /**
+ * Deployment duration (in seconds). Can be used instead of started and finished.
+ */
+ time?: number;
+
+ /**
+ * Human-readable name for the deployment.
+ */
+ name?: string;
+
+ /**
+ * URL that points to the deployment.
+ */
+ url?: string;
+};
+
+export type HandleRecoverableErrorFn = (error: unknown, throwByDefault: boolean) => void;
diff --git a/packages/bundler-plugins/src/core/utils.ts b/packages/bundler-plugins/src/core/utils.ts
new file mode 100644
index 000000000000..608929407fd8
--- /dev/null
+++ b/packages/bundler-plugins/src/core/utils.ts
@@ -0,0 +1,502 @@
+/* oxlint-disable max-lines */
+import findUp from 'find-up';
+import path from 'path';
+import fs from 'fs';
+import os from 'os';
+import crypto from 'crypto';
+import childProcess from 'child_process';
+import type { SourceMap } from 'magic-string';
+import MagicString from 'magic-string';
+
+// Only escape characters that `JSON.stringify` leaves literal but that are
+// unsafe to inline into generated JavaScript: `<`/`>`/`&` (so a ``
+// sequence can't break out of an inline