diff --git a/.craft.yml b/.craft.yml index 61bce1eff1bc..8088e79fd21a 100644 --- a/.craft.yml +++ b/.craft.yml @@ -1,31 +1,219 @@ -minVersion: '0.8.4' -github: - owner: getsentry - repo: sentry-javascript +minVersion: '0.23.1' changelogPolicy: simple preReleaseCommand: bash scripts/craft-pre-release.sh targets: + # NPM Targets + ## 1. Base Packages, node or browser SDKs depend on + ## 1.1 Types - name: npm - - name: github - includeNames: /^sentry-.*$/ + id: '@sentry/types' + includeNames: /^sentry-types-\d.*\.tgz$/ + ## 1.2 Utils + - name: npm + id: '@sentry/utils' + includeNames: /^sentry-utils-\d.*\.tgz$/ + ## 1.3 Core SDK + - name: npm + id: '@sentry/core' + includeNames: /^sentry-core-\d.*\.tgz$/ + ## 1.4 Tracing package + - name: npm + id: '@sentry-internal/tracing' + includeNames: /^sentry-internal-tracing-\d.*\.tgz$/ + ## 1.5 Replay package (browser only) + - name: npm + id: '@sentry/replay' + includeNames: /^sentry-replay-\d.*\.tgz$/ + ## 1.6. OpenTelemetry package + - name: npm + id: '@sentry/opentelemetry' + includeNames: /^sentry-opentelemetry-\d.*\.tgz$/ + ## 1.7 Feedback package (browser only) + - name: npm + id: '@sentry-internal/feedback' + includeNames: /^sentry-internal-feedback-\d.*\.tgz$/ + ## 1.8 ReplayCanvas package (browser only) + - name: npm + id: '@sentry-internal/replay-canvas' + includeNames: /^sentry-internal-replay-canvas-\d.*\.tgz$/ + + ## 2. Browser & Node SDKs + - name: npm + id: '@sentry/browser' + includeNames: /^sentry-browser-\d.*\.tgz$/ + - name: npm + id: '@sentry/node' + includeNames: /^sentry-node-\d.*\.tgz$/ + - name: npm + id: '@sentry/profiling-node' + includeNames: /^sentry-profiling-node-\d.*\.tgz$/ + + ## 3 Browser-based Packages + - name: npm + id: '@sentry/angular-ivy' + includeNames: /^sentry-angular-ivy-\d.*\.tgz$/ + - name: npm + id: '@sentry/angular' + includeNames: /^sentry-angular-\d.*\.tgz$/ + - name: npm + id: '@sentry/ember' + includeNames: /^sentry-ember-\d.*\.tgz$/ + - name: npm + id: '@sentry/react' + includeNames: /^sentry-react-\d.*\.tgz$/ + - name: npm + id: '@sentry/svelte' + includeNames: /^sentry-svelte-\d.*\.tgz$/ + - name: npm + id: '@sentry/vue' + includeNames: /^sentry-vue-\d.*\.tgz$/ + - name: npm + id: '@sentry/wasm' + includeNames: /^sentry-wasm-\d.*\.tgz$/ + - name: npm + id: '@sentry/integrations' + includeNames: /^sentry-integrations-\d.*\.tgz$/ + + ## 4. WinterCG Packages + - name: npm + id: '@sentry/vercel-edge' + includeNames: /^sentry-vercel-edge-\d.*\.tgz$/ + - name: npm + id: '@sentry/deno' + includeNames: /^sentry-deno-\d.*\.tgz$/ + - name: commit-on-git-repository + # This will publish on the Deno registry + id: getsentry/deno + archive: /^sentry-deno-\d.*\.tgz$/ + repositoryUrl: https://github.com/getsentry/sentry-deno.git + stripComponents: 1 + branch: main + createTag: true + + ## 5. Node-based Packages + - name: npm + id: '@sentry/serverless' + includeNames: /^sentry-serverless-\d.*\.tgz$/ + - name: npm + id: '@sentry/opentelemetry-node' + includeNames: /^sentry-opentelemetry-node-\d.*\.tgz$/ + - name: npm + id: '@sentry/bun' + includeNames: /^sentry-bun-\d.*\.tgz$/ + + ## 6. Fullstack/Meta Frameworks (depending on Node and Browser or Framework SDKs) + - name: npm + id: '@sentry/nextjs' + includeNames: /^sentry-nextjs-\d.*\.tgz$/ + - name: npm + id: '@sentry/remix' + includeNames: /^sentry-remix-\d.*\.tgz$/ + - name: npm + id: '@sentry/sveltekit' + includeNames: /^sentry-sveltekit-\d.*\.tgz$/ + - name: npm + id: '@sentry/gatsby' + includeNames: /^sentry-gatsby-\d.*\.tgz$/ + - name: npm + id: '@sentry/astro' + includeNames: /^sentry-astro-\d.*\.tgz$/ + + ## 7. Other Packages + ## 7.1 + - name: npm + id: '@sentry-internal/typescript' + includeNames: /^sentry-internal-typescript-\d.*\.tgz$/ + - name: npm + id: '@sentry-internal/eslint-plugin-sdk' + includeNames: /^sentry-internal-eslint-plugin-sdk-\d.*\.tgz$/ + ## 7.2 + - name: npm + id: '@sentry-internal/eslint-config-sdk' + includeNames: /^sentry-internal-eslint-config-sdk-\d.*\.tgz$/ + + ## 8. Deprecated packages we still release (but no packages depend on them anymore) + - name: npm + id: '@sentry/hub' + includeNames: /^sentry-hub-\d.*\.tgz$/ + - name: npm + id: '@sentry/tracing' + includeNames: /^sentry-tracing-\d.*\.tgz$/ + + ## 9. Experimental packages + - name: npm + id: '@sentry/node-experimental' + includeNames: /^sentry-node-experimental-\d.*\.tgz$/ + + # NOTE: We publish the v7 layer under its own name so people on v7 can still get patches + # whenever we release a new v7 version—otherwise we would overwrite the current major lambda layer. + + # AWS Lambda Layer target + - name: aws-lambda-layer + includeNames: /^sentry-node-serverless-\d+.\d+.\d+(-(beta|alpha)\.\d+)?\.zip$/ + layerName: SentryNodeServerlessSDKv7 + compatibleRuntimes: + - name: node + versions: + - nodejs10.x + - nodejs12.x + - nodejs14.x + - nodejs16.x + - nodejs18.x + - nodejs20.x + license: MIT + + # CDN Bundle Target - name: gcs + id: 'browser-cdn-bundles' includeNames: /.*\.js.*$/ bucket: sentry-js-sdk paths: - path: /{{version}}/ metadata: cacheControl: 'public, max-age=31536000' + + # Github Release Target + - name: github + includeNames: /^sentry-.*$/ + + # Sentry Release Registry Target - name: registry - type: sdk - onlyIfPresent: /^sentry-browser-.*\.tgz$/ - includeNames: /\.js$/ - checksums: - - algorithm: sha384 - format: base64 - config: - canonical: 'npm:@sentry/browser' - - name: registry - type: sdk - onlyIfPresent: /^sentry-node-.*\.tgz$/ - config: - canonical: 'npm:@sentry/node' + sdks: + 'npm:@sentry/browser': + onlyIfPresent: /^sentry-browser-\d.*\.tgz$/ + includeNames: /\.js$/ + checksums: + - algorithm: sha384 + format: base64 + 'npm:@sentry/node': + onlyIfPresent: /^sentry-node-\d.*\.tgz$/ + 'npm:@sentry/react': + onlyIfPresent: /^sentry-react-\d.*\.tgz$/ + 'npm:@sentry/vue': + onlyIfPresent: /^sentry-vue-\d.*\.tgz$/ + 'npm:@sentry/gatsby': + onlyIfPresent: /^sentry-gatsby-\d.*\.tgz$/ + 'npm:@sentry/angular-ivy': + onlyIfPresent: /^sentry-angular-ivy-\d.*\.tgz$/ + 'npm:@sentry/angular': + onlyIfPresent: /^sentry-angular-\d.*\.tgz$/ + 'npm:@sentry/astro': + onlyIfPresent: /^sentry-astro-\d.*\.tgz$/ + 'npm:@sentry/wasm': + onlyIfPresent: /^sentry-wasm-\d.*\.tgz$/ + 'npm:@sentry/nextjs': + onlyIfPresent: /^sentry-nextjs-\d.*\.tgz$/ + 'npm:@sentry/remix': + onlyIfPresent: /^sentry-remix-\d.*\.tgz$/ + 'npm:@sentry/svelte': + onlyIfPresent: /^sentry-svelte-\d.*\.tgz$/ + 'npm:@sentry/sveltekit': + onlyIfPresent: /^sentry-sveltekit-\d.*\.tgz$/ + 'npm:@sentry/opentelemetry-node': + onlyIfPresent: /^sentry-opentelemetry-node-\d.*\.tgz$/ + 'npm:@sentry/bun': + onlyIfPresent: /^sentry-bun-\d.*\.tgz$/ + 'npm:@sentry/vercel-edge': + onlyIfPresent: /^sentry-vercel-edge-\d.*\.tgz$/ + 'npm:@sentry/ember': + onlyIfPresent: /^sentry-ember-\d.*\.tgz$/ diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000000..e20424aef7f3 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,62 @@ +// Note: All paths are relative to the directory in which eslint is being run, rather than the directory where this file +// lives + +// ESLint config docs: https://eslint.org/docs/user-guide/configuring/ + +module.exports = { + root: true, + env: { + es6: true, + }, + parserOptions: { + ecmaVersion: 2018, + }, + extends: ['@sentry-internal/sdk/src/base'], + ignorePatterns: [ + 'coverage/**', + 'build/**', + 'dist/**', + 'cjs/**', + 'esm/**', + 'examples/**', + 'test/manual/**', + 'types/**', + ], + overrides: [ + { + files: ['*.ts', '*.tsx', '*.d.ts'], + parserOptions: { + project: ['tsconfig.json'], + }, + }, + { + files: ['test/**/*.ts', 'test/**/*.tsx'], + parserOptions: { + project: ['tsconfig.test.json'], + }, + }, + { + files: ['jest/**/*.ts', 'scripts/**/*.ts'], + parserOptions: { + project: ['tsconfig.dev.json'], + }, + }, + { + files: ['*.tsx'], + rules: { + // Turn off jsdoc on tsx files until jsdoc is fixed for tsx files + // See: https://github.com/getsentry/sentry-javascript/issues/3871 + 'jsdoc/require-jsdoc': 'off', + }, + }, + { + files: ['scenarios/**', 'dev-packages/rollup-utils/**'], + parserOptions: { + sourceType: 'module', + }, + rules: { + 'no-console': 'off', + }, + }, + ], +}; diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000000..c9bf669eb28c --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,17 @@ +# Since version 2.23 (released in August 2019), git-blame has a feature +# to ignore or bypass certain commits. +# +# This file contains a list of commits that are not likely what you +# are looking for in a blame, such as mass reformatting or renaming. + +# build: Add `@typescript-eslint/consistent-type-imports` rule (#6662) +2aa4e94b036675245290596884959e06dcced044 + +# chore: Rename `integration-tests` -> `browser-integration-tests` (#7455) +ef6b3c7877d5fc8031c08bb28b0ffafaeb01f501 + +# chore: Enforce formatting of MD files in repository root #10127 +aecf26f22dbf65ce2c0caadc4ce71b46266c9f45 + +# chore: Create dev-packages folder #9997 +35205b4cc5783237e69452c39ea001e461d9c84d diff --git a/.github/CANARY_FAILURE_TEMPLATE.md b/.github/CANARY_FAILURE_TEMPLATE.md new file mode 100644 index 000000000000..d52932b93fc3 --- /dev/null +++ b/.github/CANARY_FAILURE_TEMPLATE.md @@ -0,0 +1,5 @@ +--- +title: '{{ env.TITLE }}' +labels: 'Type: Tests' +--- +Canary tests failed: {{ env.RUN_LINK }} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1f2b589d530d..8b137891791f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1 @@ -* @kamilogorek -packages/* @hazat + diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 000000000000..61c24641c292 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,120 @@ +name: 🐞 Bug Report +description: Tell us about something that's not working the way we (probably) intend. +labels: ['Type: Bug'] +body: + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues + required: true + - label: I have reviewed the documentation https://docs.sentry.io/ + required: true + - label: I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases + required: true + - type: dropdown + id: type + attributes: + label: How do you use Sentry? + options: + - Sentry Saas (sentry.io) + - Self-hosted/on-premise + validations: + required: true + - type: dropdown + id: package + attributes: + label: Which SDK are you using? + description: + If you're using the CDN bundles, please specify the exact bundle (e.g. `bundle.tracing.min.js`) in your SDK + setup. + options: + - '@sentry/browser' + - '@sentry/astro' + - '@sentry/angular' + - '@sentry/angular-ivy' + - '@sentry/bun' + - '@sentry/deno' + - '@sentry/ember' + - '@sentry/gatsby' + - '@sentry/nextjs' + - '@sentry/node' + - '@sentry/opentelemetry-node' + - '@sentry/react' + - '@sentry/remix' + - '@sentry/serverless' + - '@sentry/svelte' + - '@sentry/sveltekit' + - '@sentry/vue' + - '@sentry/wasm' + - Sentry Browser Loader + - Sentry Browser CDN bundle + validations: + required: true + - type: input + id: sdk-version + attributes: + label: SDK Version + description: What version of the SDK are you using? + placeholder: ex. 7.8.0 + validations: + required: true + - type: input + id: framework-version + attributes: + label: Framework Version + description: + If you're using one of our framework-specific SDKs (`@sentry/react`, for example), what version of the + _framework_ are you using? + placeholder: ex. React 17.0.0 + - type: input + id: link-to-sentry + attributes: + label: Link to Sentry event + description: + If applicable, please provide a link to the affected event from your Sentry account. The event will only be + viewable by Sentry staff. + placeholder: https://sentry.io/organizations//issues//events//?project= + - type: textarea + id: sdk-setup + attributes: + label: SDK Setup + description: How do you set up your Sentry SDK? Please show us your `Sentry.init` options. + placeholder: |- + ```javascript + Sentry.init({ + dsn: __YOUR_DSN__ + ... + }); + ``` + validations: + required: false + - type: textarea + id: repro + attributes: + label: Steps to Reproduce + description: How can we see what you're seeing? Specific is terrific. + placeholder: |- + 1. What + 2. you + 3. did. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Result + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual Result + description: Logs? Screenshots? Yes, please. + validations: + required: true + - type: markdown + attributes: + value: |- + ## Thanks 🙏 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index d8dcf0e940e7..000000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: "\U0001F41B Bug Report" -about: Report a reproducible bug or regression in Sentry JavaScript SDKs. -title: '' -labels: 'Status: Needs Triage' -assignees: '' - ---- - - - -- [ ] Review the documentation: https://docs.sentry.io/ -- [ ] Search for existing issues: https://github.com/getsentry/sentry-javascript/issues -- [ ] Use the latest release: https://github.com/getsentry/sentry-javascript/releases -- [ ] Provide a link to the affected event from your Sentry account - -## Package + Version - -- [ ] `@sentry/browser` -- [ ] `@sentry/node` -- [ ] `raven-js` -- [ ] `raven-node` _(raven for node)_ -- [ ] other: - -### Version: - -``` -0.0.0 -``` - -## Description - -Describe your issue in detail, ideally, you have a reproducible demo that you can show. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..8021a793fd49 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://github.com/getsentry/sentry-javascript/discussions + about: Ask questions and discuss with other community members diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 000000000000..7749deee1d44 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,30 @@ +name: 💡 Feature Request +description: Create a feature request for a sentry-javascript SDK. +labels: ['Type: Improvement'] +body: + - type: markdown + attributes: + value: Thanks for taking the time to file a feature request! Please fill out this form as completely as possible. + - type: textarea + id: problem + attributes: + label: Problem Statement + description: A clear and concise description of what you want and what your use case is. + placeholder: |- + I want to make whirled peas, but Sentry doesn't blend. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Solution Brainstorm + description: We know you have bright ideas to share ... share away, friend. + placeholder: |- + Add a blender to Sentry. + validations: + required: true + - type: markdown + attributes: + value: |- + ## Thanks 🙏 + Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. diff --git a/.github/ISSUE_TEMPLATE/flaky.yml b/.github/ISSUE_TEMPLATE/flaky.yml new file mode 100644 index 000000000000..9cc9046a6ece --- /dev/null +++ b/.github/ISSUE_TEMPLATE/flaky.yml @@ -0,0 +1,45 @@ +name: ❅ Flaky Test +description: Report a flaky test in CI +title: '[Flaky CI]: ' +labels: ['Type: Tests'] +body: + - type: dropdown + id: type + attributes: + label: Flakiness Type + description: What are you observing + options: + - Timeout + - Assertion failure + - Other / Unknown + validations: + required: true + - type: input + id: job-name + attributes: + label: Name of Job + placeholder: Build & Test / Nextjs (Node 10) Tests + description: name of job as reported in the status report + validations: + required: true + - type: input + id: test-name + attributes: + label: Name of Test + placeholder: suites/replay/captureReplay/test.ts + description: file name or function name of failing test + validations: + required: false + - type: input + id: test-run-link + attributes: + label: Link to Test Run + placeholder: https://github.com/getsentry/sentry/runs/5582673807 + description: paste the URL to a test run showing the issue + validations: + required: true + - type: textarea + id: details + attributes: + label: Details + description: If you know anything else, please add it here diff --git a/.github/actions/restore-cache/action.yml b/.github/actions/restore-cache/action.yml new file mode 100644 index 000000000000..848983376840 --- /dev/null +++ b/.github/actions/restore-cache/action.yml @@ -0,0 +1,25 @@ +name: "Restore dependency & build cache" +description: "Restore the dependency & build cache." + +runs: + using: "composite" + steps: + - name: Check dependency cache + id: dep-cache + uses: actions/cache/restore@v4 + with: + path: ${{ env.CACHED_DEPENDENCY_PATHS }} + key: ${{ env.DEPENDENCY_CACHE_KEY }} + + - name: Check build cache + uses: actions/cache/restore@v4 + id: build-cache + with: + path: ${{ env.CACHED_BUILD_PATHS }} + key: ${{ env.BUILD_CACHE_KEY }} + + - name: Check if caches are restored + uses: actions/github-script@v6 + if: steps.dep-cache.outputs.cache-hit != 'true' || steps.build-cache.outputs.cache-hit != 'true' + with: + script: core.setFailed('Dependency or build cache could not be restored - please re-run ALL jobs.') diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 000000000000..d3ccaa70d705 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,4 @@ +paths-ignore: + # Our tsconfig files contain comments, which CodeQL complains about + - '**/tsconfig.json' + - '**/tsconfig.*.json' diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..6a938d15facc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'monthly' + commit-message: + prefix: ci + prefix-development: ci + include: scope + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'weekly' + allow: + - dependency-name: "@sentry/cli" + - dependency-name: "@sentry/vite-plugin" + versioning-strategy: increase + commit-message: + prefix: feat + prefix-development: feat + include: scope diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 000000000000..9526e93d55c6 --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,39 @@ +name: Gitflow - Auto prepare release +on: + pull_request: + types: + - closed + branches: + - master + +# This workflow tirggers a release when merging a branch with the pattern `prepare-release/VERSION` into master. +jobs: + release: + runs-on: ubuntu-20.04 + name: 'Prepare a new version' + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_RELEASE_PAT }} + fetch-depth: 0 + + # https://github.com/actions-ecosystem/action-regex-match + - uses: actions-ecosystem/action-regex-match@v2 + id: version + with: + # Parse version from head branch + text: ${{ github.head_ref }} + # match: preprare-release/xx.xx.xx + regex: '^prepare-release\/(\d+\.\d+\.\d+)$' + + - name: Prepare release + uses: getsentry/action-prepare-release@v1 + if: github.event.pull_request.merged == true && steps.version.outputs.match != '' + env: + GITHUB_TOKEN: ${{ secrets.GH_RELEASE_PAT }} + with: + version: ${{ steps.version.outputs.group1 }} + force: false + merge_target: master + craft_config_from_merge_target: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000000..3622e2e781e9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,1572 @@ +name: 'Build & Test' +on: + push: + branches: + - develop + - v7 + - master + - release/** + pull_request: + workflow_dispatch: + inputs: + commit: + description: If the commit you want to test isn't the head of a branch, provide its SHA here + required: false + schedule: + # Run every day at midnight (without cache) + - cron: '0 0 * * *' + +# Cancel in progress workflows on pull_requests. +# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + HEAD_COMMIT: ${{ github.event.inputs.commit || github.sha }} + + # WARNING: this disables cross os caching as ~ and + # github.workspace evaluate to differents paths + CACHED_DEPENDENCY_PATHS: | + ${{ github.workspace }}/node_modules + ${{ github.workspace }}/packages/*/node_modules + ${{ github.workspace }}/dev-packages/*/node_modules + ~/.cache/ms-playwright/ + ~/.cache/mongodb-binaries/ + + # DEPENDENCY_CACHE_KEY: can't be set here because we don't have access to yarn.lock + + # WARNING: this disables cross os caching as ~ and + # github.workspace evaluate to differents paths + # packages/utils/cjs and packages/utils/esm: Symlinks to the folders inside of `build`, needed for tests + CACHED_BUILD_PATHS: | + ${{ github.workspace }}/dev-packages/*/build + ${{ github.workspace }}/packages/*/build + ${{ github.workspace }}/packages/ember/*.d.ts + ${{ github.workspace }}/packages/gatsby/*.d.ts + ${{ github.workspace }}/packages/core/src/version.ts + ${{ github.workspace }}/packages/serverless + ${{ github.workspace }}/packages/utils/cjs + ${{ github.workspace }}/packages/utils/esm + + BUILD_CACHE_KEY: ${{ github.event.inputs.commit || github.sha }} + BUILD_PROFILING_NODE_CACHE_TARBALL_KEY: profiling-node-tarball-${{ github.event.inputs.commit || github.sha }} + + # GH will use the first restore-key it finds that matches + # So it will start by looking for one from the same branch, else take the newest one it can find elsewhere + # We want to prefer the cache from the current develop branch, if we don't find any on the current branch + NX_CACHE_RESTORE_KEYS: | + nx-Linux-${{ github.ref }}-${{ github.event.inputs.commit || github.sha }} + nx-Linux-${{ github.ref }} + nx-Linux + +jobs: + job_get_metadata: + name: Get Metadata + runs-on: ubuntu-20.04 + permissions: + pull-requests: read + steps: + - name: Check out current commit + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + # We need to check out not only the fake merge commit between the PR and the base branch which GH creates, but + # also its parents, so that we can pull the commit message from the head commit of the PR + fetch-depth: 2 + - name: Get metadata + id: get_metadata + # We need to try a number of different options for finding the head commit, because each kind of trigger event + # stores it in a different location + run: | + COMMIT_SHA=$(git rev-parse --short ${{ github.event.pull_request.head.sha || github.event.head_commit.id || env.HEAD_COMMIT }}) + echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_ENV + echo "COMMIT_MESSAGE=$(git log -n 1 --pretty=format:%s $COMMIT_SHA)" >> $GITHUB_ENV + + - name: Determine changed packages + uses: dorny/paths-filter@v3.0.0 + id: changed + with: + filters: | + workflow: &workflow + - '.github/**' + shared: &shared + - *workflow + - '*.{js,ts,json,yml,lock}' + - 'CHANGELOG.md' + - 'jest/**' + - 'scripts/**' + - 'packages/core/**' + - 'packages/rollup-utils/**' + - 'packages/tracing/**' + - 'packages/tracing-internal/**' + - 'packages/utils/**' + - 'packages/types/**' + - 'packages/integrations/**' + browser: &browser + - *shared + - 'packages/browser/**' + - 'packages/replay/**' + - 'packages/replay-canvas/**' + - 'packages/feedback/**' + - 'packages/wasm/**' + browser_integration: + - *shared + - *browser + - 'dev-packages/browser-integration-tests/**' + ember: + - *shared + - *browser + - 'packages/ember/**' + node: + - *shared + - 'packages/node/**' + - 'packages/node-experimental/**' + - 'dev-packages/node-integration-tests/**' + nextjs: + - *shared + - *browser + - 'packages/nextjs/**' + - 'packages/node/**' + - 'packages/react/**' + remix: + - *shared + - *browser + - 'packages/remix/**' + - 'packages/node/**' + - 'packages/react/**' + profiling_node: + - *shared + - 'packages/node/**' + - 'packages/node-experimental/**' + - 'packages/profiling-node/**' + - 'dev-packages/e2e-tests/test-applications/node-profiling/**' + profiling_node_bindings: + - *workflow + - 'packages/profiling-node/**' + - 'dev-packages/e2e-tests/test-applications/node-profiling/**' + deno: + - *shared + - *browser + - 'packages/deno/**' + any_code: + - '!**/*.md' + + - name: Get PR labels + id: pr-labels + uses: mydea/pr-labels-action@fn/bump-node20 + + outputs: + commit_label: '${{ env.COMMIT_SHA }}: ${{ env.COMMIT_MESSAGE }}' + changed_nextjs: ${{ steps.changed.outputs.nextjs }} + changed_ember: ${{ steps.changed.outputs.ember }} + changed_remix: ${{ steps.changed.outputs.remix }} + changed_node: ${{ steps.changed.outputs.node }} + changed_profiling_node: ${{ steps.changed.outputs.profiling_node }} + changed_profiling_node_bindings: ${{ steps.changed.outputs.profiling_node_bindings }} + changed_deno: ${{ steps.changed.outputs.deno }} + changed_browser: ${{ steps.changed.outputs.browser }} + changed_browser_integration: ${{ steps.changed.outputs.browser_integration }} + changed_any_code: ${{ steps.changed.outputs.any_code }} + # Note: These next three have to be checked as strings ('true'/'false')! + # is_develop for v7 now also means we're on the `v7` branch. + is_develop: ${{ github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/v7' }} + is_release: ${{ startsWith(github.ref, 'refs/heads/release/') }} + # When merging into master, or from master + is_gitflow_sync: ${{ github.head_ref == 'master' || github.ref == 'refs/heads/master' }} + has_gitflow_label: + ${{ github.event_name == 'pull_request' && contains(steps.pr-labels.outputs.labels, ' Gitflow ') }} + force_skip_cache: + ${{ github.event_name == 'schedule' || (github.event_name == 'pull_request' && + contains(steps.pr-labels.outputs.labels, ' ci-skip-cache ')) }} + + job_install_deps: + name: Install Dependencies + needs: job_get_metadata + runs-on: ubuntu-20.04 + timeout-minutes: 15 + if: | + (needs.job_get_metadata.outputs.is_gitflow_sync == 'false' && needs.job_get_metadata.outputs.has_gitflow_label == 'false') + steps: + - name: 'Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})' + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + # we use a hash of yarn.lock as our cache key, because if it hasn't changed, our dependencies haven't changed, + # so no need to reinstall them + - name: Compute dependency cache key + id: compute_lockfile_hash + run: echo "hash=${{ hashFiles('yarn.lock', '**/package.json') }}" >> "$GITHUB_OUTPUT" + + - name: Check dependency cache + uses: actions/cache@v4 + id: cache_dependencies + with: + path: ${{ env.CACHED_DEPENDENCY_PATHS }} + key: ${{ steps.compute_lockfile_hash.outputs.hash }} + + - name: Install dependencies + if: steps.cache_dependencies.outputs.cache-hit != 'true' + run: yarn install --ignore-engines --frozen-lockfile + outputs: + dependency_cache_key: ${{ steps.compute_lockfile_hash.outputs.hash }} + + job_check_branches: + name: Check PR branches + needs: job_get_metadata + runs-on: ubuntu-20.04 + if: github.event_name == 'pull_request' + permissions: + pull-requests: write + steps: + - name: PR is opened against master + uses: mshick/add-pr-comment@dd126dd8c253650d181ad9538d8b4fa218fc31e8 + if: ${{ github.base_ref == 'master' && !startsWith(github.head_ref, 'prepare-release/') }} + with: + message: | + ⚠️ This PR is opened against **master**. You probably want to open it against **develop**. + + job_build: + name: Build + needs: [job_get_metadata, job_install_deps] + runs-on: ubuntu-20.04-large-js + timeout-minutes: 30 + if: | + (needs.job_get_metadata.outputs.changed_any_code == 'true' || github.event_name != 'pull_request') + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Check dependency cache + uses: actions/cache/restore@v4 + with: + path: ${{ env.CACHED_DEPENDENCY_PATHS }} + key: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + fail-on-cache-miss: true + + - name: Check build cache + uses: actions/cache@v4 + id: cache_built_packages + with: + path: ${{ env.CACHED_BUILD_PATHS }} + key: ${{ env.BUILD_CACHE_KEY }} + + - name: NX cache + uses: actions/cache@v4 + # Disable cache when: + # - on release branches + # - when PR has `ci-skip-cache` label or on nightly builds + if: | + needs.job_get_metadata.outputs.is_release == 'false' && + needs.job_get_metadata.outputs.force_skip_cache == 'false' + with: + path: .nxcache + key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT }} + # On develop branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it + restore-keys: + ${{needs.job_get_metadata.outputs.is_develop == 'false' && env.NX_CACHE_RESTORE_KEYS || 'nx-never-restore'}} + + - name: Build packages + # Under normal circumstances, using the git SHA as a cache key, there shouldn't ever be a cache hit on the built + # packages, and so `yarn build` should always run. This `if` check is therefore only there for testing CI issues + # where the built packages are beside the point. In that case, you can change `BUILD_CACHE_KEY` (at the top of + # this file) to a constant and skip rebuilding all of the packages each time CI runs. + if: steps.cache_built_packages.outputs.cache-hit == '' + run: yarn build + outputs: + # this needs to be passed on, because the `needs` context only looks at direct ancestors (so steps which depend on + # `job_build` can't see `job_install_deps` and what it returned) + dependency_cache_key: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + job_size_check: + name: Size Check + needs: [job_get_metadata, job_build] + timeout-minutes: 15 + runs-on: ubuntu-20.04 + if: + github.event_name == 'pull_request' || needs.job_get_metadata.outputs.is_develop == 'true' || + needs.job_get_metadata.outputs.is_release == 'true' + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + # The size limit action runs `yarn` and `yarn build` when this job is executed on + # use Node 14 for now. + node-version: '14' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Check bundle sizes + uses: getsentry/size-limit-action@runForBranch + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + skip_step: build + main_branch: v7 + # When on release branch, we want to always run + # Else, we fall back to the default handling of the action + run_for_branch: ${{ (needs.job_get_metadata.outputs.is_release == 'true' && 'true') || '' }} + + job_lint: + name: Lint + # Even though the linter only checks source code, not built code, it needs the built code in order check that all + # inter-package dependencies resolve cleanly. + needs: [job_get_metadata, job_build] + timeout-minutes: 10 + runs-on: ubuntu-20.04 + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Lint source files + run: yarn lint:lerna + - name: Lint C++ files + run: yarn lint:clang + - name: Validate ES5 builds + run: yarn validate:es5 + + job_check_format: + name: Check file formatting + needs: [job_get_metadata, job_install_deps] + timeout-minutes: 10 + runs-on: ubuntu-20.04 + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Check dependency cache + uses: actions/cache/restore@v4 + with: + path: ${{ env.CACHED_DEPENDENCY_PATHS }} + key: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + fail-on-cache-miss: true + - name: Check file formatting + run: yarn lint:prettier && yarn lint:biome + + job_circular_dep_check: + name: Circular Dependency Check + needs: [job_get_metadata, job_build] + timeout-minutes: 10 + runs-on: ubuntu-20.04 + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Run madge + run: yarn circularDepCheck + + job_artifacts: + name: Upload Artifacts + needs: [job_get_metadata, job_build, job_compile_bindings_profiling_node] + runs-on: ubuntu-20.04 + # Build artifacts are only needed for releasing workflow. + if: needs.job_get_metadata.outputs.is_release == 'true' + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Pack tarballs + # Profiling tarball is built separately as we assemble the precompiled binaries + run: yarn build:tarball --ignore @sentry/profiling-node + + - name: Restore profiling tarball + uses: actions/cache/restore@v4 + with: + key: ${{ env.BUILD_PROFILING_NODE_CACHE_TARBALL_KEY }} + path: ${{ github.workspace }}/packages/*/*.tgz + + - name: Archive artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.sha }} + path: | + ${{ github.workspace }}/packages/browser/build/bundles/** + ${{ github.workspace }}/packages/integrations/build/bundles/** + ${{ github.workspace }}/packages/replay/build/bundles/** + ${{ github.workspace }}/packages/replay-canvas/build/bundles/** + ${{ github.workspace }}/packages/**/*.tgz + ${{ github.workspace }}/packages/serverless/build/aws/dist-serverless/*.zip + + job_browser_unit_tests: + name: Browser Unit Tests + needs: [job_get_metadata, job_build] + timeout-minutes: 10 + runs-on: ubuntu-20.04 + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Run tests + env: + NODE_VERSION: 16 + run: yarn test-ci-browser + - name: Compute test coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + job_bun_unit_tests: + name: Bun Unit Tests + needs: [job_get_metadata, job_build] + timeout-minutes: 10 + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Set up Bun + uses: oven-sh/setup-bun@v1 + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Run tests + run: | + yarn test-ci-bun + - name: Compute test coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + job_deno_unit_tests: + name: Deno Unit Tests + needs: [job_get_metadata, job_build] + if: needs.job_get_metadata.outputs.changed_deno == 'true' || github.event_name != 'pull_request' + timeout-minutes: 10 + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Set up Deno + uses: denoland/setup-deno@v1.1.4 + with: + deno-version: v1.38.5 + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Run tests + run: | + cd packages/deno + yarn build + yarn test + - name: Compute test coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + job_node_unit_tests: + name: Node (${{ matrix.node }}) Unit Tests + if: needs.job_get_metadata.outputs.changed_node == 'true' || github.event_name != 'pull_request' + needs: [job_get_metadata, job_build] + timeout-minutes: 10 + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + node: [8, 10, 12, 14, 16, 18, 20, 22] + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Run tests + env: + NODE_VERSION: ${{ matrix.node }} + run: | + [[ $NODE_VERSION == 8 ]] && yarn add --dev --ignore-engines --ignore-scripts --ignore-workspace-root-check ts-node@8.10.2 + yarn test-ci-node + - name: Compute test coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + job_profiling_node_unit_tests: + name: Node Profiling Unit Tests + needs: [job_get_metadata, job_build] + if: needs.job_get_metadata.outputs.changed_node == 'true' || needs.job_get_metadata.outputs.changed_profiling_node == 'true' || github.event_name != 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out current commit + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - uses: actions/setup-node@v4 + with: + node-version: 20 + - uses: actions/setup-python@v5 + with: + python-version: '3.11.7' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Build Configure node-gyp + run: yarn lerna run build:bindings:configure --scope @sentry/profiling-node + - name: Build Bindings for Current Environment + run: yarn build --scope @sentry/profiling-node + - name: Unit Test + run: yarn lerna run test --scope @sentry/profiling-node + + job_nextjs_integration_test: + name: Nextjs (Node ${{ matrix.node }}) Tests + needs: [job_get_metadata, job_build] + if: needs.job_get_metadata.outputs.changed_nextjs == 'true' || github.event_name != 'pull_request' + timeout-minutes: 25 + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + node: [10, 12, 14, 16, 18, 20, 22] + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Get npm cache directory + id: npm-cache-dir + run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT + - name: Get Playwright version + id: playwright-version + run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT + - uses: actions/cache@v4 + name: Check if Playwright browser is cached + id: playwright-cache + with: + path: ${{ steps.npm-cache-dir.outputs.dir }} + key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}} + - name: Install Playwright browser if not cached + if: steps.playwright-cache.outputs.cache-hit != 'true' && matrix.node >= 14 + run: npx playwright install --with-deps + env: + PLAYWRIGHT_BROWSERS_PATH: ${{steps.npm-cache-dir.outputs.dir}} + - name: Install OS dependencies of Playwright if cache hit + if: steps.playwright-cache.outputs.cache-hit == 'true' && matrix.node >= 14 + run: npx playwright install-deps + - name: Run tests + env: + NODE_VERSION: ${{ matrix.node }} + run: | + cd packages/nextjs + yarn test:integration + + job_browser_playwright_tests: + name: Playwright (${{ matrix.bundle }}${{ matrix.shard && format(' {0}/{1}', matrix.shard, matrix.shards) || ''}}) Tests + needs: [job_get_metadata, job_build] + if: needs.job_get_metadata.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request' + runs-on: ubuntu-20.04-large-js + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + bundle: + - esm + - bundle_es5 + - bundle_es5_min + - bundle_es6 + - bundle_es6_min + - bundle_replay_es6 + - bundle_replay_es6_min + - bundle_tracing_es5 + - bundle_tracing_es5_min + - bundle_tracing_es6 + - bundle_tracing_es6_min + - bundle_tracing_replay_es6 + - bundle_tracing_replay_es6_min + project: + - chromium + include: + # Only check all projects for esm & full bundle + # We also shard the tests as they take the longest + - bundle: bundle_tracing_replay_es6_min + project: '' + shard: 1 + shards: 2 + - bundle: bundle_tracing_replay_es6_min + project: '' + shard: 2 + shards: 2 + - bundle: esm + project: '' + shard: 1 + shards: 3 + - bundle: esm + shard: 2 + shards: 3 + - bundle: esm + project: '' + shard: 3 + shards: 3 + exclude: + # Do not run the default chromium-only tests + - bundle: bundle_tracing_replay_es6_min + project: 'chromium' + - bundle: esm + project: 'chromium' + + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Get npm cache directory + id: npm-cache-dir + run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT + - name: Get Playwright version + id: playwright-version + run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT + - uses: actions/cache@v4 + name: Check if Playwright browser is cached + id: playwright-cache + with: + path: ${{ steps.npm-cache-dir.outputs.dir }} + key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}} + - name: Install Playwright browser if not cached + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + env: + PLAYWRIGHT_BROWSERS_PATH: ${{steps.npm-cache-dir.outputs.dir}} + - name: Install OS dependencies of Playwright if cache hit + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx playwright install-deps + - name: Run Playwright tests + env: + PW_BUNDLE: ${{ matrix.bundle }} + working-directory: dev-packages/browser-integration-tests + run: yarn test:ci${{ matrix.project && format(' --project={0}', matrix.project) || '' }}${{ matrix.shard && format(' --shard={0}/{1}', matrix.shard, matrix.shards) || '' }} + + job_browser_loader_tests: + name: Playwright Loader (${{ matrix.bundle }}) Tests + needs: [job_get_metadata, job_build] + if: needs.job_get_metadata.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request' + runs-on: ubuntu-20.04 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + bundle: + - loader_base + - loader_eager + - loader_debug + - loader_tracing + - loader_replay + - loader_tracing_replay + + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Get npm cache directory + id: npm-cache-dir + run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT + - name: Get Playwright version + id: playwright-version + run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT + - uses: actions/cache@v4 + name: Check if Playwright browser is cached + id: playwright-cache + with: + path: ${{ steps.npm-cache-dir.outputs.dir }} + key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}} + - name: Install Playwright browser if not cached + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + env: + PLAYWRIGHT_BROWSERS_PATH: ${{steps.npm-cache-dir.outputs.dir}} + - name: Install OS dependencies of Playwright if cache hit + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx playwright install-deps + - name: Run Playwright Loader tests + env: + PW_BUNDLE: ${{ matrix.bundle }} + run: | + cd dev-packages/browser-integration-tests + yarn test:loader + + job_browser_integration_tests: + name: Browser (${{ matrix.browser }}) Tests + needs: [job_get_metadata, job_build] + if: needs.job_get_metadata.outputs.changed_browser == 'true' || github.event_name != 'pull_request' + runs-on: ubuntu-20.04-large-js + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + browser: + - ChromeHeadless + - FirefoxHeadless + - WebkitHeadless + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Run integration tests + env: + KARMA_BROWSER: ${{ matrix.browser }} + run: | + cd packages/browser + [[ $KARMA_BROWSER == WebkitHeadless ]] && yarn run playwright install-deps webkit + yarn test:integration + + job_browser_build_tests: + name: Browser Build Tests + needs: [job_get_metadata, job_build] + runs-on: ubuntu-20.04 + timeout-minutes: 5 + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Run browser build tests + run: | + cd packages/browser + yarn test:package + - name: Run utils build tests + run: | + cd packages/utils + yarn test:package + + job_check_for_faulty_dts: + name: Check for faulty .d.ts files + needs: [job_get_metadata, job_build] + runs-on: ubuntu-20.04 + timeout-minutes: 5 + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Check for dts files that reference stuff in the temporary build folder + run: | + if grep -r --include "*.d.ts" --exclude-dir ".nxcache" 'import("@sentry(-internal)?/[^/]*/build' .; then + echo "Found illegal TypeScript import statement." + exit 1 + fi + + + job_node_integration_tests: + name: + Node (${{ matrix.node }})${{ (matrix.typescript && format(' (TS {0})', matrix.typescript)) || '' }} Integration + Tests + needs: [job_get_metadata, job_build] + if: needs.job_get_metadata.outputs.changed_node == 'true' || github.event_name != 'pull_request' + runs-on: ubuntu-20.04 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + node: [10, 12, 14, 16, 18, 20, 22] + typescript: + - false + include: + # Only check typescript for latest version (to streamline CI) + - node: 22 + typescript: '3.8' + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + + - name: Overwrite typescript version + if: matrix.typescript + run: yarn add --dev --ignore-workspace-root-check typescript@${{ matrix.typescript }} + + - name: Run integration tests + env: + NODE_VERSION: ${{ matrix.node }} + run: | + cd dev-packages/node-integration-tests + yarn test + + job_remix_integration_tests: + name: Remix v${{ matrix.remix }} (Node ${{ matrix.node }}) ${{ matrix.tracingIntegration && 'TracingIntegration'}} Tests + needs: [job_get_metadata, job_build] + if: needs.job_get_metadata.outputs.changed_remix == 'true' || github.event_name != 'pull_request' + runs-on: ubuntu-20.04 + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + node: [18, 20, 22] + remix: [1, 2] + # Remix v2 only supports Node 18+, so run Node 14, 16 tests separately + include: + - node: 14 + remix: 1 + - node: 16 + remix: 1 + - tracingIntegration: true + remix: 2 + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Run integration tests + env: + NODE_VERSION: ${{ matrix.node }} + REMIX_VERSION: ${{ matrix.remix }} + TRACING_INTEGRATION: ${{ matrix.tracingIntegration }} + run: | + cd packages/remix + yarn test:integration:ci + + job_e2e_prepare: + name: Prepare E2E tests + # We want to run this if: + # - The build job was successful, not skipped + # - AND if the profiling node bindings were either successful or skipped + # AND if this is not a PR from a fork or dependabot + if: | + always() && needs.job_build.result == 'success' && + (needs.job_compile_bindings_profiling_node.result == 'success' || needs.job_compile_bindings_profiling_node.result == 'skipped') && + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && + github.actor != 'dependabot[bot]' + needs: [job_get_metadata, job_build, job_compile_bindings_profiling_node] + runs-on: ubuntu-20.04-large-js + timeout-minutes: 15 + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: NX cache + uses: actions/cache/restore@v4 + with: + path: .nxcache + key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT }} + # On develop branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it + restore-keys: ${{ env.NX_CACHE_RESTORE_KEYS }} + - name: Build tarballs + run: yarn build:tarball --ignore @sentry/profiling-node + + # Rebuild profiling by compiling TS and pull the precompiled binary artifacts + - name: Build Profiling Node + if: | + (needs.job_get_metadata.outputs.changed_profiling_node_bindings == 'true') || + (needs.job_get_metadata.outputs.is_release == 'true') || + (github.event_name != 'pull_request') + run: yarn lerna run build:lib --scope @sentry/profiling-node + + - name: Extract Profiling Node Prebuilt Binaries + # @TODO: v4 breaks convenient merging of same name artifacts + # https://github.com/actions/upload-artifact/issues/478 + if: | + (needs.job_get_metadata.outputs.changed_profiling_node_bindings == 'true') || + (github.event_name != 'pull_request') + uses: actions/download-artifact@v3 + with: + name: profiling-node-binaries-${{ github.sha }} + path: ${{ github.workspace }}/packages/profiling-node/lib/ + + - name: Build Profiling tarball + run: yarn build:tarball --scope @sentry/profiling-node + # End rebuild profiling + + - name: Stores tarballs in cache + uses: actions/cache/save@v4 + with: + path: ${{ github.workspace }}/packages/*/*.tgz + key: ${{ env.BUILD_PROFILING_NODE_CACHE_TARBALL_KEY }} + + job_e2e_tests: + name: E2E ${{ matrix.label || matrix.test-application }} Test + # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks + # Dependabot PRs sadly also don't have access to secrets, so we skip them as well + # We need to add the `always()` check here because the previous step has this as well :( + # See: https://github.com/actions/runner/issues/2205 + if: + always() && needs.job_e2e_prepare.result == 'success' && + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && + github.actor != 'dependabot[bot]' + needs: [job_get_metadata, job_build, job_e2e_prepare] + runs-on: ubuntu-20.04 + timeout-minutes: 10 + env: + E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} + E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + # Needed because some apps expect a certain prefix + NEXT_PUBLIC_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + PUBLIC_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + REACT_APP_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks' + E2E_TEST_SENTRY_TEST_PROJECT: 'sentry-javascript-e2e-tests' + strategy: + fail-fast: false + matrix: + test-application: + [ + 'angular-17', + 'cloudflare-astro', + 'node-express-app', + 'create-react-app', + 'create-next-app', + 'create-remix-app', + 'create-remix-app-v2', + 'create-remix-app-express-vite-dev', + 'debug-id-sourcemaps', + 'nextjs-app-dir', + 'nextjs-14', + 'react-create-hash-router', + 'react-router-6-use-routes', + 'standard-frontend-react', + 'standard-frontend-react-tracing-import', + 'sveltekit', + 'sveltekit-2', + 'generic-ts3.8', + 'node-experimental-fastify-app', + 'node-hapi-app', + 'node-exports-test-app', + 'vue-3' + ] + build-command: + - false + label: + - false + # Add any variations of a test app here + # You should provide an alternate build-command as well as a matching label + include: + - test-application: 'create-react-app' + build-command: 'test:build-ts3.8' + label: 'create-react-app (TS 3.8)' + - test-application: 'standard-frontend-react' + build-command: 'test:build-ts3.8' + label: 'standard-frontend-react (TS 3.8)' + - test-application: 'create-next-app' + build-command: 'test:build-13' + label: 'create-next-app (next@13)' + - test-application: 'nextjs-app-dir' + build-command: 'test:build-13' + label: 'nextjs-app-dir (next@13)' + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - uses: pnpm/action-setup@v2 + with: + version: 8.3.1 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'dev-packages/e2e-tests/package.json' + - name: Set up Bun + if: matrix.test-application == 'node-exports-test-app' + uses: oven-sh/setup-bun@v1 + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + + - name: Restore tarball cache + uses: actions/cache/restore@v4 + with: + path: ${{ github.workspace }}/packages/*/*.tgz + key: ${{ env.BUILD_PROFILING_NODE_CACHE_TARBALL_KEY }} + + - name: Get node version + id: versions + run: | + echo "echo node=$(jq -r '.volta.node' dev-packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT + + - name: Validate Verdaccio + run: yarn test:validate + working-directory: dev-packages/e2e-tests + + - name: Prepare Verdaccio + run: yarn test:prepare + working-directory: dev-packages/e2e-tests + env: + E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }} + + - name: Build E2E app + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn ${{ matrix.build-command || 'test:build' }} + + - name: Run E2E test + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn test:assert + + - name: Deploy Astro to Cloudflare + uses: cloudflare/pages-action@v1 + if: matrix.test-application == 'cloudflare-astro' + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }} + directory: dist + workingDirectory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + + job_profiling_e2e_tests: + name: E2E ${{ matrix.label || matrix.test-application }} Test + # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks + # Dependabot PRs sadly also don't have access to secrets, so we skip them as well + # We need to add the `always()` check here because the previous step has this as well :( + # See: https://github.com/actions/runner/issues/2205 + if: + # Only run profiling e2e tests if profiling node bindings have changed + always() && needs.job_e2e_prepare.result == 'success' && + (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && + github.actor != 'dependabot[bot]' && ( + (needs.job_get_metadata.outputs.changed_profiling_node_bindings == 'true') || + (needs.job_get_metadata.outputs.is_release == 'true') || + (github.event_name != 'pull_request') + ) + needs: [job_get_metadata, job_build, job_e2e_prepare] + runs-on: ubuntu-20.04 + timeout-minutes: 10 + env: + E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} + E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks' + E2E_TEST_SENTRY_TEST_PROJECT: 'sentry-javascript-e2e-tests' + strategy: + fail-fast: false + matrix: + test-application: ['node-profiling'] + build-command: + - false + label: + - false + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - uses: pnpm/action-setup@v2 + with: + version: 8.3.1 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'dev-packages/e2e-tests/package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + - name: Build Profiling Node + run: yarn lerna run build:lib --scope @sentry/profiling-node + - name: Extract Profiling Node Prebuilt Binaries + uses: actions/download-artifact@v3 + with: + name: profiling-node-binaries-${{ github.sha }} + path: ${{ github.workspace }}/packages/profiling-node/lib/ + - name: Build Profiling tarball + run: yarn build:tarball --scope @sentry/profiling-node + - name: Restore tarball cache + uses: actions/cache/restore@v4 + with: + path: ${{ github.workspace }}/packages/*/*.tgz + key: ${{ env.BUILD_PROFILING_NODE_CACHE_TARBALL_KEY }} + + - name: Get node version + id: versions + run: | + echo "echo node=$(jq -r '.volta.node' dev-packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT + + - name: Validate Verdaccio + run: yarn test:validate + working-directory: dev-packages/e2e-tests + + - name: Prepare Verdaccio + run: yarn test:prepare + working-directory: dev-packages/e2e-tests + env: + E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }} + + - name: Build E2E app + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn ${{ matrix.build-command || 'test:build' }} + + - name: Run E2E test + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn test:assert + + job_required_jobs_passed: + name: All required jobs passed or were skipped + needs: + [ + job_build, + job_compile_bindings_profiling_node, + job_browser_build_tests, + job_browser_unit_tests, + job_bun_unit_tests, + job_deno_unit_tests, + job_node_unit_tests, + job_profiling_node_unit_tests, + job_nextjs_integration_test, + job_node_integration_tests, + job_browser_playwright_tests, + job_browser_integration_tests, + job_browser_loader_tests, + job_remix_integration_tests, + job_e2e_tests, + job_profiling_e2e_tests, + job_artifacts, + job_lint, + job_check_format, + job_circular_dep_check, + ] + # Always run this, even if a dependent job failed + if: always() + runs-on: ubuntu-20.04 + steps: + - name: Check for failures + if: contains(needs.*.result, 'failure') + run: | + echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1 + + overhead_metrics: + name: Overhead metrics + needs: [job_get_metadata, job_build] + runs-on: ubuntu-20.04 + timeout-minutes: 30 + if: | + contains(github.event.pull_request.labels.*.name, 'ci-overhead-measurements') + steps: + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} + + - name: Collect + run: yarn ci:collect + working-directory: dev-packages/overhead-metrics + + - name: Process + id: process + run: yarn ci:process + working-directory: dev-packages/overhead-metrics + # Don't run on forks - the PR comment cannot be added. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: Upload results + uses: actions/upload-artifact@v4 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + with: + name: ${{ steps.process.outputs.artifactName }} + path: ${{ steps.process.outputs.artifactPath }} + + job_compile_bindings_profiling_node: + name: Compile & Test Profiling Bindings (v${{ matrix.node }}) ${{ matrix.target_platform || matrix.os }}, ${{ matrix.node || matrix.container }}, ${{ matrix.arch || matrix.container }}, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }} + needs: [job_get_metadata, job_install_deps, job_build] + # Compiling bindings can be very slow (especially on windows), so only run precompile + # Skip precompile unless we are on a release branch as precompile slows down CI times. + if: | + (needs.job_get_metadata.outputs.changed_profiling_node_bindings == 'true') || + (needs.job_get_metadata.outputs.is_release == 'true') || + (github.event_name != 'pull_request') + runs-on: ${{ matrix.os }} + container: ${{ matrix.container }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + # x64 glibc + - os: ubuntu-20.04 + node: 16 + - os: ubuntu-20.04 + node: 18 + - os: ubuntu-20.04 + node: 20 + - os: ubuntu-20.04 + node: 22 + + # x64 musl + - os: ubuntu-20.04 + container: node:16-alpine3.16 + node: 16 + - os: ubuntu-20.04 + container: node:18-alpine3.17 + node: 18 + - os: ubuntu-20.04 + container: node:20-alpine3.17 + node: 20 + - os: ubuntu-20.04 + container: node:22-alpine3.18 + node: 22 + + # arm64 glibc + - os: ubuntu-20.04 + arch: arm64 + node: 16 + - os: ubuntu-20.04 + arch: arm64 + node: 18 + - os: ubuntu-20.04 + arch: arm64 + node: 20 + - os: ubuntu-20.04 + arch: arm64 + node: 22 + + # arm64 musl + - os: ubuntu-20.04 + container: node:16-alpine3.16 + arch: arm64 + node: 16 + - os: ubuntu-20.04 + arch: arm64 + container: node:18-alpine3.17 + node: 18 + - os: ubuntu-20.04 + arch: arm64 + container: node:20-alpine3.17 + node: 20 + - os: ubuntu-20.04 + arch: arm64 + container: node:22-alpine3.18 + node: 22 + + # macos x64 + - os: macos-12 + node: 16 + arch: x64 + - os: macos-12 + node: 18 + arch: x64 + - os: macos-12 + node: 20 + arch: x64 + - os: macos-12 + node: 22 + arch: x64 + + # macos arm64 + - os: macos-12 + arch: arm64 + node: 16 + target_platform: darwin + - os: macos-12 + arch: arm64 + node: 18 + target_platform: darwin + - os: macos-12 + arch: arm64 + node: 20 + target_platform: darwin + - os: macos-12 + arch: arm64 + node: 22 + target_platform: darwin + + # windows x64 + - os: windows-2022 + node: 16 + arch: x64 + - os: windows-2022 + node: 18 + arch: x64 + - os: windows-2022 + node: 20 + arch: x64 + - os: windows-2022 + node: 22 + arch: x64 + + steps: + - name: Setup (alpine) + if: contains(matrix.container, 'alpine') + run: | + apk add --no-cache build-base git g++ make curl python3 + ln -sf python3 /usr/bin/python + + - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + + - name: Restore dependency cache + uses: actions/cache/restore@v4 + id: restore-dependencies + with: + path: ${{ env.CACHED_DEPENDENCY_PATHS }} + key: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + enableCrossOsArchive: true + + - name: Restore build cache + uses: actions/cache/restore@v4 + id: restore-build + with: + path: ${{ env.CACHED_BUILD_PATHS }} + key: ${{ needs.job_build.outputs.dependency_cache_key }} + enableCrossOsArchive: true + + - name: Configure safe directory + run: | + git config --global --add safe.directory "*" + + - name: Install yarn + run: npm i -g yarn@1.22.19 --force + + - name: Increase yarn network timeout on Windows + if: contains(matrix.os, 'windows') + run: yarn config set network-timeout 600000 -g + + - name: Setup python + uses: actions/setup-python@v5 + if: ${{ !contains(matrix.container, 'alpine') }} + id: python-setup + with: + python-version: '3.8.10' + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + + - name: Install Dependencies + if: steps.restore-dependencies.outputs.cache-hit != 'true' + run: yarn install --frozen-lockfile --ignore-engines --ignore-scripts + + - name: Setup (arm64| ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) + if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' + run: | + sudo apt-get update + sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + + - name: Setup Musl + if: contains(matrix.container, 'alpine') + run: | + cd packages/profiling-node + curl -OL https://musl.cc/aarch64-linux-musl-cross.tgz + tar -xzvf aarch64-linux-musl-cross.tgz + $(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc --version + + # configure node-gyp + - name: Configure node-gyp + if: matrix.arch != 'arm64' + run: | + cd packages/profiling-node + yarn build:bindings:configure + + - name: Configure node-gyp (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) + if: matrix.arch == 'arm64' && matrix.target_platform != 'darwin' + run: | + cd packages/profiling-node + yarn build:bindings:configure:arm64 + + - name: Configure node-gyp (arm64, darwin) + if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin' + run: | + cd packages/profiling-node + yarn build:bindings:configure:arm64 + + # build bindings + - name: Build Bindings + if: matrix.arch != 'arm64' + run: | + yarn lerna run build:bindings --scope @sentry/profiling-node + + - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) + if: matrix.arch == 'arm64' && contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' + run: | + cd packages/profiling-node + CC=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc \ + CXX=$(pwd)/aarch64-linux-musl-cross/bin/aarch64-linux-musl-g++ \ + BUILD_ARCH=arm64 \ + yarn build:bindings + + - name: Build Bindings (arm64, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }}) + if: matrix.arch == 'arm64' && !contains(matrix.container, 'alpine') && matrix.target_platform != 'darwin' + run: | + cd packages/profiling-node + CC=aarch64-linux-gnu-gcc \ + CXX=aarch64-linux-gnu-g++ \ + BUILD_ARCH=arm64 \ + yarn build:bindings:arm64 + + - name: Build Bindings (arm64, darwin) + if: matrix.arch == 'arm64' && matrix.target_platform == 'darwin' + run: | + cd packages/profiling-node + BUILD_PLATFORM=darwin \ + BUILD_ARCH=arm64 \ + yarn build:bindings:arm64 + + - name: Build Monorepo + if: steps.restore-build.outputs.cache-hit != 'true' + run: yarn build --scope @sentry/profiling-node + + - name: Test Bindings + if: matrix.arch != 'arm64' + run: | + yarn lerna run test --scope @sentry/profiling-node + + - name: Archive Binary + # @TODO: v4 breaks convenient merging of same name artifacts + # https://github.com/actions/upload-artifact/issues/478 + uses: actions/upload-artifact@v3 + with: + name: profiling-node-binaries-${{ github.sha }} + path: | + ${{ github.workspace }}/packages/profiling-node/lib/*.node diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml new file mode 100644 index 000000000000..9a856a7ce034 --- /dev/null +++ b/.github/workflows/canary.yml @@ -0,0 +1,186 @@ +name: 'Canary Tests' +on: + schedule: + # Run every day at midnight + - cron: '0 0 * * *' + workflow_dispatch: + inputs: + commit: + description: If the commit you want to test isn't the head of a branch, provide its SHA here + required: false + +env: + HEAD_COMMIT: ${{ github.event.inputs.commit || github.sha }} + +permissions: + contents: read + issues: write + +jobs: + job_e2e_prepare: + name: Prepare E2E Canary tests + runs-on: ubuntu-20.04 + timeout-minutes: 30 + steps: + - name: Check out current commit + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Check canary cache + uses: actions/cache@v4 + with: + path: | + ${{ github.workspace }}/packages/*/*.tgz + ${{ github.workspace }}/node_modules + ${{ github.workspace }}/packages/*/node_modules + key: canary-${{ env.HEAD_COMMIT }} + - name: Install dependencies + run: yarn install + - name: Build packages + run: yarn build + + - name: Build tarballs + run: yarn build:tarball + + job_e2e_tests: + name: E2E ${{ matrix.label }} Test + needs: [job_e2e_prepare] + runs-on: ubuntu-20.04 + timeout-minutes: 15 + env: + E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} + E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + # Needed because certain apps expect a certain prefix + NEXT_PUBLIC_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + PUBLIC_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + REACT_APP_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} + E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks' + E2E_TEST_SENTRY_TEST_PROJECT: 'sentry-javascript-e2e-tests' + strategy: + fail-fast: false + matrix: + include: + - test-application: 'create-react-app' + build-command: 'test:build-canary' + label: 'create-react-app (canary)' + - test-application: 'nextjs-app-dir' + build-command: 'test:build-canary' + label: 'nextjs-app-dir (canary)' + - test-application: 'nextjs-app-dir' + build-command: 'test:build-latest' + label: 'nextjs-app-dir (latest)' + - test-application: 'nextjs-14' + build-command: 'test:build-canary' + label: 'nextjs-14 (canary)' + - test-application: 'nextjs-14' + build-command: 'test:build-latest' + label: 'nextjs-14 (latest)' + - test-application: 'react-create-hash-router' + build-command: 'test:build-canary' + label: 'react-create-hash-router (canary)' + - test-application: 'standard-frontend-react' + build-command: 'test:build-canary' + label: 'standard-frontend-react (canary)' + + steps: + - name: Check out current commit + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - uses: pnpm/action-setup@v2 + with: + version: 8.3.1 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + + - name: Restore canary cache + uses: actions/cache/restore@v4 + with: + path: | + ${{ github.workspace }}/packages/*/*.tgz + ${{ github.workspace }}/node_modules + ${{ github.workspace }}/packages/*/node_modules + key: canary-${{ env.HEAD_COMMIT }} + + - name: Get node version + id: versions + run: | + echo "echo node=$(jq -r '.volta.node' package.json)" >> $GITHUB_OUTPUT + + - name: Validate Verdaccio + run: yarn test:validate + working-directory: dev-packages/e2e-tests + + - name: Prepare Verdaccio + run: yarn test:prepare + working-directory: dev-packages/e2e-tests + env: + E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }} + + - name: Build E2E app + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn ${{ matrix.build-command }} + + - name: Run E2E test + working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} + timeout-minutes: 5 + run: yarn test:assert + + - name: Create Issue + if: failure() && github.event_name == 'schedule' + uses: JasonEtco/create-an-issue@e27dddc79c92bc6e4562f268fffa5ed752639abd + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUN_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + TITLE: ${{ matrix.label }} Test Failed + with: + filename: .github/CANARY_FAILURE_TEMPLATE.md + update_existing: true + + job_ember_canary_test: + name: Ember Canary Tests + runs-on: ubuntu-20.04 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + scenario: [ember-release, embroider-optimized, ember-4.0] + steps: + - name: 'Check out current commit' + uses: actions/checkout@v4 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + - name: Install dependencies + run: yarn install --ignore-engines --frozen-lockfile + + - name: Build dependencies + run: | + yarn lerna run build:types --scope=@sentry/ember --include-dependencies + yarn lerna run build:transpile --scope=@sentry/ember --include-dependencies + + - name: Run Ember tests + run: | + cd packages/ember + yarn ember try:one ${{ matrix.scenario }} --skip-cleanup=true + + - name: Create Issue + if: failure() && github.event_name == 'schedule' + uses: JasonEtco/create-an-issue@e27dddc79c92bc6e4562f268fffa5ed752639abd + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUN_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + TITLE: Ember Canary ${{ matrix.scenario }} Test Failed + with: + filename: .github/CANARY_FAILURE_TEMPLATE.md + update_existing: true diff --git a/.github/workflows/clear-cache.yml b/.github/workflows/clear-cache.yml new file mode 100644 index 000000000000..40f7141764c3 --- /dev/null +++ b/.github/workflows/clear-cache.yml @@ -0,0 +1,11 @@ +name: Clear all GHA caches +on: + workflow_dispatch: + +jobs: + clear-caches: + name: Delete all caches + runs-on: ubuntu-20.04 + steps: + - name: Clear caches + uses: easimon/wipe-cache@v2 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000000..a74adca29afd --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,78 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: 'CodeQL' + +on: + push: + branches: [develop] + pull_request: + # The branches below must be a subset of the branches above + branches: [develop] + paths-ignore: + # When _only_ changing .md files, no need to run CodeQL analysis + - '**/*.md' + schedule: + - cron: '40 3 * * 0' + +# Cancel in progress workflows on pull_requests. +# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: ['javascript'] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + config-file: ./.github/codeql/codeql-config.yml + queries: security-extended + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml new file mode 100644 index 000000000000..b6fcd418e01e --- /dev/null +++ b/.github/workflows/enforce-license-compliance.yml @@ -0,0 +1,16 @@ +name: Enforce License Compliance + +on: + push: + branches: [master, develop, release/*, v7] + pull_request: + branches: [master, develop, v7] + +jobs: + enforce-license-compliance: + runs-on: ubuntu-20.04 + steps: + - name: 'Enforce License Compliance' + uses: getsentry/action-enforce-license-compliance@main + with: + fossa_api_key: ${{ secrets.FOSSA_API_KEY }} diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml new file mode 100644 index 000000000000..d499c12d661b --- /dev/null +++ b/.github/workflows/flaky-test-detector.yml @@ -0,0 +1,86 @@ +name: 'Detect flaky tests' +on: + workflow_dispatch: + pull_request: + paths: + - 'dev-packages/browser-integration-tests/suites/**' + branches-ignore: + - master + +env: + HEAD_COMMIT: ${{ github.event.inputs.commit || github.sha }} + + NX_CACHE_RESTORE_KEYS: | + nx-Linux-${{ github.ref }}-${{ github.event.inputs.commit || github.sha }} + nx-Linux-${{ github.ref }} + nx-Linux + +# Cancel in progress workflows on pull_requests. +# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + flaky-detector: + runs-on: ubuntu-20.04-large-js + timeout-minutes: 60 + name: 'Check tests for flakiness' + # Also skip if PR is from master -> develop + if: ${{ github.base_ref != 'master' && github.ref != 'refs/heads/master' }} + steps: + - name: Check out current branch + uses: actions/checkout@v4 + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + cache: 'yarn' + - name: Install dependencies + run: yarn install --ignore-engines --frozen-lockfile + + - name: NX cache + uses: actions/cache/restore@v4 + with: + path: .nxcache + key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT }} + restore-keys: ${{ env.NX_CACHE_RESTORE_KEYS }} + + - name: Build packages + run: yarn build + + - name: Get npm cache directory + id: npm-cache-dir + run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT + - name: Get Playwright version + id: playwright-version + run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT + - uses: actions/cache@v4 + name: Check if Playwright browser is cached + id: playwright-cache + with: + path: ${{ steps.npm-cache-dir.outputs.dir }} + key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}} + - name: Install Playwright browser if not cached + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + env: + PLAYWRIGHT_BROWSERS_PATH: ${{steps.npm-cache-dir.outputs.dir}} + - name: Install OS dependencies of Playwright if cache hit + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx playwright install-deps + + - name: Determine changed tests + uses: dorny/paths-filter@v3.0.0 + id: changed + with: + list-files: json + filters: | + browser_integration: dev-packages/browser-integration-tests/suites/** + + - name: Detect flaky tests + run: yarn test:detect-flaky + working-directory: dev-packages/browser-integration-tests + env: + CHANGED_TEST_PATHS: ${{ steps.changed.outputs.browser_integration_files }} + TEST_RUN_COUNT: 'AUTO' diff --git a/.github/workflows/gitflow-sync-develop.yml b/.github/workflows/gitflow-sync-develop.yml new file mode 100644 index 000000000000..612d2802a580 --- /dev/null +++ b/.github/workflows/gitflow-sync-develop.yml @@ -0,0 +1,53 @@ +name: Gitflow - Sync master into develop +on: + push: + branches: + - master + paths: + # When the version is updated on master (but nothing else) + - 'lerna.json' + - '!**/*.js' + - '!**/*.ts' + workflow_dispatch: + +env: + SOURCE_BRANCH: master + TARGET_BRANCH: develop + +jobs: + main: + name: Create PR master->develop + runs-on: ubuntu-20.04 + permissions: + pull-requests: write + contents: write + steps: + - name: git checkout + uses: actions/checkout@v4 + + # https://github.com/marketplace/actions/github-pull-request-action + - name: Create Pull Request + id: open-pr + uses: repo-sync/pull-request@v2 + with: + source_branch: ${{ env.SOURCE_BRANCH }} + destination_branch: ${{ env.TARGET_BRANCH }} + pr_title: '[Gitflow] Merge ${{ env.SOURCE_BRANCH }} into ${{ env.TARGET_BRANCH }}' + pr_body: 'Merge ${{ env.SOURCE_BRANCH }} branch into ${{ env.TARGET_BRANCH }}' + pr_label: 'Dev: Gitflow' + # This token is scoped to Daniel Griesser + github_token: ${{ secrets.REPO_SCOPED_TOKEN }} + + - name: Enable automerge for PR + if: steps.open-pr.outputs.pr_number != '' + run: gh pr merge --merge --auto "${{ steps.open-pr.outputs.pr_number }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # https://github.com/marketplace/actions/auto-approve + - name: Auto approve PR + if: steps.open-pr.outputs.pr_number != '' + uses: hmarr/auto-approve-action@v4 + with: + pull-request-number: ${{ steps.open-pr.outputs.pr_number }} + review-message: 'Auto approved automated PR' diff --git a/.github/workflows/issue-package-label.yml b/.github/workflows/issue-package-label.yml new file mode 100644 index 000000000000..1c496c927762 --- /dev/null +++ b/.github/workflows/issue-package-label.yml @@ -0,0 +1,97 @@ +name: 'Tag issue with package label' + +on: + issues: + types: [opened] + +jobs: + add_labels: + name: Add package label + runs-on: ubuntu-latest + if: ${{ !github.event.issue.pull_request }} + steps: + - name: Get used package from issue body + # https://github.com/actions-ecosystem/action-regex-match + uses: actions-ecosystem/action-regex-match@v2 + id: packageName + with: + # Parse used package from issue body + text: ${{ github.event.issue.body }} + regex: '### Which SDK are you using\?\n\n(.*)\n\n' + + - name: Map package to issue label + # https://github.com/kanga333/variable-mapper + uses: kanga333/variable-mapper@v0.3.0 + id: packageLabel + if: steps.packageName.outputs.match != '' + with: + key: '${{ steps.packageName.outputs.group1 }}' + # Note: Since this is handled as a regex, and JSON parse wrangles slashes /, we just use `.` instead + map: | + { + "@sentry.astro": { + "label": "Package: Astro" + }, + "@sentry.browser": { + "label": "Package: Browser" + }, + "@sentry.angular": { + "label": "Package: Angular" + }, + "@sentry.angular-ivy": { + "label": "Package: Angular" + }, + "@sentry.bun": { + "label": "Package: Bun" + }, + "@sentry.ember": { + "label": "Package: ember" + }, + "@sentry.gatsby": { + "label": "Package: gatbsy" + }, + "@sentry.nextjs": { + "label": "Package: Nextjs" + }, + "@sentry.node": { + "label": "Package: Node" + }, + "@sentry.opentelemetry-node": { + "label": "Package: otel-node" + }, + "@sentry.react": { + "label": "Package: react" + }, + "@sentry.remix": { + "label": "Package: remix" + }, + "@sentry.serverless": { + "label": "Package: Serverless" + }, + "@sentry.sveltekit": { + "label": "Package: SvelteKit" + }, + "@sentry.svelte": { + "label": "Package: svelte" + }, + "@sentry.vue": { + "label": "Package: vue" + }, + "@sentry.wasm": { + "label": "Package: wasm" + }, + "Sentry.Browser.Loader": { + "label": "Package-Meta: Loader" + }, + "Sentry.Browser.CDN.bundle": { + "label": "Package-Meta: CDN" + } + } + export_to: output + + - name: Add package label if applicable + # Note: We only add the label if the issue is still open + if: steps.packageLabel.outputs.label != '' + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: ${{ steps.packageLabel.outputs.label }} diff --git a/.github/workflows/release-size-info.yml b/.github/workflows/release-size-info.yml new file mode 100644 index 000000000000..01197d02c0b7 --- /dev/null +++ b/.github/workflows/release-size-info.yml @@ -0,0 +1,30 @@ +name: Add size info to release +on: + release: + types: + - published + workflow_dispatch: + inputs: + version: + description: Which version to add size info for + required: false + +# This workflow is triggered when a release is published +# It fetches the size-limit info from the release branch and adds it to the release +jobs: + release-size-info: + runs-on: ubuntu-20.04 + name: 'Add size-limit info to release' + + steps: + - name: Get version + id: get_version + run: echo "version=${{ github.event.inputs.version || github.event.release.tag_name }}" >> $GITHUB_OUTPUT + + - name: Update Github Release + if: steps.get_version.outputs.version != '' + uses: getsentry/size-limit-release@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + version: ${{ steps.get_version.outputs.version }} + workflow_name: 'Build & Test' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000000..e55d7b9470fb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Prepare Release +on: + workflow_dispatch: + inputs: + version: + description: Version to release + required: true + force: + description: Force a release even when there are release-blockers (optional) + required: false + merge_target: + description: Target branch to merge into. Uses the default branch as a fallback (optional) + required: false + default: v7 +jobs: + release: + runs-on: ubuntu-20.04 + name: 'Release a new version' + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_RELEASE_PAT }} + fetch-depth: 0 + - name: Prepare release + uses: getsentry/action-prepare-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GH_RELEASE_PAT }} + with: + version: ${{ github.event.inputs.version }} + force: ${{ github.event.inputs.force }} + merge_target: ${{ github.event.inputs.merge_target }} + craft_config_from_merge_target: true + diff --git a/.gitignore b/.gitignore index 13e3a16d7665..73fc943c1152 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,27 @@ # dependencies node_modules/ packages/*/package-lock.json +dev-packages/*/package-lock.json +package-lock.json # build and test +# SDK builds build/ -packages/*/dist/ -packages/*/esm/ +# various integration test builds +dist/ coverage/ scratch/ +*.js.map *.pyc *.tsbuildinfo +# side effects of running AWS lambda layer zip action locally +dist-serverless/ +sentry-node-serverless-*.zip +# transpiled transformers +jest/transformers/*.js +# node tarballs +packages/*/sentry-*.tgz +.nxcache # logs yarn-error.log @@ -26,11 +38,24 @@ local.log ._* .Spotlight-V100 .Trashes +.nx .rpt2_cache -docs lint-results.json # legacy tmp.js + +# eslint +.eslintcache +**/eslintcache/* + +# deno +packages/deno/build-types +packages/deno/build-test +packages/deno/lib.deno.d.ts + +# gatsby +packages/gatsby/gatsby-browser.d.ts +packages/gatsby/gatsby-node.d.ts diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index dd449725e188..000000000000 --- a/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -*.md diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 9fbc63fdc4af..000000000000 --- a/.prettierrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "printWidth": 120, - "proseWrap": "always", - "singleQuote": true, - "trailingComma": "all" -} diff --git a/.secret_scan_ignore b/.secret_scan_ignore new file mode 100644 index 000000000000..889f02c8f3c4 --- /dev/null +++ b/.secret_scan_ignore @@ -0,0 +1 @@ +packages\/serverless\/test\/private\.pem diff --git a/.size-limit.js b/.size-limit.js new file mode 100644 index 000000000000..92e7cd7bce58 --- /dev/null +++ b/.size-limit.js @@ -0,0 +1,175 @@ +module.exports = [ + // Main browser webpack builds + { + name: '@sentry/browser (incl. Tracing, Replay, Feedback) - Webpack (gzipped)', + path: 'packages/browser/build/npm/esm/index.js', + import: '{ init, Replay, BrowserTracing, Feedback }', + gzip: true, + limit: '90 KB', + }, + { + name: '@sentry/browser (incl. Tracing, Replay) - Webpack (gzipped)', + path: 'packages/browser/build/npm/esm/index.js', + import: '{ init, Replay, BrowserTracing }', + gzip: true, + limit: '75 KB', + }, + { + name: '@sentry/browser (incl. Tracing, Replay with Canvas) - Webpack (gzipped)', + path: 'packages/browser/build/npm/esm/index.js', + import: '{ init, Replay, BrowserTracing, ReplayCanvas }', + gzip: true, + limit: '90 KB', + }, + { + name: '@sentry/browser (incl. Tracing, Replay) - Webpack with treeshaking flags (gzipped)', + path: 'packages/browser/build/npm/esm/index.js', + import: '{ init, Replay, BrowserTracing }', + gzip: true, + limit: '75 KB', + modifyWebpackConfig: function (config) { + const webpack = require('webpack'); + config.plugins.push( + new webpack.DefinePlugin({ + __SENTRY_DEBUG__: false, + __RRWEB_EXCLUDE_SHADOW_DOM__: true, + __RRWEB_EXCLUDE_IFRAME__: true, + __SENTRY_EXCLUDE_REPLAY_WORKER__: true, + }), + ); + return config; + }, + }, + { + name: '@sentry/browser (incl. Tracing) - Webpack (gzipped)', + path: 'packages/browser/build/npm/esm/index.js', + import: '{ init, BrowserTracing }', + gzip: true, + limit: '37 KB', + }, + { + name: '@sentry/browser (incl. browserTracingIntegration) - Webpack (gzipped)', + path: 'packages/browser/build/npm/esm/index.js', + import: '{ init, browserTracingIntegration }', + gzip: true, + limit: '37 KB', + }, + { + name: '@sentry/browser (incl. Feedback) - Webpack (gzipped)', + path: 'packages/browser/build/npm/esm/index.js', + import: '{ init, Feedback }', + gzip: true, + limit: '50 KB', + }, + { + name: '@sentry/browser (incl. sendFeedback) - Webpack (gzipped)', + path: 'packages/browser/build/npm/esm/index.js', + import: '{ init, sendFeedback }', + gzip: true, + limit: '50 KB', + }, + { + name: '@sentry/browser - Webpack (gzipped)', + path: 'packages/browser/build/npm/esm/index.js', + import: '{ init }', + gzip: true, + limit: '28 KB', + }, + + // Browser CDN bundles (ES6) + { + name: '@sentry/browser (incl. Tracing, Replay, Feedback) - ES6 CDN Bundle (gzipped)', + path: 'packages/browser/build/bundles/bundle.tracing.replay.feedback.min.js', + gzip: true, + limit: '90 KB', + }, + { + name: '@sentry/browser (incl. Tracing, Replay) - ES6 CDN Bundle (gzipped)', + path: 'packages/browser/build/bundles/bundle.tracing.replay.min.js', + gzip: true, + limit: '75 KB', + }, + { + name: '@sentry/browser (incl. Tracing) - ES6 CDN Bundle (gzipped)', + path: 'packages/browser/build/bundles/bundle.tracing.min.js', + gzip: true, + limit: '38 KB', + }, + { + name: '@sentry/browser - ES6 CDN Bundle (gzipped)', + path: 'packages/browser/build/bundles/bundle.min.js', + gzip: true, + limit: '28 KB', + }, + + // browser CDN bundles (ES6 + non-gzipped) + { + name: '@sentry/browser (incl. Tracing, Replay) - ES6 CDN Bundle (minified & uncompressed)', + path: 'packages/browser/build/bundles/bundle.tracing.replay.min.js', + gzip: false, + brotli: false, + limit: '260 KB', + }, + { + name: '@sentry/browser (incl. Tracing) - ES6 CDN Bundle (minified & uncompressed)', + path: 'packages/browser/build/bundles/bundle.tracing.min.js', + gzip: false, + brotli: false, + limit: '113 KB', + }, + { + name: '@sentry/browser - ES6 CDN Bundle (minified & uncompressed)', + path: 'packages/browser/build/bundles/bundle.min.js', + gzip: false, + brotli: false, + limit: '80 KB', + }, + + // Browser CDN bundles (ES5) + // Replay is not supported in ES5 mode + { + name: '@sentry/browser (incl. Tracing) - ES5 CDN Bundle (gzipped)', + path: 'packages/browser/build/bundles/bundle.tracing.es5.min.js', + gzip: true, + limit: '41 KB', + }, + + // React + { + name: '@sentry/react (incl. Tracing, Replay) - Webpack (gzipped)', + path: 'packages/react/build/esm/index.js', + import: '{ init, BrowserTracing, Replay }', + gzip: true, + limit: '75 KB', + }, + { + name: '@sentry/react - Webpack (gzipped)', + path: 'packages/react/build/esm/index.js', + import: '{ init }', + gzip: true, + limit: '30 KB', + }, + + // Next.js + { + name: '@sentry/nextjs Client (incl. Tracing, Replay) - Webpack (gzipped)', + path: 'packages/nextjs/build/esm/client/index.js', + import: '{ init, BrowserTracing, Replay }', + gzip: true, + limit: '110 KB', + }, + { + name: '@sentry/nextjs Client - Webpack (gzipped)', + path: 'packages/nextjs/build/esm/client/index.js', + import: '{ init }', + gzip: true, + limit: '57 KB', + }, + { + name: '@sentry-internal/feedback - Webpack (gzipped)', + path: 'packages/feedback/build/npm/esm/index.js', + import: '{ Feedback }', + gzip: true, + limit: '25 KB', + }, +]; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 863a6cfa1df4..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,57 +0,0 @@ -git: - depth: false # we need this to make proper releases - -branches: - only: - - master - - /^release\/.+$/ - - /^major\/.+$/ - -install: yarn --ignore-engines -os: linux - -language: node_js -dist: bionic - -cache: - yarn: true - directories: - - node_modules - -jobs: - include: - - stage: Test - name: '@sentry/packages - build + lint + test + codecov + danger [node v12]' - node_js: '12' - script: scripts/danger.sh - - name: '@sentry/packages - build and test [node v6]' - node_js: '6' - script: scripts/test.sh - - name: '@sentry/packages - build and test [node v8]' - node_js: '8' - script: scripts/test.sh - - name: '@sentry/packages - build and test [node v10]' - node_js: '10' - script: scripts/test.sh - - name: '@sentry/packages - build and test [node v12]' - node_js: '12' - script: scripts/test.sh - - name: '@sentry/browser - browserstack integration tests' - node_js: '12' - script: scripts/browser-integration.sh - if: fork = false - - stage: Deploy - name: '@sentry/packages - pack and zeus upload' - node_js: '12' - if: branch =~ ^release/ - script: scripts/pack-and-upload.sh - -notifications: - webhooks: - urls: - - https://zeus.ci/hooks/853ee4aa-d692-11e7-8c60-0a580a28020f/public/provider/travis/webhook - on_success: always - on_failure: always - on_start: always - on_cancel: always - on_error: always diff --git a/.vscode/extensions.json b/.vscode/extensions.json index a4ca674631bd..9b4a2aa7eb87 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,11 @@ { // See http://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format - "recommendations": ["esbenp.prettier-vscode", "ms-vscode.vscode-typescript-tslint-plugin"] + "recommendations": [ + "esbenp.prettier-vscode", + "biomejs.biome", + "dbaeumer.vscode-eslint", + "augustocdias.tasks-shell-input", + "denoland.vscode-deno" + ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index b2bd3e2819de..409d7264a7e0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,24 +2,149 @@ // Use IntelliSense to learn about possible Node.js debug attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + // For available variables, visit: https://code.visualstudio.com/docs/editor/variables-reference "version": "0.2.0", + "inputs": [ + { + // Get the name of the package containing the file in the active tab. + "id": "getPackageName", + "type": "command", + "command": "shellCommand.execute", + "args": { + // Get the current file's absolute path, chop off everything up to and including the repo's `packages` + // directory, then split on `/` and take the first entry + "command": "echo '${file}' | sed s/'.*sentry-javascript\\/packages\\/'// | grep --extended-regexp --only-matching --max-count 1 '[^\\/]+' | head -1", + "cwd": "${workspaceFolder}", + // normally `input` commands bring up a selector for the user, but given that there should only be one + // choice here, this lets us skip the prompt + "useSingleResult": true + } + } + ], "configurations": [ + // Debug the ts-node script in the currently active window + { + "name": "Debug ts-node script (open file)", + "type": "pwa-node", + "cwd": "${workspaceFolder}/packages/${input:getPackageName}", + "request": "launch", + "runtimeExecutable": "yarn", + "runtimeArgs": ["ts-node", "-P", "${workspaceFolder}/tsconfig.dev.json", "${file}"], + "skipFiles": ["/**"], + "outFiles": ["${workspaceFolder}/**/*.js", "!**/node_modules/**"], + "sourceMaps": true, + "smartStep": true, + "internalConsoleOptions": "openOnSessionStart", + "outputCapture": "std" + }, + // Run rollup using the config file which is in the currently active tab. { + "name": "Debug rollup (config from open file)", + "type": "pwa-node", + "cwd": "${workspaceFolder}/packages/${input:getPackageName}", + "request": "launch", + "runtimeExecutable": "yarn", + "runtimeArgs": ["rollup", "-c", "${file}"], + "skipFiles": ["/**"], + "outFiles": ["${workspaceFolder}/**/*.js", "!**/node_modules/**"], + "sourceMaps": true, + "smartStep": true, + "internalConsoleOptions": "openOnSessionStart", + "outputCapture": "std" + }, + // Run a specific test file in watch mode (must have file in currently active tab when hitting the play button). + // NOTE: If you try to run this and VSCode complains that the command `shellCommand.execute` can't be found, go + // install the recommended extension Tasks Shell Input. + { + "name": "Debug unit tests - just open file", "type": "node", + "cwd": "${workspaceFolder}/packages/${input:getPackageName}", "request": "launch", - "cwd": "${workspaceFolder}/packages/node", - "name": "Debug @sentry/node", - "preLaunchTask": "", - "program": "${workspaceRoot}/node_modules/.bin/jest", + "program": "${workspaceFolder}/node_modules/.bin/jest", "args": [ - "--config", - "${workspaceRoot}/packages/node/package.json", + "--watch", + // this runs one test at a time, rather than running them in parallel (necessary for debugging so that you know + // you're hitting a single test's breakpoints, in order) "--runInBand", + // coverage messes up the source maps + "--coverage", + "false", + // remove this to run all package tests "${relativeFile}" ], "sourceMaps": true, "smartStep": true, - "outFiles": ["${workspaceRoot}/packages/node/dist"] + // otherwise it goes to the VSCode debug terminal, which prints the test output or console logs (depending on + // "outputCapture" option here; default is to show console logs), but not both + "console": "integratedTerminal", + // since we're not using it, don't automatically switch to it + "internalConsoleOptions": "neverOpen" + }, + + // Run a specific test file in watch mode (must have file in currently active tab when hitting the play button). + // NOTE: If you try to run this and VSCode complains that the command `shellCommand.execute` can't be found, go + // install the recommended extension Tasks Shell Input. + { + "name": "Debug playwright tests (just open file)", + "type": "pwa-node", + "cwd": "${workspaceFolder}/packages/${input:getPackageName}", + "request": "launch", + "runtimeExecutable": "yarn", + "runtimeArgs": [ + // `nodemon` is basically `node --watch` + "nodemon", + // be default it only watches JS files, so have it watch TS files instead + "--ext", + "ts", + "${workspaceFolder}/node_modules/playwright/node_modules/.bin/playwright", + "test", + "${file}" + ], + "skipFiles": ["/**"], + "outFiles": ["${workspaceFolder}/**/*.js", "!**/node_modules/**"], + "sourceMaps": true, + "smartStep": true, + "internalConsoleOptions": "openOnSessionStart", + // show stdout and stderr output in the debug console + "outputCapture": "std" + }, + + // @sentry/nextjs - Run a specific integration test file + // Must have test file in currently active tab when hitting the play button, and must already have run `yarn` in test app directory + { + "name": "Debug @sentry/nextjs integration tests - just open file", + "type": "node", + "cwd": "${workspaceFolder}/packages/nextjs", + "request": "launch", + // since we're not using the normal test runner, we need to make sure we're using the current version of all local + // SDK packages and then manually rebuild the test app + "preLaunchTask": "Prepare nextjs integration test app for debugging", + // running `server.js` directly (rather than running the tests through yarn) allows us to skip having to reinstall + // dependencies on every new test run + "program": "${workspaceFolder}/packages/nextjs/test/integration/test/server.js", + "args": [ + "--debug", + // remove these two lines to run all integration tests + "--filter", + "${fileBasename}" + ], + + "skipFiles": ["/**"], + "sourceMaps": true, + // this controls which files are sourcemapped + "outFiles": [ + // our SDK code + "${workspaceFolder}/**/cjs/**/*.js", + // the built test app + "${workspaceFolder}/packages/nextjs/test/integration/.next/**/*.js", + "!**/node_modules/**" + ], + "resolveSourceMapLocations": [ + "${workspaceFolder}/**/cjs/**", + "${workspaceFolder}/packages/nextjs/test/integration/.next/**", + "!**/node_modules/**" + ], + "internalConsoleOptions": "openOnSessionStart" } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 84d066dc93a6..2950621966b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,13 +10,33 @@ "search.exclude": { "**/node_modules/": true, "**/build/": true, - "**/dist/": true + "**/dist/": true, + "**/coverage/": true, + "**/yarn-error.log": true }, "typescript.tsdk": "./node_modules/typescript/lib", - "tslint.autoFixOnSave": true, "[json]": { "editor.formatOnType": false, "editor.formatOnPaste": false, "editor.formatOnSave": false - } + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "yaml.schemas": { + "https://json.schemastore.org/github-workflow.json": ".github/workflows/**.yml" + }, + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ], + "deno.enablePaths": ["packages/deno/test"], + "editor.codeActionsOnSave": { + "source.organizeImports.biome": "explicit", + }, + "editor.defaultFormatter": "biomejs.biome" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000000..636302bff454 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 for documentation about `tasks.json` syntax + "version": "2.0.0", + "tasks": [ + { + "label": "Prepare nextjs integration test app for VSCode debugger", + "type": "npm", + "script": "predebug", + "path": "packages/nextjs/test/integration/", + "detail": "Link the SDK (if not already linked) and build test app" + } + ] +} diff --git a/.yarnrc b/.yarnrc index 19daacaa0826..d81643fe43f1 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1 +1 @@ -workspaces-experimental true +env: NODE_OPTIONS --stack-trace-limit=10000 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2888741873f2..2cfe028a75f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,4692 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 7.119.1 + +- fix(browser/v7): Ensure wrap() only returns functions (#13838 backport) + +Work in this release contributed by @legobeat. Thank you for your contribution! + +## 7.119.0 + +- backport(tracing): Report dropped spans for transactions (#13343) + +## 7.118.0 + +- fix(v7/bundle): Ensure CDN bundles do not overwrite `window.Sentry` (#12579) + +## 7.117.0 + +- feat(browser/v7): Publish browserprofling CDN bundle (#12224) +- fix(v7/publish): Add `v7` tag to `@sentry/replay` (#12304) + +## 7.116.0 + +- build(craft): Publish lambda layer under its own name for v7 (#12098) (#12099) + +This release publishes a new AWS Lambda layer under the name `SentryNodeServerlessSDKv7` that users still running v7 can +use instead of pinning themselves to `SentryNodeServerlessSDK:235`. + +## 7.115.0 + +- feat(v7): Add support for global onUnhandled Error/Promise for Bun (#11959) +- fix(replay/v7): Fix user activity not being updated in `start()` (#12003) +- ref(api): Remove `lastEventId` deprecation warnings (#12042) + +## 7.114.0 + +### Important Changes + +- **fix(browser/v7): Continuously record CLS (#11935)** + +This release fixes a bug that caused the cumulative layout shift (CLS) web vital not to be reported in a majority of the +cases where it should have been reported. With this change, the CLS web vital should now always be reported for +pageloads with layout shift. If a pageload did not have layout shift, no CLS web vital should be reported. + +**Please note that upgrading the SDK to this version may cause data in your dashboards to drastically change.** + +### Other Changes + +- build(aws-lambda/v7): Turn off lambda layer publishing (#11875) +- feat(v7): Add `tunnel` support to multiplexed transport (#11851) +- fix(opentelemetry-node): support `HTTP_REQUEST_METHOD` attribute (#11929) +- fix(react/v7): Fix react router v4/v5 span names (#11940) + +## 7.113.0 + +### Important Changes + +- **feat(node): Support Node 22 (#11754)** + +This release adds support for Node 22! 🎉 + +It also adds prebuilt-binaries for Node 22 to `@sentry/profiling-node`. + +### Other Changes + +- feat(feedback): [v7] New feedback button design (#11841) +- feat(replay/v7): Upgrade rrweb packages to 2.15.0 (#11752) +- fix(ember/v7): Ensure unnecessary spans are avoided (#11848) + +## 7.112.2 + +- fix(nextjs|sveltekit): Ensure we can pass `browserTracingIntegration` (#11765) + +## 7.112.1 + +- fix(ember/v7): Do not create rendering spans without transaction (#11750) + +## 7.112.0 + +### Important Changes + +- **feat: Export pluggable integrations from SDK packages (#11723)** + +Instead of installing `@sentry/integrations`, you can now import the pluggable integrations directly from your SDK +package: + +```js +// Before +import * as Sentry fromv '@sentry/browser'; +import { dedupeIntegration } from '@sentry/integrations'; + +Sentry.init({ + integrations: [dedupeIntegration()], +}); + +// After +import * as Sentry from '@sentry/browser'; + +Sentry.init({ + integrations: [Sentry.dedupeIntegration()], +}); +``` + +Note that only the functional integrations (e.g. `xxxIntegration()`) are re-exported. + +### Other Changes + +- feat(replay): Add "maxCanvasSize" option for replay canvases (#11732) +- fix(serverless): [v7] Check if cloud event callback is a function (#11734) + +## 7.111.0 + +- feat(core): Add `server.address` to browser `http.client` spans (#11663) +- fix: Ensure next & sveltekit correctly handle `browserTracingIntegration` (#11647) +- fix(browser): Don't assume window.document is available (#11598) + +## 7.110.1 + +- fix(nextjs): Fix `tunnelRoute` matching logic for hybrid cloud (#11577) + +## 7.110.0 + +### Important Changes + +- **feat(tracing): Add interactions sample rate to browser tracing integrations (#11382)** + +You can now use a `interactionsSampleRate` to control the sample rate of INP spans. `interactionsSampleRate` is applied +on top of the global `tracesSampleRate`. Therefore if `interactionsSampleRate` is `0.5` and `tracesSampleRate` is `0.1`, +then the actual sample rate for interactions is `0.05`. + +```js +Sentry.init({ + tracesSampleRate: 0.1, + integrations: [ + Sentry.browserTracingIntegration({ + interactionsSampleRate: 0.5, + }), + ], +}); +``` + +- **Deprecations** + +This release deprecates the `Hub` class, as well as the `addRequestDataToTransaction` method. The `trpcMiddleware` +method is no longer on the `Handlers` export, but instead is a standalone export. + +Please see the detailed [Migration docs](./MIGRATION.md#deprecations-in-7x) on how to migrate to the new APIs. + +- feat: Deprecate and relocate `trpcMiddleware` (#11389) +- feat(core): Deprecate `Hub` class (#11528) +- feat(types): Deprecate `Hub` interface (#11530) +- ref: Deprecate `addRequestDataToTransaction` (#11368) + +### Other Changes + +- feat(core): Update metric normalization (#11519) +- feat(feedback): Customize feedback placeholder text color (#11521) +- feat(remix): Skip span creation for `OPTIONS` and `HEAD` request. (#11485) +- feat(utils): Add metric buckets rate limit (#11506) +- fix(core): unref timer to not block node exit (#11483) +- fix(metrics): Map `statsd` to `metric_bucket` (#11505) +- fix(spans): Allow zero exclusive time for INP spans (#11408) +- ref(feedback): Configure feedback fonts (#11520) + +## 7.109.0 + +This release deprecates some exports from the `@sentry/replay` package. These exports have been moved to the browser SDK +(or related framework SDKs like `@sentry/react`). + +- feat(feedback): Make "required" text for input elements configurable (#11287) +- feat(node): Add scope to ANR events (#11267) +- feat(replay): Bump `rrweb` to 2.12.0 (#11317) +- fix(node): Local variables skipped after Promise (#11248) +- fix(node): Skip capturing Hapi Boom error responses (#11324) +- fix(web-vitals): Check for undefined navigation entry (#11312) +- ref(replay): Deprecate `@sentry/replay` exports (#11242) + +Work in this release contributed by @soerface. Thank you for your contribution! + +## 7.108.0 + +This release fixes issues with Time to First Byte (TTFB) calculation in the SDK that was introduced with `7.95.0`. It +also fixes some bugs with Interaction to First Paint (INP) instrumentation. This may impact your Sentry Performance +Score calculation. + +- feat(serverless): Add Node.js 20 to compatible runtimes (#11104) +- feat(core): Backport `ResizeObserver` and `googletag` default filters (#11210) +- feat(webvitals): Adds event entry names for INP handler. Also guard against empty metric value +- fix(metrics): use correct statsd data category (#11187) +- fix(node): Record local variables with falsy values (v7) (#11190) +- fix(node): Use unique variable for ANR context transfer (v7) (#11162) +- fix(node): Time zone handling for `cron` (#11225) +- fix(tracing): use web-vitals ttfb calculation (#11231) +- fix(types): Fix incorrect `sampled` type on `Transaction` (#11146) +- fix(webvitals): Fix mapping not being maintained properly and sometimes not sending INP spans (#11183) + +Work in this release contributed by @quisido and @joshkel. Thank you for your contributions! + +## 7.107.0 + +This release fixes issues with INP instrumentation with the Next.js SDK and adds support for the `enableInp` option in +the deprecated `BrowserTracing` integration for backwards compatibility. + +- feat(performance): Port INP span instrumentation to old browser tracing (#11085) +- fix(ember): Ensure browser tracing is correctly lazy loaded (#11027) +- fix(node): Do not assert in vendored proxy code (v7 backport) (#11009) +- fix(react): Set `handled` value in ErrorBoundary depending on fallback [v7] (#11037) + +## 7.106.1 + +- fix(nextjs/v7): Use passthrough `createReduxEnhancer` on server (#11010) + +## 7.106.0 + +- feat(nextjs): Support Hybrid Cloud DSNs with `tunnelRoute` option (#10958) +- feat(remix): Add Vite dev-mode support to Express instrumentation (#10811) +- fix(core): Undeprecate `setTransactionName` +- fix(browser): Don't use chrome variable name (#10874) +- fix(nextjs): Client code should not use Node `global` (#10925) +- fix(node): support undici headers as strings or arrays (#10938) +- fix(types): Add `AttachmentType` and use for envelope `attachment_type` property (#10946) +- ref(ember): Avoid namespace import to hopefully resolve minification issue (#10885) +- chore(sveltekit): Fix punctuation in a console.log (#10895) + +Work in this release contributed by @jessezhang91 and @bfontaine. Thank you for your contributions! + +## 7.105.0 + +### Important Changes + +- **feat: Ensure `withActiveSpan` is exported everywhere (#10877)** + +You can use the `withActiveSpan` method to ensure a certain span is the active span in a given callback. This can be +used to create a span as a child of a specific span with the `startSpan` API methods: + +```js +const parentSpan = Sentry.startInactiveSpan({ name: 'parent' }); +if (parentSpan) { + withActiveSpan(parentSpan, () => { + // This will be a direct child of parentSpan + const childSpan = Sentry.startInactiveSpan({ name: 'child' }); + }); +} +``` + +## 7.104.0 + +### Important Changes + +- **feat(performance): create Interaction standalone spans on inp events (#10709)** + +This release adds support for the INP web vital. This is currently only supported for Saas Sentry, and product support +is released with the upcoming `24.3.0` release of self-hosted. + +To opt-in to this feature, you can use the `enableInp` option in the `browserTracingIntegration`: + +```js +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ + enableInp: true, + }); + ] +}) +``` + +### Other Changes + +- feat(feedback): Flush replays when feedback form opens (#10567) +- feat(profiling-node): Expose `nodeProfilingIntegration` (#10864) +- fix(profiling-node): Fix dependencies to point to current versions (#10861) +- fix(replay): Add `errorHandler` for replayCanvas integration (#10796) + +## 7.103.0 + +### Important Changes + +- **feat(core): Allow to pass `forceTransaction` to `startSpan()` APIs (#10819)** + +You can now pass `forceTransaction: true` to `startSpan()`, `startSpanManual()` and `startInactiveSpan()`. This allows +you to start a span that you want to be a transaction, if possible. Under the hood, the SDK will connect this span to +the running active span (if there is one), but still send the new span as a transaction to the Sentry backend, if +possible, ensuring it shows up as a transaction throughout the system. + +Please note that setting this to `true` does not _guarantee_ that this will be sent as a transaction, but that the SDK +will try to do so. You can enable this flag if this span is important to you and you want to ensure that you can see it +in the Sentry UI. + +### Other Changes + +- fix: Make breadcrumbs option optional in WinterCGFetch integration (#10792) + +## 7.102.1 + +- fix(performance): Fixes latest route name and source for interactions not updating properly on navigation (#10702) +- fix(tracing): Guard against missing `window.location` (#10659) +- ref: Make span types more robust (#10660) +- ref(remix): Make `@remix-run/router` a dependency (v7) (#10779) + +## 7.102.0 + +- fix: Export session API (#10712) +- fix(core): Fix scope capturing via `captureContext` function (#10737) + +## 7.101.1 + +In version 7.101.0 the `@sentry/hub` package was missing due to a publishing issue. This release contains the package +again. + +- fix(nextjs): Remove `webpack://` prefix more broadly from source map `sources` field (#10641) + +## 7.101.0 + +- feat: Export semantic attribute keys from SDK packages (#10637) +- feat(core): Add metric summaries to spans (#10554) +- feat(core): Deprecate the `Hub` constructor (#10584) +- feat(core): Make custom tracing methods return spans & set default op (#10633) +- feat(replay): Add `getReplay` utility function (#10510) +- fix(angular-ivy): Add `exports` field to `package.json` (#10569) +- fix(sveltekit): Avoid capturing Http 4xx errors on the client (#10571) +- fix(sveltekit): Properly await sourcemaps flattening (#10602) + +## 7.100.1 + +This release contains build fixes for profiling-node. + +- build(profiling-node): make sure debug build plugin is used #10534 +- build: Only run profiling e2e test if bindings have changed #10542 +- fix(feedback): Replay breadcrumb for feedback events was incorrect #10536 + +## 7.100.0 + +### Important Changes + +#### Deprecations + +This release includes some deprecations. For more details please look at our +[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md). + +The deprecation most likely to affect you is the one of `BrowserTracing`. Instead of `new BrowserTracing()`, you should +now use `browserTracingIntegration()`, which will also handle framework-specific instrumentation out of the box for +you - no need to pass a custom `routingInstrumentation` anymore. For `@sentry/react`, we expose dedicated integrations +for the different react-router versions: + +- `reactRouterV6BrowserTracingIntegration()` +- `reactRouterV5BrowserTracingIntegration()` +- `reactRouterV4BrowserTracingIntegration()` +- `reactRouterV3BrowserTracingIntegration()` + +See the +[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md#depreacted-browsertracing-integration) +for details. + +- feat(angular): Export custom `browserTracingIntegration()` (#10353) +- feat(browser): Deprecate `BrowserTracing` integration (#10493) +- feat(browser): Export `browserProfilingIntegration` (#10438) +- feat(bun): Export `bunServerIntegration()` (#10439) +- feat(nextjs): Add `browserTracingIntegration` (#10397) +- feat(react): Add `reactRouterV3BrowserTracingIntegration` for react router v3 (#10489) +- feat(react): Add `reactRouterV4/V5BrowserTracingIntegration` for react router v4 & v5 (#10488) +- feat(react): Add `reactRouterV6BrowserTracingIntegration` for react router v6 & v6.4 (#10491) +- feat(remix): Add custom `browserTracingIntegration` (#10442) +- feat(node): Expose functional integrations to replace classes (#10356) +- feat(vercel-edge): Replace `WinterCGFetch` with `winterCGFetchIntegration` (#10436) +- feat: Deprecate non-callback based `continueTrace` (#10301) +- feat(vue): Deprecate `new VueIntegration()` (#10440) +- feat(vue): Implement vue `browserTracingIntegration()` (#10477) +- feat(sveltekit): Add custom `browserTracingIntegration()` (#10450) + +#### Profiling Node + +`@sentry/profiling-node` has been ported into the monorepo. Future development for it will happen here! + +- pkg(profiling-node): port profiling-node repo to monorepo (#10151) + +### Other Changes + +- feat: Export `setHttpStatus` from all packages (#10475) +- feat(bundles): Add pluggable integrations on CDN to `Sentry` namespace (#10452) +- feat(core): Pass `name` & `attributes` to `tracesSampler` (#10426) +- feat(feedback): Add `system-ui` to start of font family (#10464) +- feat(node-experimental): Add koa integration (#10451) +- feat(node-experimental): Update opentelemetry packages (#10456) +- feat(node-experimental): Update tracing integrations to functional style (#10443) +- feat(replay): Bump `rrweb` to 2.10.0 (#10445) +- feat(replay): Enforce masking of credit card fields (#10472) +- feat(utils): Add `propagationContextFromHeaders` (#10313) +- fix: Make `startSpan`, `startSpanManual` and `startInactiveSpan` pick up the scopes at time of creation instead of + termination (#10492) +- fix(feedback): Fix logo color when colorScheme is "system" (#10465) +- fix(nextjs): Do not report redirects and notFound calls as errors in server actions (#10474) +- fix(nextjs): Fix navigation tracing on app router (#10502) +- fix(nextjs): Apply server action data to correct isolation scope (#10514) +- fix(node): Use normal `require` call to import Undici (#10388) +- ref(nextjs): Remove internally used deprecated APIs (#10453) +- ref(vue): use startInactiveSpan in tracing mixin (#10406) + +## 7.99.0 + +### Important Changes + +#### Deprecations + +This release includes some deprecations for span related methods and integrations in our Deno SDK, `@sentry/deno`. For +more details please look at our +[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md). + +- feat(core): Deprecate `Span.setHttpStatus` in favor of `setHttpStatus` (#10268) +- feat(core): Deprecate `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` (#10361) +- feat(core): Deprecate `StartSpanOptions.origin` in favour of passing attribute (#10274) +- feat(deno): Expose functional integrations to replace classes (#10355) + +### Other Changes + +- feat(bun): Add missing `@sentry/node` re-exports (#10396) +- feat(core): Add `afterAllSetup` hook for integrations (#10345) +- feat(core): Ensure `startSpan()` can handle spans that require parent (#10386) +- feat(core): Read propagation context off scopes in `startSpan` APIs (#10300) +- feat(remix): Export missing `@sentry/node` functions (#10385, #10391) +- feat(serverless): Add missing `@sentry/node` re-exports (#10390) +- feat(sveltekit): Add more missing `@sentry/node` re-exports (#10392) +- feat(tracing): Export proper type for browser tracing (#10411) +- feat(tracing): Expose new `browserTracingIntegration` (#10351) +- fix: Ensure `afterAllSetup` is called when using `addIntegration()` (#10372) +- fix(core): Export `spanToTraceContext` function from span utils (#10364) +- fix(core): Make `FunctionToString` integration use SETUP_CLIENTS weakmap (#10358) +- fix(deno): Call function if client is not setup (#10354) +- fix(react): Fix attachReduxState option (#10381) +- fix(spotlight): Use unpatched http.request (#10369) +- fix(tracing): Only create request span if there is active span (#10375) +- ref: Read propagation context off of scope and isolation scope when propagating and applying trace context (#10297) + +Work in this release contributed by @AleshaOleg. Thank you for your contribution! + +## 7.98.0 + +This release primarily fixes some type declaration errors: + +- feat(core): Export `IntegrationIndex` type (#10337) +- fix(nextjs): Fix Http integration type declaration (#10338) +- fix(node): Fix type definitions (#10339) + +## 7.97.0 + +Note: The 7.96.0 release was incomplete. This release is partially encompassing changes from `7.96.0`. + +- feat(react): Add `stripBasename` option for React Router 6 (#10314) + +## 7.96.0 + +Note: This release was incomplete. Not all Sentry SDK packages were released for this version. Please upgrade to 7.98.0 +directly. + +### Important Changes + +#### Deprecations + +This release includes some deprecations for integrations in `@sentry/browser` and frontend framework SDKs +(`@sentry/react`, `@sentry/vue`, etc.). Please take a look at our +[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md) for more details. + +- feat(browser): Export functional integrations & deprecate classes (#10267) + +#### Web Vitals Fix for LCP and CLS + +This release fixes an issue with the Web Vitals integration where LCP and CLS were not being captured correctly, +increasing capture rate by 10-30% for some apps. LCP and CLS capturing issues were introduced with version `7.75.0`. + +- fix(tracing): Ensure web vitals are correctly stopped/captured (#10323) + +### Other Changes + +- fix(node): Fix `node-cron` types and add test (#10315) +- fix(node): Fix downleveled types entry point (#10321) +- fix(node): LocalVariables integration should use setupOnce (#10307) +- fix(replay): Fix type for options of replayIntegration (#10325) + +Work in this release contributed by @Shubhdeep12. Thank you for your contribution! + +## 7.95.0 + +### Important Changes + +#### Deprecations + +This release includes some deprecations in preparation for v8. + +Most notably, it deprecates the `Replay` & `Feedback` classes in favor of a functional replacement: + +```js +import * as Sentry from '@sentry/browser'; + +Sentry.init({ + integrations: [ + // Instead of + new Sentry.Replay(), + new Sentry.Feedback(), + // Use the functional replacement: + Sentry.replayIntegration(), + Sentry.feedbackIntegration(), + ], +}); +``` + +- feat(core): Deprecate `Span.origin` in favor of `sentry.origin` attribute (#10260) +- feat(core): Deprecate `Span.parentSpanId` (#10244) +- feat(core): Expose `isInitialized()` to replace checking via `getClient` (#10296) +- feat(replay): Deprecate `Replay`, `ReplayCanvas`, `Feedback` classes (#10270) +- feat(wasm): Deprecate `Wasm` integration class (#10230) + +### Other Changes + +- feat: Make `parameterize` function available through browser and node API (#10085) +- feat(feedback): Configure feedback border radius (#10289) +- feat(sveltekit): Update default integration handling & deprecate `addOrUpdateIntegration` (#10263) +- fix(replay-canvas): Add missing dependency on @sentry/utils (#10279) +- fix(tracing): Don't send negative ttfb (#10286) + +Work in this release contributed by @AleshaOleg. Thank you for your contribution! + +## 7.94.1 + +This release fixes a publishing issue. + +## 7.94.0 + +### Important Changes + +#### Deprecations + +As we're moving closer to the next major version of the SDK, more public APIs were deprecated. + +To get a head start on migrating to the replacement APIs, please take a look at our +[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md). + +- feat: Deprecate user segment field (#10210) +- feat(core): Deprecate `finish` on `Span` interface in favour of `end` (#10161) +- feat(core): Deprecate `getCurrentHub()` (#10200) +- feat(core): Deprecate `hub.bindClient()` & `makeMain()` (#10188) +- feat(core): Deprecate `Span.instrumenter` (#10139) +- feat(core): Deprecate `Span.isSuccess()` in favor of reading span status (#10213) +- feat(core): Deprecate `Span.op` in favor of op attribute (#10189) +- feat(core): Deprecate `Span.spanRecorder` (#10199) +- feat(core): Deprecate `Span.status` (#10208) +- feat(core): Deprecate `Span.transaction` in favor of `getRootSpan` (#10134) +- feat(core): Deprecate `Transaction.instrumenter` (#10162) +- feat(core): Deprecate `Transaction.setMeasurement` in favor of `setMeasurement` (#10182) +- feat(core): Deprecate integration classes & `Integrations.X` (#10198) +- feat(core): Deprecate methods on `Hub` (#10124) +- feat(core): Deprecate remaining `setName` declarations on `Transaction` and `Span` (#10164) +- feat(core): Deprecate span `startTimestamp` & `endTimestamp` (#10192) +- feat(core): Deprecate `hub.bindClient()` and `makeMain()` (#10118) +- feat(types): Deprecate `op` on `Span` interface (#10217) +- feat(integrations): Deprecate `Transaction` integration (#10178) +- feat(integrations): Deprecate pluggable integration classes (#10211) + +#### Replay & Canvas + +We have added a new `ReplayCanvas` integration (#10112), which you can add to capture the contents of canvas elements +with Replay. + +Just add it _in addition_ to the regular replay integration: + +```js +Sentry.init({ + integrations: [new Sentry.Replay(), new Sentry.ReplayCanvas()], +}); +``` + +### Other Changes + +- feat(core): Add `client.getIntegrationByName()` (#10130) +- feat(core): Add `client.init()` to replace `client.setupIntegrations()` (#10118) +- feat(core): Add `withActiveSpan` (#10195) +- feat(core): Add `withIsolationScope` (#10141) +- feat(core): Streamline integration function results to be compatible (#10135) +- feat(core): Write data from `setUser`, `setTags`, `setExtras`, `setTag`, `setExtra`, and `setContext` to isolation + scope (#10163) +- feat(core): Add domain information to resource span data #10205 +- feat(feedback): Export sendFeedback from @sentry/browser (#10231) +- feat(node): Update and vendor https-proxy-agent (#10088) +- feat(node-experimental): Add `withActiveSpan` (#10194) +- feat(replays): Add snapshot function to replay canvas integration (#10066) +- feat(types): Add `SerializedEvent` interface (pre v8) (#10240) +- feat(types): Add support for new monitor config thresholds (#10225) +- fix: Ensure all integration classes have correct types (#10183) +- fix(astro): Fix import path when using external init files with default path (#10214) +- fix(cdn): Emit console warning instead of error for integration shims (#10193) +- fix(core): Take user from current scope when starting a session (#10153) +- fix(node-experimental): Ensure `http.status_code` is always a string (#10177) +- fix(node): Guard against `process.argv[1]` being undefined (#10155) +- fix(node): Module name resolution (#10144) +- fix(node): Remove leading slash in Windows filenames (#10147) +- fix(remix): Capture thrown fetch responses. (#10166) +- fix(tracing): Gate mongo operation span data behind sendDefaultPii (#10227) +- fix(tracing-internal): Delay pageload transaction finish until document is interactive (#10215) +- fix(tracing-internal): Only collect request/response spans when browser performance timing is available (#10207) +- fix(tracing-internal): Prefer `fetch` init headers over `fetch` input headers (#10176) +- fix(utils): Ensure dropUndefinedKeys() does not break class instances (#10245) + +## 7.93.0 + +### Important Changes + +#### Deprecations + +As we're moving closer to the next major version of the SDK, more public APIs were deprecated. + +To get a head start on migrating to the replacement APIs, please take a look at our +[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md). + +- feat(core): Deprecate `getActiveTransaction()` & `scope.getTransaction()` (#10098) +- feat(core): Deprecate `Hub.shouldSendDefaultPii` (#10062) +- feat(core): Deprecate `new Transaction()` (#10125) +- feat(core): Deprecate `scope.getSpan()` & `scope.setSpan()` (#10114) +- feat(core): Deprecate `scope.setTransactionName()` (#10113) +- feat(core): Deprecate `span.startChild()` (#10091) +- feat(core): Deprecate `startTransaction()` (#10073) +- feat(core): Deprecate `Transaction.getDynamicSamplingContext` in favor of `getDynamicSamplingContextFromSpan` (#10094) +- feat(core): Deprecate arguments for `startSpan()` (#10101) +- feat(core): Deprecate hub capture APIs and add them to `Scope` (#10039) +- feat(core): Deprecate session APIs on hub and add global replacements (#10054) +- feat(core): Deprecate span `name` and `description` (#10056) +- feat(core): Deprecate span `tags`, `data`, `context` & setters (#10053) +- feat(core): Deprecate transaction metadata in favor of attributes (#10097) +- feat(core): Deprecate `span.sampled` in favor of `span.isRecording()` (#10034) +- ref(node-experimental): Deprecate `lastEventId` on scope (#10093) + +#### Cron Monitoring Support for `node-schedule` library + +This release adds auto instrumented check-ins for the `node-schedule` library. + +```ts +import * as Sentry from '@sentry/node'; +import * as schedule from 'node-schedule'; + +const scheduleWithCheckIn = Sentry.cron.instrumentNodeSchedule(schedule); + +const job = scheduleWithCheckIn.scheduleJob('my-cron-job', '* * * * *', () => { + console.log('You will see this message every minute'); +}); +``` + +- feat(node): Instrumentation for `node-schedule` library (#10086) + +### Other Changes + +- feat(core): Add `span.spanContext()` (#10037) +- feat(core): Add `spanToJSON()` method to get span properties (#10074) +- feat(core): Allow to pass `scope` to `startSpan` APIs (#10076) +- feat(core): Allow to pass start/end timestamp for spans flexibly (#10060) +- feat(node): Make `getModuleFromFilename` compatible with ESM (#10061) +- feat(replay): Update rrweb to 2.7.3 (#10072) +- feat(utils): Add `parameterize` function (#9145) +- fix(astro): Use correct package name for CF (#10099) +- fix(core): Do not run `setup` for integration on client multiple times (#10116) +- fix(core): Ensure we copy passed in span data/tags/attributes (#10105) +- fix(cron): Make name required for instrumentNodeCron option (#10070) +- fix(nextjs): Don't capture not-found and redirect errors in generation functions (#10057) +- fix(node): `LocalVariables` integration should have correct name (#10084) +- fix(node): Anr events should have an `event_id` (#10068) +- fix(node): Revert to only use sync debugger for `LocalVariables` (#10077) +- fix(node): Update ANR min node version to v16.17.0 (#10107) + +## 7.92.0 + +### Important Changes + +#### Deprecations + +- feat(core): Add `span.updateName()` and deprecate `span.setName()` (#10018) +- feat(core): Deprecate `span.getTraceContext()` (#10032) +- feat(core): Deprecate `span.toTraceparent()` in favor of `spanToTraceHeader()` util (#10031) +- feat(core): Deprecate `trace` in favor of `startSpan` (#10012) +- feat(core): Deprecate span `toContext()` and `updateWithContext()` (#10030) +- ref: Deprecate `deepReadDirSync` (#10016) +- ref: Deprecate `lastEventId()` (#10043) + +Please take a look at the [Migration docs](./MIGRATION.md) for more details. These methods will be removed in the +upcoming [v8 major release](https://github.com/getsentry/sentry-javascript/discussions/9802). + +#### Cron Monitoring Support for `cron` and `node-cron` libraries + +- feat(node): Instrumentation for `cron` library (#9999) +- feat(node): Instrumentation for `node-cron` library (#9904) + +This release adds instrumentation for the `cron` and `node-cron` libraries. This allows you to monitor your cron jobs +with [Sentry cron monitors](https://docs.sentry.io/product/crons/). + +For [`cron`](https://www.npmjs.com/package/cron): + +```js +import * as Sentry from '@sentry/node'; +import { CronJob } from 'cron'; + +const CronJobWithCheckIn = Sentry.cron.instrumentCron(CronJob, 'my-cron-job'); + +// use the constructor +const job = new CronJobWithCheckIn('* * * * *', () => { + console.log('You will see this message every minute'); +}); + +// or from +const job = CronJobWithCheckIn.from({ + cronTime: '* * * * *', + onTick: () => { + console.log('You will see this message every minute'); + }, +}); +``` + +For [`node-cron`](https://www.npmjs.com/package/node-cron): + +```js +import * as Sentry from '@sentry/node'; +import cron from 'node-cron'; + +const cronWithCheckIn = Sentry.cron.instrumentNodeCron(cron); + +cronWithCheckIn.schedule( + '* * * * *', + () => { + console.log('running a task every minute'); + }, + { name: 'my-cron-job' }, +); +``` + +### Other Changes + +- feat(astro): Add `enabled` option to Astro integration options (#10007) +- feat(core): Add `attributes` to `Span` (#10008) +- feat(core): Add `setClient()` and `getClient()` to `Scope` (#10055) +- feat(integrations): Capture error cause with `captureErrorCause` in `ExtraErrorData` integration (#9914) +- feat(node-experimental): Allow to pass base span options to trace methods (#10006) +- feat(node): Local variables via async inspector in node 19+ (#9962) +- fix(astro): handle commonjs related issues (#10042) +- fix(astro): Handle non-utf8 encoded streams in middleware (#9989) +- fix(astro): prevent sentry from externalized (#9994) +- fix(core): Ensure `withScope` sets current scope correctly with async callbacks (#9974) +- fix(node): ANR fixes and additions (#9998) +- fix(node): Anr should not block exit (#10035) +- fix(node): Correctly resolve module name (#10001) +- fix(node): Handle inspector already open (#10025) +- fix(node): Make `NODE_VERSION` properties required (#9964) +- fix(node): Anr doesn't block exit (#10064) +- fix(utils): use correct typeof URL validation (#10028) +- perf(astro): reduce unnecessary path resolutions (#10021) +- ref(astro): Use astro logger instead of console (#9995) +- ref(remix): Isolate Express instrumentation from server auto-instrumentation. (#9966) + +Work in this release contributed by @joshkel. Thank you for your contribution! + +## 7.91.0 + +### Important Changes + +- **feat: Add server runtime metrics aggregator (#9894)** + +The release adds alpha support for [Sentry developer metrics](https://github.com/getsentry/sentry/discussions/58584) in +the server runtime SDKs (`@sentry/node`, `@sentry/deno`, `@sentry/nextjs` server-side, etc.). Via the newly introduced +APIs, you can now flush metrics directly to Sentry. + +To enable capturing metrics, you first need to add the `metricsAggregator` experiment to your `Sentry.init` call. + +```js +Sentry.init({ + dsn: '__DSN__', + _experiments: { + metricsAggregator: true, + }, +}); +``` + +Then you'll be able to add `counters`, `sets`, `distributions`, and `gauges` under the `Sentry.metrics` namespace. + +```js +// Add 4 to a counter named `hits` +Sentry.metrics.increment('hits', 4); + +// Add 2 to gauge named `parallel_requests`, tagged with `type: "a"` +Sentry.metrics.gauge('parallel_requests', 2, { tags: { type: 'a' } }); + +// Add 4.6 to a distribution named `response_time` with unit seconds +Sentry.metrics.distribution('response_time', 4.6, { unit: 'seconds' }); + +// Add 2 to a set named `valuable.ids` +Sentry.metrics.set('valuable.ids', 2); +``` + +- **feat(node): Rework ANR to use worker script via an integration (#9945)** + +The [ANR tracking integration for Node](https://docs.sentry.io/platforms/node/configuration/application-not-responding/) +has been reworked to use an integration. ANR tracking now requires a minimum Node version of 16 or higher. Previously +you had to call `Sentry.enableANRDetection` before running your application, now you can simply add the `Anr` +integration to your `Sentry.init` call. + +```js +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })], +}); +``` + +### Other Changes + +- feat(breadcrumbs): Send component names on UI breadcrumbs (#9946) +- feat(core): Add `getGlobalScope()` method (#9920) +- feat(core): Add `getIsolationScope()` method (#9957) +- feat(core): Add `span.end()` to replace `span.finish()` (#9954) +- feat(core): Ensure `startSpan` & `startSpanManual` fork scope (#9955) +- feat(react): Send component name on spans (#9949) +- feat(replay): Send component names in replay breadcrumbs (#9947) +- feat(sveltekit): Add options to configure fetch instrumentation script for CSP (#9969) +- feat(tracing): Send component name on interaction spans (#9948) +- feat(utils): Add function to extract relevant component name (#9921) +- fix(core): Rethrow caught promise rejections in `startSpan`, `startSpanManual`, `trace` (#9958) + +## 7.90.0 + +- feat(replay): Change to use preset quality values (#9903) +- fix(replay): Adjust development hydration error messages (#9922) +- fix(sveltekit): Add `types` field to package.json `exports` (#9926) + +## 7.89.0 + +### Important Changes + +#### Deprecations + +- **feat(core): Deprecate `configureScope` (#9887)** +- **feat(core): Deprecate `pushScope` & `popScope` (#9890)** + +This release deprecates `configureScope`, `pushScope`, and `popScope`, which will be removed in the upcoming v8 major +release. + +#### Hapi Integration + +- **feat(node): Add Hapi Integration (#9539)** + +This release adds an integration for Hapi. It can be used as follows: + +```ts +const Sentry = require('@sentry/node'); +const Hapi = require('@hapi/hapi'); + +const init = async () => { + const server = Hapi.server({ + // your server configuration ... + }); + + Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, + integrations: [new Sentry.Integrations.Hapi({ server })], + }); + + server.route({ + // your route configuration ... + }); + + await server.start(); +}; +``` + +#### SvelteKit 2.0 + +- **chore(sveltekit): Add SvelteKit 2.0 to peer dependencies (#9861)** + +This release adds support for SvelteKit 2.0 in the `@sentry/sveltekit` package. If you're upgrading from SvelteKit 1.x +to 2.x and already use the Sentry SvelteKit SDK, no changes apart from upgrading to this (or a newer) version are +necessary. + +### Other Changes + +- feat(core): Add type & utility for function-based integrations (#9818) +- feat(core): Update `withScope` to return callback return value (#9866) +- feat(deno): Support `Deno.CronSchedule` for cron jobs (#9880) +- feat(nextjs): Auto instrument generation functions (#9781) +- feat(nextjs): Connect server component transactions if there is no incoming trace (#9845) +- feat(node-experimental): Update to new Scope APIs (#9799) +- feat(replay): Add `canvas.type` setting (#9877) +- fix(nextjs): Export `createReduxEnhancer` (#9854) +- fix(remix): Do not capture thrown redirect responses. (#9909) +- fix(sveltekit): Add conditional exports (#9872) +- fix(sveltekit): Avoid capturing 404 errors on client side (#9902) +- fix(utils): Do not use `Event` type in worldwide (#9864) +- fix(utils): Support crypto.getRandomValues in old Chromium versions (#9251) +- fix(utils): Update `eventFromUnknownInput` to avoid scope pollution & `getCurrentHub` (#9868) +- ref: Use `addBreadcrumb` directly & allow to pass hint (#9867) + +Work in this release contributed by @adam187, and @jghinestrosa. Thank you for your contributions! + +## 7.88.0 + +### Important Changes + +- **feat(browser): Add browser metrics sdk (#9794)** + +The release adds alpha support for [Sentry developer metrics](https://github.com/getsentry/sentry/discussions/58584) in +the Browser SDKs (`@sentry/browser` and related framework SDKs). Via the newly introduced APIs, you can now flush +metrics directly to Sentry. + +To enable capturing metrics, you first need to add the `MetricsAggregator` integration. + +```js +Sentry.init({ + dsn: '__DSN__', + integrations: [new Sentry.metrics.MetricsAggregator()], +}); +``` + +Then you'll be able to add `counters`, `sets`, `distributions`, and `gauges` under the `Sentry.metrics` namespace. + +```js +// Add 4 to a counter named `hits` +Sentry.metrics.increment('hits', 4); + +// Add 2 to gauge named `parallel_requests`, tagged with `happy: "no"` +Sentry.metrics.gauge('parallel_requests', 2, { tags: { happy: 'no' } }); + +// Add 4.6 to a distribution named `response_time` with unit seconds +Sentry.metrics.distribution('response_time', 4.6, { unit: 'seconds' }); + +// Add 2 to a set named `valuable.ids` +Sentry.metrics.set('valuable.ids', 2); +``` + +In a future release we'll add support for server runtimes (Node, Deno, Bun, Vercel Edge, etc.) + +- **feat(deno): Optionally instrument `Deno.cron` (#9808)** + +This releases add support for instrumenting [Deno cron's](https://deno.com/blog/cron) with +[Sentry cron monitors](https://docs.sentry.io/product/crons/). This requires v1.38 of Deno run with the `--unstable` +flag and the usage of the `DenoCron` Sentry integration. + +```ts +// Import from the Deno registry +import * as Sentry from 'https://deno.land/x/sentry/index.mjs'; + +Sentry.init({ + dsn: '__DSN__', + integrations: [new Sentry.DenoCron()], +}); +``` + +### Other Changes + +- feat(replay): Bump `rrweb` to 2.6.0 (#9847) +- fix(nextjs): Guard against injecting multiple times (#9807) +- ref(remix): Bump Sentry CLI to ^2.23.0 (#9773) + +## 7.87.0 + +- feat: Add top level `getCurrentScope()` method (#9800) +- feat(replay): Bump `rrweb` to 2.5.0 (#9803) +- feat(replay): Capture hydration error breadcrumb (#9759) +- feat(types): Add profile envelope types (#9798) +- fix(astro): Avoid RegExp creation during route interpolation (#9815) +- fix(browser): Avoid importing from `./exports` (#9775) +- fix(nextjs): Catch rejecting flushes (#9811) +- fix(nextjs): Fix devserver CORS blockage when `assetPrefix` is defined (#9766) +- fix(node): Capture errors in tRPC middleware (#9782) + +## 7.86.0 + +- feat(core): Use SDK_VERSION for hub API version (#9732) +- feat(nextjs): Emit warning if your app directory doesn't have a global-error.js file (#9753) +- feat(node): Add cloudflare pages commit sha (#9751) +- feat(remix): Bump @sentry/cli to 2.22.3 (#9741) +- fix(nextjs): Don't accidentally trigger static generation bailout (#9749) +- fix(node): Guard `process.env.NODE_ENV` access in Spotlight integration (#9748) +- fix(utils): Fix XHR instrumentation early return (#9770) +- ref(remix): Rework Error Handling (#9725) + +## 7.85.0 + +- feat(core): Add `addEventProcessor` method (#9554) +- feat(crons): Add interface for heartbeat checkin (#9706) +- feat(feedback): Include Feedback package in browser SDK (#9586) +- fix(astro): Isolate request instrumentation in middleware (#9709) +- fix(replay): Capture JSON XHR response bodies (#9623) +- ref(feedback): Change form `box-shadow` to use CSS var (#9630) + +## 7.84.0 + +### Important Changes + +- **ref(nextjs): Set `automaticVercelMonitors` to be `false` by default (#9697)** + +From this version onwards the default for the `automaticVercelMonitors` option in the Next.js SDK is set to false. +Previously, if you made use of Vercel Crons the SDK automatically instrumented the relevant routes to create Sentry +monitors. Because this feature will soon be generally available, we are now flipping the default to avoid situations +where quota is used unexpectedly. + +If you want to continue using this feature, make sure to set the `automaticVercelMonitors` flag to `true` in your +`next.config.js` Sentry settings. + +### Other Changes + +- chore(astro): Add 4.0.0 preview versions to `astro` peer dependency range (#9696) +- feat(metrics): Add interfaces for metrics (#9698) +- feat(web-vitals): Vendor in INP from web-vitals library (#9690) +- fix(astro): Avoid adding the Sentry Vite plugin in dev mode (#9688) +- fix(nextjs): Don't match files called `middleware` in node_modules (#9686) +- fix(remix): Don't capture error responses that are not 5xx on Remix v2. (#9655) +- fix(tracing): Don't attach resource size if null (#9669) +- fix(utils): Regex match port to stop accidental replace (#9676) +- fix(utils): Try catch new URL when extracting query params (#9675) + +## 7.83.0 + +- chore(astro): Allow Astro 4.0 in peer dependencies (#9683) +- feat(astro): Add `assets` option to source maps upload options (#9668) +- feat(react): Support `exactOptionalPropertyTypes` on `ErrorBoundary` (#9098) +- fix: Don't depend on browser types in `types` (#9682) +- fix(astro): Configure sourcemap assets directory for Vercel adapter (#9665) +- fix(remix): Check the error data before spreading. (#9664) + +## 7.82.0 + +- feat(astro): Automatically add Sentry middleware in Astro integration (#9532) +- feat(core): Add optional `setup` hook to integrations (#9556) +- feat(core): Add top level `getClient()` method (#9638) +- feat(core): Allow to pass `mechanism` as event hint (#9590) +- feat(core): Allow to use `continueTrace` without callback (#9615) +- feat(feedback): Add onClose callback to showReportDialog (#9433) (#9550) +- feat(nextjs): Add request data to all edge-capable functionalities (#9636) +- feat(node): Add Spotlight option to Node SDK (#9629) +- feat(utils): Refactor `addInstrumentationHandler` to dedicated methods (#9542) +- fix: Make full url customizable for Spotlight (#9652) +- fix(astro): Remove Auth Token existence check (#9651) +- fix(nextjs): Fix middleware detection logic (#9637) +- fix(remix): Skip capturing aborted requests (#9659) +- fix(replay): Add `BODY_PARSE_ERROR` warning & time out fetch response load (#9622) +- fix(tracing): Filter out invalid resource sizes (#9641) +- ref: Hoist `RequestData` integration to `@sentry/core` (#9597) +- ref(feedback): Rename onDialog* to onForm*, remove onActorClick (#9625) + +Work in this release contributed by @arya-s. Thank you for your contribution! + +## 7.81.1 + +- fix(astro): Remove method from span op (#9603) +- fix(deno): Make sure files get published (#9611) +- fix(nextjs): Use `globalThis` instead of `global` in edge runtime (#9612) +- fix(node): Improve error handling and shutdown handling for ANR (#9548) +- fix(tracing-internal): Fix case when originalURL contain query params (#9531) + +Work in this release contributed by @powerfulyang, @LubomirIgonda1, @joshkel, and @alexgleason. Thank you for your +contributions! + +## 7.81.0 + +### Important Changes + +**- feat(nextjs): Add instrumentation utility for server actions (#9553)** + +This release adds a utility function `withServerActionInstrumentation` to the `@sentry/nextjs` SDK for instrumenting +your Next.js server actions with error and performance monitoring. + +You can optionally pass form data and headers to record them, and configure the wrapper to record the Server Action +responses: + +```tsx +import * as Sentry from '@sentry/nextjs'; +import { headers } from 'next/headers'; + +export default function ServerComponent() { + async function myServerAction(formData: FormData) { + 'use server'; + return await Sentry.withServerActionInstrumentation( + 'myServerAction', // The name you want to associate this Server Action with in Sentry + { + formData, // Optionally pass in the form data + headers: headers(), // Optionally pass in headers + recordResponse: true, // Optionally record the server action response + }, + async () => { + // ... Your Server Action code + + return { name: 'John Doe' }; + }, + ); + } + + return ( +
+ + +
+ ); +} +``` + +### Other Changes + +- docs(feedback): Example docs on `sendFeedback` (#9560) +- feat(feedback): Add `level` and remove breadcrumbs from feedback event (#9533) +- feat(vercel-edge): Add fetch instrumentation (#9504) +- feat(vue): Support Vue 3 lifecycle hooks in mixin options (#9578) +- fix(nextjs): Download CLI binary if it can't be found (#9584) +- ref: Deprecate `extractTraceParentData` from `@sentry/core` & downstream packages (#9158) +- ref(replay): Add further logging to network body parsing (#9566) + +Work in this release contributed by @snoozbuster. Thank you for your contribution! + +## 7.80.1 + +- fix(astro): Adjust Vite plugin config to upload server source maps (#9541) +- fix(nextjs): Add tracing extensions in all serverside wrappers (#9537) +- fix(nextjs): Fix serverside transaction names on Windows (#9526) +- fix(node): Fix tRPC middleware typing (#9540) +- fix(replay): Add additional safeguards for capturing network bodies (#9506) +- fix(tracing): Update prisma span to be `db.prisma` (#9512) + +## 7.80.0 + +- feat(astro): Add distributed tracing via `` tags (#9483) +- feat(node): Capture internal server errors in trpc middleware (#9482) +- feat(remix): Export a type to use for `MetaFunction` parameters (#9493) +- fix(astro): Mark SDK package as Astro-external (#9509) +- ref(nextjs): Don't initialize Server SDK during build (#9503) + +## 7.79.0 + +- feat(tracing): Add span `origin` to trace context (#9472) +- fix(deno): Emit .mjs files (#9485) +- fix(nextjs): Flush servercomponent events for edge (#9487) + +## 7.78.0 + +### Important Changes + +- **Replay Bundle Size improvements** + +We've dramatically decreased the bundle size of our Replay package, reducing the minified & gzipped bundle size by ~20 +KB! This was possible by extensive use of tree shaking and a host of small changes to reduce our footprint: + +- feat(replay): Update rrweb to 2.2.0 (#9414) +- ref(replay): Use fflate instead of pako for compression (#9436) + +By using [tree shaking](https://docs.sentry.io/platforms/javascript/configuration/tree-shaking/) it is possible to shave +up to 10 additional KB off the bundle. + +### Other Changes + +- feat(astro): Add Sentry middleware (#9445) +- feat(feedback): Add "outline focus" and "foreground hover" vars (#9462) +- feat(feedback): Add `openDialog` and `closeDialog` onto integration interface (#9464) +- feat(feedback): Implement new user feedback embeddable widget (#9217) +- feat(nextjs): Add automatic sourcemapping for edge part of the SDK (#9454) +- feat(nextjs): Add client routing instrumentation for app router (#9446) +- feat(node-experimental): Add hapi tracing support (#9449) +- feat(replay): Allow to configure `beforeErrorSampling` (#9470) +- feat(replay): Stop fixing truncated JSONs in SDK (#9437) +- fix(nextjs): Fix sourcemaps resolving for local dev when basePath is set (#9457) +- fix(nextjs): Only inject basepath in dev mode (#9465) +- fix(replay): Ensure we stop for rate limit headers (#9420) +- ref(feedback): Add treeshaking for logger statements (#9475) +- ref(replay): Use rrweb for slow click detection (#9408) +- build(polyfills): Remove output format specific logic (#9467) + +## 7.77.0 + +### Security Fixes + +- fix(nextjs): Match only numbers as orgid in tunnelRoute (#9416) (CVE-2023-46729) +- fix(nextjs): Strictly validate tunnel target parameters (#9415) (CVE-2023-46729) + +### Other Changes + +- feat: Move LinkedErrors integration to @sentry/core (#9404) +- feat(remix): Update sentry-cli version to ^2.21.2 (#9401) +- feat(replay): Allow to treeshake & configure compression worker URL (#9409) +- fix(angular-ivy): Adjust package entry points to support Angular 17 with SSR config (#9412) +- fix(feedback): Fixing feedback import (#9403) +- fix(utils): Avoid keeping a reference of last used event (#9387) + +## 7.76.0 + +### Important Changes + +- **feat(core): Add cron monitor wrapper helper (#9395)** + +This release adds `Sentry.withMonitor()`, a wrapping function that wraps a callback with a cron monitor that will +automatically report completions and failures: + +```ts +import * as Sentry from '@sentry/node'; + +// withMonitor() will send checkin when callback is started/finished +// works with async and sync callbacks. +const result = Sentry.withMonitor( + 'dailyEmail', + () => { + // withCheckIn return value is same return value here + return sendEmail(); + }, + // Optional upsert options + { + schedule: { + type: 'crontab', + value: '0 * * * *', + }, + // 🇨🇦🫡 + timezone: 'Canada/Eastern', + }, +); +``` + +### Other Changes + +- chore(angular-ivy): Allow Angular 17 in peer dependencies (#9386) +- feat(nextjs): Instrument SSR page components (#9346) +- feat(nextjs): Trace errors in page component SSR (#9388) +- fix(nextjs): Instrument route handlers with `jsx` and `tsx` file extensions (#9362) +- fix(nextjs): Trace with performance disabled (#9389) +- fix(replay): Ensure `replay_id` is not added to DSC if session expired (#9359) +- fix(replay): Remove unused parts of pako from build (#9369) +- fix(serverless): Don't mark all errors as unhandled (#9368) +- fix(tracing-internal): Fix case when middleware contain array of routes with special chars as @ (#9375) +- meta(nextjs): Bump peer deps for Next.js 14 (#9390) + +Work in this release contributed by @LubomirIgonda1. Thank you for your contribution! + +## 7.75.1 + +- feat(browser): Allow collecting of pageload profiles (#9317) +- fix(browser): Correct timestamp on pageload profiles (#9350) +- fix(nextjs): Use webpack plugin release value to inject release (#9348) + +## 7.75.0 + +### Important Changes + +- **feat(opentelemetry): Add new `@sentry/opentelemetry` package (#9238)** + +This release publishes a new package, `@sentry/opentelemetry`. This is a runtime agnostic replacement for +`@sentry/opentelemetry-node` and exports a couple of useful utilities which can be used to use Sentry together with +OpenTelemetry. + +You can read more about +[@sentry/opentelemetry in the Readme](https://github.com/getsentry/sentry-javascript/tree/develop/packages/opentelemetry). + +- **feat(replay): Allow to treeshake rrweb features (#9274)** + +Starting with this release, you can configure the following build-time flags in order to reduce the SDK bundle size: + +- `__RRWEB_EXCLUDE_CANVAS__` +- `__RRWEB_EXCLUDE_IFRAME__` +- `__RRWEB_EXCLUDE_SHADOW_DOM__` + +You can read more about +[tree shaking in our docs](https://docs.sentry.io/platforms/javascript/configuration/tree-shaking/). + +### Other Changes + +- build(deno): Prepare Deno SDK for release on npm (#9281) +- feat: Remove tslib (#9299) +- feat(node): Add abnormal session support for ANR (#9268) +- feat(node): Remove `lru_map` dependency (#9300) +- feat(node): Vendor `cookie` module (#9308) +- feat(replay): Share performance instrumentation with tracing (#9296) +- feat(types): Add missing Profiling types (macho debug image, profile measurements, stack frame properties) (#9277) +- feat(types): Add statsd envelope types (#9304) +- fix(astro): Add integration default export to types entry point (#9337) +- fix(astro): Convert SDK init file import paths to POSIX paths (#9336) +- fix(astro): Make `Replay` and `BrowserTracing` integrations tree-shakeable (#9287) +- fix(integrations): Fix transaction integration (#9334) +- fix(nextjs): Restore `autoInstrumentMiddleware` functionality (#9323) +- fix(nextjs): Guard for case where `getInitialProps` may return undefined (#9342) +- fix(node-experimental): Make node-fetch support optional (#9321) +- fix(node): Check buffer length when attempting to parse ANR frame (#9314) +- fix(replay): Fix xhr start timestamps (#9341) +- fix(tracing-internal): Remove query params from urls with a trailing slash (#9328) +- fix(types): Remove typo with CheckInEnvelope (#9303) + +## 7.74.1 + +- chore(astro): Add `astro-integration` keyword (#9265) +- fix(core): Narrow filters for health check transactions (#9257) +- fix(nextjs): Fix HMR by inserting new entrypoints at the end (#9267) +- fix(nextjs): Fix resolution of request async storage module (#9259) +- fix(node-experimental): Guard against missing `fetch` (#9275) +- fix(remix): Update `defer` injection logic. (#9242) +- fix(tracing-internal): Parameterize express middleware parameters (#8668) +- fix(utils): Move Node specific ANR impl. out of utils (#9258) + +Work in this release contributed by @LubomirIgonda1. Thank you for your contribution! + +## 7.74.0 + +### Important Changes + +- **feat(astro): Add `sentryAstro` integration (#9218)** + +This Release introduces the first alpha version of our new SDK for Astro. At this time, the SDK is considered +experimental and things might break and change in future versions. + +The core of the SDK is an Astro integration which you easily add to your Astro config: + +```js +// astro.config.js +import { defineConfig } from 'astro/config'; +import sentry from '@sentry/astro'; + +export default defineConfig({ + integrations: [ + sentry({ + dsn: '__DSN__', + sourceMapsUploadOptions: { + project: 'astro', + authToken: process.env.SENTRY_AUTH_TOKEN, + }, + }), + ], +}); +``` + +Check out the [README](./packages/astro/README.md) for usage instructions and what to expect from this alpha release. + +### Other Changes + +- feat(core): Add `addIntegration` utility (#9186) +- feat(core): Add `continueTrace` method (#9164) +- feat(node-experimental): Add NodeFetch integration (#9226) +- feat(node-experimental): Use native OTEL Spans (#9161, #9214) +- feat(node-experimental): Sample in OTEL Sampler (#9203) +- feat(serverlesss): Allow disabling transaction traces (#9154) +- feat(tracing): Allow direct pg module to enable esbuild support (#9227) +- feat(utils): Move common node ANR code to utils (#9191) +- feat(vue): Expose `VueIntegration` to initialize vue app later (#9180) +- fix: Don't set `referrerPolicy` on serverside fetch transports (#9200) +- fix: Ensure we never mutate options passed to `init` (#9162) +- fix(ember): Avoid pulling in utils at build time (#9221) +- fix(ember): Drop undefined config values (#9175) +- fix(node): Ensure mysql integration works without callback (#9222) +- fix(node): Only require `inspector` when needed (#9149) +- fix(node): Remove ANR `debug` option and instead add logger.isEnabled() (#9230) +- fix(node): Strip `.mjs` and `.cjs` extensions from module name (#9231) +- fix(replay): bump rrweb to 2.0.1 (#9240) +- fix(replay): Fix potential broken CSS in styled-components (#9234) +- fix(sveltekit): Flush in server wrappers before exiting (#9153) +- fix(types): Update signature of `processEvent` integration hook (#9151) +- fix(utils): Dereference DOM events after they have servered their purpose (#9224) +- ref(integrations): Refactor pluggable integrations to use `processEvent` (#9021) +- ref(serverless): Properly deprecate `rethrowAfterCapture` option (#9159) +- ref(utils): Deprecate `walk` method (#9157) + +Work in this release contributed by @aldenquimby. Thank you for your contributions! + +## 7.73.0 + +### Important Changes + +- **feat(replay): Upgrade to rrweb2** + +This is fully backwards compatible with prior versions of the Replay SDK. The only breaking change that we will making +is to not be masking `aria-label` by default. The reason for this change is to align with our core SDK which also does +not mask `aria-label`. This change also enables better support of searching by clicks. + +Another change that needs to be highlighted is the 13% bundle size increase. This bundle size increase is necessary to +bring improved recording performance and improved replay fidelity, especially in regards to web components and iframes. +We will be investigating the reduction of the bundle size in +[this PR](https://github.com/getsentry/sentry-javascript/issues/8815). + +Here are benchmarks comparing the version 1 of rrweb to version 2 + +| metric | v1 | v2 | +| --------- | ---------- | ---------- | +| lcp | 1486.06 ms | 1529.11 ms | +| cls | 0.40 ms | 0.40 ms | +| fid | 1.53 ms | 1.50 ms | +| tbt | 3207.22 ms | 3036.80 ms | +| memoryAvg | 131.83 MB | 124.84 MB | +| memoryMax | 324.8 MB | 339.03 MB | +| netTx | 282.67 KB | 272.51 KB | +| netRx | 8.02 MB | 8.07 MB | + +### Other Changes + +- feat: Always assemble Envelopes (#9101) +- feat(node): Rate limit local variables for caught exceptions and enable `captureAllExceptions` by default (#9102) +- fix(core): Ensure `tunnel` is considered for `isSentryUrl` checks (#9130) +- fix(nextjs): Fix `RequestAsyncStorage` fallback path (#9126) +- fix(node-otel): Suppress tracing for generated sentry spans (#9142) +- fix(node): fill in span data from http request options object (#9112) +- fix(node): Fixes and improvements to ANR detection (#9128) +- fix(sveltekit): Avoid data invalidation in wrapped client-side `load` functions (#9071) +- ref(core): Refactor `InboundFilters` integration to use `processEvent` (#9020) +- ref(wasm): Refactor Wasm integration to use `processEvent` (#9019) + +Work in this release contributed by @vlad-zhukov. Thank you for your contribution! + +## 7.72.0 + +### Important Changes + +- **feat(node): App Not Responding with stack traces (#9079)** + +This release introduces support for Application Not Responding (ANR) errors for Node.js applications. These errors are +triggered when the Node.js main thread event loop of an application is blocked for more than five seconds. The Node SDK +reports ANR errors as Sentry events and can optionally attach a stacktrace of the blocking code to the ANR event. + +To enable ANR detection, import and use the `enableANRDetection` function from the `@sentry/node` package before you run +the rest of your application code. Any event loop blocking before calling `enableANRDetection` will not be detected by +the SDK. + +Example (ESM): + +```ts +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: '___PUBLIC_DSN___', + tracesSampleRate: 1.0, +}); + +await Sentry.enableANRDetection({ captureStackTrace: true }); +// Function that runs your app +runApp(); +``` + +Example (CJS): + +```ts +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: '___PUBLIC_DSN___', + tracesSampleRate: 1.0, +}); + +Sentry.enableANRDetection({ captureStackTrace: true }).then(() => { + // Function that runs your app + runApp(); +}); +``` + +### Other Changes + +- fix(nextjs): Filter `RequestAsyncStorage` locations by locations that webpack will resolve (#9114) +- fix(replay): Ensure `replay_id` is not captured when session is expired (#9109) + +## 7.71.0 + +- feat(bun): Instrument Bun.serve (#9080) +- fix(core): Ensure global event processors are always applied to event (#9064) +- fix(core): Run client eventProcessors before global ones (#9032) +- fix(nextjs): Use webpack module paths to attempt to resolve internal request async storage module (#9100) +- fix(react): Add actual error name to boundary error name (#9065) +- fix(react): Compare location against `basename`-prefixed route. (#9076) +- ref(browser): Refactor browser integrations to use `processEvent` (#9022) + +Work in this release contributed by @jorrit. Thank you for your contribution! + +## 7.70.0 + +### Important Changes + +- **feat: Add Bun SDK (#9029)** + +This release contains the beta version of `@sentry/bun`, our SDK for the [Bun JavaScript runtime](https://bun.sh/)! For +details on how to use it, please see the [README](./packages/bun/README.md). Any feedback/bug reports are greatly +appreciated, please [reach out on GitHub](https://github.com/getsentry/sentry-javascript/discussions/7979). + +Note that as of now the Bun runtime does not support global error handlers. This is being actively worked on, see +[the tracking issue in Bun's GitHub repo](https://github.com/oven-sh/bun/issues/5091). + +- **feat(remix): Add Remix 2.x release support. (#8940)** + +The Sentry Remix SDK now officially supports Remix v2! See +[our Remix docs for more details](https://docs.sentry.io/platforms/javascript/guides/remix/). + +### Other Changes + +- chore(node): Upgrade cookie to ^0.5.0 (#9013) +- feat(core): Introduce `processEvent` hook on `Integration` (#9017) +- feat(node): Improve non-error messages (#9026) +- feat(vercel-edge): Add Vercel Edge Runtime package (#9041) +- fix(remix): Use `React.ComponentType` instead of `React.FC` as `withSentry`'s generic type. (#9043) +- fix(replay): Ensure replay events go through `preprocessEvent` hook (#9034) +- fix(replay): Fix typo in Replay types (#9028) +- fix(sveltekit): Adjust `handleErrorWithSentry` type (#9054) +- fix(utils): Try-catch monkeypatching to handle frozen objects/functions (#9031) + +Work in this release contributed by @Dima-Dim, @krist7599555 and @lifeiscontent. Thank you for your contributions! + +Special thanks for @isaacharrisholt for helping us implement a Vercel Edge Runtime SDK which we use under the hood for +our Next.js SDK. + +## 7.69.0 + +### Important Changes + +- **New Performance APIs** + - feat: Update span performance API names (#8971) + - feat(core): Introduce startSpanManual (#8913) + +This release introduces a new set of top level APIs for the Performance Monitoring SDKs. These aim to simplify creating +spans and reduce the boilerplate needed for performance instrumentation. The three new methods introduced are +`Sentry.startSpan`, `Sentry.startInactiveSpan`, and `Sentry.startSpanManual`. These methods are available in the browser +and node SDKs. + +`Sentry.startSpan` wraps a callback in a span. The span is automatically finished when the callback returns. This is the +recommended way to create spans. + +```js +// Start a span that tracks the duration of expensiveFunction +const result = Sentry.startSpan({ name: 'important function' }, () => { + return expensiveFunction(); +}); + +// You can also mutate the span wrapping the callback to set data or status +Sentry.startSpan({ name: 'important function' }, span => { + // span is undefined if performance monitoring is turned off or if + // the span was not sampled. This is done to reduce overhead. + span?.setData('version', '1.0.0'); + return expensiveFunction(); +}); +``` + +If you don't want the span to finish when the callback returns, use `Sentry.startSpanManual` to control when the span is +finished. This is useful for event emitters or similar. + +```js +// Start a span that tracks the duration of middleware +function middleware(_req, res, next) { + return Sentry.startSpanManual({ name: 'middleware' }, (span, finish) => { + res.once('finish', () => { + setHttpStatus(span, res.status); + finish(); + }); + return next(); + }); +} +``` + +`Sentry.startSpan` and `Sentry.startSpanManual` create a span and make it active for the duration of the callback. Any +spans created while this active span is running will be added as a child span to it. If you want to create a span +without making it active, use `Sentry.startInactiveSpan`. This is useful for creating parallel spans that are not +related to each other. + +```js +const span1 = Sentry.startInactiveSpan({ name: 'span1' }); + +someWork(); + +const span2 = Sentry.startInactiveSpan({ name: 'span2' }); + +moreWork(); + +const span3 = Sentry.startInactiveSpan({ name: 'span3' }); + +evenMoreWork(); + +span1?.finish(); +span2?.finish(); +span3?.finish(); +``` + +### Other Changes + +- feat(core): Export `BeforeFinishCallback` type (#8999) +- build(eslint): Enforce that ts-expect-error is used (#8987) +- feat(integration): Ensure `LinkedErrors` integration runs before all event processors (#8956) +- feat(node-experimental): Keep breadcrumbs on transaction (#8967) +- feat(redux): Add 'attachReduxState' option (#8953) +- feat(remix): Accept `org`, `project` and `url` as args to upload script (#8985) +- fix(utils): Prevent iterating over VueViewModel (#8981) +- fix(utils): uuidv4 fix for cloudflare (#8968) +- fix(core): Always use event message and exception values for `ignoreErrors` (#8986) +- fix(nextjs): Add new potential location for Next.js request AsyncLocalStorage (#9006) +- fix(node-experimental): Ensure we only create HTTP spans when outgoing (#8966) +- fix(node-experimental): Ignore OPTIONS & HEAD requests (#9001) +- fix(node-experimental): Ignore outgoing Sentry requests (#8994) +- fix(node-experimental): Require parent span for `pg` spans (#8993) +- fix(node-experimental): Use Sentry logger as Otel logger (#8960) +- fix(node-otel): Refactor OTEL span reference cleanup (#9000) +- fix(react): Switch to props in `useRoutes` (#8998) +- fix(remix): Add `glob` to Remix SDK dependencies. (#8963) +- fix(replay): Ensure `handleRecordingEmit` aborts when event is not added (#8938) +- fix(replay): Fully stop & restart session when it expires (#8834) + +Work in this release contributed by @Duncanxyz and @malay44. Thank you for your contributions! + +## 7.68.0 + +- feat(browser): Add `BroadcastChannel` and `SharedWorker` to TryCatch EventTargets (#8943) +- feat(core): Add `name` to `Span` (#8949) +- feat(core): Add `ServerRuntimeClient` (#8930) +- fix(node-experimental): Ensure `span.finish()` works as expected (#8947) +- fix(remix): Add new sourcemap-upload script files to prepack assets. (#8948) +- fix(publish): Publish downleveled TS3.8 types and fix types path (#8954) + +## 7.67.0 + +### Important Changes + +- **feat: Mark errors caught by the SDK as unhandled** + - feat(browser): Mark errors caught from `TryCatch` integration as unhandled (#8890) + - feat(integrations): Mark errors caught from `HttpClient` and `CaptureConsole` integrations as unhandled (#8891) + - feat(nextjs): Mark errors caught from NextJS wrappers as unhandled (#8893) + - feat(react): Mark errors captured from ErrorBoundary as unhandled (#8914) + - feat(remix): Add debugid injection and map deletion to sourcemaps script (#8814) + - feat(remix): Mark errors caught from Remix instrumentation as unhandled (#8894) + - feat(serverless): Mark errors caught in Serverless handlers as unhandled (#8907) + - feat(vue): Mark errors caught by Vue wrappers as unhandled (#8905) + +This release fixes inconsistent behaviour of when our SDKs classify captured errors as unhandled. Previously, some of +our instrumentations correctly set unhandled, while others set handled. Going forward, all errors caught automatically +from our SDKs will be marked as unhandled. If you manually capture errors (e.g. by calling `Sentry.captureException`), +your errors will continue to be reported as handled. + +This change might lead to a decrease in reported crash-free sessions and consequently in your release health score. If +you have concerns about this, feel free to open an issue. + +### Other Changes + +- feat(node-experimental): Implement new performance APIs (#8911) +- feat(node-experimental): Sync OTEL context with Sentry AsyncContext (#8797) +- feat(replay): Allow to configure `maxReplayDuration` (#8769) +- fix(browser): Add replay and profiling options to `BrowserClientOptions` (#8921) +- fix(browser): Check for existence of instrumentation targets (#8939) +- fix(nextjs): Don't re-export default in route handlers (#8924) +- fix(node): Improve mysql integration (#8923) +- fix(remix): Guard against missing default export for server instrument (#8909) +- ref(browser): Deprecate top-level `wrap` function (#8927) +- ref(node-otel): Avoid exporting internals & refactor attribute adding (#8920) + +Work in this release contributed by @SorsOps. Thank you for your contribution! + +## 7.66.0 + +- fix: Defer tracing decision to downstream SDKs when using SDK without performance (#8839) +- fix(nextjs): Fix `package.json` exports (#8895) +- fix(sveltekit): Ensure target file exists before applying auto instrumentation (#8881) +- ref: Use consistent console instrumentation (#8879) +- ref(browser): Refactor sentry breadcrumb to use hook (#8892) +- ref(tracing): Add `origin` to spans (#8765) + +## 7.65.0 + +- build: Remove build-specific polyfills (#8809) +- build(deps): bump protobufjs from 6.11.3 to 6.11.4 (#8822) +- deps(sveltekit): Bump `@sentry/vite-plugin` (#8877) +- feat(core): Introduce `Sentry.startActiveSpan` and `Sentry.startSpan` (#8803) +- fix: Memoize `AsyncLocalStorage` instance (#8831) +- fix(nextjs): Check for validity of API route handler signature (#8811) +- fix(nextjs): Fix `requestAsyncStorageShim` path resolution on windows (#8875) +- fix(node): Log entire error object in `OnUncaughtException` (#8876) +- fix(node): More relevant warning message when tracing extensions are missing (#8820) +- fix(replay): Streamline session creation/refresh (#8813) +- fix(sveltekit): Avoid invalidating data on route changes in `wrapServerLoadWithSentry` (#8801) +- fix(tracing): Better guarding for performance observer (#8872) +- ref(sveltekit): Remove custom client fetch instrumentation and use default instrumentation (#8802) +- ref(tracing-internal): Deprecate `tracePropagationTargets` in `BrowserTracing` (#8874) + +## 7.64.0 + +- feat(core): Add setMeasurement export (#8791) +- fix(nextjs): Check for existence of default export when wrapping pages (#8794) +- fix(nextjs): Ensure imports are valid relative paths (#8799) +- fix(nextjs): Only re-export default export if it exists (#8800) + +## 7.63.0 + +- build(deps): bump @opentelemetry/instrumentation from 0.41.0 to 0.41.2 +- feat(eventbuilder): Export `exceptionFromError` for use in hybrid SDKs (#8766) +- feat(node-experimental): Re-export from node (#8786) +- feat(tracing): Add db connection attributes for mysql spans (#8775) +- feat(tracing): Add db connection attributes for postgres spans (#8778) +- feat(tracing): Improve data collection for mongodb spans (#8774) +- fix(nextjs): Execute sentry config independently of `autoInstrumentServerFunctions` and `autoInstrumentAppDirectory` + (#8781) +- fix(replay): Ensure we do not flush if flush took too long (#8784) +- fix(replay): Ensure we do not try to flush when we force stop replay (#8783) +- fix(replay): Fix `hasCheckout` handling (#8782) +- fix(replay): Handle multiple clicks in a short time (#8773) +- ref(replay): Skip events being added too long after initial segment (#8768) + +## 7.62.0 + +### Important Changes + +- **feat(integrations): Add `ContextLines` integration for html-embedded JS stack frames (#8699)** + +This release adds the `ContextLines` integration as an optional integration for the Browser SDKs to +`@sentry/integrations`. + +This integration adds source code from inline JavaScript of the current page's HTML (e.g. JS in ` + + + diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/keepSentryGlobal/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/keepSentryGlobal/test.ts new file mode 100644 index 000000000000..e4a2bf12e829 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/keepSentryGlobal/test.ts @@ -0,0 +1,24 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; + +sentryTest('keeps data on window.Sentry intact', async ({ getLocalTestUrl, page }) => { + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForErrorRequestOnUrl(page, url); + + const eventData = envelopeRequestParser(req); + + expect(eventData.message).toBe('Test exception'); + + const customThingy = await page.evaluate('window.Sentry._customThingOnSentry'); + expect(customThingy).toBe('customThingOnSentry'); +}); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/init.js b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/init.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/subject.js b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/subject.js new file mode 100644 index 000000000000..1dcef58798cc --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/subject.js @@ -0,0 +1,7 @@ +Sentry.forceLoad(); + +setTimeout(() => { + Sentry.onLoad(function () { + Sentry.captureException('Test exception'); + }); +}, 200); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/test.ts new file mode 100644 index 000000000000..46bbf81f3c58 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/test.ts @@ -0,0 +1,21 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; + +sentryTest('late onLoad call is handled', async ({ getLocalTestUrl, page }) => { + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForErrorRequestOnUrl(page, url); + + const eventData = envelopeRequestParser(req); + + expect(eventData.message).toBe('Test exception'); +}); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/pageloadTransaction/init.js b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/pageloadTransaction/init.js new file mode 100644 index 000000000000..7c0fceed58a4 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/pageloadTransaction/init.js @@ -0,0 +1,5 @@ +window._testBaseTimestamp = performance.timeOrigin / 1000; + +Sentry.onLoad(function () { + Sentry.init({}); +}); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/pageloadTransaction/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/pageloadTransaction/test.ts new file mode 100644 index 000000000000..410cf5e72382 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/pageloadTransaction/test.ts @@ -0,0 +1,28 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { + envelopeRequestParser, + shouldSkipTracingTest, + waitForTransactionRequestOnUrl, +} from '../../../../utils/helpers'; + +sentryTest('should create a pageload transaction', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForTransactionRequestOnUrl(page, url); + + const eventData = envelopeRequestParser(req); + const timeOrigin = await page.evaluate('window._testBaseTimestamp'); + + const { start_timestamp: startTimestamp } = eventData; + + expect(startTimestamp).toBeCloseTo(timeOrigin, 1); + + expect(eventData.contexts?.trace?.op).toBe('pageload'); + expect(eventData.spans?.length).toBeGreaterThan(0); + expect(eventData.transaction_info?.source).toEqual('url'); +}); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/init.js b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/init.js new file mode 100644 index 000000000000..e63705186b2f --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/init.js @@ -0,0 +1,3 @@ +Sentry.onLoad(function () { + Sentry.init({}); +}); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/test.ts new file mode 100644 index 000000000000..c162db40a9e5 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/test.ts @@ -0,0 +1,28 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getReplayEvent, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers'; + +sentryTest('should capture a replay', async ({ getLocalTestUrl, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const req = waitForReplayRequest(page); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const eventData = getReplayEvent(await req); + + expect(eventData).toBeDefined(); + expect(eventData.segment_id).toBe(0); +}); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/init.js b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/init.js new file mode 100644 index 000000000000..e599fa75bd0a --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/init.js @@ -0,0 +1 @@ +// we define sentryOnLoad in template diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/subject.js b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/subject.js new file mode 100644 index 000000000000..fb0796f7f299 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/subject.js @@ -0,0 +1 @@ +Sentry.captureException('Test exception'); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/template.html b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/template.html new file mode 100644 index 000000000000..ed23488f1bf3 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/template.html @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/test.ts new file mode 100644 index 000000000000..eea57a33e738 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoad/test.ts @@ -0,0 +1,15 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; + +sentryTest('sentryOnLoad callback is called before Sentry.onLoad()', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForErrorRequestOnUrl(page, url); + + const eventData = envelopeRequestParser(req); + + expect(eventData.message).toBe('Test exception'); + + expect(await page.evaluate('Sentry.getClient().getOptions().tracesSampleRate')).toEqual(0.123); +}); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/init.js b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/init.js new file mode 100644 index 000000000000..422f4ca103df --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/init.js @@ -0,0 +1,4 @@ +Sentry.onLoad(function () { + // this should be called _after_ window.sentryOnLoad + Sentry.captureException(`Test exception: ${Sentry.getClient().getOptions().tracesSampleRate}`); +}); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/subject.js b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/subject.js new file mode 100644 index 000000000000..fb0796f7f299 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/subject.js @@ -0,0 +1 @@ +Sentry.captureException('Test exception'); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/template.html b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/template.html new file mode 100644 index 000000000000..ed23488f1bf3 --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/template.html @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/test.ts new file mode 100644 index 000000000000..d753a69b2a0c --- /dev/null +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/sentryOnLoadAndOnLoad/test.ts @@ -0,0 +1,15 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; + +sentryTest('sentryOnLoad callback is used', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForErrorRequestOnUrl(page, url); + + const eventData = envelopeRequestParser(req); + + expect(eventData.message).toBe('Test exception: 0.123'); + + expect(await page.evaluate('Sentry.getClient().getOptions().tracesSampleRate')).toEqual(0.123); +}); diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json new file mode 100644 index 000000000000..d08f0453c888 --- /dev/null +++ b/dev-packages/browser-integration-tests/package.json @@ -0,0 +1,67 @@ +{ + "name": "@sentry-internal/browser-integration-tests", + "version": "7.119.1", + "main": "index.js", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "private": true, + "scripts": { + "clean": "rimraf -g suites/**/dist loader-suites/**/dist tmp", + "install-browsers": "npx playwright install --with-deps", + "lint": "eslint . --format stylish", + "fix": "eslint . --format stylish --fix", + "type-check": "tsc", + "postinstall": "yarn install-browsers", + "pretest": "yarn clean && yarn type-check", + "test": "yarn test:all --project='chromium'", + "test:all": "npx playwright test -c playwright.browser.config.ts", + "test:bundle:es5": "PW_BUNDLE=bundle_es5 yarn test", + "test:bundle:es5:min": "PW_BUNDLE=bundle_es5_min yarn test", + "test:bundle:es6": "PW_BUNDLE=bundle_es6 yarn test", + "test:bundle:es6:min": "PW_BUNDLE=bundle_es6_min yarn test", + "test:bundle:replay:es6": "PW_BUNDLE=bundle_replay_es6 yarn test", + "test:bundle:replay:es6:min": "PW_BUNDLE=bundle_replay_es6_min yarn test", + "test:bundle:tracing:es5": "PW_BUNDLE=bundle_tracing_es5 yarn test", + "test:bundle:tracing:es5:min": "PW_BUNDLE=bundle_tracing_es5_min yarn test", + "test:bundle:tracing:es6": "PW_BUNDLE=bundle_tracing_es6 yarn test", + "test:bundle:tracing:es6:min": "PW_BUNDLE=bundle_tracing_es6_min yarn test", + "test:bundle:tracing:replay:es6": "PW_BUNDLE=bundle_tracing_replay_es6 yarn test", + "test:bundle:tracing:replay:es6:min": "PW_BUNDLE=bundle_tracing_replay_es6_min yarn test", + "test:cjs": "PW_BUNDLE=cjs yarn test", + "test:esm": "PW_BUNDLE=esm yarn test", + "test:loader": "npx playwright test -c playwright.loader.config.ts --project='chromium'", + "test:loader:base": "PW_BUNDLE=loader_base yarn test:loader", + "test:loader:eager": "PW_BUNDLE=loader_eager yarn test:loader", + "test:loader:tracing": "PW_BUNDLE=loader_tracing yarn test:loader", + "test:loader:replay": "PW_BUNDLE=loader_replay yarn test:loader", + "test:loader:full": "PW_BUNDLE=loader_tracing_replay yarn test:loader", + "test:loader:debug": "PW_BUNDLE=loader_debug yarn test:loader", + "test:ci": "yarn test:all --reporter='line'", + "test:update-snapshots": "yarn test:all --update-snapshots", + "test:detect-flaky": "ts-node scripts/detectFlakyTests.ts", + "validate:es5": "es-check es5 'fixtures/loader.js'" + }, + "dependencies": { + "@babel/preset-typescript": "^7.16.7", + "@playwright/test": "^1.40.1", + "@sentry-internal/rrweb": "2.11.0", + "@sentry/browser": "7.119.1", + "@sentry/tracing": "7.119.1", + "axios": "1.6.0", + "babel-loader": "^8.2.2", + "html-webpack-plugin": "^5.5.0", + "pako": "^2.1.0", + "webpack": "^5.52.0" + }, + "devDependencies": { + "@types/glob": "8.0.0", + "@types/node": "^14.6.4", + "@types/pako": "^2.0.0", + "glob": "8.0.3" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/browser-integration-tests/playwright.browser.config.ts b/dev-packages/browser-integration-tests/playwright.browser.config.ts new file mode 100644 index 000000000000..dd48c8f54746 --- /dev/null +++ b/dev-packages/browser-integration-tests/playwright.browser.config.ts @@ -0,0 +1,9 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import CorePlaywrightConfig from './playwright.config'; + +const config: PlaywrightTestConfig = { + ...CorePlaywrightConfig, + testDir: './suites', +}; + +export default config; diff --git a/dev-packages/browser-integration-tests/playwright.config.ts b/dev-packages/browser-integration-tests/playwright.config.ts new file mode 100644 index 000000000000..91568658d316 --- /dev/null +++ b/dev-packages/browser-integration-tests/playwright.config.ts @@ -0,0 +1,32 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + retries: 0, + // Run tests inside of a single file in parallel + fullyParallel: true, + // Use 3 workers on CI, else use defaults (based on available CPU cores) + // Note that 3 is a random number selected to work well with our CI setup + workers: process.env.CI ? 3 : undefined, + testMatch: /test.ts/, + + projects: [ + { + name: 'chromium', + use: devices['Desktop Chrome'], + }, + { + name: 'webkit', + use: devices['Desktop Safari'], + }, + { + name: 'firefox', + grep: /@firefox/i, + use: devices['Desktop Firefox'], + }, + ], + + globalSetup: require.resolve('./playwright.setup.ts'), +}; + +export default config; diff --git a/dev-packages/browser-integration-tests/playwright.loader.config.ts b/dev-packages/browser-integration-tests/playwright.loader.config.ts new file mode 100644 index 000000000000..01a829fbba3a --- /dev/null +++ b/dev-packages/browser-integration-tests/playwright.loader.config.ts @@ -0,0 +1,9 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import CorePlaywrightConfig from './playwright.config'; + +const config: PlaywrightTestConfig = { + ...CorePlaywrightConfig, + testDir: './loader-suites', +}; + +export default config; diff --git a/dev-packages/browser-integration-tests/playwright.setup.ts b/dev-packages/browser-integration-tests/playwright.setup.ts new file mode 100644 index 000000000000..fe97c814199e --- /dev/null +++ b/dev-packages/browser-integration-tests/playwright.setup.ts @@ -0,0 +1,5 @@ +import setupStaticAssets from './utils/staticAssets'; + +export default function globalSetup(): Promise { + return setupStaticAssets(); +} diff --git a/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts b/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts new file mode 100644 index 000000000000..299ebb002692 --- /dev/null +++ b/dev-packages/browser-integration-tests/scripts/detectFlakyTests.ts @@ -0,0 +1,114 @@ +import * as childProcess from 'child_process'; +import * as path from 'path'; +import * as glob from 'glob'; + +async function run(): Promise { + let testPaths: string[] = []; + + const changedPaths: string[] = process.env.CHANGED_TEST_PATHS ? JSON.parse(process.env.CHANGED_TEST_PATHS) : []; + + if (changedPaths.length > 0) { + console.log(`Detected changed test paths: +${changedPaths.join('\n')} + +`); + + testPaths = getTestPaths().filter(p => changedPaths.some(changedPath => changedPath.includes(p))); + if (testPaths.length === 0) { + console.log('Could not find matching tests, aborting...'); + process.exit(1); + } + } + + let runCount: number; + if (process.env.TEST_RUN_COUNT === 'AUTO') { + // No test paths detected: run everything 5x + runCount = 5; + + if (testPaths.length > 0) { + // Run everything up to 100x, assuming that total runtime is less than 60min. + // We assume an average runtime of 3s per test, times 4 (for different browsers) = 12s per detected testPaths + // We want to keep overall runtime under 30min + const testCount = testPaths.length * 4; + const expectedRuntimePerTestPath = testCount * 3; + const expectedRuntime = Math.floor((30 * 60) / expectedRuntimePerTestPath); + runCount = Math.min(50, Math.max(expectedRuntime, 5)); + } + } else { + runCount = parseInt(process.env.TEST_RUN_COUNT || '10'); + } + + const cwd = path.join(__dirname, '../'); + + try { + await new Promise((resolve, reject) => { + const cp = childProcess.spawn( + `npx playwright test ${ + testPaths.length ? testPaths.join(' ') : './suites' + } --reporter='line' --repeat-each ${runCount}`, + { shell: true, cwd }, + ); + + let error: Error | undefined; + + cp.stdout.on('data', data => { + console.log(data ? (data as object).toString() : ''); + }); + + cp.stderr.on('data', data => { + console.log(data ? (data as object).toString() : ''); + }); + + cp.on('error', e => { + console.error(e); + error = e; + }); + + cp.on('close', status => { + const err = error || (status !== 0 ? new Error(`Process exited with status ${status}`) : undefined); + + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } catch (error) { + console.log(''); + console.log(''); + + console.error(`⚠️ Some tests failed.`); + console.error(error); + process.exit(1); + } + + console.log(''); + console.log(''); + console.log(`☑️ All tests passed.`); +} + +function getTestPaths(): string[] { + const paths = glob.sync('suites/**/test.{ts,js}', { + cwd: path.join(__dirname, '../'), + }); + + return paths.map(p => path.dirname(p)); +} + +function logError(error: unknown) { + if (process.env.CI) { + console.log('::group::Test failed'); + } else { + console.error(' ⚠️ Test failed:'); + } + + console.log((error as any).stdout); + console.log((error as any).stderr); + + if (process.env.CI) { + console.log('::endgroup::'); + } +} + +run(); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/init.js b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/init.js new file mode 100644 index 000000000000..e64314facd79 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/init.js @@ -0,0 +1,9 @@ +import { Feedback } from '@sentry-internal/feedback'; +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [new Feedback()], +}); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/template.html b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/template.html new file mode 100644 index 000000000000..57334d4ad2f1 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/template.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts new file mode 100644 index 000000000000..26ba665a418f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts @@ -0,0 +1,74 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, getEnvelopeType } from '../../../utils/helpers'; + +sentryTest('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => { + if (process.env.PW_BUNDLE) { + sentryTest.skip(); + } + + const feedbackRequestPromise = page.waitForResponse(res => { + const req = res.request(); + + const postData = req.postData(); + if (!postData) { + return false; + } + + try { + return getEnvelopeType(req) === 'feedback'; + } catch (err) { + return false; + } + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + await page.getByText('Report a Bug').click(); + expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + await page.getByLabel('Send Bug Report').click(); + + const feedbackEvent = envelopeRequestParser((await feedbackRequestPromise).request()); + expect(feedbackEvent).toEqual({ + type: 'feedback', + contexts: { + feedback: { + contact_email: 'janedoe@example.org', + message: 'my example feedback', + name: 'Jane Doe', + source: 'widget', + url: expect.stringContaining('/dist/index.html'), + }, + }, + level: 'info', + timestamp: expect.any(Number), + event_id: expect.stringMatching(/\w{32}/), + environment: 'production', + sdk: { + integrations: expect.arrayContaining(['Feedback']), + version: expect.any(String), + name: 'sentry.javascript.browser', + packages: expect.anything(), + }, + request: { + url: expect.stringContaining('/dist/index.html'), + headers: { + 'User-Agent': expect.stringContaining(''), + }, + }, + platform: 'javascript', + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/init.js b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/init.js new file mode 100644 index 000000000000..9428907f51e7 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/init.js @@ -0,0 +1,18 @@ +import { Feedback } from '@sentry-internal/feedback'; +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + replaysOnErrorSampleRate: 1.0, + replaysSessionSampleRate: 0, + integrations: [ + new Sentry.Replay({ + flushMinDelay: 200, + flushMaxDelay: 200, + minReplayDuration: 0, + }), + new Feedback(), + ], +}); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts new file mode 100644 index 000000000000..6868caf99545 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts @@ -0,0 +1,107 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, getEnvelopeType } from '../../../../utils/helpers'; +import { getCustomRecordingEvents, getReplayEvent, waitForReplayRequest } from '../../../../utils/replayHelpers'; + +sentryTest( + 'should capture feedback (@sentry-internal/feedback import)', + async ({ forceFlushReplay, getLocalTestPath, page }) => { + if (process.env.PW_BUNDLE) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + const reqPromise2 = waitForReplayRequest(page, 2); + const feedbackRequestPromise = page.waitForResponse(res => { + const req = res.request(); + + const postData = req.postData(); + if (!postData) { + return false; + } + + try { + return getEnvelopeType(req) === 'feedback'; + } catch (err) { + return false; + } + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const [, , replayReq0] = await Promise.all([page.goto(url), page.getByText('Report a Bug').click(), reqPromise0]); + + // Inputs are slow, these need to be serial + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + + // Force flush here, as inputs are slow and can cause click event to be in unpredictable segments + await Promise.all([forceFlushReplay(), reqPromise1]); + + const [, feedbackResp, replayReq2] = await Promise.all([ + page.getByLabel('Send Bug Report').click(), + feedbackRequestPromise, + reqPromise2, + ]); + + const feedbackEvent = envelopeRequestParser(feedbackResp.request()); + const replayEvent = getReplayEvent(replayReq0); + // Feedback breadcrumb is on second segment because we flush when "Report a Bug" is clicked + // And then the breadcrumb is sent when feedback form is submitted + const { breadcrumbs } = getCustomRecordingEvents(replayReq2); + + expect(breadcrumbs).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + category: 'sentry.feedback', + data: { feedbackId: expect.any(String) }, + timestamp: expect.any(Number), + type: 'default', + }), + ]), + ); + + expect(feedbackEvent).toEqual({ + type: 'feedback', + breadcrumbs: expect.any(Array), + contexts: { + feedback: { + contact_email: 'janedoe@example.org', + message: 'my example feedback', + name: 'Jane Doe', + replay_id: replayEvent.event_id, + source: 'widget', + url: expect.stringContaining('/dist/index.html'), + }, + }, + level: 'info', + timestamp: expect.any(Number), + event_id: expect.stringMatching(/\w{32}/), + environment: 'production', + sdk: { + integrations: expect.arrayContaining(['Feedback']), + version: expect.any(String), + name: 'sentry.javascript.browser', + packages: expect.anything(), + }, + request: { + url: expect.stringContaining('/dist/index.html'), + headers: { + 'User-Agent': expect.stringContaining(''), + }, + }, + platform: 'javascript', + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/template.html b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/template.html new file mode 100644 index 000000000000..57334d4ad2f1 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/template.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts new file mode 100644 index 000000000000..1590c68652a3 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts @@ -0,0 +1,90 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, getEnvelopeType } from '../../../utils/helpers'; +import { getCustomRecordingEvents, getReplayEvent, waitForReplayRequest } from '../../../utils/replayHelpers'; + +sentryTest('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => { + if (process.env.PW_BUNDLE) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const feedbackRequestPromise = page.waitForResponse(res => { + const req = res.request(); + + const postData = req.postData(); + if (!postData) { + return false; + } + + try { + return getEnvelopeType(req) === 'feedback'; + } catch (err) { + return false; + } + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + await page.getByText('Report a Bug').click(); + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + await page.getByLabel('Send Bug Report').click(); + + const [feedbackResp, replayReq] = await Promise.all([feedbackRequestPromise, reqPromise0]); + + const feedbackEvent = envelopeRequestParser(feedbackResp.request()); + const replayEvent = getReplayEvent(replayReq); + const { breadcrumbs } = getCustomRecordingEvents(replayReq); + + expect(breadcrumbs).toEqual( + expect.arrayContaining([ + { + category: 'sentry.feedback', + data: { feedbackId: expect.any(String) }, + }, + ]), + ); + + expect(feedbackEvent).toEqual({ + type: 'feedback', + contexts: { + feedback: { + contact_email: 'janedoe@example.org', + message: 'my example feedback', + name: 'Jane Doe', + replay_id: replayEvent.event_id, + source: 'widget', + url: expect.stringContaining('/dist/index.html'), + }, + }, + level: 'info', + timestamp: expect.any(Number), + event_id: expect.stringMatching(/\w{32}/), + environment: 'production', + sdk: { + integrations: expect.arrayContaining(['Feedback']), + version: expect.any(String), + name: 'sentry.javascript.browser', + packages: expect.anything(), + }, + request: { + url: expect.stringContaining('/dist/index.html'), + headers: { + 'User-Agent': expect.stringContaining(''), + }, + }, + platform: 'javascript', + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/template.html b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/template.html new file mode 100644 index 000000000000..e54da47ff09d --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/template.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/test.ts new file mode 100644 index 000000000000..a9f4a335c4e4 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/click/test.ts @@ -0,0 +1,94 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; + +sentryTest('captures Breadcrumb for clicks & debounces them for a second', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 200, + body: JSON.stringify({ + userNames: ['John', 'Jane'], + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + await page.locator('#button1').click(); + + // not debounced because other target + await page.locator('#button2').click(); + // This should be debounced + await page.locator('#button2').click(); + + // Wait a second for the debounce to finish + await page.waitForTimeout(1000); + await page.locator('#button2').click(); + + const [eventData] = await Promise.all([promise, page.evaluate('Sentry.captureException("test exception")')]); + + expect(eventData.exception?.values).toHaveLength(1); + + expect(eventData.breadcrumbs).toEqual([ + { + timestamp: expect.any(Number), + category: 'ui.click', + message: 'body > button#button1[type="button"]', + }, + { + timestamp: expect.any(Number), + category: 'ui.click', + message: 'body > button#button2[type="button"]', + }, + { + timestamp: expect.any(Number), + category: 'ui.click', + message: 'body > button#button2[type="button"]', + }, + ]); +}); + +sentryTest( + 'uses the annotated component name in the breadcrumb messages and adds it to the data object', + async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 200, + body: JSON.stringify({ + userNames: ['John', 'Jane'], + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + await page.locator('#annotated-button').click(); + await page.evaluate('Sentry.captureException("test exception")'); + + const eventData = await promise; + + expect(eventData.breadcrumbs).toEqual([ + { + timestamp: expect.any(Number), + category: 'ui.click', + message: 'body > AnnotatedButton', + data: { 'ui.component_name': 'AnnotatedButton' }, + }, + ]); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/init.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/init.js new file mode 100644 index 000000000000..9bd2d9649ed8 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + defaultIntegrations: false, + integrations: [new Sentry.Integrations.Breadcrumbs()], + sampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/textInput/template.html b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/textInput/template.html new file mode 100644 index 000000000000..a16ca41e45da --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/textInput/template.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/textInput/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/textInput/test.ts new file mode 100644 index 000000000000..b4549a105c7a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/textInput/test.ts @@ -0,0 +1,100 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; + +sentryTest('captures Breadcrumb for events on inputs & debounced them', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 200, + body: JSON.stringify({ + userNames: ['John', 'Jane'], + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + // Not debounced because other event type + await page.locator('#input1').pressSequentially('John', { delay: 1 }); + + // This should be debounced + await page.locator('#input1').pressSequentially('Abby', { delay: 1 }); + + // not debounced because other target + await page.locator('#input2').pressSequentially('Anne', { delay: 1 }); + + // Wait a second for the debounce to finish + await page.waitForTimeout(1000); + await page.locator('#input2').pressSequentially('John', { delay: 1 }); + + await page.evaluate('Sentry.captureException("test exception")'); + + const eventData = await promise; + + expect(eventData.exception?.values).toHaveLength(1); + + expect(eventData.breadcrumbs).toEqual([ + { + timestamp: expect.any(Number), + category: 'ui.input', + message: 'body > input#input1[type="text"]', + }, + { + timestamp: expect.any(Number), + category: 'ui.input', + message: 'body > input#input2[type="text"]', + }, + { + timestamp: expect.any(Number), + category: 'ui.input', + message: 'body > input#input2[type="text"]', + }, + ]); +}); + +sentryTest( + 'includes the annotated component name within the breadcrumb message and data', + async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 200, + body: JSON.stringify({ + userNames: ['John', 'Jane'], + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + await page.locator('#annotated-input').pressSequentially('John', { delay: 1 }); + + await page.evaluate('Sentry.captureException("test exception")'); + const eventData = await promise; + expect(eventData.exception?.values).toHaveLength(1); + + expect(eventData.breadcrumbs).toEqual([ + { + timestamp: expect.any(Number), + category: 'ui.input', + message: 'body > AnnotatedInput', + data: { 'ui.component_name': 'AnnotatedInput' }, + }, + ]); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js new file mode 100644 index 000000000000..fc9ffd720768 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js @@ -0,0 +1,5 @@ +const xhr = new XMLHttpRequest(); + +fetch('http://localhost:7654/foo').then(() => { + Sentry.captureException('test error'); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts new file mode 100644 index 000000000000..f4e173940bc4 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts @@ -0,0 +1,37 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; + +sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 200, + body: JSON.stringify({ + userNames: ['John', 'Jane'], + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.exception?.values).toHaveLength(1); + + expect(eventData?.breadcrumbs?.length).toBe(1); + expect(eventData!.breadcrumbs![0]).toEqual({ + timestamp: expect.any(Number), + category: 'fetch', + type: 'http', + data: { + method: 'GET', + status_code: 200, + url: 'http://localhost:7654/foo', + }, + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/init.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/init.js new file mode 100644 index 000000000000..9bd2d9649ed8 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + defaultIntegrations: false, + integrations: [new Sentry.Integrations.Breadcrumbs()], + sampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js new file mode 100644 index 000000000000..595e9395aa80 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js @@ -0,0 +1,13 @@ +const xhr = new XMLHttpRequest(); + +fetch('http://localhost:7654/foo', { + method: 'POST', + body: '{"my":"body"}', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Cache: 'no-cache', + }, +}).then(() => { + Sentry.captureException('test error'); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts new file mode 100644 index 000000000000..9267c1099bfd --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts @@ -0,0 +1,37 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; + +sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 200, + body: JSON.stringify({ + userNames: ['John', 'Jane'], + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.exception?.values).toHaveLength(1); + + expect(eventData?.breadcrumbs?.length).toBe(1); + expect(eventData!.breadcrumbs![0]).toEqual({ + timestamp: expect.any(Number), + category: 'fetch', + type: 'http', + data: { + method: 'POST', + status_code: 200, + url: 'http://localhost:7654/foo', + }, + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/subject.js new file mode 100644 index 000000000000..f95bcb45c166 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/subject.js @@ -0,0 +1,10 @@ +const xhr = new XMLHttpRequest(); + +xhr.open('GET', 'http://localhost:7654/foo'); +xhr.send(); + +xhr.addEventListener('readystatechange', function () { + if (xhr.readyState === 4) { + Sentry.captureException('test error'); + } +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts new file mode 100644 index 000000000000..858392b38444 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts @@ -0,0 +1,38 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; + +sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 200, + body: JSON.stringify({ + userNames: ['John', 'Jane'], + }), + headers: { + 'Content-Type': 'application/json', + 'Content-Length': '', + }, + }); + }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.exception?.values).toHaveLength(1); + + expect(eventData?.breadcrumbs?.length).toBe(1); + expect(eventData!.breadcrumbs![0]).toEqual({ + timestamp: expect.any(Number), + category: 'xhr', + type: 'http', + data: { + method: 'GET', + status_code: 200, + url: 'http://localhost:7654/foo', + }, + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/init.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/init.js new file mode 100644 index 000000000000..9bd2d9649ed8 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + defaultIntegrations: false, + integrations: [new Sentry.Integrations.Breadcrumbs()], + sampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/subject.js new file mode 100644 index 000000000000..f8779b681d58 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/subject.js @@ -0,0 +1,12 @@ +const xhr = new XMLHttpRequest(); + +xhr.open('POST', 'http://localhost:7654/foo'); +xhr.setRequestHeader('Accept', 'application/json'); +xhr.setRequestHeader('Content-Type', 'application/json'); +xhr.send('{"my":"body"}'); + +xhr.addEventListener('readystatechange', function () { + if (xhr.readyState === 4) { + Sentry.captureException('test error'); + } +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts new file mode 100644 index 000000000000..8df0e468a12b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts @@ -0,0 +1,37 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest } from '../../../../../utils/helpers'; + +sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('**/foo', route => { + return route.fulfill({ + status: 200, + body: JSON.stringify({ + userNames: ['John', 'Jane'], + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.exception?.values).toHaveLength(1); + + expect(eventData?.breadcrumbs?.length).toBe(1); + expect(eventData!.breadcrumbs![0]).toEqual({ + timestamp: expect.any(Number), + category: 'xhr', + type: 'http', + data: { + method: 'POST', + status_code: 200, + url: 'http://localhost:7654/foo', + }, + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js new file mode 100644 index 000000000000..4461826e0214 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/init.js @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/browser'; +import { ContextLines } from '@sentry/integrations'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [new ContextLines()], +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/inline/template.html b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/inline/template.html new file mode 100644 index 000000000000..acb69e682644 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/inline/template.html @@ -0,0 +1,12 @@ + + + + + + + + +
+ Some text... + + diff --git a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/inline/test.ts b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/inline/test.ts new file mode 100644 index 000000000000..157e4d163067 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/inline/test.ts @@ -0,0 +1,53 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; + +sentryTest( + 'should add source context lines around stack frames from errors in Html inline JS', + async ({ getLocalTestPath, page, browserName }) => { + if (browserName === 'webkit') { + // The error we're throwing in this test is thrown as "Script error." in Webkit. + // We filter "Script error." out by default in `InboundFilters`. + // I don't think there's much value to disable InboundFilters defaults for this test, + // given that most of our users won't do that either. + // Let's skip it instead for Webkit. + sentryTest.skip(); + } + + const url = await getLocalTestPath({ testDir: __dirname }); + + const eventReqPromise = waitForErrorRequestOnUrl(page, url); + + const clickPromise = page.locator('#inline-error-btn').click(); + + const [req] = await Promise.all([eventReqPromise, clickPromise]); + + const eventData = envelopeRequestParser(req); + + expect(eventData.exception?.values).toHaveLength(1); + + const exception = eventData.exception?.values?.[0]; + + expect(exception).toMatchObject({ + stacktrace: { + frames: [ + { + pre_context: ['', '', '', ' ', ' ', ' '], + context_line: + ' ', + post_context: [ + expect.stringContaining('', + ' Some text...', + ' ', + '', + '
', + '', + ], + }, + ], + }, + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/subject.js b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/subject.js new file mode 100644 index 000000000000..744649fb291c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/subject.js @@ -0,0 +1,3 @@ +document.getElementById('script-error-btn').addEventListener('click', () => { + throw new Error('Error without context lines'); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/template.html b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/template.html new file mode 100644 index 000000000000..80569790143a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/ContextLines/noAddedLines/template.html @@ -0,0 +1,12 @@ + + + + + + + + +